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