xref: /haiku/src/apps/terminal/TermWindow.cpp (revision 1345706a9ff6ad0dc041339a02d4259998b0765d)
1 /*
2  * Copyright 2007-2009, 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 <string.h>
15 #include <time.h>
16 
17 #include <Alert.h>
18 #include <Application.h>
19 #include <Catalog.h>
20 #include <Clipboard.h>
21 #include <Dragger.h>
22 #include <Locale.h>
23 #include <Menu.h>
24 #include <MenuBar.h>
25 #include <MenuItem.h>
26 #include <Path.h>
27 #include <PrintJob.h>
28 #include <Roster.h>
29 #include <Screen.h>
30 #include <ScrollBar.h>
31 #include <ScrollView.h>
32 #include <String.h>
33 
34 #include "Arguments.h"
35 #include "AppearPrefView.h"
36 #include "Encoding.h"
37 #include "FindWindow.h"
38 #include "Globals.h"
39 #include "PrefWindow.h"
40 #include "PrefHandler.h"
41 #include "SmartTabView.h"
42 #include "TermConst.h"
43 #include "TermScrollView.h"
44 #include "TermView.h"
45 
46 
47 const static int32 kMaxTabs = 6;
48 const static int32 kTermViewOffset = 3;
49 
50 // messages constants
51 const static uint32 kNewTab = 'NTab';
52 const static uint32 kCloseView = 'ClVw';
53 const static uint32 kIncreaseFontSize = 'InFs';
54 const static uint32 kDecreaseFontSize = 'DcFs';
55 const static uint32 kSetActiveTab = 'STab';
56 
57 #undef B_TRANSLATE_CONTEXT
58 #define B_TRANSLATE_CONTEXT "Terminal TermWindow"
59 
60 class CustomTermView : public TermView {
61 public:
62 	CustomTermView(int32 rows, int32 columns, int32 argc, const char **argv, int32 historySize = 1000);
63 	virtual void NotifyQuit(int32 reason);
64 	virtual void SetTitle(const char *title);
65 };
66 
67 
68 class TermViewContainerView : public BView {
69 public:
70 	TermViewContainerView(TermView* termView)
71 		:
72 		BView(BRect(), "term view container", B_FOLLOW_ALL, 0),
73 		fTermView(termView)
74 	{
75 		termView->MoveTo(kTermViewOffset, kTermViewOffset);
76 		BRect frame(termView->Frame());
77 		ResizeTo(frame.right + kTermViewOffset, frame.bottom + kTermViewOffset);
78 		AddChild(termView);
79 	}
80 
81 	TermView* GetTermView() const	{ return fTermView; }
82 
83 	virtual void GetPreferredSize(float* _width, float* _height)
84 	{
85 		float width, height;
86 		fTermView->GetPreferredSize(&width, &height);
87 		*_width = width + 2 * kTermViewOffset;
88 		*_height = height + 2 * kTermViewOffset;
89 	}
90 
91 private:
92 	TermView*	fTermView;
93 };
94 
95 
96 struct TermWindow::Session {
97 	int32					id;
98 	BString					name;
99 	BString					windowTitle;
100 	TermViewContainerView*	containerView;
101 
102 	Session(int32 id, TermViewContainerView* containerView)
103 		:
104 		id(id),
105 		containerView(containerView)
106 	{
107 		name = "Shell ";
108 		name << id;
109 	}
110 };
111 
112 
113 class TermWindow::TabView : public SmartTabView {
114 public:
115 	TabView(TermWindow* window, BRect frame, const char *name)
116 		:
117 		SmartTabView(frame, name),
118 		fWindow(window)
119 	{
120 	}
121 
122 	virtual	void Select(int32 tab)
123 	{
124 		SmartTabView::Select(tab);
125 		fWindow->SessionChanged();
126 	}
127 
128 	virtual	void RemoveAndDeleteTab(int32 index)
129 	{
130 		fWindow->_RemoveTab(index);
131 	}
132 
133 private:
134 	TermWindow*	fWindow;
135 };
136 
137 
138 TermWindow::TermWindow(BRect frame, const char* title, Arguments* args)
139 	:
140 	BWindow(frame, title, B_DOCUMENT_WINDOW,
141 		B_CURRENT_WORKSPACE | B_QUIT_ON_WINDOW_CLOSE),
142 	fInitialTitle(title),
143 	fTabView(NULL),
144 	fMenubar(NULL),
145 	fFilemenu(NULL),
146 	fEditmenu(NULL),
147 	fEncodingmenu(NULL),
148 	fHelpmenu(NULL),
149 	fWindowSizeMenu(NULL),
150 	fPrintSettings(NULL),
151 	fPrefWindow(NULL),
152 	fFindPanel(NULL),
153 	fSavedFrame(0, 0, -1, -1),
154 	fFindString(""),
155 	fFindNextMenuItem(NULL),
156 	fFindPreviousMenuItem(NULL),
157 	fFindSelection(false),
158 	fForwardSearch(false),
159 	fMatchCase(false),
160 	fMatchWord(false),
161 	fFullScreen(false)
162 {
163 	_InitWindow();
164 	_AddTab(args);
165 }
166 
167 
168 TermWindow::~TermWindow()
169 {
170 	if (fPrefWindow)
171 		fPrefWindow->PostMessage(B_QUIT_REQUESTED);
172 
173 	if (fFindPanel && fFindPanel->Lock()) {
174 		fFindPanel->Quit();
175 		fFindPanel = NULL;
176 	}
177 
178 	PrefHandler::DeleteDefault();
179 
180 	for (int32 i = 0; Session* session = (Session*)fSessions.ItemAt(i); i++)
181 		delete session;
182 }
183 
184 
185 void
186 TermWindow::SetSessionWindowTitle(TermView* termView, const char* title)
187 {
188 	int32 index = _IndexOfTermView(termView);
189 	if (Session* session = (Session*)fSessions.ItemAt(index)) {
190 		session->windowTitle = title;
191 		BTab* tab = fTabView->TabAt(index);
192 		tab->SetLabel(session->windowTitle.String());
193 		if (index == fTabView->Selection())
194 			SetTitle(session->windowTitle.String());
195 	}
196 }
197 
198 
199 void
200 TermWindow::SessionChanged()
201 {
202 	int32 index = fTabView->Selection();
203 	if (Session* session = (Session*)fSessions.ItemAt(index))
204 		SetTitle(session->windowTitle.String());
205 }
206 
207 
208 void
209 TermWindow::_InitWindow()
210 {
211 	// make menu bar
212 	_SetupMenu();
213 
214 	// shortcuts to switch tabs
215 	for (int32 i = 0; i < 9; i++) {
216 		BMessage* message = new BMessage(kSetActiveTab);
217 		message->AddInt32("index", i);
218 		AddShortcut('1' + i, B_COMMAND_KEY, message);
219 	}
220 
221 	BRect textFrame = Bounds();
222 	textFrame.top = fMenubar->Bounds().bottom + 1.0;
223 
224 	fTabView = new TabView(this, textFrame, "tab view");
225 	AddChild(fTabView);
226 
227 	// Make the scroll view one pixel wider than the tab view container view, so
228 	// the scroll bar will look good.
229 	fTabView->SetInsets(0, 0, -1, 0);
230 }
231 
232 
233 bool
234 TermWindow::QuitRequested()
235 {
236 	bool warnOnExit = PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT);
237 
238 	if (!warnOnExit)
239 		return BWindow::QuitRequested();
240 
241 	bool isBusy = false;
242 	for (int32 i = 0; i < fSessions.CountItems(); i++) {
243 		if (_TermViewAt(i)->IsShellBusy()) {
244 			isBusy = true;
245 			break;
246 		}
247 	}
248 
249 	if (isBusy) {
250 		const char* alertMessage = B_TRANSLATE("A process is still running.\n"
251 			"If you close the Terminal the process will be killed.");
252 		BAlert* alert = new BAlert(B_TRANSLATE("Really quit?"),
253 			alertMessage, B_TRANSLATE("OK"), B_TRANSLATE("Cancel"), NULL,
254 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
255 		int32 result = alert->Go();
256 		if (result == 1)
257 			return false;
258 	}
259 
260 	return BWindow::QuitRequested();
261 }
262 
263 
264 void
265 TermWindow::MenusBeginning()
266 {
267 	TermView* view = _ActiveTermView();
268 
269 	// Syncronize Encode Menu Pop-up menu and Preference.
270 	BMenuItem* item = fEncodingmenu->FindItem(
271 		EncodingAsString(view->Encoding()));
272 	if (item != NULL)
273 		item->SetMarked(true);
274 
275 	BFont font;
276 	view->GetTermFont(&font);
277 
278 	float size = font.Size();
279 
280 	fDecreaseFontSizeMenuItem->SetEnabled(size > 9);
281 	fIncreaseFontSizeMenuItem->SetEnabled(size < 18);
282 
283 	BWindow::MenusBeginning();
284 }
285 
286 
287 /* static */
288 BMenu*
289 TermWindow::_MakeEncodingMenu()
290 {
291 	BMenu* menu = new (std::nothrow) BMenu(B_TRANSLATE("Text encoding"));
292 	if (menu == NULL)
293 		return NULL;
294 
295 	int encoding;
296 	int i = 0;
297 	while (get_next_encoding(i, &encoding) == B_OK) {
298 		BMessage *message = new BMessage(MENU_ENCODING);
299 		if (message != NULL) {
300 			message->AddInt32("op", (int32)encoding);
301 			menu->AddItem(new BMenuItem(EncodingAsString(encoding),
302 				message));
303 		}
304 		i++;
305 	}
306 
307 	menu->SetRadioMode(true);
308 
309 	return menu;
310 }
311 
312 
313 void
314 TermWindow::_SetupMenu()
315 {
316 	// Menu bar object.
317 	fMenubar = new BMenuBar(Bounds(), "mbar");
318 
319 	// Make File Menu.
320 	fFilemenu = new BMenu(B_TRANSLATE("Terminal"));
321 	fFilemenu->AddItem(new BMenuItem(B_TRANSLATE("Switch Terminals"),
322 		new BMessage(MENU_SWITCH_TERM), B_TAB));
323 	fFilemenu->AddItem(new BMenuItem(B_TRANSLATE("New Terminal"),
324 		new BMessage(MENU_NEW_TERM), 'N'));
325 	fFilemenu->AddItem(new BMenuItem(B_TRANSLATE("New tab"),
326 		new BMessage(kNewTab), 'T'));
327 
328 	fFilemenu->AddSeparatorItem();
329 	fFilemenu->AddItem(new BMenuItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS),
330 		new BMessage(MENU_PAGE_SETUP)));
331 	fFilemenu->AddItem(new BMenuItem(B_TRANSLATE("Print"),
332 		new BMessage(MENU_PRINT),'P'));
333 	fFilemenu->AddSeparatorItem();
334 	fFilemenu->AddItem(new BMenuItem(
335 		B_TRANSLATE("About Terminal" B_UTF8_ELLIPSIS),
336 		new BMessage(B_ABOUT_REQUESTED)));
337 	fFilemenu->AddSeparatorItem();
338 	fFilemenu->AddItem(new BMenuItem(B_TRANSLATE("Close active tab"),
339 		new BMessage(kCloseView), 'W', B_SHIFT_KEY));
340 	fFilemenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
341 		new BMessage(B_QUIT_REQUESTED), 'Q'));
342 	fMenubar->AddItem(fFilemenu);
343 
344 	// Make Edit Menu.
345 	fEditmenu = new BMenu("Edit");
346 	fEditmenu->AddItem(new BMenuItem(B_TRANSLATE("Copy"),
347 		new BMessage(B_COPY),'C'));
348 	fEditmenu->AddItem(new BMenuItem(B_TRANSLATE("Paste"),
349 		new BMessage(B_PASTE),'V'));
350 	fEditmenu->AddSeparatorItem();
351 	fEditmenu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
352 		new BMessage(B_SELECT_ALL), 'A'));
353 	fEditmenu->AddItem(new BMenuItem(B_TRANSLATE("Clear all"),
354 		new BMessage(MENU_CLEAR_ALL), 'L'));
355 	fEditmenu->AddSeparatorItem();
356 	fEditmenu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS),
357 		new BMessage(MENU_FIND_STRING),'F'));
358 	fFindPreviousMenuItem = new BMenuItem(B_TRANSLATE("Find previous"),
359 		new BMessage(MENU_FIND_PREVIOUS), 'G', B_SHIFT_KEY);
360 	fEditmenu->AddItem(fFindPreviousMenuItem);
361 	fFindPreviousMenuItem->SetEnabled(false);
362 	fFindNextMenuItem = new BMenuItem(B_TRANSLATE("Find next"),
363 		new BMessage(MENU_FIND_NEXT), 'G');
364 	fEditmenu->AddItem(fFindNextMenuItem);
365 	fFindNextMenuItem->SetEnabled(false);
366 
367 	fMenubar->AddItem(fEditmenu);
368 
369 	// Make Help Menu.
370 	fHelpmenu = new BMenu(B_TRANSLATE("Settings"));
371 	fWindowSizeMenu = _MakeWindowSizeMenu();
372 
373 	fEncodingmenu = _MakeEncodingMenu();
374 
375 	fSizeMenu = new BMenu(B_TRANSLATE("Text size"));
376 
377 	fIncreaseFontSizeMenuItem = new BMenuItem(B_TRANSLATE("Increase"),
378 		new BMessage(kIncreaseFontSize), '+', B_COMMAND_KEY);
379 
380 	fDecreaseFontSizeMenuItem = new BMenuItem(B_TRANSLATE("Decrease"),
381 		new BMessage(kDecreaseFontSize), '-', B_COMMAND_KEY);
382 
383 	fSizeMenu->AddItem(fIncreaseFontSizeMenuItem);
384 	fSizeMenu->AddItem(fDecreaseFontSizeMenuItem);
385 
386 	fHelpmenu->AddItem(fWindowSizeMenu);
387 	fHelpmenu->AddItem(fEncodingmenu);
388 	fHelpmenu->AddItem(fSizeMenu);
389 	fHelpmenu->AddSeparatorItem();
390 	fHelpmenu->AddItem(new BMenuItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS),
391 		new BMessage(MENU_PREF_OPEN)));
392 	fHelpmenu->AddSeparatorItem();
393 	fHelpmenu->AddItem(new BMenuItem(B_TRANSLATE("Save as default"),
394 		new BMessage(SAVE_AS_DEFAULT)));
395 	fMenubar->AddItem(fHelpmenu);
396 
397 	AddChild(fMenubar);
398 }
399 
400 
401 void
402 TermWindow::_GetPreferredFont(BFont& font)
403 {
404 	// Default to be_fixed_font
405 	font = be_fixed_font;
406 
407 	const char* family = PrefHandler::Default()->getString(PREF_HALF_FONT_FAMILY);
408 	const char* style = PrefHandler::Default()->getString(PREF_HALF_FONT_STYLE);
409 
410 	font.SetFamilyAndStyle(family, style);
411 
412 	float size = PrefHandler::Default()->getFloat(PREF_HALF_FONT_SIZE);
413 	if (size < 6.0f)
414 		size = 6.0f;
415 	font.SetSize(size);
416 }
417 
418 
419 void
420 TermWindow::MessageReceived(BMessage *message)
421 {
422 	int32 encodingId;
423 	bool findresult;
424 
425 	switch (message->what) {
426 		case B_COPY:
427 			_ActiveTermView()->Copy(be_clipboard);
428 			break;
429 
430 		case B_PASTE:
431 			_ActiveTermView()->Paste(be_clipboard);
432 			break;
433 
434 		case B_SELECT_ALL:
435 			_ActiveTermView()->SelectAll();
436 			break;
437 
438 		case B_ABOUT_REQUESTED:
439 			be_app->PostMessage(B_ABOUT_REQUESTED);
440 			break;
441 
442 		case MENU_CLEAR_ALL:
443 			_ActiveTermView()->Clear();
444 			break;
445 
446 		case MENU_SWITCH_TERM:
447 			be_app->PostMessage(MENU_SWITCH_TERM);
448 			break;
449 
450 		case MENU_NEW_TERM:
451 		{
452 			app_info info;
453 			be_app->GetAppInfo(&info);
454 
455 			// try launching two different ways to work around possible problems
456 			if (be_roster->Launch(&info.ref) != B_OK)
457 				be_roster->Launch(TERM_SIGNATURE);
458 			break;
459 		}
460 
461 		case MENU_PREF_OPEN:
462 			if (!fPrefWindow) {
463 				fPrefWindow = new PrefWindow(this);
464 			}
465 			else
466 				fPrefWindow->Activate();
467 			break;
468 
469 		case MSG_PREF_CLOSED:
470 			fPrefWindow = NULL;
471 			break;
472 
473 		case MENU_FIND_STRING:
474 			if (!fFindPanel) {
475 				fFindPanel = new FindWindow(this, fFindString, fFindSelection,
476 					fMatchWord, fMatchCase, fForwardSearch);
477 			}
478 			else
479 				fFindPanel->Activate();
480 			break;
481 
482 		case MSG_FIND:
483 		{
484 			fFindPanel->PostMessage(B_QUIT_REQUESTED);
485 			message->FindBool("findselection", &fFindSelection);
486 			if (!fFindSelection)
487 				message->FindString("findstring", &fFindString);
488 			else
489 				_ActiveTermView()->GetSelection(fFindString);
490 
491 			if (fFindString.Length() == 0) {
492 				const char* errorMsg = !fFindSelection
493 					? B_TRANSLATE("No search string was entered.")
494 					: B_TRANSLATE("Nothing is selected.");
495 				BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
496 					errorMsg, B_TRANSLATE("OK"), NULL, NULL,
497 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
498 
499 				alert->Go();
500 				fFindPreviousMenuItem->SetEnabled(false);
501 				fFindNextMenuItem->SetEnabled(false);
502 				break;
503 			}
504 
505 			message->FindBool("forwardsearch", &fForwardSearch);
506 			message->FindBool("matchcase", &fMatchCase);
507 			message->FindBool("matchword", &fMatchWord);
508 			findresult = _ActiveTermView()->Find(fFindString, fForwardSearch, fMatchCase, fMatchWord);
509 
510 			if (!findresult) {
511 				BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
512 					B_TRANSLATE("Text not found."),
513 					B_TRANSLATE("OK"), NULL, NULL,
514 					B_WIDTH_AS_USUAL, B_WARNING_ALERT);
515 				alert->SetShortcut(0, B_ESCAPE);
516 				alert->Go();
517 				fFindPreviousMenuItem->SetEnabled(false);
518 				fFindNextMenuItem->SetEnabled(false);
519 				break;
520 			}
521 
522 			// Enable the menu items Find Next and Find Previous
523 			fFindPreviousMenuItem->SetEnabled(true);
524 			fFindNextMenuItem->SetEnabled(true);
525 			break;
526 		}
527 
528 		case MENU_FIND_NEXT:
529 		case MENU_FIND_PREVIOUS:
530 			findresult = _ActiveTermView()->Find(fFindString,
531 				(message->what == MENU_FIND_NEXT) == fForwardSearch,
532 				fMatchCase, fMatchWord);
533 			if (!findresult) {
534 				BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
535 					B_TRANSLATE("Not found."), B_TRANSLATE("OK"),
536 					NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
537 				alert->SetShortcut(0, B_ESCAPE);
538 				alert->Go();
539 			}
540 			break;
541 
542 		case MSG_FIND_CLOSED:
543 			fFindPanel = NULL;
544 			break;
545 
546 		case MENU_ENCODING:
547 			if (message->FindInt32("op", &encodingId) == B_OK)
548 				_ActiveTermView()->SetEncoding(encodingId);
549 			break;
550 
551 		case MSG_COLS_CHANGED:
552 		{
553 			int32 columns, rows;
554 			message->FindInt32("columns", &columns);
555 			message->FindInt32("rows", &rows);
556 
557 			_ActiveTermView()->SetTermSize(rows, columns);
558 
559 			_ResizeView(_ActiveTermView());
560 			break;
561 		}
562 		case MSG_HALF_FONT_CHANGED:
563 		case MSG_FULL_FONT_CHANGED:
564 		case MSG_HALF_SIZE_CHANGED:
565 		case MSG_FULL_SIZE_CHANGED:
566 		{
567 			BFont font;
568 			_GetPreferredFont(font);
569 			_ActiveTermView()->SetTermFont(&font);
570 
571 			_ResizeView(_ActiveTermView());
572 			break;
573 		}
574 
575 		case FULLSCREEN:
576 			if (!fSavedFrame.IsValid()) { // go fullscreen
577 				_ActiveTermView()->DisableResizeView();
578 				float mbHeight = fMenubar->Bounds().Height() + 1;
579 				fSavedFrame = Frame();
580 				BScreen screen(this);
581 				if (fTabView->CountTabs() == 1)
582 					_ActiveTermView()->ScrollBar()->Hide();
583 
584 				fMenubar->Hide();
585 				fTabView->ResizeBy(0, mbHeight);
586 				fTabView->MoveBy(0, -mbHeight);
587 				fSavedLook = Look();
588 				// done before ResizeTo to work around a Dano bug (not erasing the decor)
589 				SetLook(B_NO_BORDER_WINDOW_LOOK);
590 				ResizeTo(screen.Frame().Width()+1, screen.Frame().Height()+1);
591 				MoveTo(screen.Frame().left, screen.Frame().top);
592 				fFullScreen = true;
593 			} else { // exit fullscreen
594 				_ActiveTermView()->DisableResizeView();
595 				float mbHeight = fMenubar->Bounds().Height() + 1;
596 				fMenubar->Show();
597 				_ActiveTermView()->ScrollBar()->Show();
598 				ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
599 				MoveTo(fSavedFrame.left, fSavedFrame.top);
600 				fTabView->ResizeBy(0, -mbHeight);
601 				fTabView->MoveBy(0, mbHeight);
602 				SetLook(fSavedLook);
603 				fSavedFrame = BRect(0,0,-1,-1);
604 				fFullScreen = false;
605 			}
606 			break;
607 
608 		case MSG_FONT_CHANGED:
609 			PostMessage(MSG_HALF_FONT_CHANGED);
610 			break;
611 
612 		case MSG_COLOR_CHANGED:
613 		case MSG_COLOR_SCHEMA_CHANGED:
614 		{
615 			_SetTermColors(_ActiveTermViewContainerView());
616 			_ActiveTermViewContainerView()->Invalidate();
617 			_ActiveTermView()->Invalidate();
618 			break;
619 		}
620 
621 		case SAVE_AS_DEFAULT:
622 		{
623 			BPath path;
624 			if (PrefHandler::GetDefaultPath(path) == B_OK)
625 				PrefHandler::Default()->SaveAsText(path.Path(), PREFFILE_MIMETYPE);
626 			break;
627 		}
628 		case MENU_PAGE_SETUP:
629 			_DoPageSetup();
630 			break;
631 
632 		case MENU_PRINT:
633 			_DoPrint();
634 			break;
635 
636 		case MSG_CHECK_CHILDREN:
637 			_CheckChildren();
638 			break;
639 
640 		case MSG_PREVIOUS_TAB:
641 		case MSG_NEXT_TAB:
642 		{
643 			TermView* termView;
644 			if (message->FindPointer("termView", (void**)&termView) == B_OK) {
645 				int32 count = fSessions.CountItems();
646 				int32 index = _IndexOfTermView(termView);
647 				if (count > 1 && index >= 0) {
648 					index += message->what == MSG_PREVIOUS_TAB ? -1 : 1;
649 					fTabView->Select((index + count) % count);
650 				}
651 			}
652 			break;
653 		}
654 
655 		case kSetActiveTab:
656 		{
657 			int32 index;
658 			if (message->FindInt32("index", &index) == B_OK
659 					&& index >= 0 && index < fSessions.CountItems()) {
660 				fTabView->Select(index);
661 			}
662 			break;
663 		}
664 
665 		case kNewTab:
666 			if (fTabView->CountTabs() < kMaxTabs) {
667 				if (fFullScreen)
668 					_ActiveTermView()->ScrollBar()->Show();
669 				_AddTab(NULL);
670 			}
671 			break;
672 
673 		case kCloseView:
674 		{
675 			TermView* termView;
676 			int32 index = -1;
677 			if (message->FindPointer("termView", (void**)&termView) == B_OK)
678 				index = _IndexOfTermView(termView);
679 			else
680 				index = _IndexOfTermView(_ActiveTermView());
681 
682 			if (index >= 0)
683 				_RemoveTab(index);
684 
685 			break;
686 		}
687 
688 		case kIncreaseFontSize:
689 		case kDecreaseFontSize:
690 		{
691 			TermView* view = _ActiveTermView();
692 			BFont font;
693 			view->GetTermFont(&font);
694 
695 			float size = font.Size();
696 			if (message->what == kIncreaseFontSize)
697 				size += 1;
698 			else
699 				size -= 1;
700 
701 			// limit the font size
702 			if (size < 9)
703 				size = 9;
704 			if (size > 18)
705 				size = 18;
706 
707 			font.SetSize(size);
708 			view->SetTermFont(&font);
709 			PrefHandler::Default()->setInt32(PREF_HALF_FONT_SIZE, (int32)size);
710 
711 			_ResizeView(view);
712 			break;
713 		}
714 
715 		default:
716 			BWindow::MessageReceived(message);
717 			break;
718 	}
719 }
720 
721 
722 void
723 TermWindow::WindowActivated(bool activated)
724 {
725 	BWindow::WindowActivated(activated);
726 }
727 
728 
729 void
730 TermWindow::_SetTermColors(TermViewContainerView* containerView)
731 {
732 	PrefHandler* handler = PrefHandler::Default();
733 	rgb_color background = handler->getRGB(PREF_TEXT_BACK_COLOR);
734 
735 	containerView->SetViewColor(background);
736 
737 	TermView *termView = containerView->GetTermView();
738 	termView->SetTextColor(handler->getRGB(PREF_TEXT_FORE_COLOR), background);
739 
740 	termView->SetSelectColor(handler->getRGB(PREF_SELECT_FORE_COLOR),
741 		handler->getRGB(PREF_SELECT_BACK_COLOR));
742 
743 	termView->SetCursorColor(handler->getRGB(PREF_CURSOR_FORE_COLOR),
744 		handler->getRGB(PREF_CURSOR_BACK_COLOR));
745 }
746 
747 
748 status_t
749 TermWindow::_DoPageSetup()
750 {
751 	BPrintJob job("PageSetup");
752 
753 	// display the page configure panel
754 	status_t status = job.ConfigPage();
755 
756 	// save a pointer to the settings
757 	fPrintSettings = job.Settings();
758 
759 	return status;
760 }
761 
762 
763 void
764 TermWindow::_DoPrint()
765 {
766 	if (!fPrintSettings || (_DoPageSetup() != B_OK)) {
767 		(new BAlert(B_TRANSLATE("Cancel"), B_TRANSLATE("Print cancelled."),
768 			B_TRANSLATE("OK")))->Go();
769 		return;
770 	}
771 
772 	BPrintJob job("Print");
773 	job.SetSettings(new BMessage(*fPrintSettings));
774 
775 	BRect pageRect = job.PrintableRect();
776 	BRect curPageRect = pageRect;
777 
778 	int pHeight = (int)pageRect.Height();
779 	int pWidth = (int)pageRect.Width();
780 	float w,h;
781 	_ActiveTermView()->GetFrameSize(&w, &h);
782 	int xPages = (int)ceil(w / pWidth);
783 	int yPages = (int)ceil(h / pHeight);
784 
785 	job.BeginJob();
786 
787 	// loop through and draw each page, and write to spool
788 	for (int x = 0; x < xPages; x++) {
789 		for (int y = 0; y < yPages; y++) {
790 			curPageRect.OffsetTo(x * pWidth, y * pHeight);
791 			job.DrawView(_ActiveTermView(), curPageRect, B_ORIGIN);
792 			job.SpoolPage();
793 
794 			if (!job.CanContinue()) {
795 				// It is likely that the only way that the job was cancelled is
796 				// because the user hit 'Cancel' in the page setup window, in
797 				// which case, the user does *not* need to be told that it was
798 				// cancelled.
799 				// He/she will simply expect that it was done.
800 				return;
801 			}
802 		}
803 	}
804 
805 	job.CommitJob();
806 }
807 
808 
809 void
810 TermWindow::_AddTab(Arguments* args)
811 {
812 	int argc = 0;
813 	const char* const* argv = NULL;
814 	if (args != NULL)
815 		args->GetShellArguments(argc, argv);
816 
817 	try {
818 		// Note: I don't pass the Arguments class directly to the termview,
819 		// only to avoid adding it as a dependency: in other words, to keep
820 		// the TermView class as agnostic as possible about the surrounding
821 		// world.
822 		CustomTermView* view = new CustomTermView(
823 			PrefHandler::Default()->getInt32(PREF_ROWS),
824 			PrefHandler::Default()->getInt32(PREF_COLS),
825 			argc, (const char**)argv,
826 			PrefHandler::Default()->getInt32(PREF_HISTORY_SIZE));
827 
828 		TermViewContainerView* containerView = new TermViewContainerView(view);
829 		BScrollView* scrollView = new TermScrollView("scrollView",
830 			containerView, view, fSessions.IsEmpty());
831 
832 		if (fSessions.IsEmpty())
833 			fTabView->SetScrollView(scrollView);
834 
835 		Session* session = new Session(_NewSessionID(), containerView);
836 		session->windowTitle = fInitialTitle;
837 		fSessions.AddItem(session);
838 
839 		int width, height;
840 		view->GetFontSize(&width, &height);
841 
842 		float minimumHeight = -1;
843 		if (fMenubar)
844 			minimumHeight += fMenubar->Bounds().Height() + 1;
845 		if (fTabView && fTabView->CountTabs() > 0)
846 			minimumHeight += fTabView->TabHeight() + 1;
847 		SetSizeLimits(MIN_COLS * width - 1, MAX_COLS * width - 1,
848 			minimumHeight + MIN_ROWS * height - 1,
849 			minimumHeight + MAX_ROWS * height - 1);
850 			// TODO: The size limit computation is apparently broken, since
851 			// the terminal can be resized smaller than MIN_ROWS/MIN_COLS!
852 
853 		// If it's the first time we're called, setup the window
854 		if (fTabView->CountTabs() == 0) {
855 			float viewWidth, viewHeight;
856 			containerView->GetPreferredSize(&viewWidth, &viewHeight);
857 
858 			// Resize Window
859 			ResizeTo(viewWidth + B_V_SCROLL_BAR_WIDTH,
860 				viewHeight + fMenubar->Bounds().Height() + 1);
861 				// NOTE: Width is one pixel too small, since the scroll view
862 				// is one pixel wider than its parent.
863 		}
864 
865 		BTab* tab = new BTab;
866 		fTabView->AddTab(scrollView, tab);
867 		tab->SetLabel(session->name.String());
868 			// TODO: Use a better name. For example, do like MacOS X's Terminal
869 			// and update the title using the last executed command ?
870 			// Or like Gnome's Terminal and use the current path ?
871 		view->SetScrollBar(scrollView->ScrollBar(B_VERTICAL));
872 		view->SetMouseClipboard(gMouseClipboard);
873 		view->SetEncoding(EncodingID(
874 			PrefHandler::Default()->getString(PREF_TEXT_ENCODING)));
875 
876 		BFont font;
877 		_GetPreferredFont(font);
878 		view->SetTermFont(&font);
879 
880 		_SetTermColors(containerView);
881 
882 		// TODO: No fTabView->Select(tab); ?
883 		fTabView->Select(fTabView->CountTabs() - 1);
884 	} catch (...) {
885 		// most probably out of memory. That's bad.
886 		// TODO: Should cleanup, I guess
887 
888 		// Quit the application if we don't have a shell already
889 		if (fTabView->CountTabs() == 0) {
890 			fprintf(stderr, "Terminal couldn't open a shell\n");
891 			PostMessage(B_QUIT_REQUESTED);
892 		}
893 	}
894 }
895 
896 
897 void
898 TermWindow::_RemoveTab(int32 index)
899 {
900 	if (fSessions.CountItems() > 1) {
901 		if (Session* session = (Session*)fSessions.RemoveItem(index)) {
902 			if (fSessions.CountItems() == 1) {
903 				fTabView->SetScrollView(dynamic_cast<BScrollView*>(
904 					((Session*)fSessions.ItemAt(0))->containerView->Parent()));
905 			}
906 
907 			delete session;
908 			delete fTabView->RemoveTab(index);
909 			if (fFullScreen)
910 				_ActiveTermView()->ScrollBar()->Hide();
911 		}
912 	} else
913 		PostMessage(B_QUIT_REQUESTED);
914 }
915 
916 
917 TermViewContainerView*
918 TermWindow::_ActiveTermViewContainerView() const
919 {
920 	return _TermViewContainerViewAt(fTabView->Selection());
921 }
922 
923 
924 TermViewContainerView*
925 TermWindow::_TermViewContainerViewAt(int32 index) const
926 {
927 	if (Session* session = (Session*)fSessions.ItemAt(index))
928 		return session->containerView;
929 	return NULL;
930 }
931 
932 
933 TermView*
934 TermWindow::_ActiveTermView() const
935 {
936 	return _ActiveTermViewContainerView()->GetTermView();
937 }
938 
939 
940 TermView*
941 TermWindow::_TermViewAt(int32 index) const
942 {
943 	TermViewContainerView* view = _TermViewContainerViewAt(index);
944 	return view != NULL ? view->GetTermView() : NULL;
945 }
946 
947 
948 int32
949 TermWindow::_IndexOfTermView(TermView* termView) const
950 {
951 	if (!termView)
952 		return -1;
953 
954 	// find the view
955 	int32 count = fTabView->CountTabs();
956 	for (int32 i = count - 1; i >= 0; i--) {
957 		if (termView == _TermViewAt(i))
958 			return i;
959 	}
960 
961 	return -1;
962 }
963 
964 
965 void
966 TermWindow::_CheckChildren()
967 {
968 	int32 count = fSessions.CountItems();
969 	for (int32 i = count - 1; i >= 0; i--) {
970 		Session* session = (Session*)fSessions.ItemAt(i);
971 		session->containerView->GetTermView()->CheckShellGone();
972 	}
973 }
974 
975 
976 void
977 TermWindow::Zoom(BPoint leftTop, float width, float height)
978 {
979 	_ActiveTermView()->DisableResizeView();
980 	BWindow::Zoom(leftTop, width, height);
981 }
982 
983 
984 void
985 TermWindow::FrameResized(float newWidth, float newHeight)
986 {
987 	BWindow::FrameResized(newWidth, newHeight);
988 
989 	TermView* view = _ActiveTermView();
990 	PrefHandler::Default()->setInt32(PREF_COLS, view->Columns());
991 	PrefHandler::Default()->setInt32(PREF_ROWS, view->Rows());
992 }
993 
994 
995 void
996 TermWindow::_ResizeView(TermView *view)
997 {
998 	int fontWidth, fontHeight;
999 	view->GetFontSize(&fontWidth, &fontHeight);
1000 
1001 	float minimumHeight = -1;
1002 	if (fMenubar)
1003 		minimumHeight += fMenubar->Bounds().Height() + 1;
1004 	if (fTabView && fTabView->CountTabs() > 1)
1005 		minimumHeight += fTabView->TabHeight() + 1;
1006 
1007 	SetSizeLimits(MIN_COLS * fontWidth - 1, MAX_COLS * fontWidth - 1,
1008 		minimumHeight + MIN_ROWS * fontHeight - 1,
1009 		minimumHeight + MAX_ROWS * fontHeight - 1);
1010 
1011 	float width;
1012 	float height;
1013 	view->Parent()->GetPreferredSize(&width, &height);
1014 	width += B_V_SCROLL_BAR_WIDTH;
1015 		// NOTE: Width is one pixel too small, since the scroll view
1016 		// is one pixel wider than its parent.
1017 	height += fMenubar->Bounds().Height() + 1;
1018 
1019 	ResizeTo(width, height);
1020 
1021 	view->Invalidate();
1022 }
1023 
1024 
1025 /* static */
1026 BMenu*
1027 TermWindow::_MakeWindowSizeMenu()
1028 {
1029 	BMenu* menu = new (std::nothrow) BMenu(B_TRANSLATE("Window size"));
1030 	if (menu == NULL)
1031 		return NULL;
1032 
1033 	const int32 windowSizes[4][2] = {
1034 		{ 80, 25 },
1035 		{ 80, 40 },
1036 		{ 132, 25 },
1037 		{ 132, 40 }
1038 	};
1039 
1040 	const int32 sizeNum = sizeof(windowSizes) / sizeof(windowSizes[0]);
1041 	for (int32 i = 0; i < sizeNum; i++) {
1042 		char label[32];
1043 		int32 columns = windowSizes[i][0];
1044 		int32 rows = windowSizes[i][1];
1045 		snprintf(label, sizeof(label), "%ldx%ld", columns, rows);
1046 		BMessage* message = new BMessage(MSG_COLS_CHANGED);
1047 		message->AddInt32("columns", columns);
1048 		message->AddInt32("rows", rows);
1049 		menu->AddItem(new BMenuItem(label, message));
1050 	}
1051 
1052 	menu->AddSeparatorItem();
1053 	menu->AddItem(new BMenuItem(B_TRANSLATE("Full screen"),
1054 		new BMessage(FULLSCREEN), B_ENTER));
1055 
1056 	return menu;
1057 }
1058 
1059 
1060 int32
1061 TermWindow::_NewSessionID()
1062 {
1063 	for (int32 id = 1; ; id++) {
1064 		bool used = false;
1065 
1066 		for (int32 i = 0;
1067 			 Session* session = (Session*)fSessions.ItemAt(i); i++) {
1068 			if (id == session->id) {
1069 				used = true;
1070 				break;
1071 			}
1072 		}
1073 
1074 		if (!used)
1075 			return id;
1076 	}
1077 }
1078 
1079 
1080 // #pragma mark -
1081 
1082 
1083 // CustomTermView
1084 CustomTermView::CustomTermView(int32 rows, int32 columns, int32 argc, const char **argv, int32 historySize)
1085 	:
1086 	TermView(rows, columns, argc, argv, historySize)
1087 {
1088 }
1089 
1090 
1091 void
1092 CustomTermView::NotifyQuit(int32 reason)
1093 {
1094 	BWindow* window = Window();
1095 	// TODO: If we got this from a view in a tab not currently selected,
1096 	// Window() will be NULL, as the view is detached.
1097 	// So we send the message to the first application window
1098 	// This isn't so cool, but should be safe, since a Terminal
1099 	// application has only one window, at least for now.
1100 	if (window == NULL)
1101 		window = be_app->WindowAt(0);
1102 
1103 	if (window != NULL) {
1104 		BMessage message(kCloseView);
1105 		message.AddPointer("termView", this);
1106 		message.AddInt32("reason", reason);
1107 		window->PostMessage(&message);
1108 	}
1109 }
1110 
1111 
1112 void
1113 CustomTermView::SetTitle(const char* title)
1114 {
1115 	dynamic_cast<TermWindow*>(Window())->SetSessionWindowTitle(this, title);
1116 }
1117 
1118