HaikuDepot: notify work status from main window
[haiku.git] / src / apps / terminal / TermViewStates.cpp
blobe24fa8dc91af490ad993702b6c75f43e2a6d73c3
1 /*
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.
7 * Authors:
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"
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/stat.h>
23 #include <Catalog.h>
24 #include <Clipboard.h>
25 #include <Cursor.h>
26 #include <FindDirectory.h>
27 #include <LayoutBuilder.h>
28 #include <MessageRunner.h>
29 #include <Path.h>
30 #include <PopUpMenu.h>
31 #include <ScrollBar.h>
32 #include <UTF8.h>
33 #include <Window.h>
35 #include <Array.h>
37 #include "ActiveProcessInfo.h"
38 #include "Shell.h"
39 #include "TermConst.h"
40 #include "TerminalBuffer.h"
41 #include "VTkeymap.h"
42 #include "VTKeyTbl.h"
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "Terminal TermView"
49 // selection granularity
50 enum {
51 SELECT_CHARS,
52 SELECT_WORDS,
53 SELECT_LINES
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)
72 fView(view)
77 TermView::State::~State()
82 void
83 TermView::State::Entered()
88 void
89 TermView::State::Exited()
94 bool
95 TermView::State::MessageReceived(BMessage* message)
97 return false;
101 void
102 TermView::State::ModifiersChanged(int32 oldModifiers, int32 modifiers)
107 void
108 TermView::State::KeyDown(const char* bytes, int32 numBytes)
113 void
114 TermView::State::MouseDown(BPoint where, int32 buttons, int32 modifiers)
119 void
120 TermView::State::MouseMoved(BPoint where, uint32 transit,
121 const BMessage* message, int32 modifiers)
126 void
127 TermView::State::MouseUp(BPoint where, int32 buttons)
132 void
133 TermView::State::WindowActivated(bool active)
138 void
139 TermView::State::VisibleTextBufferChanged()
144 // #pragma mark - StandardBaseState
147 TermView::StandardBaseState::StandardBaseState(TermView* view)
149 State(view)
154 bool
155 TermView::StandardBaseState::_StandardMouseMoved(BPoint where, int32 modifiers)
157 if (!fView->fReportAnyMouseEvent && !fView->fReportButtonMouseEvent)
158 return false;
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;
169 } else {
170 fView->_SendMouseEvent(fView->fMouseButtons, modifiers, clickPos.x,
171 clickPos.y, true);
174 return true;
178 // #pragma mark - DefaultState
181 TermView::DefaultState::DefaultState(TermView* view)
183 StandardBaseState(view)
188 void
189 TermView::DefaultState::ModifiersChanged(int32 oldModifiers, int32 modifiers)
191 _CheckEnterHyperLinkState(modifiers);
195 void
196 TermView::DefaultState::KeyDown(const char* bytes, int32 numBytes)
198 int32 key;
199 int32 mod;
200 int32 rawChar;
201 BMessage* currentMessage = fView->Looper()->CurrentMessage();
202 if (currentMessage == NULL)
203 return;
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
212 if (numBytes > 1) {
213 if (fView->fEncoding != M_UTF8) {
214 char destBuffer[16];
215 int32 destLen = sizeof(destBuffer);
216 int32 state = 0;
217 convert_from_utf8(fView->fEncoding, bytes, &numBytes, destBuffer,
218 &destLen, &state, '?');
219 fView->_ScrollTo(0, true);
220 fView->fShell->Write(destBuffer, destLen);
221 return;
224 fView->_ScrollTo(0, true);
225 fView->fShell->Write(bytes, numBytes);
226 return;
229 // Terminal filters RET, ENTER, F1...F12, and ARROW key code.
230 const char *toWrite = NULL;
232 switch (*bytes) {
233 case B_RETURN:
234 if (rawChar == B_RETURN)
235 toWrite = "\r";
236 break;
238 case B_DELETE:
239 toWrite = DELETE_KEY_CODE;
240 break;
242 case B_BACKSPACE:
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;
247 break;
249 case B_LEFT_ARROW:
250 if (rawChar == B_LEFT_ARROW) {
251 if ((mod & B_SHIFT_KEY) != 0) {
252 if (fView->fListener != NULL)
253 fView->fListener->PreviousTermView(fView);
254 return;
256 if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY))
257 toWrite = CTRL_LEFT_ARROW_KEY_CODE;
258 else
259 toWrite = LEFT_ARROW_KEY_CODE;
261 break;
263 case B_RIGHT_ARROW:
264 if (rawChar == B_RIGHT_ARROW) {
265 if ((mod & B_SHIFT_KEY) != 0) {
266 if (fView->fListener != NULL)
267 fView->fListener->NextTermView(fView);
268 return;
270 if ((mod & B_CONTROL_KEY) || (mod & B_COMMAND_KEY))
271 toWrite = CTRL_RIGHT_ARROW_KEY_CODE;
272 else
273 toWrite = RIGHT_ARROW_KEY_CODE;
275 break;
277 case B_UP_ARROW:
278 if (mod & B_SHIFT_KEY) {
279 fView->_ScrollTo(fView->fScrollOffset - fView->fFontHeight,
280 true);
281 return;
283 if (rawChar == B_UP_ARROW) {
284 if (mod & B_CONTROL_KEY)
285 toWrite = CTRL_UP_ARROW_KEY_CODE;
286 else
287 toWrite = UP_ARROW_KEY_CODE;
289 break;
291 case B_DOWN_ARROW:
292 if (mod & B_SHIFT_KEY) {
293 fView->_ScrollTo(fView->fScrollOffset + fView->fFontHeight,
294 true);
295 return;
298 if (rawChar == B_DOWN_ARROW) {
299 if (mod & B_CONTROL_KEY)
300 toWrite = CTRL_DOWN_ARROW_KEY_CODE;
301 else
302 toWrite = DOWN_ARROW_KEY_CODE;
304 break;
306 case B_INSERT:
307 if (rawChar == B_INSERT)
308 toWrite = INSERT_KEY_CODE;
309 break;
311 case B_HOME:
312 if (rawChar == B_HOME)
313 toWrite = HOME_KEY_CODE;
314 break;
316 case B_END:
317 if (rawChar == B_END)
318 toWrite = END_KEY_CODE;
319 break;
321 case B_PAGE_UP:
322 if (mod & B_SHIFT_KEY) {
323 fView->_ScrollTo(
324 fView->fScrollOffset - fView->fFontHeight * fView->fRows,
325 true);
326 return;
328 if (rawChar == B_PAGE_UP)
329 toWrite = PAGE_UP_KEY_CODE;
330 break;
332 case B_PAGE_DOWN:
333 if (mod & B_SHIFT_KEY) {
334 fView->_ScrollTo(
335 fView->fScrollOffset + fView->fFontHeight * fView->fRows,
336 true);
337 return;
339 if (rawChar == B_PAGE_DOWN)
340 toWrite = PAGE_DOWN_KEY_CODE;
341 break;
343 case B_FUNCTION_KEY:
344 for (int32 i = 0; i < 12; i++) {
345 if (key == function_keycode_table[i]) {
346 toWrite = function_key_char_table[i];
347 break;
350 break;
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.
355 size_t toWriteLen;
356 if (toWrite != NULL) {
357 toWriteLen = strlen(toWrite);
358 } else {
359 toWrite = bytes;
360 toWriteLen = numBytes;
363 fView->_ScrollTo(0, true);
364 fView->fShell->Write(toWrite, toWriteLen);
368 void
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,
375 false);
376 return;
379 // paste button
380 if ((buttons & (B_SECONDARY_MOUSE_BUTTON | B_TERTIARY_MOUSE_BUTTON)) != 0) {
381 fView->Paste(fView->fMouseClipboard);
382 return;
385 // select region
386 if (buttons == B_PRIMARY_MOUSE_BUTTON) {
387 fView->fSelectState->Prepare(where, modifiers);
388 fView->_NextState(fView->fSelectState);
393 void
394 TermView::DefaultState::MouseMoved(BPoint where, uint32 transit,
395 const BMessage* dragMessage, int32 modifiers)
397 if (_CheckEnterHyperLinkState(modifiers))
398 return;
400 _StandardMouseMoved(where, modifiers);
404 void
405 TermView::DefaultState::WindowActivated(bool active)
407 if (active)
408 _CheckEnterHyperLinkState(fView->fModifiers);
412 bool
413 TermView::DefaultState::_CheckEnterHyperLinkState(int32 modifiers)
415 if ((modifiers & B_COMMAND_KEY) != 0 && fView->Window()->IsActive()) {
416 fView->_NextState(fView->fHyperLinkState);
417 return true;
420 return false;
424 // #pragma mark - SelectState
427 TermView::SelectState::SelectState(TermView* view)
429 StandardBaseState(view),
430 fSelectGranularity(SELECT_CHARS),
431 fCheckMouseTracking(false),
432 fMouseTracking(false)
437 void
438 TermView::SelectState::Prepare(BPoint where, int32 modifiers)
440 int32 clicks;
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) {
447 BPoint p;
448 uint32 bt;
449 do {
450 fView->GetMouse(&p, &bt);
452 if (bt == 0) {
453 fView->_Deselect();
454 return;
457 snooze(40000);
459 } while (abs((int)(where.x - p.x)) < 4
460 && abs((int)(where.y - p.y)) < 4);
462 fView->InitiateDrag();
463 return;
468 // If mouse has moved too much, disable double/triple click.
469 if (fView->_MouseDistanceSinceLastClick(where) > 8)
470 clicks = 1;
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);
481 } else {
482 fView->_Deselect();
483 fView->fInitialSelectionStart = clickPos;
484 fView->fInitialSelectionEnd = clickPos;
487 // If clicks larger than 3, reset mouse click counter.
488 clicks = (clicks - 1) % 3 + 1;
490 switch (clicks) {
491 case 1:
492 fCheckMouseTracking = true;
493 fSelectGranularity = SELECT_CHARS;
494 break;
496 case 2:
497 fView->_SelectWord(where, (modifiers & B_SHIFT_KEY) != 0, false);
498 fMouseTracking = true;
499 fSelectGranularity = SELECT_WORDS;
500 break;
502 case 3:
503 fView->_SelectLine(where, (modifiers & B_SHIFT_KEY) != 0, false);
504 fMouseTracking = true;
505 fSelectGranularity = SELECT_LINES;
506 break;
511 bool
512 TermView::SelectState::MessageReceived(BMessage* message)
514 if (message->what == kAutoScroll) {
515 _AutoScrollUpdate();
516 return true;
519 return false;
523 void
524 TermView::SelectState::MouseMoved(BPoint where, uint32 transit,
525 const BMessage* message, int32 modifiers)
527 if (_StandardMouseMoved(where, modifiers))
528 return;
530 if (fCheckMouseTracking) {
531 if (fView->_MouseDistanceSinceLastClick(where) > 9)
532 fMouseTracking = true;
534 if (!fMouseTracking)
535 return;
537 bool doAutoScroll = false;
539 if (where.y < 0) {
540 doAutoScroll = true;
541 fView->fAutoScrollSpeed = where.y;
542 where.x = 0;
543 where.y = 0;
546 BRect bounds(fView->Bounds());
547 if (where.y > bounds.bottom) {
548 doAutoScroll = true;
549 fView->fAutoScrollSpeed = where.y - bounds.bottom;
550 where.x = bounds.right;
551 where.y = bounds.bottom;
554 if (doAutoScroll) {
555 if (fView->fAutoScrollRunner == NULL) {
556 BMessage message(kAutoScroll);
557 fView->fAutoScrollRunner = new (std::nothrow) BMessageRunner(
558 BMessenger(fView), &message, 10000);
560 } else {
561 delete fView->fAutoScrollRunner;
562 fView->fAutoScrollRunner = NULL;
565 switch (fSelectGranularity) {
566 case SELECT_CHARS:
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,
578 true);
579 break;
581 case SELECT_WORDS:
582 fView->_SelectWord(where, true, true);
583 break;
584 case SELECT_LINES:
585 fView->_SelectLine(where, true, true);
586 break;
591 void
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
603 // clipboard.
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);
618 void
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);
628 } else {
629 fView->_ExtendSelection(
630 fView->_ConvertToTerminal(fView->Bounds().RightBottom()), true,
631 true);
637 // #pragma mark - HyperLinkState
640 TermView::HyperLinkState::HyperLinkState(TermView* view)
642 State(view),
643 fURLCharClassifier(kURLAdditionalWordCharacters),
644 fPathComponentCharClassifier(
645 BString(kDefaultAdditionalWordCharacters).RemoveFirst("/")),
646 fCurrentDirectory(),
647 fHighlight(),
648 fHighlightActive(false)
650 fHighlight.SetHighlighter(this);
654 void
655 TermView::HyperLinkState::Entered()
657 ActiveProcessInfo activeProcessInfo;
658 if (fView->GetActiveProcessInfo(activeProcessInfo))
659 fCurrentDirectory = activeProcessInfo.CurrentDirectory();
660 else
661 fCurrentDirectory.Truncate(0);
663 _UpdateHighlight();
667 void
668 TermView::HyperLinkState::Exited()
670 _DeactivateHighlight();
674 void
675 TermView::HyperLinkState::ModifiersChanged(int32 oldModifiers, int32 modifiers)
677 if ((modifiers & B_COMMAND_KEY) == 0)
678 fView->_NextState(fView->fDefaultState);
679 else
680 _UpdateHighlight();
684 void
685 TermView::HyperLinkState::MouseDown(BPoint where, int32 buttons,
686 int32 modifiers)
688 TermPos start;
689 TermPos end;
690 HyperLink link;
692 bool pathPrefixOnly = (modifiers & B_SHIFT_KEY) != 0;
693 if (!_GetHyperLinkAt(where, pathPrefixOnly, link, start, end))
694 return;
696 if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
697 link.Open();
698 } else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
699 fView->fHyperLinkMenuState->Prepare(where, link);
700 fView->_NextState(fView->fHyperLinkMenuState);
705 void
706 TermView::HyperLinkState::MouseMoved(BPoint where, uint32 transit,
707 const BMessage* message, int32 modifiers)
709 _UpdateHighlight(where, modifiers);
713 void
714 TermView::HyperLinkState::WindowActivated(bool active)
716 if (!active)
717 fView->_NextState(fView->fDefaultState);
721 void
722 TermView::HyperLinkState::VisibleTextBufferChanged()
724 _UpdateHighlight();
728 rgb_color
729 TermView::HyperLinkState::ForegroundColor()
731 return make_color(0, 0, 255);
735 rgb_color
736 TermView::HyperLinkState::BackgroundColor()
738 return fView->fTextBackColor;
742 uint32
743 TermView::HyperLinkState::AdjustTextAttributes(uint32 attributes)
745 return attributes | UNDERLINE;
749 bool
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
759 BString text;
760 if (!textBuffer->FindWord(pos, &fURLCharClassifier, false, _start, _end))
761 return false;
763 text.Truncate(0);
764 textBuffer->GetStringFromRegion(text, _start, _end);
765 text.Trim();
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);
773 return true;
777 // no obvious URL -- try file name
778 if (!textBuffer->FindWord(pos, fView->fCharClassifier, false, _start, _end))
779 return false;
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)) {
788 _end = componentEnd;
789 } else {
790 // That means pos points to a '/'. We simply use the previous
791 // position.
792 _end = pos;
793 if (_start == _end) {
794 // Well, must be just "/". Advance to the next position.
795 if (!textBuffer->NextLinePos(_end, false))
796 return false;
801 text.Truncate(0);
802 textBuffer->GetStringFromRegion(text, _start, _end);
803 text.Trim();
804 if (text.IsEmpty())
805 return false;
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;) {
814 TermPos foundStart;
815 TermPos foundEnd;
816 if (!textBuffer->Find(":", searchPos, true, true, false, foundStart,
817 foundEnd)) {
818 return false;
821 CharPosition colonPosition;
822 colonPosition.index = index;
823 colonPosition.position = foundStart;
824 if (!colonPositions.Add(colonPosition))
825 return false;
827 index++;
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
835 // end.
836 BString originalText = text;
837 TermPos originalStart = _start;
838 TermPos originalEnd = _end;
840 int32 colonCount = colonPositions.Count();
841 for (int32 startColonIndex = -1; startColonIndex < colonCount;
842 startColonIndex++) {
843 int32 startIndex;
844 if (startColonIndex < 0) {
845 startIndex = 0;
846 _start = originalStart;
847 } else {
848 startIndex = colonPositions[startColonIndex].index + 1;
849 _start = colonPositions[startColonIndex].position;
850 if (_start >= pos)
851 break;
852 _start.x++;
853 // Note: This is potentially a non-normalized position (i.e.
854 // the end of a soft-wrapped line). While not that nice, it
855 // works anyway.
858 for (int32 endColonIndex = colonCount; endColonIndex > startColonIndex;
859 endColonIndex--) {
860 int32 endIndex;
861 if (endColonIndex == colonCount) {
862 endIndex = originalText.Length();
863 _end = originalEnd;
864 } else {
865 endIndex = colonPositions[endColonIndex].index;
866 _end = colonPositions[endColonIndex].position;
867 if (_end <= pos)
868 break;
871 originalText.CopyInto(text, startIndex, endIndex - startIndex);
872 if (text.IsEmpty())
873 continue;
875 // check, whether the file exists
876 BString actualPath;
877 if (_EntryExists(text, actualPath)) {
878 _link = HyperLink(text, actualPath, HyperLink::TYPE_PATH);
879 return true;
882 // As such this isn't an existing path. We also want to recognize:
883 // * "<path>:<line>"
884 // * "<path>:<line>:<column>"
886 BString path = text;
888 for (int32 i = 0; i < 2; i++) {
889 int32 colonIndex = path.FindLast(':');
890 if (colonIndex <= 0 || colonIndex == path.Length() - 1)
891 break;
893 char* numberEnd;
894 strtol(path.String() + colonIndex + 1, &numberEnd, 0);
895 if (*numberEnd != '\0')
896 break;
898 path.Truncate(colonIndex);
899 if (_EntryExists(path, actualPath)) {
900 BString address = path == actualPath
901 ? text
902 : BString(actualPath) << (text.String() + colonIndex);
903 _link = HyperLink(text, address,
904 i == 0
905 ? HyperLink::TYPE_PATH_WITH_LINE
906 : HyperLink::TYPE_PATH_WITH_LINE_AND_COLUMN);
907 return true;
913 return false;
917 bool
918 TermView::HyperLinkState::_EntryExists(const BString& path,
919 BString& _actualPath) const
921 if (path.IsEmpty())
922 return false;
924 if (path[0] == '/' || fCurrentDirectory.IsEmpty()) {
925 _actualPath = path;
926 } else if (path == "~" || path.StartsWith("~/")) {
927 // Replace '~' with the user's home directory. We don't handle "~user"
928 // here yet.
929 BPath homeDirectory;
930 if (find_directory(B_USER_DIRECTORY, &homeDirectory) != B_OK)
931 return false;
932 _actualPath = homeDirectory.Path();
933 _actualPath << path.String() + 1;
934 } else {
935 _actualPath.Truncate(0);
936 _actualPath << fCurrentDirectory << '/' << path;
939 struct stat st;
940 return lstat(_actualPath, &st) == 0;
944 void
945 TermView::HyperLinkState::_UpdateHighlight()
947 BPoint where;
948 uint32 buttons;
949 fView->GetMouse(&where, &buttons, false);
950 _UpdateHighlight(where, fView->fModifiers);
954 void
955 TermView::HyperLinkState::_UpdateHighlight(BPoint where, int32 modifiers)
957 TermPos start;
958 TermPos end;
959 HyperLink link;
961 bool pathPrefixOnly = (modifiers & B_SHIFT_KEY) != 0;
962 if (_GetHyperLinkAt(where, pathPrefixOnly, link, start, end))
963 _ActivateHighlight(start, end);
964 else
965 _DeactivateHighlight();
969 void
970 TermView::HyperLinkState::_ActivateHighlight(const TermPos& start,
971 const TermPos& end)
973 if (fHighlightActive) {
974 if (fHighlight.Start() == start && fHighlight.End() == end)
975 return;
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;
988 void
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 {
1004 public:
1005 PopUpMenu(const BMessenger& messageTarget)
1007 BPopUpMenu("open hyperlink"),
1008 fMessageTarget(messageTarget)
1010 SetAsyncAutoDestruct(true);
1013 ~PopUpMenu()
1015 fMessageTarget.SendMessage(kMessageMenuClosed);
1018 private:
1019 BMessenger fMessageTarget;
1023 TermView::HyperLinkMenuState::HyperLinkMenuState(TermView* view)
1025 State(view),
1026 fLink()
1031 void
1032 TermView::HyperLinkMenuState::Prepare(BPoint point, const HyperLink& link)
1034 fLink = 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:
1041 menuBuilder
1042 .AddItem(B_TRANSLATE("Open link"), kMessageOpenLink)
1043 .AddItem(B_TRANSLATE("Copy link location"), kMessageCopyLink);
1044 break;
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);
1055 break;
1057 menu->SetTargetForItems(fView);
1058 menu->Go(fView->ConvertToScreen(point), true, true, true);
1062 void
1063 TermView::HyperLinkMenuState::Exited()
1065 fLink = HyperLink();
1069 bool
1070 TermView::HyperLinkMenuState::MessageReceived(BMessage* message)
1072 switch (message->what) {
1073 case kMessageOpenLink:
1074 if (fLink.IsValid())
1075 fLink.Open();
1076 return true;
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())
1086 return true;
1088 be_clipboard->Clear();
1090 if (BMessage *data = be_clipboard->Data()) {
1091 data->AddData("text/plain", B_MIME_TYPE, toCopy.String(),
1092 toCopy.Length());
1093 be_clipboard->Commit();
1096 be_clipboard->Unlock();
1098 return true;
1101 case kMessageMenuClosed:
1102 fView->_NextState(fView->fDefaultState);
1103 return true;
1106 return false;