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