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