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