summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/app.c2
-rw-r--r--src/bookmarks.c31
-rw-r--r--src/bookmarks.h6
-rw-r--r--src/lookup.c11
-rw-r--r--src/lookup.h3
-rw-r--r--src/ui/inputwidget.c26
-rw-r--r--src/ui/inputwidget.h3
-rw-r--r--src/ui/listwidget.c47
-rw-r--r--src/ui/listwidget.h8
-rw-r--r--src/ui/lookupwidget.c374
-rw-r--r--src/ui/lookupwidget.h2
-rw-r--r--src/ui/sidebarwidget.c6
-rw-r--r--src/ui/util.c2
-rw-r--r--src/ui/window.c16
14 files changed, 489 insertions, 48 deletions
diff --git a/src/app.c b/src/app.c
index da14f06c..a7aceb19 100644
--- a/src/app.c
+++ b/src/app.c
@@ -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
29void init_Bookmark(iBookmark *d) { 30void 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)
50static const char *fileName_Bookmarks_ = "bookmarks.txt"; 51static const char *fileName_Bookmarks_ = "bookmarks.txt";
51 52
52struct Impl_Bookmarks { 53struct Impl_Bookmarks {
53 int idEnum; 54 iMutex *mtx;
54 iHash bookmarks; 55 int idEnum;
56 iHash bookmarks;
55}; 57};
56 58
57iDefineTypeConstruction(Bookmarks) 59iDefineTypeConstruction(Bookmarks)
58 60
59void init_Bookmarks(iBookmarks *d) { 61void 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) {
64void deinit_Bookmarks(iBookmarks *d) { 67void 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
69void clear_Bookmarks(iBookmarks *d) { 73void 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
77static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) { 83static 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
82void load_Bookmarks(iBookmarks *d, const char *dirPath) { 90void load_Bookmarks(iBookmarks *d, const char *dirPath) {
@@ -111,6 +119,7 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) {
111} 119}
112 120
113void save_Bookmarks(const iBookmarks *d, const char *dirPath) { 121void 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
132void add_Bookmarks(iBookmarks *d, const iString *url, const iString *title, const iString *tags, 142void 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
143iBool remove_Bookmarks(iBookmarks *d, uint32_t id) { 155iBool 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
152iBookmark *get_Bookmarks(iBookmarks *d, uint32_t id) { 165iBookmark *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
156const iPtrArray *list_Bookmarks(const iBookmarks *d, iBookmarksFilterFunc filter, 169const 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
52iBool remove_Bookmarks (iBookmarks *, uint32_t id); 52iBool remove_Bookmarks (iBookmarks *, uint32_t id);
53iBookmark *get_Bookmarks (iBookmarks *, uint32_t id); 53iBookmark *get_Bookmarks (iBookmarks *, uint32_t id);
54 54
55typedef iBool (*iBookmarksFilterFunc) (const iBookmark *); 55typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *);
56typedef int (*iBookmarksCompareFunc)(const iBookmark **, const iBookmark **); 56typedef 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 */
69const iPtrArray *list_Bookmarks(const iBookmarks *, iBookmarksFilterFunc filter, 69const 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
27void init_LookupResult(iLookupResult *d) { 27void 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
42iLookupResult *copy_LookupResult(const iLookupResult *d) {
43 iLookupResult *copy = new_LookupResult();
44 copy->type = d->type;
45 copy->relevance = d->relevance;
46 set_String(&copy->label, &d->label);
47 set_String(&copy->url, &d->url);
48 set_String(&copy->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
38struct Impl_LookupResult { 38struct 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
46iDeclareTypeConstruction(LookupResult) 47iDeclareTypeConstruction(LookupResult)
48
49iLookupResult * 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
305void setNotifyEdits_InputWidget(iInputWidget *d, iBool notifyEdits) {
306 d->notifyEdits = notifyEdits;
307}
308
303static iRanges mark_InputWidget_(const iInputWidget *d) { 309static 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
313static 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
307static iBool deleteMarked_InputWidget_(iInputWidget *d) { 319static 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
644static void draw_InputWidget_(const iInputWidget *d) { 669static 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);
39void setText_InputWidget (iInputWidget *, const iString *text); 39void setText_InputWidget (iInputWidget *, const iString *text);
40void setTextCStr_InputWidget (iInputWidget *, const char *cstr); 40void setTextCStr_InputWidget (iInputWidget *, const char *cstr);
41void setCursor_InputWidget (iInputWidget *, size_t pos); 41void setCursor_InputWidget (iInputWidget *, size_t pos);
42void setSelectAllOnFocus_InputWidget(iInputWidget *, iBool selectAllOnFocus); 42void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus);
43void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits);
43void begin_InputWidget (iInputWidget *); 44void begin_InputWidget (iInputWidget *);
44void end_InputWidget (iInputWidget *, iBool accept); 45void 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
100void invalidateItem_ListWidget(iListWidget *d, size_t index) {
101 insert_IntSet(&d->invalidItems, index);
102 refresh_Widget(d);
103}
104
100void clear_ListWidget(iListWidget *d) { 105void 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
126void updateVisible_ListWidget(iListWidget *d) { 131void 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
137void setItemHeight_ListWidget(iListWidget *d, int itemHeight) { 146void 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
196const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) { 205const 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
203iAnyObject *hoverItem_ListWidget(iListWidget *d) { 212const 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
216iAnyObject *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
223iAnyObject *hoverItem_ListWidget(iListWidget *d) {
224 return item_ListWidget(d, d->hoverItem);
225}
226
210static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { 227static 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)
49void setItemHeight_ListWidget(iListWidget *, int itemHeight); 49void setItemHeight_ListWidget(iListWidget *, int itemHeight);
50 50
51void invalidate_ListWidget (iListWidget *); 51void invalidate_ListWidget (iListWidget *);
52void invalidateItem_ListWidget(iListWidget *, size_t index);
52void clear_ListWidget (iListWidget *); 53void clear_ListWidget (iListWidget *);
53void addItem_ListWidget (iListWidget *, iAnyObject *item); 54void addItem_ListWidget (iListWidget *, iAnyObject *item);
54 55
@@ -62,9 +63,12 @@ void scrollOffset_ListWidget (iListWidget *, int offset);
62void updateVisible_ListWidget (iListWidget *); 63void updateVisible_ListWidget (iListWidget *);
63void updateMouseHover_ListWidget (iListWidget *); 64void updateMouseHover_ListWidget (iListWidget *);
64 65
66iAnyObject *item_ListWidget (iListWidget *, size_t index);
67iAnyObject *hoverItem_ListWidget (iListWidget *);
68
65size_t numItems_ListWidget (const iListWidget *); 69size_t numItems_ListWidget (const iListWidget *);
66size_t itemIndex_ListWidget(const iListWidget *, iInt2 pos); 70size_t itemIndex_ListWidget(const iListWidget *, iInt2 pos);
67const iAnyObject *constHoverItem_ListWidget(const iListWidget *); 71const iAnyObject *constItem_ListWidget (const iListWidget *, size_t index);
68iAnyObject *hoverItem_ListWidget(iListWidget *); 72const iAnyObject *constHoverItem_ListWidget (const iListWidget *);
69 73
70iBool isMouseDown_ListWidget (const iListWidget *); 74iBool 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
30iDeclareType(LookupJob) 37iDeclareType(LookupJob)
31 38
32struct Impl_LookupJob { 39struct Impl_LookupJob {
33 iString term; 40 iRegExp *term;
34 iPtrArray results; 41 iPtrArray results;
35}; 42};
36 43
37static void init_LookupJob(iLookupJob *d) { 44static 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
50iDefineTypeConstruction(LookupJob) 57iDefineTypeConstruction(LookupJob)
51 58
59/*----------------------------------------------------------------------------------------------*/
60
61iDeclareType(LookupItem)
62typedef iListItemClass iLookupItemClass;
63
64struct Impl_LookupItem {
65 iListItem listItem;
66 iLookupResult *result;
67 int font;
68 int fg;
69 iString text;
70 iString command;
71};
72
73static 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
82static void deinit_LookupItem(iLookupItem *d) {
83 deinit_String(&d->command);
84 deinit_String(&d->text);
85 delete_LookupResult(d->result);
86}
87
88static 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
110iBeginDefineSubclass(LookupItem, ListItem)
111 .draw = (iAny *) draw_LookupItem_,
112iEndDefineSubclass(LookupItem)
113
114iDefineObjectConstructionArgs(LookupItem, (const iLookupResult *res), res)
115
116/*----------------------------------------------------------------------------------------------*/
117
52struct Impl_LookupWidget { 118struct 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
129static 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
140static 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
150static iBool matchBookmark_LookupJob_(void *context, const iBookmark *bm) {
151 return bookmarkRelevance_LookupJob_(context, bm) > 0;
152}
153
154static 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
62static iThreadResult worker_LookupWidget_(iThread *thread) { 169static 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
106void deinit_LookupWidget(iLookupWidget *d) { 240void 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
255void 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
121static void draw_LookupWidget_(const iLookupWidget *d) { 268static 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
280static 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
292static 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
307static 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
359static iLookupItem *item_LookupWidget_(iLookupWidget *d, size_t index) {
360 return item_ListWidget(d->list, index);
361}
362
363static 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
126static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { 378static 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. */
27iDeclareWidgetClass(LookupWidget) 27iDeclareWidgetClass(LookupWidget)
28iDeclareObjectConstruction(LookupWidget) 28iDeclareObjectConstruction(LookupWidget)
29 29
30 30void 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
750static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, 750static 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) {
459const iWidget *currentTabPage_Widget(const iWidget *tabs) { 459const 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" },