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