xref: /haiku/src/apps/terminal/TermWindow.cpp (revision 3369e03d5cde9709c8aa70c99bfe6ce24ba65bf9)
1 /*
2  * Copyright 2007-2015, Haiku, Inc. All rights reserved.
3  * Copyright (c) 2004 Daniel Furrer <assimil8or@users.sourceforge.net>
4  * Copyright (c) 2003-2004 Kian Duffy <myob@users.sourceforge.net>
5  * Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
6  *
7  * Distributed under the terms of the MIT license.
8  *
9  * Authors:
10  *		Kian Duffy, myob@users.sourceforge.net
11  *		Daniel Furrer, assimil8or@users.sourceforge.net
12  *		John Scipione, jscipione@gmail.com
13  *		Siarzhuk Zharski, zharik@gmx.li
14  */
15 
16 
17 #include "TermWindow.h"
18 
19 #include <new>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <strings.h>
23 #include <time.h>
24 
25 #include <Alert.h>
26 #include <Application.h>
27 #include <Catalog.h>
28 #include <CharacterSet.h>
29 #include <CharacterSetRoster.h>
30 #include <Clipboard.h>
31 #include <Dragger.h>
32 #include <File.h>
33 #include <FindDirectory.h>
34 #include <LayoutBuilder.h>
35 #include <LayoutUtils.h>
36 #include <Locale.h>
37 #include <Menu.h>
38 #include <MenuBar.h>
39 #include <MenuItem.h>
40 #include <Path.h>
41 #include <PopUpMenu.h>
42 #include <PrintJob.h>
43 #include <Rect.h>
44 #include <Roster.h>
45 #include <Screen.h>
46 #include <ScrollBar.h>
47 #include <ScrollView.h>
48 #include <String.h>
49 #include <UTF8.h>
50 
51 #include <AutoLocker.h>
52 
53 #include "ActiveProcessInfo.h"
54 #include "Arguments.h"
55 #include "AppearPrefView.h"
56 #include "FindWindow.h"
57 #include "Globals.h"
58 #include "PrefWindow.h"
59 #include "PrefHandler.h"
60 #include "SetTitleDialog.h"
61 #include "ShellParameters.h"
62 #include "TermConst.h"
63 #include "TermScrollView.h"
64 #include "TitlePlaceholderMapper.h"
65 
66 
67 const static int32 kTermViewOffset = 3;
68 
69 const static int32 kMinimumFontSize = 8;
70 const static int32 kMaximumFontSize = 36;
71 
72 // messages constants
73 static const uint32 kNewTab = 'NTab';
74 static const uint32 kCloseView = 'ClVw';
75 static const uint32 kCloseOtherViews = 'CloV';
76 static const uint32 kIncreaseFontSize = 'InFs';
77 static const uint32 kDecreaseFontSize = 'DcFs';
78 static const uint32 kSetActiveTab = 'STab';
79 static const uint32 kUpdateTitles = 'UPti';
80 static const uint32 kEditTabTitle = 'ETti';
81 static const uint32 kEditWindowTitle = 'EWti';
82 static const uint32 kTabTitleChanged = 'TTch';
83 static const uint32 kWindowTitleChanged = 'WTch';
84 static const uint32 kUpdateSwitchTerminalsMenuItem = 'Ustm';
85 
86 using namespace BPrivate ; // BCharacterSet stuff
87 
88 #undef B_TRANSLATION_CONTEXT
89 #define B_TRANSLATION_CONTEXT "Terminal TermWindow"
90 
91 // actually an arrow
92 #define UTF8_ENTER "\xe2\x86\xb5"
93 
94 
95 // #pragma mark - TermViewContainerView
96 
97 
98 class TermViewContainerView : public BView {
99 public:
100 	TermViewContainerView(TermView* termView)
101 		:
102 		BView(BRect(), "term view container", B_FOLLOW_ALL, 0),
103 		fTermView(termView)
104 	{
105 		termView->MoveTo(kTermViewOffset, kTermViewOffset);
106 		BRect frame(termView->Frame());
107 		ResizeTo(frame.right + kTermViewOffset, frame.bottom + kTermViewOffset);
108 		AddChild(termView);
109 	}
110 
111 	TermView* GetTermView() const	{ return fTermView; }
112 
113 	virtual void GetPreferredSize(float* _width, float* _height)
114 	{
115 		float width, height;
116 		fTermView->GetPreferredSize(&width, &height);
117 		*_width = width + 2 * kTermViewOffset;
118 		*_height = height + 2 * kTermViewOffset;
119 	}
120 
121 private:
122 	TermView*	fTermView;
123 };
124 
125 
126 // #pragma mark - SessionID
127 
128 
129 TermWindow::SessionID::SessionID(int32 id)
130 	:
131 	fID(id)
132 {
133 }
134 
135 
136 TermWindow::SessionID::SessionID(const BMessage& message, const char* field)
137 {
138 	if (message.FindInt32(field, &fID) != B_OK)
139 		fID = -1;
140 }
141 
142 
143 status_t
144 TermWindow::SessionID::AddToMessage(BMessage& message, const char* field) const
145 {
146 	return message.AddInt32(field, fID);
147 }
148 
149 
150 // #pragma mark - Session
151 
152 
153 struct TermWindow::Session {
154 	SessionID				id;
155 	int32					index;
156 	Title					title;
157 	TermViewContainerView*	containerView;
158 
159 	Session(SessionID id, int32 index, TermViewContainerView* containerView)
160 		:
161 		id(id),
162 		index(index),
163 		containerView(containerView)
164 	{
165 		title.title = B_TRANSLATE("Shell ");
166 		title.title << index;
167 		title.patternUserDefined = false;
168 	}
169 };
170 
171 
172 // #pragma mark - TermWindow
173 
174 
175 TermWindow::TermWindow(const BString& title, Arguments* args)
176 	:
177 	BWindow(BRect(0, 0, 0, 0), title, B_DOCUMENT_WINDOW,
178 		B_CURRENT_WORKSPACE | B_QUIT_ON_WINDOW_CLOSE),
179 	fTitleUpdateRunner(this, BMessage(kUpdateTitles), 1000000),
180 	fNextSessionID(0),
181 	fTabView(NULL),
182 	fMenuBar(NULL),
183 	fSwitchTerminalsMenuItem(NULL),
184 	fEncodingMenu(NULL),
185 	fPrintSettings(NULL),
186 	fPrefWindow(NULL),
187 	fFindPanel(NULL),
188 	fSavedFrame(0, 0, -1, -1),
189 	fSetWindowTitleDialog(NULL),
190 	fSetTabTitleDialog(NULL),
191 	fFindString(""),
192 	fFindNextMenuItem(NULL),
193 	fFindPreviousMenuItem(NULL),
194 	fFindSelection(false),
195 	fForwardSearch(false),
196 	fMatchCase(false),
197 	fMatchWord(false),
198 	fFullScreen(false)
199 {
200 	// register this terminal
201 	fTerminalRoster.Register(Team(), this);
202 	fTerminalRoster.SetListener(this);
203 	int32 id = fTerminalRoster.ID();
204 
205 	// apply the title settings
206 	fTitle.pattern = title;
207 	if (fTitle.pattern.Length() == 0) {
208 		fTitle.pattern = B_TRANSLATE_SYSTEM_NAME("Terminal");
209 
210 		if (id >= 0)
211 			fTitle.pattern << " " << id + 1;
212 
213 		fTitle.patternUserDefined = false;
214 	} else
215 		fTitle.patternUserDefined = true;
216 
217 	fTitle.title = fTitle.pattern;
218 	fTitle.pattern = title;
219 
220 	_TitleSettingsChanged();
221 
222 	// get the saved window position and workspaces
223 	BRect frame;
224 	uint32 workspaces;
225 	if (_LoadWindowPosition(&frame, &workspaces) == B_OK) {
226 		// make sure the window is still on screen
227 		// (for example if there was a resolution change)
228 		BRect screenFrame = BScreen(this).Frame();
229 		if (frame.Width() <= screenFrame.Width()
230 			&& frame.Height() <= screenFrame.Height())
231 			ResizeTo(frame.Width(), frame.Height());
232 
233 		MoveTo(frame.LeftTop());
234 		MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN);
235 
236 		SetWorkspaces(workspaces);
237 	} else {
238 		// use computed defaults
239 		int row = id / 16;
240 		int column = id % 16;
241 		int x = (column * 16) + (row * 64) + 50;
242 		int y = (column * 16) + 50;
243 
244 		MoveTo(x, y);
245 	}
246 
247 	// init the GUI and add a tab
248 	_InitWindow();
249 	_AddTab(args);
250 
251 	// Announce our window as no longer minimized. That's not true, since it's
252 	// still hidden at this point, but it will be shown very soon.
253 	fTerminalRoster.SetWindowInfo(false, Workspaces());
254 }
255 
256 
257 TermWindow::~TermWindow()
258 {
259 	fTerminalRoster.Unregister();
260 
261 	_FinishTitleDialog();
262 
263 	if (fPrefWindow)
264 		fPrefWindow->PostMessage(B_QUIT_REQUESTED);
265 
266 	if (fFindPanel && fFindPanel->Lock()) {
267 		fFindPanel->Quit();
268 		fFindPanel = NULL;
269 	}
270 
271 	PrefHandler::DeleteDefault();
272 
273 	for (int32 i = 0; Session* session = _SessionAt(i); i++)
274 		delete session;
275 }
276 
277 
278 void
279 TermWindow::SessionChanged()
280 {
281 	_UpdateSessionTitle(fTabView->Selection());
282 }
283 
284 
285 void
286 TermWindow::_InitWindow()
287 {
288 	// make menu bar
289 	_SetupMenu();
290 
291 	// shortcuts to switch tabs
292 	for (int32 i = 0; i < 9; i++) {
293 		BMessage* message = new BMessage(kSetActiveTab);
294 		message->AddInt32("index", i);
295 		AddShortcut('1' + i, B_COMMAND_KEY, message);
296 	}
297 
298 	AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
299 		new BMessage(MSG_MOVE_TAB_LEFT));
300 	AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
301 		new BMessage(MSG_MOVE_TAB_RIGHT));
302 
303 	BRect textFrame = Bounds();
304 	textFrame.top = fMenuBar->Bounds().bottom + 1.0;
305 
306 	fTabView = new SmartTabView(textFrame, "tab view", B_WIDTH_FROM_LABEL);
307 	fTabView->SetListener(this);
308 	AddChild(fTabView);
309 
310 	// Make the scroll view one pixel wider than the tab view container view, so
311 	// the scroll bar will look good.
312 	fTabView->SetInsets(0, 0, -1, 0);
313 }
314 
315 
316 bool
317 TermWindow::_CanClose(int32 index)
318 {
319 	bool warnOnExit = PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT);
320 
321 	if (!warnOnExit)
322 		return true;
323 
324 	uint32 busyProcessCount = 0;
325 	BString busyProcessNames;
326 		// all names, separated by "\n\t"
327 
328 	if (index != -1) {
329 		ShellInfo shellInfo;
330 		ActiveProcessInfo info;
331 		TermView* termView = _TermViewAt(index);
332 		if (termView->GetShellInfo(shellInfo)
333 			&& termView->GetActiveProcessInfo(info)
334 			&& (info.ID() != shellInfo.ProcessID()
335 				|| !shellInfo.IsDefaultShell())) {
336 			busyProcessCount++;
337 			busyProcessNames = info.Name();
338 		}
339 	} else {
340 		for (int32 i = 0; i < fSessions.CountItems(); i++) {
341 			ShellInfo shellInfo;
342 			ActiveProcessInfo info;
343 			TermView* termView = _TermViewAt(i);
344 			if (termView->GetShellInfo(shellInfo)
345 				&& termView->GetActiveProcessInfo(info)
346 				&& (info.ID() != shellInfo.ProcessID()
347 					|| !shellInfo.IsDefaultShell())) {
348 				if (++busyProcessCount > 1)
349 					busyProcessNames << "\n\t";
350 				busyProcessNames << info.Name();
351 			}
352 		}
353 	}
354 
355 	if (busyProcessCount == 0)
356 		return true;
357 
358 	BString alertMessage;
359 	if (busyProcessCount == 1) {
360 		// Only one pending process. Select the alert text depending on whether
361 		// the terminal will be closed.
362 		alertMessage = index == -1 || fSessions.CountItems() == 1
363 			? B_TRANSLATE("The process \"%1\" is still running.\n"
364 				"If you close the Terminal, the process will be killed.")
365 			: B_TRANSLATE("The process \"%1\" is still running.\n"
366 				"If you close the tab, the process will be killed.");
367 	} else {
368 		// multiple pending processes
369 		alertMessage = B_TRANSLATE(
370 			"The following processes are still running:\n\n"
371 			"\t%1\n\n"
372 			"If you close the Terminal, the processes will be killed.");
373 	}
374 
375 	alertMessage.ReplaceFirst("%1", busyProcessNames);
376 
377 	BAlert* alert = new BAlert(B_TRANSLATE("Really close?"),
378 		alertMessage, B_TRANSLATE("Close"), B_TRANSLATE("Cancel"), NULL,
379 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
380 	alert->SetShortcut(1, B_ESCAPE);
381 	return alert->Go() == 0;
382 }
383 
384 
385 bool
386 TermWindow::QuitRequested()
387 {
388 	_FinishTitleDialog();
389 
390 	if (!_CanClose(-1))
391 		return false;
392 
393 	_SaveWindowPosition();
394 
395 	return BWindow::QuitRequested();
396 }
397 
398 
399 void
400 TermWindow::MenusBeginning()
401 {
402 	TermView* view = _ActiveTermView();
403 
404 	// Syncronize Encode Menu Pop-up menu and Preference.
405 	const BCharacterSet* charset
406 		= BCharacterSetRoster::GetCharacterSetByConversionID(view->Encoding());
407 	if (charset != NULL) {
408 		BString name(charset->GetPrintName());
409 		const char* mime = charset->GetMIMEName();
410 		if (mime)
411 			name << " (" << mime << ")";
412 
413 		BMenuItem* item = fEncodingMenu->FindItem(name);
414 		if (item != NULL)
415 			item->SetMarked(true);
416 	}
417 
418 	BFont font;
419 	view->GetTermFont(&font);
420 
421 	float size = font.Size();
422 
423 	fDecreaseFontSizeMenuItem->SetEnabled(size > kMinimumFontSize);
424 	fIncreaseFontSizeMenuItem->SetEnabled(size < kMaximumFontSize);
425 
426 	BWindow::MenusBeginning();
427 }
428 
429 
430 /* static */ void
431 TermWindow::MakeEncodingMenu(BMenu* menu)
432 {
433 	BCharacterSetRoster roster;
434 	BCharacterSet charset;
435 	while (roster.GetNextCharacterSet(&charset) == B_OK) {
436 		int encoding = M_UTF8;
437 		const char* mime = charset.GetMIMEName();
438 		if (mime == NULL || strcasecmp(mime, "UTF-8") != 0)
439 			encoding = charset.GetConversionID();
440 
441 		// filter out currently (???) not supported USC-2 and UTF-16
442 		if (encoding == B_UTF16_CONVERSION || encoding == B_UNICODE_CONVERSION)
443 			continue;
444 
445 		BString name(charset.GetPrintName());
446 		if (mime)
447 			name << " (" << mime << ")";
448 
449 		BMessage *message = new BMessage(MENU_ENCODING);
450 		if (message != NULL) {
451 			message->AddInt32("op", (int32)encoding);
452 			menu->AddItem(new BMenuItem(name, message));
453 		}
454 	}
455 
456 	menu->SetRadioMode(true);
457 }
458 
459 
460 void
461 TermWindow::_SetupMenu()
462 {
463 	fFontSizeMenu = _MakeFontSizeMenu(MSG_HALF_SIZE_CHANGED,
464 		PrefHandler::Default()->getInt32(PREF_HALF_FONT_SIZE));
465 	fIncreaseFontSizeMenuItem = new BMenuItem(B_TRANSLATE("Increase"),
466 		new BMessage(kIncreaseFontSize), '+', B_COMMAND_KEY);
467 	fDecreaseFontSizeMenuItem = new BMenuItem(B_TRANSLATE("Decrease"),
468 		new BMessage(kDecreaseFontSize), '-', B_COMMAND_KEY);
469 	fFontSizeMenu->AddSeparatorItem();
470 	fFontSizeMenu->AddItem(fIncreaseFontSizeMenuItem);
471 	fFontSizeMenu->AddItem(fDecreaseFontSizeMenuItem);
472 
473 	BMenu* windowSize = new(std::nothrow) BMenu(B_TRANSLATE("Window size"));
474 	if (windowSize != NULL) {
475 		MakeWindowSizeMenu(windowSize);
476 		windowSize->AddSeparatorItem();
477 		windowSize->AddItem(new BMenuItem(B_TRANSLATE("Full screen"),
478 			new BMessage(FULLSCREEN), B_ENTER));
479 	}
480 
481 	fEncodingMenu = new(std::nothrow) BMenu(B_TRANSLATE("Text encoding"));
482 	if (fEncodingMenu != NULL)
483 		MakeEncodingMenu(fEncodingMenu);
484 
485 	BLayoutBuilder::Menu<>(fMenuBar = new BMenuBar(Bounds(), "mbar"))
486 		// Terminal
487 		.AddMenu(B_TRANSLATE_COMMENT("Terminal", "The title for the main window"
488 				" menubar entry related to terminal sessions"))
489 			.AddItem(B_TRANSLATE("Switch Terminals"), MENU_SWITCH_TERM, B_TAB)
490 				.GetItem(fSwitchTerminalsMenuItem)
491 			.AddItem(B_TRANSLATE("New Terminal"), MENU_NEW_TERM, 'N')
492 			.AddItem(B_TRANSLATE("New tab"), kNewTab, 'T')
493 			.AddSeparator()
494 			.AddItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), MENU_PAGE_SETUP)
495 			.AddItem(B_TRANSLATE("Print"), MENU_PRINT, 'P')
496 			.AddSeparator()
497 			.AddItem(B_TRANSLATE("Close window"), B_QUIT_REQUESTED, 'W',
498 				B_SHIFT_KEY)
499 			.AddItem(B_TRANSLATE("Close active tab"), kCloseView, 'W')
500 			.AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q')
501 		.End()
502 
503 		// Edit
504 		.AddMenu(B_TRANSLATE("Edit"))
505 			.AddItem(B_TRANSLATE("Copy"), B_COPY, 'C')
506 			.AddItem(B_TRANSLATE("Paste"), B_PASTE, 'V')
507 			.AddSeparator()
508 			.AddItem(B_TRANSLATE("Select all"), B_SELECT_ALL, 'A')
509 			.AddItem(B_TRANSLATE("Clear all"), MENU_CLEAR_ALL, 'L')
510 			.AddSeparator()
511 			.AddItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), MENU_FIND_STRING, 'F')
512 			.AddItem(B_TRANSLATE("Find previous"), MENU_FIND_PREVIOUS, 'G',
513 					B_SHIFT_KEY)
514 				.GetItem(fFindPreviousMenuItem)
515 				.SetEnabled(false)
516 			.AddItem(B_TRANSLATE("Find next"), MENU_FIND_NEXT, 'G')
517 				.GetItem(fFindNextMenuItem)
518 				.SetEnabled(false)
519 		.End()
520 
521 		// Settings
522 		.AddMenu(B_TRANSLATE("Settings"))
523 			.AddItem(B_TRANSLATE("Window title" B_UTF8_ELLIPSIS),
524 				kEditWindowTitle)
525 			.AddItem(windowSize)
526 			.AddItem(fEncodingMenu)
527 			.AddItem(fFontSizeMenu)
528 			.AddItem(B_TRANSLATE("Save as default"), MSG_SAVE_AS_DEFAULT)
529 			.AddSeparator()
530 			.AddItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), MENU_PREF_OPEN)
531 		.End();
532 
533 	AddChild(fMenuBar);
534 
535 	_UpdateSwitchTerminalsMenuItem();
536 
537 #ifdef USE_DEBUG_SNAPSHOTS
538 	AddShortcut('S', B_COMMAND_KEY | B_CONTROL_KEY,
539 		new BMessage(SHORTCUT_DEBUG_SNAPSHOTS));
540 	AddShortcut('C', B_COMMAND_KEY | B_CONTROL_KEY,
541 		new BMessage(SHORTCUT_DEBUG_CAPTURE));
542 #endif
543 }
544 
545 
546 status_t
547 TermWindow::_GetWindowPositionFile(BFile* file, uint32 openMode)
548 {
549 	BPath path;
550 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
551 	if (status != B_OK)
552 		return status;
553 
554 	status = path.Append("Terminal");
555 	if (status != B_OK)
556 		return status;
557 
558 	status = path.Append("Windows");
559 	if (status != B_OK)
560 		return status;
561 
562 	return file->SetTo(path.Path(), openMode);
563 }
564 
565 
566 status_t
567 TermWindow::_LoadWindowPosition(BRect* frame, uint32* workspaces)
568 {
569 	status_t status;
570 	BMessage position;
571 
572 	BFile file;
573 	status = _GetWindowPositionFile(&file, B_READ_ONLY);
574 	if (status != B_OK)
575 		return status;
576 
577 	status = position.Unflatten(&file);
578 
579 	file.Unset();
580 
581 	if (status != B_OK)
582 		return status;
583 
584 	int32 id = fTerminalRoster.ID();
585 	status = position.FindRect("rect", id, frame);
586 	if (status != B_OK)
587 		return status;
588 
589 	int32 _workspaces;
590 	status = position.FindInt32("workspaces", id, &_workspaces);
591 	if (status != B_OK)
592 		return status;
593 	if (modifiers() & B_SHIFT_KEY)
594 		*workspaces = _workspaces;
595 	else
596 		*workspaces = B_CURRENT_WORKSPACE;
597 
598 	return B_OK;
599 }
600 
601 
602 status_t
603 TermWindow::_SaveWindowPosition()
604 {
605 	BFile file;
606 	BMessage originalSettings;
607 
608 	// Read the settings file if it exists and is a valid BMessage.
609 	status_t status = _GetWindowPositionFile(&file, B_READ_ONLY);
610 	if (status == B_OK) {
611 		status = originalSettings.Unflatten(&file);
612 		file.Unset();
613 
614 		if (status != B_OK)
615 			status = originalSettings.MakeEmpty();
616 
617 		if (status != B_OK)
618 			return status;
619 	}
620 
621 	// Replace the settings
622 	int32 id = fTerminalRoster.ID();
623 	BRect rect(Frame());
624 	if (originalSettings.ReplaceRect("rect", id, rect) != B_OK)
625 		originalSettings.AddRect("rect", rect);
626 
627 	int32 workspaces = Workspaces();
628 	if (originalSettings.ReplaceInt32("workspaces", id, workspaces) != B_OK)
629 		originalSettings.AddInt32("workspaces", workspaces);
630 
631 	// Resave the whole thing
632 	status = _GetWindowPositionFile (&file,
633 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
634 	if (status != B_OK)
635 		return status;
636 
637 	return originalSettings.Flatten(&file);
638 }
639 
640 
641 void
642 TermWindow::_GetPreferredFont(BFont& font)
643 {
644 	// Default to be_fixed_font
645 	font = be_fixed_font;
646 
647 	const char* family
648 		= PrefHandler::Default()->getString(PREF_HALF_FONT_FAMILY);
649 	const char* style
650 		= PrefHandler::Default()->getString(PREF_HALF_FONT_STYLE);
651 	const char* size = PrefHandler::Default()->getString(PREF_HALF_FONT_SIZE);
652 
653 	font.SetFamilyAndStyle(family, style);
654 	font.SetSize(atoi(size));
655 
656 	// mark the font size menu item
657 	for (int32 i = 0; i < fFontSizeMenu->CountItems(); i++) {
658 		BMenuItem* item = fFontSizeMenu->ItemAt(i);
659 		if (item == NULL)
660 			continue;
661 
662 		item->SetMarked(false);
663 		if (strcmp(item->Label(), size) == 0)
664 			item->SetMarked(true);
665 	}
666 }
667 
668 
669 void
670 TermWindow::MessageReceived(BMessage *message)
671 {
672 	int32 encodingId;
673 	bool findresult;
674 
675 	switch (message->what) {
676 		case B_COPY:
677 			_ActiveTermView()->Copy(be_clipboard);
678 			break;
679 
680 		case B_PASTE:
681 			_ActiveTermView()->Paste(be_clipboard);
682 			break;
683 
684 #ifdef USE_DEBUG_SNAPSHOTS
685 		case SHORTCUT_DEBUG_SNAPSHOTS:
686 			_ActiveTermView()->MakeDebugSnapshots();
687 			break;
688 
689 		case SHORTCUT_DEBUG_CAPTURE:
690 			_ActiveTermView()->StartStopDebugCapture();
691 			break;
692 #endif
693 
694 		case B_SELECT_ALL:
695 			_ActiveTermView()->SelectAll();
696 			break;
697 
698 		case MENU_CLEAR_ALL:
699 			_ActiveTermView()->Clear();
700 			break;
701 
702 		case MENU_SWITCH_TERM:
703 			_SwitchTerminal();
704 			break;
705 
706 		case MENU_NEW_TERM:
707 		{
708 			// Set our current working directory to that of the active tab, so
709 			// that the new terminal and its shell inherit it.
710 			// Note: That's a bit lame. We should rather fork() and change the
711 			// CWD in the child, but since ATM there aren't any side effects of
712 			// changing our CWD, we save ourselves the trouble.
713 			ActiveProcessInfo activeProcessInfo;
714 			if (_ActiveTermView()->GetActiveProcessInfo(activeProcessInfo))
715 				chdir(activeProcessInfo.CurrentDirectory());
716 
717 			app_info info;
718 			be_app->GetAppInfo(&info);
719 
720 			// try launching two different ways to work around possible problems
721 			if (be_roster->Launch(&info.ref) != B_OK)
722 				be_roster->Launch(TERM_SIGNATURE);
723 			break;
724 		}
725 
726 		case MENU_PREF_OPEN:
727 			if (!fPrefWindow) {
728 				fPrefWindow = new PrefWindow(this);
729 			} else
730 				fPrefWindow->Activate();
731 			break;
732 
733 		case MSG_PREF_CLOSED:
734 			fPrefWindow = NULL;
735 			break;
736 
737 		case MSG_WINDOW_TITLE_SETTING_CHANGED:
738 		case MSG_TAB_TITLE_SETTING_CHANGED:
739 			_TitleSettingsChanged();
740 			break;
741 
742 		case MENU_FIND_STRING:
743 			if (fFindPanel == NULL) {
744 				fFindPanel = new FindWindow(this, fFindString, fFindSelection,
745 					fMatchWord, fMatchCase, fForwardSearch);
746 
747 				fFindPanel->CenterIn(Frame());
748 				_MoveWindowInScreen(fFindPanel);
749 				fFindPanel->Show();
750 			} else
751 				fFindPanel->Activate();
752 			break;
753 
754 		case MSG_FIND:
755 		{
756 			fFindPanel->PostMessage(B_QUIT_REQUESTED);
757 			message->FindBool("findselection", &fFindSelection);
758 			if (!fFindSelection)
759 				message->FindString("findstring", &fFindString);
760 			else
761 				_ActiveTermView()->GetSelection(fFindString);
762 
763 			if (fFindString.Length() == 0) {
764 				const char* errorMsg = !fFindSelection
765 					? B_TRANSLATE("No search string was entered.")
766 					: B_TRANSLATE("Nothing is selected.");
767 				BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
768 					errorMsg, B_TRANSLATE("OK"), NULL, NULL,
769 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
770 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
771 
772 				alert->Go();
773 				fFindPreviousMenuItem->SetEnabled(false);
774 				fFindNextMenuItem->SetEnabled(false);
775 				break;
776 			}
777 
778 			message->FindBool("forwardsearch", &fForwardSearch);
779 			message->FindBool("matchcase", &fMatchCase);
780 			message->FindBool("matchword", &fMatchWord);
781 			findresult = _ActiveTermView()->Find(fFindString, fForwardSearch,
782 				fMatchCase, fMatchWord);
783 
784 			if (!findresult) {
785 				BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
786 					B_TRANSLATE("Text not found."),
787 					B_TRANSLATE("OK"), NULL, NULL,
788 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
789 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
790 				alert->Go();
791 				fFindPreviousMenuItem->SetEnabled(false);
792 				fFindNextMenuItem->SetEnabled(false);
793 				break;
794 			}
795 
796 			// Enable the menu items Find Next and Find Previous
797 			fFindPreviousMenuItem->SetEnabled(true);
798 			fFindNextMenuItem->SetEnabled(true);
799 			break;
800 		}
801 
802 		case MENU_FIND_NEXT:
803 		case MENU_FIND_PREVIOUS:
804 			findresult = _ActiveTermView()->Find(fFindString,
805 				(message->what == MENU_FIND_NEXT) == fForwardSearch,
806 				fMatchCase, fMatchWord);
807 			if (!findresult) {
808 				BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
809 					B_TRANSLATE("Not found."), B_TRANSLATE("OK"),
810 					NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
811 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
812 				alert->Go();
813 			}
814 			break;
815 
816 		case MSG_FIND_CLOSED:
817 			fFindPanel = NULL;
818 			break;
819 
820 		case MENU_ENCODING:
821 			if (message->FindInt32("op", &encodingId) == B_OK)
822 				_ActiveTermView()->SetEncoding(encodingId);
823 			break;
824 
825 		case MSG_COLS_CHANGED:
826 		{
827 			int32 columns, rows;
828 			if (message->FindInt32("columns", &columns) != B_OK
829 				|| message->FindInt32("rows", &rows) != B_OK) {
830 				break;
831 			}
832 
833 			for (int32 i = 0; i < fTabView->CountTabs(); i++) {
834 				TermView* view = _TermViewAt(i);
835 				view->SetTermSize(rows, columns, true);
836 				_ResizeView(view);
837 			}
838 			break;
839 		}
840 
841 		case MSG_BLINK_CURSOR_CHANGED:
842 		{
843 			bool blinkingCursor
844 				= PrefHandler::Default()->getBool(PREF_BLINK_CURSOR);
845 
846 			for (int32 i = 0; i < fTabView->CountTabs(); i++) {
847 				TermView* view = _TermViewAt(i);
848 				view->SwitchCursorBlinking(blinkingCursor);
849 			}
850 			break;
851 		}
852 
853 		case MSG_HALF_FONT_CHANGED:
854 		case MSG_FULL_FONT_CHANGED:
855 		case MSG_ALLOW_BOLD_CHANGED:
856 		{
857 			BFont font;
858 			_GetPreferredFont(font);
859 			for (int32 i = 0; i < fTabView->CountTabs(); i++) {
860 				TermView* view = _TermViewAt(i);
861 				view->SetTermFont(&font);
862 				_ResizeView(view);
863 			}
864 			break;
865 		}
866 
867 		case MSG_HALF_SIZE_CHANGED:
868 		case MSG_FULL_SIZE_CHANGED:
869 		{
870 			const char* size = NULL;
871 			if (message->FindString("font_size", &size) != B_OK)
872 				break;
873 
874 			// mark the font size menu item
875 			for (int32 i = 0; i < fFontSizeMenu->CountItems(); i++) {
876 				BMenuItem* item = fFontSizeMenu->ItemAt(i);
877 				if (item == NULL)
878 					continue;
879 
880 				item->SetMarked(false);
881 				if (strcmp(item->Label(), size) == 0)
882 					item->SetMarked(true);
883 			}
884 
885 			BFont font;
886 			_ActiveTermView()->GetTermFont(&font);
887 			font.SetSize(atoi(size));
888 			PrefHandler::Default()->setInt32(PREF_HALF_FONT_SIZE,
889 				(int32)atoi(size));
890 			for (int32 i = 0; i < fTabView->CountTabs(); i++) {
891 				TermView* view = _TermViewAt(i);
892 				_TermViewAt(i)->SetTermFont(&font);
893 				_ResizeView(view);
894 			}
895 			break;
896 		}
897 
898 		case FULLSCREEN:
899 			if (!fSavedFrame.IsValid()) { // go fullscreen
900 				_ActiveTermView()->DisableResizeView();
901 				float mbHeight = fMenuBar->Bounds().Height() + 1;
902 				fSavedFrame = Frame();
903 				BScreen screen(this);
904 
905 				for (int32 i = fTabView->CountTabs() - 1; i >= 0 ; i--)
906 					_TermViewAt(i)->ScrollBar()->ResizeBy(0,
907 						(B_H_SCROLL_BAR_HEIGHT - 1));
908 
909 				fMenuBar->Hide();
910 				fTabView->ResizeBy(0, mbHeight);
911 				fTabView->MoveBy(0, -mbHeight);
912 				fSavedLook = Look();
913 				// done before ResizeTo to work around a Dano bug
914 				// (not erasing the decor)
915 				SetLook(B_NO_BORDER_WINDOW_LOOK);
916 				ResizeTo(screen.Frame().Width() + 1, screen.Frame().Height() + 1);
917 				MoveTo(screen.Frame().left, screen.Frame().top);
918 				SetFlags(Flags() | (B_NOT_RESIZABLE | B_NOT_MOVABLE));
919 				fFullScreen = true;
920 			} else { // exit fullscreen
921 				_ActiveTermView()->DisableResizeView();
922 				float mbHeight = fMenuBar->Bounds().Height() + 1;
923 				fMenuBar->Show();
924 
925 				for (int32 i = fTabView->CountTabs() - 1; i >= 0 ; i--)
926 					_TermViewAt(i)->ScrollBar()->ResizeBy(0,
927 						-(B_H_SCROLL_BAR_HEIGHT - 1));
928 
929 				ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
930 				MoveTo(fSavedFrame.left, fSavedFrame.top);
931 				fTabView->ResizeBy(0, -mbHeight);
932 				fTabView->MoveBy(0, mbHeight);
933 				SetLook(fSavedLook);
934 				fSavedFrame = BRect(0, 0, -1, -1);
935 				SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
936 				fFullScreen = false;
937 			}
938 			break;
939 
940 		case MSG_FONT_CHANGED:
941 			PostMessage(MSG_HALF_FONT_CHANGED);
942 			break;
943 
944 		case MSG_COLOR_CHANGED:
945 		case MSG_COLOR_SCHEME_CHANGED:
946 		{
947 			_SetTermColors(_ActiveTermViewContainerView());
948 			_ActiveTermViewContainerView()->Invalidate();
949 			_ActiveTermView()->Invalidate();
950 			break;
951 		}
952 		case MSG_SAVE_AS_DEFAULT:
953 		{
954 			BPath path;
955 			if (PrefHandler::GetDefaultPath(path) == B_OK) {
956 				PrefHandler::Default()->SaveAsText(path.Path(),
957 					PREFFILE_MIMETYPE);
958 			}
959 			break;
960 		}
961 
962 		case MENU_PAGE_SETUP:
963 			_DoPageSetup();
964 			break;
965 
966 		case MENU_PRINT:
967 			_DoPrint();
968 			break;
969 
970 		case MSG_CHECK_CHILDREN:
971 			_CheckChildren();
972 			break;
973 
974 		case MSG_MOVE_TAB_LEFT:
975 		case MSG_MOVE_TAB_RIGHT:
976 			_NavigateTab(_IndexOfTermView(_ActiveTermView()),
977 				message->what == MSG_MOVE_TAB_LEFT ? -1 : 1, true);
978 			break;
979 
980 		case kTabTitleChanged:
981 		{
982 			// tab title changed message from SetTitleDialog
983 			SessionID sessionID(*message, "session");
984 			if (Session* session = _SessionForID(sessionID)) {
985 				BString title;
986 				if (message->FindString("title", &title) == B_OK) {
987 					session->title.pattern = title;
988 					session->title.patternUserDefined = true;
989 				} else {
990 					session->title.pattern.Truncate(0);
991 					session->title.patternUserDefined = false;
992 				}
993 				_UpdateSessionTitle(_IndexOfSession(session));
994 			}
995 			break;
996 		}
997 
998 		case kWindowTitleChanged:
999 		{
1000 			// window title changed message from SetTitleDialog
1001 			BString title;
1002 			if (message->FindString("title", &title) == B_OK) {
1003 				fTitle.pattern = title;
1004 				fTitle.patternUserDefined = true;
1005 			} else {
1006 				fTitle.pattern
1007 					= PrefHandler::Default()->getString(PREF_WINDOW_TITLE);
1008 				fTitle.patternUserDefined = false;
1009 			}
1010 
1011 			_UpdateSessionTitle(fTabView->Selection());
1012 				// updates the window title as a side effect
1013 
1014 			break;
1015 		}
1016 
1017 		case kSetActiveTab:
1018 		{
1019 			int32 index;
1020 			if (message->FindInt32("index", &index) == B_OK
1021 					&& index >= 0 && index < fSessions.CountItems()) {
1022 				fTabView->Select(index);
1023 			}
1024 			break;
1025 		}
1026 
1027 		case kNewTab:
1028 			_NewTab();
1029 			break;
1030 
1031 		case kCloseView:
1032 		{
1033 			int32 index = -1;
1034 			SessionID sessionID(*message, "session");
1035 			if (sessionID.IsValid()) {
1036 				if (Session* session = _SessionForID(sessionID))
1037 					index = _IndexOfSession(session);
1038 			} else
1039 				index = _IndexOfTermView(_ActiveTermView());
1040 
1041 			if (index >= 0)
1042 				_RemoveTab(index);
1043 
1044 			break;
1045 		}
1046 
1047 		case kCloseOtherViews:
1048 		{
1049 			Session* session = _SessionForID(SessionID(*message, "session"));
1050 			if (session == NULL)
1051 				break;
1052 
1053 			int32 count = fSessions.CountItems();
1054 			for (int32 i = count - 1; i >= 0; i--) {
1055 				if (_SessionAt(i) != session)
1056 					_RemoveTab(i);
1057 			}
1058 
1059 			break;
1060 		}
1061 
1062 		case kIncreaseFontSize:
1063 		case kDecreaseFontSize:
1064 		{
1065 			BFont font;
1066 			_ActiveTermView()->GetTermFont(&font);
1067 			float size = font.Size();
1068 
1069 			if (message->what == kIncreaseFontSize) {
1070 				if (size < 12)
1071 					size += 1;
1072 				else if (size < 24)
1073 					size += 2;
1074 				else
1075 					size += 4;
1076 			} else {
1077 				if (size <= 12)
1078 					size -= 1;
1079 				else if (size <= 24)
1080 					size -= 2;
1081 				else
1082 					size -= 4;
1083 			}
1084 
1085 			// constrain the font size
1086 			if (size < kMinimumFontSize)
1087 				size = kMinimumFontSize;
1088 			if (size > kMaximumFontSize)
1089 				size = kMaximumFontSize;
1090 
1091 			// mark the font size menu item
1092 			for (int32 i = 0; i < fFontSizeMenu->CountItems(); i++) {
1093 				BMenuItem* item = fFontSizeMenu->ItemAt(i);
1094 				if (item == NULL)
1095 					continue;
1096 
1097 				item->SetMarked(false);
1098 				if (atoi(item->Label()) == size)
1099 					item->SetMarked(true);
1100 			}
1101 
1102 			font.SetSize(size);
1103 			PrefHandler::Default()->setInt32(PREF_HALF_FONT_SIZE, (int32)size);
1104 			for (int32 i = 0; i < fTabView->CountTabs(); i++) {
1105 				TermView* view = _TermViewAt(i);
1106 				_TermViewAt(i)->SetTermFont(&font);
1107 				_ResizeView(view);
1108 			}
1109 			break;
1110 		}
1111 
1112 		case kUpdateTitles:
1113 			_UpdateTitles();
1114 			break;
1115 
1116 		case kEditTabTitle:
1117 		{
1118 			SessionID sessionID(*message, "session");
1119 			if (Session* session = _SessionForID(sessionID))
1120 				_OpenSetTabTitleDialog(_IndexOfSession(session));
1121 			break;
1122 		}
1123 
1124 		case kEditWindowTitle:
1125 			_OpenSetWindowTitleDialog();
1126 			break;
1127 
1128 		case kUpdateSwitchTerminalsMenuItem:
1129 			_UpdateSwitchTerminalsMenuItem();
1130 			break;
1131 
1132 		default:
1133 			BWindow::MessageReceived(message);
1134 			break;
1135 	}
1136 }
1137 
1138 
1139 void
1140 TermWindow::WindowActivated(bool activated)
1141 {
1142 	if (activated)
1143 		_UpdateSwitchTerminalsMenuItem();
1144 }
1145 
1146 
1147 void
1148 TermWindow::_SetTermColors(TermViewContainerView* containerView)
1149 {
1150 	PrefHandler* handler = PrefHandler::Default();
1151 	rgb_color background = handler->getRGB(PREF_TEXT_BACK_COLOR);
1152 
1153 	containerView->SetViewColor(background);
1154 
1155 	TermView *termView = containerView->GetTermView();
1156 	termView->SetTextColor(handler->getRGB(PREF_TEXT_FORE_COLOR), background);
1157 
1158 	termView->SetCursorColor(handler->getRGB(PREF_CURSOR_FORE_COLOR),
1159 		handler->getRGB(PREF_CURSOR_BACK_COLOR));
1160 	termView->SetSelectColor(handler->getRGB(PREF_SELECT_FORE_COLOR),
1161 		handler->getRGB(PREF_SELECT_BACK_COLOR));
1162 }
1163 
1164 
1165 status_t
1166 TermWindow::_DoPageSetup()
1167 {
1168 	BPrintJob job("PageSetup");
1169 
1170 	// display the page configure panel
1171 	status_t status = job.ConfigPage();
1172 
1173 	// save a pointer to the settings
1174 	fPrintSettings = job.Settings();
1175 
1176 	return status;
1177 }
1178 
1179 
1180 void
1181 TermWindow::_DoPrint()
1182 {
1183 	BPrintJob job("Print");
1184 	if (fPrintSettings)
1185 		job.SetSettings(new BMessage(*fPrintSettings));
1186 
1187 	if (job.ConfigJob() != B_OK)
1188 		return;
1189 
1190 	BRect pageRect = job.PrintableRect();
1191 	BRect curPageRect = pageRect;
1192 
1193 	int pHeight = (int)pageRect.Height();
1194 	int pWidth = (int)pageRect.Width();
1195 	float w, h;
1196 	_ActiveTermView()->GetFrameSize(&w, &h);
1197 	int xPages = (int)ceil(w / pWidth);
1198 	int yPages = (int)ceil(h / pHeight);
1199 
1200 	job.BeginJob();
1201 
1202 	// loop through and draw each page, and write to spool
1203 	for (int x = 0; x < xPages; x++) {
1204 		for (int y = 0; y < yPages; y++) {
1205 			curPageRect.OffsetTo(x * pWidth, y * pHeight);
1206 			job.DrawView(_ActiveTermView(), curPageRect, B_ORIGIN);
1207 			job.SpoolPage();
1208 
1209 			if (!job.CanContinue()) {
1210 				// It is likely that the only way that the job was cancelled is
1211 				// because the user hit 'Cancel' in the page setup window, in
1212 				// which case, the user does *not* need to be told that it was
1213 				// cancelled.
1214 				// He/she will simply expect that it was done.
1215 				return;
1216 			}
1217 		}
1218 	}
1219 
1220 	job.CommitJob();
1221 }
1222 
1223 
1224 void
1225 TermWindow::_NewTab()
1226 {
1227 	ActiveProcessInfo info;
1228 	if (_ActiveTermView()->GetActiveProcessInfo(info))
1229 		_AddTab(NULL, info.CurrentDirectory());
1230 	else
1231 		_AddTab(NULL);
1232 }
1233 
1234 
1235 void
1236 TermWindow::_AddTab(Arguments* args, const BString& currentDirectory)
1237 {
1238 	int argc = 0;
1239 	const char* const* argv = NULL;
1240 	if (args != NULL)
1241 		args->GetShellArguments(argc, argv);
1242 	ShellParameters shellParameters(argc, argv, currentDirectory);
1243 
1244 	try {
1245 		TermView* view = new TermView(
1246 			PrefHandler::Default()->getInt32(PREF_ROWS),
1247 			PrefHandler::Default()->getInt32(PREF_COLS),
1248 			shellParameters,
1249 			PrefHandler::Default()->getInt32(PREF_HISTORY_SIZE));
1250 		view->SetListener(this);
1251 
1252 		TermViewContainerView* containerView = new TermViewContainerView(view);
1253 		BScrollView* scrollView = new TermScrollView("scrollView",
1254 			containerView, view, fSessions.IsEmpty());
1255 		if (!fFullScreen)
1256 			scrollView->ScrollBar(B_VERTICAL)
1257 				->ResizeBy(0, -(B_H_SCROLL_BAR_HEIGHT - 1));
1258 
1259 		if (fSessions.IsEmpty())
1260 			fTabView->SetScrollView(scrollView);
1261 
1262 		Session* session = new Session(_NewSessionID(), _NewSessionIndex(),
1263 			containerView);
1264 		fSessions.AddItem(session);
1265 
1266 		BFont font;
1267 		_GetPreferredFont(font);
1268 		view->SetTermFont(&font);
1269 
1270 		int width, height;
1271 		view->GetFontSize(&width, &height);
1272 
1273 		float minimumHeight = -1;
1274 		if (fMenuBar != NULL)
1275 			minimumHeight += fMenuBar->Bounds().Height() + 1;
1276 
1277 		if (fTabView != NULL && fTabView->CountTabs() > 0)
1278 			minimumHeight += fTabView->TabHeight() + 1;
1279 
1280 		SetSizeLimits(MIN_COLS * width - 1, MAX_COLS * width - 1,
1281 			minimumHeight + MIN_ROWS * height - 1,
1282 			minimumHeight + MAX_ROWS * height - 1);
1283 			// TODO: The size limit computation is apparently broken, since
1284 			// the terminal can be resized smaller than MIN_ROWS/MIN_COLS!
1285 
1286 		// If it's the first time we're called, setup the window
1287 		if (fTabView != NULL && fTabView->CountTabs() == 0) {
1288 			float viewWidth, viewHeight;
1289 			containerView->GetPreferredSize(&viewWidth, &viewHeight);
1290 
1291 			// Resize Window
1292 			ResizeTo(viewWidth + B_V_SCROLL_BAR_WIDTH,
1293 				viewHeight + fMenuBar->Bounds().Height() + 1);
1294 				// NOTE: Width is one pixel too small, since the scroll view
1295 				// is one pixel wider than its parent.
1296 		}
1297 
1298 		BTab* tab = new BTab;
1299 		fTabView->AddTab(scrollView, tab);
1300 		view->SetScrollBar(scrollView->ScrollBar(B_VERTICAL));
1301 		view->SetMouseClipboard(gMouseClipboard);
1302 
1303 		const BCharacterSet* charset
1304 			= BCharacterSetRoster::FindCharacterSetByName(
1305 				PrefHandler::Default()->getString(PREF_TEXT_ENCODING));
1306 		if (charset != NULL)
1307 			view->SetEncoding(charset->GetConversionID());
1308 
1309 		_SetTermColors(containerView);
1310 
1311 		int32 tabIndex = fTabView->CountTabs() - 1;
1312 		fTabView->Select(tabIndex);
1313 
1314 		_UpdateSessionTitle(tabIndex);
1315 	} catch (...) {
1316 		// most probably out of memory. That's bad.
1317 		// TODO: Should cleanup, I guess
1318 
1319 		// Quit the application if we don't have a shell already
1320 		if (fTabView->CountTabs() == 0) {
1321 			fprintf(stderr, "Terminal couldn't open a shell\n");
1322 			PostMessage(B_QUIT_REQUESTED);
1323 		}
1324 	}
1325 }
1326 
1327 
1328 void
1329 TermWindow::_RemoveTab(int32 index)
1330 {
1331 	_FinishTitleDialog();
1332 		// always close to avoid confusion
1333 
1334 	if (fSessions.CountItems() > 1) {
1335 		if (!_CanClose(index))
1336 			return;
1337 		if (Session* session = (Session*)fSessions.RemoveItem(index)) {
1338 			if (fSessions.CountItems() == 1) {
1339 				fTabView->SetScrollView(dynamic_cast<BScrollView*>(
1340 					_SessionAt(0)->containerView->Parent()));
1341 			}
1342 
1343 			delete session;
1344 			delete fTabView->RemoveTab(index);
1345 		}
1346 	} else
1347 		PostMessage(B_QUIT_REQUESTED);
1348 }
1349 
1350 
1351 void
1352 TermWindow::_NavigateTab(int32 index, int32 direction, bool move)
1353 {
1354 	int32 count = fSessions.CountItems();
1355 	if (count <= 1 || index < 0 || index >= count)
1356 		return;
1357 
1358 	int32 newIndex = (index + direction + count) % count;
1359 	if (newIndex == index)
1360 		return;
1361 
1362 	if (move) {
1363 		// move the given tab to the new index
1364 		Session* session = (Session*)fSessions.RemoveItem(index);
1365 		fSessions.AddItem(session, newIndex);
1366 		fTabView->MoveTab(index, newIndex);
1367 	}
1368 
1369 	// activate the respective tab
1370 	fTabView->Select(newIndex);
1371 }
1372 
1373 
1374 TermViewContainerView*
1375 TermWindow::_ActiveTermViewContainerView() const
1376 {
1377 	return _TermViewContainerViewAt(fTabView->Selection());
1378 }
1379 
1380 
1381 TermViewContainerView*
1382 TermWindow::_TermViewContainerViewAt(int32 index) const
1383 {
1384 	if (Session* session = _SessionAt(index))
1385 		return session->containerView;
1386 	return NULL;
1387 }
1388 
1389 
1390 TermView*
1391 TermWindow::_ActiveTermView() const
1392 {
1393 	return _ActiveTermViewContainerView()->GetTermView();
1394 }
1395 
1396 
1397 TermView*
1398 TermWindow::_TermViewAt(int32 index) const
1399 {
1400 	TermViewContainerView* view = _TermViewContainerViewAt(index);
1401 	return view != NULL ? view->GetTermView() : NULL;
1402 }
1403 
1404 
1405 int32
1406 TermWindow::_IndexOfTermView(TermView* termView) const
1407 {
1408 	if (!termView)
1409 		return -1;
1410 
1411 	// find the view
1412 	int32 count = fTabView->CountTabs();
1413 	for (int32 i = count - 1; i >= 0; i--) {
1414 		if (termView == _TermViewAt(i))
1415 			return i;
1416 	}
1417 
1418 	return -1;
1419 }
1420 
1421 
1422 TermWindow::Session*
1423 TermWindow::_SessionAt(int32 index) const
1424 {
1425 	return (Session*)fSessions.ItemAt(index);
1426 }
1427 
1428 
1429 TermWindow::Session*
1430 TermWindow::_SessionForID(const SessionID& sessionID) const
1431 {
1432 	for (int32 i = 0; Session* session = _SessionAt(i); i++) {
1433 		if (session->id == sessionID)
1434 			return session;
1435 	}
1436 
1437 	return NULL;
1438 }
1439 
1440 
1441 int32
1442 TermWindow::_IndexOfSession(Session* session) const
1443 {
1444 	return fSessions.IndexOf(session);
1445 }
1446 
1447 
1448 void
1449 TermWindow::_CheckChildren()
1450 {
1451 	int32 count = fSessions.CountItems();
1452 	for (int32 i = count - 1; i >= 0; i--) {
1453 		Session* session = _SessionAt(i);
1454 		if (session->containerView->GetTermView()->CheckShellGone())
1455 			NotifyTermViewQuit(session->containerView->GetTermView(), 0);
1456 	}
1457 }
1458 
1459 
1460 void
1461 TermWindow::Zoom(BPoint leftTop, float width, float height)
1462 {
1463 	_ActiveTermView()->DisableResizeView();
1464 	BWindow::Zoom(leftTop, width, height);
1465 }
1466 
1467 
1468 void
1469 TermWindow::FrameResized(float newWidth, float newHeight)
1470 {
1471 	BWindow::FrameResized(newWidth, newHeight);
1472 
1473 	TermView* view = _ActiveTermView();
1474 	PrefHandler::Default()->setInt32(PREF_COLS, view->Columns());
1475 	PrefHandler::Default()->setInt32(PREF_ROWS, view->Rows());
1476 }
1477 
1478 
1479 void
1480 TermWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces)
1481 {
1482 	fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces());
1483 }
1484 
1485 
1486 void
1487 TermWindow::WorkspaceActivated(int32 workspace, bool state)
1488 {
1489 	fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces());
1490 }
1491 
1492 
1493 void
1494 TermWindow::Minimize(bool minimize)
1495 {
1496 	BWindow::Minimize(minimize);
1497 	fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces());
1498 }
1499 
1500 
1501 void
1502 TermWindow::TabSelected(SmartTabView* tabView, int32 index)
1503 {
1504 	SessionChanged();
1505 }
1506 
1507 
1508 void
1509 TermWindow::TabDoubleClicked(SmartTabView* tabView, BPoint point, int32 index)
1510 {
1511 	if (index >= 0) {
1512 		// clicked on a tab -- open the title dialog
1513 		_OpenSetTabTitleDialog(index);
1514 	} else {
1515 		// not clicked on a tab -- create a new one
1516 		_NewTab();
1517 	}
1518 }
1519 
1520 
1521 void
1522 TermWindow::TabMiddleClicked(SmartTabView* tabView, BPoint point, int32 index)
1523 {
1524 	if (index >= 0)
1525 		_RemoveTab(index);
1526 }
1527 
1528 
1529 void
1530 TermWindow::TabRightClicked(SmartTabView* tabView, BPoint point, int32 index)
1531 {
1532 	if (index < 0)
1533 		return;
1534 
1535 	TermView* termView = _TermViewAt(index);
1536 	if (termView == NULL)
1537 		return;
1538 
1539 	BMessage* closeMessage = new BMessage(kCloseView);
1540 	_SessionAt(index)->id.AddToMessage(*closeMessage, "session");
1541 
1542 	BMessage* closeOthersMessage = new BMessage(kCloseOtherViews);
1543 	_SessionAt(index)->id.AddToMessage(*closeOthersMessage, "session");
1544 
1545 	BMessage* editTitleMessage = new BMessage(kEditTabTitle);
1546 	_SessionAt(index)->id.AddToMessage(*editTitleMessage, "session");
1547 
1548 	BPopUpMenu* popUpMenu = new BPopUpMenu("tab menu");
1549 	BLayoutBuilder::Menu<>(popUpMenu)
1550 		.AddItem(B_TRANSLATE("Close tab"), closeMessage)
1551 		.AddItem(B_TRANSLATE("Close other tabs"), closeOthersMessage)
1552 		.AddSeparator()
1553 		.AddItem(B_TRANSLATE("Edit tab title" B_UTF8_ELLIPSIS),
1554 			editTitleMessage)
1555 	;
1556 
1557 	popUpMenu->SetAsyncAutoDestruct(true);
1558 	popUpMenu->SetTargetForItems(BMessenger(this));
1559 
1560 	BPoint screenWhere = tabView->ConvertToScreen(point);
1561 	BRect mouseRect(screenWhere, screenWhere);
1562 	mouseRect.InsetBy(-4.0, -4.0);
1563 	popUpMenu->Go(screenWhere, true, true, mouseRect, true);
1564 }
1565 
1566 
1567 void
1568 TermWindow::NotifyTermViewQuit(TermView* view, int32 reason)
1569 {
1570 	// Since the notification can come from the view, we send a message to
1571 	// ourselves to avoid deleting the caller synchronously.
1572 	if (Session* session = _SessionAt(_IndexOfTermView(view))) {
1573 		BMessage message(kCloseView);
1574 		session->id.AddToMessage(message, "session");
1575 		message.AddInt32("reason", reason);
1576 		PostMessage(&message);
1577 	}
1578 }
1579 
1580 
1581 void
1582 TermWindow::SetTermViewTitle(TermView* view, const char* title)
1583 {
1584 	int32 index = _IndexOfTermView(view);
1585 	if (Session* session = _SessionAt(index)) {
1586 		session->title.pattern = title;
1587 		session->title.patternUserDefined = true;
1588 		_UpdateSessionTitle(index);
1589 	}
1590 }
1591 
1592 
1593 void
1594 TermWindow::TitleChanged(SetTitleDialog* dialog, const BString& title,
1595 	bool titleUserDefined)
1596 {
1597 	if (dialog == fSetTabTitleDialog) {
1598 		// tab title
1599 		BMessage message(kTabTitleChanged);
1600 		fSetTabTitleSession.AddToMessage(message, "session");
1601 		if (titleUserDefined)
1602 			message.AddString("title", title);
1603 
1604 		PostMessage(&message);
1605 	} else if (dialog == fSetWindowTitleDialog) {
1606 		// window title
1607 		BMessage message(kWindowTitleChanged);
1608 		if (titleUserDefined)
1609 			message.AddString("title", title);
1610 
1611 		PostMessage(&message);
1612 	}
1613 }
1614 
1615 
1616 void
1617 TermWindow::SetTitleDialogDone(SetTitleDialog* dialog)
1618 {
1619 	if (dialog == fSetTabTitleDialog) {
1620 		fSetTabTitleSession = SessionID();
1621 		fSetTabTitleDialog = NULL;
1622 			// assuming this is atomic
1623 	}
1624 }
1625 
1626 
1627 void
1628 TermWindow::TerminalInfosUpdated(TerminalRoster* roster)
1629 {
1630 	PostMessage(kUpdateSwitchTerminalsMenuItem);
1631 }
1632 
1633 
1634 void
1635 TermWindow::PreviousTermView(TermView* view)
1636 {
1637 	_NavigateTab(_IndexOfTermView(view), -1, false);
1638 }
1639 
1640 
1641 void
1642 TermWindow::NextTermView(TermView* view)
1643 {
1644 	_NavigateTab(_IndexOfTermView(view), 1, false);
1645 }
1646 
1647 
1648 void
1649 TermWindow::_ResizeView(TermView *view)
1650 {
1651 	int fontWidth, fontHeight;
1652 	view->GetFontSize(&fontWidth, &fontHeight);
1653 
1654 	float minimumHeight = -1;
1655 	if (fMenuBar != NULL)
1656 		minimumHeight += fMenuBar->Bounds().Height() + 1;
1657 
1658 	if (fTabView != NULL && fTabView->CountTabs() > 1)
1659 		minimumHeight += fTabView->TabHeight() + 1;
1660 
1661 	SetSizeLimits(MIN_COLS * fontWidth - 1, MAX_COLS * fontWidth - 1,
1662 		minimumHeight + MIN_ROWS * fontHeight - 1,
1663 		minimumHeight + MAX_ROWS * fontHeight - 1);
1664 
1665 	float width;
1666 	float height;
1667 	view->Parent()->GetPreferredSize(&width, &height);
1668 
1669 	width += B_V_SCROLL_BAR_WIDTH;
1670 		// NOTE: Width is one pixel too small, since the scroll view
1671 		// is one pixel wider than its parent.
1672 	if (fMenuBar != NULL)
1673 		height += fMenuBar->Bounds().Height() + 1;
1674 	if (fTabView != NULL && fTabView->CountTabs() > 1)
1675 		height += fTabView->TabHeight() + 1;
1676 
1677 	ResizeTo(width, height);
1678 	view->Invalidate();
1679 }
1680 
1681 
1682 /* static */ void
1683 TermWindow::MakeWindowSizeMenu(BMenu* menu)
1684 {
1685 	const int32 windowSizes[4][2] = {
1686 		{ 80, 25 },
1687 		{ 80, 40 },
1688 		{ 132, 25 },
1689 		{ 132, 40 }
1690 	};
1691 
1692 	const int32 sizeNum = sizeof(windowSizes) / sizeof(windowSizes[0]);
1693 	for (int32 i = 0; i < sizeNum; i++) {
1694 		char label[32];
1695 		int32 columns = windowSizes[i][0];
1696 		int32 rows = windowSizes[i][1];
1697 		snprintf(label, sizeof(label), "%" B_PRId32 "x%" B_PRId32, columns,
1698 			rows);
1699 		BMessage* message = new BMessage(MSG_COLS_CHANGED);
1700 		message->AddInt32("columns", columns);
1701 		message->AddInt32("rows", rows);
1702 		menu->AddItem(new BMenuItem(label, message));
1703 	}
1704 }
1705 
1706 
1707 /*static*/ BMenu*
1708 TermWindow::_MakeFontSizeMenu(uint32 command, uint8 defaultSize)
1709 {
1710 	BMenu* menu = new (std::nothrow) BMenu(B_TRANSLATE("Font size"));
1711 	if (menu == NULL)
1712 		return NULL;
1713 
1714 	int32 sizes[] = {
1715 		8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, 0
1716 	};
1717 
1718 	bool found = false;
1719 
1720 	for (uint32 i = 0; sizes[i]; i++) {
1721 		BString string;
1722 		string << sizes[i];
1723 		BMessage* message = new BMessage(command);
1724 		message->AddString("font_size", string);
1725 		BMenuItem* item = new BMenuItem(string.String(), message);
1726 		menu->AddItem(item);
1727 		if (sizes[i] == defaultSize) {
1728 			item->SetMarked(true);
1729 			found = true;
1730 		}
1731 	}
1732 
1733 	if (!found) {
1734 		for (uint32 i = 0; sizes[i]; i++) {
1735 			if (sizes[i] > defaultSize) {
1736 				BString string;
1737 				string << defaultSize;
1738 				BMessage* message = new BMessage(command);
1739 				message->AddString("font_size", string);
1740 				BMenuItem* item = new BMenuItem(string.String(), message);
1741 				item->SetMarked(true);
1742 				menu->AddItem(item, i);
1743 				break;
1744 			}
1745 		}
1746 	}
1747 
1748 	return menu;
1749 }
1750 
1751 
1752 void
1753 TermWindow::_UpdateSwitchTerminalsMenuItem()
1754 {
1755 	fSwitchTerminalsMenuItem->SetEnabled(_FindSwitchTerminalTarget() >= 0);
1756 }
1757 
1758 
1759 void
1760 TermWindow::_TitleSettingsChanged()
1761 {
1762 	if (!fTitle.patternUserDefined)
1763 		fTitle.pattern = PrefHandler::Default()->getString(PREF_WINDOW_TITLE);
1764 
1765 	fSessionTitlePattern = PrefHandler::Default()->getString(PREF_TAB_TITLE);
1766 
1767 	_UpdateTitles();
1768 }
1769 
1770 
1771 void
1772 TermWindow::_UpdateTitles()
1773 {
1774 	int32 sessionCount = fSessions.CountItems();
1775 	for (int32 i = 0; i < sessionCount; i++)
1776 		_UpdateSessionTitle(i);
1777 }
1778 
1779 
1780 void
1781 TermWindow::_UpdateSessionTitle(int32 index)
1782 {
1783 	Session* session = _SessionAt(index);
1784 	if (session == NULL)
1785 		return;
1786 
1787 	// get the shell and active process infos
1788 	ShellInfo shellInfo;
1789 	ActiveProcessInfo activeProcessInfo;
1790 	TermView* termView = _TermViewAt(index);
1791 	if (!termView->GetShellInfo(shellInfo)
1792 		|| !termView->GetActiveProcessInfo(activeProcessInfo)) {
1793 		return;
1794 	}
1795 
1796 	// evaluate the session title pattern
1797 	BString sessionTitlePattern = session->title.patternUserDefined
1798 		? session->title.pattern : fSessionTitlePattern;
1799 	TabTitlePlaceholderMapper tabMapper(shellInfo, activeProcessInfo,
1800 		session->index);
1801 	const BString& sessionTitle = PatternEvaluator::Evaluate(
1802 		sessionTitlePattern, tabMapper);
1803 
1804 	// set the tab title
1805 	if (sessionTitle != session->title.title) {
1806 		session->title.title = sessionTitle;
1807 		fTabView->TabAt(index)->SetLabel(session->title.title);
1808 		fTabView->Invalidate();
1809 			// Invalidate the complete tab view, since other tabs might change
1810 			// their positions.
1811 	}
1812 
1813 	// If this is the active tab, also recompute the window title.
1814 	if (index != fTabView->Selection())
1815 		return;
1816 
1817 	// evaluate the window title pattern
1818 	WindowTitlePlaceholderMapper windowMapper(shellInfo, activeProcessInfo,
1819 		fTerminalRoster.CountTerminals() > 1
1820 			? fTerminalRoster.ID() + 1 : 0, sessionTitle);
1821 	const BString& windowTitle = PatternEvaluator::Evaluate(fTitle.pattern,
1822 		windowMapper);
1823 
1824 	// set the window title
1825 	if (windowTitle != fTitle.title) {
1826 		fTitle.title = windowTitle;
1827 		SetTitle(fTitle.title);
1828 	}
1829 
1830 	// If fullscreen, add a tooltip with the title and a keyboard shortcut hint
1831 	if (fFullScreen) {
1832 		BString toolTip(fTitle.title);
1833 		toolTip += "\n(";
1834 		toolTip += B_TRANSLATE("Full screen");
1835 		toolTip += " (ALT " UTF8_ENTER "))";
1836 		termView->SetToolTip(toolTip.String());
1837 	} else
1838 		termView->SetToolTip((const char *)NULL);
1839 }
1840 
1841 
1842 void
1843 TermWindow::_OpenSetTabTitleDialog(int32 index)
1844 {
1845 	// If a dialog is active, finish it.
1846 	_FinishTitleDialog();
1847 
1848 	BString toolTip = BString(B_TRANSLATE(
1849 		"The pattern specifying the current tab title. The following "
1850 			"placeholders\n"
1851 		"can be used:\n")) << kTooTipSetTabTitlePlaceholders;
1852 	fSetTabTitleDialog = new SetTitleDialog(
1853 		B_TRANSLATE("Set tab title"), B_TRANSLATE("Tab title:"),
1854 		toolTip);
1855 
1856 	Session* session = _SessionAt(index);
1857 	bool userDefined = session->title.patternUserDefined;
1858 	const BString& title = userDefined
1859 		? session->title.pattern : fSessionTitlePattern;
1860 	fSetTabTitleSession = session->id;
1861 
1862 	// place the dialog window directly under the tab, but keep it on screen
1863 	BPoint location = fTabView->ConvertToScreen(
1864 		fTabView->TabFrame(index).LeftBottom() + BPoint(0, 1));
1865 	fSetTabTitleDialog->MoveTo(location);
1866 	_MoveWindowInScreen(fSetTabTitleDialog);
1867 
1868 	fSetTabTitleDialog->Go(title, userDefined, this);
1869 }
1870 
1871 
1872 void
1873 TermWindow::_OpenSetWindowTitleDialog()
1874 {
1875 	// If a dialog is active, finish it.
1876 	_FinishTitleDialog();
1877 
1878 	BString toolTip = BString(B_TRANSLATE(
1879 		"The pattern specifying the window title. The following placeholders\n"
1880 		"can be used:\n")) << kTooTipSetTabTitlePlaceholders;
1881 	fSetWindowTitleDialog = new SetTitleDialog(B_TRANSLATE("Set window title"),
1882 		B_TRANSLATE("Window title:"), toolTip);
1883 
1884 	// center the dialog in the window frame, but keep it on screen
1885 	fSetWindowTitleDialog->CenterIn(Frame());
1886 	_MoveWindowInScreen(fSetWindowTitleDialog);
1887 
1888 	fSetWindowTitleDialog->Go(fTitle.pattern, fTitle.patternUserDefined, this);
1889 }
1890 
1891 
1892 void
1893 TermWindow::_FinishTitleDialog()
1894 {
1895 	SetTitleDialog* oldDialog = fSetTabTitleDialog;
1896 	if (oldDialog != NULL && oldDialog->Lock()) {
1897 		// might have been unset in the meantime, so recheck
1898 		if (fSetTabTitleDialog == oldDialog) {
1899 			oldDialog->Finish();
1900 				// this also unsets the variables
1901 		}
1902 		oldDialog->Unlock();
1903 		return;
1904 	}
1905 
1906 	oldDialog = fSetWindowTitleDialog;
1907 	if (oldDialog != NULL && oldDialog->Lock()) {
1908 		// might have been unset in the meantime, so recheck
1909 		if (fSetWindowTitleDialog == oldDialog) {
1910 			oldDialog->Finish();
1911 				// this also unsets the variable
1912 		}
1913 		oldDialog->Unlock();
1914 		return;
1915 	}
1916 }
1917 
1918 
1919 void
1920 TermWindow::_SwitchTerminal()
1921 {
1922 	team_id teamID = _FindSwitchTerminalTarget();
1923 	if (teamID < 0)
1924 		return;
1925 
1926 	BMessenger app(TERM_SIGNATURE, teamID);
1927 	app.SendMessage(MSG_ACTIVATE_TERM);
1928 }
1929 
1930 
1931 team_id
1932 TermWindow::_FindSwitchTerminalTarget()
1933 {
1934 	AutoLocker<TerminalRoster> rosterLocker(fTerminalRoster);
1935 
1936 	team_id myTeamID = Team();
1937 
1938 	int32 numTerms = fTerminalRoster.CountTerminals();
1939 	if (numTerms <= 1)
1940 		return -1;
1941 
1942 	// Find our position in the Terminal teams.
1943 	int32 i;
1944 
1945 	for (i = 0; i < numTerms; i++) {
1946 		if (myTeamID == fTerminalRoster.TerminalAt(i)->team)
1947 			break;
1948 	}
1949 
1950 	if (i == numTerms) {
1951 		// we didn't find ourselves -- that shouldn't happen
1952 		return -1;
1953 	}
1954 
1955 	uint32 currentWorkspace = 1L << current_workspace();
1956 
1957 	while (true) {
1958 		if (--i < 0)
1959 			i = numTerms - 1;
1960 
1961 		const TerminalRoster::Info* info = fTerminalRoster.TerminalAt(i);
1962 		if (info->team == myTeamID) {
1963 			// That's ourselves again. We've run through the complete list.
1964 			return -1;
1965 		}
1966 
1967 		if (!info->minimized && (info->workspaces & currentWorkspace) != 0)
1968 			return info->team;
1969 	}
1970 }
1971 
1972 
1973 TermWindow::SessionID
1974 TermWindow::_NewSessionID()
1975 {
1976 	return fNextSessionID++;
1977 }
1978 
1979 
1980 int32
1981 TermWindow::_NewSessionIndex()
1982 {
1983 	for (int32 id = 1; ; id++) {
1984 		bool used = false;
1985 
1986 		for (int32 i = 0;
1987 			Session* session = _SessionAt(i); i++) {
1988 			if (id == session->index) {
1989 				used = true;
1990 				break;
1991 			}
1992 		}
1993 
1994 		if (!used)
1995 			return id;
1996 	}
1997 }
1998 
1999 
2000 void
2001 TermWindow::_MoveWindowInScreen(BWindow* window)
2002 {
2003 	BRect frame = window->Frame();
2004 	BSize screenSize(BScreen(window).Frame().Size());
2005 	window->MoveTo(BLayoutUtils::MoveIntoFrame(frame, screenSize).LeftTop());
2006 }
2007