summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-09-24 16:00:22 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-09-24 16:00:22 +0300
commit53f0596cfd6060cd63cc8e6f92f981672da97ee2 (patch)
tree313eaa75bf3311e368678bac70871d2059abc14a
parent446df26adb969dcb4dd9cf5e171fbb9453a8e27c (diff)
Bookmark folders
The user can now create bookmark folders, and drag bookmarks into them. Folders can also be nested. The bookmark sorting menu item sorts inside the chosen folder/root. Remote bookmark sources appear also as folders, although they cannot be sorted/edited. IssueID #339
-rwxr-xr-xpo/compile.py4
-rw-r--r--po/en.po23
-rw-r--r--res/lang/de.binbin24335 -> 24591 bytes
-rw-r--r--res/lang/en.binbin22936 -> 23192 bytes
-rw-r--r--res/lang/es.binbin25466 -> 25722 bytes
-rw-r--r--res/lang/fi.binbin25471 -> 25727 bytes
-rw-r--r--res/lang/fr.binbin26440 -> 26696 bytes
-rw-r--r--res/lang/ia.binbin25063 -> 25319 bytes
-rw-r--r--res/lang/ie.binbin24825 -> 25081 bytes
-rw-r--r--res/lang/pl.binbin25996 -> 26252 bytes
-rw-r--r--res/lang/ru.binbin37722 -> 37978 bytes
-rw-r--r--res/lang/sr.binbin37379 -> 37635 bytes
-rw-r--r--res/lang/tok.binbin23261 -> 23517 bytes
-rw-r--r--res/lang/zh_Hans.binbin22015 -> 22271 bytes
-rw-r--r--res/lang/zh_Hant.binbin22200 -> 22456 bytes
-rw-r--r--src/app.c20
-rw-r--r--src/bookmarks.c30
-rw-r--r--src/bookmarks.h13
-rw-r--r--src/defs.h2
-rw-r--r--src/ui/keys.c1
-rw-r--r--src/ui/listwidget.c30
-rw-r--r--src/ui/lookupwidget.c3
-rw-r--r--src/ui/sidebarwidget.c170
-rw-r--r--src/ui/util.c8
-rw-r--r--src/ui/window.c3
25 files changed, 247 insertions, 60 deletions
diff --git a/po/compile.py b/po/compile.py
index 178342a6..2b0273b6 100755
--- a/po/compile.py
+++ b/po/compile.py
@@ -101,7 +101,9 @@ def parse_po(src):
101def compile_string(msg_id, msg_str): 101def compile_string(msg_id, msg_str):
102 return msg_id.encode('utf-8') + bytes([0]) + \ 102 return msg_id.encode('utf-8') + bytes([0]) + \
103 msg_str.encode('utf-8') + bytes([0]) 103 msg_str.encode('utf-8') + bytes([0])
104 104
105
106os.chdir(os.path.dirname(__file__))
105 107
106if MODE == 'compile': 108if MODE == 'compile':
107 BASE_STRINGS = {} 109 BASE_STRINGS = {}
diff --git a/po/en.po b/po/en.po
index 63aeb75f..24525b03 100644
--- a/po/en.po
+++ b/po/en.po
@@ -225,7 +225,13 @@ msgid "menu.zoom.reset"
225msgstr "Reset Zoom" 225msgstr "Reset Zoom"
226 226
227msgid "menu.view.split" 227msgid "menu.view.split"
228msgstr "Split View..." 228msgstr "Split View…"
229
230msgid "menu.newfolder"
231msgstr "New Folder…"
232
233msgid "menu.sort.alpha"
234msgstr "Sort Alphabetically"
229 235
230msgid "menu.bookmarks.list" 236msgid "menu.bookmarks.list"
231msgstr "List All Bookmarks" 237msgstr "List All Bookmarks"
@@ -1189,6 +1195,18 @@ msgstr "Icon:"
1189msgid "heading.bookmark.tags" 1195msgid "heading.bookmark.tags"
1190msgstr "SPECIAL TAGS" 1196msgstr "SPECIAL TAGS"
1191 1197
1198msgid "heading.addfolder"
1199msgstr "ADD FOLDER"
1200
1201msgid "dlg.addfolder.defaulttitle"
1202msgstr "New Folder"
1203
1204msgid "dlg.addfolder.prompt"
1205msgstr "Enter the name of the new folder:"
1206
1207msgid "dlg.addfolder"
1208msgstr "Add Folder"
1209
1192msgid "heading.prefs" 1210msgid "heading.prefs"
1193msgstr "PREFERENCES" 1211msgstr "PREFERENCES"
1194 1212
@@ -1564,6 +1582,9 @@ msgstr "Next set of home row key links"
1564msgid "keys.bookmark.add" 1582msgid "keys.bookmark.add"
1565msgstr "Add bookmark" 1583msgstr "Add bookmark"
1566 1584
1585msgid "keys.bookmark.addfolder"
1586msgstr "Add bookmark folder"
1587
1567msgid "keys.subscribe" 1588msgid "keys.subscribe"
1568msgstr "Subscribe to page" 1589msgstr "Subscribe to page"
1569 1590
diff --git a/res/lang/de.bin b/res/lang/de.bin
index 060745b2..4d48c61f 100644
--- a/res/lang/de.bin
+++ b/res/lang/de.bin
Binary files differ
diff --git a/res/lang/en.bin b/res/lang/en.bin
index d30b1cd3..8782920f 100644
--- a/res/lang/en.bin
+++ b/res/lang/en.bin
Binary files differ
diff --git a/res/lang/es.bin b/res/lang/es.bin
index c03b6960..fa86ea3a 100644
--- a/res/lang/es.bin
+++ b/res/lang/es.bin
Binary files differ
diff --git a/res/lang/fi.bin b/res/lang/fi.bin
index 4898c850..f23cbf09 100644
--- a/res/lang/fi.bin
+++ b/res/lang/fi.bin
Binary files differ
diff --git a/res/lang/fr.bin b/res/lang/fr.bin
index 65a17bfe..d4c5cf55 100644
--- a/res/lang/fr.bin
+++ b/res/lang/fr.bin
Binary files differ
diff --git a/res/lang/ia.bin b/res/lang/ia.bin
index 93904f71..ee6533ca 100644
--- a/res/lang/ia.bin
+++ b/res/lang/ia.bin
Binary files differ
diff --git a/res/lang/ie.bin b/res/lang/ie.bin
index 769f38b1..2bbc7ada 100644
--- a/res/lang/ie.bin
+++ b/res/lang/ie.bin
Binary files differ
diff --git a/res/lang/pl.bin b/res/lang/pl.bin
index 36c6e552..651a6231 100644
--- a/res/lang/pl.bin
+++ b/res/lang/pl.bin
Binary files differ
diff --git a/res/lang/ru.bin b/res/lang/ru.bin
index e1479727..1a3f7213 100644
--- a/res/lang/ru.bin
+++ b/res/lang/ru.bin
Binary files differ
diff --git a/res/lang/sr.bin b/res/lang/sr.bin
index 90e04616..184699f1 100644
--- a/res/lang/sr.bin
+++ b/res/lang/sr.bin
Binary files differ
diff --git a/res/lang/tok.bin b/res/lang/tok.bin
index b0be44d4..a77aa161 100644
--- a/res/lang/tok.bin
+++ b/res/lang/tok.bin
Binary files differ
diff --git a/res/lang/zh_Hans.bin b/res/lang/zh_Hans.bin
index d2685429..1fe4392d 100644
--- a/res/lang/zh_Hans.bin
+++ b/res/lang/zh_Hans.bin
Binary files differ
diff --git a/res/lang/zh_Hant.bin b/res/lang/zh_Hant.bin
index 5319e518..244bb3a1 100644
--- a/res/lang/zh_Hant.bin
+++ b/res/lang/zh_Hant.bin
Binary files differ
diff --git a/src/app.c b/src/app.c
index 5ff93f2a..c6918eb5 100644
--- a/src/app.c
+++ b/src/app.c
@@ -2033,6 +2033,7 @@ static void resetFonts_App_(iApp *d) {
2033iBool handleCommand_App(const char *cmd) { 2033iBool handleCommand_App(const char *cmd) {
2034 iApp *d = &app_; 2034 iApp *d = &app_;
2035 const iBool isFrozen = !d->window || d->window->isDrawFrozen; 2035 const iBool isFrozen = !d->window || d->window->isDrawFrozen;
2036 /* TODO: Maybe break this up a little bit? There's a very long list of ifs here. */
2036 if (equal_Command(cmd, "config.error")) { 2037 if (equal_Command(cmd, "config.error")) {
2037 makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR", 2038 makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR",
2038 format_CStr("Error in config file: %s\n" 2039 format_CStr("Error in config file: %s\n"
@@ -2764,6 +2765,25 @@ iBool handleCommand_App(const char *cmd) {
2764 makeFeedSettings_Widget(findUrl_Bookmarks(d->bookmarks, url)); 2765 makeFeedSettings_Widget(findUrl_Bookmarks(d->bookmarks, url));
2765 return iTrue; 2766 return iTrue;
2766 } 2767 }
2768 else if (equal_Command(cmd, "bookmarks.addfolder")) {
2769 if (suffixPtr_Command(cmd, "value")) {
2770 add_Bookmarks(d->bookmarks, NULL, collect_String(suffix_Command(cmd, "value")), NULL, 0);
2771 postCommand_App("bookmarks.changed");
2772 }
2773 else {
2774 iWidget *dlg = makeValueInput_Widget(get_Root()->widget,
2775 collectNewCStr_String(cstr_Lang("dlg.addfolder.defaulttitle")),
2776 uiHeading_ColorEscape "${heading.addfolder}", "${dlg.addfolder.prompt}",
2777 uiTextAction_ColorEscape "${dlg.addfolder}", "bookmarks.addfolder");
2778 setSelectAllOnFocus_InputWidget(findChild_Widget(dlg, "input"), iTrue);
2779 }
2780 return iTrue;
2781 }
2782 else if (equal_Command(cmd, "bookmarks.sort")) {
2783 sort_Bookmarks(d->bookmarks, arg_Command(cmd), cmpTitleAscending_Bookmark);
2784 postCommand_App("bookmarks.changed");
2785 return iTrue;
2786 }
2767 else if (equal_Command(cmd, "bookmarks.reload.remote")) { 2787 else if (equal_Command(cmd, "bookmarks.reload.remote")) {
2768 fetchRemote_Bookmarks(bookmarks_App()); 2788 fetchRemote_Bookmarks(bookmarks_App());
2769 return iTrue; 2789 return iTrue;
diff --git a/src/bookmarks.c b/src/bookmarks.c
index 616e4632..f7691655 100644
--- a/src/bookmarks.c
+++ b/src/bookmarks.c
@@ -79,7 +79,7 @@ static int cmpTimeDescending_Bookmark_(const iBookmark **a, const iBookmark **b)
79 return iCmp(seconds_Time(&(*b)->when), seconds_Time(&(*a)->when)); 79 return iCmp(seconds_Time(&(*b)->when), seconds_Time(&(*a)->when));
80} 80}
81 81
82static int cmpTitleAscending_Bookmark_(const iBookmark **a, const iBookmark **b) { 82int cmpTitleAscending_Bookmark(const iBookmark **a, const iBookmark **b) {
83 return cmpStringCase_String(&(*a)->title, &(*b)->title); 83 return cmpStringCase_String(&(*a)->title, &(*b)->title);
84} 84}
85 85
@@ -250,7 +250,20 @@ static void load_BookmarkLoader(iBookmarkLoader *d, iFile *file) {
250iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b) 250iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b)
251 251
252/*----------------------------------------------------------------------------------------------*/ 252/*----------------------------------------------------------------------------------------------*/
253 253
254static iBool isMatchingParent_Bookmark_(void *context, const iBookmark *bm) {
255 return bm->parentId == *(const uint32_t *) context;
256}
257
258void sort_Bookmarks(iBookmarks *d, uint32_t parentId, iBookmarksCompareFunc cmp) {
259 lock_Mutex(d->mtx);
260 iConstForEach(PtrArray, i, list_Bookmarks(d, cmp, isMatchingParent_Bookmark_, &parentId)) {
261 iBookmark *bm = i.ptr;
262 bm->order = index_PtrArrayConstIterator(&i) + 1;
263 }
264 unlock_Mutex(d->mtx);
265}
266
254void load_Bookmarks(iBookmarks *d, const char *dirPath) { 267void load_Bookmarks(iBookmarks *d, const char *dirPath) {
255 clear_Bookmarks(d); 268 clear_Bookmarks(d);
256 /* Load new .ini bookmarks, if present. */ 269 /* Load new .ini bookmarks, if present. */
@@ -258,11 +271,8 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) {
258 if (!open_File(f, readOnly_FileMode | text_FileMode)) { 271 if (!open_File(f, readOnly_FileMode | text_FileMode)) {
259 /* As a fallback, try loading the v1.6 bookmarks file. */ 272 /* As a fallback, try loading the v1.6 bookmarks file. */
260 loadOldFormat_Bookmarks(d, dirPath); 273 loadOldFormat_Bookmarks(d, dirPath);
261 /* Set ordering based on titles. */ 274 /* Old format has an implicit alphabetic sort order. */
262 iConstForEach(PtrArray, i, list_Bookmarks(d, cmpTitleAscending_Bookmark_, NULL, NULL)) { 275 sort_Bookmarks(d, 0, cmpTitleAscending_Bookmark);
263 iBookmark *bm = i.ptr;
264 bm->order = index_PtrArrayConstIterator(&i) + 1;
265 }
266 return; 276 return;
267 } 277 }
268 iBookmarkLoader loader; 278 iBookmarkLoader loader;
@@ -317,7 +327,9 @@ uint32_t add_Bookmarks(iBookmarks *d, const iString *url, const iString *title,
317 iChar icon) { 327 iChar icon) {
318 lock_Mutex(d->mtx); 328 lock_Mutex(d->mtx);
319 iBookmark *bm = new_Bookmark(); 329 iBookmark *bm = new_Bookmark();
320 set_String(&bm->url, canonicalUrl_String(url)); 330 if (url) {
331 set_String(&bm->url, canonicalUrl_String(url));
332 }
321 set_String(&bm->title, title); 333 set_String(&bm->title, title);
322 if (tags) { 334 if (tags) {
323 set_String(&bm->tags, tags); 335 set_String(&bm->tags, tags);
@@ -471,7 +483,7 @@ const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkLis
471 const iPtrArray *bmList = list_Bookmarks(d, 483 const iPtrArray *bmList = list_Bookmarks(d,
472 listType == listByCreationTime_BookmarkListType 484 listType == listByCreationTime_BookmarkListType
473 ? cmpTimeDescending_Bookmark_ 485 ? cmpTimeDescending_Bookmark_
474 : cmpTitleAscending_Bookmark_, 486 : cmpTitleAscending_Bookmark,
475 NULL, 487 NULL,
476 NULL); 488 NULL);
477 iConstForEach(PtrArray, i, bmList) { 489 iConstForEach(PtrArray, i, bmList) {
diff --git a/src/bookmarks.h b/src/bookmarks.h
index 0de930d7..40170062 100644
--- a/src/bookmarks.h
+++ b/src/bookmarks.h
@@ -53,7 +53,8 @@ struct Impl_Bookmark {
53 int order; /* sort order */ 53 int order; /* sort order */
54}; 54};
55 55
56iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; } 56iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; }
57iLocalDef iBool isFolder_Bookmark (const iBookmark *d) { return isEmpty_String(&d->url); }
57 58
58iBool hasTag_Bookmark (const iBookmark *, const char *tag); 59iBool hasTag_Bookmark (const iBookmark *, const char *tag);
59void addTag_Bookmark (iBookmark *, const char *tag); 60void addTag_Bookmark (iBookmark *, const char *tag);
@@ -73,11 +74,17 @@ iLocalDef void addOrRemoveTag_Bookmark(iBookmark *d, const char *tag, iBool add)
73 } 74 }
74} 75}
75 76
77int cmpTitleAscending_Bookmark (const iBookmark **, const iBookmark **);
78int cmpTree_Bookmark (const iBookmark **, const iBookmark **);
79
76/*----------------------------------------------------------------------------------------------*/ 80/*----------------------------------------------------------------------------------------------*/
77 81
78iDeclareType(Bookmarks) 82iDeclareType(Bookmarks)
79iDeclareTypeConstruction(Bookmarks) 83iDeclareTypeConstruction(Bookmarks)
80 84
85typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *);
86typedef int (*iBookmarksCompareFunc) (const iBookmark **, const iBookmark **);
87
81void clear_Bookmarks (iBookmarks *); 88void clear_Bookmarks (iBookmarks *);
82void load_Bookmarks (iBookmarks *, const char *dirPath); 89void load_Bookmarks (iBookmarks *, const char *dirPath);
83void save_Bookmarks (const iBookmarks *, const char *dirPath); 90void save_Bookmarks (const iBookmarks *, const char *dirPath);
@@ -88,15 +95,13 @@ iBool remove_Bookmarks (iBookmarks *, uint32_t id);
88iBookmark * get_Bookmarks (iBookmarks *, uint32_t id); 95iBookmark * get_Bookmarks (iBookmarks *, uint32_t id);
89void reorder_Bookmarks (iBookmarks *, uint32_t id, int newOrder); 96void reorder_Bookmarks (iBookmarks *, uint32_t id, int newOrder);
90iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon); 97iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon);
98void sort_Bookmarks (iBookmarks *, uint32_t parentId, iBookmarksCompareFunc cmp);
91void fetchRemote_Bookmarks (iBookmarks *); 99void fetchRemote_Bookmarks (iBookmarks *);
92void requestFinished_Bookmarks (iBookmarks *, iGmRequest *req); 100void requestFinished_Bookmarks (iBookmarks *, iGmRequest *req);
93 101
94iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url); 102iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url);
95uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */ 103uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */
96 104
97typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *);
98typedef int (*iBookmarksCompareFunc)(const iBookmark **, const iBookmark **);
99
100iBool filterTagsRegExp_Bookmarks (void *regExp, const iBookmark *); 105iBool filterTagsRegExp_Bookmarks (void *regExp, const iBookmark *);
101 106
102/** 107/**
diff --git a/src/defs.h b/src/defs.h
index 01bf2b3d..f199fd2b 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -107,6 +107,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {
107#define rightArrow_Icon "\u279e" 107#define rightArrow_Icon "\u279e"
108#define barLeftArrow_Icon "\u21a4" 108#define barLeftArrow_Icon "\u21a4"
109#define barRightArrow_Icon "\u21a6" 109#define barRightArrow_Icon "\u21a6"
110#define upDownArrow_Icon "\u21c5"
110#define clock_Icon "\U0001f553" 111#define clock_Icon "\U0001f553"
111#define pin_Icon "\U0001f588" 112#define pin_Icon "\U0001f588"
112#define star_Icon "\u2605" 113#define star_Icon "\u2605"
@@ -155,6 +156,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {
155#define return_Icon "\u23ce" 156#define return_Icon "\u23ce"
156#define undo_Icon "\u23ea" 157#define undo_Icon "\u23ea"
157#define select_Icon "\u2b1a" 158#define select_Icon "\u2b1a"
159#define downAngle_Icon "\ufe40"
158 160
159#if defined (iPlatformApple) 161#if defined (iPlatformApple)
160# define shift_Icon "\u21e7" 162# define shift_Icon "\u21e7"
diff --git a/src/ui/keys.c b/src/ui/keys.c
index 6de30f57..30072572 100644
--- a/src/ui/keys.c
+++ b/src/ui/keys.c
@@ -213,6 +213,7 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] =
213 { 46, { "${keys.link.homerow.hover}", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 }, 213 { 46, { "${keys.link.homerow.hover}", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 },
214 { 47, { "${keys.link.homerow.next}", '.', 0, "document.linkkeys more:1" }, 0 }, 214 { 47, { "${keys.link.homerow.next}", '.', 0, "document.linkkeys more:1" }, 0 },
215 { 50, { "${keys.bookmark.add}", 'd', KMOD_PRIMARY, "bookmark.add" }, 0 }, 215 { 50, { "${keys.bookmark.add}", 'd', KMOD_PRIMARY, "bookmark.add" }, 0 },
216 { 51, { "${keys.bookmark.addfolder}", 'n', KMOD_SHIFT, "bookmarks.addfolder" }, 0 },
216 { 55, { "${keys.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, 0 }, 217 { 55, { "${keys.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, 0 },
217 { 60, { "${keys.findtext}", 'f', KMOD_PRIMARY, "focus.set id:find.input" }, 0 }, 218 { 60, { "${keys.findtext}", 'f', KMOD_PRIMARY, "focus.set id:find.input" }, 0 },
218 { 70, { "${keys.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 }, 219 { 70, { "${keys.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 },
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c
index a34f3d03..f896b493 100644
--- a/src/ui/listwidget.c
+++ b/src/ui/listwidget.c
@@ -331,8 +331,8 @@ static size_t resolveDragDestination_ListWidget_(const iListWidget *d, iInt2 dst
331 const iRect rect = itemRect_ListWidget(d, index); 331 const iRect rect = itemRect_ListWidget(d, index);
332 const iRangei span = ySpan_Rect(rect); 332 const iRangei span = ySpan_Rect(rect);
333 if (item->isDropTarget) { 333 if (item->isDropTarget) {
334 const int pad = size_Range(&span) / 4; 334 const int pad = size_Range(&span) / 3;
335 if (dstPos.y >= span.start + pad && dstPos.y < span.end) { 335 if (dstPos.y >= span.start + pad && dstPos.y < span.end - pad) {
336 *isOnto = iTrue; 336 *isOnto = iTrue;
337 return index; 337 return index;
338 } 338 }
@@ -352,11 +352,13 @@ static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) {
352 stop_Anim(&d->scrollY.pos); 352 stop_Anim(&d->scrollY.pos);
353 iBool isOnto; 353 iBool isOnto;
354 const size_t index = resolveDragDestination_ListWidget_(d, endPos, &isOnto); 354 const size_t index = resolveDragDestination_ListWidget_(d, endPos, &isOnto);
355 if (isOnto) { 355 if (index != d->dragItem) {
356 postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index); 356 if (isOnto) {
357 } 357 postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index);
358 else if (index != d->dragItem) { 358 }
359 postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index); 359 else {
360 postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index);
361 }
360 } 362 }
361 invalidateItem_ListWidget(d, d->dragItem); 363 invalidateItem_ListWidget(d, d->dragItem);
362 d->dragItem = iInvalidPos; 364 d->dragItem = iInvalidPos;
@@ -394,7 +396,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
394 } 396 }
395 else if (d->dragItem != iInvalidPos) { 397 else if (d->dragItem != iInvalidPos) {
396 /* Start scrolling if near the ends. */ 398 /* Start scrolling if near the ends. */
397 const int zone = d->itemHeight; 399 const int zone = 2 * d->itemHeight;
398 const iRect bounds = bounds_Widget(w); 400 const iRect bounds = bounds_Widget(w);
399 float scrollSpeed = 0.0f; 401 float scrollSpeed = 0.0f;
400 if (mousePos.y > bottom_Rect(bounds) - zone) { 402 if (mousePos.y > bottom_Rect(bounds) - zone) {
@@ -410,7 +412,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
410 } 412 }
411 else { 413 else {
412 setValueSpeed_Anim(&d->scrollY.pos, scrollSpeed < 0 ? 0 : scrollMax_ListWidget_(d), 414 setValueSpeed_Anim(&d->scrollY.pos, scrollSpeed < 0 ? 0 : scrollMax_ListWidget_(d),
413 iAbs(scrollSpeed * gap_UI * 100)); 415 scrollSpeed * scrollSpeed * gap_UI * 400);
414 refreshWhileScrolling_ListWidget_(d); 416 refreshWhileScrolling_ListWidget_(d);
415 } 417 }
416 } 418 }
@@ -451,7 +453,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
451 ((const iListItem *) item_ListWidget(d, over))->isDraggable) { 453 ((const iListItem *) item_ListWidget(d, over))->isDraggable) {
452 d->dragItem = over; 454 d->dragItem = over;
453 d->dragOrigin = sub_I2(topLeft_Rect(itemRect_ListWidget(d, over)), 455 d->dragOrigin = sub_I2(topLeft_Rect(itemRect_ListWidget(d, over)),
454 pos_Click(&d->click)); 456 d->click.startPos);
455 invalidateItem_ListWidget(d, d->dragItem); 457 invalidateItem_ListWidget(d, d->dragItem);
456 } 458 }
457 } 459 }
@@ -569,20 +571,22 @@ static void draw_ListWidget_(const iListWidget *d) {
569 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); 571 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
570 iBool dstOnto; 572 iBool dstOnto;
571 const size_t dstIndex = resolveDragDestination_ListWidget_(d, mousePos, &dstOnto); 573 const size_t dstIndex = resolveDragDestination_ListWidget_(d, mousePos, &dstOnto);
572 if (dstIndex != d->dragItem && dstIndex != d->dragItem + 1) { 574 if (dstIndex != d->dragItem) {
573 const iRect dstRect = itemRect_ListWidget(d, dstIndex); 575 const iRect dstRect = itemRect_ListWidget(d, dstIndex);
574 p.alpha = 0xff; 576 p.alpha = 0xff;
575 if (dstOnto) { 577 if (dstOnto) {
576 fillRect_Paint(&p, dstRect, uiTextAction_ColorId); 578 drawRectThickness_Paint(&p, dstRect, gap_UI / 2, uiTextAction_ColorId);
577 } 579 }
578 else { 580 else if (dstIndex != d->dragItem + 1) {
579 fillRect_Paint(&p, (iRect){ addY_I2(dstRect.pos, -gap_UI / 4), 581 fillRect_Paint(&p, (iRect){ addY_I2(dstRect.pos, -gap_UI / 4),
580 init_I2(width_Rect(dstRect), gap_UI / 2) }, 582 init_I2(width_Rect(dstRect), gap_UI / 2) },
581 uiTextAction_ColorId); 583 uiTextAction_ColorId);
582 } 584 }
583 } 585 }
584 p.alpha = 0x80; 586 p.alpha = 0x80;
587 setOpacity_Text(0.5f);
585 class_ListItem(item)->draw(item, &p, itemRect, d); 588 class_ListItem(item)->draw(item, &p, itemRect, d);
589 setOpacity_Text(1.0f);
586 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 590 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
587 } 591 }
588 unsetClip_Paint(&p); 592 unsetClip_Paint(&p);
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c
index 85217336..ab649eee 100644
--- a/src/ui/lookupwidget.c
+++ b/src/ui/lookupwidget.c
@@ -171,6 +171,9 @@ static float scoreMatch_(const iRegExp *pattern, iRangecc text) {
171} 171}
172 172
173static float bookmarkRelevance_LookupJob_(const iLookupJob *d, const iBookmark *bm) { 173static float bookmarkRelevance_LookupJob_(const iLookupJob *d, const iBookmark *bm) {
174 if (isFolder_Bookmark(bm)) {
175 return 0.0f;
176 }
174 iUrl parts; 177 iUrl parts;
175 init_Url(&parts, &bm->url); 178 init_Url(&parts, &bm->url);
176 const float t = scoreMatch_(d->term, range_String(&bm->title)); 179 const float t = scoreMatch_(d->term, range_String(&bm->title));
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 20e43153..6c2934ec 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -108,6 +108,7 @@ struct Impl_SidebarWidget {
108 iWidget * menu; 108 iWidget * menu;
109 iSidebarItem * contextItem; /* list item accessed in the context menu */ 109 iSidebarItem * contextItem; /* list item accessed in the context menu */
110 size_t contextIndex; /* index of list item accessed in the context menu */ 110 size_t contextIndex; /* index of list item accessed in the context menu */
111 iIntSet closedFolders; /* otherwise open */
111}; 112};
112 113
113iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) 114iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side)
@@ -116,26 +117,67 @@ static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) {
116 return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; 117 return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0;
117} 118}
118 119
119static int cmpTree_Bookmark_(const iBookmark **a, const iBookmark **b) { 120iBookmark *parent_Bookmark(const iBookmark *d) {
121 /* TODO: Parent pointers should be prefetched! */
122 if (d->parentId) {
123 return get_Bookmarks(bookmarks_App(), d->parentId);
124 }
125 return NULL;
126}
127
128iBool hasParent_Bookmark(const iBookmark *d, uint32_t parentId) {
129 /* TODO: Parent pointers should be prefetched! */
130 while (d->parentId) {
131 if (d->parentId == parentId) {
132 return iTrue;
133 }
134 d = get_Bookmarks(bookmarks_App(), d->parentId);
135 }
136 return iFalse;
137}
138
139int depth_Bookmark(const iBookmark *d) {
140 /* TODO: Precalculate this! */
141 int depth = 0;
142 for (; d->parentId; depth++) {
143 d = get_Bookmarks(bookmarks_App(), d->parentId);
144 }
145 return depth;
146}
147
148int cmpTree_Bookmark(const iBookmark **a, const iBookmark **b) {
120 const iBookmark *bm1 = *a, *bm2 = *b; 149 const iBookmark *bm1 = *a, *bm2 = *b;
121 if (bm2->parentId == id_Bookmark(bm1)) { 150 /* Contents of a parent come after it. */
151 if (hasParent_Bookmark(bm2, id_Bookmark(bm1))) {
122 return -1; 152 return -1;
123 } 153 }
124 if (bm1->parentId == id_Bookmark(bm2)) { 154 if (hasParent_Bookmark(bm1, id_Bookmark(bm2))) {
125 return 1; 155 return 1;
126 } 156 }
127 if (bm1->parentId == bm2->parentId) { 157 /* Comparisons are only valid inside the same parent. */
128 //return cmpStringCase_String(&bm1->title, &bm2->title); 158 while (bm1->parentId != bm2->parentId) {
129 return iCmp(bm1->order, bm2->order); 159 int depth1 = depth_Bookmark(bm1);
130 } 160 int depth2 = depth_Bookmark(bm2);
131 if (bm1->parentId) { 161 if (depth1 != depth2) {
132 bm1 = get_Bookmarks(bookmarks_App(), bm1->parentId); 162 /* Equalize the depth. */
133 } 163 while (depth1 > depth2) {
134 if (bm2->parentId) { 164 bm1 = parent_Bookmark(bm1);
135 bm2 = get_Bookmarks(bookmarks_App(), bm2->parentId); 165 depth1--;
166 }
167 while (depth2 > depth1) {
168 bm2 = parent_Bookmark(bm2);
169 depth2--;
170 }
171 continue;
172 }
173 bm1 = parent_Bookmark(bm1);
174 depth1--;
175 bm2 = parent_Bookmark(bm2);
176 depth2--;
136 } 177 }
137// return cmpStringCase_String(&bm1->title, &bm2->title); 178 const int cmp = iCmp(bm1->order, bm2->order);
138 return iCmp(bm1->order, bm2->order); 179 if (cmp) return cmp;
180 return cmpStringCase_String(&bm1->title, &bm2->title);
139} 181}
140 182
141static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const char *label, 183static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const char *label,
@@ -211,6 +253,16 @@ static void updateContextMenu_SidebarWidget_(iSidebarWidget *d) {
211 d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); 253 d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items));
212} 254}
213 255
256static iBool isBookmarkFolded_SidebarWidget_(const iSidebarWidget *d, const iBookmark *bm) {
257 while (bm->parentId) {
258 if (contains_IntSet(&d->closedFolders, bm->parentId)) {
259 return iTrue;
260 }
261 bm = get_Bookmarks(bookmarks_App(), bm->parentId);
262 }
263 return iFalse;
264}
265
214static void updateItems_SidebarWidget_(iSidebarWidget *d) { 266static void updateItems_SidebarWidget_(iSidebarWidget *d) {
215 clear_ListWidget(d->list); 267 clear_ListWidget(d->list);
216 releaseChildren_Widget(d->blank); 268 releaseChildren_Widget(d->blank);
@@ -334,12 +386,22 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
334 iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption)); 386 iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption));
335 iRegExp *remoteTag = iClob(new_RegExp("\\b" remote_BookmarkTag "\\b", caseSensitive_RegExpOption)); 387 iRegExp *remoteTag = iClob(new_RegExp("\\b" remote_BookmarkTag "\\b", caseSensitive_RegExpOption));
336 iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption)); 388 iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption));
337 iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark_, NULL, NULL)) { 389 iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark, NULL, NULL)) {
338 const iBookmark *bm = i.ptr; 390 const iBookmark *bm = i.ptr;
391 if (isBookmarkFolded_SidebarWidget_(d, bm)) {
392 continue; /* inside a closed folder */
393 }
339 iSidebarItem *item = new_SidebarItem(); 394 iSidebarItem *item = new_SidebarItem();
340 item->listItem.isDraggable = iTrue; 395 item->listItem.isDraggable = iTrue;
396 item->isBold = item->listItem.isDropTarget = isFolder_Bookmark(bm);
341 item->id = id_Bookmark(bm); 397 item->id = id_Bookmark(bm);
342 item->icon = bm->icon; 398 item->indent = depth_Bookmark(bm);
399 if (isFolder_Bookmark(bm)) {
400 item->icon = contains_IntSet(&d->closedFolders, item->id) ? 0x27e9 : 0xfe40;
401 }
402 else {
403 item->icon = bm->icon;
404 }
343 set_String(&item->url, &bm->url); 405 set_String(&item->url, &bm->url);
344 set_String(&item->label, &bm->title); 406 set_String(&item->label, &bm->title);
345 /* Icons for special tags. */ { 407 /* Icons for special tags. */ {
@@ -384,8 +446,11 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
384 { "---", 0, 0, NULL }, 446 { "---", 0, 0, NULL },
385 { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, 447 { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" },
386 { "---", 0, 0, NULL }, 448 { "---", 0, 0, NULL },
387 { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, 449 { add_Icon " ${menu.newfolder}", 0, 0, "bookmarks.addfolder" },
388 14); 450 { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" },
451 { "---", 0, 0, NULL },
452 { upDownArrow_Icon " ${menu.sort.alpha}", 0, 0, "bookmark.sortfolder" } },
453 17);
389 break; 454 break;
390 } 455 }
391 case history_SidebarMode: { 456 case history_SidebarMode: {
@@ -671,6 +736,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
671 d->resizer = NULL; 736 d->resizer = NULL;
672 d->list = NULL; 737 d->list = NULL;
673 d->actions = NULL; 738 d->actions = NULL;
739 init_IntSet(&d->closedFolders);
674 /* On a phone, the right sidebar is used exclusively for Identities. */ 740 /* On a phone, the right sidebar is used exclusively for Identities. */
675 const iBool isPhone = deviceType_App() == phone_AppDeviceType; 741 const iBool isPhone = deviceType_App() == phone_AppDeviceType;
676 if (!isPhone || d->side == left_SidebarSide) { 742 if (!isPhone || d->side == left_SidebarSide) {
@@ -754,6 +820,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
754 820
755void deinit_SidebarWidget(iSidebarWidget *d) { 821void deinit_SidebarWidget(iSidebarWidget *d) {
756 deinit_String(&d->cmdPrefix); 822 deinit_String(&d->cmdPrefix);
823 deinit_IntSet(&d->closedFolders);
757} 824}
758 825
759iBool setButtonFont_SidebarWidget(iSidebarWidget *d, int font) { 826iBool setButtonFont_SidebarWidget(iSidebarWidget *d, int font) {
@@ -801,6 +868,17 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si
801 break; 868 break;
802 } 869 }
803 case bookmarks_SidebarMode: 870 case bookmarks_SidebarMode:
871 if (isEmpty_String(&item->url)) /* a folder */ {
872 if (contains_IntSet(&d->closedFolders, item->id)) {
873 remove_IntSet(&d->closedFolders, item->id);
874 }
875 else {
876 insert_IntSet(&d->closedFolders, item->id);
877 }
878 updateItems_SidebarWidget_(d);
879 break;
880 }
881 /* fall through */
804 case history_SidebarMode: { 882 case history_SidebarMode: {
805 if (!isEmpty_String(&item->url)) { 883 if (!isEmpty_String(&item->url)) {
806 postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s", 884 postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s",
@@ -1013,10 +1091,24 @@ static void bookmarkMoved_SidebarWidget_(iSidebarWidget *d, size_t index, size_t
1013 isLast ? numItems_ListWidget(d->list) - 1 1091 isLast ? numItems_ListWidget(d->list) - 1
1014 : beforeIndex); 1092 : beforeIndex);
1015 const iBookmark *dst = get_Bookmarks(bookmarks_App(), dstItem->id); 1093 const iBookmark *dst = get_Bookmarks(bookmarks_App(), dstItem->id);
1094 if (hasParent_Bookmark(dst, movingItem->id)) {
1095 return;
1096 }
1016 reorder_Bookmarks(bookmarks_App(), movingItem->id, dst->order + (isLast ? 1 : 0)); 1097 reorder_Bookmarks(bookmarks_App(), movingItem->id, dst->order + (isLast ? 1 : 0));
1098 get_Bookmarks(bookmarks_App(), movingItem->id)->parentId = dst->parentId;
1017 updateItems_SidebarWidget_(d); 1099 updateItems_SidebarWidget_(d);
1018 /* Don't confuse the user: keep the dragged item in hover state. */ 1100 /* Don't confuse the user: keep the dragged item in hover state. */
1019 setHoverItem_ListWidget(d->list, index < beforeIndex ? beforeIndex - 1 : beforeIndex); 1101 setHoverItem_ListWidget(d->list, index < beforeIndex ? beforeIndex - 1 : beforeIndex);
1102 postCommandf_App("bookmarks.changed nosidebar:%p", d); /* skip this sidebar since we updated already */
1103}
1104
1105static void bookmarkMovedOntoFolder_SidebarWidget_(iSidebarWidget *d, size_t index,
1106 size_t folderIndex) {
1107 const iSidebarItem *movingItem = item_ListWidget(d->list, index);
1108 const iSidebarItem *dstItem = item_ListWidget(d->list, folderIndex);
1109 iBookmark *bm = get_Bookmarks(bookmarks_App(), movingItem->id);
1110 bm->parentId = dstItem->id;
1111 postCommand_App("bookmarks.changed");
1020} 1112}
1021 1113
1022static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { 1114static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) {
@@ -1070,7 +1162,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1070 } 1162 }
1071 else if (equal_Command(cmd, "bookmarks.changed") && (d->mode == bookmarks_SidebarMode || 1163 else if (equal_Command(cmd, "bookmarks.changed") && (d->mode == bookmarks_SidebarMode ||
1072 d->mode == feeds_SidebarMode)) { 1164 d->mode == feeds_SidebarMode)) {
1073 updateItems_SidebarWidget_(d); 1165 if (pointerLabel_Command(cmd, "nosidebar") != d) {
1166 updateItems_SidebarWidget_(d);
1167 }
1074 } 1168 }
1075 else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) { 1169 else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) {
1076 updateItems_SidebarWidget_(d); 1170 updateItems_SidebarWidget_(d);
@@ -1135,6 +1229,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1135 } 1229 }
1136 else { 1230 else {
1137 /* Dragged onto a folder. */ 1231 /* Dragged onto a folder. */
1232 bookmarkMovedOntoFolder_SidebarWidget_(d,
1233 argU32Label_Command(cmd, "arg"),
1234 argU32Label_Command(cmd, "onto"));
1138 } 1235 }
1139 return iTrue; 1236 return iTrue;
1140 } 1237 }
@@ -1170,15 +1267,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1170 setText_InputWidget(findChild_Widget(dlg, "bmed.icon"), 1267 setText_InputWidget(findChild_Widget(dlg, "bmed.icon"),
1171 collect_String(newUnicodeN_String(&bm->icon, 1))); 1268 collect_String(newUnicodeN_String(&bm->icon, 1)));
1172 } 1269 }
1173 setFlags_Widget(findChild_Widget(dlg, "bmed.tag.home"), 1270 setToggle_Widget(findChild_Widget(dlg, "bmed.tag.home"),
1174 selected_WidgetFlag, 1271 hasTag_Bookmark(bm, homepage_BookmarkTag));
1175 hasTag_Bookmark(bm, homepage_BookmarkTag)); 1272 setToggle_Widget(findChild_Widget(dlg, "bmed.tag.remote"),
1176 setFlags_Widget(findChild_Widget(dlg, "bmed.tag.remote"), 1273 hasTag_Bookmark(bm, remoteSource_BookmarkTag));
1177 selected_WidgetFlag, 1274 setToggle_Widget(findChild_Widget(dlg, "bmed.tag.linksplit"),
1178 hasTag_Bookmark(bm, remoteSource_BookmarkTag)); 1275 hasTag_Bookmark(bm, linkSplit_BookmarkTag));
1179 setFlags_Widget(findChild_Widget(dlg, "bmed.tag.linksplit"),
1180 selected_WidgetFlag,
1181 hasTag_Bookmark(bm, linkSplit_BookmarkTag));
1182 setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_); 1276 setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_);
1183 setFocus_Widget(findChild_Widget(dlg, "bmed.title")); 1277 setFocus_Widget(findChild_Widget(dlg, "bmed.title"));
1184 } 1278 }
@@ -1225,6 +1319,16 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1225 } 1319 }
1226 return iTrue; 1320 return iTrue;
1227 } 1321 }
1322 else if (isCommand_Widget(w, ev, "bookmark.sortfolder")) {
1323 const iSidebarItem *item = d->contextItem;
1324 if (d->mode == bookmarks_SidebarMode && item) {
1325 postCommandf_App("bookmarks.sort arg:%zu",
1326 item->listItem.isDropTarget
1327 ? item->id
1328 : get_Bookmarks(bookmarks_App(), item->id)->parentId);
1329 }
1330 return iTrue;
1331 }
1228 else if (equal_Command(cmd, "feeds.update.finished")) { 1332 else if (equal_Command(cmd, "feeds.update.finished")) {
1229 d->numUnreadEntries = argLabel_Command(cmd, "unread"); 1333 d->numUnreadEntries = argLabel_Command(cmd, "unread");
1230 checkModeButtonLayout_SidebarWidget_(d); 1334 checkModeButtonLayout_SidebarWidget_(d);
@@ -1638,7 +1742,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1638 fillRect_Paint(p, itemRect, bg); 1742 fillRect_Paint(p, itemRect, bg);
1639 } 1743 }
1640 else if (sidebar->mode == bookmarks_SidebarMode) { 1744 else if (sidebar->mode == bookmarks_SidebarMode) {
1641 if (d->icon == 0x2913) { /* TODO: Remote icon; meaning: is this in a folder? */ 1745 if (d->indent) /* remote icon */ {
1642 bg = uiBackgroundFolder_ColorId; 1746 bg = uiBackgroundFolder_ColorId;
1643 fillRect_Paint(p, itemRect, bg); 1747 fillRect_Paint(p, itemRect, bg);
1644 } 1748 }
@@ -1740,11 +1844,13 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1740 } 1844 }
1741 else if (sidebar->mode == bookmarks_SidebarMode) { 1845 else if (sidebar->mode == bookmarks_SidebarMode) {
1742 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) 1846 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
1743 : uiText_ColorId; 1847 : d->listItem.isDropTarget ? uiHeading_ColorId : uiText_ColorId;
1848 /* The icon. */
1744 iString str; 1849 iString str;
1745 init_String(&str); 1850 init_String(&str);
1746 appendChar_String(&str, d->icon ? d->icon : 0x1f588); 1851 appendChar_String(&str, d->icon ? d->icon : 0x1f588);
1747 const iRect iconArea = { addX_I2(pos, gap_UI), 1852 const int leftIndent = d->indent * gap_UI * 4;
1853 const iRect iconArea = { addX_I2(pos, gap_UI + leftIndent),
1748 init_I2(1.75f * lineHeight_Text(font), itemHeight) }; 1854 init_I2(1.75f * lineHeight_Text(font), itemHeight) };
1749 drawCentered_Text(font, 1855 drawCentered_Text(font,
1750 iconArea, 1856 iconArea,
diff --git a/src/ui/util.c b/src/ui/util.c
index 7fa5d675..5b9f15a9 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -816,6 +816,14 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) {
816 setUserData_Object(menu, deepCopyMenuItems_(menu, items, n)); 816 setUserData_Object(menu, deepCopyMenuItems_(menu, items, n));
817 addChild_Widget(parent, menu); 817 addChild_Widget(parent, menu);
818 iRelease(menu); /* owned by parent now */ 818 iRelease(menu); /* owned by parent now */
819 /* Keyboard shortcuts still need to triggerable via the menu, although the items don't exist. */ {
820 for (size_t i = 0; i < n; i++) {
821 const iMenuItem *item = &items[i];
822 if (item->key) {
823 addAction_Widget(menu, item->key, item->kmods, item->command);
824 }
825 }
826 }
819#else 827#else
820 /* Non-native custom popup menu. This may still be displayed inside a separate window. */ 828 /* Non-native custom popup menu. This may still be displayed inside a separate window. */
821 setDrawBufferEnabled_Widget(menu, iTrue); 829 setDrawBufferEnabled_Widget(menu, iTrue);
diff --git a/src/ui/window.c b/src/ui/window.c
index 0863aa47..5941ef5f 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -120,6 +120,7 @@ static const iMenuItem viewMenuItems_[] = {
120static iMenuItem bookmarksMenuItems_[] = { 120static iMenuItem bookmarksMenuItems_[] = {
121 { "${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, 121 { "${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" },
122 { "${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, 122 { "${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" },
123 { "${menu.newfolder}", 0, 0, "bookmarks.addfolder" },
123 { "---", 0, 0, NULL }, 124 { "---", 0, 0, NULL },
124 { "${menu.import.links}", 0, 0, "bookmark.links confirm:1" }, 125 { "${menu.import.links}", 0, 0, "bookmark.links confirm:1" },
125 { "---", 0, 0, NULL }, 126 { "---", 0, 0, NULL },
@@ -128,6 +129,8 @@ static iMenuItem bookmarksMenuItems_[] = {
128 { "${macos.menu.bookmarks.bytime}", 0, 0, "open url:about:bookmarks?created" }, 129 { "${macos.menu.bookmarks.bytime}", 0, 0, "open url:about:bookmarks?created" },
129 { "${menu.feeds.entrylist}", 0, 0, "open url:about:feeds" }, 130 { "${menu.feeds.entrylist}", 0, 0, "open url:about:feeds" },
130 { "---", 0, 0, NULL }, 131 { "---", 0, 0, NULL },
132 { "${menu.sort.alpha}", 0, 0, "bookmarks.sort" },
133 { "---", 0, 0, NULL },
131 { "${menu.bookmarks.refresh}", 0, 0, "bookmarks.reload.remote" }, 134 { "${menu.bookmarks.refresh}", 0, 0, "bookmarks.reload.remote" },
132 { "${menu.feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" }, 135 { "${menu.feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" },
133}; 136};