HaikuDepot TextView: add support for hyperlinks/clikable areas
Specific text spans can be assigned a cursor and BMessage to send when they are clicked. This allows for implementing hyperlinks, specific popup menus, and clickable text of any type. With some extra work it can also be used to implement spell checking suggestions, buttons, and so on. Change-Id: I390e0c44656da76a950c432bdd934bd51af49baf Reviewed-on: https://review.haiku-os.org/c/haiku/+/7130 Reviewed-by: Adrien Destugues <pulkomandy@pulkomandy.tk> Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org>
This commit is contained in:
parent
9cd2a23203
commit
6af1381397
@ -180,6 +180,48 @@ TextDocument::CharacterStyleAt(int32 textOffset) const
|
||||
}
|
||||
|
||||
|
||||
const BMessage*
|
||||
TextDocument::ClickMessageAt(int32 textOffset) const
|
||||
{
|
||||
int32 paragraphOffset;
|
||||
const Paragraph& paragraph = ParagraphAt(textOffset, paragraphOffset);
|
||||
|
||||
textOffset -= paragraphOffset;
|
||||
int32 index;
|
||||
int32 count = paragraph.CountTextSpans();
|
||||
|
||||
for (index = 0; index < count; index++) {
|
||||
const TextSpan& span = paragraph.TextSpanAtIndex(index);
|
||||
if (textOffset - span.CountChars() < 0)
|
||||
return span.ClickMessage();
|
||||
textOffset -= span.CountChars();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
BCursor
|
||||
TextDocument::CursorAt(int32 textOffset) const
|
||||
{
|
||||
int32 paragraphOffset;
|
||||
const Paragraph& paragraph = ParagraphAt(textOffset, paragraphOffset);
|
||||
|
||||
textOffset -= paragraphOffset;
|
||||
int32 index;
|
||||
int32 count = paragraph.CountTextSpans();
|
||||
|
||||
for (index = 0; index < count; index++) {
|
||||
const TextSpan& span = paragraph.TextSpanAtIndex(index);
|
||||
if (textOffset - span.CountChars() < 0)
|
||||
return span.Cursor();
|
||||
textOffset -= span.CountChars();
|
||||
}
|
||||
|
||||
return BCursor((BMessage*)NULL);
|
||||
}
|
||||
|
||||
|
||||
const ParagraphStyle&
|
||||
TextDocument::ParagraphStyleAt(int32 textOffset) const
|
||||
{
|
||||
|
@ -55,6 +55,8 @@ public:
|
||||
// Style access
|
||||
const CharacterStyle& CharacterStyleAt(int32 textOffset) const;
|
||||
const ParagraphStyle& ParagraphStyleAt(int32 textOffset) const;
|
||||
BCursor CursorAt(int32 textOffset) const;
|
||||
const BMessage* ClickMessageAt(int32 textOffset) const;
|
||||
|
||||
int32 CountParagraphs() const;
|
||||
const Paragraph& ParagraphAtIndex(int32 index) const;
|
||||
|
@ -137,14 +137,28 @@ TextDocumentView::MakeFocus(bool focus)
|
||||
void
|
||||
TextDocumentView::MouseDown(BPoint where)
|
||||
{
|
||||
BMessage* currentMessage = NULL;
|
||||
if (Window() != NULL)
|
||||
currentMessage = Window()->CurrentMessage();
|
||||
|
||||
// First of all, check for links and other clickable things
|
||||
bool unused;
|
||||
int32 offset = fTextDocumentLayout.TextOffsetAt(where.x, where.y, unused);
|
||||
const BMessage* message = fTextDocument->ClickMessageAt(offset);
|
||||
if (message != NULL) {
|
||||
BMessage clickMessage(*message);
|
||||
clickMessage.Append(*currentMessage);
|
||||
Invoke(&clickMessage);
|
||||
}
|
||||
|
||||
if (!fSelectionEnabled)
|
||||
return;
|
||||
|
||||
MakeFocus();
|
||||
|
||||
int32 modifiers = 0;
|
||||
if (Window() != NULL && Window()->CurrentMessage() != NULL)
|
||||
Window()->CurrentMessage()->FindInt32("modifiers", &modifiers);
|
||||
if (currentMessage != NULL)
|
||||
currentMessage->FindInt32("modifiers", &modifiers);
|
||||
|
||||
fMouseDown = true;
|
||||
SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
|
||||
@ -165,11 +179,22 @@ void
|
||||
TextDocumentView::MouseMoved(BPoint where, uint32 transit,
|
||||
const BMessage* dragMessage)
|
||||
{
|
||||
BCursor cursor(B_CURSOR_ID_I_BEAM);
|
||||
|
||||
if (transit != B_EXITED_VIEW) {
|
||||
bool unused;
|
||||
int32 offset = fTextDocumentLayout.TextOffsetAt(where.x, where.y, unused);
|
||||
const BCursor& newCursor = fTextDocument->CursorAt(offset);
|
||||
if (newCursor.InitCheck() == B_OK) {
|
||||
cursor = newCursor;
|
||||
SetViewCursor(&cursor);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fSelectionEnabled)
|
||||
return;
|
||||
|
||||
BCursor iBeamCursor(B_CURSOR_ID_I_BEAM);
|
||||
SetViewCursor(&iBeamCursor);
|
||||
SetViewCursor(&cursor);
|
||||
|
||||
if (fMouseDown)
|
||||
SetCaret(where, true);
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef TEXT_DOCUMENT_VIEW_H
|
||||
#define TEXT_DOCUMENT_VIEW_H
|
||||
|
||||
#include <Invoker.h>
|
||||
#include <String.h>
|
||||
#include <View.h>
|
||||
|
||||
@ -17,7 +18,7 @@ class BClipboard;
|
||||
class BMessageRunner;
|
||||
|
||||
|
||||
class TextDocumentView : public BView {
|
||||
class TextDocumentView : public BView, public BInvoker {
|
||||
public:
|
||||
TextDocumentView(const char* name = NULL);
|
||||
virtual ~TextDocumentView();
|
||||
|
@ -10,7 +10,9 @@ TextSpan::TextSpan()
|
||||
:
|
||||
fText(),
|
||||
fCharCount(0),
|
||||
fStyle()
|
||||
fStyle(),
|
||||
fCursor((BMessage*)NULL),
|
||||
fClickMessage()
|
||||
{
|
||||
}
|
||||
|
||||
@ -19,7 +21,9 @@ TextSpan::TextSpan(const BString& text, const CharacterStyle& style)
|
||||
:
|
||||
fText(text),
|
||||
fCharCount(text.CountChars()),
|
||||
fStyle(style)
|
||||
fStyle(style),
|
||||
fCursor((BMessage*)NULL),
|
||||
fClickMessage()
|
||||
{
|
||||
}
|
||||
|
||||
@ -28,7 +32,9 @@ TextSpan::TextSpan(const TextSpan& other)
|
||||
:
|
||||
fText(other.fText),
|
||||
fCharCount(other.fCharCount),
|
||||
fStyle(other.fStyle)
|
||||
fStyle(other.fStyle),
|
||||
fCursor(other.fCursor),
|
||||
fClickMessage(other.fClickMessage)
|
||||
{
|
||||
}
|
||||
|
||||
@ -39,6 +45,8 @@ TextSpan::operator=(const TextSpan& other)
|
||||
fText = other.fText;
|
||||
fCharCount = other.fCharCount;
|
||||
fStyle = other.fStyle;
|
||||
fCursor = other.fCursor;
|
||||
fClickMessage = other.fClickMessage;
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -49,7 +57,10 @@ TextSpan::operator==(const TextSpan& other) const
|
||||
{
|
||||
return fCharCount == other.fCharCount
|
||||
&& fStyle == other.fStyle
|
||||
&& fText == other.fText;
|
||||
&& fText == other.fText
|
||||
&& fCursor == other.fCursor
|
||||
&& fClickMessage.what == other.fClickMessage.what
|
||||
&& fClickMessage.HasSameData(other.fClickMessage);
|
||||
}
|
||||
|
||||
|
||||
@ -75,6 +86,20 @@ TextSpan::SetStyle(const CharacterStyle& style)
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TextSpan::SetCursor(const BCursor& cursor)
|
||||
{
|
||||
fCursor = cursor;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TextSpan::SetClickMessage(BMessage* message)
|
||||
{
|
||||
fClickMessage = *message;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
TextSpan::Append(const BString& text)
|
||||
{
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define TEXT_SPAN_H
|
||||
|
||||
|
||||
#include <Cursor.h>
|
||||
#include <String.h>
|
||||
|
||||
#include "CharacterStyle.h"
|
||||
@ -39,6 +40,12 @@ public:
|
||||
|
||||
TextSpan SubSpan(int32 start, int32 count) const;
|
||||
|
||||
void SetCursor(const BCursor& cursor);
|
||||
inline const BCursor& Cursor() const
|
||||
{ return fCursor; }
|
||||
void SetClickMessage(BMessage* message);
|
||||
inline const BMessage* ClickMessage() const
|
||||
{ return fClickMessage.IsEmpty() ? NULL : &fClickMessage; }
|
||||
private:
|
||||
void _TruncateInsert(int32& start) const;
|
||||
void _TruncateRemove(int32& start,
|
||||
@ -48,6 +55,9 @@ private:
|
||||
BString fText;
|
||||
int32 fCharCount;
|
||||
CharacterStyle fStyle;
|
||||
|
||||
BCursor fCursor;
|
||||
BMessage fClickMessage;
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user