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