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