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