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