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