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