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 | |
parent | 52a1652536e4e27751ac121009f85113e72afe7d (diff) |
LookupWidget: Keyboard focus and cursor
-rw-r--r-- | src/app.c | 2 | ||||
-rw-r--r-- | src/bookmarks.c | 31 | ||||
-rw-r--r-- | src/bookmarks.h | 6 | ||||
-rw-r--r-- | src/lookup.c | 11 | ||||
-rw-r--r-- | src/lookup.h | 3 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 26 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 3 | ||||
-rw-r--r-- | src/ui/listwidget.c | 47 | ||||
-rw-r--r-- | src/ui/listwidget.h | 8 | ||||
-rw-r--r-- | src/ui/lookupwidget.c | 374 | ||||
-rw-r--r-- | src/ui/lookupwidget.h | 2 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 6 | ||||
-rw-r--r-- | src/ui/util.c | 2 | ||||
-rw-r--r-- | src/ui/window.c | 16 |
14 files changed, 489 insertions, 48 deletions
@@ -144,7 +144,7 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
144 | appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y); | 144 | appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y); |
145 | appendFormat_String(str, "sidebar.width arg:%d\n", width_SidebarWidget(sidebar)); | 145 | appendFormat_String(str, "sidebar.width arg:%d\n", width_SidebarWidget(sidebar)); |
146 | } | 146 | } |
147 | if (isVisible_Widget(constAs_Widget(sidebar))) { | 147 | if (isVisible_Widget(sidebar)) { |
148 | appendCStr_String(str, "sidebar.toggle\n"); | 148 | appendCStr_String(str, "sidebar.toggle\n"); |
149 | } | 149 | } |
150 | if (d->forceWrap) { | 150 | if (d->forceWrap) { |
diff --git a/src/bookmarks.c b/src/bookmarks.c index eff0146d..6a5eb296 100644 --- a/src/bookmarks.c +++ b/src/bookmarks.c | |||
@@ -23,8 +23,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #include "bookmarks.h" | 23 | #include "bookmarks.h" |
24 | 24 | ||
25 | #include <the_Foundation/file.h> | 25 | #include <the_Foundation/file.h> |
26 | #include <the_Foundation/path.h> | ||
27 | #include <the_Foundation/hash.h> | 26 | #include <the_Foundation/hash.h> |
27 | #include <the_Foundation/mutex.h> | ||
28 | #include <the_Foundation/path.h> | ||
28 | 29 | ||
29 | void init_Bookmark(iBookmark *d) { | 30 | void init_Bookmark(iBookmark *d) { |
30 | init_String(&d->url); | 31 | init_String(&d->url); |
@@ -50,13 +51,15 @@ static int cmpTimeDescending_Bookmark_(const iBookmark **a, const iBookmark **b) | |||
50 | static const char *fileName_Bookmarks_ = "bookmarks.txt"; | 51 | static const char *fileName_Bookmarks_ = "bookmarks.txt"; |
51 | 52 | ||
52 | struct Impl_Bookmarks { | 53 | struct Impl_Bookmarks { |
53 | int idEnum; | 54 | iMutex *mtx; |
54 | iHash bookmarks; | 55 | int idEnum; |
56 | iHash bookmarks; | ||
55 | }; | 57 | }; |
56 | 58 | ||
57 | iDefineTypeConstruction(Bookmarks) | 59 | iDefineTypeConstruction(Bookmarks) |
58 | 60 | ||
59 | void init_Bookmarks(iBookmarks *d) { | 61 | void init_Bookmarks(iBookmarks *d) { |
62 | d->mtx = new_Mutex(); | ||
60 | d->idEnum = 0; | 63 | d->idEnum = 0; |
61 | init_Hash(&d->bookmarks); | 64 | init_Hash(&d->bookmarks); |
62 | } | 65 | } |
@@ -64,19 +67,24 @@ void init_Bookmarks(iBookmarks *d) { | |||
64 | void deinit_Bookmarks(iBookmarks *d) { | 67 | void deinit_Bookmarks(iBookmarks *d) { |
65 | clear_Bookmarks(d); | 68 | clear_Bookmarks(d); |
66 | deinit_Hash(&d->bookmarks); | 69 | deinit_Hash(&d->bookmarks); |
70 | delete_Mutex(d->mtx); | ||
67 | } | 71 | } |
68 | 72 | ||
69 | void clear_Bookmarks(iBookmarks *d) { | 73 | void clear_Bookmarks(iBookmarks *d) { |
74 | lock_Mutex(d->mtx); | ||
70 | iForEach(Hash, i, &d->bookmarks) { | 75 | iForEach(Hash, i, &d->bookmarks) { |
71 | delete_Bookmark((iBookmark *) i.value); | 76 | delete_Bookmark((iBookmark *) i.value); |
72 | } | 77 | } |
73 | clear_Hash(&d->bookmarks); | 78 | clear_Hash(&d->bookmarks); |
74 | d->idEnum = 0; | 79 | d->idEnum = 0; |
80 | unlock_Mutex(d->mtx); | ||
75 | } | 81 | } |
76 | 82 | ||
77 | static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) { | 83 | static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) { |
84 | lock_Mutex(d->mtx); | ||
78 | bookmark->node.key = ++d->idEnum; | 85 | bookmark->node.key = ++d->idEnum; |
79 | insert_Hash(&d->bookmarks, &bookmark->node); | 86 | insert_Hash(&d->bookmarks, &bookmark->node); |
87 | unlock_Mutex(d->mtx); | ||
80 | } | 88 | } |
81 | 89 | ||
82 | void load_Bookmarks(iBookmarks *d, const char *dirPath) { | 90 | void load_Bookmarks(iBookmarks *d, const char *dirPath) { |
@@ -111,6 +119,7 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) { | |||
111 | } | 119 | } |
112 | 120 | ||
113 | void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | 121 | void save_Bookmarks(const iBookmarks *d, const char *dirPath) { |
122 | lock_Mutex(d->mtx); | ||
114 | iFile *f = newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_)); | 123 | iFile *f = newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_)); |
115 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | 124 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { |
116 | iString *str = collectNew_String(); | 125 | iString *str = collectNew_String(); |
@@ -127,10 +136,12 @@ void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | |||
127 | } | 136 | } |
128 | } | 137 | } |
129 | iRelease(f); | 138 | iRelease(f); |
139 | unlock_Mutex(d->mtx); | ||
130 | } | 140 | } |
131 | 141 | ||
132 | void add_Bookmarks(iBookmarks *d, const iString *url, const iString *title, const iString *tags, | 142 | void add_Bookmarks(iBookmarks *d, const iString *url, const iString *title, const iString *tags, |
133 | iChar icon) { | 143 | iChar icon) { |
144 | lock_Mutex(d->mtx); | ||
134 | iBookmark *bm = new_Bookmark(); | 145 | iBookmark *bm = new_Bookmark(); |
135 | set_String(&bm->url, url); | 146 | set_String(&bm->url, url); |
136 | set_String(&bm->title, title); | 147 | set_String(&bm->title, title); |
@@ -138,30 +149,34 @@ void add_Bookmarks(iBookmarks *d, const iString *url, const iString *title, cons | |||
138 | bm->icon = icon; | 149 | bm->icon = icon; |
139 | initCurrent_Time(&bm->when); | 150 | initCurrent_Time(&bm->when); |
140 | insert_Bookmarks_(d, bm); | 151 | insert_Bookmarks_(d, bm); |
152 | unlock_Mutex(d->mtx); | ||
141 | } | 153 | } |
142 | 154 | ||
143 | iBool remove_Bookmarks(iBookmarks *d, uint32_t id) { | 155 | iBool remove_Bookmarks(iBookmarks *d, uint32_t id) { |
156 | lock_Mutex(d->mtx); | ||
144 | iBookmark *bm = (iBookmark *) remove_Hash(&d->bookmarks, id); | 157 | iBookmark *bm = (iBookmark *) remove_Hash(&d->bookmarks, id); |
145 | if (bm) { | 158 | if (bm) { |
146 | delete_Bookmark(bm); | 159 | delete_Bookmark(bm); |
147 | return iTrue; | ||
148 | } | 160 | } |
149 | return iFalse; | 161 | unlock_Mutex(d->mtx); |
162 | return bm != NULL; | ||
150 | } | 163 | } |
151 | 164 | ||
152 | iBookmark *get_Bookmarks(iBookmarks *d, uint32_t id) { | 165 | iBookmark *get_Bookmarks(iBookmarks *d, uint32_t id) { |
153 | return (iBookmark *) value_Hash(&d->bookmarks, id); | 166 | return (iBookmark *) value_Hash(&d->bookmarks, id); |
154 | } | 167 | } |
155 | 168 | ||
156 | const iPtrArray *list_Bookmarks(const iBookmarks *d, iBookmarksFilterFunc filter, | 169 | const iPtrArray *list_Bookmarks(const iBookmarks *d, iBookmarksCompareFunc cmp, |
157 | iBookmarksCompareFunc cmp) { | 170 | iBookmarksFilterFunc filter, void *context) { |
171 | lock_Mutex(d->mtx); | ||
158 | iPtrArray *list = collectNew_PtrArray(); | 172 | iPtrArray *list = collectNew_PtrArray(); |
159 | iConstForEach(Hash, i, &d->bookmarks) { | 173 | iConstForEach(Hash, i, &d->bookmarks) { |
160 | const iBookmark *bm = (const iBookmark *) i.value; | 174 | const iBookmark *bm = (const iBookmark *) i.value; |
161 | if (!filter || filter(bm)) { | 175 | if (!filter || filter(context, bm)) { |
162 | pushBack_PtrArray(list, bm); | 176 | pushBack_PtrArray(list, bm); |
163 | } | 177 | } |
164 | } | 178 | } |
179 | unlock_Mutex(d->mtx); | ||
165 | if (!cmp) cmp = cmpTimeDescending_Bookmark_; | 180 | if (!cmp) cmp = cmpTimeDescending_Bookmark_; |
166 | sort_Array(list, (int (*)(const void *, const void *)) cmp); | 181 | sort_Array(list, (int (*)(const void *, const void *)) cmp); |
167 | return list; | 182 | return list; |
diff --git a/src/bookmarks.h b/src/bookmarks.h index a3269a59..aac83be1 100644 --- a/src/bookmarks.h +++ b/src/bookmarks.h | |||
@@ -52,7 +52,7 @@ void add_Bookmarks (iBookmarks *, const iString *url, const iString *ti | |||
52 | iBool remove_Bookmarks (iBookmarks *, uint32_t id); | 52 | iBool remove_Bookmarks (iBookmarks *, uint32_t id); |
53 | iBookmark *get_Bookmarks (iBookmarks *, uint32_t id); | 53 | iBookmark *get_Bookmarks (iBookmarks *, uint32_t id); |
54 | 54 | ||
55 | typedef iBool (*iBookmarksFilterFunc) (const iBookmark *); | 55 | typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *); |
56 | typedef int (*iBookmarksCompareFunc)(const iBookmark **, const iBookmark **); | 56 | typedef int (*iBookmarksCompareFunc)(const iBookmark **, const iBookmark **); |
57 | 57 | ||
58 | /** | 58 | /** |
@@ -66,5 +66,5 @@ typedef int (*iBookmarksCompareFunc)(const iBookmark **, const iBookmark **); | |||
66 | * @return Collected array of bookmarks. Caller does not get ownership of the | 66 | * @return Collected array of bookmarks. Caller does not get ownership of the |
67 | * listed bookmarks. | 67 | * listed bookmarks. |
68 | */ | 68 | */ |
69 | const iPtrArray *list_Bookmarks(const iBookmarks *, iBookmarksFilterFunc filter, | 69 | const iPtrArray *list_Bookmarks(const iBookmarks *, iBookmarksCompareFunc cmp, |
70 | iBookmarksCompareFunc cmp); | 70 | iBookmarksFilterFunc filter, void *context); |
diff --git a/src/lookup.c b/src/lookup.c index 4f00a66a..6ad8d708 100644 --- a/src/lookup.c +++ b/src/lookup.c | |||
@@ -26,6 +26,7 @@ iDefineTypeConstruction(LookupResult) | |||
26 | 26 | ||
27 | void init_LookupResult(iLookupResult *d) { | 27 | void init_LookupResult(iLookupResult *d) { |
28 | d->type = none_LookupResultType; | 28 | d->type = none_LookupResultType; |
29 | d->relevance = 0; | ||
29 | init_String(&d->label); | 30 | init_String(&d->label); |
30 | init_String(&d->url); | 31 | init_String(&d->url); |
31 | init_String(&d->meta); | 32 | init_String(&d->meta); |
@@ -37,3 +38,13 @@ void deinit_LookupResult(iLookupResult *d) { | |||
37 | deinit_String(&d->url); | 38 | deinit_String(&d->url); |
38 | deinit_String(&d->label); | 39 | deinit_String(&d->label); |
39 | } | 40 | } |
41 | |||
42 | iLookupResult *copy_LookupResult(const iLookupResult *d) { | ||
43 | iLookupResult *copy = new_LookupResult(); | ||
44 | copy->type = d->type; | ||
45 | copy->relevance = d->relevance; | ||
46 | set_String(©->label, &d->label); | ||
47 | set_String(©->url, &d->url); | ||
48 | set_String(©->meta, &d->meta); | ||
49 | return copy; | ||
50 | } | ||
diff --git a/src/lookup.h b/src/lookup.h index ecbe0036..a20a36d0 100644 --- a/src/lookup.h +++ b/src/lookup.h | |||
@@ -37,6 +37,7 @@ enum iLookupResultType { | |||
37 | 37 | ||
38 | struct Impl_LookupResult { | 38 | struct Impl_LookupResult { |
39 | enum iLookupResultType type; | 39 | enum iLookupResultType type; |
40 | float relevance; /* used for sorting results */ | ||
40 | iString label; | 41 | iString label; |
41 | iString url; | 42 | iString url; |
42 | iString meta; | 43 | iString meta; |
@@ -44,3 +45,5 @@ struct Impl_LookupResult { | |||
44 | }; | 45 | }; |
45 | 46 | ||
46 | iDeclareTypeConstruction(LookupResult) | 47 | iDeclareTypeConstruction(LookupResult) |
48 | |||
49 | iLookupResult * copy_LookupResult (const iLookupResult *); | ||
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 16db6bf5..8f5a0656 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -54,6 +54,7 @@ struct Impl_InputWidget { | |||
54 | iBool isSensitive; | 54 | iBool isSensitive; |
55 | iBool enterPressed; | 55 | iBool enterPressed; |
56 | iBool selectAllOnFocus; | 56 | iBool selectAllOnFocus; |
57 | iBool notifyEdits; | ||
57 | size_t maxLen; | 58 | size_t maxLen; |
58 | iArray text; /* iChar[] */ | 59 | iArray text; /* iChar[] */ |
59 | iArray oldText; /* iChar[] */ | 60 | iArray oldText; /* iChar[] */ |
@@ -97,6 +98,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
97 | d->isSensitive = iFalse; | 98 | d->isSensitive = iFalse; |
98 | d->enterPressed = iFalse; | 99 | d->enterPressed = iFalse; |
99 | d->selectAllOnFocus = iFalse; | 100 | d->selectAllOnFocus = iFalse; |
101 | d->notifyEdits = iFalse; | ||
100 | iZap(d->mark); | 102 | iZap(d->mark); |
101 | setMaxLen_InputWidget(d, maxLen); | 103 | setMaxLen_InputWidget(d, maxLen); |
102 | /* Caller must arrange the width, but the height is fixed. */ | 104 | /* Caller must arrange the width, but the height is fixed. */ |
@@ -300,10 +302,20 @@ void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { | |||
300 | d->selectAllOnFocus = selectAllOnFocus; | 302 | d->selectAllOnFocus = selectAllOnFocus; |
301 | } | 303 | } |
302 | 304 | ||
305 | void setNotifyEdits_InputWidget(iInputWidget *d, iBool notifyEdits) { | ||
306 | d->notifyEdits = notifyEdits; | ||
307 | } | ||
308 | |||
303 | static iRanges mark_InputWidget_(const iInputWidget *d) { | 309 | static iRanges mark_InputWidget_(const iInputWidget *d) { |
304 | return (iRanges){ iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) }; | 310 | return (iRanges){ iMin(d->mark.start, d->mark.end), iMax(d->mark.start, d->mark.end) }; |
305 | } | 311 | } |
306 | 312 | ||
313 | static void contentsWereChanged_InputWidget_(iInputWidget *d) { | ||
314 | if (d->notifyEdits) { | ||
315 | postCommand_Widget(d, "input.edited id:%s", cstr_String(id_Widget(constAs_Widget(d)))); | ||
316 | } | ||
317 | } | ||
318 | |||
307 | static iBool deleteMarked_InputWidget_(iInputWidget *d) { | 319 | static iBool deleteMarked_InputWidget_(iInputWidget *d) { |
308 | const iRanges m = mark_InputWidget_(d); | 320 | const iRanges m = mark_InputWidget_(d); |
309 | if (!isEmpty_Range(&m)) { | 321 | if (!isEmpty_Range(&m)) { |
@@ -481,6 +493,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
481 | if (key == 'x') { | 493 | if (key == 'x') { |
482 | pushUndo_InputWidget_(d); | 494 | pushUndo_InputWidget_(d); |
483 | deleteMarked_InputWidget_(d); | 495 | deleteMarked_InputWidget_(d); |
496 | contentsWereChanged_InputWidget_(d); | ||
484 | } | 497 | } |
485 | } | 498 | } |
486 | return iTrue; | 499 | return iTrue; |
@@ -494,11 +507,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
494 | iConstForEach(String, i, paste) { | 507 | iConstForEach(String, i, paste) { |
495 | insertChar_InputWidget_(d, i.value); | 508 | insertChar_InputWidget_(d, i.value); |
496 | } | 509 | } |
510 | contentsWereChanged_InputWidget_(d); | ||
497 | } | 511 | } |
498 | return iTrue; | 512 | return iTrue; |
499 | case 'z': | 513 | case 'z': |
500 | if (popUndo_InputWidget_(d)) { | 514 | if (popUndo_InputWidget_(d)) { |
501 | refresh_Widget(w); | 515 | refresh_Widget(w); |
516 | contentsWereChanged_InputWidget_(d); | ||
502 | } | 517 | } |
503 | return iTrue; | 518 | return iTrue; |
504 | } | 519 | } |
@@ -518,16 +533,19 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
518 | if (!isEmpty_Range(&d->mark)) { | 533 | if (!isEmpty_Range(&d->mark)) { |
519 | pushUndo_InputWidget_(d); | 534 | pushUndo_InputWidget_(d); |
520 | deleteMarked_InputWidget_(d); | 535 | deleteMarked_InputWidget_(d); |
536 | contentsWereChanged_InputWidget_(d); | ||
521 | } | 537 | } |
522 | else if (mods & KMOD_ALT) { | 538 | else if (mods & KMOD_ALT) { |
523 | pushUndo_InputWidget_(d); | 539 | pushUndo_InputWidget_(d); |
524 | d->mark.start = d->cursor; | 540 | d->mark.start = d->cursor; |
525 | d->mark.end = skipWord_InputWidget_(d, d->cursor, -1); | 541 | d->mark.end = skipWord_InputWidget_(d, d->cursor, -1); |
526 | deleteMarked_InputWidget_(d); | 542 | deleteMarked_InputWidget_(d); |
543 | contentsWereChanged_InputWidget_(d); | ||
527 | } | 544 | } |
528 | else if (d->cursor > 0) { | 545 | else if (d->cursor > 0) { |
529 | pushUndo_InputWidget_(d); | 546 | pushUndo_InputWidget_(d); |
530 | remove_Array(&d->text, --d->cursor); | 547 | remove_Array(&d->text, --d->cursor); |
548 | contentsWereChanged_InputWidget_(d); | ||
531 | } | 549 | } |
532 | showCursor_InputWidget_(d); | 550 | showCursor_InputWidget_(d); |
533 | refresh_Widget(w); | 551 | refresh_Widget(w); |
@@ -538,16 +556,19 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
538 | if (!isEmpty_Range(&d->mark)) { | 556 | if (!isEmpty_Range(&d->mark)) { |
539 | pushUndo_InputWidget_(d); | 557 | pushUndo_InputWidget_(d); |
540 | deleteMarked_InputWidget_(d); | 558 | deleteMarked_InputWidget_(d); |
559 | contentsWereChanged_InputWidget_(d); | ||
541 | } | 560 | } |
542 | else if (mods & KMOD_ALT) { | 561 | else if (mods & KMOD_ALT) { |
543 | pushUndo_InputWidget_(d); | 562 | pushUndo_InputWidget_(d); |
544 | d->mark.start = d->cursor; | 563 | d->mark.start = d->cursor; |
545 | d->mark.end = skipWord_InputWidget_(d, d->cursor, +1); | 564 | d->mark.end = skipWord_InputWidget_(d, d->cursor, +1); |
546 | deleteMarked_InputWidget_(d); | 565 | deleteMarked_InputWidget_(d); |
566 | contentsWereChanged_InputWidget_(d); | ||
547 | } | 567 | } |
548 | else if (d->cursor < size_Array(&d->text)) { | 568 | else if (d->cursor < size_Array(&d->text)) { |
549 | pushUndo_InputWidget_(d); | 569 | pushUndo_InputWidget_(d); |
550 | remove_Array(&d->text, d->cursor); | 570 | remove_Array(&d->text, d->cursor); |
571 | contentsWereChanged_InputWidget_(d); | ||
551 | } | 572 | } |
552 | showCursor_InputWidget_(d); | 573 | showCursor_InputWidget_(d); |
553 | refresh_Widget(w); | 574 | refresh_Widget(w); |
@@ -557,10 +578,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
557 | if (!isEmpty_Range(&d->mark)) { | 578 | if (!isEmpty_Range(&d->mark)) { |
558 | pushUndo_InputWidget_(d); | 579 | pushUndo_InputWidget_(d); |
559 | deleteMarked_InputWidget_(d); | 580 | deleteMarked_InputWidget_(d); |
581 | contentsWereChanged_InputWidget_(d); | ||
560 | } | 582 | } |
561 | else { | 583 | else { |
562 | pushUndo_InputWidget_(d); | 584 | pushUndo_InputWidget_(d); |
563 | removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor); | 585 | removeN_Array(&d->text, d->cursor, size_Array(&d->text) - d->cursor); |
586 | contentsWereChanged_InputWidget_(d); | ||
564 | } | 587 | } |
565 | showCursor_InputWidget_(d); | 588 | showCursor_InputWidget_(d); |
566 | refresh_Widget(w); | 589 | refresh_Widget(w); |
@@ -612,6 +635,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
612 | return iTrue; | 635 | return iTrue; |
613 | } | 636 | } |
614 | case SDLK_TAB: | 637 | case SDLK_TAB: |
638 | case SDLK_DOWN: /* for moving to lookup from url entry */ | ||
615 | /* Allow focus switching. */ | 639 | /* Allow focus switching. */ |
616 | return processEvent_Widget(as_Widget(d), ev); | 640 | return processEvent_Widget(as_Widget(d), ev); |
617 | } | 641 | } |
@@ -627,6 +651,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
627 | iConstForEach(String, i, uni) { | 651 | iConstForEach(String, i, uni) { |
628 | insertChar_InputWidget_(d, i.value); | 652 | insertChar_InputWidget_(d, i.value); |
629 | } | 653 | } |
654 | contentsWereChanged_InputWidget_(d); | ||
630 | return iTrue; | 655 | return iTrue; |
631 | } | 656 | } |
632 | return processEvent_Widget(w, ev); | 657 | return processEvent_Widget(w, ev); |
@@ -643,7 +668,6 @@ static iBool isWhite_(const iString *str) { | |||
643 | 668 | ||
644 | static void draw_InputWidget_(const iInputWidget *d) { | 669 | static void draw_InputWidget_(const iInputWidget *d) { |
645 | const iWidget *w = constAs_Widget(d); | 670 | const iWidget *w = constAs_Widget(d); |
646 | const uint32_t time = frameTime_Window(get_Window()); | ||
647 | iRect bounds = adjusted_Rect(bounds_Widget(w), padding_(), neg_I2(padding_())); | 671 | iRect bounds = adjusted_Rect(bounds_Widget(w), padding_(), neg_I2(padding_())); |
648 | iBool isHint = iFalse; | 672 | iBool isHint = iFalse; |
649 | const iBool isFocused = isFocused_Widget(w); | 673 | const iBool isFocused = isFocused_Widget(w); |
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h index fed9c3d9..ced7f968 100644 --- a/src/ui/inputwidget.h +++ b/src/ui/inputwidget.h | |||
@@ -39,7 +39,8 @@ void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); | |||
39 | void setText_InputWidget (iInputWidget *, const iString *text); | 39 | void setText_InputWidget (iInputWidget *, const iString *text); |
40 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); | 40 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); |
41 | void setCursor_InputWidget (iInputWidget *, size_t pos); | 41 | void setCursor_InputWidget (iInputWidget *, size_t pos); |
42 | void setSelectAllOnFocus_InputWidget(iInputWidget *, iBool selectAllOnFocus); | 42 | void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); |
43 | void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); | ||
43 | void begin_InputWidget (iInputWidget *); | 44 | void begin_InputWidget (iInputWidget *); |
44 | void end_InputWidget (iInputWidget *, iBool accept); | 45 | void end_InputWidget (iInputWidget *, iBool accept); |
45 | 46 | ||
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index 3c061bdc..06689023 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c | |||
@@ -97,6 +97,11 @@ void invalidate_ListWidget(iListWidget *d) { | |||
97 | refresh_Widget(as_Widget(d)); | 97 | refresh_Widget(as_Widget(d)); |
98 | } | 98 | } |
99 | 99 | ||
100 | void invalidateItem_ListWidget(iListWidget *d, size_t index) { | ||
101 | insert_IntSet(&d->invalidItems, index); | ||
102 | refresh_Widget(d); | ||
103 | } | ||
104 | |||
100 | void clear_ListWidget(iListWidget *d) { | 105 | void clear_ListWidget(iListWidget *d) { |
101 | iForEach(PtrArray, i, &d->items) { | 106 | iForEach(PtrArray, i, &d->items) { |
102 | deref_Object(i.ptr); | 107 | deref_Object(i.ptr); |
@@ -124,14 +129,18 @@ static int scrollMax_ListWidget_(const iListWidget *d) { | |||
124 | } | 129 | } |
125 | 130 | ||
126 | void updateVisible_ListWidget(iListWidget *d) { | 131 | void updateVisible_ListWidget(iListWidget *d) { |
127 | const int contentSize = size_PtrArray(&d->items) * d->itemHeight; | 132 | const int contentSize = size_PtrArray(&d->items) * d->itemHeight; |
128 | const iRect bounds = innerBounds_Widget(as_Widget(d)); | 133 | const iRect bounds = innerBounds_Widget(as_Widget(d)); |
134 | const iBool wasVisible = isVisible_Widget(d->scroll); | ||
129 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) }); | 135 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax_ListWidget_(d) }); |
130 | setThumb_ScrollWidget(d->scroll, | 136 | setThumb_ScrollWidget(d->scroll, |
131 | d->scrollY, | 137 | d->scrollY, |
132 | contentSize > 0 ? height_Rect(bounds_Widget(as_Widget(d->scroll))) * | 138 | contentSize > 0 ? height_Rect(bounds_Widget(as_Widget(d->scroll))) * |
133 | height_Rect(bounds) / contentSize | 139 | height_Rect(bounds) / contentSize |
134 | : 0); | 140 | : 0); |
141 | if (wasVisible != isVisible_Widget(d->scroll)) { | ||
142 | invalidate_ListWidget(d); /* clip margins changed */ | ||
143 | } | ||
135 | } | 144 | } |
136 | 145 | ||
137 | void setItemHeight_ListWidget(iListWidget *d, int itemHeight) { | 146 | void setItemHeight_ListWidget(iListWidget *d, int itemHeight) { |
@@ -193,20 +202,28 @@ size_t itemIndex_ListWidget(const iListWidget *d, iInt2 pos) { | |||
193 | return index; | 202 | return index; |
194 | } | 203 | } |
195 | 204 | ||
196 | const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) { | 205 | const iAnyObject *constItem_ListWidget(const iListWidget *d, size_t index) { |
197 | if (d->hoverItem < size_PtrArray(&d->items)) { | 206 | if (index < size_PtrArray(&d->items)) { |
198 | return constAt_PtrArray(&d->items, d->hoverItem); | 207 | return constAt_PtrArray(&d->items, index); |
199 | } | 208 | } |
200 | return NULL; | 209 | return NULL; |
201 | } | 210 | } |
202 | 211 | ||
203 | iAnyObject *hoverItem_ListWidget(iListWidget *d) { | 212 | const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) { |
204 | if (d->hoverItem < size_PtrArray(&d->items)) { | 213 | return constItem_ListWidget(d, d->hoverItem); |
205 | return at_PtrArray(&d->items, d->hoverItem); | 214 | } |
215 | |||
216 | iAnyObject *item_ListWidget(iListWidget *d, size_t index) { | ||
217 | if (index < size_PtrArray(&d->items)) { | ||
218 | return at_PtrArray(&d->items, index); | ||
206 | } | 219 | } |
207 | return NULL; | 220 | return NULL; |
208 | } | 221 | } |
209 | 222 | ||
223 | iAnyObject *hoverItem_ListWidget(iListWidget *d) { | ||
224 | return item_ListWidget(d, d->hoverItem); | ||
225 | } | ||
226 | |||
210 | static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { | 227 | static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { |
211 | if (index < size_PtrArray(&d->items)) { | 228 | if (index < size_PtrArray(&d->items)) { |
212 | const iListItem *item = at_PtrArray(&d->items, index); | 229 | const iListItem *item = at_PtrArray(&d->items, index); |
@@ -265,7 +282,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
265 | switch (processEvent_Click(&d->click, ev)) { | 282 | switch (processEvent_Click(&d->click, ev)) { |
266 | case started_ClickResult: | 283 | case started_ClickResult: |
267 | redrawHoverItem_ListWidget_(d); | 284 | redrawHoverItem_ListWidget_(d); |
268 | break; | 285 | return iTrue; |
269 | case aborted_ClickResult: | 286 | case aborted_ClickResult: |
270 | redrawHoverItem_ListWidget_(d); | 287 | redrawHoverItem_ListWidget_(d); |
271 | break; | 288 | break; |
@@ -277,7 +294,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
277 | postCommand_Widget(w, "list.clicked arg:%zu item:%p", | 294 | postCommand_Widget(w, "list.clicked arg:%zu item:%p", |
278 | d->hoverItem, constHoverItem_ListWidget(d)); | 295 | d->hoverItem, constHoverItem_ListWidget(d)); |
279 | } | 296 | } |
280 | break; | 297 | return iTrue; |
281 | default: | 298 | default: |
282 | break; | 299 | break; |
283 | } | 300 | } |
@@ -382,7 +399,15 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
382 | fillRect_Paint(&p, itemRect, w->bgColor); | 399 | fillRect_Paint(&p, itemRect, w->bgColor); |
383 | } | 400 | } |
384 | class_ListItem(item)->draw(item, &p, itemRect, d); | 401 | class_ListItem(item)->draw(item, &p, itemRect, d); |
385 | unsetClip_Paint(&p); | 402 | /* Clear under the scrollbar. */ |
403 | if (isVisible_Widget(d->scroll)) { | ||
404 | fillRect_Paint( | ||
405 | &p, | ||
406 | (iRect){ addX_I2(topRight_Rect(itemRect), -width_Widget(d->scroll)), | ||
407 | bottomRight_Rect(itemRect) }, | ||
408 | w->bgColor); | ||
409 | } | ||
410 | unsetClip_Paint(&p); | ||
386 | } | 411 | } |
387 | pos.y += d->itemHeight; | 412 | pos.y += d->itemHeight; |
388 | } | 413 | } |
diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h index c8a07f51..72b7dba4 100644 --- a/src/ui/listwidget.h +++ b/src/ui/listwidget.h | |||
@@ -49,6 +49,7 @@ iDeclareObjectConstruction(ListWidget) | |||
49 | void setItemHeight_ListWidget(iListWidget *, int itemHeight); | 49 | void setItemHeight_ListWidget(iListWidget *, int itemHeight); |
50 | 50 | ||
51 | void invalidate_ListWidget (iListWidget *); | 51 | void invalidate_ListWidget (iListWidget *); |
52 | void invalidateItem_ListWidget(iListWidget *, size_t index); | ||
52 | void clear_ListWidget (iListWidget *); | 53 | void clear_ListWidget (iListWidget *); |
53 | void addItem_ListWidget (iListWidget *, iAnyObject *item); | 54 | void addItem_ListWidget (iListWidget *, iAnyObject *item); |
54 | 55 | ||
@@ -62,9 +63,12 @@ void scrollOffset_ListWidget (iListWidget *, int offset); | |||
62 | void updateVisible_ListWidget (iListWidget *); | 63 | void updateVisible_ListWidget (iListWidget *); |
63 | void updateMouseHover_ListWidget (iListWidget *); | 64 | void updateMouseHover_ListWidget (iListWidget *); |
64 | 65 | ||
66 | iAnyObject *item_ListWidget (iListWidget *, size_t index); | ||
67 | iAnyObject *hoverItem_ListWidget (iListWidget *); | ||
68 | |||
65 | size_t numItems_ListWidget (const iListWidget *); | 69 | size_t numItems_ListWidget (const iListWidget *); |
66 | size_t itemIndex_ListWidget(const iListWidget *, iInt2 pos); | 70 | size_t itemIndex_ListWidget(const iListWidget *, iInt2 pos); |
67 | const iAnyObject *constHoverItem_ListWidget(const iListWidget *); | 71 | const iAnyObject *constItem_ListWidget (const iListWidget *, size_t index); |
68 | iAnyObject *hoverItem_ListWidget(iListWidget *); | 72 | const iAnyObject *constHoverItem_ListWidget (const iListWidget *); |
69 | 73 | ||
70 | iBool isMouseDown_ListWidget (const iListWidget *); | 74 | iBool isMouseDown_ListWidget (const iListWidget *); |
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 | ||
diff --git a/src/ui/lookupwidget.h b/src/ui/lookupwidget.h index 31bcbcb9..87ea4968 100644 --- a/src/ui/lookupwidget.h +++ b/src/ui/lookupwidget.h | |||
@@ -27,4 +27,4 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
27 | iDeclareWidgetClass(LookupWidget) | 27 | iDeclareWidgetClass(LookupWidget) |
28 | iDeclareObjectConstruction(LookupWidget) | 28 | iDeclareObjectConstruction(LookupWidget) |
29 | 29 | ||
30 | 30 | void submit_LookupWidget (iLookupWidget *, const iString *term); | |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index e84967cc..3b02f85c 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -123,7 +123,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
123 | break; | 123 | break; |
124 | } | 124 | } |
125 | case bookmarks_SidebarMode: { | 125 | case bookmarks_SidebarMode: { |
126 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), NULL, cmpTitle_Bookmark_)) { | 126 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTitle_Bookmark_, NULL, NULL)) { |
127 | const iBookmark *bm = i.ptr; | 127 | const iBookmark *bm = i.ptr; |
128 | iSidebarItem *item = new_SidebarItem(); | 128 | iSidebarItem *item = new_SidebarItem(); |
129 | item->id = id_Bookmark(bm); | 129 | item->id = id_Bookmark(bm); |
@@ -749,16 +749,16 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) { | |||
749 | 749 | ||
750 | static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | 750 | static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, |
751 | const iListWidget *list) { | 751 | const iListWidget *list) { |
752 | const int font = uiContent_FontId; | ||
752 | const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), | 753 | const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), |
753 | &Class_SidebarWidget); | 754 | &Class_SidebarWidget); |
754 | const iBool isPressing = isMouseDown_ListWidget(list); | 755 | const iBool isPressing = isMouseDown_ListWidget(list); |
755 | const int font = uiContent_FontId; | ||
756 | const iBool isHover = | 756 | const iBool isHover = |
757 | isHover_Widget(constAs_Widget(list)) && constHoverItem_ListWidget(list) == d; | 757 | isHover_Widget(constAs_Widget(list)) && constHoverItem_ListWidget(list) == d; |
758 | const int itemHeight = height_Rect(itemRect); | 758 | const int itemHeight = height_Rect(itemRect); |
759 | const int iconColor = | 759 | const int iconColor = |
760 | isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) : uiIcon_ColorId; | 760 | isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) : uiIcon_ColorId; |
761 | if (isHover) /* && !d->listItem.isSeparator)*/ { | 761 | if (isHover) { |
762 | fillRect_Paint(p, | 762 | fillRect_Paint(p, |
763 | itemRect, | 763 | itemRect, |
764 | isPressing ? uiBackgroundPressed_ColorId | 764 | isPressing ? uiBackgroundPressed_ColorId |
diff --git a/src/ui/util.c b/src/ui/util.c index 50aabf88..7ca065b1 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -459,7 +459,7 @@ size_t tabPageIndex_Widget(const iWidget *tabs, const iAnyObject *page) { | |||
459 | const iWidget *currentTabPage_Widget(const iWidget *tabs) { | 459 | const iWidget *currentTabPage_Widget(const iWidget *tabs) { |
460 | iWidget *pages = findChild_Widget(tabs, "tabs.pages"); | 460 | iWidget *pages = findChild_Widget(tabs, "tabs.pages"); |
461 | iConstForEach(ObjectList, i, pages->children) { | 461 | iConstForEach(ObjectList, i, pages->children) { |
462 | if (isVisible_Widget(constAs_Widget(i.object))) { | 462 | if (isVisible_Widget(i.object)) { |
463 | return constAs_Widget(i.object); | 463 | return constAs_Widget(i.object); |
464 | } | 464 | } |
465 | } | 465 | } |
diff --git a/src/ui/window.c b/src/ui/window.c index 78567bb5..b886a686 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -203,9 +203,17 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | |||
203 | refresh_Widget(navBar); | 203 | refresh_Widget(navBar); |
204 | return iFalse; | 204 | return iFalse; |
205 | } | 205 | } |
206 | else if (equal_Command(cmd, "input.edited")) { | ||
207 | iAnyObject *url = findChild_Widget(navBar, "url"); | ||
208 | if (pointer_Command(cmd) == url) { | ||
209 | submit_LookupWidget(findWidget_App("lookup"), text_InputWidget(url)); | ||
210 | return iTrue; | ||
211 | } | ||
212 | } | ||
206 | else if (equal_Command(cmd, "input.ended")) { | 213 | else if (equal_Command(cmd, "input.ended")) { |
207 | iInputWidget *url = findChild_Widget(navBar, "url"); | 214 | iInputWidget *url = findChild_Widget(navBar, "url"); |
208 | if (arg_Command(cmd) && pointer_Command(cmd) == url) { | 215 | if (arg_Command(cmd) && pointer_Command(cmd) == url && |
216 | !isFocused_Widget(findWidget_App("lookup"))) { | ||
209 | postCommandf_App( | 217 | postCommandf_App( |
210 | "open url:%s", | 218 | "open url:%s", |
211 | cstr_String(absoluteUrl_String(&iStringLiteral(""), text_InputWidget(url)))); | 219 | cstr_String(absoluteUrl_String(&iStringLiteral(""), text_InputWidget(url)))); |
@@ -276,8 +284,7 @@ static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) { | |||
276 | if (equal_Command(cmd, "input.ended") && | 284 | if (equal_Command(cmd, "input.ended") && |
277 | cmp_String(string_Command(cmd, "id"), "find.input") == 0) { | 285 | cmp_String(string_Command(cmd, "id"), "find.input") == 0) { |
278 | iInputWidget *input = findChild_Widget(searchBar, "find.input"); | 286 | iInputWidget *input = findChild_Widget(searchBar, "find.input"); |
279 | if (arg_Command(cmd) && argLabel_Command(cmd, "enter") && | 287 | if (arg_Command(cmd) && argLabel_Command(cmd, "enter") && isVisible_Widget(input)) { |
280 | isVisible_Widget(as_Widget(input))) { | ||
281 | postCommand_App("find.next"); | 288 | postCommand_App("find.next"); |
282 | /* Keep focus when pressing Enter. */ | 289 | /* Keep focus when pressing Enter. */ |
283 | if (!isEmpty_String(text_InputWidget(input))) { | 290 | if (!isEmpty_String(text_InputWidget(input))) { |
@@ -349,6 +356,7 @@ static void setupUserInterface_Window(iWindow *d) { | |||
349 | iInputWidget *url = new_InputWidget(0); | 356 | iInputWidget *url = new_InputWidget(0); |
350 | setSelectAllOnFocus_InputWidget(url, iTrue); | 357 | setSelectAllOnFocus_InputWidget(url, iTrue); |
351 | setId_Widget(as_Widget(url), "url"); | 358 | setId_Widget(as_Widget(url), "url"); |
359 | setNotifyEdits_InputWidget(url, iTrue); | ||
352 | setTextCStr_InputWidget(url, "gemini://"); | 360 | setTextCStr_InputWidget(url, "gemini://"); |
353 | addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag); | 361 | addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag); |
354 | setId_Widget(addChild_Widget( | 362 | setId_Widget(addChild_Widget( |
@@ -406,7 +414,7 @@ static void setupUserInterface_Window(iWindow *d) { | |||
406 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget("\u2a2f", SDLK_ESCAPE, 0, "find.close"))); | 414 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget("\u2a2f", SDLK_ESCAPE, 0, "find.close"))); |
407 | } | 415 | } |
408 | iLookupWidget *lookup = new_LookupWidget(); | 416 | iLookupWidget *lookup = new_LookupWidget(); |
409 | addChildFlags_Widget(d->root, iClob(lookup), hidden_WidgetFlag); | 417 | addChildFlags_Widget(d->root, iClob(lookup), fixedPosition_WidgetFlag | hidden_WidgetFlag); |
410 | iWidget *tabsMenu = makeMenu_Widget(d->root, | 418 | iWidget *tabsMenu = makeMenu_Widget(d->root, |
411 | (iMenuItem[]){ | 419 | (iMenuItem[]){ |
412 | { "Close Tab", 0, 0, "tabs.close" }, | 420 | { "Close Tab", 0, 0, "tabs.close" }, |