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