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