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 FindWindow.cpp
Globals.cpp Globals.cpp
HistoryBuffer.cpp HistoryBuffer.cpp
HyperLink.cpp
InlineInput.cpp InlineInput.cpp
PatternEvaluator.cpp PatternEvaluator.cpp
PrefHandler.cpp PrefHandler.cpp
@@ -33,6 +34,7 @@ Application Terminal :
TermParse.cpp TermParse.cpp
TermScrollView.cpp TermScrollView.cpp
TermView.cpp TermView.cpp
TermViewHighlight.cpp
TermViewStates.cpp TermViewStates.cpp
TermWindow.cpp TermWindow.cpp
TitlePlaceholderMapper.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%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\t-\tThe title of the current tab.\n"
"\t%%\t-\tThe character '%'."); "\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 kTooTipSetTabTitlePlaceholders;
extern const char* const kTooTipSetWindowTitlePlaceholders; extern const char* const kTooTipSetWindowTitlePlaceholders;
extern const char* const kShellEscapeCharacters;
extern const char* const kDefaultAdditionalWordCharacters;
extern const char* const kURLAdditionalWordCharacters;
// Cursor style // Cursor style
enum { enum {
+236 -125
View File
@@ -16,7 +16,6 @@
#include "TermView.h" #include "TermView.h"
#include <ctype.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.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 kBlackColor = { 0, 0, 0, 255 };
static const rgb_color kWhiteColor = { 255, 255, 255, 255 }; static const rgb_color kWhiteColor = { 255, 255, 255, 255 };
static const char* kDefaultSpecialWordChars = ":@-./_~";
static const char* kEscapeCharacters = " ~`#$&*()\\|[]{};'\"<>?!";
// secondary mouse button drop // secondary mouse button drop
const int32 kSecondaryMouseDropAction = 'SMDA'; 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: public:
CharClassifier(const char* specialWordChars) TextBufferSyncLocker(TermView* view)
:
fView(view)
{ {
const char* p = specialWordChars; fView->fTextBuffer->Lock();
while (p != NULL && *p) {
int count = UTF8Char::ByteCount(*p);
if (count <= 0 || count > 4)
break;
fSpecialWordChars.push_back(UTF8Char(p, count));
p += count;
}
} }
virtual int Classify(const UTF8Char& character) ~TextBufferSyncLocker()
{ {
if (character.IsSpace()) fView->fTextBuffer->Unlock();
return CHAR_TYPE_SPACE;
if (character.IsAlNum()) if (fView->fVisibleTextBufferChanged)
return CHAR_TYPE_WORD_CHAR; fView->_VisibleTextBufferChanged();
if (std::find(fSpecialWordChars.begin(), fSpecialWordChars.end(),
character) != fSpecialWordChars.end())
return CHAR_TYPE_WORD_CHAR;
return CHAR_TYPE_WORD_DELIMITER;
} }
private: private:
std::vector<UTF8Char> fSpecialWordChars; TermView* fView;
}; };
@@ -299,6 +283,7 @@ TermView::_InitObject(const ShellParameters& shellParameters)
fCursor = TermPos(0, 0); fCursor = TermPos(0, 0);
fTextBuffer = NULL; fTextBuffer = NULL;
fVisibleTextBuffer = NULL; fVisibleTextBuffer = NULL;
fVisibleTextBufferChanged = false;
fScrollBar = NULL; fScrollBar = NULL;
fInline = NULL; fInline = NULL;
fSelectForeColor = kWhiteColor; fSelectForeColor = kWhiteColor;
@@ -308,8 +293,8 @@ TermView::_InitObject(const ShellParameters& shellParameters)
fScrolledSinceLastSync = 0; fScrolledSinceLastSync = 0;
fSyncRunner = NULL; fSyncRunner = NULL;
fConsiderClockedSync = false; fConsiderClockedSync = false;
fSelStart = TermPos(-1, -1); fSelection.SetHighlighter(this);
fSelEnd = TermPos(-1, -1); fSelection.SetRange(TermPos(0, 0), TermPos(0, 0));
fPrevPos = TermPos(-1, - 1); fPrevPos = TermPos(-1, - 1);
fReportX10MouseEvent = false; fReportX10MouseEvent = false;
fReportNormalMouseEvent = false; fReportNormalMouseEvent = false;
@@ -318,6 +303,8 @@ TermView::_InitObject(const ShellParameters& shellParameters)
fMouseClipboard = be_clipboard; fMouseClipboard = be_clipboard;
fDefaultState = new(std::nothrow) DefaultState(this); fDefaultState = new(std::nothrow) DefaultState(this);
fSelectState = new(std::nothrow) SelectState(this); fSelectState = new(std::nothrow) SelectState(this);
fHyperLinkState = new(std::nothrow) HyperLinkState(this);
fHyperLinkMenuState = new(std::nothrow) HyperLinkMenuState(this);
fActiveState = NULL; fActiveState = NULL;
fTextBuffer = new(std::nothrow) TerminalBuffer; fTextBuffer = new(std::nothrow) TerminalBuffer;
@@ -329,8 +316,8 @@ TermView::_InitObject(const ShellParameters& shellParameters)
return B_NO_MEMORY; return B_NO_MEMORY;
// TODO: Make the special word chars user-settable! // TODO: Make the special word chars user-settable!
fCharClassifier = new(std::nothrow) CharClassifier( fCharClassifier = new(std::nothrow) DefaultCharClassifier(
kDefaultSpecialWordChars); kDefaultAdditionalWordCharacters);
if (fCharClassifier == NULL) if (fCharClassifier == NULL)
return B_NO_MEMORY; return B_NO_MEMORY;
@@ -362,8 +349,12 @@ TermView::_InitObject(const ShellParameters& shellParameters)
if (error < B_OK) if (error < B_OK)
return error; return error;
if (fDefaultState == NULL || fSelectState == NULL) fHighlights.AddItem(&fSelection);
if (fDefaultState == NULL || fSelectState == NULL || fHyperLinkState == NULL
|| fHyperLinkMenuState == NULL) {
return B_NO_MEMORY; return B_NO_MEMORY;
}
SetLowColor(fTextBackColor); SetLowColor(fTextBackColor);
SetViewColor(B_TRANSPARENT_32_BIT); SetViewColor(B_TRANSPARENT_32_BIT);
@@ -383,6 +374,8 @@ TermView::~TermView()
delete fDefaultState; delete fDefaultState;
delete fSelectState; delete fSelectState;
delete fHyperLinkState;
delete fHyperLinkMenuState;
delete fSyncRunner; delete fSyncRunner;
delete fAutoScrollRunner; delete fAutoScrollRunner;
delete fCharClassifier; 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 inline int32
TermView::_LineAt(float y) TermView::_LineAt(float y)
{ {
@@ -582,12 +589,13 @@ TermView::SetTermSize(int rows, int columns, bool notifyShell)
// synchronize the visible text buffer // synchronize the visible text buffer
{ {
BAutolock _(fTextBuffer); TextBufferSyncLocker _(this);
_SynchronizeWithTextBuffer(0, -1); _SynchronizeWithTextBuffer(0, -1);
int32 offset = _LineAt(0); int32 offset = _LineAt(0);
fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset, fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset,
offset + rows + 2); offset + rows + 2);
fVisibleTextBufferChanged = true;
} }
if (notifyShell) if (notifyShell)
@@ -784,7 +792,8 @@ TermView::Copy(BClipboard *clipboard)
return; return;
BString copyStr; BString copyStr;
fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd); fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(),
fSelection.End());
if (clipboard->Lock()) { if (clipboard->Lock()) {
BMessage *clipMsg = NULL; BMessage *clipMsg = NULL;
@@ -934,8 +943,11 @@ TermView::_Deactivate()
//! Draw part of a line in the given view. //! Draw part of a line in the given view.
void void
TermView::_DrawLinePart(int32 x1, int32 y1, uint32 attr, char *buf, 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); inView->SetFont(IS_BOLD(attr) && !fEmulateBold ? &fBoldFont : &fHalfFont);
// Set pen point // Set pen point
@@ -958,9 +970,9 @@ TermView::_DrawLinePart(int32 x1, int32 y1, uint32 attr, char *buf,
if (cursor) { if (cursor) {
rgb_fore = fCursorForeColor; rgb_fore = fCursorForeColor;
rgb_back = fCursorBackColor; rgb_back = fCursorBackColor;
} else if (mouse) { } else if (highlight != NULL) {
rgb_fore = fSelectForeColor; rgb_fore = highlight->Highlighter()->ForegroundColor();
rgb_back = fSelectBackColor; rgb_back = highlight->Highlighter()->BackgroundColor();
} else { } else {
// Reverse attribute(If selected area, don't reverse color). // Reverse attribute(If selected area, don't reverse color).
if (IS_INVERSE(attr)) { 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, if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x,
character, attr) == A_CHAR character, attr) == A_CHAR
&& (fCursorStyle == BLOCK_CURSOR || !cursorVisible)) { && (fCursorStyle == BLOCK_CURSOR || !cursorVisible)) {
@@ -1037,11 +1049,11 @@ TermView::_DrawCursor()
buffer[bytes] = '\0'; buffer[bytes] = '\0';
_DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer, _DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer,
width, selected, cursorVisible, this); width, highlight, cursorVisible, this);
} else { } else {
if (selected) if (highlight != NULL)
SetHighColor(fSelectBackColor); SetHighColor(highlight->Highlighter()->BackgroundColor());
else if (cursorVisible ) else if (cursorVisible)
SetHighColor(fCursorBackColor ); SetHighColor(fCursorBackColor );
else { else {
uint32 count = 0; uint32 count = 0;
@@ -1144,6 +1156,7 @@ void
TermView::AttachedToWindow() TermView::AttachedToWindow()
{ {
fMouseButtons = 0; fMouseButtons = 0;
fModifiers = modifiers();
// update the terminal size because it may have changed while the TermView // update the terminal size because it may have changed while the TermView
// was detached from the window. On such conditions FrameResized was not // was detached from the window. On such conditions FrameResized was not
@@ -1162,7 +1175,7 @@ TermView::AttachedToWindow()
&message, 500000); &message, 500000);
{ {
BAutolock _(fTextBuffer); TextBufferSyncLocker _(this);
fTextBuffer->SetListener(thisMessenger); fTextBuffer->SetListener(thisMessenger);
_SynchronizeWithTextBuffer(0, -1); _SynchronizeWithTextBuffer(0, -1);
} }
@@ -1240,15 +1253,15 @@ TermView::Draw(BRect updateRect)
for (int32 i = k; i <= x2;) { for (int32 i = k; i <= x2;) {
int32 lastColumn = 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 // This will clip lastColumn to the selection start or end
// to ensure the selection is not drawn at the same time as // to ensure the selection is not drawn at the same time as
// something else // something else
int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i, int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i,
lastColumn, buf, attr); lastColumn, buf, attr);
// debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), selected: %d\n", // debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), highlight: %p\n",
// j - firstVisible, i, lastColumn, count, (int)count, buf, insideSelection); // j - firstVisible, i, lastColumn, count, (int)count, buf, highlight);
if (count == 0) { if (count == 0) {
// No chars to draw : we just fill the rectangle with the // No chars to draw : we just fill the rectangle with the
@@ -1258,8 +1271,9 @@ TermView::Draw(BRect updateRect)
fFontWidth * nextColumn - 1, 0); fFontWidth * nextColumn - 1, 0);
rect.bottom = rect.top + fFontHeight - 1; rect.bottom = rect.top + fFontHeight - 1;
rgb_color rgb_back = insideSelection rgb_color rgb_back = highlight != NULL
? fSelectBackColor : fTextBackColor; ? highlight->Highlighter()->BackgroundColor()
: fTextBackColor;
if (fTextBuffer->IsAlternateScreenActive()) { if (fTextBuffer->IsAlternateScreenActive()) {
// alternate screen uses cell attributes // alternate screen uses cell attributes
@@ -1294,7 +1308,7 @@ TermView::Draw(BRect updateRect)
count = FULL_WIDTH; count = FULL_WIDTH;
_DrawLinePart(fFontWidth * i, (int32)_LineOffset(j), _DrawLinePart(fFontWidth * i, (int32)_LineOffset(j),
attr, buf, count, insideSelection, false, this); attr, buf, count, highlight, false, this);
i += count; i += count;
} }
} }
@@ -1365,6 +1379,15 @@ TermView::WindowActivated(bool active)
if (fActive) if (fActive)
_Deactivate(); _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; 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: case B_INPUT_METHOD_EVENT:
{ {
int32 opcode; int32 opcode;
@@ -1677,7 +1709,7 @@ TermView::MessageReceived(BMessage *msg)
break; break;
case MSG_TERMINAL_BUFFER_CHANGED: case MSG_TERMINAL_BUFFER_CHANGED:
{ {
BAutolock _(fTextBuffer); TextBufferSyncLocker _(this);
_SynchronizeWithTextBuffer(0, -1); _SynchronizeWithTextBuffer(0, -1);
break; break;
} }
@@ -1842,7 +1874,7 @@ TermView::ScrollTo(BPoint where)
//debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine); //debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine);
fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine); fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine);
} }
BAutolock _(fTextBuffer); TextBufferSyncLocker _(this);
if (diff < 0) if (diff < 0)
_SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1); _SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1);
else else
@@ -2004,11 +2036,11 @@ TermView::_DoSecondaryMouseDropAction(BMessage* msg)
int32 slash = string.FindLast("/"); int32 slash = string.FindLast("/");
string.Truncate(slash); string.Truncate(slash);
} }
string.CharacterEscape(kEscapeCharacters, '\\'); string.CharacterEscape(kShellEscapeCharacters, '\\');
itemString += string; itemString += string;
break; break;
} }
string.CharacterEscape(kEscapeCharacters, '\\'); string.CharacterEscape(kShellEscapeCharacters, '\\');
itemString += string; itemString += string;
} }
@@ -2035,7 +2067,7 @@ TermView::_DoFileDrop(entry_ref& ref)
BPath path(&ent); BPath path(&ent);
BString string(path.Path()); BString string(path.Path());
string.CharacterEscape(kEscapeCharacters, '\\'); string.CharacterEscape(kShellEscapeCharacters, '\\');
_WritePTY(string.String(), string.Length()); _WritePTY(string.String(), string.Length());
} }
@@ -2099,7 +2131,9 @@ TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
// sync time not passed yet -- keep counting // sync time not passed yet -- keep counting
fScrolledSinceLastSync += linesScrolled; fScrolledSinceLastSync += linesScrolled;
return; return;
} else if (fScrolledSinceLastSync + linesScrolled <= fRows) { }
if (fScrolledSinceLastSync + linesScrolled <= fRows) {
// time's up, but not enough happened // time's up, but not enough happened
delete fSyncRunner; delete fSyncRunner;
fSyncRunner = NULL; fSyncRunner = NULL;
@@ -2111,6 +2145,8 @@ TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
fScrolledSinceLastSync = 0; fScrolledSinceLastSync = 0;
} }
fVisibleTextBufferChanged = true;
// Simple case first -- complete invalidation. // Simple case first -- complete invalidation.
if (info.invalidateAll) { if (info.invalidateAll) {
Invalidate(); Invalidate();
@@ -2194,15 +2230,23 @@ TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop,
fVisibleTextBuffer->ScrollBy(linesScrolled); fVisibleTextBuffer->ScrollBy(linesScrolled);
} }
// move selection // move highlights
if (fSelStart != fSelEnd) { for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
fSelStart.y -= linesScrolled; if (highlight->IsEmpty())
fSelEnd.y -= linesScrolled; continue;
fInitialSelectionStart.y -= linesScrolled;
fInitialSelectionEnd.y -= linesScrolled;
if (fSelStart.y < -historySize) highlight->ScrollRange(linesScrolled);
_Deselect(); 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); info.dirtyBottom);
// clear the selection, if affected // clear the selection, if affected
if (fSelStart != fSelEnd) { if (!fSelection.IsEmpty()) {
// TODO: We're clearing the selection more often than necessary -- // TODO: We're clearing the selection more often than necessary --
// to avoid that, we'd also need to track the x coordinates of the // to avoid that, we'd also need to track the x coordinates of the
// dirty range. // dirty range.
int32 selectionBottom = fSelEnd.x > 0 ? fSelEnd.y : fSelEnd.y - 1; int32 selectionBottom = fSelection.End().x > 0
if (fSelStart.y <= info.dirtyBottom ? fSelection.End().y : fSelection.End().y - 1;
if (fSelection.Start().y <= info.dirtyBottom
&& info.dirtyTop <= selectionBottom) { && info.dirtyTop <= selectionBottom) {
_Deselect(); _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 /*! Write strings to PTY device. If encoding system isn't UTF8, change
encoding to UTF8 before writing PTY. encoding to UTF8 before writing PTY.
*/ */
@@ -2322,9 +2378,8 @@ TermView::MouseDown(BPoint where)
BMessage* currentMessage = Window()->CurrentMessage(); BMessage* currentMessage = Window()->CurrentMessage();
int32 buttons = currentMessage->GetInt32("buttons", 0); int32 buttons = currentMessage->GetInt32("buttons", 0);
int32 modifiers = currentMessage->GetInt32("modifiers", 0);
fActiveState->MouseDown(where, buttons, modifiers); fActiveState->MouseDown(where, buttons, fModifiers);
fMouseButtons = buttons; fMouseButtons = buttons;
fLastClickPoint = where; fLastClickPoint = where;
@@ -2334,7 +2389,7 @@ TermView::MouseDown(BPoint where)
void void
TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message) 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, TermView::_Select(TermPos start, TermPos end, bool inclusive,
bool setInitialSelection) bool setInitialSelection)
{ {
BAutolock _(fTextBuffer); TextBufferSyncLocker _(this);
_SynchronizeWithTextBuffer(0, -1); _SynchronizeWithTextBuffer(0, -1);
@@ -2396,18 +2451,17 @@ TermView::_Select(TermPos start, TermPos end, bool inclusive,
end.x = fColumns; end.x = fColumns;
} }
if (fSelStart != fSelEnd) if (!fSelection.IsEmpty())
_InvalidateTextRange(fSelStart, fSelEnd); _InvalidateTextRange(fSelection.Start(), fSelection.End());
fSelStart = start; fSelection.SetRange(start, end);
fSelEnd = end;
if (setInitialSelection) { if (setInitialSelection) {
fInitialSelectionStart = fSelStart; fInitialSelectionStart = fSelection.Start();
fInitialSelectionEnd = fSelEnd; fInitialSelectionEnd = fSelection.End();
} }
_InvalidateTextRange(fSelStart, fSelEnd); _InvalidateTextRange(fSelection.Start(), fSelection.End());
} }
@@ -2419,8 +2473,8 @@ TermView::_ExtendSelection(TermPos pos, bool inclusive,
if (!useInitialSelection && !_HasSelection()) if (!useInitialSelection && !_HasSelection())
return; return;
TermPos start = fSelStart; TermPos start = fSelection.Start();
TermPos end = fSelEnd; TermPos end = fSelection.End();
if (useInitialSelection) { if (useInitialSelection) {
start = fInitialSelectionStart; start = fInitialSelectionStart;
@@ -2446,22 +2500,17 @@ void
TermView::_Deselect() TermView::_Deselect()
{ {
//debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection()); //debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection());
if (!_HasSelection()) if (_ClearHighlight(&fSelection)) {
return; fInitialSelectionStart.SetTo(0, 0);
fInitialSelectionEnd.SetTo(0, 0);
_InvalidateTextRange(fSelStart, fSelEnd); }
fSelStart.SetTo(0, 0);
fSelEnd.SetTo(0, 0);
fInitialSelectionStart.SetTo(0, 0);
fInitialSelectionEnd.SetTo(0, 0);
} }
bool bool
TermView::_HasSelection() const TermView::_HasSelection() const
{ {
return fSelStart != fSelEnd; return !fSelection.IsEmpty();
} }
@@ -2476,11 +2525,15 @@ TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection)
return; return;
if (extend) { if (extend) {
if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart)) if (start
< (useInitialSelection
? fInitialSelectionStart : fSelection.Start())) {
_ExtendSelection(start, false, useInitialSelection); _ExtendSelection(start, false, useInitialSelection);
else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd)) } else if (end
> (useInitialSelection
? fInitialSelectionEnd : fSelection.End())) {
_ExtendSelection(end, false, useInitialSelection); _ExtendSelection(end, false, useInitialSelection);
else if (useInitialSelection) } else if (useInitialSelection)
_Select(start, end, false, false); _Select(start, end, false, false);
} else } else
_Select(start, end, false, !useInitialSelection); _Select(start, end, false, !useInitialSelection);
@@ -2494,47 +2547,101 @@ TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection)
TermPos end = TermPos(0, start.y + 1); TermPos end = TermPos(0, start.y + 1);
if (extend) { if (extend) {
if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart)) if (start
< (useInitialSelection
? fInitialSelectionStart : fSelection.Start())) {
_ExtendSelection(start, false, useInitialSelection); _ExtendSelection(start, false, useInitialSelection);
else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd)) } else if (end
> (useInitialSelection
? fInitialSelectionEnd : fSelection.End())) {
_ExtendSelection(end, false, useInitialSelection); _ExtendSelection(end, false, useInitialSelection);
else if (useInitialSelection) } else if (useInitialSelection)
_Select(start, end, false, false); _Select(start, end, false, false);
} else } else
_Select(start, end, false, !useInitialSelection); _Select(start, end, false, !useInitialSelection);
} }
bool void
TermView::_CheckSelectedRegion(const TermPos &pos) const 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 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 int32& lastColumn) const
{ {
if (fSelStart == fSelEnd) Highlight* nextHighlight = NULL;
return false;
if (row == fSelStart.y && firstColumn < fSelStart.x for (int32 i = 0; Highlight* highlight = fHighlights.ItemAt(i); i++) {
&& lastColumn >= fSelStart.x) { if (highlight->IsEmpty())
// region starts before the selection, but intersects with it continue;
lastColumn = fSelStart.x - 1;
return false; 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 if (nextHighlight != NULL)
&& lastColumn >= fSelEnd.x) { lastColumn = nextHighlight->Start().x - 1;
// region starts in the selection, but exceeds the end return NULL;
lastColumn = fSelEnd.x - 1;
return true;
}
TermPos pos(firstColumn, row);
return pos >= fSelStart && pos < fSelEnd;
} }
@@ -2560,15 +2667,15 @@ bool
TermView::Find(const BString &str, bool forwardSearch, bool matchCase, TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
bool matchWord) bool matchWord)
{ {
BAutolock _(fTextBuffer); TextBufferSyncLocker _(this);
_SynchronizeWithTextBuffer(0, -1); _SynchronizeWithTextBuffer(0, -1);
TermPos start; TermPos start;
if (_HasSelection()) { if (_HasSelection()) {
if (forwardSearch) if (forwardSearch)
start = fSelEnd; start = fSelection.End();
else else
start = fSelStart; start = fSelection.Start();
} else { } else {
// search from the very beginning/end // search from the very beginning/end
if (forwardSearch) if (forwardSearch)
@@ -2584,7 +2691,7 @@ TermView::Find(const BString &str, bool forwardSearch, bool matchCase,
} }
_Select(matchStart, matchEnd, false, true); _Select(matchStart, matchEnd, false, true);
_ScrollToRange(fSelStart, fSelEnd); _ScrollToRange(fSelection.Start(), fSelection.End());
return true; return true;
} }
@@ -2596,7 +2703,7 @@ TermView::GetSelection(BString &str)
{ {
str.SetTo(""); str.SetTo("");
BAutolock _(fTextBuffer); BAutolock _(fTextBuffer);
fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd); fTextBuffer->GetStringFromRegion(str, fSelection.Start(), fSelection.End());
} }
@@ -2619,17 +2726,18 @@ TermView::InitiateDrag()
BAutolock _(fTextBuffer); BAutolock _(fTextBuffer);
BString copyStr(""); BString copyStr("");
fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd); fTextBuffer->GetStringFromRegion(copyStr, fSelection.Start(),
fSelection.End());
BMessage message(B_MIME_DATA); BMessage message(B_MIME_DATA);
message.AddData("text/plain", B_MIME_TYPE, copyStr.String(), message.AddData("text/plain", B_MIME_TYPE, copyStr.String(),
copyStr.Length()); copyStr.Length());
BPoint start = _ConvertFromTerminal(fSelStart); BPoint start = _ConvertFromTerminal(fSelection.Start());
BPoint end = _ConvertFromTerminal(fSelEnd); BPoint end = _ConvertFromTerminal(fSelection.End());
BRect rect; 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); rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight);
else else
rect.Set(0, start.y, fColumns * fFontWidth, end.y + fFontHeight); 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 #ifdef USE_DEBUG_SNAPSHOTS
void void
+39 -9
View File
@@ -16,10 +16,12 @@
#include <Autolock.h> #include <Autolock.h>
#include <Messenger.h> #include <Messenger.h>
#include <ObjectList.h>
#include <String.h> #include <String.h>
#include <View.h> #include <View.h>
#include "TermPos.h" #include "TermPos.h"
#include "TermViewHighlight.h"
class ActiveProcessInfo; class ActiveProcessInfo;
@@ -30,6 +32,7 @@ class BScrollView;
class BString; class BString;
class BStringView; class BStringView;
class BasicTerminalBuffer; class BasicTerminalBuffer;
class DefaultCharClassifier;
class InlineInput; class InlineInput;
class ResizeWindow; class ResizeWindow;
class ShellInfo; class ShellInfo;
@@ -38,10 +41,14 @@ class TermBuffer;
class TerminalBuffer; class TerminalBuffer;
class Shell; class Shell;
class TermView : public BView {
class TermView : public BView, private TermViewHighlighter {
public: public:
class Listener; class Listener;
typedef TermViewHighlighter Highlighter;
typedef TermViewHighlight Highlight;
public: public:
TermView(BRect frame, TermView(BRect frame,
const ShellParameters& shellParameters, const ShellParameters& shellParameters,
@@ -141,17 +148,29 @@ protected:
const char* property); const char* property);
private: private:
class CharClassifier; class TextBufferSyncLocker;
friend class TextBufferSyncLocker;
class State; class State;
class StandardBaseState; class StandardBaseState;
class DefaultState; class DefaultState;
class SelectState; class SelectState;
class HyperLinkState;
class HyperLinkMenuState;
friend class State; friend class State;
friend class StandardBaseState; friend class StandardBaseState;
friend class DefaultState; friend class DefaultState;
friend class SelectState; friend class SelectState;
friend class HyperLinkState;
friend class HyperLinkMenuState;
typedef BObjectList<Highlight> HighlightList;
private:
// TermViewHighlighter
virtual rgb_color ForegroundColor();
virtual rgb_color BackgroundColor();
private: private:
// point and text offset conversion // point and text offset conversion
@@ -174,8 +193,9 @@ private:
void _SwitchCursorBlinking(bool blinkingOn); void _SwitchCursorBlinking(bool blinkingOn);
void _DrawLinePart(int32 x1, int32 y1, uint32 attr, void _DrawLinePart(int32 x1, int32 y1, uint32 attr,
char* buffer, int32 width, bool mouse, char* buffer, int32 width,
bool cursor, BView* inView); Highlight* highlight, bool cursor,
BView* inView);
void _DrawCursor(); void _DrawCursor();
void _InvalidateTextRange(TermPos start, void _InvalidateTextRange(TermPos start,
TermPos end); TermPos end);
@@ -193,6 +213,7 @@ private:
void _SynchronizeWithTextBuffer( void _SynchronizeWithTextBuffer(
int32 visibleDirtyTop, int32 visibleDirtyTop,
int32 visibleDirtyBottom); int32 visibleDirtyBottom);
void _VisibleTextBufferChanged();
void _WritePTY(const char* text, int32 numBytes); void _WritePTY(const char* text, int32 numBytes);
@@ -209,8 +230,12 @@ private:
void _SelectLine(BPoint where, bool extend, void _SelectLine(BPoint where, bool extend,
bool useInitialSelection); bool useInitialSelection);
bool _CheckSelectedRegion(const TermPos& pos) const; void _AddHighlight(Highlight* highlight);
bool _CheckSelectedRegion(int32 row, void _RemoveHighlight(Highlight* highlight);
bool _ClearHighlight(Highlight* highlight);
Highlight* _CheckHighlightRegion(const TermPos& pos) const;
Highlight* _CheckHighlightRegion(int32 row,
int32 firstColumn, int32& lastColumn) const; int32 firstColumn, int32& lastColumn) const;
void _UpdateSIGWINCH(); void _UpdateSIGWINCH();
@@ -237,7 +262,7 @@ private:
BMessageRunner* fAutoScrollRunner; BMessageRunner* fAutoScrollRunner;
BMessageRunner* fResizeRunner; BMessageRunner* fResizeRunner;
BStringView* fResizeView; BStringView* fResizeView;
CharClassifier* fCharClassifier; DefaultCharClassifier* fCharClassifier;
// Font and Width // Font and Width
BFont fHalfFont; BFont fHalfFont;
@@ -272,6 +297,7 @@ private:
// Object pointer. // Object pointer.
TerminalBuffer* fTextBuffer; TerminalBuffer* fTextBuffer;
BasicTerminalBuffer* fVisibleTextBuffer; BasicTerminalBuffer* fVisibleTextBuffer;
bool fVisibleTextBufferChanged;
BScrollBar* fScrollBar; BScrollBar* fScrollBar;
InlineInput* fInline; InlineInput* fInline;
@@ -297,14 +323,16 @@ private:
bool fConsiderClockedSync; bool fConsiderClockedSync;
// selection // selection
TermPos fSelStart; Highlight fSelection;
TermPos fSelEnd;
TermPos fInitialSelectionStart; TermPos fInitialSelectionStart;
TermPos fInitialSelectionEnd; TermPos fInitialSelectionEnd;
BPoint fLastClickPoint; BPoint fLastClickPoint;
HighlightList fHighlights;
// mouse // mouse
int32 fMouseButtons; int32 fMouseButtons;
int32 fModifiers;
TermPos fPrevPos; TermPos fPrevPos;
bool fReportX10MouseEvent; bool fReportX10MouseEvent;
bool fReportNormalMouseEvent; bool fReportNormalMouseEvent;
@@ -315,6 +343,8 @@ private:
// states // states
DefaultState* fDefaultState; DefaultState* fDefaultState;
SelectState* fSelectState; SelectState* fSelectState;
HyperLinkState* fHyperLinkState;
HyperLinkMenuState* fHyperLinkMenuState;
State* fActiveState; State* fActiveState;
}; };
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2013, Ingo Weinhold, [email protected].
* 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, [email protected].
* 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 "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 <MessageRunner.h>
#include <PopUpMenu.h>
#include <ScrollBar.h> #include <ScrollBar.h>
#include <UTF8.h> #include <UTF8.h>
#include <Window.h> #include <Window.h>
#include "Shell.h" #include "Shell.h"
#include "TermConst.h" #include "TermConst.h"
#include "TerminalBuffer.h"
#include "VTkeymap.h" #include "VTkeymap.h"
#include "VTKeyTbl.h" #include "VTKeyTbl.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Terminal TermView"
// selection granularity // selection granularity
enum { enum {
SELECT_CHARS, SELECT_CHARS,
@@ -36,6 +49,13 @@ enum {
static const uint32 kAutoScroll = 'AScr'; 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 // #pragma mark - State
@@ -71,6 +91,12 @@ TermView::State::MessageReceived(BMessage* message)
} }
void
TermView::State::ModifiersChanged(int32 oldModifiers, int32 modifiers)
{
}
void void
TermView::State::KeyDown(const char* bytes, int32 numBytes) TermView::State::KeyDown(const char* bytes, int32 numBytes)
{ {
@@ -85,7 +111,7 @@ TermView::State::MouseDown(BPoint where, int32 buttons, int32 modifiers)
void void
TermView::State::MouseMoved(BPoint where, uint32 transit, 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 // #pragma mark - StandardBaseState
@@ -107,25 +145,22 @@ TermView::StandardBaseState::StandardBaseState(TermView* view)
bool bool
TermView::StandardBaseState::_StandardMouseMoved(BPoint where) TermView::StandardBaseState::_StandardMouseMoved(BPoint where, int32 modifiers)
{ {
if (!fView->fReportAnyMouseEvent && !fView->fReportButtonMouseEvent) if (!fView->fReportAnyMouseEvent && !fView->fReportButtonMouseEvent)
return false; return false;
int32 modifier;
fView->Window()->CurrentMessage()->FindInt32("modifiers", &modifier);
TermPos clickPos = fView->_ConvertToTerminal(where); TermPos clickPos = fView->_ConvertToTerminal(where);
if (fView->fReportButtonMouseEvent) { if (fView->fReportButtonMouseEvent) {
if (fView->fPrevPos.x != clickPos.x if (fView->fPrevPos.x != clickPos.x
|| fView->fPrevPos.y != clickPos.y) { || fView->fPrevPos.y != clickPos.y) {
fView->_SendMouseEvent(fView->fMouseButtons, modifier, fView->_SendMouseEvent(fView->fMouseButtons, modifiers,
clickPos.x, clickPos.y, true); clickPos.x, clickPos.y, true);
} }
fView->fPrevPos = clickPos; fView->fPrevPos = clickPos;
} else { } else {
fView->_SendMouseEvent(fView->fMouseButtons, modifier, clickPos.x, fView->_SendMouseEvent(fView->fMouseButtons, modifiers, clickPos.x,
clickPos.y, true); clickPos.y, true);
} }
@@ -143,6 +178,13 @@ TermView::DefaultState::DefaultState(TermView* view)
} }
void
TermView::DefaultState::ModifiersChanged(int32 oldModifiers, int32 modifiers)
{
_CheckEnterHyperLinkState(modifiers);
}
void void
TermView::DefaultState::KeyDown(const char* bytes, int32 numBytes) TermView::DefaultState::KeyDown(const char* bytes, int32 numBytes)
{ {
@@ -341,9 +383,32 @@ TermView::DefaultState::MouseDown(BPoint where, int32 buttons, int32 modifiers)
void void
TermView::DefaultState::MouseMoved(BPoint where, uint32 transit, 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()) { if (fView->_HasSelection()) {
TermPos inPos = fView->_ConvertToTerminal(where); TermPos inPos = fView->_ConvertToTerminal(where);
if (fView->_CheckSelectedRegion(inPos)) { if (fView->fSelection.RangeContains(inPos)) {
if (modifiers & B_CONTROL_KEY) { if (modifiers & B_CONTROL_KEY) {
BPoint p; BPoint p;
uint32 bt; uint32 bt;
@@ -448,9 +513,9 @@ TermView::SelectState::MessageReceived(BMessage* message)
void void
TermView::SelectState::MouseMoved(BPoint where, uint32 transit, TermView::SelectState::MouseMoved(BPoint where, uint32 transit,
const BMessage* message) const BMessage* message, int32 modifiers)
{ {
if (_StandardMouseMoved(where)) if (_StandardMouseMoved(where, modifiers))
return; return;
if (fCheckMouseTracking) { 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 #define TERMVIEW_STATES_H
#include "HyperLink.h"
#include "TerminalCharClassifier.h"
#include "TermView.h" #include "TermView.h"
@@ -28,14 +30,20 @@ public:
virtual bool MessageReceived(BMessage* message); virtual bool MessageReceived(BMessage* message);
// returns true, if handled // returns true, if handled
virtual void ModifiersChanged(int32 oldModifiers,
int32 modifiers);
virtual void KeyDown(const char* bytes, int32 numBytes); virtual void KeyDown(const char* bytes, int32 numBytes);
virtual void MouseDown(BPoint where, int32 buttons, virtual void MouseDown(BPoint where, int32 buttons,
int32 modifiers); int32 modifiers);
virtual void MouseMoved(BPoint where, uint32 transit, virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* message); const BMessage* message, int32 modifiers);
virtual void MouseUp(BPoint where, int32 buttons); virtual void MouseUp(BPoint where, int32 buttons);
virtual void WindowActivated(bool active);
virtual void VisibleTextBufferChanged();
protected: protected:
TermView* fView; TermView* fView;
}; };
@@ -46,7 +54,8 @@ public:
StandardBaseState(TermView* view); StandardBaseState(TermView* view);
protected: protected:
bool _StandardMouseMoved(BPoint where); bool _StandardMouseMoved(BPoint where,
int32 modifiers);
}; };
@@ -54,12 +63,20 @@ class TermView::DefaultState : public TermView::StandardBaseState {
public: public:
DefaultState(TermView* view); DefaultState(TermView* view);
virtual void ModifiersChanged(int32 oldModifiers,
int32 modifiers);
virtual void KeyDown(const char* bytes, int32 numBytes); virtual void KeyDown(const char* bytes, int32 numBytes);
virtual void MouseDown(BPoint where, int32 buttons, virtual void MouseDown(BPoint where, int32 buttons,
int32 modifiers); int32 modifiers);
virtual void MouseMoved(BPoint where, uint32 transit, 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 bool MessageReceived(BMessage* message);
virtual void MouseMoved(BPoint where, uint32 transit, virtual void MouseMoved(BPoint where, uint32 transit,
const BMessage* message); const BMessage* message, int32 modifiers);
virtual void MouseUp(BPoint where, int32 buttons); virtual void MouseUp(BPoint where, int32 buttons);
private: 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 #endif // TERMVIEW_STATES_H
+42 -1
View File
@@ -1,11 +1,52 @@
/* /*
* Copyright 2008, Ingo Weinhold, [email protected]. * Copyright 2008-2013, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
#include "TerminalCharClassifier.h" #include "TerminalCharClassifier.h"
#include <ctype.h>
#include <algorithm>
// #pragma mark - TerminalCharClassifier
TerminalCharClassifier::~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, [email protected]. * Copyright 2008-2013, Ingo Weinhold, [email protected].
* Distributed under the terms of the MIT License. * Distributed under the terms of the MIT License.
*/ */
#ifndef TERMINAL_CHAR_CLASSIFIER_H #ifndef TERMINAL_CHAR_CLASSIFIER_H
#define TERMINAL_CHAR_CLASSIFIER_H #define TERMINAL_CHAR_CLASSIFIER_H
#include <vector>
#include "UTF8Char.h"
enum { enum {
CHAR_TYPE_SPACE, CHAR_TYPE_SPACE,
CHAR_TYPE_WORD_CHAR, 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 #endif // TERMINAL_CHAR_CLASSIFIER_H