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