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