1 /* 2 * Copyright 2007-2010, Haiku, Inc. All rights reserved. 3 * Copyright (c) 2004 Daniel Furrer <assimil8or@users.sourceforge.net> 4 * Copyright (c) 2003-2004 Kian Duffy <myob@users.sourceforge.net> 5 * Copyright (C) 1998,99 Kazuho Okui and Takashi Murai. 6 * 7 * Distributed under the terms of the MIT license. 8 */ 9 10 #include "TermWindow.h" 11 12 #include <new> 13 #include <stdio.h> 14 #include <string.h> 15 #include <time.h> 16 17 #include <Alert.h> 18 #include <Application.h> 19 #include <Catalog.h> 20 #include <Clipboard.h> 21 #include <Dragger.h> 22 #include <File.h> 23 #include <FindDirectory.h> 24 #include <LayoutBuilder.h> 25 #include <LayoutUtils.h> 26 #include <Locale.h> 27 #include <Menu.h> 28 #include <MenuBar.h> 29 #include <MenuItem.h> 30 #include <Path.h> 31 #include <PopUpMenu.h> 32 #include <PrintJob.h> 33 #include <Roster.h> 34 #include <Screen.h> 35 #include <ScrollBar.h> 36 #include <ScrollView.h> 37 #include <String.h> 38 39 #include <AutoLocker.h> 40 41 #include "ActiveProcessInfo.h" 42 #include "Arguments.h" 43 #include "AppearPrefView.h" 44 #include "Encoding.h" 45 #include "FindWindow.h" 46 #include "Globals.h" 47 #include "PrefWindow.h" 48 #include "PrefHandler.h" 49 #include "SetTitleDialog.h" 50 #include "ShellParameters.h" 51 #include "TermConst.h" 52 #include "TermScrollView.h" 53 #include "TitlePlaceholderMapper.h" 54 55 56 const static int32 kMaxTabs = 6; 57 const static int32 kTermViewOffset = 3; 58 59 // messages constants 60 static const uint32 kNewTab = 'NTab'; 61 static const uint32 kCloseView = 'ClVw'; 62 static const uint32 kCloseOtherViews = 'CloV'; 63 static const uint32 kIncreaseFontSize = 'InFs'; 64 static const uint32 kDecreaseFontSize = 'DcFs'; 65 static const uint32 kSetActiveTab = 'STab'; 66 static const uint32 kUpdateTitles = 'UPti'; 67 static const uint32 kEditTabTitle = 'ETti'; 68 static const uint32 kEditWindowTitle = 'EWti'; 69 static const uint32 kTabTitleChanged = 'TTch'; 70 static const uint32 kWindowTitleChanged = 'WTch'; 71 static const uint32 kUpdateSwitchTerminalsMenuItem = 'Ustm'; 72 73 74 #undef B_TRANSLATE_CONTEXT 75 #define B_TRANSLATE_CONTEXT "Terminal TermWindow" 76 77 78 // #pragma mark - TermViewContainerView 79 80 81 class TermViewContainerView : public BView { 82 public: 83 TermViewContainerView(TermView* termView) 84 : 85 BView(BRect(), "term view container", B_FOLLOW_ALL, 0), 86 fTermView(termView) 87 { 88 termView->MoveTo(kTermViewOffset, kTermViewOffset); 89 BRect frame(termView->Frame()); 90 ResizeTo(frame.right + kTermViewOffset, frame.bottom + kTermViewOffset); 91 AddChild(termView); 92 } 93 94 TermView* GetTermView() const { return fTermView; } 95 96 virtual void GetPreferredSize(float* _width, float* _height) 97 { 98 float width, height; 99 fTermView->GetPreferredSize(&width, &height); 100 *_width = width + 2 * kTermViewOffset; 101 *_height = height + 2 * kTermViewOffset; 102 } 103 104 private: 105 TermView* fTermView; 106 }; 107 108 109 // #pragma mark - SessionID 110 111 112 TermWindow::SessionID::SessionID(int32 id) 113 : 114 fID(id) 115 { 116 } 117 118 119 TermWindow::SessionID::SessionID(const BMessage& message, const char* field) 120 { 121 if (message.FindInt32(field, &fID) != B_OK) 122 fID = -1; 123 } 124 125 126 status_t 127 TermWindow::SessionID::AddToMessage(BMessage& message, const char* field) const 128 { 129 return message.AddInt32(field, fID); 130 } 131 132 133 // #pragma mark - Session 134 135 136 struct TermWindow::Session { 137 SessionID id; 138 int32 index; 139 Title title; 140 TermViewContainerView* containerView; 141 142 Session(SessionID id, int32 index, TermViewContainerView* containerView) 143 : 144 id(id), 145 index(index), 146 containerView(containerView) 147 { 148 title.title = B_TRANSLATE("Shell "); 149 title.title << index; 150 title.patternUserDefined = false; 151 } 152 }; 153 154 155 // #pragma mark - TermWindow 156 157 158 TermWindow::TermWindow(const BString& title, Arguments* args) 159 : 160 BWindow(BRect(0, 0, 0, 0), title, B_DOCUMENT_WINDOW, 161 B_CURRENT_WORKSPACE | B_QUIT_ON_WINDOW_CLOSE), 162 fTitleUpdateRunner(this, BMessage(kUpdateTitles), 1000000), 163 fNextSessionID(0), 164 fTabView(NULL), 165 fMenuBar(NULL), 166 fSwitchTerminalsMenuItem(NULL), 167 fEncodingMenu(NULL), 168 fPrintSettings(NULL), 169 fPrefWindow(NULL), 170 fFindPanel(NULL), 171 fSavedFrame(0, 0, -1, -1), 172 fSetWindowTitleDialog(NULL), 173 fSetTabTitleDialog(NULL), 174 fFindString(""), 175 fFindNextMenuItem(NULL), 176 fFindPreviousMenuItem(NULL), 177 fFindSelection(false), 178 fForwardSearch(false), 179 fMatchCase(false), 180 fMatchWord(false), 181 fFullScreen(false) 182 { 183 // register this terminal 184 fTerminalRoster.Register(Team(), this); 185 fTerminalRoster.SetListener(this); 186 int32 id = fTerminalRoster.ID(); 187 188 // apply the title settings 189 fTitle.pattern = title; 190 if (fTitle.pattern.Length() == 0) { 191 fTitle.pattern = B_TRANSLATE_SYSTEM_NAME("Terminal"); 192 193 if (id >= 0) 194 fTitle.pattern << " " << id + 1; 195 196 fTitle.patternUserDefined = false; 197 } else 198 fTitle.patternUserDefined = true; 199 200 fTitle.title = fTitle.pattern; 201 fTitle.pattern = title; 202 203 _TitleSettingsChanged(); 204 205 // get the saved window position and workspaces 206 BRect frame; 207 uint32 workspaces; 208 if (_LoadWindowPosition(&frame, &workspaces) == B_OK) { 209 // apply 210 MoveTo(frame.LeftTop()); 211 ResizeTo(frame.Width(), frame.Height()); 212 SetWorkspaces(workspaces); 213 } else { 214 // use computed defaults 215 int i = id / 16; 216 int j = id % 16; 217 int k = (j * 16) + (i * 64) + 50; 218 int l = (j * 16) + 50; 219 220 MoveTo(k, l); 221 } 222 223 // init the GUI and add a tab 224 _InitWindow(); 225 _AddTab(args); 226 227 // Announce our window as no longer minimized. That's not true, since it's 228 // still hidden at this point, but it will be shown very soon. 229 fTerminalRoster.SetWindowInfo(false, Workspaces()); 230 } 231 232 233 TermWindow::~TermWindow() 234 { 235 fTerminalRoster.Unregister(); 236 237 _FinishTitleDialog(); 238 239 if (fPrefWindow) 240 fPrefWindow->PostMessage(B_QUIT_REQUESTED); 241 242 if (fFindPanel && fFindPanel->Lock()) { 243 fFindPanel->Quit(); 244 fFindPanel = NULL; 245 } 246 247 PrefHandler::DeleteDefault(); 248 249 for (int32 i = 0; Session* session = _SessionAt(i); i++) 250 delete session; 251 } 252 253 254 void 255 TermWindow::SessionChanged() 256 { 257 _UpdateSessionTitle(fTabView->Selection()); 258 } 259 260 261 void 262 TermWindow::_InitWindow() 263 { 264 // make menu bar 265 _SetupMenu(); 266 267 // shortcuts to switch tabs 268 for (int32 i = 0; i < 9; i++) { 269 BMessage* message = new BMessage(kSetActiveTab); 270 message->AddInt32("index", i); 271 AddShortcut('1' + i, B_COMMAND_KEY, message); 272 } 273 274 AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY, 275 new BMessage(MSG_MOVE_TAB_LEFT)); 276 AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY, 277 new BMessage(MSG_MOVE_TAB_RIGHT)); 278 279 BRect textFrame = Bounds(); 280 textFrame.top = fMenuBar->Bounds().bottom + 1.0; 281 282 fTabView = new SmartTabView(textFrame, "tab view", B_WIDTH_FROM_WIDEST); 283 fTabView->SetListener(this); 284 AddChild(fTabView); 285 286 // Make the scroll view one pixel wider than the tab view container view, so 287 // the scroll bar will look good. 288 fTabView->SetInsets(0, 0, -1, 0); 289 } 290 291 292 bool 293 TermWindow::_CanClose(int32 index) 294 { 295 bool warnOnExit = PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT); 296 297 if (!warnOnExit) 298 return true; 299 300 uint32 busyProcessCount = 0; 301 BString busyProcessNames; 302 // all names, separated by "\n\t" 303 304 if (index != -1) { 305 ShellInfo shellInfo; 306 ActiveProcessInfo info; 307 TermView* termView = _TermViewAt(index); 308 if (termView->GetShellInfo(shellInfo) 309 && termView->GetActiveProcessInfo(info) 310 && (info.ID() != shellInfo.ProcessID() 311 || !shellInfo.IsDefaultShell())) { 312 busyProcessCount++; 313 busyProcessNames = info.Name(); 314 } 315 } else { 316 for (int32 i = 0; i < fSessions.CountItems(); i++) { 317 ShellInfo shellInfo; 318 ActiveProcessInfo info; 319 TermView* termView = _TermViewAt(i); 320 if (termView->GetShellInfo(shellInfo) 321 && termView->GetActiveProcessInfo(info) 322 && (info.ID() != shellInfo.ProcessID() 323 || !shellInfo.IsDefaultShell())) { 324 if (++busyProcessCount > 1) 325 busyProcessNames << "\n\t"; 326 busyProcessNames << info.Name(); 327 } 328 } 329 } 330 331 if (busyProcessCount == 0) 332 return true; 333 334 BString alertMessage; 335 if (busyProcessCount == 1) { 336 // Only one pending process. Select the alert text depending on whether 337 // the terminal will be closed. 338 alertMessage = index == -1 || fSessions.CountItems() == 1 339 ? B_TRANSLATE("The process \"%1\" is still running.\n" 340 "If you close the Terminal, the process will be killed.") 341 : B_TRANSLATE("The process \"%1\" is still running.\n" 342 "If you close the tab, the process will be killed."); 343 } else { 344 // multiple pending processes 345 alertMessage = B_TRANSLATE( 346 "The following processes are still running:\n\n" 347 "\t%1\n\n" 348 "If you close the Terminal, the processes will be killed."); 349 } 350 351 alertMessage.ReplaceFirst("%1", busyProcessNames); 352 353 BAlert* alert = new BAlert(B_TRANSLATE("Really close?"), 354 alertMessage, B_TRANSLATE("Close"), B_TRANSLATE("Cancel"), NULL, 355 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 356 alert->SetShortcut(1, B_ESCAPE); 357 return alert->Go() == 0; 358 } 359 360 361 bool 362 TermWindow::QuitRequested() 363 { 364 _FinishTitleDialog(); 365 366 if (!_CanClose(-1)) 367 return false; 368 369 _SaveWindowPosition(); 370 371 return BWindow::QuitRequested(); 372 } 373 374 375 void 376 TermWindow::MenusBeginning() 377 { 378 TermView* view = _ActiveTermView(); 379 380 // Syncronize Encode Menu Pop-up menu and Preference. 381 BMenuItem* item = fEncodingMenu->FindItem( 382 EncodingAsString(view->Encoding())); 383 if (item != NULL) 384 item->SetMarked(true); 385 386 BFont font; 387 view->GetTermFont(&font); 388 389 float size = font.Size(); 390 391 fDecreaseFontSizeMenuItem->SetEnabled(size > 9); 392 fIncreaseFontSizeMenuItem->SetEnabled(size < 18); 393 394 BWindow::MenusBeginning(); 395 } 396 397 398 /* static */ 399 BMenu* 400 TermWindow::_MakeEncodingMenu() 401 { 402 BMenu* menu = new (std::nothrow) BMenu(B_TRANSLATE("Text encoding")); 403 if (menu == NULL) 404 return NULL; 405 406 int encoding; 407 int i = 0; 408 while (get_next_encoding(i, &encoding) == B_OK) { 409 BMessage *message = new BMessage(MENU_ENCODING); 410 if (message != NULL) { 411 message->AddInt32("op", (int32)encoding); 412 menu->AddItem(new BMenuItem(EncodingAsString(encoding), 413 message)); 414 } 415 i++; 416 } 417 418 menu->SetRadioMode(true); 419 420 return menu; 421 } 422 423 424 void 425 TermWindow::_SetupMenu() 426 { 427 BLayoutBuilder::Menu<>(fMenuBar = new BMenuBar(Bounds(), "mbar")) 428 // Terminal 429 .AddMenu(B_TRANSLATE_SYSTEM_NAME("Terminal")) 430 .AddItem(B_TRANSLATE("Switch Terminals"), MENU_SWITCH_TERM, B_TAB) 431 .GetItem(fSwitchTerminalsMenuItem) 432 .AddItem(B_TRANSLATE("New Terminal"), MENU_NEW_TERM, 'N') 433 .AddItem(B_TRANSLATE("New tab"), kNewTab, 'T') 434 .AddSeparator() 435 .AddItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), MENU_PAGE_SETUP) 436 .AddItem(B_TRANSLATE("Print"), MENU_PRINT,'P') 437 .AddSeparator() 438 .AddItem(B_TRANSLATE("Close window"), B_QUIT_REQUESTED, 'W', 439 B_SHIFT_KEY) 440 .AddItem(B_TRANSLATE("Close active tab"), kCloseView, 'W') 441 .AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q') 442 .End() 443 444 // Edit 445 .AddMenu(B_TRANSLATE("Edit")) 446 .AddItem(B_TRANSLATE("Copy"), B_COPY,'C') 447 .AddItem(B_TRANSLATE("Paste"), B_PASTE,'V') 448 .AddSeparator() 449 .AddItem(B_TRANSLATE("Select all"), B_SELECT_ALL, 'A') 450 .AddItem(B_TRANSLATE("Clear all"), MENU_CLEAR_ALL, 'L') 451 .AddSeparator() 452 .AddItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), MENU_FIND_STRING,'F') 453 .AddItem(B_TRANSLATE("Find previous"), MENU_FIND_PREVIOUS, 'G', 454 B_SHIFT_KEY) 455 .GetItem(fFindPreviousMenuItem) 456 .SetEnabled(false) 457 .AddItem(B_TRANSLATE("Find next"), MENU_FIND_NEXT, 'G') 458 .GetItem(fFindNextMenuItem) 459 .SetEnabled(false) 460 .AddSeparator() 461 .AddItem(B_TRANSLATE("Window title" B_UTF8_ELLIPSIS), 462 kEditWindowTitle) 463 .End() 464 465 // Settings 466 .AddMenu(B_TRANSLATE("Settings")) 467 .AddItem(_MakeWindowSizeMenu()) 468 .AddItem(fEncodingMenu = _MakeEncodingMenu()) 469 .AddMenu(B_TRANSLATE("Text size")) 470 .AddItem(B_TRANSLATE("Increase"), kIncreaseFontSize, '+', 471 B_COMMAND_KEY) 472 .GetItem(fIncreaseFontSizeMenuItem) 473 .AddItem(B_TRANSLATE("Decrease"), kDecreaseFontSize, '-', 474 B_COMMAND_KEY) 475 .GetItem(fDecreaseFontSizeMenuItem) 476 .End() 477 .AddSeparator() 478 .AddItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), MENU_PREF_OPEN) 479 .AddSeparator() 480 .AddItem(B_TRANSLATE("Save as default"), SAVE_AS_DEFAULT) 481 .End() 482 ; 483 484 AddChild(fMenuBar); 485 486 _UpdateSwitchTerminalsMenuItem(); 487 } 488 489 490 status_t 491 TermWindow::_GetWindowPositionFile(BFile* file, uint32 openMode) 492 { 493 BPath path; 494 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 495 if (status != B_OK) 496 return status; 497 498 status = path.Append("Terminal_windows"); 499 if (status != B_OK) 500 return status; 501 502 return file->SetTo(path.Path(), openMode); 503 } 504 505 506 status_t 507 TermWindow::_LoadWindowPosition(BRect* frame, uint32* workspaces) 508 { 509 status_t status; 510 BMessage position; 511 512 BFile file; 513 status = _GetWindowPositionFile(&file, B_READ_ONLY); 514 if (status != B_OK) 515 return status; 516 517 status = position.Unflatten(&file); 518 519 file.Unset(); 520 521 if (status != B_OK) 522 return status; 523 524 int32 id = fTerminalRoster.ID(); 525 status = position.FindRect("rect", id, frame); 526 if (status != B_OK) 527 return status; 528 529 int32 _workspaces; 530 status = position.FindInt32("workspaces", id, &_workspaces); 531 if (status != B_OK) 532 return status; 533 if (modifiers() & B_SHIFT_KEY) 534 *workspaces = _workspaces; 535 else 536 *workspaces = B_CURRENT_WORKSPACE; 537 538 return B_OK; 539 } 540 541 542 status_t 543 TermWindow::_SaveWindowPosition() 544 { 545 BFile file; 546 BMessage originalSettings; 547 548 // Read the settings file if it exists and is a valid BMessage. 549 status_t status = _GetWindowPositionFile(&file, B_READ_ONLY); 550 if (status == B_OK) { 551 status = originalSettings.Unflatten(&file); 552 file.Unset(); 553 554 if (status != B_OK) 555 status = originalSettings.MakeEmpty(); 556 557 if (status != B_OK) 558 return status; 559 } 560 561 // Replace the settings 562 int32 id = fTerminalRoster.ID(); 563 BRect rect(Frame()); 564 if (originalSettings.ReplaceRect("rect", id, rect) != B_OK) 565 originalSettings.AddRect("rect", rect); 566 567 int32 workspaces = Workspaces(); 568 if (originalSettings.ReplaceInt32("workspaces", id, workspaces) != B_OK) 569 originalSettings.AddInt32("workspaces", workspaces); 570 571 // Resave the whole thing 572 status = _GetWindowPositionFile (&file, 573 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 574 if (status != B_OK) 575 return status; 576 577 return originalSettings.Flatten(&file); 578 } 579 580 581 void 582 TermWindow::_GetPreferredFont(BFont& font) 583 { 584 // Default to be_fixed_font 585 font = be_fixed_font; 586 587 const char* family = PrefHandler::Default()->getString(PREF_HALF_FONT_FAMILY); 588 const char* style = PrefHandler::Default()->getString(PREF_HALF_FONT_STYLE); 589 590 font.SetFamilyAndStyle(family, style); 591 592 float size = PrefHandler::Default()->getFloat(PREF_HALF_FONT_SIZE); 593 if (size < 6.0f) 594 size = 6.0f; 595 font.SetSize(size); 596 } 597 598 599 void 600 TermWindow::MessageReceived(BMessage *message) 601 { 602 int32 encodingId; 603 bool findresult; 604 605 switch (message->what) { 606 case B_COPY: 607 _ActiveTermView()->Copy(be_clipboard); 608 break; 609 610 case B_PASTE: 611 _ActiveTermView()->Paste(be_clipboard); 612 break; 613 614 case B_SELECT_ALL: 615 _ActiveTermView()->SelectAll(); 616 break; 617 618 case MENU_CLEAR_ALL: 619 _ActiveTermView()->Clear(); 620 break; 621 622 case MENU_SWITCH_TERM: 623 _SwitchTerminal(); 624 break; 625 626 case MENU_NEW_TERM: 627 { 628 // Set our current working directory to that of the active tab, so 629 // that the new terminal and its shell inherit it. 630 // Note: That's a bit lame. We should rather fork() and change the 631 // CWD in the child, but since ATM there aren't any side effects of 632 // changing our CWD, we save ourselves the trouble. 633 ActiveProcessInfo activeProcessInfo; 634 if (_ActiveTermView()->GetActiveProcessInfo(activeProcessInfo)) 635 chdir(activeProcessInfo.CurrentDirectory()); 636 637 app_info info; 638 be_app->GetAppInfo(&info); 639 640 // try launching two different ways to work around possible problems 641 if (be_roster->Launch(&info.ref) != B_OK) 642 be_roster->Launch(TERM_SIGNATURE); 643 break; 644 } 645 646 case MENU_PREF_OPEN: 647 if (!fPrefWindow) { 648 fPrefWindow = new PrefWindow(this); 649 } 650 else 651 fPrefWindow->Activate(); 652 break; 653 654 case MSG_PREF_CLOSED: 655 fPrefWindow = NULL; 656 break; 657 658 case MSG_WINDOW_TITLE_SETTING_CHANGED: 659 case MSG_TAB_TITLE_SETTING_CHANGED: 660 _TitleSettingsChanged(); 661 break; 662 663 case MENU_FIND_STRING: 664 if (!fFindPanel) { 665 fFindPanel = new FindWindow(this, fFindString, fFindSelection, 666 fMatchWord, fMatchCase, fForwardSearch); 667 } 668 else 669 fFindPanel->Activate(); 670 break; 671 672 case MSG_FIND: 673 { 674 fFindPanel->PostMessage(B_QUIT_REQUESTED); 675 message->FindBool("findselection", &fFindSelection); 676 if (!fFindSelection) 677 message->FindString("findstring", &fFindString); 678 else 679 _ActiveTermView()->GetSelection(fFindString); 680 681 if (fFindString.Length() == 0) { 682 const char* errorMsg = !fFindSelection 683 ? B_TRANSLATE("No search string was entered.") 684 : B_TRANSLATE("Nothing is selected."); 685 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"), 686 errorMsg, B_TRANSLATE("OK"), NULL, NULL, 687 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 688 alert->SetShortcut(0, B_ESCAPE); 689 690 alert->Go(); 691 fFindPreviousMenuItem->SetEnabled(false); 692 fFindNextMenuItem->SetEnabled(false); 693 break; 694 } 695 696 message->FindBool("forwardsearch", &fForwardSearch); 697 message->FindBool("matchcase", &fMatchCase); 698 message->FindBool("matchword", &fMatchWord); 699 findresult = _ActiveTermView()->Find(fFindString, fForwardSearch, fMatchCase, fMatchWord); 700 701 if (!findresult) { 702 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"), 703 B_TRANSLATE("Text not found."), 704 B_TRANSLATE("OK"), NULL, NULL, 705 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 706 alert->SetShortcut(0, B_ESCAPE); 707 alert->Go(); 708 fFindPreviousMenuItem->SetEnabled(false); 709 fFindNextMenuItem->SetEnabled(false); 710 break; 711 } 712 713 // Enable the menu items Find Next and Find Previous 714 fFindPreviousMenuItem->SetEnabled(true); 715 fFindNextMenuItem->SetEnabled(true); 716 break; 717 } 718 719 case MENU_FIND_NEXT: 720 case MENU_FIND_PREVIOUS: 721 findresult = _ActiveTermView()->Find(fFindString, 722 (message->what == MENU_FIND_NEXT) == fForwardSearch, 723 fMatchCase, fMatchWord); 724 if (!findresult) { 725 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"), 726 B_TRANSLATE("Not found."), B_TRANSLATE("OK"), 727 NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 728 alert->SetShortcut(0, B_ESCAPE); 729 alert->Go(); 730 } 731 break; 732 733 case MSG_FIND_CLOSED: 734 fFindPanel = NULL; 735 break; 736 737 case MENU_ENCODING: 738 if (message->FindInt32("op", &encodingId) == B_OK) 739 _ActiveTermView()->SetEncoding(encodingId); 740 break; 741 742 case MSG_COLS_CHANGED: 743 { 744 int32 columns, rows; 745 message->FindInt32("columns", &columns); 746 message->FindInt32("rows", &rows); 747 748 _ActiveTermView()->SetTermSize(rows, columns); 749 750 _ResizeView(_ActiveTermView()); 751 break; 752 } 753 case MSG_HALF_FONT_CHANGED: 754 case MSG_FULL_FONT_CHANGED: 755 case MSG_HALF_SIZE_CHANGED: 756 case MSG_FULL_SIZE_CHANGED: 757 { 758 BFont font; 759 _GetPreferredFont(font); 760 _ActiveTermView()->SetTermFont(&font); 761 762 _ResizeView(_ActiveTermView()); 763 break; 764 } 765 766 case FULLSCREEN: 767 if (!fSavedFrame.IsValid()) { // go fullscreen 768 _ActiveTermView()->DisableResizeView(); 769 float mbHeight = fMenuBar->Bounds().Height() + 1; 770 fSavedFrame = Frame(); 771 BScreen screen(this); 772 for (int32 i = fTabView->CountTabs() - 1; i >=0 ; i--) 773 _TermViewAt(i)->ScrollBar()->ResizeBy(0, (B_H_SCROLL_BAR_HEIGHT - 1)); 774 775 fMenuBar->Hide(); 776 fTabView->ResizeBy(0, mbHeight); 777 fTabView->MoveBy(0, -mbHeight); 778 fSavedLook = Look(); 779 // done before ResizeTo to work around a Dano bug (not erasing the decor) 780 SetLook(B_NO_BORDER_WINDOW_LOOK); 781 ResizeTo(screen.Frame().Width()+1, screen.Frame().Height()+1); 782 MoveTo(screen.Frame().left, screen.Frame().top); 783 fFullScreen = true; 784 } else { // exit fullscreen 785 _ActiveTermView()->DisableResizeView(); 786 float mbHeight = fMenuBar->Bounds().Height() + 1; 787 fMenuBar->Show(); 788 for (int32 i = fTabView->CountTabs() - 1; i >=0 ; i--) 789 _TermViewAt(i)->ScrollBar()->ResizeBy(0, -(B_H_SCROLL_BAR_HEIGHT - 1)); 790 ResizeTo(fSavedFrame.Width(), fSavedFrame.Height()); 791 MoveTo(fSavedFrame.left, fSavedFrame.top); 792 fTabView->ResizeBy(0, -mbHeight); 793 fTabView->MoveBy(0, mbHeight); 794 SetLook(fSavedLook); 795 fSavedFrame = BRect(0,0,-1,-1); 796 fFullScreen = false; 797 } 798 break; 799 800 case MSG_FONT_CHANGED: 801 PostMessage(MSG_HALF_FONT_CHANGED); 802 break; 803 804 case MSG_COLOR_CHANGED: 805 case MSG_COLOR_SCHEMA_CHANGED: 806 { 807 _SetTermColors(_ActiveTermViewContainerView()); 808 _ActiveTermViewContainerView()->Invalidate(); 809 _ActiveTermView()->Invalidate(); 810 break; 811 } 812 813 case SAVE_AS_DEFAULT: 814 { 815 BPath path; 816 if (PrefHandler::GetDefaultPath(path) == B_OK) 817 PrefHandler::Default()->SaveAsText(path.Path(), PREFFILE_MIMETYPE); 818 break; 819 } 820 case MENU_PAGE_SETUP: 821 _DoPageSetup(); 822 break; 823 824 case MENU_PRINT: 825 _DoPrint(); 826 break; 827 828 case MSG_CHECK_CHILDREN: 829 _CheckChildren(); 830 break; 831 832 case MSG_MOVE_TAB_LEFT: 833 case MSG_MOVE_TAB_RIGHT: 834 _NavigateTab(_IndexOfTermView(_ActiveTermView()), 835 message->what == MSG_MOVE_TAB_LEFT ? -1 : 1, true); 836 break; 837 838 case kTabTitleChanged: 839 { 840 // tab title changed message from SetTitleDialog 841 SessionID sessionID(*message, "session"); 842 if (Session* session = _SessionForID(sessionID)) { 843 BString title; 844 if (message->FindString("title", &title) == B_OK) { 845 session->title.pattern = title; 846 session->title.patternUserDefined = true; 847 } else { 848 session->title.pattern.Truncate(0); 849 session->title.patternUserDefined = false; 850 } 851 _UpdateSessionTitle(_IndexOfSession(session)); 852 } 853 break; 854 } 855 856 case kWindowTitleChanged: 857 { 858 // window title changed message from SetTitleDialog 859 BString title; 860 if (message->FindString("title", &title) == B_OK) { 861 fTitle.pattern = title; 862 fTitle.patternUserDefined = true; 863 } else { 864 fTitle.pattern 865 = PrefHandler::Default()->getString(PREF_WINDOW_TITLE); 866 fTitle.patternUserDefined = false; 867 } 868 869 _UpdateSessionTitle(fTabView->Selection()); 870 // updates the window title as a side effect 871 872 break; 873 } 874 875 case kSetActiveTab: 876 { 877 int32 index; 878 if (message->FindInt32("index", &index) == B_OK 879 && index >= 0 && index < fSessions.CountItems()) { 880 fTabView->Select(index); 881 } 882 break; 883 } 884 885 case kNewTab: 886 _NewTab(); 887 break; 888 889 case kCloseView: 890 { 891 int32 index = -1; 892 SessionID sessionID(*message, "session"); 893 if (sessionID.IsValid()) { 894 if (Session* session = _SessionForID(sessionID)) 895 index = _IndexOfSession(session); 896 } else 897 index = _IndexOfTermView(_ActiveTermView()); 898 899 if (index >= 0) 900 _RemoveTab(index); 901 902 break; 903 } 904 905 case kCloseOtherViews: 906 { 907 Session* session = _SessionForID(SessionID(*message, "session")); 908 if (session == NULL) 909 break; 910 911 int32 count = fSessions.CountItems(); 912 for (int32 i = count - 1; i >= 0; i--) { 913 if (_SessionAt(i) != session) 914 _RemoveTab(i); 915 } 916 917 break; 918 } 919 920 case kIncreaseFontSize: 921 case kDecreaseFontSize: 922 { 923 TermView* view = _ActiveTermView(); 924 BFont font; 925 view->GetTermFont(&font); 926 927 float size = font.Size(); 928 if (message->what == kIncreaseFontSize) 929 size += 1; 930 else 931 size -= 1; 932 933 // limit the font size 934 if (size < 9) 935 size = 9; 936 if (size > 18) 937 size = 18; 938 939 font.SetSize(size); 940 view->SetTermFont(&font); 941 PrefHandler::Default()->setInt32(PREF_HALF_FONT_SIZE, (int32)size); 942 943 _ResizeView(view); 944 break; 945 } 946 947 case kUpdateTitles: 948 _UpdateTitles(); 949 break; 950 951 case kEditTabTitle: 952 { 953 SessionID sessionID(*message, "session"); 954 if (Session* session = _SessionForID(sessionID)) 955 _OpenSetTabTitleDialog(_IndexOfSession(session)); 956 break; 957 } 958 959 case kEditWindowTitle: 960 _OpenSetWindowTitleDialog(); 961 break; 962 963 case kUpdateSwitchTerminalsMenuItem: 964 _UpdateSwitchTerminalsMenuItem(); 965 break; 966 967 default: 968 BWindow::MessageReceived(message); 969 break; 970 } 971 } 972 973 974 void 975 TermWindow::WindowActivated(bool activated) 976 { 977 if (activated) 978 _UpdateSwitchTerminalsMenuItem(); 979 } 980 981 982 void 983 TermWindow::_SetTermColors(TermViewContainerView* containerView) 984 { 985 PrefHandler* handler = PrefHandler::Default(); 986 rgb_color background = handler->getRGB(PREF_TEXT_BACK_COLOR); 987 988 containerView->SetViewColor(background); 989 990 TermView *termView = containerView->GetTermView(); 991 termView->SetTextColor(handler->getRGB(PREF_TEXT_FORE_COLOR), background); 992 993 termView->SetSelectColor(handler->getRGB(PREF_SELECT_FORE_COLOR), 994 handler->getRGB(PREF_SELECT_BACK_COLOR)); 995 } 996 997 998 status_t 999 TermWindow::_DoPageSetup() 1000 { 1001 BPrintJob job("PageSetup"); 1002 1003 // display the page configure panel 1004 status_t status = job.ConfigPage(); 1005 1006 // save a pointer to the settings 1007 fPrintSettings = job.Settings(); 1008 1009 return status; 1010 } 1011 1012 1013 void 1014 TermWindow::_DoPrint() 1015 { 1016 if (!fPrintSettings || _DoPageSetup() != B_OK) { 1017 BAlert* alert = new BAlert(B_TRANSLATE("Cancel"), 1018 B_TRANSLATE("Print cancelled."), B_TRANSLATE("OK")); 1019 alert->SetShortcut(0, B_ESCAPE); 1020 alert->Go(); 1021 return; 1022 } 1023 1024 BPrintJob job("Print"); 1025 job.SetSettings(new BMessage(*fPrintSettings)); 1026 1027 BRect pageRect = job.PrintableRect(); 1028 BRect curPageRect = pageRect; 1029 1030 int pHeight = (int)pageRect.Height(); 1031 int pWidth = (int)pageRect.Width(); 1032 float w,h; 1033 _ActiveTermView()->GetFrameSize(&w, &h); 1034 int xPages = (int)ceil(w / pWidth); 1035 int yPages = (int)ceil(h / pHeight); 1036 1037 job.BeginJob(); 1038 1039 // loop through and draw each page, and write to spool 1040 for (int x = 0; x < xPages; x++) { 1041 for (int y = 0; y < yPages; y++) { 1042 curPageRect.OffsetTo(x * pWidth, y * pHeight); 1043 job.DrawView(_ActiveTermView(), curPageRect, B_ORIGIN); 1044 job.SpoolPage(); 1045 1046 if (!job.CanContinue()) { 1047 // It is likely that the only way that the job was cancelled is 1048 // because the user hit 'Cancel' in the page setup window, in 1049 // which case, the user does *not* need to be told that it was 1050 // cancelled. 1051 // He/she will simply expect that it was done. 1052 return; 1053 } 1054 } 1055 } 1056 1057 job.CommitJob(); 1058 } 1059 1060 1061 void 1062 TermWindow::_NewTab() 1063 { 1064 if (fTabView->CountTabs() < kMaxTabs) { 1065 ActiveProcessInfo info; 1066 if (_ActiveTermView()->GetActiveProcessInfo(info)) 1067 _AddTab(NULL, info.CurrentDirectory()); 1068 else 1069 _AddTab(NULL); 1070 } 1071 } 1072 1073 1074 void 1075 TermWindow::_AddTab(Arguments* args, const BString& currentDirectory) 1076 { 1077 int argc = 0; 1078 const char* const* argv = NULL; 1079 if (args != NULL) 1080 args->GetShellArguments(argc, argv); 1081 ShellParameters shellParameters(argc, argv, currentDirectory); 1082 1083 try { 1084 TermView* view = new TermView( 1085 PrefHandler::Default()->getInt32(PREF_ROWS), 1086 PrefHandler::Default()->getInt32(PREF_COLS), 1087 shellParameters, 1088 PrefHandler::Default()->getInt32(PREF_HISTORY_SIZE)); 1089 view->SetListener(this); 1090 1091 TermViewContainerView* containerView = new TermViewContainerView(view); 1092 BScrollView* scrollView = new TermScrollView("scrollView", 1093 containerView, view, fSessions.IsEmpty()); 1094 if (!fFullScreen) 1095 scrollView->ScrollBar(B_VERTICAL)->ResizeBy(0, -(B_H_SCROLL_BAR_HEIGHT - 1)); 1096 1097 if (fSessions.IsEmpty()) 1098 fTabView->SetScrollView(scrollView); 1099 1100 Session* session = new Session(_NewSessionID(), _NewSessionIndex(), 1101 containerView); 1102 fSessions.AddItem(session); 1103 1104 BFont font; 1105 _GetPreferredFont(font); 1106 view->SetTermFont(&font); 1107 1108 int width, height; 1109 view->GetFontSize(&width, &height); 1110 1111 float minimumHeight = -1; 1112 if (fMenuBar) 1113 minimumHeight += fMenuBar->Bounds().Height() + 1; 1114 if (fTabView && fTabView->CountTabs() > 0) 1115 minimumHeight += fTabView->TabHeight() + 1; 1116 SetSizeLimits(MIN_COLS * width - 1, MAX_COLS * width - 1, 1117 minimumHeight + MIN_ROWS * height - 1, 1118 minimumHeight + MAX_ROWS * height - 1); 1119 // TODO: The size limit computation is apparently broken, since 1120 // the terminal can be resized smaller than MIN_ROWS/MIN_COLS! 1121 1122 // If it's the first time we're called, setup the window 1123 if (fTabView->CountTabs() == 0) { 1124 float viewWidth, viewHeight; 1125 containerView->GetPreferredSize(&viewWidth, &viewHeight); 1126 1127 // Resize Window 1128 ResizeTo(viewWidth + B_V_SCROLL_BAR_WIDTH, 1129 viewHeight + fMenuBar->Bounds().Height() + 1); 1130 // NOTE: Width is one pixel too small, since the scroll view 1131 // is one pixel wider than its parent. 1132 } 1133 1134 BTab* tab = new BTab; 1135 fTabView->AddTab(scrollView, tab); 1136 view->SetScrollBar(scrollView->ScrollBar(B_VERTICAL)); 1137 view->SetMouseClipboard(gMouseClipboard); 1138 view->SetEncoding(EncodingID( 1139 PrefHandler::Default()->getString(PREF_TEXT_ENCODING))); 1140 1141 _SetTermColors(containerView); 1142 1143 int32 tabIndex = fTabView->CountTabs() - 1; 1144 fTabView->Select(tabIndex); 1145 1146 _UpdateSessionTitle(tabIndex); 1147 } catch (...) { 1148 // most probably out of memory. That's bad. 1149 // TODO: Should cleanup, I guess 1150 1151 // Quit the application if we don't have a shell already 1152 if (fTabView->CountTabs() == 0) { 1153 fprintf(stderr, "Terminal couldn't open a shell\n"); 1154 PostMessage(B_QUIT_REQUESTED); 1155 } 1156 } 1157 } 1158 1159 1160 void 1161 TermWindow::_RemoveTab(int32 index) 1162 { 1163 _FinishTitleDialog(); 1164 // always close to avoid confusion 1165 1166 if (fSessions.CountItems() > 1) { 1167 if (!_CanClose(index)) 1168 return; 1169 if (Session* session = (Session*)fSessions.RemoveItem(index)) { 1170 if (fSessions.CountItems() == 1) { 1171 fTabView->SetScrollView(dynamic_cast<BScrollView*>( 1172 _SessionAt(0)->containerView->Parent())); 1173 } 1174 1175 delete session; 1176 delete fTabView->RemoveTab(index); 1177 } 1178 } else 1179 PostMessage(B_QUIT_REQUESTED); 1180 } 1181 1182 1183 void 1184 TermWindow::_NavigateTab(int32 index, int32 direction, bool move) 1185 { 1186 int32 count = fSessions.CountItems(); 1187 if (count <= 1 || index < 0 || index >= count) 1188 return; 1189 1190 int32 newIndex = (index + direction + count) % count; 1191 if (newIndex == index) 1192 return; 1193 1194 if (move) { 1195 // move the given tab to the new index 1196 Session* session = (Session*)fSessions.RemoveItem(index); 1197 fSessions.AddItem(session, newIndex); 1198 fTabView->MoveTab(index, newIndex); 1199 } 1200 1201 // activate the respective tab 1202 fTabView->Select(newIndex); 1203 } 1204 1205 1206 TermViewContainerView* 1207 TermWindow::_ActiveTermViewContainerView() const 1208 { 1209 return _TermViewContainerViewAt(fTabView->Selection()); 1210 } 1211 1212 1213 TermViewContainerView* 1214 TermWindow::_TermViewContainerViewAt(int32 index) const 1215 { 1216 if (Session* session = _SessionAt(index)) 1217 return session->containerView; 1218 return NULL; 1219 } 1220 1221 1222 TermView* 1223 TermWindow::_ActiveTermView() const 1224 { 1225 return _ActiveTermViewContainerView()->GetTermView(); 1226 } 1227 1228 1229 TermView* 1230 TermWindow::_TermViewAt(int32 index) const 1231 { 1232 TermViewContainerView* view = _TermViewContainerViewAt(index); 1233 return view != NULL ? view->GetTermView() : NULL; 1234 } 1235 1236 1237 int32 1238 TermWindow::_IndexOfTermView(TermView* termView) const 1239 { 1240 if (!termView) 1241 return -1; 1242 1243 // find the view 1244 int32 count = fTabView->CountTabs(); 1245 for (int32 i = count - 1; i >= 0; i--) { 1246 if (termView == _TermViewAt(i)) 1247 return i; 1248 } 1249 1250 return -1; 1251 } 1252 1253 1254 TermWindow::Session* 1255 TermWindow::_SessionAt(int32 index) const 1256 { 1257 return (Session*)fSessions.ItemAt(index); 1258 } 1259 1260 1261 TermWindow::Session* 1262 TermWindow::_SessionForID(const SessionID& sessionID) const 1263 { 1264 for (int32 i = 0; Session* session = _SessionAt(i); i++) { 1265 if (session->id == sessionID) 1266 return session; 1267 } 1268 1269 return NULL; 1270 } 1271 1272 1273 int32 1274 TermWindow::_IndexOfSession(Session* session) const 1275 { 1276 return fSessions.IndexOf(session); 1277 } 1278 1279 1280 void 1281 TermWindow::_CheckChildren() 1282 { 1283 int32 count = fSessions.CountItems(); 1284 for (int32 i = count - 1; i >= 0; i--) { 1285 Session* session = _SessionAt(i); 1286 if (session->containerView->GetTermView()->CheckShellGone()) 1287 NotifyTermViewQuit(session->containerView->GetTermView(), 0); 1288 } 1289 } 1290 1291 1292 void 1293 TermWindow::Zoom(BPoint leftTop, float width, float height) 1294 { 1295 _ActiveTermView()->DisableResizeView(); 1296 BWindow::Zoom(leftTop, width, height); 1297 } 1298 1299 1300 void 1301 TermWindow::FrameResized(float newWidth, float newHeight) 1302 { 1303 BWindow::FrameResized(newWidth, newHeight); 1304 1305 TermView* view = _ActiveTermView(); 1306 PrefHandler::Default()->setInt32(PREF_COLS, view->Columns()); 1307 PrefHandler::Default()->setInt32(PREF_ROWS, view->Rows()); 1308 } 1309 1310 1311 void 1312 TermWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces) 1313 { 1314 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); 1315 } 1316 1317 1318 void 1319 TermWindow::WorkspaceActivated(int32 workspace, bool state) 1320 { 1321 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); 1322 } 1323 1324 1325 void 1326 TermWindow::Minimize(bool minimize) 1327 { 1328 BWindow::Minimize(minimize); 1329 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); 1330 } 1331 1332 1333 void 1334 TermWindow::TabSelected(SmartTabView* tabView, int32 index) 1335 { 1336 SessionChanged(); 1337 } 1338 1339 1340 void 1341 TermWindow::TabDoubleClicked(SmartTabView* tabView, BPoint point, int32 index) 1342 { 1343 if (index >= 0) { 1344 // clicked on a tab -- open the title dialog 1345 _OpenSetTabTitleDialog(index); 1346 } else { 1347 // not clicked on a tab -- create a new one 1348 _NewTab(); 1349 } 1350 } 1351 1352 1353 void 1354 TermWindow::TabMiddleClicked(SmartTabView* tabView, BPoint point, int32 index) 1355 { 1356 if (index >= 0) 1357 _RemoveTab(index); 1358 } 1359 1360 1361 void 1362 TermWindow::TabRightClicked(SmartTabView* tabView, BPoint point, int32 index) 1363 { 1364 if (index < 0) 1365 return; 1366 1367 TermView* termView = _TermViewAt(index); 1368 if (termView == NULL) 1369 return; 1370 1371 BMessage* closeMessage = new BMessage(kCloseView); 1372 _SessionAt(index)->id.AddToMessage(*closeMessage, "session"); 1373 1374 BMessage* closeOthersMessage = new BMessage(kCloseOtherViews); 1375 _SessionAt(index)->id.AddToMessage(*closeOthersMessage, "session"); 1376 1377 BMessage* editTitleMessage = new BMessage(kEditTabTitle); 1378 _SessionAt(index)->id.AddToMessage(*editTitleMessage, "session"); 1379 1380 BPopUpMenu* popUpMenu = new BPopUpMenu("tab menu"); 1381 BLayoutBuilder::Menu<>(popUpMenu) 1382 .AddItem(B_TRANSLATE("Close tab"), closeMessage) 1383 .AddItem(B_TRANSLATE("Close other tabs"), closeOthersMessage) 1384 .AddSeparator() 1385 .AddItem(B_TRANSLATE("Edit tab title" B_UTF8_ELLIPSIS), 1386 editTitleMessage) 1387 ; 1388 1389 popUpMenu->SetAsyncAutoDestruct(true); 1390 popUpMenu->SetTargetForItems(BMessenger(this)); 1391 1392 BPoint screenWhere = tabView->ConvertToScreen(point); 1393 BRect mouseRect(screenWhere, screenWhere); 1394 mouseRect.InsetBy(-4.0, -4.0); 1395 popUpMenu->Go(screenWhere, true, true, mouseRect, true); 1396 } 1397 1398 1399 void 1400 TermWindow::NotifyTermViewQuit(TermView* view, int32 reason) 1401 { 1402 // Since the notification can come from the view, we send a message to 1403 // ourselves to avoid deleting the caller synchronously. 1404 if (Session* session = _SessionAt(_IndexOfTermView(view))) { 1405 BMessage message(kCloseView); 1406 session->id.AddToMessage(message, "session"); 1407 message.AddInt32("reason", reason); 1408 PostMessage(&message); 1409 } 1410 } 1411 1412 1413 void 1414 TermWindow::SetTermViewTitle(TermView* view, const char* title) 1415 { 1416 int32 index = _IndexOfTermView(view); 1417 if (Session* session = _SessionAt(index)) { 1418 session->title.pattern = title; 1419 session->title.patternUserDefined = true; 1420 _UpdateSessionTitle(index); 1421 } 1422 } 1423 1424 1425 void 1426 TermWindow::TitleChanged(SetTitleDialog* dialog, const BString& title, 1427 bool titleUserDefined) 1428 { 1429 if (dialog == fSetTabTitleDialog) { 1430 // tab title 1431 BMessage message(kTabTitleChanged); 1432 fSetTabTitleSession.AddToMessage(message, "session"); 1433 if (titleUserDefined) 1434 message.AddString("title", title); 1435 1436 PostMessage(&message); 1437 } else if (dialog == fSetWindowTitleDialog) { 1438 // window title 1439 BMessage message(kWindowTitleChanged); 1440 if (titleUserDefined) 1441 message.AddString("title", title); 1442 1443 PostMessage(&message); 1444 } 1445 } 1446 1447 1448 void 1449 TermWindow::SetTitleDialogDone(SetTitleDialog* dialog) 1450 { 1451 if (dialog == fSetTabTitleDialog) { 1452 fSetTabTitleSession = SessionID(); 1453 fSetTabTitleDialog = NULL; 1454 // assuming this is atomic 1455 } 1456 } 1457 1458 1459 void 1460 TermWindow::TerminalInfosUpdated(TerminalRoster* roster) 1461 { 1462 PostMessage(kUpdateSwitchTerminalsMenuItem); 1463 } 1464 1465 1466 void 1467 TermWindow::PreviousTermView(TermView* view) 1468 { 1469 _NavigateTab(_IndexOfTermView(view), -1, false); 1470 } 1471 1472 1473 void 1474 TermWindow::NextTermView(TermView* view) 1475 { 1476 _NavigateTab(_IndexOfTermView(view), 1, false); 1477 } 1478 1479 1480 void 1481 TermWindow::_ResizeView(TermView *view) 1482 { 1483 int fontWidth, fontHeight; 1484 view->GetFontSize(&fontWidth, &fontHeight); 1485 1486 float minimumHeight = -1; 1487 if (fMenuBar) 1488 minimumHeight += fMenuBar->Bounds().Height() + 1; 1489 if (fTabView && fTabView->CountTabs() > 1) 1490 minimumHeight += fTabView->TabHeight() + 1; 1491 1492 SetSizeLimits(MIN_COLS * fontWidth - 1, MAX_COLS * fontWidth - 1, 1493 minimumHeight + MIN_ROWS * fontHeight - 1, 1494 minimumHeight + MAX_ROWS * fontHeight - 1); 1495 1496 float width; 1497 float height; 1498 view->Parent()->GetPreferredSize(&width, &height); 1499 width += B_V_SCROLL_BAR_WIDTH; 1500 // NOTE: Width is one pixel too small, since the scroll view 1501 // is one pixel wider than its parent. 1502 height += fMenuBar->Bounds().Height() + 1; 1503 1504 ResizeTo(width, height); 1505 1506 view->Invalidate(); 1507 } 1508 1509 1510 /* static */ 1511 BMenu* 1512 TermWindow::_MakeWindowSizeMenu() 1513 { 1514 BMenu* menu = new (std::nothrow) BMenu(B_TRANSLATE("Window size")); 1515 if (menu == NULL) 1516 return NULL; 1517 1518 const int32 windowSizes[4][2] = { 1519 { 80, 25 }, 1520 { 80, 40 }, 1521 { 132, 25 }, 1522 { 132, 40 } 1523 }; 1524 1525 const int32 sizeNum = sizeof(windowSizes) / sizeof(windowSizes[0]); 1526 for (int32 i = 0; i < sizeNum; i++) { 1527 char label[32]; 1528 int32 columns = windowSizes[i][0]; 1529 int32 rows = windowSizes[i][1]; 1530 snprintf(label, sizeof(label), "%ldx%ld", columns, rows); 1531 BMessage* message = new BMessage(MSG_COLS_CHANGED); 1532 message->AddInt32("columns", columns); 1533 message->AddInt32("rows", rows); 1534 menu->AddItem(new BMenuItem(label, message)); 1535 } 1536 1537 menu->AddSeparatorItem(); 1538 menu->AddItem(new BMenuItem(B_TRANSLATE("Full screen"), 1539 new BMessage(FULLSCREEN), B_ENTER)); 1540 1541 return menu; 1542 } 1543 1544 1545 void 1546 TermWindow::_UpdateSwitchTerminalsMenuItem() 1547 { 1548 fSwitchTerminalsMenuItem->SetEnabled(_FindSwitchTerminalTarget() >= 0); 1549 } 1550 1551 1552 void 1553 TermWindow::_TitleSettingsChanged() 1554 { 1555 if (!fTitle.patternUserDefined) 1556 fTitle.pattern = PrefHandler::Default()->getString(PREF_WINDOW_TITLE); 1557 1558 fSessionTitlePattern = PrefHandler::Default()->getString(PREF_TAB_TITLE); 1559 1560 _UpdateTitles(); 1561 } 1562 1563 1564 void 1565 TermWindow::_UpdateTitles() 1566 { 1567 int32 sessionCount = fSessions.CountItems(); 1568 for (int32 i = 0; i < sessionCount; i++) 1569 _UpdateSessionTitle(i); 1570 } 1571 1572 1573 void 1574 TermWindow::_UpdateSessionTitle(int32 index) 1575 { 1576 Session* session = _SessionAt(index); 1577 if (session == NULL) 1578 return; 1579 1580 // get the shell and active process infos 1581 ShellInfo shellInfo; 1582 ActiveProcessInfo activeProcessInfo; 1583 TermView* termView = _TermViewAt(index); 1584 if (!termView->GetShellInfo(shellInfo) 1585 || !termView->GetActiveProcessInfo(activeProcessInfo)) { 1586 return; 1587 } 1588 1589 // evaluate the session title pattern 1590 BString sessionTitlePattern = session->title.patternUserDefined 1591 ? session->title.pattern : fSessionTitlePattern; 1592 TabTitlePlaceholderMapper tabMapper(shellInfo, activeProcessInfo, 1593 session->index); 1594 const BString& sessionTitle = PatternEvaluator::Evaluate( 1595 sessionTitlePattern, tabMapper); 1596 1597 // set the tab title 1598 if (sessionTitle != session->title.title) { 1599 session->title.title = sessionTitle; 1600 fTabView->TabAt(index)->SetLabel(session->title.title); 1601 fTabView->Invalidate(); 1602 // Invalidate the complete tab view, since other tabs might change 1603 // their positions. 1604 } 1605 1606 // If this is the active tab, also recompute the window title. 1607 if (index != fTabView->Selection()) 1608 return; 1609 1610 // evaluate the window title pattern 1611 WindowTitlePlaceholderMapper windowMapper(shellInfo, activeProcessInfo, 1612 fTerminalRoster.ID() + 1, sessionTitle); 1613 const BString& windowTitle = PatternEvaluator::Evaluate(fTitle.pattern, 1614 windowMapper); 1615 1616 // set the window title 1617 if (windowTitle != fTitle.title) { 1618 fTitle.title = windowTitle; 1619 SetTitle(fTitle.title); 1620 } 1621 } 1622 1623 1624 void 1625 TermWindow::_OpenSetTabTitleDialog(int32 index) 1626 { 1627 // If a dialog is active, finish it. 1628 _FinishTitleDialog(); 1629 1630 BString toolTip = BString(B_TRANSLATE( 1631 "The pattern specifying the current tab title. The following " 1632 "placeholders\n" 1633 "can be used:\n")) << kTooTipSetTabTitlePlaceholders; 1634 fSetTabTitleDialog = new SetTitleDialog( 1635 B_TRANSLATE("Set tab title"), B_TRANSLATE("Tab title:"), 1636 toolTip); 1637 1638 Session* session = _SessionAt(index); 1639 bool userDefined = session->title.patternUserDefined; 1640 const BString& title = userDefined 1641 ? session->title.pattern : fSessionTitlePattern; 1642 fSetTabTitleSession = session->id; 1643 1644 // place the dialog window directly under the tab, but keep it on screen 1645 BPoint location = fTabView->ConvertToScreen( 1646 fTabView->TabFrame(index).LeftBottom() + BPoint(0, 1)); 1647 BRect frame(fSetTabTitleDialog->Frame().OffsetToCopy(location)); 1648 BSize screenSize(BScreen(fSetTabTitleDialog).Frame().Size()); 1649 fSetTabTitleDialog->MoveTo( 1650 BLayoutUtils::MoveIntoFrame(frame, screenSize).LeftTop()); 1651 1652 fSetTabTitleDialog->Go(title, userDefined, this); 1653 } 1654 1655 1656 void 1657 TermWindow::_OpenSetWindowTitleDialog() 1658 { 1659 // If a dialog is active, finish it. 1660 _FinishTitleDialog(); 1661 1662 BString toolTip = BString(B_TRANSLATE( 1663 "The pattern specifying the window title. The following placeholders\n" 1664 "can be used:\n")) << kTooTipSetTabTitlePlaceholders; 1665 fSetWindowTitleDialog = new SetTitleDialog(B_TRANSLATE("Set window title"), 1666 B_TRANSLATE("Window title:"), toolTip); 1667 1668 // center the dialog in the window frame, but keep it on screen 1669 fSetWindowTitleDialog->CenterIn(Frame()); 1670 BRect frame(fSetWindowTitleDialog->Frame()); 1671 BSize screenSize(BScreen(fSetWindowTitleDialog).Frame().Size()); 1672 fSetWindowTitleDialog->MoveTo( 1673 BLayoutUtils::MoveIntoFrame(frame, screenSize).LeftTop()); 1674 1675 fSetWindowTitleDialog->Go(fTitle.pattern, fTitle.patternUserDefined, this); 1676 } 1677 1678 1679 void 1680 TermWindow::_FinishTitleDialog() 1681 { 1682 SetTitleDialog* oldDialog = fSetTabTitleDialog; 1683 if (oldDialog != NULL && oldDialog->Lock()) { 1684 // might have been unset in the meantime, so recheck 1685 if (fSetTabTitleDialog == oldDialog) { 1686 oldDialog->Finish(); 1687 // this also unsets the variables 1688 } 1689 oldDialog->Unlock(); 1690 return; 1691 } 1692 1693 oldDialog = fSetWindowTitleDialog; 1694 if (oldDialog != NULL && oldDialog->Lock()) { 1695 // might have been unset in the meantime, so recheck 1696 if (fSetWindowTitleDialog == oldDialog) { 1697 oldDialog->Finish(); 1698 // this also unsets the variable 1699 } 1700 oldDialog->Unlock(); 1701 return; 1702 } 1703 } 1704 1705 1706 void 1707 TermWindow::_SwitchTerminal() 1708 { 1709 team_id teamID = _FindSwitchTerminalTarget(); 1710 if (teamID < 0) 1711 return; 1712 1713 BMessenger app(TERM_SIGNATURE, teamID); 1714 app.SendMessage(MSG_ACTIVATE_TERM); 1715 } 1716 1717 1718 team_id 1719 TermWindow::_FindSwitchTerminalTarget() 1720 { 1721 AutoLocker<TerminalRoster> rosterLocker(fTerminalRoster); 1722 1723 team_id myTeamID = Team(); 1724 1725 int32 numTerms = fTerminalRoster.CountTerminals(); 1726 if (numTerms <= 1) 1727 return -1; 1728 1729 // Find our position in the Terminal teams. 1730 int32 i; 1731 1732 for (i = 0; i < numTerms; i++) { 1733 if (myTeamID == fTerminalRoster.TerminalAt(i)->team) 1734 break; 1735 } 1736 1737 if (i == numTerms) { 1738 // we didn't find ourselves -- that shouldn't happen 1739 return -1; 1740 } 1741 1742 uint32 currentWorkspace = 1L << current_workspace(); 1743 1744 while (true) { 1745 if (--i < 0) 1746 i = numTerms - 1; 1747 1748 const TerminalRoster::Info* info = fTerminalRoster.TerminalAt(i); 1749 if (info->team == myTeamID) { 1750 // That's ourselves again. We've run through the complete list. 1751 return -1; 1752 } 1753 1754 if (!info->minimized && (info->workspaces & currentWorkspace) != 0) 1755 return info->team; 1756 } 1757 } 1758 1759 1760 TermWindow::SessionID 1761 TermWindow::_NewSessionID() 1762 { 1763 return fNextSessionID++; 1764 } 1765 1766 1767 int32 1768 TermWindow::_NewSessionIndex() 1769 { 1770 for (int32 id = 1; ; id++) { 1771 bool used = false; 1772 1773 for (int32 i = 0; 1774 Session* session = _SessionAt(i); i++) { 1775 if (id == session->index) { 1776 used = true; 1777 break; 1778 } 1779 } 1780 1781 if (!used) 1782 return id; 1783 } 1784 } 1785