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