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