xref: /haiku/src/apps/aboutsystem/HyperTextView.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
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 
19 HyperTextAction::HyperTextAction()
20 {
21 }
22 
23 
24 HyperTextAction::~HyperTextAction()
25 {
26 }
27 
28 
29 void
30 HyperTextAction::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 
43 void
44 HyperTextAction::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 
57 void
58 HyperTextAction::Clicked(HyperTextView* view, BPoint where, BMessage* message)
59 {
60 }
61 
62 
63 // #pragma mark - HyperTextView
64 
65 
66 struct 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 
100 class HyperTextView::ActionInfoList
101 	: public BObjectList<HyperTextView::ActionInfo> {
102 public:
103 	ActionInfoList(int32 itemsPerBlock = 20, bool owning = false)
104 		: BObjectList<HyperTextView::ActionInfo>(itemsPerBlock, owning)
105 	{
106 	}
107 };
108 
109 
110 HyperTextView::HyperTextView(const char* name, uint32 flags)
111 	:
112 	BTextView(name, flags),
113 	fActionInfos(new ActionInfoList(100, true)),
114 	fLastActionInfo(NULL)
115 {
116 }
117 
118 
119 HyperTextView::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 
129 HyperTextView::~HyperTextView()
130 {
131 	delete fActionInfos;
132 }
133 
134 
135 void
136 HyperTextView::MouseDown(BPoint where)
137 {
138 	// We eat all mouse button events.
139 
140 	BTextView::MouseDown(where);
141 }
142 
143 
144 void
145 HyperTextView::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 
157 void
158 HyperTextView::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 
197 void
198 HyperTextView::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 
213 void
214 HyperTextView::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 
225 void
226 HyperTextView::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 
237 const HyperTextView::ActionInfo*
238 HyperTextView::_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 
250 HyperTextAction*
251 HyperTextView::_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