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