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