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