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