1/*
2 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT license.
4 */
5
6#include "HyperTextView.h"
7
8#include <Cursor.h>
9#include <Message.h>
10#include <Region.h>
11#include <Window.h>
12
13#include <ObjectList.h>
14
15
16// #pragma mark - HyperTextAction
17
18
19HyperTextAction::HyperTextAction()
20{
21}
22
23
24HyperTextAction::~HyperTextAction()
25{
26}
27
28
29void
30HyperTextAction::MouseOver(HyperTextView* view, BPoint where, int32 startOffset,
31	int32 endOffset, BMessage* message)
32{
33	BCursor linkCursor(B_CURSOR_ID_FOLLOW_LINK);
34	view->SetViewCursor(&linkCursor);
35
36	BFont font;
37	view->GetFont(&font);
38	font.SetFace(B_UNDERSCORE_FACE);
39	view->SetFontAndColor(startOffset, endOffset, &font, B_FONT_FACE);
40}
41
42
43void
44HyperTextAction::MouseAway(HyperTextView* view, BPoint where, int32 startOffset,
45	int32 endOffset, BMessage* message)
46{
47	BCursor linkCursor(B_CURSOR_ID_SYSTEM_DEFAULT);
48	view->SetViewCursor(&linkCursor);
49
50	BFont font;
51	view->GetFont(&font);
52	font.SetFace(B_REGULAR_FACE);
53	view->SetFontAndColor(startOffset, endOffset, &font, B_FONT_FACE);
54}
55
56
57void
58HyperTextAction::Clicked(HyperTextView* view, BPoint where, BMessage* message)
59{
60}
61
62
63// #pragma mark - HyperTextView
64
65
66struct HyperTextView::ActionInfo {
67	ActionInfo(int32 startOffset, int32 endOffset, HyperTextAction* action)
68		:
69		startOffset(startOffset),
70		endOffset(endOffset),
71		action(action)
72	{
73	}
74
75	~ActionInfo()
76	{
77		delete action;
78	}
79
80	static int Compare(const ActionInfo* a, const ActionInfo* b)
81	{
82		return a->startOffset - b->startOffset;
83	}
84
85	static int CompareEqualIfIntersecting(const ActionInfo* a,
86		const ActionInfo* b)
87	{
88		if (a->startOffset < b->endOffset && b->startOffset < a->endOffset)
89			return 0;
90		return a->startOffset - b->startOffset;
91	}
92
93	int32				startOffset;
94	int32				endOffset;
95	HyperTextAction*	action;
96};
97
98
99
100class HyperTextView::ActionInfoList
101	: public BObjectList<HyperTextView::ActionInfo> {
102public:
103	ActionInfoList(int32 itemsPerBlock = 20, bool owning = false)
104		: BObjectList<HyperTextView::ActionInfo>(itemsPerBlock, owning)
105	{
106	}
107};
108
109
110HyperTextView::HyperTextView(const char* name, uint32 flags)
111	:
112	BTextView(name, flags),
113	fActionInfos(new ActionInfoList(100, true)),
114	fLastActionInfo(NULL)
115{
116}
117
118
119HyperTextView::HyperTextView(BRect frame, const char* name, BRect textRect,
120	uint32 resizeMask, uint32 flags)
121	:
122	BTextView(frame, name, textRect, resizeMask, flags),
123	fActionInfos(new ActionInfoList(100, true)),
124	fLastActionInfo(NULL)
125{
126}
127
128
129HyperTextView::~HyperTextView()
130{
131	delete fActionInfos;
132}
133
134
135void
136HyperTextView::MouseDown(BPoint where)
137{
138	// We eat all mouse button events.
139
140	BTextView::MouseDown(where);
141}
142
143
144void
145HyperTextView::MouseUp(BPoint where)
146{
147	BMessage* message = Window()->CurrentMessage();
148
149	HyperTextAction* action = _ActionAt(where);
150	if (action != NULL)
151		action->Clicked(this, where, message);
152
153	BTextView::MouseUp(where);
154}
155
156
157void
158HyperTextView::MouseMoved(BPoint where, uint32 transit,
159	const BMessage* dragMessage)
160{
161	BMessage* message = Window()->CurrentMessage();
162
163	HyperTextAction* action;
164	const ActionInfo* actionInfo = _ActionInfoAt(where);
165	if (actionInfo != fLastActionInfo) {
166		// We moved to a different "action" zone, de-highlight the previous one
167		if (fLastActionInfo != NULL) {
168			action = fLastActionInfo->action;
169			if (action != NULL) {
170				action->MouseAway(this, where, fLastActionInfo->startOffset,
171						fLastActionInfo->endOffset, message);
172			}
173		}
174
175		// ... and highlight the new one
176		if (actionInfo != NULL) {
177			action = actionInfo->action;
178			if (action != NULL) {
179				action->MouseOver(this, where, actionInfo->startOffset,
180						actionInfo->endOffset, message);
181			}
182		}
183
184		fLastActionInfo = actionInfo;
185	}
186
187	int32 buttons = 0;
188	message->FindInt32("buttons", (int32*)&buttons);
189	if (actionInfo == NULL || buttons != 0) {
190		// This will restore the default mouse pointer, so do it only when not
191		// hovering a link, or when clicking
192		BTextView::MouseMoved(where, transit, dragMessage);
193	}
194}
195
196
197void
198HyperTextView::AddHyperTextAction(int32 startOffset, int32 endOffset,
199	HyperTextAction* action)
200{
201	if (action == NULL || startOffset >= endOffset) {
202		delete action;
203		return;
204	}
205
206	fActionInfos->BinaryInsert(new ActionInfo(startOffset, endOffset, action),
207		ActionInfo::Compare);
208
209	// TODO: Of course we should check for overlaps...
210}
211
212
213void
214HyperTextView::InsertHyperText(const char* inText, HyperTextAction* action,
215	const text_run_array* inRuns)
216{
217	int32 startOffset = TextLength();
218	Insert(inText, inRuns);
219	int32 endOffset = TextLength();
220
221	AddHyperTextAction(startOffset, endOffset, action);
222}
223
224
225void
226HyperTextView::InsertHyperText(const char* inText, int32 inLength,
227	HyperTextAction* action, const text_run_array* inRuns)
228{
229	int32 startOffset = TextLength();
230	Insert(inText, inLength, inRuns);
231	int32 endOffset = TextLength();
232
233	AddHyperTextAction(startOffset, endOffset, action);
234}
235
236
237const HyperTextView::ActionInfo*
238HyperTextView::_ActionInfoAt(const BPoint& where) const
239{
240	int32 offset = OffsetAt(where);
241
242	ActionInfo pointer(offset, offset + 1, NULL);
243
244	const ActionInfo* action = fActionInfos->BinarySearch(pointer,
245			ActionInfo::CompareEqualIfIntersecting);
246	return action;
247}
248
249
250HyperTextAction*
251HyperTextView::_ActionAt(const BPoint& where) const
252{
253	const ActionInfo* action = _ActionInfoAt(where);
254
255	if (action != NULL) {
256		// verify that the text region was hit
257		BRegion textRegion;
258		GetTextRegion(action->startOffset, action->endOffset, &textRegion);
259		if (textRegion.Contains(where))
260			return action->action;
261	}
262
263	return NULL;
264}
265
266