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