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