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