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