Terminal: Add a hyperlink mode

When holding down Command, text under the mouse is checked whether it
looks like a URL or a local path. If so, it is highlighted and can be
clicked, which will open the URL/file. Right-clicking opens a context
menu with items for opening the link/file or copying it to the
clipboard. When additionally holding down Shift, path prefixes up to
the component under the mouse will be considered (no effect for URLs).

Changes:
* Add HyperLink class. Encapsulates a type, the address, and an
  optional base address. Features an Open() method to open the address.
* Move/add some string constants to TermConst.
* Move TermView::CharClassifier to top level and rename to
  DefaultCharClassifier.
* Introduce TermViewHighlight and TermViewHighlighter. The former
  refers to a range of text in a TermView's text buffer. It also
  contains a pointer to a TermViewHighlighter object, which specifies
  how the text range shall be rendered (colors and attributes).
* TermView:
  - Add respective _{Add,Remove}Highlight() methods and adjust the code
    to support highlights.
  - Make the selection a TermViewHighlight. At least its visual aspect
    is now handled like other highlights.
  - Introduce an inner TextBufferSyncLocker. It is used instead of
    BAutolock when locking the text buffer to synchronize the visual
    buffer with it. After it unlocks it calls
    _VisibleTextBufferChanged(), if the visual text buffer has changed,
    which in turn calls a new callback on the active state.
  - Add WindowActivated() and ModifiersChanged() callbacks to the state
    interface.
  - Add new states HyperLinkState and HyperLinkMenuState which
    implement the new feature.

