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