diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-06 22:45:15 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-06 22:45:15 +0300 |
commit | a364d9456dfdfd8181904fca6308e9c36eefd10a (patch) | |
tree | f355ded227cf52053784b991f5d8441a5502e447 /src/ui/lookupwidget.c | |
parent | 52a1652536e4e27751ac121009f85113e72afe7d (diff) |
LookupWidget: Keyboard focus and cursor
Diffstat (limited to 'src/ui/lookupwidget.c')
-rw-r--r-- | src/ui/lookupwidget.c | 374 |
1 files changed, 362 insertions, 12 deletions
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index d2550c16..fbb5d365 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -23,19 +23,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #include "lookupwidget.h" | 23 | #include "lookupwidget.h" |
24 | #include "lookup.h" | 24 | #include "lookup.h" |
25 | #include "listwidget.h" | 25 | #include "listwidget.h" |
26 | #include "inputwidget.h" | ||
27 | #include "util.h" | ||
28 | #include "command.h" | ||
29 | #include "bookmarks.h" | ||
30 | #include "gmutil.h" | ||
31 | #include "app.h" | ||
26 | 32 | ||
27 | #include <the_Foundation/mutex.h> | 33 | #include <the_Foundation/mutex.h> |
28 | #include <the_Foundation/thread.h> | 34 | #include <the_Foundation/thread.h> |
35 | #include <the_Foundation/regexp.h> | ||
29 | 36 | ||
30 | iDeclareType(LookupJob) | 37 | iDeclareType(LookupJob) |
31 | 38 | ||
32 | struct Impl_LookupJob { | 39 | struct Impl_LookupJob { |
33 | iString term; | 40 | iRegExp *term; |
34 | iPtrArray results; | 41 | iPtrArray results; |
35 | }; | 42 | }; |
36 | 43 | ||
37 | static void init_LookupJob(iLookupJob *d) { | 44 | static void init_LookupJob(iLookupJob *d) { |
38 | init_String(&d->term); | 45 | d->term = NULL; |
39 | init_PtrArray(&d->results); | 46 | init_PtrArray(&d->results); |
40 | } | 47 | } |
41 | 48 | ||
@@ -44,23 +51,124 @@ static void deinit_LookupJob(iLookupJob *d) { | |||
44 | delete_LookupResult(i.ptr); | 51 | delete_LookupResult(i.ptr); |
45 | } | 52 | } |
46 | deinit_PtrArray(&d->results); | 53 | deinit_PtrArray(&d->results); |
47 | deinit_String(&d->term); | 54 | iRelease(d->term); |
48 | } | 55 | } |
49 | 56 | ||
50 | iDefineTypeConstruction(LookupJob) | 57 | iDefineTypeConstruction(LookupJob) |
51 | 58 | ||
59 | /*----------------------------------------------------------------------------------------------*/ | ||
60 | |||
61 | iDeclareType(LookupItem) | ||
62 | typedef iListItemClass iLookupItemClass; | ||
63 | |||
64 | struct Impl_LookupItem { | ||
65 | iListItem listItem; | ||
66 | iLookupResult *result; | ||
67 | int font; | ||
68 | int fg; | ||
69 | iString text; | ||
70 | iString command; | ||
71 | }; | ||
72 | |||
73 | static void init_LookupItem(iLookupItem *d, const iLookupResult *res) { | ||
74 | init_ListItem(&d->listItem); | ||
75 | d->result = res ? copy_LookupResult(res) : NULL; | ||
76 | d->font = uiContent_FontId; | ||
77 | d->fg = uiText_ColorId; | ||
78 | init_String(&d->text); | ||
79 | init_String(&d->command); | ||
80 | } | ||
81 | |||
82 | static void deinit_LookupItem(iLookupItem *d) { | ||
83 | deinit_String(&d->command); | ||
84 | deinit_String(&d->text); | ||
85 | delete_LookupResult(d->result); | ||
86 | } | ||
87 | |||
88 | static void draw_LookupItem_(iLookupItem *d, iPaint *p, iRect rect, const iListWidget *list) { | ||
89 | const iBool isPressing = isMouseDown_ListWidget(list); | ||
90 | const iBool isHover = isHover_Widget(list) && constHoverItem_ListWidget(list) == d; | ||
91 | const iBool isCursor = d->listItem.isSelected; | ||
92 | if (isHover || isCursor) { | ||
93 | fillRect_Paint(p, | ||
94 | rect, | ||
95 | isPressing || isCursor ? uiBackgroundPressed_ColorId | ||
96 | : uiBackgroundFramelessHover_ColorId); | ||
97 | } | ||
98 | int fg = isHover || isCursor | ||
99 | ? permanent_ColorId | (isPressing || isCursor ? uiTextPressed_ColorId | ||
100 | : uiTextFramelessHover_ColorId) | ||
101 | : d->fg; | ||
102 | const iInt2 size = measure_Text(d->font, cstr_String(&d->text)); | ||
103 | iInt2 pos = init_I2(left_Rect(rect) + 3 * gap_UI, mid_Rect(rect).y - size.y / 2); | ||
104 | if (d->listItem.isSeparator) { | ||
105 | pos.y = bottom_Rect(rect) - lineHeight_Text(d->font) - gap_UI; | ||
106 | } | ||
107 | drawRange_Text(d->font, pos, fg, range_String(&d->text)); | ||
108 | } | ||
109 | |||
110 | iBeginDefineSubclass(LookupItem, ListItem) | ||
111 | .draw = (iAny *) draw_LookupItem_, | ||
112 | iEndDefineSubclass(LookupItem) | ||
113 | |||
114 | iDefineObjectConstructionArgs(LookupItem, (const iLookupResult *res), res) | ||
115 | |||
116 | /*----------------------------------------------------------------------------------------------*/ | ||
117 | |||
52 | struct Impl_LookupWidget { | 118 | struct Impl_LookupWidget { |
53 | iWidget widget; | 119 | iWidget widget; |
54 | iListWidget *list; | 120 | iListWidget *list; |
55 | iThread *work; | 121 | size_t cursor; |
56 | iCondition jobAvailable; /* wakes up the work thread */ | 122 | iThread * work; |
57 | iMutex *mtx; | 123 | iCondition jobAvailable; /* wakes up the work thread */ |
58 | iString nextJob; | 124 | iMutex * mtx; |
59 | iLookupJob *finishedJob; | 125 | iString nextJob; |
126 | iLookupJob * finishedJob; | ||
60 | }; | 127 | }; |
61 | 128 | ||
129 | static float scoreMatch_(const iRegExp *pattern, iRangecc text) { | ||
130 | float score = 0.0f; | ||
131 | iRegExpMatch m; | ||
132 | init_RegExpMatch(&m); | ||
133 | while (matchRange_RegExp(pattern, text, &m)) { | ||
134 | /* Match near the beginning is scored higher. */ | ||
135 | score += (float) size_Range(&m.range) / ((float) m.range.start + 1); | ||
136 | } | ||
137 | return score; | ||
138 | } | ||
139 | |||
140 | static float bookmarkRelevance_LookupJob_(const iLookupJob *d, const iBookmark *bm) { | ||
141 | iUrl parts; | ||
142 | init_Url(&parts, &bm->url); | ||
143 | const float t = scoreMatch_(d->term, range_String(&bm->title)); | ||
144 | const float h = scoreMatch_(d->term, parts.host); | ||
145 | const float p = scoreMatch_(d->term, parts.path); | ||
146 | const float g = scoreMatch_(d->term, range_String(&bm->tags)); | ||
147 | return h + iMax(p, t) + 2 * g; /* extra weight for tags */ | ||
148 | } | ||
149 | |||
150 | static iBool matchBookmark_LookupJob_(void *context, const iBookmark *bm) { | ||
151 | return bookmarkRelevance_LookupJob_(context, bm) > 0; | ||
152 | } | ||
153 | |||
154 | static void searchBookmarks_LookupJob_(iLookupJob *d) { | ||
155 | /* Note: Called in a background thread. */ | ||
156 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), NULL, matchBookmark_LookupJob_, d)) { | ||
157 | const iBookmark *bm = i.ptr; | ||
158 | iLookupResult * res = new_LookupResult(); | ||
159 | res->type = bookmark_LookupResultType; | ||
160 | res->relevance = bookmarkRelevance_LookupJob_(d, bm); | ||
161 | set_String(&res->label, &bm->title); | ||
162 | // appendFormat_String(&res->label, " (%f)", res->relevance); | ||
163 | set_String(&res->url, &bm->url); | ||
164 | res->when = bm->when; | ||
165 | pushBack_PtrArray(&d->results, res); | ||
166 | } | ||
167 | } | ||
168 | |||
62 | static iThreadResult worker_LookupWidget_(iThread *thread) { | 169 | static iThreadResult worker_LookupWidget_(iThread *thread) { |
63 | iLookupWidget *d = userData_Thread(thread); | 170 | iLookupWidget *d = userData_Thread(thread); |
171 | printf("[LookupWidget] worker is running\n"); fflush(stdout); | ||
64 | lock_Mutex(d->mtx); | 172 | lock_Mutex(d->mtx); |
65 | for (;;) { | 173 | for (;;) { |
66 | wait_Condition(&d->jobAvailable, d->mtx); | 174 | wait_Condition(&d->jobAvailable, d->mtx); |
@@ -68,11 +176,31 @@ static iThreadResult worker_LookupWidget_(iThread *thread) { | |||
68 | break; /* Time to quit. */ | 176 | break; /* Time to quit. */ |
69 | } | 177 | } |
70 | iLookupJob *job = new_LookupJob(); | 178 | iLookupJob *job = new_LookupJob(); |
71 | set_String(&job->term, &d->nextJob); | 179 | /* Make a regular expression to search for multiple alternative words. */ { |
180 | iString *pattern = new_String(); | ||
181 | iRangecc word = iNullRange; | ||
182 | iBool isFirst = iTrue; | ||
183 | while (nextSplit_Rangecc(range_String(&d->nextJob), " ", &word)) { | ||
184 | if (isEmpty_Range(&word)) continue; | ||
185 | if (!isFirst) appendChar_String(pattern, '|'); | ||
186 | for (const char *ch = word.start; ch != word.end; ch++) { | ||
187 | /* Escape regular expression characters. */ | ||
188 | if (isSyntaxChar_RegExp(*ch)) { | ||
189 | appendChar_String(pattern, '\\'); | ||
190 | } | ||
191 | appendChar_String(pattern, *ch); | ||
192 | } | ||
193 | isFirst = iFalse; | ||
194 | } | ||
195 | iAssert(!isEmpty_String(pattern)); | ||
196 | // printf("{%s}\n", cstr_String(pattern)); | ||
197 | job->term = new_RegExp(cstr_String(pattern), caseInsensitive_RegExpOption); | ||
198 | delete_String(pattern); | ||
199 | } | ||
72 | clear_String(&d->nextJob); | 200 | clear_String(&d->nextJob); |
73 | unlock_Mutex(d->mtx); | 201 | unlock_Mutex(d->mtx); |
74 | /* Do the lookup. */ { | 202 | /* Do the lookup. */ { |
75 | 203 | searchBookmarks_LookupJob_(job); | |
76 | } | 204 | } |
77 | /* Submit the result. */ | 205 | /* Submit the result. */ |
78 | lock_Mutex(d->mtx); | 206 | lock_Mutex(d->mtx); |
@@ -80,7 +208,10 @@ static iThreadResult worker_LookupWidget_(iThread *thread) { | |||
80 | /* Previous results haven't been taken yet. */ | 208 | /* Previous results haven't been taken yet. */ |
81 | delete_LookupJob(d->finishedJob); | 209 | delete_LookupJob(d->finishedJob); |
82 | } | 210 | } |
211 | printf("[LookupWidget] worker has %zu results\n", size_PtrArray(&job->results)); | ||
212 | fflush(stdout); | ||
83 | d->finishedJob = job; | 213 | d->finishedJob = job; |
214 | postCommand_Widget(as_Widget(d), "lookup.ready"); | ||
84 | } | 215 | } |
85 | unlock_Mutex(d->mtx); | 216 | unlock_Mutex(d->mtx); |
86 | printf("[LookupWidget] worker has quit\n"); fflush(stdout); | 217 | printf("[LookupWidget] worker has quit\n"); fflush(stdout); |
@@ -93,14 +224,17 @@ void init_LookupWidget(iLookupWidget *d) { | |||
93 | iWidget *w = as_Widget(d); | 224 | iWidget *w = as_Widget(d); |
94 | init_Widget(w); | 225 | init_Widget(w); |
95 | setId_Widget(w, "lookup"); | 226 | setId_Widget(w, "lookup"); |
96 | setFlags_Widget(w, resizeChildren_WidgetFlag, iTrue); | 227 | setFlags_Widget(w, focusable_WidgetFlag | resizeChildren_WidgetFlag, iTrue); |
97 | d->list = addChild_Widget(w, iClob(new_ListWidget())); | 228 | d->list = addChild_Widget(w, iClob(new_ListWidget())); |
229 | setItemHeight_ListWidget(d->list, lineHeight_Text(default_FontId) * 2); | ||
230 | d->cursor = iInvalidPos; | ||
98 | d->work = new_Thread(worker_LookupWidget_); | 231 | d->work = new_Thread(worker_LookupWidget_); |
99 | setUserData_Thread(d->work, d); | 232 | setUserData_Thread(d->work, d); |
100 | init_Condition(&d->jobAvailable); | 233 | init_Condition(&d->jobAvailable); |
101 | d->mtx = new_Mutex(); | 234 | d->mtx = new_Mutex(); |
102 | init_String(&d->nextJob); | 235 | init_String(&d->nextJob); |
103 | d->finishedJob = NULL; | 236 | d->finishedJob = NULL; |
237 | start_Thread(d->work); | ||
104 | } | 238 | } |
105 | 239 | ||
106 | void deinit_LookupWidget(iLookupWidget *d) { | 240 | void deinit_LookupWidget(iLookupWidget *d) { |
@@ -118,13 +252,229 @@ void deinit_LookupWidget(iLookupWidget *d) { | |||
118 | deinit_Condition(&d->jobAvailable); | 252 | deinit_Condition(&d->jobAvailable); |
119 | } | 253 | } |
120 | 254 | ||
255 | void submit_LookupWidget(iLookupWidget *d, const iString *term) { | ||
256 | iGuardMutex(d->mtx, { | ||
257 | set_String(&d->nextJob, term); | ||
258 | trim_String(&d->nextJob); | ||
259 | if (!isEmpty_String(&d->nextJob)) { | ||
260 | signal_Condition(&d->jobAvailable); | ||
261 | } | ||
262 | else { | ||
263 | setFlags_Widget(as_Widget(d), hidden_WidgetFlag, iTrue); | ||
264 | } | ||
265 | }); | ||
266 | } | ||
267 | |||
121 | static void draw_LookupWidget_(const iLookupWidget *d) { | 268 | static void draw_LookupWidget_(const iLookupWidget *d) { |
122 | const iWidget *w = constAs_Widget(d); | 269 | const iWidget *w = constAs_Widget(d); |
123 | draw_Widget(w); | 270 | draw_Widget(w); |
271 | /* Draw a frame. */ { | ||
272 | iPaint p; | ||
273 | init_Paint(&p); | ||
274 | drawRect_Paint(&p, | ||
275 | bounds_Widget(w), | ||
276 | isFocused_Widget(w) ? uiInputFrameFocused_ColorId : uiSeparator_ColorId); | ||
277 | } | ||
278 | } | ||
279 | |||
280 | static int cmpPtr_LookupResult_(const void *p1, const void *p2) { | ||
281 | const iLookupResult *a = *(const iLookupResult **) p1; | ||
282 | const iLookupResult *b = *(const iLookupResult **) p2; | ||
283 | if (a->type != b->type) { | ||
284 | return iCmp(a->type, b->type); | ||
285 | } | ||
286 | if (fabsf(a->relevance - b->relevance) < 0.0001f) { | ||
287 | return cmpString_String(&a->url, &b->url); | ||
288 | } | ||
289 | return -iCmp(a->relevance, b->relevance); | ||
290 | } | ||
291 | |||
292 | static const char *cstr_LookupResultType(enum iLookupResultType d) { | ||
293 | switch (d) { | ||
294 | case bookmark_LookupResultType: | ||
295 | return "BOOKMARKS"; | ||
296 | case history_LookupResultType: | ||
297 | return "HISTORY"; | ||
298 | case content_LookupResultType: | ||
299 | return "PAGE CONTENTS"; | ||
300 | case identity_LookupResultType: | ||
301 | return "IDENTITIES"; | ||
302 | default: | ||
303 | return "OTHER"; | ||
304 | } | ||
305 | } | ||
306 | |||
307 | static void presentResults_LookupWidget_(iLookupWidget *d) { | ||
308 | iLookupJob *job; | ||
309 | iGuardMutex(d->mtx, { | ||
310 | job = d->finishedJob; | ||
311 | d->finishedJob = NULL; | ||
312 | }); | ||
313 | if (!job) return; | ||
314 | clear_ListWidget(d->list); | ||
315 | sort_Array(&job->results, cmpPtr_LookupResult_); | ||
316 | enum iLookupResultType lastType = none_LookupResultType; | ||
317 | iConstForEach(PtrArray, i, &job->results) { | ||
318 | const iLookupResult *res = i.ptr; | ||
319 | if (lastType != res->type) { | ||
320 | /* Heading separator. */ | ||
321 | iLookupItem *item = new_LookupItem(NULL); | ||
322 | item->listItem.isSeparator = iTrue; | ||
323 | item->fg = uiHeading_ColorId; | ||
324 | item->font = default_FontId; | ||
325 | format_String(&item->text, "%s", cstr_LookupResultType(res->type)); | ||
326 | addItem_ListWidget(d->list, item); | ||
327 | iRelease(item); | ||
328 | lastType = res->type; | ||
329 | } | ||
330 | iLookupItem *item = new_LookupItem(res); | ||
331 | switch (res->type) { | ||
332 | case bookmark_LookupResultType: { | ||
333 | item->fg = uiTextStrong_ColorId; | ||
334 | item->font = default_FontId; | ||
335 | const char *url = cstr_String(&res->url); | ||
336 | if (startsWithCase_String(&res->url, "gemini://")) { | ||
337 | url += 9; | ||
338 | } | ||
339 | format_String(&item->text, "%s\n%s Open %s", cstr_String(&res->label), | ||
340 | uiText_ColorEscape, url); | ||
341 | format_String(&item->command, "open url:%s", cstr_String(&res->url)); | ||
342 | break; | ||
343 | } | ||
344 | } | ||
345 | addItem_ListWidget(d->list, item); | ||
346 | iRelease(item); | ||
347 | } | ||
348 | delete_LookupJob(job); | ||
349 | /* Re-select the item at the cursor. */ | ||
350 | if (d->cursor != iInvalidPos) { | ||
351 | d->cursor = iMin(d->cursor, numItems_ListWidget(d->list) - 1); | ||
352 | ((iListItem *) item_ListWidget(d->list, d->cursor))->isSelected = iTrue; | ||
353 | } | ||
354 | updateVisible_ListWidget(d->list); | ||
355 | invalidate_ListWidget(d->list); | ||
356 | setFlags_Widget(as_Widget(d), hidden_WidgetFlag, numItems_ListWidget(d->list) == 0); | ||
357 | } | ||
358 | |||
359 | static iLookupItem *item_LookupWidget_(iLookupWidget *d, size_t index) { | ||
360 | return item_ListWidget(d->list, index); | ||
361 | } | ||
362 | |||
363 | static void setCursor_LookupWidget_(iLookupWidget *d, size_t index) { | ||
364 | if (index != d->cursor) { | ||
365 | iLookupItem *item = item_LookupWidget_(d, d->cursor); | ||
366 | if (item) { | ||
367 | item->listItem.isSelected = iFalse; | ||
368 | invalidateItem_ListWidget(d->list, d->cursor); | ||
369 | } | ||
370 | d->cursor = index; | ||
371 | if ((item = item_LookupWidget_(d, d->cursor)) != NULL) { | ||
372 | item->listItem.isSelected = iTrue; | ||
373 | invalidateItem_ListWidget(d->list, d->cursor); | ||
374 | } | ||
375 | } | ||
124 | } | 376 | } |
125 | 377 | ||
126 | static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { | 378 | static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { |
127 | iWidget *w = as_Widget(d); | 379 | iWidget *w = as_Widget(d); |
380 | const char *cmd = command_UserEvent(ev); | ||
381 | // if (ev->type == SDL_MOUSEMOTION && contains_Widget(w, init_I2(ev->motion.x, ev->motion.y))) { | ||
382 | // setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | ||
383 | // } | ||
384 | if (isCommand_Widget(w, ev, "lookup.ready")) { | ||
385 | /* Take the results and present them in the list. */ | ||
386 | presentResults_LookupWidget_(d); | ||
387 | return iTrue; | ||
388 | } | ||
389 | if (isResize_UserEvent(ev)) { | ||
390 | /* Position the lookup popup under the URL bar. */ { | ||
391 | setSize_Widget(w, init_I2(width_Widget(findWidget_App("url")), | ||
392 | get_Window()->root->rect.size.y / 2)); | ||
393 | setPos_Widget(w, bottomLeft_Rect(bounds_Widget(findWidget_App("url")))); | ||
394 | arrange_Widget(w); | ||
395 | } | ||
396 | updateVisible_ListWidget(d->list); | ||
397 | invalidate_ListWidget(d->list); | ||
398 | } | ||
399 | if (equal_Command(cmd, "input.ended") && !cmp_String(string_Command(cmd, "id"), "url") && | ||
400 | !isFocused_Widget(w)) { | ||
401 | setFlags_Widget(w, hidden_WidgetFlag, iTrue); | ||
402 | } | ||
403 | if (isCommand_Widget(w, ev, "focus.lost")) { | ||
404 | setCursor_LookupWidget_(d, iInvalidPos); | ||
405 | } | ||
406 | if (isCommand_Widget(w, ev, "focus.gained")) { | ||
407 | if (d->cursor == iInvalidPos) { | ||
408 | setCursor_LookupWidget_(d, 1); | ||
409 | } | ||
410 | } | ||
411 | if (isCommand_Widget(w, ev, "list.clicked")) { | ||
412 | setTextCStr_InputWidget(findWidget_App("url"), ""); | ||
413 | const iLookupItem *item = constItem_ListWidget(d->list, arg_Command(cmd)); | ||
414 | if (item && !isEmpty_String(&item->command)) { | ||
415 | postCommandString_App(&item->command); | ||
416 | setFlags_Widget(w, hidden_WidgetFlag, iTrue); | ||
417 | setCursor_LookupWidget_(d, iInvalidPos); | ||
418 | setFocus_Widget(NULL); | ||
419 | } | ||
420 | return iTrue; | ||
421 | } | ||
422 | if (ev->type == SDL_KEYDOWN) { | ||
423 | const int mods = keyMods_Sym(ev->key.keysym.mod); | ||
424 | const int key = ev->key.keysym.sym; | ||
425 | if (isFocused_Widget(d)) { | ||
426 | iWidget *url = findWidget_App("url"); | ||
427 | switch (key) { | ||
428 | case SDLK_ESCAPE: | ||
429 | setFlags_Widget(w, hidden_WidgetFlag, iTrue); | ||
430 | setCursor_LookupWidget_(d, iInvalidPos); | ||
431 | setFocus_Widget(url); | ||
432 | return iTrue; | ||
433 | case SDLK_UP: | ||
434 | for (;;) { | ||
435 | if (d->cursor == 0) { | ||
436 | setCursor_LookupWidget_(d, iInvalidPos); | ||
437 | setFocus_Widget(url); | ||
438 | break; | ||
439 | } | ||
440 | setCursor_LookupWidget_(d, d->cursor - 1); | ||
441 | if (!item_LookupWidget_(d, d->cursor)->listItem.isSeparator) { | ||
442 | break; | ||
443 | } | ||
444 | } | ||
445 | return iTrue; | ||
446 | case SDLK_DOWN: | ||
447 | while (d->cursor < numItems_ListWidget(d->list) - 1) { | ||
448 | setCursor_LookupWidget_(d, d->cursor + 1); | ||
449 | if (!item_LookupWidget_(d, d->cursor)->listItem.isSeparator) { | ||
450 | break; | ||
451 | } | ||
452 | } | ||
453 | return iTrue; | ||
454 | case SDLK_PAGEUP: | ||
455 | return iTrue; | ||
456 | case SDLK_PAGEDOWN: | ||
457 | return iTrue; | ||
458 | case SDLK_HOME: | ||
459 | setCursor_LookupWidget_(d, 1); | ||
460 | return iTrue; | ||
461 | case SDLK_END: | ||
462 | setCursor_LookupWidget_(d, numItems_ListWidget(d->list) - 1); | ||
463 | return iTrue; | ||
464 | case SDLK_KP_ENTER: | ||
465 | case SDLK_SPACE: | ||
466 | case SDLK_RETURN: | ||
467 | postCommand_Widget(w, "list.clicked arg:%zu", d->cursor); | ||
468 | return iTrue; | ||
469 | } | ||
470 | } | ||
471 | if (key == SDLK_DOWN && !mods && focus_Widget() == findWidget_App("url") && | ||
472 | numItems_ListWidget(d->list)) { | ||
473 | setCursor_LookupWidget_(d, 1); /* item 0 is always the first heading */ | ||
474 | setFocus_Widget(w); | ||
475 | return iTrue; | ||
476 | } | ||
477 | } | ||
128 | return processEvent_Widget(w, ev); | 478 | return processEvent_Widget(w, ev); |
129 | } | 479 | } |
130 | 480 | ||