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