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("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("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("About Terminal" B_UTF8_ELLIPSIS), 439 B_ABOUT_REQUESTED) 440 .AddSeparator() 441 .AddItem(B_TRANSLATE("Close window"), B_QUIT_REQUESTED, 'W', 442 B_SHIFT_KEY) 443 .AddItem(B_TRANSLATE("Close active tab"), kCloseView, 'W') 444 .AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q') 445 .End() 446 447 // Edit 448 .AddMenu(B_TRANSLATE("Edit")) 449 .AddItem(B_TRANSLATE("Copy"), B_COPY,'C') 450 .AddItem(B_TRANSLATE("Paste"), B_PASTE,'V') 451 .AddSeparator() 452 .AddItem(B_TRANSLATE("Select all"), B_SELECT_ALL, 'A') 453 .AddItem(B_TRANSLATE("Clear all"), MENU_CLEAR_ALL, 'L') 454 .AddSeparator() 455 .AddItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), MENU_FIND_STRING,'F') 456 .AddItem(B_TRANSLATE("Find previous"), MENU_FIND_PREVIOUS, 'G', 457 B_SHIFT_KEY) 458 .GetItem(fFindPreviousMenuItem) 459 .SetEnabled(false) 460 .AddItem(B_TRANSLATE("Find next"), MENU_FIND_NEXT, 'G') 461 .GetItem(fFindNextMenuItem) 462 .SetEnabled(false) 463 .AddSeparator() 464 .AddItem(B_TRANSLATE("Window title" B_UTF8_ELLIPSIS), 465 kEditWindowTitle) 466 .End() 467 468 // Settings 469 .AddMenu(B_TRANSLATE("Settings")) 470 .AddItem(_MakeWindowSizeMenu()) 471 .AddItem(fEncodingMenu = _MakeEncodingMenu()) 472 .AddMenu(B_TRANSLATE("Text size")) 473 .AddItem(B_TRANSLATE("Increase"), kIncreaseFontSize, '+', 474 B_COMMAND_KEY) 475 .GetItem(fIncreaseFontSizeMenuItem) 476 .AddItem(B_TRANSLATE("Decrease"), kDecreaseFontSize, '-', 477 B_COMMAND_KEY) 478 .GetItem(fDecreaseFontSizeMenuItem) 479 .End() 480 .AddSeparator() 481 .AddItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), MENU_PREF_OPEN) 482 .AddSeparator() 483 .AddItem(B_TRANSLATE("Save as default"), SAVE_AS_DEFAULT) 484 .End() 485 ; 486 487 AddChild(fMenuBar); 488 489 _UpdateSwitchTerminalsMenuItem(); 490 } 491 492 493 status_t 494 TermWindow::_GetWindowPositionFile(BFile* file, uint32 openMode) 495 { 496 BPath path; 497 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true); 498 if (status != B_OK) 499 return status; 500 501 status = path.Append("Terminal_windows"); 502 if (status != B_OK) 503 return status; 504 505 return file->SetTo(path.Path(), openMode); 506 } 507 508 509 status_t 510 TermWindow::_LoadWindowPosition(BRect* frame, uint32* workspaces) 511 { 512 status_t status; 513 BMessage position; 514 515 BFile file; 516 status = _GetWindowPositionFile(&file, B_READ_ONLY); 517 if (status != B_OK) 518 return status; 519 520 status = position.Unflatten(&file); 521 522 file.Unset(); 523 524 if (status != B_OK) 525 return status; 526 527 int32 id = fTerminalRoster.ID(); 528 status = position.FindRect("rect", id, frame); 529 if (status != B_OK) 530 return status; 531 532 int32 _workspaces; 533 status = position.FindInt32("workspaces", id, &_workspaces); 534 if (status != B_OK) 535 return status; 536 if (modifiers() & B_SHIFT_KEY) 537 *workspaces = _workspaces; 538 else 539 *workspaces = B_CURRENT_WORKSPACE; 540 541 return B_OK; 542 } 543 544 545 status_t 546 TermWindow::_SaveWindowPosition() 547 { 548 BFile file; 549 BMessage originalSettings; 550 551 // We append ourself to the existing settings file 552 // So we have to read it, insert our BMessage, and rewrite it. 553 554 status_t status = _GetWindowPositionFile(&file, B_READ_ONLY); 555 if (status == B_OK) { 556 originalSettings.Unflatten(&file); 557 // No error checking on that : it fails if the settings 558 // file is missing, but we can create it. 559 560 file.Unset(); 561 } 562 563 // Append the new settings 564 int32 id = fTerminalRoster.ID(); 565 BRect rect(Frame()); 566 if (originalSettings.ReplaceRect("rect", id, rect) != B_OK) 567 originalSettings.AddRect("rect", rect); 568 569 int32 workspaces = Workspaces(); 570 if (originalSettings.ReplaceInt32("workspaces", id, workspaces) != B_OK) 571 originalSettings.AddInt32("workspaces", workspaces); 572 573 // Resave the whole thing 574 status = _GetWindowPositionFile (&file, 575 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); 576 if (status != B_OK) 577 return status; 578 579 return originalSettings.Flatten(&file); 580 } 581 582 583 void 584 TermWindow::_GetPreferredFont(BFont& font) 585 { 586 // Default to be_fixed_font 587 font = be_fixed_font; 588 589 const char* family = PrefHandler::Default()->getString(PREF_HALF_FONT_FAMILY); 590 const char* style = PrefHandler::Default()->getString(PREF_HALF_FONT_STYLE); 591 592 font.SetFamilyAndStyle(family, style); 593 594 float size = PrefHandler::Default()->getFloat(PREF_HALF_FONT_SIZE); 595 if (size < 6.0f) 596 size = 6.0f; 597 font.SetSize(size); 598 } 599 600 601 void 602 TermWindow::MessageReceived(BMessage *message) 603 { 604 int32 encodingId; 605 bool findresult; 606 607 switch (message->what) { 608 case B_COPY: 609 _ActiveTermView()->Copy(be_clipboard); 610 break; 611 612 case B_PASTE: 613 _ActiveTermView()->Paste(be_clipboard); 614 break; 615 616 case B_SELECT_ALL: 617 _ActiveTermView()->SelectAll(); 618 break; 619 620 case B_ABOUT_REQUESTED: 621 be_app->PostMessage(B_ABOUT_REQUESTED); 622 break; 623 624 case MENU_CLEAR_ALL: 625 _ActiveTermView()->Clear(); 626 break; 627 628 case MENU_SWITCH_TERM: 629 _SwitchTerminal(); 630 break; 631 632 case MENU_NEW_TERM: 633 { 634 // Set our current working directory to that of the active tab, so 635 // that the new terminal and its shell inherit it. 636 // Note: That's a bit lame. We should rather fork() and change the 637 // CWD in the child, but since ATM there aren't any side effects of 638 // changing our CWD, we save ourselves the trouble. 639 ActiveProcessInfo activeProcessInfo; 640 if (_ActiveTermView()->GetActiveProcessInfo(activeProcessInfo)) 641 chdir(activeProcessInfo.CurrentDirectory()); 642 643 app_info info; 644 be_app->GetAppInfo(&info); 645 646 // try launching two different ways to work around possible problems 647 if (be_roster->Launch(&info.ref) != B_OK) 648 be_roster->Launch(TERM_SIGNATURE); 649 break; 650 } 651 652 case MENU_PREF_OPEN: 653 if (!fPrefWindow) { 654 fPrefWindow = new PrefWindow(this); 655 } 656 else 657 fPrefWindow->Activate(); 658 break; 659 660 case MSG_PREF_CLOSED: 661 fPrefWindow = NULL; 662 break; 663 664 case MSG_WINDOW_TITLE_SETTING_CHANGED: 665 case MSG_TAB_TITLE_SETTING_CHANGED: 666 _TitleSettingsChanged(); 667 break; 668 669 case MENU_FIND_STRING: 670 if (!fFindPanel) { 671 fFindPanel = new FindWindow(this, fFindString, fFindSelection, 672 fMatchWord, fMatchCase, fForwardSearch); 673 } 674 else 675 fFindPanel->Activate(); 676 break; 677 678 case MSG_FIND: 679 { 680 fFindPanel->PostMessage(B_QUIT_REQUESTED); 681 message->FindBool("findselection", &fFindSelection); 682 if (!fFindSelection) 683 message->FindString("findstring", &fFindString); 684 else 685 _ActiveTermView()->GetSelection(fFindString); 686 687 if (fFindString.Length() == 0) { 688 const char* errorMsg = !fFindSelection 689 ? B_TRANSLATE("No search string was entered.") 690 : B_TRANSLATE("Nothing is selected."); 691 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"), 692 errorMsg, B_TRANSLATE("OK"), NULL, NULL, 693 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 694 alert->SetShortcut(0, B_ESCAPE); 695 696 alert->Go(); 697 fFindPreviousMenuItem->SetEnabled(false); 698 fFindNextMenuItem->SetEnabled(false); 699 break; 700 } 701 702 message->FindBool("forwardsearch", &fForwardSearch); 703 message->FindBool("matchcase", &fMatchCase); 704 message->FindBool("matchword", &fMatchWord); 705 findresult = _ActiveTermView()->Find(fFindString, fForwardSearch, fMatchCase, fMatchWord); 706 707 if (!findresult) { 708 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"), 709 B_TRANSLATE("Text not found."), 710 B_TRANSLATE("OK"), NULL, NULL, 711 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 712 alert->SetShortcut(0, B_ESCAPE); 713 alert->Go(); 714 fFindPreviousMenuItem->SetEnabled(false); 715 fFindNextMenuItem->SetEnabled(false); 716 break; 717 } 718 719 // Enable the menu items Find Next and Find Previous 720 fFindPreviousMenuItem->SetEnabled(true); 721 fFindNextMenuItem->SetEnabled(true); 722 break; 723 } 724 725 case MENU_FIND_NEXT: 726 case MENU_FIND_PREVIOUS: 727 findresult = _ActiveTermView()->Find(fFindString, 728 (message->what == MENU_FIND_NEXT) == fForwardSearch, 729 fMatchCase, fMatchWord); 730 if (!findresult) { 731 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"), 732 B_TRANSLATE("Not found."), B_TRANSLATE("OK"), 733 NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 734 alert->SetShortcut(0, B_ESCAPE); 735 alert->Go(); 736 } 737 break; 738 739 case MSG_FIND_CLOSED: 740 fFindPanel = NULL; 741 break; 742 743 case MENU_ENCODING: 744 if (message->FindInt32("op", &encodingId) == B_OK) 745 _ActiveTermView()->SetEncoding(encodingId); 746 break; 747 748 case MSG_COLS_CHANGED: 749 { 750 int32 columns, rows; 751 message->FindInt32("columns", &columns); 752 message->FindInt32("rows", &rows); 753 754 _ActiveTermView()->SetTermSize(rows, columns); 755 756 _ResizeView(_ActiveTermView()); 757 break; 758 } 759 case MSG_HALF_FONT_CHANGED: 760 case MSG_FULL_FONT_CHANGED: 761 case MSG_HALF_SIZE_CHANGED: 762 case MSG_FULL_SIZE_CHANGED: 763 { 764 BFont font; 765 _GetPreferredFont(font); 766 _ActiveTermView()->SetTermFont(&font); 767 768 _ResizeView(_ActiveTermView()); 769 break; 770 } 771 772 case FULLSCREEN: 773 if (!fSavedFrame.IsValid()) { // go fullscreen 774 _ActiveTermView()->DisableResizeView(); 775 float mbHeight = fMenuBar->Bounds().Height() + 1; 776 fSavedFrame = Frame(); 777 BScreen screen(this); 778 for (int32 i = fTabView->CountTabs() - 1; i >=0 ; i--) 779 _TermViewAt(i)->ScrollBar()->ResizeBy(0, (B_H_SCROLL_BAR_HEIGHT - 1)); 780 781 fMenuBar->Hide(); 782 fTabView->ResizeBy(0, mbHeight); 783 fTabView->MoveBy(0, -mbHeight); 784 fSavedLook = Look(); 785 // done before ResizeTo to work around a Dano bug (not erasing the decor) 786 SetLook(B_NO_BORDER_WINDOW_LOOK); 787 ResizeTo(screen.Frame().Width()+1, screen.Frame().Height()+1); 788 MoveTo(screen.Frame().left, screen.Frame().top); 789 fFullScreen = true; 790 } else { // exit fullscreen 791 _ActiveTermView()->DisableResizeView(); 792 float mbHeight = fMenuBar->Bounds().Height() + 1; 793 fMenuBar->Show(); 794 for (int32 i = fTabView->CountTabs() - 1; i >=0 ; i--) 795 _TermViewAt(i)->ScrollBar()->ResizeBy(0, -(B_H_SCROLL_BAR_HEIGHT - 1)); 796 ResizeTo(fSavedFrame.Width(), fSavedFrame.Height()); 797 MoveTo(fSavedFrame.left, fSavedFrame.top); 798 fTabView->ResizeBy(0, -mbHeight); 799 fTabView->MoveBy(0, mbHeight); 800 SetLook(fSavedLook); 801 fSavedFrame = BRect(0,0,-1,-1); 802 fFullScreen = false; 803 } 804 break; 805 806 case MSG_FONT_CHANGED: 807 PostMessage(MSG_HALF_FONT_CHANGED); 808 break; 809 810 case MSG_COLOR_CHANGED: 811 case MSG_COLOR_SCHEMA_CHANGED: 812 { 813 _SetTermColors(_ActiveTermViewContainerView()); 814 _ActiveTermViewContainerView()->Invalidate(); 815 _ActiveTermView()->Invalidate(); 816 break; 817 } 818 819 case SAVE_AS_DEFAULT: 820 { 821 BPath path; 822 if (PrefHandler::GetDefaultPath(path) == B_OK) 823 PrefHandler::Default()->SaveAsText(path.Path(), PREFFILE_MIMETYPE); 824 break; 825 } 826 case MENU_PAGE_SETUP: 827 _DoPageSetup(); 828 break; 829 830 case MENU_PRINT: 831 _DoPrint(); 832 break; 833 834 case MSG_CHECK_CHILDREN: 835 _CheckChildren(); 836 break; 837 838 case MSG_MOVE_TAB_LEFT: 839 case MSG_MOVE_TAB_RIGHT: 840 _NavigateTab(_IndexOfTermView(_ActiveTermView()), 841 message->what == MSG_MOVE_TAB_LEFT ? -1 : 1, true); 842 break; 843 844 case kTabTitleChanged: 845 { 846 // tab title changed message from SetTitleDialog 847 SessionID sessionID(*message, "session"); 848 if (Session* session = _SessionForID(sessionID)) { 849 BString title; 850 if (message->FindString("title", &title) == B_OK) { 851 session->title.pattern = title; 852 session->title.patternUserDefined = true; 853 } else { 854 session->title.pattern.Truncate(0); 855 session->title.patternUserDefined = false; 856 } 857 _UpdateSessionTitle(_IndexOfSession(session)); 858 } 859 break; 860 } 861 862 case kWindowTitleChanged: 863 { 864 // window title changed message from SetTitleDialog 865 BString title; 866 if (message->FindString("title", &title) == B_OK) { 867 fTitle.pattern = title; 868 fTitle.patternUserDefined = true; 869 } else { 870 fTitle.pattern 871 = PrefHandler::Default()->getString(PREF_WINDOW_TITLE); 872 fTitle.patternUserDefined = false; 873 } 874 875 _UpdateSessionTitle(fTabView->Selection()); 876 // updates the window title as a side effect 877 878 break; 879 } 880 881 case kSetActiveTab: 882 { 883 int32 index; 884 if (message->FindInt32("index", &index) == B_OK 885 && index >= 0 && index < fSessions.CountItems()) { 886 fTabView->Select(index); 887 } 888 break; 889 } 890 891 case kNewTab: 892 _NewTab(); 893 break; 894 895 case kCloseView: 896 { 897 int32 index = -1; 898 SessionID sessionID(*message, "session"); 899 if (sessionID.IsValid()) { 900 if (Session* session = _SessionForID(sessionID)) 901 index = _IndexOfSession(session); 902 } else 903 index = _IndexOfTermView(_ActiveTermView()); 904 905 if (index >= 0) 906 _RemoveTab(index); 907 908 break; 909 } 910 911 case kCloseOtherViews: 912 { 913 Session* session = _SessionForID(SessionID(*message, "session")); 914 if (session == NULL) 915 break; 916 917 int32 count = fSessions.CountItems(); 918 for (int32 i = count - 1; i >= 0; i--) { 919 if (_SessionAt(i) != session) 920 _RemoveTab(i); 921 } 922 923 break; 924 } 925 926 case kIncreaseFontSize: 927 case kDecreaseFontSize: 928 { 929 TermView* view = _ActiveTermView(); 930 BFont font; 931 view->GetTermFont(&font); 932 933 float size = font.Size(); 934 if (message->what == kIncreaseFontSize) 935 size += 1; 936 else 937 size -= 1; 938 939 // limit the font size 940 if (size < 9) 941 size = 9; 942 if (size > 18) 943 size = 18; 944 945 font.SetSize(size); 946 view->SetTermFont(&font); 947 PrefHandler::Default()->setInt32(PREF_HALF_FONT_SIZE, (int32)size); 948 949 _ResizeView(view); 950 break; 951 } 952 953 case kUpdateTitles: 954 _UpdateTitles(); 955 break; 956 957 case kEditTabTitle: 958 { 959 SessionID sessionID(*message, "session"); 960 if (Session* session = _SessionForID(sessionID)) 961 _OpenSetTabTitleDialog(_IndexOfSession(session)); 962 break; 963 } 964 965 case kEditWindowTitle: 966 _OpenSetWindowTitleDialog(); 967 break; 968 969 case kUpdateSwitchTerminalsMenuItem: 970 _UpdateSwitchTerminalsMenuItem(); 971 break; 972 973 default: 974 BWindow::MessageReceived(message); 975 break; 976 } 977 } 978 979 980 void 981 TermWindow::WindowActivated(bool activated) 982 { 983 if (activated) 984 _UpdateSwitchTerminalsMenuItem(); 985 } 986 987 988 void 989 TermWindow::_SetTermColors(TermViewContainerView* containerView) 990 { 991 PrefHandler* handler = PrefHandler::Default(); 992 rgb_color background = handler->getRGB(PREF_TEXT_BACK_COLOR); 993 994 containerView->SetViewColor(background); 995 996 TermView *termView = containerView->GetTermView(); 997 termView->SetTextColor(handler->getRGB(PREF_TEXT_FORE_COLOR), background); 998 999 termView->SetSelectColor(handler->getRGB(PREF_SELECT_FORE_COLOR), 1000 handler->getRGB(PREF_SELECT_BACK_COLOR)); 1001 } 1002 1003 1004 status_t 1005 TermWindow::_DoPageSetup() 1006 { 1007 BPrintJob job("PageSetup"); 1008 1009 // display the page configure panel 1010 status_t status = job.ConfigPage(); 1011 1012 // save a pointer to the settings 1013 fPrintSettings = job.Settings(); 1014 1015 return status; 1016 } 1017 1018 1019 void 1020 TermWindow::_DoPrint() 1021 { 1022 if (!fPrintSettings || _DoPageSetup() != B_OK) { 1023 BAlert* alert = new BAlert(B_TRANSLATE("Cancel"), 1024 B_TRANSLATE("Print cancelled."), B_TRANSLATE("OK")); 1025 alert->SetShortcut(0, B_ESCAPE); 1026 alert->Go(); 1027 return; 1028 } 1029 1030 BPrintJob job("Print"); 1031 job.SetSettings(new BMessage(*fPrintSettings)); 1032 1033 BRect pageRect = job.PrintableRect(); 1034 BRect curPageRect = pageRect; 1035 1036 int pHeight = (int)pageRect.Height(); 1037 int pWidth = (int)pageRect.Width(); 1038 float w,h; 1039 _ActiveTermView()->GetFrameSize(&w, &h); 1040 int xPages = (int)ceil(w / pWidth); 1041 int yPages = (int)ceil(h / pHeight); 1042 1043 job.BeginJob(); 1044 1045 // loop through and draw each page, and write to spool 1046 for (int x = 0; x < xPages; x++) { 1047 for (int y = 0; y < yPages; y++) { 1048 curPageRect.OffsetTo(x * pWidth, y * pHeight); 1049 job.DrawView(_ActiveTermView(), curPageRect, B_ORIGIN); 1050 job.SpoolPage(); 1051 1052 if (!job.CanContinue()) { 1053 // It is likely that the only way that the job was cancelled is 1054 // because the user hit 'Cancel' in the page setup window, in 1055 // which case, the user does *not* need to be told that it was 1056 // cancelled. 1057 // He/she will simply expect that it was done. 1058 return; 1059 } 1060 } 1061 } 1062 1063 job.CommitJob(); 1064 } 1065 1066 1067 void 1068 TermWindow::_NewTab() 1069 { 1070 if (fTabView->CountTabs() < kMaxTabs) { 1071 ActiveProcessInfo info; 1072 if (_ActiveTermView()->GetActiveProcessInfo(info)) 1073 _AddTab(NULL, info.CurrentDirectory()); 1074 else 1075 _AddTab(NULL); 1076 } 1077 } 1078 1079 1080 void 1081 TermWindow::_AddTab(Arguments* args, const BString& currentDirectory) 1082 { 1083 int argc = 0; 1084 const char* const* argv = NULL; 1085 if (args != NULL) 1086 args->GetShellArguments(argc, argv); 1087 ShellParameters shellParameters(argc, argv, currentDirectory); 1088 1089 try { 1090 TermView* view = new TermView( 1091 PrefHandler::Default()->getInt32(PREF_ROWS), 1092 PrefHandler::Default()->getInt32(PREF_COLS), 1093 shellParameters, 1094 PrefHandler::Default()->getInt32(PREF_HISTORY_SIZE)); 1095 view->SetListener(this); 1096 1097 TermViewContainerView* containerView = new TermViewContainerView(view); 1098 BScrollView* scrollView = new TermScrollView("scrollView", 1099 containerView, view, fSessions.IsEmpty()); 1100 if (!fFullScreen) 1101 scrollView->ScrollBar(B_VERTICAL)->ResizeBy(0, -(B_H_SCROLL_BAR_HEIGHT - 1)); 1102 1103 if (fSessions.IsEmpty()) 1104 fTabView->SetScrollView(scrollView); 1105 1106 Session* session = new Session(_NewSessionID(), _NewSessionIndex(), 1107 containerView); 1108 fSessions.AddItem(session); 1109 1110 BFont font; 1111 _GetPreferredFont(font); 1112 view->SetTermFont(&font); 1113 1114 int width, height; 1115 view->GetFontSize(&width, &height); 1116 1117 float minimumHeight = -1; 1118 if (fMenuBar) 1119 minimumHeight += fMenuBar->Bounds().Height() + 1; 1120 if (fTabView && fTabView->CountTabs() > 0) 1121 minimumHeight += fTabView->TabHeight() + 1; 1122 SetSizeLimits(MIN_COLS * width - 1, MAX_COLS * width - 1, 1123 minimumHeight + MIN_ROWS * height - 1, 1124 minimumHeight + MAX_ROWS * height - 1); 1125 // TODO: The size limit computation is apparently broken, since 1126 // the terminal can be resized smaller than MIN_ROWS/MIN_COLS! 1127 1128 // If it's the first time we're called, setup the window 1129 if (fTabView->CountTabs() == 0) { 1130 float viewWidth, viewHeight; 1131 containerView->GetPreferredSize(&viewWidth, &viewHeight); 1132 1133 // Resize Window 1134 ResizeTo(viewWidth + B_V_SCROLL_BAR_WIDTH, 1135 viewHeight + fMenuBar->Bounds().Height() + 1); 1136 // NOTE: Width is one pixel too small, since the scroll view 1137 // is one pixel wider than its parent. 1138 } 1139 1140 BTab* tab = new BTab; 1141 fTabView->AddTab(scrollView, tab); 1142 view->SetScrollBar(scrollView->ScrollBar(B_VERTICAL)); 1143 view->SetMouseClipboard(gMouseClipboard); 1144 view->SetEncoding(EncodingID( 1145 PrefHandler::Default()->getString(PREF_TEXT_ENCODING))); 1146 1147 _SetTermColors(containerView); 1148 1149 int32 tabIndex = fTabView->CountTabs() - 1; 1150 fTabView->Select(tabIndex); 1151 1152 _UpdateSessionTitle(tabIndex); 1153 } catch (...) { 1154 // most probably out of memory. That's bad. 1155 // TODO: Should cleanup, I guess 1156 1157 // Quit the application if we don't have a shell already 1158 if (fTabView->CountTabs() == 0) { 1159 fprintf(stderr, "Terminal couldn't open a shell\n"); 1160 PostMessage(B_QUIT_REQUESTED); 1161 } 1162 } 1163 } 1164 1165 1166 void 1167 TermWindow::_RemoveTab(int32 index) 1168 { 1169 _FinishTitleDialog(); 1170 // always close to avoid confusion 1171 1172 if (fSessions.CountItems() > 1) { 1173 if (!_CanClose(index)) 1174 return; 1175 if (Session* session = (Session*)fSessions.RemoveItem(index)) { 1176 if (fSessions.CountItems() == 1) { 1177 fTabView->SetScrollView(dynamic_cast<BScrollView*>( 1178 _SessionAt(0)->containerView->Parent())); 1179 } 1180 1181 delete session; 1182 delete fTabView->RemoveTab(index); 1183 } 1184 } else 1185 PostMessage(B_QUIT_REQUESTED); 1186 } 1187 1188 1189 void 1190 TermWindow::_NavigateTab(int32 index, int32 direction, bool move) 1191 { 1192 int32 count = fSessions.CountItems(); 1193 if (count <= 1 || index < 0 || index >= count) 1194 return; 1195 1196 int32 newIndex = (index + direction + count) % count; 1197 if (newIndex == index) 1198 return; 1199 1200 if (move) { 1201 // move the given tab to the new index 1202 Session* session = (Session*)fSessions.RemoveItem(index); 1203 fSessions.AddItem(session, newIndex); 1204 fTabView->MoveTab(index, newIndex); 1205 } 1206 1207 // activate the respective tab 1208 fTabView->Select(newIndex); 1209 } 1210 1211 1212 TermViewContainerView* 1213 TermWindow::_ActiveTermViewContainerView() const 1214 { 1215 return _TermViewContainerViewAt(fTabView->Selection()); 1216 } 1217 1218 1219 TermViewContainerView* 1220 TermWindow::_TermViewContainerViewAt(int32 index) const 1221 { 1222 if (Session* session = _SessionAt(index)) 1223 return session->containerView; 1224 return NULL; 1225 } 1226 1227 1228 TermView* 1229 TermWindow::_ActiveTermView() const 1230 { 1231 return _ActiveTermViewContainerView()->GetTermView(); 1232 } 1233 1234 1235 TermView* 1236 TermWindow::_TermViewAt(int32 index) const 1237 { 1238 TermViewContainerView* view = _TermViewContainerViewAt(index); 1239 return view != NULL ? view->GetTermView() : NULL; 1240 } 1241 1242 1243 int32 1244 TermWindow::_IndexOfTermView(TermView* termView) const 1245 { 1246 if (!termView) 1247 return -1; 1248 1249 // find the view 1250 int32 count = fTabView->CountTabs(); 1251 for (int32 i = count - 1; i >= 0; i--) { 1252 if (termView == _TermViewAt(i)) 1253 return i; 1254 } 1255 1256 return -1; 1257 } 1258 1259 1260 TermWindow::Session* 1261 TermWindow::_SessionAt(int32 index) const 1262 { 1263 return (Session*)fSessions.ItemAt(index); 1264 } 1265 1266 1267 TermWindow::Session* 1268 TermWindow::_SessionForID(const SessionID& sessionID) const 1269 { 1270 for (int32 i = 0; Session* session = _SessionAt(i); i++) { 1271 if (session->id == sessionID) 1272 return session; 1273 } 1274 1275 return NULL; 1276 } 1277 1278 1279 int32 1280 TermWindow::_IndexOfSession(Session* session) const 1281 { 1282 return fSessions.IndexOf(session); 1283 } 1284 1285 1286 void 1287 TermWindow::_CheckChildren() 1288 { 1289 int32 count = fSessions.CountItems(); 1290 for (int32 i = count - 1; i >= 0; i--) { 1291 Session* session = _SessionAt(i); 1292 if (session->containerView->GetTermView()->CheckShellGone()) 1293 NotifyTermViewQuit(session->containerView->GetTermView(), 0); 1294 } 1295 } 1296 1297 1298 void 1299 TermWindow::Zoom(BPoint leftTop, float width, float height) 1300 { 1301 _ActiveTermView()->DisableResizeView(); 1302 BWindow::Zoom(leftTop, width, height); 1303 } 1304 1305 1306 void 1307 TermWindow::FrameResized(float newWidth, float newHeight) 1308 { 1309 BWindow::FrameResized(newWidth, newHeight); 1310 1311 TermView* view = _ActiveTermView(); 1312 PrefHandler::Default()->setInt32(PREF_COLS, view->Columns()); 1313 PrefHandler::Default()->setInt32(PREF_ROWS, view->Rows()); 1314 } 1315 1316 1317 void 1318 TermWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces) 1319 { 1320 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); 1321 } 1322 1323 1324 void 1325 TermWindow::WorkspaceActivated(int32 workspace, bool state) 1326 { 1327 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); 1328 } 1329 1330 1331 void 1332 TermWindow::Minimize(bool minimize) 1333 { 1334 BWindow::Minimize(minimize); 1335 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces()); 1336 } 1337 1338 1339 void 1340 TermWindow::TabSelected(SmartTabView* tabView, int32 index) 1341 { 1342 SessionChanged(); 1343 } 1344 1345 1346 void 1347 TermWindow::TabDoubleClicked(SmartTabView* tabView, BPoint point, int32 index) 1348 { 1349 if (index >= 0) { 1350 // clicked on a tab -- open the title dialog 1351 _OpenSetTabTitleDialog(index); 1352 } else { 1353 // not clicked on a tab -- create a new one 1354 _NewTab(); 1355 } 1356 } 1357 1358 1359 void 1360 TermWindow::TabMiddleClicked(SmartTabView* tabView, BPoint point, int32 index) 1361 { 1362 if (index >= 0) 1363 _RemoveTab(index); 1364 } 1365 1366 1367 void 1368 TermWindow::TabRightClicked(SmartTabView* tabView, BPoint point, int32 index) 1369 { 1370 if (index < 0) 1371 return; 1372 1373 TermView* termView = _TermViewAt(index); 1374 if (termView == NULL) 1375 return; 1376 1377 BMessage* closeMessage = new BMessage(kCloseView); 1378 _SessionAt(index)->id.AddToMessage(*closeMessage, "session"); 1379 1380 BMessage* closeOthersMessage = new BMessage(kCloseOtherViews); 1381 _SessionAt(index)->id.AddToMessage(*closeOthersMessage, "session"); 1382 1383 BMessage* editTitleMessage = new BMessage(kEditTabTitle); 1384 _SessionAt(index)->id.AddToMessage(*editTitleMessage, "session"); 1385 1386 BPopUpMenu* popUpMenu = new BPopUpMenu("tab menu"); 1387 BLayoutBuilder::Menu<>(popUpMenu) 1388 .AddItem(B_TRANSLATE("Close tab"), closeMessage) 1389 .AddItem(B_TRANSLATE("Close other tabs"), closeOthersMessage) 1390 .AddSeparator() 1391 .AddItem(B_TRANSLATE("Edit tab title" B_UTF8_ELLIPSIS), 1392 editTitleMessage) 1393 ; 1394 1395 popUpMenu->SetAsyncAutoDestruct(true); 1396 popUpMenu->SetTargetForItems(BMessenger(this)); 1397 1398 BPoint screenWhere = tabView->ConvertToScreen(point); 1399 BRect mouseRect(screenWhere, screenWhere); 1400 mouseRect.InsetBy(-4.0, -4.0); 1401 popUpMenu->Go(screenWhere, true, true, mouseRect, true); 1402 } 1403 1404 1405 void 1406 TermWindow::NotifyTermViewQuit(TermView* view, int32 reason) 1407 { 1408 // Since the notification can come from the view, we send a message to 1409 // ourselves to avoid deleting the caller synchronously. 1410 if (Session* session = _SessionAt(_IndexOfTermView(view))) { 1411 BMessage message(kCloseView); 1412 session->id.AddToMessage(message, "session"); 1413 message.AddInt32("reason", reason); 1414 PostMessage(&message); 1415 } 1416 } 1417 1418 1419 void 1420 TermWindow::SetTermViewTitle(TermView* view, const char* title) 1421 { 1422 int32 index = _IndexOfTermView(view); 1423 if (Session* session = _SessionAt(index)) { 1424 session->title.pattern = title; 1425 session->title.patternUserDefined = true; 1426 _UpdateSessionTitle(index); 1427 } 1428 } 1429 1430 1431 void 1432 TermWindow::TitleChanged(SetTitleDialog* dialog, const BString& title, 1433 bool titleUserDefined) 1434 { 1435 if (dialog == fSetTabTitleDialog) { 1436 // tab title 1437 BMessage message(kTabTitleChanged); 1438 fSetTabTitleSession.AddToMessage(message, "session"); 1439 if (titleUserDefined) 1440 message.AddString("title", title); 1441 1442 PostMessage(&message); 1443 } else if (dialog == fSetWindowTitleDialog) { 1444 // window title 1445 BMessage message(kWindowTitleChanged); 1446 if (titleUserDefined) 1447 message.AddString("title", title); 1448 1449 PostMessage(&message); 1450 } 1451 } 1452 1453 1454 void 1455 TermWindow::SetTitleDialogDone(SetTitleDialog* dialog) 1456 { 1457 if (dialog == fSetTabTitleDialog) { 1458 fSetTabTitleSession = SessionID(); 1459 fSetTabTitleDialog = NULL; 1460 // assuming this is atomic 1461 } 1462 } 1463 1464 1465 void 1466 TermWindow::TerminalInfosUpdated(TerminalRoster* roster) 1467 { 1468 PostMessage(kUpdateSwitchTerminalsMenuItem); 1469 } 1470 1471 1472 void 1473 TermWindow::PreviousTermView(TermView* view) 1474 { 1475 _NavigateTab(_IndexOfTermView(view), -1, false); 1476 } 1477 1478 1479 void 1480 TermWindow::NextTermView(TermView* view) 1481 { 1482 _NavigateTab(_IndexOfTermView(view), 1, false); 1483 } 1484 1485 1486 void 1487 TermWindow::_ResizeView(TermView *view) 1488 { 1489 int fontWidth, fontHeight; 1490 view->GetFontSize(&fontWidth, &fontHeight); 1491 1492 float minimumHeight = -1; 1493 if (fMenuBar) 1494 minimumHeight += fMenuBar->Bounds().Height() + 1; 1495 if (fTabView && fTabView->CountTabs() > 1) 1496 minimumHeight += fTabView->TabHeight() + 1; 1497 1498 SetSizeLimits(MIN_COLS * fontWidth - 1, MAX_COLS * fontWidth - 1, 1499 minimumHeight + MIN_ROWS * fontHeight - 1, 1500 minimumHeight + MAX_ROWS * fontHeight - 1); 1501 1502 float width; 1503 float height; 1504 view->Parent()->GetPreferredSize(&width, &height); 1505 width += B_V_SCROLL_BAR_WIDTH; 1506 // NOTE: Width is one pixel too small, since the scroll view 1507 // is one pixel wider than its parent. 1508 height += fMenuBar->Bounds().Height() + 1; 1509 1510 ResizeTo(width, height); 1511 1512 view->Invalidate(); 1513 } 1514 1515 1516 /* static */ 1517 BMenu* 1518 TermWindow::_MakeWindowSizeMenu() 1519 { 1520 BMenu* menu = new (std::nothrow) BMenu(B_TRANSLATE("Window size")); 1521 if (menu == NULL) 1522 return NULL; 1523 1524 const int32 windowSizes[4][2] = { 1525 { 80, 25 }, 1526 { 80, 40 }, 1527 { 132, 25 }, 1528 { 132, 40 } 1529 }; 1530 1531 const int32 sizeNum = sizeof(windowSizes) / sizeof(windowSizes[0]); 1532 for (int32 i = 0; i < sizeNum; i++) { 1533 char label[32]; 1534 int32 columns = windowSizes[i][0]; 1535 int32 rows = windowSizes[i][1]; 1536 snprintf(label, sizeof(label), "%ldx%ld", columns, rows); 1537 BMessage* message = new BMessage(MSG_COLS_CHANGED); 1538 message->AddInt32("columns", columns); 1539 message->AddInt32("rows", rows); 1540 menu->AddItem(new BMenuItem(label, message)); 1541 } 1542 1543 menu->AddSeparatorItem(); 1544 menu->AddItem(new BMenuItem(B_TRANSLATE("Full screen"), 1545 new BMessage(FULLSCREEN), B_ENTER)); 1546 1547 return menu; 1548 } 1549 1550 1551 void 1552 TermWindow::_UpdateSwitchTerminalsMenuItem() 1553 { 1554 fSwitchTerminalsMenuItem->SetEnabled(_FindSwitchTerminalTarget() >= 0); 1555 } 1556 1557 1558 void 1559 TermWindow::_TitleSettingsChanged() 1560 { 1561 if (!fTitle.patternUserDefined) 1562 fTitle.pattern = PrefHandler::Default()->getString(PREF_WINDOW_TITLE); 1563 1564 fSessionTitlePattern = PrefHandler::Default()->getString(PREF_TAB_TITLE); 1565 1566 _UpdateTitles(); 1567 } 1568 1569 1570 void 1571 TermWindow::_UpdateTitles() 1572 { 1573 int32 sessionCount = fSessions.CountItems(); 1574 for (int32 i = 0; i < sessionCount; i++) 1575 _UpdateSessionTitle(i); 1576 } 1577 1578 1579 void 1580 TermWindow::_UpdateSessionTitle(int32 index) 1581 { 1582 Session* session = _SessionAt(index); 1583 if (session == NULL) 1584 return; 1585 1586 // get the shell and active process infos 1587 ShellInfo shellInfo; 1588 ActiveProcessInfo activeProcessInfo; 1589 TermView* termView = _TermViewAt(index); 1590 if (!termView->GetShellInfo(shellInfo) 1591 || !termView->GetActiveProcessInfo(activeProcessInfo)) { 1592 return; 1593 } 1594 1595 // evaluate the session title pattern 1596 BString sessionTitlePattern = session->title.patternUserDefined 1597 ? session->title.pattern : fSessionTitlePattern; 1598 TabTitlePlaceholderMapper tabMapper(shellInfo, activeProcessInfo, 1599 session->index); 1600 const BString& sessionTitle = PatternEvaluator::Evaluate( 1601 sessionTitlePattern, tabMapper); 1602 1603 // set the tab title 1604 if (sessionTitle != session->title.title) { 1605 session->title.title = sessionTitle; 1606 fTabView->TabAt(index)->SetLabel(session->title.title); 1607 fTabView->Invalidate(); 1608 // Invalidate the complete tab view, since other tabs might change 1609 // their positions. 1610 } 1611 1612 // If this is the active tab, also recompute the window title. 1613 if (index != fTabView->Selection()) 1614 return; 1615 1616 // evaluate the window title pattern 1617 WindowTitlePlaceholderMapper windowMapper(shellInfo, activeProcessInfo, 1618 fTerminalRoster.ID() + 1, sessionTitle); 1619 const BString& windowTitle = PatternEvaluator::Evaluate(fTitle.pattern, 1620 windowMapper); 1621 1622 // set the window title 1623 if (windowTitle != fTitle.title) { 1624 fTitle.title = windowTitle; 1625 SetTitle(fTitle.title); 1626 } 1627 } 1628 1629 1630 void 1631 TermWindow::_OpenSetTabTitleDialog(int32 index) 1632 { 1633 // If a dialog is active, finish it. 1634 _FinishTitleDialog(); 1635 1636 BString toolTip = BString(B_TRANSLATE( 1637 "The pattern specifying the current tab title. The following " 1638 "placeholders\n" 1639 "can be used:\n")) << kTooTipSetTabTitlePlaceholders; 1640 fSetTabTitleDialog = new SetTitleDialog( 1641 B_TRANSLATE("Set tab title"), B_TRANSLATE("Tab title:"), 1642 toolTip); 1643 1644 Session* session = _SessionAt(index); 1645 bool userDefined = session->title.patternUserDefined; 1646 const BString& title = userDefined 1647 ? session->title.pattern : fSessionTitlePattern; 1648 fSetTabTitleSession = session->id; 1649 1650 // place the dialog window directly under the tab, but keep it on screen 1651 BPoint location = fTabView->ConvertToScreen( 1652 fTabView->TabFrame(index).LeftBottom() + BPoint(0, 1)); 1653 BRect frame(fSetTabTitleDialog->Frame().OffsetToCopy(location)); 1654 BSize screenSize(BScreen(fSetTabTitleDialog).Frame().Size()); 1655 fSetTabTitleDialog->MoveTo( 1656 BLayoutUtils::MoveIntoFrame(frame, screenSize).LeftTop()); 1657 1658 fSetTabTitleDialog->Go(title, userDefined, this); 1659 } 1660 1661 1662 void 1663 TermWindow::_OpenSetWindowTitleDialog() 1664 { 1665 // If a dialog is active, finish it. 1666 _FinishTitleDialog(); 1667 1668 BString toolTip = BString(B_TRANSLATE( 1669 "The pattern specifying the window title. The following placeholders\n" 1670 "can be used:\n")) << kTooTipSetTabTitlePlaceholders; 1671 fSetWindowTitleDialog = new SetTitleDialog(B_TRANSLATE("Set window title"), 1672 B_TRANSLATE("Window title:"), toolTip); 1673 1674 // center the dialog in the window frame, but keep it on screen 1675 fSetWindowTitleDialog->CenterIn(Frame()); 1676 BRect frame(fSetWindowTitleDialog->Frame()); 1677 BSize screenSize(BScreen(fSetWindowTitleDialog).Frame().Size()); 1678 fSetWindowTitleDialog->MoveTo( 1679 BLayoutUtils::MoveIntoFrame(frame, screenSize).LeftTop()); 1680 1681 fSetWindowTitleDialog->Go(fTitle.pattern, fTitle.patternUserDefined, this); 1682 } 1683 1684 1685 void 1686 TermWindow::_FinishTitleDialog() 1687 { 1688 SetTitleDialog* oldDialog = fSetTabTitleDialog; 1689 if (oldDialog != NULL && oldDialog->Lock()) { 1690 // might have been unset in the meantime, so recheck 1691 if (fSetTabTitleDialog == oldDialog) { 1692 oldDialog->Finish(); 1693 // this also unsets the variables 1694 } 1695 oldDialog->Unlock(); 1696 return; 1697 } 1698 1699 oldDialog = fSetWindowTitleDialog; 1700 if (oldDialog != NULL && oldDialog->Lock()) { 1701 // might have been unset in the meantime, so recheck 1702 if (fSetWindowTitleDialog == oldDialog) { 1703 oldDialog->Finish(); 1704 // this also unsets the variable 1705 } 1706 oldDialog->Unlock(); 1707 return; 1708 } 1709 } 1710 1711 1712 void 1713 TermWindow::_SwitchTerminal() 1714 { 1715 team_id teamID = _FindSwitchTerminalTarget(); 1716 if (teamID < 0) 1717 return; 1718 1719 BMessenger app(TERM_SIGNATURE, teamID); 1720 app.SendMessage(MSG_ACTIVATE_TERM); 1721 } 1722 1723 1724 team_id 1725 TermWindow::_FindSwitchTerminalTarget() 1726 { 1727 AutoLocker<TerminalRoster> rosterLocker(fTerminalRoster); 1728 1729 team_id myTeamID = Team(); 1730 1731 int32 numTerms = fTerminalRoster.CountTerminals(); 1732 if (numTerms <= 1) 1733 return -1; 1734 1735 // Find our position in the Terminal teams. 1736 int32 i; 1737 1738 for (i = 0; i < numTerms; i++) { 1739 if (myTeamID == fTerminalRoster.TerminalAt(i)->team) 1740 break; 1741 } 1742 1743 if (i == numTerms) { 1744 // we didn't find ourselves -- that shouldn't happen 1745 return -1; 1746 } 1747 1748 uint32 currentWorkspace = 1L << current_workspace(); 1749 1750 while (true) { 1751 if (--i < 0) 1752 i = numTerms - 1; 1753 1754 const TerminalRoster::Info* info = fTerminalRoster.TerminalAt(i); 1755 if (info->team == myTeamID) { 1756 // That's ourselves again. We've run through the complete list. 1757 return -1; 1758 } 1759 1760 if (!info->minimized && (info->workspaces & currentWorkspace) != 0) 1761 return info->team; 1762 } 1763 } 1764 1765 1766 TermWindow::SessionID 1767 TermWindow::_NewSessionID() 1768 { 1769 return fNextSessionID++; 1770 } 1771 1772 1773 int32 1774 TermWindow::_NewSessionIndex() 1775 { 1776 for (int32 id = 1; ; id++) { 1777 bool used = false; 1778 1779 for (int32 i = 0; 1780 Session* session = _SessionAt(i); i++) { 1781 if (id == session->index) { 1782 used = true; 1783 break; 1784 } 1785 } 1786 1787 if (!used) 1788 return id; 1789 } 1790 } 1791