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