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