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