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