BTRFS: Implement BTree::Path and change _Find.
[haiku.git] / src / apps / terminal / TermWindow.cpp
blob81896f7aaa13ab4f78cb239dd618be70c39acd15
1 /*
2 * Copyright 2007-2015, 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.
7 * Distributed under the terms of the MIT license.
9 * Authors:
10 * Kian Duffy, myob@users.sourceforge.net
11 * Daniel Furrer, assimil8or@users.sourceforge.net
12 * John Scipione, jscipione@gmail.com
13 * Siarzhuk Zharski, zharik@gmx.li
17 #include "TermWindow.h"
19 #include <new>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <strings.h>
23 #include <time.h>
25 #include <Alert.h>
26 #include <Application.h>
27 #include <Catalog.h>
28 #include <CharacterSet.h>
29 #include <CharacterSetRoster.h>
30 #include <Clipboard.h>
31 #include <Dragger.h>
32 #include <File.h>
33 #include <FindDirectory.h>
34 #include <LayoutBuilder.h>
35 #include <LayoutUtils.h>
36 #include <Locale.h>
37 #include <Menu.h>
38 #include <MenuBar.h>
39 #include <MenuItem.h>
40 #include <Path.h>
41 #include <PopUpMenu.h>
42 #include <PrintJob.h>
43 #include <Rect.h>
44 #include <Roster.h>
45 #include <Screen.h>
46 #include <ScrollBar.h>
47 #include <ScrollView.h>
48 #include <String.h>
49 #include <UTF8.h>
51 #include <AutoLocker.h>
53 #include "ActiveProcessInfo.h"
54 #include "Arguments.h"
55 #include "AppearPrefView.h"
56 #include "FindWindow.h"
57 #include "Globals.h"
58 #include "PrefWindow.h"
59 #include "PrefHandler.h"
60 #include "SetTitleDialog.h"
61 #include "ShellParameters.h"
62 #include "TermConst.h"
63 #include "TermScrollView.h"
64 #include "TitlePlaceholderMapper.h"
67 const static int32 kTermViewOffset = 3;
69 const static int32 kMinimumFontSize = 8;
70 const static int32 kMaximumFontSize = 36;
72 // messages constants
73 static const uint32 kNewTab = 'NTab';
74 static const uint32 kCloseView = 'ClVw';
75 static const uint32 kCloseOtherViews = 'CloV';
76 static const uint32 kIncreaseFontSize = 'InFs';
77 static const uint32 kDecreaseFontSize = 'DcFs';
78 static const uint32 kSetActiveTab = 'STab';
79 static const uint32 kUpdateTitles = 'UPti';
80 static const uint32 kEditTabTitle = 'ETti';
81 static const uint32 kEditWindowTitle = 'EWti';
82 static const uint32 kTabTitleChanged = 'TTch';
83 static const uint32 kWindowTitleChanged = 'WTch';
84 static const uint32 kUpdateSwitchTerminalsMenuItem = 'Ustm';
86 using namespace BPrivate ; // BCharacterSet stuff
88 #undef B_TRANSLATION_CONTEXT
89 #define B_TRANSLATION_CONTEXT "Terminal TermWindow"
91 // actually an arrow
92 #define UTF8_ENTER "\xe2\x86\xb5"
95 // #pragma mark - TermViewContainerView
98 class TermViewContainerView : public BView {
99 public:
100 TermViewContainerView(TermView* termView)
102 BView(BRect(), "term view container", B_FOLLOW_ALL, 0),
103 fTermView(termView)
105 termView->MoveTo(kTermViewOffset, kTermViewOffset);
106 BRect frame(termView->Frame());
107 ResizeTo(frame.right + kTermViewOffset, frame.bottom + kTermViewOffset);
108 AddChild(termView);
111 TermView* GetTermView() const { return fTermView; }
113 virtual void GetPreferredSize(float* _width, float* _height)
115 float width, height;
116 fTermView->GetPreferredSize(&width, &height);
117 *_width = width + 2 * kTermViewOffset;
118 *_height = height + 2 * kTermViewOffset;
121 private:
122 TermView* fTermView;
126 // #pragma mark - SessionID
129 TermWindow::SessionID::SessionID(int32 id)
131 fID(id)
136 TermWindow::SessionID::SessionID(const BMessage& message, const char* field)
138 if (message.FindInt32(field, &fID) != B_OK)
139 fID = -1;
143 status_t
144 TermWindow::SessionID::AddToMessage(BMessage& message, const char* field) const
146 return message.AddInt32(field, fID);
150 // #pragma mark - Session
153 struct TermWindow::Session {
154 SessionID id;
155 int32 index;
156 Title title;
157 TermViewContainerView* containerView;
159 Session(SessionID id, int32 index, TermViewContainerView* containerView)
161 id(id),
162 index(index),
163 containerView(containerView)
165 title.title = B_TRANSLATE("Shell ");
166 title.title << index;
167 title.patternUserDefined = false;
172 // #pragma mark - TermWindow
175 TermWindow::TermWindow(const BString& title, Arguments* args)
177 BWindow(BRect(0, 0, 0, 0), title, B_DOCUMENT_WINDOW,
178 B_CURRENT_WORKSPACE | B_QUIT_ON_WINDOW_CLOSE),
179 fTitleUpdateRunner(this, BMessage(kUpdateTitles), 1000000),
180 fNextSessionID(0),
181 fTabView(NULL),
182 fMenuBar(NULL),
183 fSwitchTerminalsMenuItem(NULL),
184 fEncodingMenu(NULL),
185 fPrintSettings(NULL),
186 fPrefWindow(NULL),
187 fFindPanel(NULL),
188 fSavedFrame(0, 0, -1, -1),
189 fSetWindowTitleDialog(NULL),
190 fSetTabTitleDialog(NULL),
191 fFindString(""),
192 fFindNextMenuItem(NULL),
193 fFindPreviousMenuItem(NULL),
194 fFindSelection(false),
195 fForwardSearch(false),
196 fMatchCase(false),
197 fMatchWord(false),
198 fFullScreen(false)
200 // register this terminal
201 fTerminalRoster.Register(Team(), this);
202 fTerminalRoster.SetListener(this);
203 int32 id = fTerminalRoster.ID();
205 // apply the title settings
206 fTitle.pattern = title;
207 if (fTitle.pattern.Length() == 0) {
208 fTitle.pattern = B_TRANSLATE_SYSTEM_NAME("Terminal");
210 if (id >= 0)
211 fTitle.pattern << " " << id + 1;
213 fTitle.patternUserDefined = false;
214 } else
215 fTitle.patternUserDefined = true;
217 fTitle.title = fTitle.pattern;
218 fTitle.pattern = title;
220 _TitleSettingsChanged();
222 // get the saved window position and workspaces
223 BRect frame;
224 uint32 workspaces;
225 if (_LoadWindowPosition(&frame, &workspaces) == B_OK) {
226 // make sure the window is still on screen
227 // (for example if there was a resolution change)
228 BRect screenFrame = BScreen(this).Frame();
229 if (frame.Width() <= screenFrame.Width()
230 && frame.Height() <= screenFrame.Height())
231 ResizeTo(frame.Width(), frame.Height());
233 MoveTo(frame.LeftTop());
234 MoveOnScreen(B_MOVE_IF_PARTIALLY_OFFSCREEN);
236 SetWorkspaces(workspaces);
237 } else {
238 // use computed defaults
239 int row = id / 16;
240 int column = id % 16;
241 int x = (column * 16) + (row * 64) + 50;
242 int y = (column * 16) + 50;
244 MoveTo(x, y);
247 // init the GUI and add a tab
248 _InitWindow();
249 _AddTab(args);
251 // Announce our window as no longer minimized. That's not true, since it's
252 // still hidden at this point, but it will be shown very soon.
253 fTerminalRoster.SetWindowInfo(false, Workspaces());
257 TermWindow::~TermWindow()
259 fTerminalRoster.Unregister();
261 _FinishTitleDialog();
263 if (fPrefWindow)
264 fPrefWindow->PostMessage(B_QUIT_REQUESTED);
266 if (fFindPanel && fFindPanel->Lock()) {
267 fFindPanel->Quit();
268 fFindPanel = NULL;
271 PrefHandler::DeleteDefault();
273 for (int32 i = 0; Session* session = _SessionAt(i); i++)
274 delete session;
278 void
279 TermWindow::SessionChanged()
281 _UpdateSessionTitle(fTabView->Selection());
285 void
286 TermWindow::_InitWindow()
288 // make menu bar
289 _SetupMenu();
291 // shortcuts to switch tabs
292 for (int32 i = 0; i < 9; i++) {
293 BMessage* message = new BMessage(kSetActiveTab);
294 message->AddInt32("index", i);
295 AddShortcut('1' + i, B_COMMAND_KEY, message);
298 AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
299 new BMessage(MSG_MOVE_TAB_LEFT));
300 AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY | B_SHIFT_KEY,
301 new BMessage(MSG_MOVE_TAB_RIGHT));
303 BRect textFrame = Bounds();
304 textFrame.top = fMenuBar->Bounds().bottom + 1.0;
306 fTabView = new SmartTabView(textFrame, "tab view", B_WIDTH_FROM_LABEL);
307 fTabView->SetListener(this);
308 AddChild(fTabView);
310 // Make the scroll view one pixel wider than the tab view container view, so
311 // the scroll bar will look good.
312 fTabView->SetInsets(0, 0, -1, 0);
316 bool
317 TermWindow::_CanClose(int32 index)
319 bool warnOnExit = PrefHandler::Default()->getBool(PREF_WARN_ON_EXIT);
321 if (!warnOnExit)
322 return true;
324 uint32 busyProcessCount = 0;
325 BString busyProcessNames;
326 // all names, separated by "\n\t"
328 if (index != -1) {
329 ShellInfo shellInfo;
330 ActiveProcessInfo info;
331 TermView* termView = _TermViewAt(index);
332 if (termView->GetShellInfo(shellInfo)
333 && termView->GetActiveProcessInfo(info)
334 && (info.ID() != shellInfo.ProcessID()
335 || !shellInfo.IsDefaultShell())) {
336 busyProcessCount++;
337 busyProcessNames = info.Name();
339 } else {
340 for (int32 i = 0; i < fSessions.CountItems(); i++) {
341 ShellInfo shellInfo;
342 ActiveProcessInfo info;
343 TermView* termView = _TermViewAt(i);
344 if (termView->GetShellInfo(shellInfo)
345 && termView->GetActiveProcessInfo(info)
346 && (info.ID() != shellInfo.ProcessID()
347 || !shellInfo.IsDefaultShell())) {
348 if (++busyProcessCount > 1)
349 busyProcessNames << "\n\t";
350 busyProcessNames << info.Name();
355 if (busyProcessCount == 0)
356 return true;
358 BString alertMessage;
359 if (busyProcessCount == 1) {
360 // Only one pending process. Select the alert text depending on whether
361 // the terminal will be closed.
362 alertMessage = index == -1 || fSessions.CountItems() == 1
363 ? B_TRANSLATE("The process \"%1\" is still running.\n"
364 "If you close the Terminal, the process will be killed.")
365 : B_TRANSLATE("The process \"%1\" is still running.\n"
366 "If you close the tab, the process will be killed.");
367 } else {
368 // multiple pending processes
369 alertMessage = B_TRANSLATE(
370 "The following processes are still running:\n\n"
371 "\t%1\n\n"
372 "If you close the Terminal, the processes will be killed.");
375 alertMessage.ReplaceFirst("%1", busyProcessNames);
377 BAlert* alert = new BAlert(B_TRANSLATE("Really close?"),
378 alertMessage, B_TRANSLATE("Close"), B_TRANSLATE("Cancel"), NULL,
379 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
380 alert->SetShortcut(1, B_ESCAPE);
381 return alert->Go() == 0;
385 bool
386 TermWindow::QuitRequested()
388 _FinishTitleDialog();
390 if (!_CanClose(-1))
391 return false;
393 _SaveWindowPosition();
395 return BWindow::QuitRequested();
399 void
400 TermWindow::MenusBeginning()
402 TermView* view = _ActiveTermView();
404 // Syncronize Encode Menu Pop-up menu and Preference.
405 const BCharacterSet* charset
406 = BCharacterSetRoster::GetCharacterSetByConversionID(view->Encoding());
407 if (charset != NULL) {
408 BString name(charset->GetPrintName());
409 const char* mime = charset->GetMIMEName();
410 if (mime)
411 name << " (" << mime << ")";
413 BMenuItem* item = fEncodingMenu->FindItem(name);
414 if (item != NULL)
415 item->SetMarked(true);
418 BFont font;
419 view->GetTermFont(&font);
421 float size = font.Size();
423 fDecreaseFontSizeMenuItem->SetEnabled(size > kMinimumFontSize);
424 fIncreaseFontSizeMenuItem->SetEnabled(size < kMaximumFontSize);
426 BWindow::MenusBeginning();
430 /* static */ void
431 TermWindow::MakeEncodingMenu(BMenu* menu)
433 BCharacterSetRoster roster;
434 BCharacterSet charset;
435 while (roster.GetNextCharacterSet(&charset) == B_OK) {
436 int encoding = M_UTF8;
437 const char* mime = charset.GetMIMEName();
438 if (mime == NULL || strcasecmp(mime, "UTF-8") != 0)
439 encoding = charset.GetConversionID();
441 // filter out currently (???) not supported USC-2 and UTF-16
442 if (encoding == B_UTF16_CONVERSION || encoding == B_UNICODE_CONVERSION)
443 continue;
445 BString name(charset.GetPrintName());
446 if (mime)
447 name << " (" << mime << ")";
449 BMessage *message = new BMessage(MENU_ENCODING);
450 if (message != NULL) {
451 message->AddInt32("op", (int32)encoding);
452 menu->AddItem(new BMenuItem(name, message));
456 menu->SetRadioMode(true);
460 void
461 TermWindow::_SetupMenu()
463 fFontSizeMenu = _MakeFontSizeMenu(MSG_HALF_SIZE_CHANGED,
464 PrefHandler::Default()->getInt32(PREF_HALF_FONT_SIZE));
465 fIncreaseFontSizeMenuItem = new BMenuItem(B_TRANSLATE("Increase"),
466 new BMessage(kIncreaseFontSize), '+', B_COMMAND_KEY);
467 fDecreaseFontSizeMenuItem = new BMenuItem(B_TRANSLATE("Decrease"),
468 new BMessage(kDecreaseFontSize), '-', B_COMMAND_KEY);
469 fFontSizeMenu->AddSeparatorItem();
470 fFontSizeMenu->AddItem(fIncreaseFontSizeMenuItem);
471 fFontSizeMenu->AddItem(fDecreaseFontSizeMenuItem);
473 BMenu* windowSize = new(std::nothrow) BMenu(B_TRANSLATE("Window size"));
474 if (windowSize != NULL) {
475 MakeWindowSizeMenu(windowSize);
476 windowSize->AddSeparatorItem();
477 windowSize->AddItem(new BMenuItem(B_TRANSLATE("Full screen"),
478 new BMessage(FULLSCREEN), B_ENTER));
481 fEncodingMenu = new(std::nothrow) BMenu(B_TRANSLATE("Text encoding"));
482 if (fEncodingMenu != NULL)
483 MakeEncodingMenu(fEncodingMenu);
485 BLayoutBuilder::Menu<>(fMenuBar = new BMenuBar(Bounds(), "mbar"))
486 // Terminal
487 .AddMenu(B_TRANSLATE_COMMENT("Terminal", "The title for the main window"
488 " menubar entry related to terminal sessions"))
489 .AddItem(B_TRANSLATE("Switch Terminals"), MENU_SWITCH_TERM, B_TAB)
490 .GetItem(fSwitchTerminalsMenuItem)
491 .AddItem(B_TRANSLATE("New Terminal"), MENU_NEW_TERM, 'N')
492 .AddItem(B_TRANSLATE("New tab"), kNewTab, 'T')
493 .AddSeparator()
494 .AddItem(B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), MENU_PAGE_SETUP)
495 .AddItem(B_TRANSLATE("Print"), MENU_PRINT, 'P')
496 .AddSeparator()
497 .AddItem(B_TRANSLATE("Close window"), B_QUIT_REQUESTED, 'W',
498 B_SHIFT_KEY)
499 .AddItem(B_TRANSLATE("Close active tab"), kCloseView, 'W')
500 .AddItem(B_TRANSLATE("Quit"), B_QUIT_REQUESTED, 'Q')
501 .End()
503 // Edit
504 .AddMenu(B_TRANSLATE("Edit"))
505 .AddItem(B_TRANSLATE("Copy"), B_COPY, 'C')
506 .AddItem(B_TRANSLATE("Paste"), B_PASTE, 'V')
507 .AddSeparator()
508 .AddItem(B_TRANSLATE("Select all"), B_SELECT_ALL, 'A')
509 .AddItem(B_TRANSLATE("Clear all"), MENU_CLEAR_ALL, 'L')
510 .AddSeparator()
511 .AddItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), MENU_FIND_STRING, 'F')
512 .AddItem(B_TRANSLATE("Find previous"), MENU_FIND_PREVIOUS, 'G',
513 B_SHIFT_KEY)
514 .GetItem(fFindPreviousMenuItem)
515 .SetEnabled(false)
516 .AddItem(B_TRANSLATE("Find next"), MENU_FIND_NEXT, 'G')
517 .GetItem(fFindNextMenuItem)
518 .SetEnabled(false)
519 .End()
521 // Settings
522 .AddMenu(B_TRANSLATE("Settings"))
523 .AddItem(B_TRANSLATE("Window title" B_UTF8_ELLIPSIS),
524 kEditWindowTitle)
525 .AddItem(windowSize)
526 .AddItem(fEncodingMenu)
527 .AddItem(fFontSizeMenu)
528 .AddItem(B_TRANSLATE("Save as default"), MSG_SAVE_AS_DEFAULT)
529 .AddSeparator()
530 .AddItem(B_TRANSLATE("Settings" B_UTF8_ELLIPSIS), MENU_PREF_OPEN)
531 .End();
533 AddChild(fMenuBar);
535 _UpdateSwitchTerminalsMenuItem();
537 #ifdef USE_DEBUG_SNAPSHOTS
538 AddShortcut('S', B_COMMAND_KEY | B_CONTROL_KEY,
539 new BMessage(SHORTCUT_DEBUG_SNAPSHOTS));
540 AddShortcut('C', B_COMMAND_KEY | B_CONTROL_KEY,
541 new BMessage(SHORTCUT_DEBUG_CAPTURE));
542 #endif
546 status_t
547 TermWindow::_GetWindowPositionFile(BFile* file, uint32 openMode)
549 BPath path;
550 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, true);
551 if (status != B_OK)
552 return status;
554 status = path.Append("Terminal");
555 if (status != B_OK)
556 return status;
558 status = path.Append("Windows");
559 if (status != B_OK)
560 return status;
562 return file->SetTo(path.Path(), openMode);
566 status_t
567 TermWindow::_LoadWindowPosition(BRect* frame, uint32* workspaces)
569 status_t status;
570 BMessage position;
572 BFile file;
573 status = _GetWindowPositionFile(&file, B_READ_ONLY);
574 if (status != B_OK)
575 return status;
577 status = position.Unflatten(&file);
579 file.Unset();
581 if (status != B_OK)
582 return status;
584 int32 id = fTerminalRoster.ID();
585 status = position.FindRect("rect", id, frame);
586 if (status != B_OK)
587 return status;
589 int32 _workspaces;
590 status = position.FindInt32("workspaces", id, &_workspaces);
591 if (status != B_OK)
592 return status;
593 if (modifiers() & B_SHIFT_KEY)
594 *workspaces = _workspaces;
595 else
596 *workspaces = B_CURRENT_WORKSPACE;
598 return B_OK;
602 status_t
603 TermWindow::_SaveWindowPosition()
605 BFile file;
606 BMessage originalSettings;
608 // Read the settings file if it exists and is a valid BMessage.
609 status_t status = _GetWindowPositionFile(&file, B_READ_ONLY);
610 if (status == B_OK) {
611 status = originalSettings.Unflatten(&file);
612 file.Unset();
614 if (status != B_OK)
615 status = originalSettings.MakeEmpty();
617 if (status != B_OK)
618 return status;
621 // Replace the settings
622 int32 id = fTerminalRoster.ID();
623 BRect rect(Frame());
624 if (originalSettings.ReplaceRect("rect", id, rect) != B_OK)
625 originalSettings.AddRect("rect", rect);
627 int32 workspaces = Workspaces();
628 if (originalSettings.ReplaceInt32("workspaces", id, workspaces) != B_OK)
629 originalSettings.AddInt32("workspaces", workspaces);
631 // Resave the whole thing
632 status = _GetWindowPositionFile (&file,
633 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
634 if (status != B_OK)
635 return status;
637 return originalSettings.Flatten(&file);
641 void
642 TermWindow::_GetPreferredFont(BFont& font)
644 // Default to be_fixed_font
645 font = be_fixed_font;
647 const char* family
648 = PrefHandler::Default()->getString(PREF_HALF_FONT_FAMILY);
649 const char* style
650 = PrefHandler::Default()->getString(PREF_HALF_FONT_STYLE);
651 const char* size = PrefHandler::Default()->getString(PREF_HALF_FONT_SIZE);
653 font.SetFamilyAndStyle(family, style);
654 font.SetSize(atoi(size));
656 // mark the font size menu item
657 for (int32 i = 0; i < fFontSizeMenu->CountItems(); i++) {
658 BMenuItem* item = fFontSizeMenu->ItemAt(i);
659 if (item == NULL)
660 continue;
662 item->SetMarked(false);
663 if (strcmp(item->Label(), size) == 0)
664 item->SetMarked(true);
669 void
670 TermWindow::MessageReceived(BMessage *message)
672 int32 encodingId;
673 bool findresult;
675 switch (message->what) {
676 case B_COPY:
677 _ActiveTermView()->Copy(be_clipboard);
678 break;
680 case B_PASTE:
681 _ActiveTermView()->Paste(be_clipboard);
682 break;
684 #ifdef USE_DEBUG_SNAPSHOTS
685 case SHORTCUT_DEBUG_SNAPSHOTS:
686 _ActiveTermView()->MakeDebugSnapshots();
687 break;
689 case SHORTCUT_DEBUG_CAPTURE:
690 _ActiveTermView()->StartStopDebugCapture();
691 break;
692 #endif
694 case B_SELECT_ALL:
695 _ActiveTermView()->SelectAll();
696 break;
698 case MENU_CLEAR_ALL:
699 _ActiveTermView()->Clear();
700 break;
702 case MENU_SWITCH_TERM:
703 _SwitchTerminal();
704 break;
706 case MENU_NEW_TERM:
708 // Set our current working directory to that of the active tab, so
709 // that the new terminal and its shell inherit it.
710 // Note: That's a bit lame. We should rather fork() and change the
711 // CWD in the child, but since ATM there aren't any side effects of
712 // changing our CWD, we save ourselves the trouble.
713 ActiveProcessInfo activeProcessInfo;
714 if (_ActiveTermView()->GetActiveProcessInfo(activeProcessInfo))
715 chdir(activeProcessInfo.CurrentDirectory());
717 app_info info;
718 be_app->GetAppInfo(&info);
720 // try launching two different ways to work around possible problems
721 if (be_roster->Launch(&info.ref) != B_OK)
722 be_roster->Launch(TERM_SIGNATURE);
723 break;
726 case MENU_PREF_OPEN:
727 if (!fPrefWindow) {
728 fPrefWindow = new PrefWindow(this);
729 } else
730 fPrefWindow->Activate();
731 break;
733 case MSG_PREF_CLOSED:
734 fPrefWindow = NULL;
735 break;
737 case MSG_WINDOW_TITLE_SETTING_CHANGED:
738 case MSG_TAB_TITLE_SETTING_CHANGED:
739 _TitleSettingsChanged();
740 break;
742 case MENU_FIND_STRING:
743 if (fFindPanel == NULL) {
744 fFindPanel = new FindWindow(this, fFindString, fFindSelection,
745 fMatchWord, fMatchCase, fForwardSearch);
747 fFindPanel->CenterIn(Frame());
748 _MoveWindowInScreen(fFindPanel);
749 fFindPanel->Show();
750 } else
751 fFindPanel->Activate();
752 break;
754 case MSG_FIND:
756 fFindPanel->PostMessage(B_QUIT_REQUESTED);
757 message->FindBool("findselection", &fFindSelection);
758 if (!fFindSelection)
759 message->FindString("findstring", &fFindString);
760 else
761 _ActiveTermView()->GetSelection(fFindString);
763 if (fFindString.Length() == 0) {
764 const char* errorMsg = !fFindSelection
765 ? B_TRANSLATE("No search string was entered.")
766 : B_TRANSLATE("Nothing is selected.");
767 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
768 errorMsg, B_TRANSLATE("OK"), NULL, NULL,
769 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
770 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
772 alert->Go();
773 fFindPreviousMenuItem->SetEnabled(false);
774 fFindNextMenuItem->SetEnabled(false);
775 break;
778 message->FindBool("forwardsearch", &fForwardSearch);
779 message->FindBool("matchcase", &fMatchCase);
780 message->FindBool("matchword", &fMatchWord);
781 findresult = _ActiveTermView()->Find(fFindString, fForwardSearch,
782 fMatchCase, fMatchWord);
784 if (!findresult) {
785 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
786 B_TRANSLATE("Text not found."),
787 B_TRANSLATE("OK"), NULL, NULL,
788 B_WIDTH_AS_USUAL, B_WARNING_ALERT);
789 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
790 alert->Go();
791 fFindPreviousMenuItem->SetEnabled(false);
792 fFindNextMenuItem->SetEnabled(false);
793 break;
796 // Enable the menu items Find Next and Find Previous
797 fFindPreviousMenuItem->SetEnabled(true);
798 fFindNextMenuItem->SetEnabled(true);
799 break;
802 case MENU_FIND_NEXT:
803 case MENU_FIND_PREVIOUS:
804 findresult = _ActiveTermView()->Find(fFindString,
805 (message->what == MENU_FIND_NEXT) == fForwardSearch,
806 fMatchCase, fMatchWord);
807 if (!findresult) {
808 BAlert* alert = new BAlert(B_TRANSLATE("Find failed"),
809 B_TRANSLATE("Not found."), B_TRANSLATE("OK"),
810 NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
811 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
812 alert->Go();
814 break;
816 case MSG_FIND_CLOSED:
817 fFindPanel = NULL;
818 break;
820 case MENU_ENCODING:
821 if (message->FindInt32("op", &encodingId) == B_OK)
822 _ActiveTermView()->SetEncoding(encodingId);
823 break;
825 case MSG_COLS_CHANGED:
827 int32 columns, rows;
828 if (message->FindInt32("columns", &columns) != B_OK
829 || message->FindInt32("rows", &rows) != B_OK) {
830 break;
833 for (int32 i = 0; i < fTabView->CountTabs(); i++) {
834 TermView* view = _TermViewAt(i);
835 view->SetTermSize(rows, columns, true);
836 _ResizeView(view);
838 break;
841 case MSG_HALF_FONT_CHANGED:
842 case MSG_FULL_FONT_CHANGED:
843 case MSG_ALLOW_BOLD_CHANGED:
845 BFont font;
846 _GetPreferredFont(font);
847 for (int32 i = 0; i < fTabView->CountTabs(); i++) {
848 TermView* view = _TermViewAt(i);
849 view->SetTermFont(&font);
850 _ResizeView(view);
852 break;
855 case MSG_HALF_SIZE_CHANGED:
856 case MSG_FULL_SIZE_CHANGED:
858 const char* size = NULL;
859 if (message->FindString("font_size", &size) != B_OK)
860 break;
862 // mark the font size menu item
863 for (int32 i = 0; i < fFontSizeMenu->CountItems(); i++) {
864 BMenuItem* item = fFontSizeMenu->ItemAt(i);
865 if (item == NULL)
866 continue;
868 item->SetMarked(false);
869 if (strcmp(item->Label(), size) == 0)
870 item->SetMarked(true);
873 BFont font;
874 _ActiveTermView()->GetTermFont(&font);
875 font.SetSize(atoi(size));
876 PrefHandler::Default()->setInt32(PREF_HALF_FONT_SIZE,
877 (int32)atoi(size));
878 for (int32 i = 0; i < fTabView->CountTabs(); i++) {
879 TermView* view = _TermViewAt(i);
880 _TermViewAt(i)->SetTermFont(&font);
881 _ResizeView(view);
883 break;
886 case FULLSCREEN:
887 if (!fSavedFrame.IsValid()) { // go fullscreen
888 _ActiveTermView()->DisableResizeView();
889 float mbHeight = fMenuBar->Bounds().Height() + 1;
890 fSavedFrame = Frame();
891 BScreen screen(this);
893 for (int32 i = fTabView->CountTabs() - 1; i >= 0 ; i--)
894 _TermViewAt(i)->ScrollBar()->ResizeBy(0,
895 (B_H_SCROLL_BAR_HEIGHT - 1));
897 fMenuBar->Hide();
898 fTabView->ResizeBy(0, mbHeight);
899 fTabView->MoveBy(0, -mbHeight);
900 fSavedLook = Look();
901 // done before ResizeTo to work around a Dano bug
902 // (not erasing the decor)
903 SetLook(B_NO_BORDER_WINDOW_LOOK);
904 ResizeTo(screen.Frame().Width() + 1, screen.Frame().Height() + 1);
905 MoveTo(screen.Frame().left, screen.Frame().top);
906 SetFlags(Flags() | (B_NOT_RESIZABLE | B_NOT_MOVABLE));
907 fFullScreen = true;
908 } else { // exit fullscreen
909 _ActiveTermView()->DisableResizeView();
910 float mbHeight = fMenuBar->Bounds().Height() + 1;
911 fMenuBar->Show();
913 for (int32 i = fTabView->CountTabs() - 1; i >= 0 ; i--)
914 _TermViewAt(i)->ScrollBar()->ResizeBy(0,
915 -(B_H_SCROLL_BAR_HEIGHT - 1));
917 ResizeTo(fSavedFrame.Width(), fSavedFrame.Height());
918 MoveTo(fSavedFrame.left, fSavedFrame.top);
919 fTabView->ResizeBy(0, -mbHeight);
920 fTabView->MoveBy(0, mbHeight);
921 SetLook(fSavedLook);
922 fSavedFrame = BRect(0, 0, -1, -1);
923 SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE));
924 fFullScreen = false;
926 break;
928 case MSG_FONT_CHANGED:
929 PostMessage(MSG_HALF_FONT_CHANGED);
930 break;
932 case MSG_COLOR_CHANGED:
933 case MSG_COLOR_SCHEME_CHANGED:
935 _SetTermColors(_ActiveTermViewContainerView());
936 _ActiveTermViewContainerView()->Invalidate();
937 _ActiveTermView()->Invalidate();
938 break;
940 case MSG_SAVE_AS_DEFAULT:
942 BPath path;
943 if (PrefHandler::GetDefaultPath(path) == B_OK) {
944 PrefHandler::Default()->SaveAsText(path.Path(),
945 PREFFILE_MIMETYPE);
947 break;
950 case MENU_PAGE_SETUP:
951 _DoPageSetup();
952 break;
954 case MENU_PRINT:
955 _DoPrint();
956 break;
958 case MSG_CHECK_CHILDREN:
959 _CheckChildren();
960 break;
962 case MSG_MOVE_TAB_LEFT:
963 case MSG_MOVE_TAB_RIGHT:
964 _NavigateTab(_IndexOfTermView(_ActiveTermView()),
965 message->what == MSG_MOVE_TAB_LEFT ? -1 : 1, true);
966 break;
968 case kTabTitleChanged:
970 // tab title changed message from SetTitleDialog
971 SessionID sessionID(*message, "session");
972 if (Session* session = _SessionForID(sessionID)) {
973 BString title;
974 if (message->FindString("title", &title) == B_OK) {
975 session->title.pattern = title;
976 session->title.patternUserDefined = true;
977 } else {
978 session->title.pattern.Truncate(0);
979 session->title.patternUserDefined = false;
981 _UpdateSessionTitle(_IndexOfSession(session));
983 break;
986 case kWindowTitleChanged:
988 // window title changed message from SetTitleDialog
989 BString title;
990 if (message->FindString("title", &title) == B_OK) {
991 fTitle.pattern = title;
992 fTitle.patternUserDefined = true;
993 } else {
994 fTitle.pattern
995 = PrefHandler::Default()->getString(PREF_WINDOW_TITLE);
996 fTitle.patternUserDefined = false;
999 _UpdateSessionTitle(fTabView->Selection());
1000 // updates the window title as a side effect
1002 break;
1005 case kSetActiveTab:
1007 int32 index;
1008 if (message->FindInt32("index", &index) == B_OK
1009 && index >= 0 && index < fSessions.CountItems()) {
1010 fTabView->Select(index);
1012 break;
1015 case kNewTab:
1016 _NewTab();
1017 break;
1019 case kCloseView:
1021 int32 index = -1;
1022 SessionID sessionID(*message, "session");
1023 if (sessionID.IsValid()) {
1024 if (Session* session = _SessionForID(sessionID))
1025 index = _IndexOfSession(session);
1026 } else
1027 index = _IndexOfTermView(_ActiveTermView());
1029 if (index >= 0)
1030 _RemoveTab(index);
1032 break;
1035 case kCloseOtherViews:
1037 Session* session = _SessionForID(SessionID(*message, "session"));
1038 if (session == NULL)
1039 break;
1041 int32 count = fSessions.CountItems();
1042 for (int32 i = count - 1; i >= 0; i--) {
1043 if (_SessionAt(i) != session)
1044 _RemoveTab(i);
1047 break;
1050 case kIncreaseFontSize:
1051 case kDecreaseFontSize:
1053 BFont font;
1054 _ActiveTermView()->GetTermFont(&font);
1055 float size = font.Size();
1057 if (message->what == kIncreaseFontSize) {
1058 if (size < 12)
1059 size += 1;
1060 else if (size < 24)
1061 size += 2;
1062 else
1063 size += 4;
1064 } else {
1065 if (size <= 12)
1066 size -= 1;
1067 else if (size <= 24)
1068 size -= 2;
1069 else
1070 size -= 4;
1073 // constrain the font size
1074 if (size < kMinimumFontSize)
1075 size = kMinimumFontSize;
1076 if (size > kMaximumFontSize)
1077 size = kMaximumFontSize;
1079 // mark the font size menu item
1080 for (int32 i = 0; i < fFontSizeMenu->CountItems(); i++) {
1081 BMenuItem* item = fFontSizeMenu->ItemAt(i);
1082 if (item == NULL)
1083 continue;
1085 item->SetMarked(false);
1086 if (atoi(item->Label()) == size)
1087 item->SetMarked(true);
1090 font.SetSize(size);
1091 PrefHandler::Default()->setInt32(PREF_HALF_FONT_SIZE, (int32)size);
1092 for (int32 i = 0; i < fTabView->CountTabs(); i++) {
1093 TermView* view = _TermViewAt(i);
1094 _TermViewAt(i)->SetTermFont(&font);
1095 _ResizeView(view);
1097 break;
1100 case kUpdateTitles:
1101 _UpdateTitles();
1102 break;
1104 case kEditTabTitle:
1106 SessionID sessionID(*message, "session");
1107 if (Session* session = _SessionForID(sessionID))
1108 _OpenSetTabTitleDialog(_IndexOfSession(session));
1109 break;
1112 case kEditWindowTitle:
1113 _OpenSetWindowTitleDialog();
1114 break;
1116 case kUpdateSwitchTerminalsMenuItem:
1117 _UpdateSwitchTerminalsMenuItem();
1118 break;
1120 default:
1121 BWindow::MessageReceived(message);
1122 break;
1127 void
1128 TermWindow::WindowActivated(bool activated)
1130 if (activated)
1131 _UpdateSwitchTerminalsMenuItem();
1135 void
1136 TermWindow::_SetTermColors(TermViewContainerView* containerView)
1138 PrefHandler* handler = PrefHandler::Default();
1139 rgb_color background = handler->getRGB(PREF_TEXT_BACK_COLOR);
1141 containerView->SetViewColor(background);
1143 TermView *termView = containerView->GetTermView();
1144 termView->SetTextColor(handler->getRGB(PREF_TEXT_FORE_COLOR), background);
1146 termView->SetCursorColor(handler->getRGB(PREF_CURSOR_FORE_COLOR),
1147 handler->getRGB(PREF_CURSOR_BACK_COLOR));
1148 termView->SetSelectColor(handler->getRGB(PREF_SELECT_FORE_COLOR),
1149 handler->getRGB(PREF_SELECT_BACK_COLOR));
1153 status_t
1154 TermWindow::_DoPageSetup()
1156 BPrintJob job("PageSetup");
1158 // display the page configure panel
1159 status_t status = job.ConfigPage();
1161 // save a pointer to the settings
1162 fPrintSettings = job.Settings();
1164 return status;
1168 void
1169 TermWindow::_DoPrint()
1171 BPrintJob job("Print");
1172 if (fPrintSettings)
1173 job.SetSettings(new BMessage(*fPrintSettings));
1175 if (job.ConfigJob() != B_OK)
1176 return;
1178 BRect pageRect = job.PrintableRect();
1179 BRect curPageRect = pageRect;
1181 int pHeight = (int)pageRect.Height();
1182 int pWidth = (int)pageRect.Width();
1183 float w, h;
1184 _ActiveTermView()->GetFrameSize(&w, &h);
1185 int xPages = (int)ceil(w / pWidth);
1186 int yPages = (int)ceil(h / pHeight);
1188 job.BeginJob();
1190 // loop through and draw each page, and write to spool
1191 for (int x = 0; x < xPages; x++) {
1192 for (int y = 0; y < yPages; y++) {
1193 curPageRect.OffsetTo(x * pWidth, y * pHeight);
1194 job.DrawView(_ActiveTermView(), curPageRect, B_ORIGIN);
1195 job.SpoolPage();
1197 if (!job.CanContinue()) {
1198 // It is likely that the only way that the job was cancelled is
1199 // because the user hit 'Cancel' in the page setup window, in
1200 // which case, the user does *not* need to be told that it was
1201 // cancelled.
1202 // He/she will simply expect that it was done.
1203 return;
1208 job.CommitJob();
1212 void
1213 TermWindow::_NewTab()
1215 ActiveProcessInfo info;
1216 if (_ActiveTermView()->GetActiveProcessInfo(info))
1217 _AddTab(NULL, info.CurrentDirectory());
1218 else
1219 _AddTab(NULL);
1223 void
1224 TermWindow::_AddTab(Arguments* args, const BString& currentDirectory)
1226 int argc = 0;
1227 const char* const* argv = NULL;
1228 if (args != NULL)
1229 args->GetShellArguments(argc, argv);
1230 ShellParameters shellParameters(argc, argv, currentDirectory);
1232 try {
1233 TermView* view = new TermView(
1234 PrefHandler::Default()->getInt32(PREF_ROWS),
1235 PrefHandler::Default()->getInt32(PREF_COLS),
1236 shellParameters,
1237 PrefHandler::Default()->getInt32(PREF_HISTORY_SIZE));
1238 view->SetListener(this);
1240 TermViewContainerView* containerView = new TermViewContainerView(view);
1241 BScrollView* scrollView = new TermScrollView("scrollView",
1242 containerView, view, fSessions.IsEmpty());
1243 if (!fFullScreen)
1244 scrollView->ScrollBar(B_VERTICAL)
1245 ->ResizeBy(0, -(B_H_SCROLL_BAR_HEIGHT - 1));
1247 if (fSessions.IsEmpty())
1248 fTabView->SetScrollView(scrollView);
1250 Session* session = new Session(_NewSessionID(), _NewSessionIndex(),
1251 containerView);
1252 fSessions.AddItem(session);
1254 BFont font;
1255 _GetPreferredFont(font);
1256 view->SetTermFont(&font);
1258 int width, height;
1259 view->GetFontSize(&width, &height);
1261 float minimumHeight = -1;
1262 if (fMenuBar != NULL)
1263 minimumHeight += fMenuBar->Bounds().Height() + 1;
1265 if (fTabView != NULL && fTabView->CountTabs() > 0)
1266 minimumHeight += fTabView->TabHeight() + 1;
1268 SetSizeLimits(MIN_COLS * width - 1, MAX_COLS * width - 1,
1269 minimumHeight + MIN_ROWS * height - 1,
1270 minimumHeight + MAX_ROWS * height - 1);
1271 // TODO: The size limit computation is apparently broken, since
1272 // the terminal can be resized smaller than MIN_ROWS/MIN_COLS!
1274 // If it's the first time we're called, setup the window
1275 if (fTabView != NULL && fTabView->CountTabs() == 0) {
1276 float viewWidth, viewHeight;
1277 containerView->GetPreferredSize(&viewWidth, &viewHeight);
1279 // Resize Window
1280 ResizeTo(viewWidth + B_V_SCROLL_BAR_WIDTH,
1281 viewHeight + fMenuBar->Bounds().Height() + 1);
1282 // NOTE: Width is one pixel too small, since the scroll view
1283 // is one pixel wider than its parent.
1286 BTab* tab = new BTab;
1287 fTabView->AddTab(scrollView, tab);
1288 view->SetScrollBar(scrollView->ScrollBar(B_VERTICAL));
1289 view->SetMouseClipboard(gMouseClipboard);
1291 const BCharacterSet* charset
1292 = BCharacterSetRoster::FindCharacterSetByName(
1293 PrefHandler::Default()->getString(PREF_TEXT_ENCODING));
1294 if (charset != NULL)
1295 view->SetEncoding(charset->GetConversionID());
1297 _SetTermColors(containerView);
1299 int32 tabIndex = fTabView->CountTabs() - 1;
1300 fTabView->Select(tabIndex);
1302 _UpdateSessionTitle(tabIndex);
1303 } catch (...) {
1304 // most probably out of memory. That's bad.
1305 // TODO: Should cleanup, I guess
1307 // Quit the application if we don't have a shell already
1308 if (fTabView->CountTabs() == 0) {
1309 fprintf(stderr, "Terminal couldn't open a shell\n");
1310 PostMessage(B_QUIT_REQUESTED);
1316 void
1317 TermWindow::_RemoveTab(int32 index)
1319 _FinishTitleDialog();
1320 // always close to avoid confusion
1322 if (fSessions.CountItems() > 1) {
1323 if (!_CanClose(index))
1324 return;
1325 if (Session* session = (Session*)fSessions.RemoveItem(index)) {
1326 if (fSessions.CountItems() == 1) {
1327 fTabView->SetScrollView(dynamic_cast<BScrollView*>(
1328 _SessionAt(0)->containerView->Parent()));
1331 delete session;
1332 delete fTabView->RemoveTab(index);
1334 } else
1335 PostMessage(B_QUIT_REQUESTED);
1339 void
1340 TermWindow::_NavigateTab(int32 index, int32 direction, bool move)
1342 int32 count = fSessions.CountItems();
1343 if (count <= 1 || index < 0 || index >= count)
1344 return;
1346 int32 newIndex = (index + direction + count) % count;
1347 if (newIndex == index)
1348 return;
1350 if (move) {
1351 // move the given tab to the new index
1352 Session* session = (Session*)fSessions.RemoveItem(index);
1353 fSessions.AddItem(session, newIndex);
1354 fTabView->MoveTab(index, newIndex);
1357 // activate the respective tab
1358 fTabView->Select(newIndex);
1362 TermViewContainerView*
1363 TermWindow::_ActiveTermViewContainerView() const
1365 return _TermViewContainerViewAt(fTabView->Selection());
1369 TermViewContainerView*
1370 TermWindow::_TermViewContainerViewAt(int32 index) const
1372 if (Session* session = _SessionAt(index))
1373 return session->containerView;
1374 return NULL;
1378 TermView*
1379 TermWindow::_ActiveTermView() const
1381 return _ActiveTermViewContainerView()->GetTermView();
1385 TermView*
1386 TermWindow::_TermViewAt(int32 index) const
1388 TermViewContainerView* view = _TermViewContainerViewAt(index);
1389 return view != NULL ? view->GetTermView() : NULL;
1393 int32
1394 TermWindow::_IndexOfTermView(TermView* termView) const
1396 if (!termView)
1397 return -1;
1399 // find the view
1400 int32 count = fTabView->CountTabs();
1401 for (int32 i = count - 1; i >= 0; i--) {
1402 if (termView == _TermViewAt(i))
1403 return i;
1406 return -1;
1410 TermWindow::Session*
1411 TermWindow::_SessionAt(int32 index) const
1413 return (Session*)fSessions.ItemAt(index);
1417 TermWindow::Session*
1418 TermWindow::_SessionForID(const SessionID& sessionID) const
1420 for (int32 i = 0; Session* session = _SessionAt(i); i++) {
1421 if (session->id == sessionID)
1422 return session;
1425 return NULL;
1429 int32
1430 TermWindow::_IndexOfSession(Session* session) const
1432 return fSessions.IndexOf(session);
1436 void
1437 TermWindow::_CheckChildren()
1439 int32 count = fSessions.CountItems();
1440 for (int32 i = count - 1; i >= 0; i--) {
1441 Session* session = _SessionAt(i);
1442 if (session->containerView->GetTermView()->CheckShellGone())
1443 NotifyTermViewQuit(session->containerView->GetTermView(), 0);
1448 void
1449 TermWindow::Zoom(BPoint leftTop, float width, float height)
1451 _ActiveTermView()->DisableResizeView();
1452 BWindow::Zoom(leftTop, width, height);
1456 void
1457 TermWindow::FrameResized(float newWidth, float newHeight)
1459 BWindow::FrameResized(newWidth, newHeight);
1461 TermView* view = _ActiveTermView();
1462 PrefHandler::Default()->setInt32(PREF_COLS, view->Columns());
1463 PrefHandler::Default()->setInt32(PREF_ROWS, view->Rows());
1467 void
1468 TermWindow::WorkspacesChanged(uint32 oldWorkspaces, uint32 newWorkspaces)
1470 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces());
1474 void
1475 TermWindow::WorkspaceActivated(int32 workspace, bool state)
1477 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces());
1481 void
1482 TermWindow::Minimize(bool minimize)
1484 BWindow::Minimize(minimize);
1485 fTerminalRoster.SetWindowInfo(IsMinimized(), Workspaces());
1489 void
1490 TermWindow::TabSelected(SmartTabView* tabView, int32 index)
1492 SessionChanged();
1496 void
1497 TermWindow::TabDoubleClicked(SmartTabView* tabView, BPoint point, int32 index)
1499 if (index >= 0) {
1500 // clicked on a tab -- open the title dialog
1501 _OpenSetTabTitleDialog(index);
1502 } else {
1503 // not clicked on a tab -- create a new one
1504 _NewTab();
1509 void
1510 TermWindow::TabMiddleClicked(SmartTabView* tabView, BPoint point, int32 index)
1512 if (index >= 0)
1513 _RemoveTab(index);
1517 void
1518 TermWindow::TabRightClicked(SmartTabView* tabView, BPoint point, int32 index)
1520 if (index < 0)
1521 return;
1523 TermView* termView = _TermViewAt(index);
1524 if (termView == NULL)
1525 return;
1527 BMessage* closeMessage = new BMessage(kCloseView);
1528 _SessionAt(index)->id.AddToMessage(*closeMessage, "session");
1530 BMessage* closeOthersMessage = new BMessage(kCloseOtherViews);
1531 _SessionAt(index)->id.AddToMessage(*closeOthersMessage, "session");
1533 BMessage* editTitleMessage = new BMessage(kEditTabTitle);
1534 _SessionAt(index)->id.AddToMessage(*editTitleMessage, "session");
1536 BPopUpMenu* popUpMenu = new BPopUpMenu("tab menu");
1537 BLayoutBuilder::Menu<>(popUpMenu)
1538 .AddItem(B_TRANSLATE("Close tab"), closeMessage)
1539 .AddItem(B_TRANSLATE("Close other tabs"), closeOthersMessage)
1540 .AddSeparator()
1541 .AddItem(B_TRANSLATE("Edit tab title" B_UTF8_ELLIPSIS),
1542 editTitleMessage)
1545 popUpMenu->SetAsyncAutoDestruct(true);
1546 popUpMenu->SetTargetForItems(BMessenger(this));
1548 BPoint screenWhere = tabView->ConvertToScreen(point);
1549 BRect mouseRect(screenWhere, screenWhere);
1550 mouseRect.InsetBy(-4.0, -4.0);
1551 popUpMenu->Go(screenWhere, true, true, mouseRect, true);
1555 void
1556 TermWindow::NotifyTermViewQuit(TermView* view, int32 reason)
1558 // Since the notification can come from the view, we send a message to
1559 // ourselves to avoid deleting the caller synchronously.
1560 if (Session* session = _SessionAt(_IndexOfTermView(view))) {
1561 BMessage message(kCloseView);
1562 session->id.AddToMessage(message, "session");
1563 message.AddInt32("reason", reason);
1564 PostMessage(&message);
1569 void
1570 TermWindow::SetTermViewTitle(TermView* view, const char* title)
1572 int32 index = _IndexOfTermView(view);
1573 if (Session* session = _SessionAt(index)) {
1574 session->title.pattern = title;
1575 session->title.patternUserDefined = true;
1576 _UpdateSessionTitle(index);
1581 void
1582 TermWindow::TitleChanged(SetTitleDialog* dialog, const BString& title,
1583 bool titleUserDefined)
1585 if (dialog == fSetTabTitleDialog) {
1586 // tab title
1587 BMessage message(kTabTitleChanged);
1588 fSetTabTitleSession.AddToMessage(message, "session");
1589 if (titleUserDefined)
1590 message.AddString("title", title);
1592 PostMessage(&message);
1593 } else if (dialog == fSetWindowTitleDialog) {
1594 // window title
1595 BMessage message(kWindowTitleChanged);
1596 if (titleUserDefined)
1597 message.AddString("title", title);
1599 PostMessage(&message);
1604 void
1605 TermWindow::SetTitleDialogDone(SetTitleDialog* dialog)
1607 if (dialog == fSetTabTitleDialog) {
1608 fSetTabTitleSession = SessionID();
1609 fSetTabTitleDialog = NULL;
1610 // assuming this is atomic
1615 void
1616 TermWindow::TerminalInfosUpdated(TerminalRoster* roster)
1618 PostMessage(kUpdateSwitchTerminalsMenuItem);
1622 void
1623 TermWindow::PreviousTermView(TermView* view)
1625 _NavigateTab(_IndexOfTermView(view), -1, false);
1629 void
1630 TermWindow::NextTermView(TermView* view)
1632 _NavigateTab(_IndexOfTermView(view), 1, false);
1636 void
1637 TermWindow::_ResizeView(TermView *view)
1639 int fontWidth, fontHeight;
1640 view->GetFontSize(&fontWidth, &fontHeight);
1642 float minimumHeight = -1;
1643 if (fMenuBar != NULL)
1644 minimumHeight += fMenuBar->Bounds().Height() + 1;
1646 if (fTabView != NULL && fTabView->CountTabs() > 1)
1647 minimumHeight += fTabView->TabHeight() + 1;
1649 SetSizeLimits(MIN_COLS * fontWidth - 1, MAX_COLS * fontWidth - 1,
1650 minimumHeight + MIN_ROWS * fontHeight - 1,
1651 minimumHeight + MAX_ROWS * fontHeight - 1);
1653 float width;
1654 float height;
1655 view->Parent()->GetPreferredSize(&width, &height);
1657 width += B_V_SCROLL_BAR_WIDTH;
1658 // NOTE: Width is one pixel too small, since the scroll view
1659 // is one pixel wider than its parent.
1660 if (fMenuBar != NULL)
1661 height += fMenuBar->Bounds().Height() + 1;
1662 if (fTabView != NULL && fTabView->CountTabs() > 1)
1663 height += fTabView->TabHeight() + 1;
1665 ResizeTo(width, height);
1666 view->Invalidate();
1670 /* static */ void
1671 TermWindow::MakeWindowSizeMenu(BMenu* menu)
1673 const int32 windowSizes[4][2] = {
1674 { 80, 25 },
1675 { 80, 40 },
1676 { 132, 25 },
1677 { 132, 40 }
1680 const int32 sizeNum = sizeof(windowSizes) / sizeof(windowSizes[0]);
1681 for (int32 i = 0; i < sizeNum; i++) {
1682 char label[32];
1683 int32 columns = windowSizes[i][0];
1684 int32 rows = windowSizes[i][1];
1685 snprintf(label, sizeof(label), "%" B_PRId32 "x%" B_PRId32, columns,
1686 rows);
1687 BMessage* message = new BMessage(MSG_COLS_CHANGED);
1688 message->AddInt32("columns", columns);
1689 message->AddInt32("rows", rows);
1690 menu->AddItem(new BMenuItem(label, message));
1695 /*static*/ BMenu*
1696 TermWindow::_MakeFontSizeMenu(uint32 command, uint8 defaultSize)
1698 BMenu* menu = new (std::nothrow) BMenu(B_TRANSLATE("Font size"));
1699 if (menu == NULL)
1700 return NULL;
1702 int32 sizes[] = {
1703 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, 0
1706 bool found = false;
1708 for (uint32 i = 0; sizes[i]; i++) {
1709 BString string;
1710 string << sizes[i];
1711 BMessage* message = new BMessage(command);
1712 message->AddString("font_size", string);
1713 BMenuItem* item = new BMenuItem(string.String(), message);
1714 menu->AddItem(item);
1715 if (sizes[i] == defaultSize) {
1716 item->SetMarked(true);
1717 found = true;
1721 if (!found) {
1722 for (uint32 i = 0; sizes[i]; i++) {
1723 if (sizes[i] > defaultSize) {
1724 BString string;
1725 string << defaultSize;
1726 BMessage* message = new BMessage(command);
1727 message->AddString("font_size", string);
1728 BMenuItem* item = new BMenuItem(string.String(), message);
1729 item->SetMarked(true);
1730 menu->AddItem(item, i);
1731 break;
1736 return menu;
1740 void
1741 TermWindow::_UpdateSwitchTerminalsMenuItem()
1743 fSwitchTerminalsMenuItem->SetEnabled(_FindSwitchTerminalTarget() >= 0);
1747 void
1748 TermWindow::_TitleSettingsChanged()
1750 if (!fTitle.patternUserDefined)
1751 fTitle.pattern = PrefHandler::Default()->getString(PREF_WINDOW_TITLE);
1753 fSessionTitlePattern = PrefHandler::Default()->getString(PREF_TAB_TITLE);
1755 _UpdateTitles();
1759 void
1760 TermWindow::_UpdateTitles()
1762 int32 sessionCount = fSessions.CountItems();
1763 for (int32 i = 0; i < sessionCount; i++)
1764 _UpdateSessionTitle(i);
1768 void
1769 TermWindow::_UpdateSessionTitle(int32 index)
1771 Session* session = _SessionAt(index);
1772 if (session == NULL)
1773 return;
1775 // get the shell and active process infos
1776 ShellInfo shellInfo;
1777 ActiveProcessInfo activeProcessInfo;
1778 TermView* termView = _TermViewAt(index);
1779 if (!termView->GetShellInfo(shellInfo)
1780 || !termView->GetActiveProcessInfo(activeProcessInfo)) {
1781 return;
1784 // evaluate the session title pattern
1785 BString sessionTitlePattern = session->title.patternUserDefined
1786 ? session->title.pattern : fSessionTitlePattern;
1787 TabTitlePlaceholderMapper tabMapper(shellInfo, activeProcessInfo,
1788 session->index);
1789 const BString& sessionTitle = PatternEvaluator::Evaluate(
1790 sessionTitlePattern, tabMapper);
1792 // set the tab title
1793 if (sessionTitle != session->title.title) {
1794 session->title.title = sessionTitle;
1795 fTabView->TabAt(index)->SetLabel(session->title.title);
1796 fTabView->Invalidate();
1797 // Invalidate the complete tab view, since other tabs might change
1798 // their positions.
1801 // If this is the active tab, also recompute the window title.
1802 if (index != fTabView->Selection())
1803 return;
1805 // evaluate the window title pattern
1806 WindowTitlePlaceholderMapper windowMapper(shellInfo, activeProcessInfo,
1807 fTerminalRoster.CountTerminals() > 1
1808 ? fTerminalRoster.ID() + 1 : 0, sessionTitle);
1809 const BString& windowTitle = PatternEvaluator::Evaluate(fTitle.pattern,
1810 windowMapper);
1812 // set the window title
1813 if (windowTitle != fTitle.title) {
1814 fTitle.title = windowTitle;
1815 SetTitle(fTitle.title);
1818 // If fullscreen, add a tooltip with the title and a keyboard shortcut hint
1819 if (fFullScreen) {
1820 BString toolTip(fTitle.title);
1821 toolTip += "\n(";
1822 toolTip += B_TRANSLATE("Full screen");
1823 toolTip += " (ALT " UTF8_ENTER "))";
1824 termView->SetToolTip(toolTip.String());
1825 } else
1826 termView->SetToolTip((const char *)NULL);
1830 void
1831 TermWindow::_OpenSetTabTitleDialog(int32 index)
1833 // If a dialog is active, finish it.
1834 _FinishTitleDialog();
1836 BString toolTip = BString(B_TRANSLATE(
1837 "The pattern specifying the current tab title. The following "
1838 "placeholders\n"
1839 "can be used:\n")) << kTooTipSetTabTitlePlaceholders;
1840 fSetTabTitleDialog = new SetTitleDialog(
1841 B_TRANSLATE("Set tab title"), B_TRANSLATE("Tab title:"),
1842 toolTip);
1844 Session* session = _SessionAt(index);
1845 bool userDefined = session->title.patternUserDefined;
1846 const BString& title = userDefined
1847 ? session->title.pattern : fSessionTitlePattern;
1848 fSetTabTitleSession = session->id;
1850 // place the dialog window directly under the tab, but keep it on screen
1851 BPoint location = fTabView->ConvertToScreen(
1852 fTabView->TabFrame(index).LeftBottom() + BPoint(0, 1));
1853 fSetTabTitleDialog->MoveTo(location);
1854 _MoveWindowInScreen(fSetTabTitleDialog);
1856 fSetTabTitleDialog->Go(title, userDefined, this);
1860 void
1861 TermWindow::_OpenSetWindowTitleDialog()
1863 // If a dialog is active, finish it.
1864 _FinishTitleDialog();
1866 BString toolTip = BString(B_TRANSLATE(
1867 "The pattern specifying the window title. The following placeholders\n"
1868 "can be used:\n")) << kTooTipSetTabTitlePlaceholders;
1869 fSetWindowTitleDialog = new SetTitleDialog(B_TRANSLATE("Set window title"),
1870 B_TRANSLATE("Window title:"), toolTip);
1872 // center the dialog in the window frame, but keep it on screen
1873 fSetWindowTitleDialog->CenterIn(Frame());
1874 _MoveWindowInScreen(fSetWindowTitleDialog);
1876 fSetWindowTitleDialog->Go(fTitle.pattern, fTitle.patternUserDefined, this);
1880 void
1881 TermWindow::_FinishTitleDialog()
1883 SetTitleDialog* oldDialog = fSetTabTitleDialog;
1884 if (oldDialog != NULL && oldDialog->Lock()) {
1885 // might have been unset in the meantime, so recheck
1886 if (fSetTabTitleDialog == oldDialog) {
1887 oldDialog->Finish();
1888 // this also unsets the variables
1890 oldDialog->Unlock();
1891 return;
1894 oldDialog = fSetWindowTitleDialog;
1895 if (oldDialog != NULL && oldDialog->Lock()) {
1896 // might have been unset in the meantime, so recheck
1897 if (fSetWindowTitleDialog == oldDialog) {
1898 oldDialog->Finish();
1899 // this also unsets the variable
1901 oldDialog->Unlock();
1902 return;
1907 void
1908 TermWindow::_SwitchTerminal()
1910 team_id teamID = _FindSwitchTerminalTarget();
1911 if (teamID < 0)
1912 return;
1914 BMessenger app(TERM_SIGNATURE, teamID);
1915 app.SendMessage(MSG_ACTIVATE_TERM);
1919 team_id
1920 TermWindow::_FindSwitchTerminalTarget()
1922 AutoLocker<TerminalRoster> rosterLocker(fTerminalRoster);
1924 team_id myTeamID = Team();
1926 int32 numTerms = fTerminalRoster.CountTerminals();
1927 if (numTerms <= 1)
1928 return -1;
1930 // Find our position in the Terminal teams.
1931 int32 i;
1933 for (i = 0; i < numTerms; i++) {
1934 if (myTeamID == fTerminalRoster.TerminalAt(i)->team)
1935 break;
1938 if (i == numTerms) {
1939 // we didn't find ourselves -- that shouldn't happen
1940 return -1;
1943 uint32 currentWorkspace = 1L << current_workspace();
1945 while (true) {
1946 if (--i < 0)
1947 i = numTerms - 1;
1949 const TerminalRoster::Info* info = fTerminalRoster.TerminalAt(i);
1950 if (info->team == myTeamID) {
1951 // That's ourselves again. We've run through the complete list.
1952 return -1;
1955 if (!info->minimized && (info->workspaces & currentWorkspace) != 0)
1956 return info->team;
1961 TermWindow::SessionID
1962 TermWindow::_NewSessionID()
1964 return fNextSessionID++;
1968 int32
1969 TermWindow::_NewSessionIndex()
1971 for (int32 id = 1; ; id++) {
1972 bool used = false;
1974 for (int32 i = 0;
1975 Session* session = _SessionAt(i); i++) {
1976 if (id == session->index) {
1977 used = true;
1978 break;
1982 if (!used)
1983 return id;
1988 void
1989 TermWindow::_MoveWindowInScreen(BWindow* window)
1991 BRect frame = window->Frame();
1992 BSize screenSize(BScreen(window).Frame().Size());
1993 window->MoveTo(BLayoutUtils::MoveIntoFrame(frame, screenSize).LeftTop());