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