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