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