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