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