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