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