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