2 * Copyright 2001-2015, Haiku, Inc.
3 * Copyright 2003-2004 Kian Duffy, myob@users.sourceforge.net
4 * Parts Copyright 1998-1999 Kazuho Okui and Takashi Murai.
5 * All rights reserved. Distributed under the terms of the MIT license.
8 * Stefano Ceccherini, stefano.ceccherini@gmail.com
9 * Kian Duffy, myob@users.sourceforge.net
10 * Y.Hayakawa, hida@sawada.riec.tohoku.ac.jp
11 * Ingo Weinhold, ingo_weinhold@gmx.de
12 * Clemens Zeidler, haiku@Clemens-Zeidler.de
13 * Siarzhuk Zharski, zharik@gmx.li
17 #include "TermViewStates.h"
24 #include <Clipboard.h>
26 #include <FindDirectory.h>
27 #include <LayoutBuilder.h>
28 #include <MessageRunner.h>
30 #include <PopUpMenu.h>
31 #include <ScrollBar.h>
37 #include "ActiveProcessInfo.h"
39 #include "TermConst.h"
40 #include "TerminalBuffer.h"
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "Terminal TermView"
49 // selection granularity
56 static const uint32 kAutoScroll
= 'AScr';
58 static const uint32 kMessageOpenLink
= 'OLnk';
59 static const uint32 kMessageCopyLink
= 'CLnk';
60 static const uint32 kMessageCopyAbsolutePath
= 'CAbs';
61 static const uint32 kMessageMenuClosed
= 'MClo';
64 static const char* const kKnownURLProtocols
= "http:https:ftp:mailto";
67 // #pragma mark - State
70 TermView::State::State(TermView
* view
)
77 TermView::State::~State()
83 TermView::State::Entered()
89 TermView::State::Exited()
95 TermView::State::MessageReceived(BMessage
* message
)
102 TermView::State::ModifiersChanged(int32 oldModifiers
, int32 modifiers
)
108 TermView::State::KeyDown(const char* bytes
, int32 numBytes
)
114 TermView::State::MouseDown(BPoint where
, int32 buttons
, int32 modifiers
)
120 TermView::State::MouseMoved(BPoint where
, uint32 transit
,
121 const BMessage
* message
, int32 modifiers
)
127 TermView::State::MouseUp(BPoint where
, int32 buttons
)
133 TermView::State::WindowActivated(bool active
)
139 TermView::State::VisibleTextBufferChanged()
144 // #pragma mark - StandardBaseState
147 TermView::StandardBaseState::StandardBaseState(TermView
* view
)
155 TermView::StandardBaseState::_StandardMouseMoved(BPoint where
, int32 modifiers
)
157 if (!fView
->fReportAnyMouseEvent
&& !fView
->fReportButtonMouseEvent
)
160 TermPos clickPos
= fView
->_ConvertToTerminal(where
);
162 if (fView
->fReportButtonMouseEvent
) {
163 if (fView
->fPrevPos
.x
!= clickPos
.x
164 || fView
->fPrevPos
.y
!= clickPos
.y
) {
165 fView
->_SendMouseEvent(fView
->fMouseButtons
, modifiers
,
166 clickPos
.x
, clickPos
.y
, true);
168 fView
->fPrevPos
= clickPos
;
170 fView
->_SendMouseEvent(fView
->fMouseButtons
, modifiers
, clickPos
.x
,
178 // #pragma mark - DefaultState
181 TermView::DefaultState::DefaultState(TermView
* view
)
183 StandardBaseState(view
)
189 TermView::DefaultState::ModifiersChanged(int32 oldModifiers
, int32 modifiers
)
191 _CheckEnterHyperLinkState(modifiers
);
196 TermView::DefaultState::KeyDown(const char* bytes
, int32 numBytes
)
201 BMessage
* currentMessage
= fView
->Looper()->CurrentMessage();
202 if (currentMessage
== NULL
)
205 currentMessage
->FindInt32("modifiers", &mod
);
206 currentMessage
->FindInt32("key", &key
);
207 currentMessage
->FindInt32("raw_char", &rawChar
);
209 fView
->_ActivateCursor(true);
211 // handle multi-byte chars
213 if (fView
->fEncoding
!= M_UTF8
) {
215 int32 destLen
= sizeof(destBuffer
);
217 convert_from_utf8(fView
->fEncoding
, bytes
, &numBytes
, destBuffer
,
218 &destLen
, &state
, '?');
219 fView
->_ScrollTo(0, true);
220 fView
->fShell
->Write(destBuffer
, destLen
);
224 fView
->_ScrollTo(0, true);
225 fView
->fShell
->Write(bytes
, numBytes
);
229 // Terminal filters RET, ENTER, F1...F12, and ARROW key code.
230 const char *toWrite
= NULL
;
234 if (rawChar
== B_RETURN
)
239 toWrite
= DELETE_KEY_CODE
;
243 // Translate only the actual backspace key to the backspace
244 // code. CTRL-H shall just be echoed.
245 if (!((mod
& B_CONTROL_KEY
) && rawChar
== 'h'))
246 toWrite
= BACKSPACE_KEY_CODE
;
250 if (rawChar
== B_LEFT_ARROW
) {
251 if ((mod
& B_SHIFT_KEY
) != 0) {
252 if (fView
->fListener
!= NULL
)
253 fView
->fListener
->PreviousTermView(fView
);
256 if ((mod
& B_CONTROL_KEY
) || (mod
& B_COMMAND_KEY
))
257 toWrite
= CTRL_LEFT_ARROW_KEY_CODE
;
259 toWrite
= LEFT_ARROW_KEY_CODE
;
264 if (rawChar
== B_RIGHT_ARROW
) {
265 if ((mod
& B_SHIFT_KEY
) != 0) {
266 if (fView
->fListener
!= NULL
)
267 fView
->fListener
->NextTermView(fView
);
270 if ((mod
& B_CONTROL_KEY
) || (mod
& B_COMMAND_KEY
))
271 toWrite
= CTRL_RIGHT_ARROW_KEY_CODE
;
273 toWrite
= RIGHT_ARROW_KEY_CODE
;
278 if (mod
& B_SHIFT_KEY
) {
279 fView
->_ScrollTo(fView
->fScrollOffset
- fView
->fFontHeight
,
283 if (rawChar
== B_UP_ARROW
) {
284 if (mod
& B_CONTROL_KEY
)
285 toWrite
= CTRL_UP_ARROW_KEY_CODE
;
287 toWrite
= UP_ARROW_KEY_CODE
;
292 if (mod
& B_SHIFT_KEY
) {
293 fView
->_ScrollTo(fView
->fScrollOffset
+ fView
->fFontHeight
,
298 if (rawChar
== B_DOWN_ARROW
) {
299 if (mod
& B_CONTROL_KEY
)
300 toWrite
= CTRL_DOWN_ARROW_KEY_CODE
;
302 toWrite
= DOWN_ARROW_KEY_CODE
;
307 if (rawChar
== B_INSERT
)
308 toWrite
= INSERT_KEY_CODE
;
312 if (rawChar
== B_HOME
)
313 toWrite
= HOME_KEY_CODE
;
317 if (rawChar
== B_END
)
318 toWrite
= END_KEY_CODE
;
322 if (mod
& B_SHIFT_KEY
) {
324 fView
->fScrollOffset
- fView
->fFontHeight
* fView
->fRows
,
328 if (rawChar
== B_PAGE_UP
)
329 toWrite
= PAGE_UP_KEY_CODE
;
333 if (mod
& B_SHIFT_KEY
) {
335 fView
->fScrollOffset
+ fView
->fFontHeight
* fView
->fRows
,
339 if (rawChar
== B_PAGE_DOWN
)
340 toWrite
= PAGE_DOWN_KEY_CODE
;
344 for (int32 i
= 0; i
< 12; i
++) {
345 if (key
== function_keycode_table
[i
]) {
346 toWrite
= function_key_char_table
[i
];
353 // If the above code proposed an alternative string to write, we get it's
354 // length. Otherwise we write exactly the bytes passed to this method.
356 if (toWrite
!= NULL
) {
357 toWriteLen
= strlen(toWrite
);
360 toWriteLen
= numBytes
;
363 fView
->_ScrollTo(0, true);
364 fView
->fShell
->Write(toWrite
, toWriteLen
);
369 TermView::DefaultState::MouseDown(BPoint where
, int32 buttons
, int32 modifiers
)
371 if (fView
->fReportAnyMouseEvent
|| fView
->fReportButtonMouseEvent
372 || fView
->fReportNormalMouseEvent
|| fView
->fReportX10MouseEvent
) {
373 TermPos clickPos
= fView
->_ConvertToTerminal(where
);
374 fView
->_SendMouseEvent(buttons
, modifiers
, clickPos
.x
, clickPos
.y
,
380 if ((buttons
& (B_SECONDARY_MOUSE_BUTTON
| B_TERTIARY_MOUSE_BUTTON
)) != 0) {
381 fView
->Paste(fView
->fMouseClipboard
);
386 if (buttons
== B_PRIMARY_MOUSE_BUTTON
) {
387 fView
->fSelectState
->Prepare(where
, modifiers
);
388 fView
->_NextState(fView
->fSelectState
);
394 TermView::DefaultState::MouseMoved(BPoint where
, uint32 transit
,
395 const BMessage
* dragMessage
, int32 modifiers
)
397 if (_CheckEnterHyperLinkState(modifiers
))
400 _StandardMouseMoved(where
, modifiers
);
405 TermView::DefaultState::WindowActivated(bool active
)
408 _CheckEnterHyperLinkState(fView
->fModifiers
);
413 TermView::DefaultState::_CheckEnterHyperLinkState(int32 modifiers
)
415 if ((modifiers
& B_COMMAND_KEY
) != 0 && fView
->Window()->IsActive()) {
416 fView
->_NextState(fView
->fHyperLinkState
);
424 // #pragma mark - SelectState
427 TermView::SelectState::SelectState(TermView
* view
)
429 StandardBaseState(view
),
430 fSelectGranularity(SELECT_CHARS
),
431 fCheckMouseTracking(false),
432 fMouseTracking(false)
438 TermView::SelectState::Prepare(BPoint where
, int32 modifiers
)
441 fView
->Window()->CurrentMessage()->FindInt32("clicks", &clicks
);
443 if (fView
->_HasSelection()) {
444 TermPos inPos
= fView
->_ConvertToTerminal(where
);
445 if (fView
->fSelection
.RangeContains(inPos
)) {
446 if (modifiers
& B_CONTROL_KEY
) {
450 fView
->GetMouse(&p
, &bt
);
459 } while (abs((int)(where
.x
- p
.x
)) < 4
460 && abs((int)(where
.y
- p
.y
)) < 4);
462 fView
->InitiateDrag();
468 // If mouse has moved too much, disable double/triple click.
469 if (fView
->_MouseDistanceSinceLastClick(where
) > 8)
472 fView
->SetMouseEventMask(B_POINTER_EVENTS
| B_KEYBOARD_EVENTS
,
473 B_NO_POINTER_HISTORY
| B_LOCK_WINDOW_FOCUS
);
475 TermPos clickPos
= fView
->_ConvertToTerminal(where
);
477 if (modifiers
& B_SHIFT_KEY
) {
478 fView
->fInitialSelectionStart
= clickPos
;
479 fView
->fInitialSelectionEnd
= clickPos
;
480 fView
->_ExtendSelection(fView
->fInitialSelectionStart
, true, false);
483 fView
->fInitialSelectionStart
= clickPos
;
484 fView
->fInitialSelectionEnd
= clickPos
;
487 // If clicks larger than 3, reset mouse click counter.
488 clicks
= (clicks
- 1) % 3 + 1;
492 fCheckMouseTracking
= true;
493 fSelectGranularity
= SELECT_CHARS
;
497 fView
->_SelectWord(where
, (modifiers
& B_SHIFT_KEY
) != 0, false);
498 fMouseTracking
= true;
499 fSelectGranularity
= SELECT_WORDS
;
503 fView
->_SelectLine(where
, (modifiers
& B_SHIFT_KEY
) != 0, false);
504 fMouseTracking
= true;
505 fSelectGranularity
= SELECT_LINES
;
512 TermView::SelectState::MessageReceived(BMessage
* message
)
514 if (message
->what
== kAutoScroll
) {
524 TermView::SelectState::MouseMoved(BPoint where
, uint32 transit
,
525 const BMessage
* message
, int32 modifiers
)
527 if (_StandardMouseMoved(where
, modifiers
))
530 if (fCheckMouseTracking
) {
531 if (fView
->_MouseDistanceSinceLastClick(where
) > 9)
532 fMouseTracking
= true;
537 bool doAutoScroll
= false;
541 fView
->fAutoScrollSpeed
= where
.y
;
546 BRect
bounds(fView
->Bounds());
547 if (where
.y
> bounds
.bottom
) {
549 fView
->fAutoScrollSpeed
= where
.y
- bounds
.bottom
;
550 where
.x
= bounds
.right
;
551 where
.y
= bounds
.bottom
;
555 if (fView
->fAutoScrollRunner
== NULL
) {
556 BMessage
message(kAutoScroll
);
557 fView
->fAutoScrollRunner
= new (std::nothrow
) BMessageRunner(
558 BMessenger(fView
), &message
, 10000);
561 delete fView
->fAutoScrollRunner
;
562 fView
->fAutoScrollRunner
= NULL
;
565 switch (fSelectGranularity
) {
568 // If we just start selecting, we first select the initially
569 // hit char, so that we get a proper initial selection -- the char
570 // in question, which will thus always be selected, regardless of
571 // whether selecting forward or backward.
572 if (fView
->fInitialSelectionStart
== fView
->fInitialSelectionEnd
) {
573 fView
->_Select(fView
->fInitialSelectionStart
,
574 fView
->fInitialSelectionEnd
, true, true);
577 fView
->_ExtendSelection(fView
->_ConvertToTerminal(where
), true,
582 fView
->_SelectWord(where
, true, true);
585 fView
->_SelectLine(where
, true, true);
592 TermView::SelectState::MouseUp(BPoint where
, int32 buttons
)
594 fCheckMouseTracking
= false;
595 fMouseTracking
= false;
597 if (fView
->fAutoScrollRunner
!= NULL
) {
598 delete fView
->fAutoScrollRunner
;
599 fView
->fAutoScrollRunner
= NULL
;
602 // When releasing the first mouse button, we copy the selected text to the
605 if (fView
->fReportAnyMouseEvent
|| fView
->fReportButtonMouseEvent
606 || fView
->fReportNormalMouseEvent
) {
607 TermPos clickPos
= fView
->_ConvertToTerminal(where
);
608 fView
->_SendMouseEvent(0, 0, clickPos
.x
, clickPos
.y
, false);
609 } else if ((buttons
& B_PRIMARY_MOUSE_BUTTON
) == 0
610 && (fView
->fMouseButtons
& B_PRIMARY_MOUSE_BUTTON
) != 0) {
611 fView
->Copy(fView
->fMouseClipboard
);
614 fView
->_NextState(fView
->fDefaultState
);
619 TermView::SelectState::_AutoScrollUpdate()
621 if (fMouseTracking
&& fView
->fAutoScrollRunner
!= NULL
622 && fView
->fScrollBar
!= NULL
) {
623 float value
= fView
->fScrollBar
->Value();
624 fView
->_ScrollTo(value
+ fView
->fAutoScrollSpeed
, true);
625 if (fView
->fAutoScrollSpeed
< 0) {
626 fView
->_ExtendSelection(
627 fView
->_ConvertToTerminal(BPoint(0, 0)), true, true);
629 fView
->_ExtendSelection(
630 fView
->_ConvertToTerminal(fView
->Bounds().RightBottom()), true,
637 // #pragma mark - HyperLinkState
640 TermView::HyperLinkState::HyperLinkState(TermView
* view
)
643 fURLCharClassifier(kURLAdditionalWordCharacters
),
644 fPathComponentCharClassifier(
645 BString(kDefaultAdditionalWordCharacters
).RemoveFirst("/")),
648 fHighlightActive(false)
650 fHighlight
.SetHighlighter(this);
655 TermView::HyperLinkState::Entered()
657 ActiveProcessInfo activeProcessInfo
;
658 if (fView
->GetActiveProcessInfo(activeProcessInfo
))
659 fCurrentDirectory
= activeProcessInfo
.CurrentDirectory();
661 fCurrentDirectory
.Truncate(0);
668 TermView::HyperLinkState::Exited()
670 _DeactivateHighlight();
675 TermView::HyperLinkState::ModifiersChanged(int32 oldModifiers
, int32 modifiers
)
677 if ((modifiers
& B_COMMAND_KEY
) == 0)
678 fView
->_NextState(fView
->fDefaultState
);
685 TermView::HyperLinkState::MouseDown(BPoint where
, int32 buttons
,
692 bool pathPrefixOnly
= (modifiers
& B_SHIFT_KEY
) != 0;
693 if (!_GetHyperLinkAt(where
, pathPrefixOnly
, link
, start
, end
))
696 if ((buttons
& B_PRIMARY_MOUSE_BUTTON
) != 0) {
698 } else if ((buttons
& B_SECONDARY_MOUSE_BUTTON
) != 0) {
699 fView
->fHyperLinkMenuState
->Prepare(where
, link
);
700 fView
->_NextState(fView
->fHyperLinkMenuState
);
706 TermView::HyperLinkState::MouseMoved(BPoint where
, uint32 transit
,
707 const BMessage
* message
, int32 modifiers
)
709 _UpdateHighlight(where
, modifiers
);
714 TermView::HyperLinkState::WindowActivated(bool active
)
717 fView
->_NextState(fView
->fDefaultState
);
722 TermView::HyperLinkState::VisibleTextBufferChanged()
729 TermView::HyperLinkState::ForegroundColor()
731 return make_color(0, 0, 255);
736 TermView::HyperLinkState::BackgroundColor()
738 return fView
->fTextBackColor
;
743 TermView::HyperLinkState::AdjustTextAttributes(uint32 attributes
)
745 return attributes
| UNDERLINE
;
750 TermView::HyperLinkState::_GetHyperLinkAt(BPoint where
, bool pathPrefixOnly
,
751 HyperLink
& _link
, TermPos
& _start
, TermPos
& _end
)
753 TerminalBuffer
* textBuffer
= fView
->fTextBuffer
;
754 BAutolock
textBufferLocker(textBuffer
);
756 TermPos pos
= fView
->_ConvertToTerminal(where
);
758 // try to get a URL first
760 if (!textBuffer
->FindWord(pos
, &fURLCharClassifier
, false, _start
, _end
))
764 textBuffer
->GetStringFromRegion(text
, _start
, _end
);
767 // We're only happy, if it has a protocol part which we know.
768 int32 colonIndex
= text
.FindFirst(':');
769 if (colonIndex
>= 0) {
770 BString
protocol(text
, colonIndex
);
771 if (strstr(kKnownURLProtocols
, protocol
) != NULL
) {
772 _link
= HyperLink(text
, HyperLink::TYPE_URL
);
777 // no obvious URL -- try file name
778 if (!textBuffer
->FindWord(pos
, fView
->fCharClassifier
, false, _start
, _end
))
781 // In path-prefix-only mode we determine the end position anew by omitting
782 // the '/' in the allowed word chars.
783 if (pathPrefixOnly
) {
784 TermPos componentStart
;
785 TermPos componentEnd
;
786 if (textBuffer
->FindWord(pos
, &fPathComponentCharClassifier
, false,
787 componentStart
, componentEnd
)) {
790 // That means pos points to a '/'. We simply use the previous
793 if (_start
== _end
) {
794 // Well, must be just "/". Advance to the next position.
795 if (!textBuffer
->NextLinePos(_end
, false))
802 textBuffer
->GetStringFromRegion(text
, _start
, _end
);
807 // Collect a list of colons in the string and their respective positions in
808 // the text buffer. We do this up-front so we can unlock the text buffer
809 // while we're doing all the entry existence tests.
810 typedef Array
<CharPosition
> ColonList
;
811 ColonList colonPositions
;
812 TermPos searchPos
= _start
;
813 for (int32 index
= 0; (index
= text
.FindFirst(':', index
)) >= 0;) {
816 if (!textBuffer
->Find(":", searchPos
, true, true, false, foundStart
,
821 CharPosition colonPosition
;
822 colonPosition
.index
= index
;
823 colonPosition
.position
= foundStart
;
824 if (!colonPositions
.Add(colonPosition
))
828 searchPos
= foundEnd
;
831 textBufferLocker
.Unlock();
833 // Since we also want to consider ':' a potential path delimiter, in two
834 // nested loops we chop off components from the beginning respective the
836 BString originalText
= text
;
837 TermPos originalStart
= _start
;
838 TermPos originalEnd
= _end
;
840 int32 colonCount
= colonPositions
.Count();
841 for (int32 startColonIndex
= -1; startColonIndex
< colonCount
;
844 if (startColonIndex
< 0) {
846 _start
= originalStart
;
848 startIndex
= colonPositions
[startColonIndex
].index
+ 1;
849 _start
= colonPositions
[startColonIndex
].position
;
853 // Note: This is potentially a non-normalized position (i.e.
854 // the end of a soft-wrapped line). While not that nice, it
858 for (int32 endColonIndex
= colonCount
; endColonIndex
> startColonIndex
;
861 if (endColonIndex
== colonCount
) {
862 endIndex
= originalText
.Length();
865 endIndex
= colonPositions
[endColonIndex
].index
;
866 _end
= colonPositions
[endColonIndex
].position
;
871 originalText
.CopyInto(text
, startIndex
, endIndex
- startIndex
);
875 // check, whether the file exists
877 if (_EntryExists(text
, actualPath
)) {
878 _link
= HyperLink(text
, actualPath
, HyperLink::TYPE_PATH
);
882 // As such this isn't an existing path. We also want to recognize:
884 // * "<path>:<line>:<column>"
888 for (int32 i
= 0; i
< 2; i
++) {
889 int32 colonIndex
= path
.FindLast(':');
890 if (colonIndex
<= 0 || colonIndex
== path
.Length() - 1)
894 strtol(path
.String() + colonIndex
+ 1, &numberEnd
, 0);
895 if (*numberEnd
!= '\0')
898 path
.Truncate(colonIndex
);
899 if (_EntryExists(path
, actualPath
)) {
900 BString address
= path
== actualPath
902 : BString(actualPath
) << (text
.String() + colonIndex
);
903 _link
= HyperLink(text
, address
,
905 ? HyperLink::TYPE_PATH_WITH_LINE
906 : HyperLink::TYPE_PATH_WITH_LINE_AND_COLUMN
);
918 TermView::HyperLinkState::_EntryExists(const BString
& path
,
919 BString
& _actualPath
) const
924 if (path
[0] == '/' || fCurrentDirectory
.IsEmpty()) {
926 } else if (path
== "~" || path
.StartsWith("~/")) {
927 // Replace '~' with the user's home directory. We don't handle "~user"
930 if (find_directory(B_USER_DIRECTORY
, &homeDirectory
) != B_OK
)
932 _actualPath
= homeDirectory
.Path();
933 _actualPath
<< path
.String() + 1;
935 _actualPath
.Truncate(0);
936 _actualPath
<< fCurrentDirectory
<< '/' << path
;
940 return lstat(_actualPath
, &st
) == 0;
945 TermView::HyperLinkState::_UpdateHighlight()
949 fView
->GetMouse(&where
, &buttons
, false);
950 _UpdateHighlight(where
, fView
->fModifiers
);
955 TermView::HyperLinkState::_UpdateHighlight(BPoint where
, int32 modifiers
)
961 bool pathPrefixOnly
= (modifiers
& B_SHIFT_KEY
) != 0;
962 if (_GetHyperLinkAt(where
, pathPrefixOnly
, link
, start
, end
))
963 _ActivateHighlight(start
, end
);
965 _DeactivateHighlight();
970 TermView::HyperLinkState::_ActivateHighlight(const TermPos
& start
,
973 if (fHighlightActive
) {
974 if (fHighlight
.Start() == start
&& fHighlight
.End() == end
)
977 _DeactivateHighlight();
980 fHighlight
.SetRange(start
, end
);
981 fView
->_AddHighlight(&fHighlight
);
982 BCursor
cursor(B_CURSOR_ID_FOLLOW_LINK
);
983 fView
->SetViewCursor(&cursor
);
984 fHighlightActive
= true;
989 TermView::HyperLinkState::_DeactivateHighlight()
991 if (fHighlightActive
) {
992 fView
->_RemoveHighlight(&fHighlight
);
993 BCursor
cursor(B_CURSOR_ID_SYSTEM_DEFAULT
);
994 fView
->SetViewCursor(&cursor
);
995 fHighlightActive
= false;
1000 // #pragma mark - HyperLinkMenuState
1003 class TermView::HyperLinkMenuState::PopUpMenu
: public BPopUpMenu
{
1005 PopUpMenu(const BMessenger
& messageTarget
)
1007 BPopUpMenu("open hyperlink"),
1008 fMessageTarget(messageTarget
)
1010 SetAsyncAutoDestruct(true);
1015 fMessageTarget
.SendMessage(kMessageMenuClosed
);
1019 BMessenger fMessageTarget
;
1023 TermView::HyperLinkMenuState::HyperLinkMenuState(TermView
* view
)
1032 TermView::HyperLinkMenuState::Prepare(BPoint point
, const HyperLink
& link
)
1036 // open context menu
1037 PopUpMenu
* menu
= new PopUpMenu(fView
);
1038 BLayoutBuilder::Menu
<> menuBuilder(menu
);
1039 switch (link
.GetType()) {
1040 case HyperLink::TYPE_URL
:
1042 .AddItem(B_TRANSLATE("Open link"), kMessageOpenLink
)
1043 .AddItem(B_TRANSLATE("Copy link location"), kMessageCopyLink
);
1046 case HyperLink::TYPE_PATH
:
1047 case HyperLink::TYPE_PATH_WITH_LINE
:
1048 case HyperLink::TYPE_PATH_WITH_LINE_AND_COLUMN
:
1049 menuBuilder
.AddItem(B_TRANSLATE("Open path"), kMessageOpenLink
);
1050 menuBuilder
.AddItem(B_TRANSLATE("Copy path"), kMessageCopyLink
);
1051 if (fLink
.Text() != fLink
.Address()) {
1052 menuBuilder
.AddItem(B_TRANSLATE("Copy absolute path"),
1053 kMessageCopyAbsolutePath
);
1057 menu
->SetTargetForItems(fView
);
1058 menu
->Go(fView
->ConvertToScreen(point
), true, true, true);
1063 TermView::HyperLinkMenuState::Exited()
1065 fLink
= HyperLink();
1070 TermView::HyperLinkMenuState::MessageReceived(BMessage
* message
)
1072 switch (message
->what
) {
1073 case kMessageOpenLink
:
1074 if (fLink
.IsValid())
1078 case kMessageCopyLink
:
1079 case kMessageCopyAbsolutePath
:
1081 if (fLink
.IsValid()) {
1082 BString toCopy
= message
->what
== kMessageCopyLink
1083 ? fLink
.Text() : fLink
.Address();
1085 if (!be_clipboard
->Lock())
1088 be_clipboard
->Clear();
1090 if (BMessage
*data
= be_clipboard
->Data()) {
1091 data
->AddData("text/plain", B_MIME_TYPE
, toCopy
.String(),
1093 be_clipboard
->Commit();
1096 be_clipboard
->Unlock();
1101 case kMessageMenuClosed
:
1102 fView
->_NextState(fView
->fDefaultState
);