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