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