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