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