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