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