Fix modifier issues
This commit is contained in:
Ingo Weinhold
2013-05-11 04:44:25 +02:00
parent 314e8a20c6
commit e9bad28aaf
13 changed files with 1067 additions and 152 deletions
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright 2013, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#include "HyperLink.h"
#include <errno.h>
#include <stdlib.h>
#include "TermConst.h"
HyperLink::HyperLink()
:
fAddress(),
fType(TYPE_URL)
{
}
HyperLink::HyperLink(const BString& address, Type type,
const BString& baseAddress)
:
fAddress(address),
fBaseAddress(baseAddress.IsEmpty() ? address : baseAddress),
fType(type)
{
}
status_t
HyperLink::Open()
{
if (!IsValid())
return B_BAD_VALUE;
// open with the "open" program
BString address(fAddress);
address.CharacterEscape(kShellEscapeCharacters, '\\');
BString commandLine;
commandLine.SetToFormat("/bin/open %s", address.String());
return system(commandLine) == 0 ? B_OK : errno;
}
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright 2013, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License.
*/
#ifndef HYPER_LINK_H
#define HYPER_LINK_H
#include <String.h>
class HyperLink {
public:
enum Type {
TYPE_URL,
TYPE_PATH,
TYPE_PATH_WITH_LINE,
TYPE_PATH_WITH_LINE_AND_COLUMN
};
public:
HyperLink();
HyperLink(const BString& address, Type type,
const BString& baseAddress = BString());
bool IsValid() const { return !fAddress.IsEmpty(); }
const BString& Address() const { return fAddress; }
const BString& BaseAddress() const { return fBaseAddress; }
Type GetType() const { return fType; }
status_t Open();
private:
BString fAddress;
BString fBaseAddress;
Type fType;
};
#endif // HYPER_LINK_H
+2
View File
@@ -16,6 +16,7 @@ Application Terminal :
FindWindow.cpp
Globals.cpp
HistoryBuffer.cpp
HyperLink.cpp
InlineInput.cpp
PatternEvaluator.cpp
PrefHandler.cpp
@@ -33,6 +34,7 @@ Application Terminal :
TermParse.cpp
TermScrollView.cpp
TermView.cpp
TermViewHighlight.cpp
TermViewStates.cpp
TermWindow.cpp
TitlePlaceholderMapper.cpp
+4
View File
@@ -31,3 +31,7 @@ const char* const kTooTipSetWindowTitlePlaceholders = B_TRANSLATE(
"\t%p\t-\tThe name of the active process in the current tab.\n"
"\t%t\t-\tThe title of the current tab.\n"
"\t%%\t-\tThe character '%'.");
const char* const kShellEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!";
const char* const kDefaultAdditionalWordCharacters = ":@-./_~";
const char* const kURLAdditionalWordCharacters = ":/-._~[]?#@!$&'()*+,;=";
+4
View File
@@ -145,6 +145,10 @@ static const char* const PREF_WINDOW_TITLE = "Window title";
extern const char* const kTooTipSetTabTitlePlaceholders;
extern const char* const kTooTipSetWindowTitlePlaceholders;
extern const char* const kShellEscapeCharacters;
extern const char* const kDefaultAdditionalWordCharacters;
extern const char* const kURLAdditionalWordCharacters;
// Cursor style
enum {
+236 -125
View File
@@ -16,7 +16,6 @@
#include "TermView.h"
#include <ctype.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
@@ -102,9 +101,6 @@ static const bigtime_t kCursorBlinkInterval = 500000;
static const rgb_color kBlackColor = { 0, 0, 0, 255 };
static const rgb_color kWhiteColor = { 255, 255, 255, 255 };
static const char* kDefaultSpecialWordChars = ":@-./_~";
static const char* kEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!";
// secondary mouse button drop
const int32 kSecondaryMouseDropAction = 'SMDA';
@@ -125,40 +121,28 @@ restrict_value(const Type& value, const Type& min, const Type& max)
}
// #pragma mark - CharClassifier
// #pragma mark - TextBufferSyncLocker
class TermView::CharClassifier : public TerminalCharClassifier {
class TermView::TextBufferSyncLocker {
public:
CharClassifier(const char* specialWordChars)
TextBufferSyncLocker(TermView* view)
:
fView(view)
{
const char* p = specialWordChars;
while (p != NULL && *p) {
int count = UTF8Char::ByteCount(*p);
if (count <= 0 || count > 4)
break;
fSpecialWordChars.push_back(UTF8Char(p, count));
p += count;
}
fView->fTextBuffer->Lock();
}
virtual int Classify(const UTF8Char& character)
~TextBufferSyncLocker()
{
if (character.IsSpace())
return CHAR_TYPE_SPACE;
fView->fTextBuffer->Unlock();
if (character.IsAlNum())
return CHAR_TYPE_WORD_CHAR;
if (std::find(fSpecialWordChars.begin(), fSpecialWordChars.end(),
character) != fSpecialWordChars.end())
return CHAR_TYPE_WORD_CHAR;
return CHAR_TYPE_WORD_DELIMITER;
if (fView->fVisibleTextBufferChanged)
fView->_VisibleTextBufferChanged();
}
private:
std::vector<UTF8Char> fSpecialWordChars;
TermView* fView;
};
@@ -299,6 +283,7 @@ TermView::_InitObject(const ShellParameters& shellParameters)
fCursor = TermPos(0, 0);
fTextBuffer = NULL;
fVisibleTextBuffer = NULL;
fVisibleTextBufferChanged = false;
fScrollBar = NULL;
fInline = NULL;
fSelectForeColor = kWhiteColor;
@@ -308,8 +293,8 @@ TermView::_InitObject(const ShellParameters& shellParameters)
fScrolledSinceLastSync = 0;
fSyncRunner = NULL;
fConsiderClockedSync = false;
fSelStart = TermPos(-1, -1);
fSelEnd = TermPos(-1, -1);
fSelection.SetHighlighter(this);
fSelection.SetRange(TermPos(0, 0), TermPos(0, 0));
fPrevPos = TermPos(-1, - 1);
fReportX10MouseEvent = false;
fReportNormalMouseEvent = false;
@@ -318,6 +303,8 @@ TermView::_InitObject(const ShellParameters& shellParameters)
fMouseClipboard = be_clipboard;
fDefaultState = new(std::nothrow) DefaultState(this);
fSelectState = new(std::nothrow) SelectState(this);
fHyperLinkState = new(std::nothrow) HyperLinkState(this);
fHyperLinkMenuState = new(std::nothrow) HyperLinkMenuState(this);
fActiveState = NULL;
fTextBuffer = new(std::nothrow) TerminalBuffer;
@@ -329,8 +316,8 @@ TermView::_InitObject(const ShellParameters& shellParameters)
return B_NO_MEMORY;
// TODO: Make the special word chars user-settable!
fCharClassifier = new(std::nothrow) CharClassifier(
kDefaultSpecialWordChars);
fCharClassifier = new(std::nothrow) DefaultCharClassifier(
kDefaultAdditionalWordCharacters);
if (fCharClassifier == NULL)
return B_NO_MEMORY;
@@ -362,8 +349,12 @@ TermView::_InitObject(const ShellParameters& shellParameters)
if (error < B_OK)
return error;
if (fDefaultState == NULL || fSelectState == NULL)
fHighlights.AddItem(&fSelection);
if (fDefaultState == NULL || fSelectState == NULL || fHyperLinkState == NULL
|| fHyperLinkMenuState == NULL) {
return B_NO_MEMORY;
}
SetLowColor(fTextBackColor);
SetViewColor(B_TRANSPARENT_32_BIT);
@@ -383,6 +374,8 @@ TermView::~TermView()
delete fDefaultState;
delete fSelectState;
delete fHyperLinkState;
delete fHyperLinkMenuState;
delete fSyncRunner;
delete fAutoScrollRunner;
delete fCharClassifier;
@@ -457,6 +450,20 @@ TermView::Archive(BMessage* data, bool deep) const
}
rgb_color
TermView::ForegroundColor()
{
return fSelectForeColor;
}
rgb_color
TermView::BackgroundColor()
{
return fSelectBackColor;
}
inline int32
TermView::_LineAt(float y)
{
@@ -582,12 +589,13 @@ TermView::SetTermSize(int rows, int columns, bool notifyShell)
// synchronize the visible text buffer
{
BAutolock _(fTextBuffer);
TextBufferSyncLocker _(this);
_SynchronizeWithTextBuffer(0, -1);
int32 offset = _LineAt(0);
fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
offset + rows + 2);
fVisibleTextBufferChanged = true;
}
if (notifyShell)
@@ -784,7 +792,8 @@ TermView::Copy(BClipboard *clipboard)
return;
BString copyStr;
fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(),
fSelection.End());
if (clipboard->Lock()) {
BMessage *clipMsg = NULL;
@@ -934,8 +943,11 @@ TermView::_Deactivate()
//! Draw part of a line in the given view.
void
TermView::_DrawLinePart(int32 x1, int32 y1, uint32 attr, char *buf,
int32 width, bool mouse, bool cursor, BView *inView)
int32 width, Highlight* highlight, bool cursor, BView *inView)
{
if (highlight != NULL)
attr = highlight->Highlighter()->AdjustTextAttributes(attr);
inView->SetFont(IS_BOLD(attr) && !fEmulateBold ? &fBoldFont : &fHalfFont);
// Set pen point
@@ -958,9 +970,9 @@ TermView::_DrawLinePart(int32 x1, int32 y1, uint32 attr, char *buf,
if (cursor) {
rgb_fore = fCursorForeColor;
rgb_back = fCursorBackColor;
} else if (mouse) {
rgb_fore = fSelectForeColor;
rgb_back = fSelectBackColor;
} else if (highlight != NULL) {
rgb_fore = highlight->Highlighter()->ForegroundColor();
rgb_back = highlight->Highlighter()->BackgroundColor();
} else {
// Reverse attribute(If selected area, don't reverse color).
if (IS_INVERSE(attr)) {
@@ -1025,7 +1037,7 @@ TermView::_DrawCursor()
}
}
bool selected = _CheckSelectedRegion(TermPos(fCursor.x, fCursor.y));
Highlight* highlight = _CheckHighlightRegion(TermPos(fCursor.x, fCursor.y));
if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x,
character, attr) == A_CHAR
&& (fCursorStyle == BLOCK_CURSOR || !cursorVisible)) {
@@ -1037,11 +1049,11 @@ TermView::_DrawCursor()
buffer[bytes] = '\0';
_DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer,
width, selected, cursorVisible, this);
width, highlight, cursorVisible, this);
} else {
if (selected)
SetHighColor(fSelectBackColor);
else if (cursorVisible )
if (highlight != NULL)
SetHighColor(highlight->Highlighter()->BackgroundColor());
else if (cursorVisible)
SetHighColor(fCursorBackColor );
else {
uint32 count = 0;
@@ -1144,6 +1156,7 @@ void
TermView::AttachedToWindow()
{
fMouseButtons = 0;
fModifiers = modifiers();
// update the terminal size because it may have changed while the TermView
// was detached from the window. On such conditions FrameResized was not
@@ -1162,7 +1175,7 @@ TermView::AttachedToWindow()
&message, 500000);
{
BAutolock _(fTextBuffer);
TextBufferSyncLocker _(this);
fTextBuffer->SetListener(thisMessenger);
_SynchronizeWithTextBuffer(0, -1);
}
@@ -1240,15 +1253,15 @@ TermView::Draw(BRect updateRect)
for (int32 i = k; i <= x2;) {
int32 lastColumn = x2;
bool insideSelection = _CheckSelectedRegion(j, i, lastColumn);
Highlight* highlight = _CheckHighlightRegion(j, i, lastColumn);
// This will clip lastColumn to the selection start or end
// to ensure the selection is not drawn at the same time as
// something else
int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i,
lastColumn, buf, attr);
// debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), selected: %d\n",
// j - firstVisible, i, lastColumn, count, (int)count, buf, insideSelection);
// debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), highlight: %p\n",
// j - firstVisible, i, lastColumn, count, (int)count, buf, highlight);
if (count == 0) {
// No chars to draw : we just fill the rectangle with the
@@ -1258,8 +1271,9 @@ TermView::Draw(BRect updateRect)
fFontWidth * nextColumn - 1, 0);
rect.bottom = rect.top + fFontHeight - 1;
rgb_color rgb_back = insideSelection
? fSelectBackColor : fTextBackColor;
rgb_color rgb_back = highlight != NULL
? highlight->Highlighter()->BackgroundColor()
: fTextBackColor;
if (fTextBuffer->IsAlternateScreenActive()) {
// alternate screen uses cell attributes
@@ -1294,7 +1308,7 @@ TermView::Draw(BRect updateRect)
count = FULL_WIDTH;
_DrawLinePart(fFontWidth * i, (int32)_LineOffset(j),
attr, buf, count, insideSelection, false, this);
attr, buf, count, highlight, false, this);
i += count;
}
}
@@ -1365,6 +1379,15 @@ TermView::WindowActivated(bool active)
if (fActive)
_Deactivate();
}
fActiveState->WindowActivated(active);
if (active) {
int32 oldModifiers = fModifiers;
fModifiers = modifiers();
if (fModifiers != oldModifiers)
fActiveState->ModifiersChanged(oldModifiers, fModifiers);
}
}
@@ -1576,6 +1599,15 @@ TermView::MessageReceived(BMessage *msg)
break;
}
case B_MODIFIERS_CHANGED:
{
int32 oldModifiers = fModifiers;
fModifiers = msg->GetInt32("modifiers", 0);
if (fModifiers != oldModifiers)
fActiveState->ModifiersChanged(oldModifiers, fModifiers);
break;
}
case B_INPUT_METHOD_EVENT:
{
int32 opcode;
@@ -1677,7 +1709,7 @@ TermView::MessageReceived(BMessage *msg)
break;
case MSG_TERMINAL_BUFFER_CHANGED:
{
BAutolock _(fTextBuffer);
TextBufferSyncLocker _(this);
_SynchronizeWithTextBuffer(0, -1);
break;
}
@@ -1842,7 +1874,7 @@ TermView::ScrollTo(BPoint where)
//debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine);
fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine);
}
BAutolock _(fTextBuffer);
TextBufferSyncLocker _(this);
if (diff < 0)
_SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1);
else
@@ -2004,11 +2036,11 @@ TermView::_DoSecondaryMouseDropAction(BMessage* msg)
int32 slash = string.FindLast("/");
string.Truncate(slash);
}
string.CharacterEscape(kEscapeCharacters, '\\');
string.CharacterEscape(kShellEscapeCharacters, '\\');
itemString += string;
break;
}
string.CharacterEscape(kEscapeCharacters, '\\');
string.CharacterEscape(kShellEscapeCharacters, '\\');
itemString += string;
}
@@ -2035,7 +2067,7 @@ TermView::_DoFileDrop(entry_ref& ref)
BPath path(&ent);
BString string(path.Path());
string.CharacterEscape(kEscapeCharacters, '\\');
string.CharacterEscape(kShellEscapeCharacters, '\\');
_WritePTY(string.String(), string.Length());
}
@@ -2099,7 +2131,9 @@ TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
// sync time not passed yet -- keep counting
fScrolledSinceLastSync += linesScrolled;
return;
} else if (fScrolledSinceLastSync + linesScrolled <= fRows) {
}
if (fScrolledSinceLastSync + linesScrolled <= fRows) {
// time's up, but not enough happened
delete fSyncRunner;
fSyncRunner = NULL;
@@ -2111,6 +2145,8 @@ TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
fScrolledSinceLastSync = 0;
}
fVisibleTextBufferChanged = true;
// Simple case first -- complete invalidation.
if (info.invalidateAll) {
Invalidate();
@@ -2194,15 +2230,23 @@ TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
fVisibleTextBuffer->ScrollBy(linesScrolled);
}
// move selection
if (fSelStart != fSelEnd) {
fSelStart.y -= linesScrolled;
fSelEnd.y -= linesScrolled;
fInitialSelectionStart.y -= linesScrolled;
fInitialSelectionEnd.y -= linesScrolled;
// move highlights
for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
if (highlight->IsEmpty())
continue;
if (fSelStart.y < -historySize)
_Deselect();
highlight->ScrollRange(linesScrolled);
if (highlight == &fSelection) {
fInitialSelectionStart.y -= linesScrolled;
fInitialSelectionEnd.y -= linesScrolled;
}
if (highlight->Start().y < -historySize) {
if (highlight == &fSelection)
_Deselect();
else
_ClearHighlight(highlight);
}
}
}
@@ -2212,12 +2256,13 @@ TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
info.dirtyBottom);
// clear the selection, if affected
if (fSelStart != fSelEnd) {
if (!fSelection.IsEmpty()) {
// TODO: We're clearing the selection more often than necessary --
// to avoid that, we'd also need to track the x coordinates of the
// dirty range.
int32 selectionBottom = fSelEnd.x > 0 ? fSelEnd.y : fSelEnd.y - 1;
if (fSelStart.y <= info.dirtyBottom
int32 selectionBottom = fSelection.End().x > 0
? fSelection.End().y : fSelection.End().y - 1;
if (fSelection.Start().y <= info.dirtyBottom
&& info.dirtyTop <= selectionBottom) {
_Deselect();
}
@@ -2247,6 +2292,17 @@ TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
}
void
TermView::_VisibleTextBufferChanged()
{
if (!fVisibleTextBufferChanged)
return;
fVisibleTextBufferChanged = false;
fActiveState->VisibleTextBufferChanged();
}
/*! Write strings to PTY device. If encoding system isn't UTF8, change
encoding to UTF8 before writing PTY.
*/
@@ -2322,9 +2378,8 @@ TermView::MouseDown(BPoint where)
BMessage* currentMessage = Window()->CurrentMessage();
int32 buttons = currentMessage->GetInt32("buttons", 0);
int32 modifiers = currentMessage->GetInt32("modifiers", 0);
fActiveState->MouseDown(where, buttons, modifiers);
fActiveState->MouseDown(where, buttons, fModifiers);
fMouseButtons = buttons;
fLastClickPoint = where;
@@ -2334,7 +2389,7 @@ TermView::MouseDown(BPoint where)
void
TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message)
{
fActiveState->MouseMoved(where, transit, message);
fActiveState->MouseMoved(where, transit, message, fModifiers);
}
@@ -2354,7 +2409,7 @@ void
TermView::_Select(TermPos start, TermPos end, bool inclusive,
bool setInitialSelection)
{
BAutolock _(fTextBuffer);
TextBufferSyncLocker _(this);
_SynchronizeWithTextBuffer(0, -1);
@@ -2396,18 +2451,17 @@ TermView::_Select(TermPos start, TermPos end, bool inclusive,
end.x = fColumns;
}
if (fSelStart != fSelEnd)
_InvalidateTextRange(fSelStart, fSelEnd);
if (!fSelection.IsEmpty())
_InvalidateTextRange(fSelection.Start(), fSelection.End());
fSelStart = start;
fSelEnd = end;
fSelection.SetRange(start, end);
if (setInitialSelection) {
fInitialSelectionStart = fSelStart;
fInitialSelectionEnd = fSelEnd;
fInitialSelectionStart = fSelection.Start();
fInitialSelectionEnd = fSelection.End();
}
_InvalidateTextRange(fSelStart, fSelEnd);
_InvalidateTextRange(fSelection.Start(), fSelection.End());
}
@@ -2419,8 +2473,8 @@ TermView::_ExtendSelection(TermPos pos, bool inclusive,
if (!useInitialSelection && !_HasSelection())
return;
TermPos start = fSelStart;
TermPos end = fSelEnd;
TermPos start = fSelection.Start();
TermPos end = fSelection.End();
if (useInitialSelection) {
start = fInitialSelectionStart;
@@ -2446,22 +2500,17 @@ void
TermView::_Deselect()
{
//debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection());
if (!_HasSelection())
return;
_InvalidateTextRange(fSelStart, fSelEnd);
fSelStart.SetTo(0, 0);
fSelEnd.SetTo(0, 0);
fInitialSelectionStart.SetTo(0, 0);
fInitialSelectionEnd.SetTo(0, 0);
if (_ClearHighlight(&fSelection)) {
fInitialSelectionStart.SetTo(0, 0);
fInitialSelectionEnd.SetTo(0, 0);
}
}
bool
TermView::_HasSelection() const
{
return fSelStart != fSelEnd;
return !fSelection.IsEmpty();
}
@@ -2476,11 +2525,15 @@ TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection)
return;
if (extend) {
if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
if (start
< (useInitialSelection
? fInitialSelectionStart : fSelection.Start())) {
_ExtendSelection(start, false, useInitialSelection);
else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
} else if (end
> (useInitialSelection
? fInitialSelectionEnd : fSelection.End())) {
_ExtendSelection(end, false, useInitialSelection);
else if (useInitialSelection)
} else if (useInitialSelection)
_Select(start, end, false, false);
} else
_Select(start, end, false, !useInitialSelection);
@@ -2494,47 +2547,101 @@ TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection)
TermPos end = TermPos(0, start.y + 1);
if (extend) {
if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart))
if (start
< (useInitialSelection
? fInitialSelectionStart : fSelection.Start())) {
_ExtendSelection(start, false, useInitialSelection);
else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd))
} else if (end
> (useInitialSelection
? fInitialSelectionEnd : fSelection.End())) {
_ExtendSelection(end, false, useInitialSelection);
else if (useInitialSelection)
} else if (useInitialSelection)
_Select(start, end, false, false);
} else
_Select(start, end, false, !useInitialSelection);
}
bool
TermView::_CheckSelectedRegion(const TermPos &pos) const
void
TermView::_AddHighlight(Highlight* highlight)
{
return pos >= fSelStart && pos < fSelEnd;
fHighlights.AddItem(highlight);
if (!highlight->IsEmpty())
_InvalidateTextRange(highlight->Start(), highlight->End());
}
void
TermView::_RemoveHighlight(Highlight* highlight)
{
fHighlights.RemoveItem(highlight);
if (!highlight->IsEmpty())
_InvalidateTextRange(highlight->Start(), highlight->End());
}
bool
TermView::_CheckSelectedRegion(int32 row, int32 firstColumn,
TermView::_ClearHighlight(Highlight* highlight)
{
if (highlight->IsEmpty())
return false;
_InvalidateTextRange(highlight->Start(), highlight->End());
highlight->SetRange(TermPos(0, 0), TermPos(0, 0));
return true;
}
TermView::Highlight*
TermView::_CheckHighlightRegion(const TermPos &pos) const
{
for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
if (highlight->RangeContains(pos))
return highlight;
}
return NULL;
}
TermView::Highlight*
TermView::_CheckHighlightRegion(int32 row, int32 firstColumn,
int32& lastColumn) const
{
if (fSelStart == fSelEnd)
return false;
Highlight* nextHighlight = NULL;
if (row == fSelStart.y && firstColumn < fSelStart.x
&& lastColumn >= fSelStart.x) {
// region starts before the selection, but intersects with it
lastColumn = fSelStart.x - 1;
return false;
for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
if (highlight->IsEmpty())
continue;
if (row == highlight->Start().y && firstColumn < highlight->Start().x
&& lastColumn >= highlight->Start().x) {
// region starts before the highlight, but intersects with it
if (nextHighlight == NULL
|| highlight->Start().x < nextHighlight->Start().x) {
nextHighlight = highlight;
}
continue;
}
if (row == highlight->End().y && firstColumn < highlight->End().x
&& lastColumn >= highlight->End().x) {
// region starts in the highlight, but exceeds the end
lastColumn = highlight->End().x - 1;
return highlight;
}
TermPos pos(firstColumn, row);
if (highlight->RangeContains(pos))
return highlight;
}
if (row == fSelEnd.y && firstColumn < fSelEnd.x
&& lastColumn >= fSelEnd.x) {
// region starts in the selection, but exceeds the end
lastColumn = fSelEnd.x - 1;
return true;
}
TermPos pos(firstColumn, row);
return pos >= fSelStart && pos < fSelEnd;
if (nextHighlight != NULL)
lastColumn = nextHighlight->Start().x - 1;
return NULL;
}
@@ -2560,15 +2667,15 @@ bool
TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
bool matchWord)
{
BAutolock _(fTextBuffer);
TextBufferSyncLocker _(this);
_SynchronizeWithTextBuffer(0, -1);
TermPos start;
if (_HasSelection()) {
if (forwardSearch)
start = fSelEnd;
start = fSelection.End();
else
start = fSelStart;
start = fSelection.Start();
} else {
// search from the very beginning/end
if (forwardSearch)
@@ -2584,7 +2691,7 @@ TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
}
_Select(matchStart, matchEnd, false, true);
_ScrollToRange(fSelStart, fSelEnd);
_ScrollToRange(fSelection.Start(), fSelection.End());
return true;
}
@@ -2596,7 +2703,7 @@ TermView::GetSelection(BString &str)
{
str.SetTo("");
BAutolock _(fTextBuffer);
fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd);
fTextBuffer->GetStringFromRegion(str, fSelection.Start(), fSelection.End());
}
@@ -2619,17 +2726,18 @@ TermView::InitiateDrag()
BAutolock _(fTextBuffer);
BString copyStr("");
fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd);
fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(),
fSelection.End());
BMessage message(B_MIME_DATA);
message.AddData("text/plain", B_MIME_TYPE, copyStr.String(),
copyStr.Length());
BPoint start = _ConvertFromTerminal(fSelStart);
BPoint end = _ConvertFromTerminal(fSelEnd);
BPoint start = _ConvertFromTerminal(fSelection.Start());
BPoint end = _ConvertFromTerminal(fSelection.End());
BRect rect;
if (fSelStart.y == fSelEnd.y)
if (fSelection.Start().y == fSelection.End().y)
rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight);
else
rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight);
@@ -2911,6 +3019,9 @@ TermView::Listener::NextTermView(TermView* view)
}
// #pragma mark -
#ifdef USE_DEBUG_SNAPSHOTS
void
+39 -9
View File
@@ -16,10 +16,12 @@
#include <Autolock.h>
#include <Messenger.h>
#include <ObjectList.h>
#include <String.h>
#include <View.h>
#include "TermPos.h"
#include "TermViewHighlight.h"
class ActiveProcessInfo;
@@ -30,6 +32,7 @@ class BScrollView;
class BString;
class BStringView;
class BasicTerminalBuffer;
class DefaultCharClassifier;
class InlineInput;
class ResizeWindow;
class ShellInfo;
@@ -38,10 +41,14 @@ class TermBuffer;
class TerminalBuffer;
class Shell;
class TermView : public BView {
class TermView : public BView, private TermViewHighlighter {
public:
class Listener;
typedef TermViewHighlighter Highlighter;
typedef TermViewHighlight Highlight;
public:
TermView(BRect frame,
const ShellParameters& shellParameters,
@@ -141,17 +148,29 @@ protected:
const char* property);
private:
class CharClassifier;
class TextBufferSyncLocker;
friend class TextBufferSyncLocker;
class State;
class StandardBaseState;
class DefaultState;
class SelectState;
class HyperLinkState;
class HyperLinkMenuState;
friend class State;
friend class StandardBaseState;
friend class DefaultState;
friend class SelectState;
friend class HyperLinkState;
friend class HyperLinkMenuState;
typedef BObjectList<Highlight> HighlightList;
private:
// TermViewHighlighter
virtual rgb_color ForegroundColor();
virtual rgb_color BackgroundColor();
private:
// point and text offset conversion
@@ -174,8 +193,9 @@ private:
void _SwitchCursorBlinking(bool blinkingOn);
void _DrawLinePart(int32 x1, int32 y1, uint32 attr,
char* buffer, int32 width, bool mouse,
bool cursor, BView* inView);
char* buffer, int32 width,
Highlight* highlight, bool cursor,
BView* inView);
void _DrawCursor();
void _InvalidateTextRange(TermPos start,
TermPos end);
@@ -193,6 +213,7 @@ private:
void _SynchronizeWithTextBuffer(
int32 visibleDirtyTop,
int32 visibleDirtyBottom);
void _VisibleTextBufferChanged();
void _WritePTY(const char* text, int32 numBytes);
@@ -209,8 +230,12 @@ private:
void _SelectLine(BPoint where, bool extend,
bool useInitialSelection);
bool _CheckSelectedRegion(const TermPos& pos) const;
bool _CheckSelectedRegion(int32 row,
void _AddHighlight(Highlight* highlight);
void _RemoveHighlight(Highlight* highlight);
bool _ClearHighlight(Highlight* highlight);
Highlight* _CheckHighlightRegion(const TermPos& pos) const;
Highlight* _CheckHighlightRegion(int32 row,
int32 firstColumn, int32& lastColumn) const;
void _UpdateSIGWINCH();
@@ -237,7 +262,7 @@ private:
BMessageRunner* fAutoScrollRunner;
BMessageRunner* fResizeRunner;
BStringView* fResizeView;
CharClassifier* fCharClassifier;
DefaultCharClassifier* fCharClassifier;
// Font and Width
BFont fHalfFont;
@@ -272,6 +297,7 @@ private:
// Object pointer.
TerminalBuffer* fTextBuffer;
BasicTerminalBuffer* fVisibleTextBuffer;
bool fVisibleTextBufferChanged;
BScrollBar* fScrollBar;
InlineInput* fInline;
@@ -297,14 +323,16 @@ private:
bool fConsiderClockedSync;
// selection
TermPos fSelStart;
TermPos fSelEnd;
Highlight fSelection;
TermPos fInitialSelectionStart;
TermPos fInitialSelectionEnd;
BPoint fLastClickPoint;
HighlightList fHighlights;
// mouse
int32 fMouseButtons;
int32 fModifiers;
TermPos fPrevPos;
bool fReportX10MouseEvent;
bool fReportNormalMouseEvent;
@@ -315,6 +343,8 @@ private:
// states
DefaultState* fDefaultState;
SelectState* fSelectState;
HyperLinkState* fHyperLinkState;
HyperLinkMenuState* fHyperLinkMenuState;
State* fActiveState;
};
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "TermViewHighlight.h"
TermViewHighlighter::~TermViewHighlighter()
{
}
uint32
TermViewHighlighter::AdjustTextAttributes(uint32 attributes)
{
return attributes;
}
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef TERMVIEW_HIGHLIGHT_H
#define TERMVIEW_HIGHLIGHT_H
#include <GraphicsDefs.h>
#include "TermPos.h"
class TermViewHighlighter {
public:
virtual ~TermViewHighlighter();
virtual rgb_color ForegroundColor() = 0;
virtual rgb_color BackgroundColor() = 0;
virtual uint32 AdjustTextAttributes(uint32 attributes);
};
class TermViewHighlight {
public:
TermViewHighlight()
:
fHighlighter(NULL),
fStart(-1, -1),
fEnd(-1, -1)
{
}
TermViewHighlighter* Highlighter() const
{
return fHighlighter;
}
void SetHighlighter(TermViewHighlighter* highligher)
{
fHighlighter = highligher;
}
const TermPos& Start() const
{
return fStart;
}
const TermPos& End() const
{
return fEnd;
}
bool IsEmpty() const
{
return fStart == fEnd;
}
bool RangeContains(const TermPos& pos) const
{
return pos >= fStart && pos < fEnd;
}
void SetRange(const TermPos& start, const TermPos& end)
{
fStart = start;
fEnd = end;
}
void ScrollRange(int32 byLines)
{
fStart.y -= byLines;
fEnd.y -= byLines;
}
private:
TermViewHighlighter* fHighlighter;
TermPos fStart;
TermPos fEnd;
};
#endif // TERMVIEW_HIGHLIGHT_H
+450 -12
View File
@@ -16,17 +16,30 @@
#include "TermViewStates.h"
#include <stdio.h>
#include <sys/stat.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <Cursor.h>
#include <LayoutBuilder.h>
#include <MessageRunner.h>
#include <PopUpMenu.h>
#include <ScrollBar.h>
#include <UTF8.h>
#include <Window.h>
#include "Shell.h"
#include "TermConst.h"
#include "TerminalBuffer.h"
#include "VTkeymap.h"
#include "VTKeyTbl.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Terminal TermView"
// selection granularity
enum {
SELECT_CHARS,
@@ -36,6 +49,13 @@ enum {
static const uint32 kAutoScroll = 'AScr';
static const uint32 kMessageOpenLink = 'OLnk';
static const uint32 kMessageCopyLink = 'CLnk';
static const uint32 kMessageMenuClosed = 'MClo';
static const char* const kKnownURLProtocols = "http:https:ftp:mailto";
// #pragma mark - State
@@ -71,6 +91,12 @@ TermView::State::MessageReceived(BMessage* message)
}
void
TermView::State::ModifiersChanged(int32 oldModifiers, int32 modifiers)
{
}
void
TermView::State::KeyDown(const char* bytes, int32 numBytes)
{
@@ -85,7 +111,7 @@ TermView::State::MouseDown(BPoint where, int32 buttons, int32 modifiers)
void
TermView::State::MouseMoved(BPoint where, uint32 transit,
const BMessage* message)
const BMessage* message, int32 modifiers)
{
}
@@ -96,6 +122,18 @@ TermView::State::MouseUp(BPoint where, int32 buttons)
}
void
TermView::State::WindowActivated(bool active)
{
}
void
TermView::State::VisibleTextBufferChanged()
{
}
// #pragma mark - StandardBaseState
@@ -107,25 +145,22 @@ TermView::StandardBaseState::StandardBaseState(TermView* view)
bool
TermView::StandardBaseState::_StandardMouseMoved(BPoint where)
TermView::StandardBaseState::_StandardMouseMoved(BPoint where, int32 modifiers)
{
if (!fView->fReportAnyMouseEvent && !fView->fReportButtonMouseEvent)
return false;
int32 modifier;
fView->Window()->CurrentMessage()->FindInt32("modifiers", &modifier);
TermPos clickPos = fView->_ConvertToTerminal(where);
if (fView->fReportButtonMouseEvent) {
if (fView->fPrevPos.x != clickPos.x
|| fView->fPrevPos.y != clickPos.y) {
fView->_SendMouseEvent(fView->fMouseButtons, modifier,
fView->_SendMouseEvent(fView->fMouseButtons, modifiers,
clickPos.x, clickPos.y, true);
}
fView->fPrevPos = clickPos;
} else {
fView->_SendMouseEvent(fView->fMouseButtons, modifier, clickPos.x,
fView->_SendMouseEvent(fView->fMouseButtons, modifiers, clickPos.x,
clickPos.y, true);
}
@@ -143,6 +178,13 @@ TermView::DefaultState::DefaultState(TermView* view)
}
void
TermView::DefaultState::ModifiersChanged(int32 oldModifiers, int32 modifiers)
{
_CheckEnterHyperLinkState(modifiers);
}
void
TermView::DefaultState::KeyDown(const char* bytes, int32 numBytes)
{
@@ -341,9 +383,32 @@ TermView::DefaultState::MouseDown(BPoint where, int32 buttons, int32 modifiers)
void
TermView::DefaultState::MouseMoved(BPoint where, uint32 transit,
const BMessage* message)
const BMessage* message, int32 modifiers)
{
_StandardMouseMoved(where);
if (_CheckEnterHyperLinkState(modifiers))
return;
_StandardMouseMoved(where, modifiers);
}
void
TermView::DefaultState::WindowActivated(bool active)
{
if (active)
_CheckEnterHyperLinkState(fView->fModifiers);
}
bool
TermView::DefaultState::_CheckEnterHyperLinkState(int32 modifiers)
{
if ((modifiers & B_COMMAND_KEY) != 0 && fView->Window()->IsActive()) {
fView->_NextState(fView->fHyperLinkState);
return true;
}
return false;
}
@@ -368,7 +433,7 @@ TermView::SelectState::Prepare(BPoint where, int32 modifiers)
if (fView->_HasSelection()) {
TermPos inPos = fView->_ConvertToTerminal(where);
if (fView->_CheckSelectedRegion(inPos)) {
if (fView->fSelection.RangeContains(inPos)) {
if (modifiers & B_CONTROL_KEY) {
BPoint p;
uint32 bt;
@@ -448,9 +513,9 @@ TermView::SelectState::MessageReceived(BMessage* message)
void
TermView::SelectState::MouseMoved(BPoint where, uint32 transit,
const BMessage* message)
const BMessage* message, int32 modifiers)
{
if (_StandardMouseMoved(where))
if (_StandardMouseMoved(where, modifiers))
return;
if (fCheckMouseTracking) {
@@ -561,3 +626,376 @@ TermView::SelectState::_AutoScrollUpdate()
}
}
}
// #pragma mark - HyperLinkState
TermView::HyperLinkState::HyperLinkState(TermView* view)
:
State(view),
fURLCharClassifier(kURLAdditionalWordCharacters),
fPathComponentCharClassifier(
BString(kDefaultAdditionalWordCharacters).RemoveFirst("/")),
fHighlight(),
fHighlightActive(false)
{
fHighlight.SetHighlighter(this);
}
void
TermView::HyperLinkState::Entered()
{
_UpdateHighlight();
}
void
TermView::HyperLinkState::Exited()
{
_DeactivateHighlight();
}
void
TermView::HyperLinkState::ModifiersChanged(int32 oldModifiers, int32 modifiers)
{
if ((modifiers & B_COMMAND_KEY) == 0)
fView->_NextState(fView->fDefaultState);
else
_UpdateHighlight();
}
void
TermView::HyperLinkState::MouseDown(BPoint where, int32 buttons,
int32 modifiers)
{
TermPos start;
TermPos end;
HyperLink link;
bool pathPrefixOnly = (modifiers & B_SHIFT_KEY) != 0;
if (!_GetHyperLinkAt(where, pathPrefixOnly, link, start, end))
return;
if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
link.Open();
} else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
fView->fHyperLinkMenuState->Prepare(where, link);
fView->_NextState(fView->fHyperLinkMenuState);
}
}
void
TermView::HyperLinkState::MouseMoved(BPoint where, uint32 transit,
const BMessage* message, int32 modifiers)
{
_UpdateHighlight(where, modifiers);
}
void
TermView::HyperLinkState::WindowActivated(bool active)
{
if (!active)
fView->_NextState(fView->fDefaultState);
}
void
TermView::HyperLinkState::VisibleTextBufferChanged()
{
_UpdateHighlight();
}
rgb_color
TermView::HyperLinkState::ForegroundColor()
{
return make_color(0, 0, 255);
}
rgb_color
TermView::HyperLinkState::BackgroundColor()
{
return fView->fTextBackColor;
}
uint32
TermView::HyperLinkState::AdjustTextAttributes(uint32 attributes)
{
return attributes | UNDERLINE;
}
bool
TermView::HyperLinkState::_GetHyperLinkAt(BPoint where, bool pathPrefixOnly,
HyperLink& _link, TermPos& _start, TermPos& _end)
{
TerminalBuffer* textBuffer = fView->fTextBuffer;
BAutolock textBufferLocker(textBuffer);
TermPos pos = fView->_ConvertToTerminal(where);
// try to get a URL first
BString text;
if (!textBuffer->FindWord(pos, &fURLCharClassifier, false, _start, _end))
return false;
text.Truncate(0);
textBuffer->GetStringFromRegion(text, _start, _end);
text.Trim();
// We're only happy, if it has a protocol part which we know.
int32 colonIndex = text.FindFirst(':');
if (colonIndex >= 0) {
BString protocol(text, colonIndex);
if (strstr(kKnownURLProtocols, protocol) != NULL) {
_link = HyperLink(text, HyperLink::TYPE_URL);
return true;
}
}
// no obvious URL -- try file name
if (!textBuffer->FindWord(pos, fView->fCharClassifier, false, _start, _end))
return false;
// In path-prefix-only mode we determine the end position anew by omitting
// the '/' in the allowed word chars.
if (pathPrefixOnly) {
TermPos componentStart;
TermPos componentEnd;
if (textBuffer->FindWord(pos, &fPathComponentCharClassifier, false,
componentStart, componentEnd)) {
_end = componentEnd;
} else {
// That means pos points to a '/'. We simply use the previous
// position.
_end = pos;
if (_start == _end) {
// Well, must be just "/". Advance to the next position.
if (!textBuffer->NextLinePos(_end, false))
return false;
}
}
}
text.Truncate(0);
textBuffer->GetStringFromRegion(text, _start, _end);
text.Trim();
if (text.IsEmpty())
return false;
// check, whether the file exists
struct stat st;
if (lstat(text, &st) == 0) {
_link = HyperLink(text, HyperLink::TYPE_PATH);
return true;
}
// As such this isn't an existing path. Try a few common alternative cases:
// * "<path>:"
// * "<path>:<line>"
// * "<path>:<line>:"
// * "<path>:<line>:<column>"
// * "<path>:<line>:<column>:"
if (text.Length() <= 1)
return false;
if (text[text.Length() - 1] == ':') {
text.Truncate(text.Length() - 1);
if (!textBuffer->PreviousLinePos(_end))
return false;
if (lstat(text, &st) == 0) {
_link = HyperLink(text, HyperLink::TYPE_PATH);
return true;
}
}
BString path = text;
for (int32 i = 0; i < 2; i++) {
int32 colonIndex = path.FindLast(':');
if (colonIndex <= 0 || colonIndex == path.Length() - 1)
return false;
char* numberEnd;
strtol(path.String() + colonIndex + 1, &numberEnd, 0);
if (*numberEnd != '\0')
return false;
path.Truncate(colonIndex);
if (lstat(path, &st) == 0) {
_link = HyperLink(text,
i == 0
? HyperLink::TYPE_PATH_WITH_LINE
: HyperLink::TYPE_PATH_WITH_LINE_AND_COLUMN,
path);
return true;
}
}
return false;
}
void
TermView::HyperLinkState::_UpdateHighlight()
{
BPoint where;
uint32 buttons;
fView->GetMouse(&where, &buttons, false);
_UpdateHighlight(where, fView->fModifiers);
}
void
TermView::HyperLinkState::_UpdateHighlight(BPoint where, int32 modifiers)
{
TermPos start;
TermPos end;
HyperLink link;
bool pathPrefixOnly = (modifiers & B_SHIFT_KEY) != 0;
if (_GetHyperLinkAt(where, pathPrefixOnly, link, start, end))
_ActivateHighlight(start, end);
else
_DeactivateHighlight();
}
void
TermView::HyperLinkState::_ActivateHighlight(const TermPos& start,
const TermPos& end)
{
if (fHighlightActive) {
if (fHighlight.Start() == start && fHighlight.End() == end)
return;
_DeactivateHighlight();
}
fHighlight.SetRange(start, end);
fView->_AddHighlight(&fHighlight);
BCursor cursor(B_CURSOR_ID_FOLLOW_LINK);
fView->SetViewCursor(&cursor);
fHighlightActive = true;
}
void
TermView::HyperLinkState::_DeactivateHighlight()
{
if (fHighlightActive) {
fView->_RemoveHighlight(&fHighlight);
BCursor cursor(B_CURSOR_ID_SYSTEM_DEFAULT);
fView->SetViewCursor(&cursor);
fHighlightActive = false;
}
}
// #pragma mark - HyperLinkMenuState
class TermView::HyperLinkMenuState::PopUpMenu : public BPopUpMenu {
public:
PopUpMenu(const BMessenger& messageTarget)
:
BPopUpMenu("open hyperlink"),
fMessageTarget(messageTarget)
{
SetAsyncAutoDestruct(true);
}
~PopUpMenu()
{
fMessageTarget.SendMessage(kMessageMenuClosed);
}
private:
BMessenger fMessageTarget;
};
TermView::HyperLinkMenuState::HyperLinkMenuState(TermView* view)
:
State(view),
fLink()
{
}
void
TermView::HyperLinkMenuState::Prepare(BPoint point, const HyperLink& link)
{
fLink = link;
// open context menu
PopUpMenu* menu = new PopUpMenu(fView);
BLayoutBuilder::Menu<> menuBuilder(menu);
switch (link.GetType()) {
case HyperLink::TYPE_URL:
menuBuilder
.AddItem(B_TRANSLATE("Open link"), kMessageOpenLink)
.AddItem(B_TRANSLATE("Copy link location"), kMessageCopyLink);
break;
case HyperLink::TYPE_PATH:
case HyperLink::TYPE_PATH_WITH_LINE:
case HyperLink::TYPE_PATH_WITH_LINE_AND_COLUMN:
menuBuilder
.AddItem(B_TRANSLATE("Open path"), kMessageOpenLink)
.AddItem(B_TRANSLATE("Copy path"), kMessageCopyLink);
break;
}
menu->SetTargetForItems(fView);
menu->Go(fView->ConvertToScreen(point), true, true, true);
}
void
TermView::HyperLinkMenuState::Exited()
{
fLink = HyperLink();
}
bool
TermView::HyperLinkMenuState::MessageReceived(BMessage* message)
{
switch (message->what) {
case kMessageOpenLink:
if (fLink.IsValid())
fLink.Open();
return true;
case kMessageCopyLink:
if (fLink.IsValid()) {
if (!be_clipboard->Lock())
return true;
be_clipboard->Clear();
if (BMessage *data = be_clipboard->Data()) {
data->AddData("text/plain", B_MIME_TYPE,
fLink.Address().String(),
fLink.Address().Length());
be_clipboard->Commit();
}
be_clipboard->Unlock();
}
return true;
case kMessageMenuClosed:
fView->_NextState(fView->fDefaultState);
return true;
}
return false;
}
+84 -4
View File
@@ -14,6 +14,8 @@
#define TERMVIEW_STATES_H
#include "HyperLink.h"
#include "TerminalCharClassifier.h"
#include "TermView.h"
@@ -28,14 +30,20 @@ public:
virtual bool MessageReceived(BMessage* message);
// returns true, if handled
virtual void ModifiersChanged(int32 oldModifiers,
int32 modifiers);
virtual void KeyDown(const char* bytes, int32 numBytes);
virtual void MouseDown(BPoint where, int32 buttons,
int32 modifiers);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* message);
const BMessage* message, int32 modifiers);
virtual void MouseUp(BPoint where, int32 buttons);
virtual void WindowActivated(bool active);
virtual void VisibleTextBufferChanged();
protected:
TermView* fView;
};
@@ -46,7 +54,8 @@ public:
StandardBaseState(TermView* view);
protected:
bool _StandardMouseMoved(BPoint where);
bool _StandardMouseMoved(BPoint where,
int32 modifiers);
};
@@ -54,12 +63,20 @@ class TermView::DefaultState : public TermView::StandardBaseState {
public:
DefaultState(TermView* view);
virtual void ModifiersChanged(int32 oldModifiers,
int32 modifiers);
virtual void KeyDown(const char* bytes, int32 numBytes);
virtual void MouseDown(BPoint where, int32 buttons,
int32 modifiers);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* message);
const BMessage* message, int32 modifiers);
virtual void WindowActivated(bool active);
private:
bool _CheckEnterHyperLinkState(int32 modifiers);
};
@@ -72,7 +89,7 @@ public:
virtual bool MessageReceived(BMessage* message);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* message);
const BMessage* message, int32 modifiers);
virtual void MouseUp(BPoint where, int32 buttons);
private:
@@ -85,4 +102,67 @@ private:
};
class TermView::HyperLinkState : public TermView::State,
private TermViewHighlighter {
public:
HyperLinkState(TermView* view);
virtual void Entered();
virtual void Exited();
virtual void ModifiersChanged(int32 oldModifiers,
int32 modifiers);
virtual void MouseDown(BPoint where, int32 buttons,
int32 modifiers);
virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* message, int32 modifiers);
virtual void WindowActivated(bool active);
virtual void VisibleTextBufferChanged();
private:
// TermViewHighlighter
virtual rgb_color ForegroundColor();
virtual rgb_color BackgroundColor();
virtual uint32 AdjustTextAttributes(uint32 attributes);
private:
bool _GetHyperLinkAt(BPoint where,
bool pathPrefixOnly, HyperLink& _link,
TermPos& _start, TermPos& _end);
void _UpdateHighlight();
void _UpdateHighlight(BPoint where, int32 modifiers);
void _ActivateHighlight(const TermPos& start,
const TermPos& end);
void _DeactivateHighlight();
private:
DefaultCharClassifier fURLCharClassifier;
DefaultCharClassifier fPathComponentCharClassifier;
TermViewHighlight fHighlight;
bool fHighlightActive;
};
class TermView::HyperLinkMenuState : public TermView::State {
public:
HyperLinkMenuState(TermView* view);
void Prepare(BPoint point, const HyperLink& link);
virtual void Exited();
virtual bool MessageReceived(BMessage* message);
private:
class PopUpMenu;
private:
HyperLink fLink;
};
#endif // TERMVIEW_STATES_H
+42 -1
View File
@@ -1,11 +1,52 @@
/*
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2008-2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#include "TerminalCharClassifier.h"
#include <ctype.h>
#include <algorithm>
// #pragma mark - TerminalCharClassifier
TerminalCharClassifier::~TerminalCharClassifier()
{
}
// #pragma mark - DefaultCharClassifier
DefaultCharClassifier::DefaultCharClassifier(const char* additionalWordChars)
{
const char* p = additionalWordChars;
while (p != NULL && *p != '\0') {
int count = UTF8Char::ByteCount(*p);
if (count <= 0 || count > 4)
break;
fAdditionalWordChars.push_back(UTF8Char(p, count));
p += count;
}
}
int
DefaultCharClassifier::Classify(const UTF8Char& character)
{
if (character.IsSpace())
return CHAR_TYPE_SPACE;
if (character.IsAlNum())
return CHAR_TYPE_WORD_CHAR;
if (std::find(fAdditionalWordChars.begin(), fAdditionalWordChars.end(),
character) != fAdditionalWordChars.end()) {
return CHAR_TYPE_WORD_CHAR;
}
return CHAR_TYPE_WORD_DELIMITER;
}
+18 -1
View File
@@ -1,11 +1,16 @@
/*
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
* Copyright 2008-2013, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*/
#ifndef TERMINAL_CHAR_CLASSIFIER_H
#define TERMINAL_CHAR_CLASSIFIER_H
#include <vector>
#include "UTF8Char.h"
enum {
CHAR_TYPE_SPACE,
CHAR_TYPE_WORD_CHAR,
@@ -23,4 +28,16 @@ public:
};
class DefaultCharClassifier: public TerminalCharClassifier {
public:
DefaultCharClassifier(
const char* additionalWordChars);
virtual int Classify(const UTF8Char& character);
private:
std::vector<UTF8Char> fAdditionalWordChars;
};
#endif // TERMINAL_CHAR_CLASSIFIER_H