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