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