summaryrefslogtreecommitdiff
path: root/src/ui/sidebarwidget.c
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-09-25 10:59:28 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-09-25 10:59:28 +0300
commitc80331992585bfee3d65a7ba24f3a4b640c48735 (patch)
tree5eaa85a0a3a1c46bdca7cccfae0e7349e22f6d13 /src/ui/sidebarwidget.c
parent562a0d2d38c0621a296e8343270f3f1efc268156 (diff)
parent242e8231ea61278fe482020658be86c2dec0ae53 (diff)
Merge branch 'work/v1.7' into dev
Diffstat (limited to 'src/ui/sidebarwidget.c')
-rw-r--r--src/ui/sidebarwidget.c379
1 files changed, 290 insertions, 89 deletions
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index c2ad7bc6..3018f16d 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -100,7 +100,7 @@ struct Impl_SidebarWidget {
100 int modeScroll[max_SidebarMode]; 100 int modeScroll[max_SidebarMode];
101 iLabelWidget * modeButtons[max_SidebarMode]; 101 iLabelWidget * modeButtons[max_SidebarMode];
102 int maxButtonLabelWidth; 102 int maxButtonLabelWidth;
103 int widthAsGaps; 103 float widthAsGaps;
104 int buttonFont; 104 int buttonFont;
105 int itemFonts[2]; 105 int itemFonts[2];
106 size_t numUnreadEntries; 106 size_t numUnreadEntries;
@@ -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,23 +117,66 @@ 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 cmpTitle_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->sourceId == 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->sourceId == id_Bookmark(bm2)) { 154 if (hasParent_Bookmark(bm1, id_Bookmark(bm2))) {
125 return 1; 155 return 1;
126 } 156 }
127 if (bm1->sourceId == bm2->sourceId) { 157 /* Comparisons are only valid inside the same parent. */
128 return cmpStringCase_String(&bm1->title, &bm2->title); 158 while (bm1->parentId != bm2->parentId) {
129 } 159 int depth1 = depth_Bookmark(bm1);
130 if (bm1->sourceId) { 160 int depth2 = depth_Bookmark(bm2);
131 bm1 = get_Bookmarks(bookmarks_App(), bm1->sourceId); 161 if (depth1 != depth2) {
132 } 162 /* Equalize the depth. */
133 if (bm2->sourceId) { 163 while (depth1 > depth2) {
134 bm2 = get_Bookmarks(bookmarks_App(), bm2->sourceId); 164 bm1 = parent_Bookmark(bm1);
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--;
135 } 177 }
178 const int cmp = iCmp(bm1->order, bm2->order);
179 if (cmp) return cmp;
136 return cmpStringCase_String(&bm1->title, &bm2->title); 180 return cmpStringCase_String(&bm1->title, &bm2->title);
137} 181}
138 182
@@ -143,7 +187,9 @@ static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const cha
143 //(deviceType_App() != desktop_AppDeviceType ? 187 //(deviceType_App() != desktop_AppDeviceType ?
144 // extraPadding_WidgetFlag : 0) | 188 // extraPadding_WidgetFlag : 0) |
145 flags); 189 flags);
146 setFont_LabelWidget(btn, d->buttonFont); 190 setFont_LabelWidget(btn, deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide
191 ? defaultBig_FontId
192 : d->buttonFont);
147 checkIcon_LabelWidget(btn); 193 checkIcon_LabelWidget(btn);
148 return btn; 194 return btn;
149} 195}
@@ -207,6 +253,16 @@ static void updateContextMenu_SidebarWidget_(iSidebarWidget *d) {
207 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));
208} 254}
209 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
210static void updateItems_SidebarWidget_(iSidebarWidget *d) { 266static void updateItems_SidebarWidget_(iSidebarWidget *d) {
211 clear_ListWidget(d->list); 267 clear_ListWidget(d->list);
212 releaseChildren_Widget(d->blank); 268 releaseChildren_Widget(d->blank);
@@ -328,12 +384,24 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
328 iRegExp *homeTag = iClob(new_RegExp("\\b" homepage_BookmarkTag "\\b", caseSensitive_RegExpOption)); 384 iRegExp *homeTag = iClob(new_RegExp("\\b" homepage_BookmarkTag "\\b", caseSensitive_RegExpOption));
329 iRegExp *subTag = iClob(new_RegExp("\\b" subscribed_BookmarkTag "\\b", caseSensitive_RegExpOption)); 385 iRegExp *subTag = iClob(new_RegExp("\\b" subscribed_BookmarkTag "\\b", caseSensitive_RegExpOption));
330 iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption)); 386 iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption));
387 iRegExp *remoteTag = iClob(new_RegExp("\\b" remote_BookmarkTag "\\b", caseSensitive_RegExpOption));
331 iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption)); 388 iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption));
332 iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTitle_Bookmark_, NULL, NULL)) { 389 iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark, NULL, NULL)) {
333 const iBookmark *bm = i.ptr; 390 const iBookmark *bm = i.ptr;
391 if (isBookmarkFolded_SidebarWidget_(d, bm)) {
392 continue; /* inside a closed folder */
393 }
334 iSidebarItem *item = new_SidebarItem(); 394 iSidebarItem *item = new_SidebarItem();
395 item->listItem.isDraggable = iTrue;
396 item->isBold = item->listItem.isDropTarget = isFolder_Bookmark(bm);
335 item->id = id_Bookmark(bm); 397 item->id = id_Bookmark(bm);
336 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 }
337 set_String(&item->url, &bm->url); 405 set_String(&item->url, &bm->url);
338 set_String(&item->label, &bm->title); 406 set_String(&item->label, &bm->title);
339 /* Icons for special tags. */ { 407 /* Icons for special tags. */ {
@@ -347,6 +415,10 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
347 appendChar_String(&item->meta, 0x1f3e0); 415 appendChar_String(&item->meta, 0x1f3e0);
348 } 416 }
349 init_RegExpMatch(&m); 417 init_RegExpMatch(&m);
418 if (matchString_RegExp(remoteTag, &bm->tags, &m)) {
419 item->listItem.isDraggable = iFalse;
420 }
421 init_RegExpMatch(&m);
350 if (matchString_RegExp(remoteSourceTag, &bm->tags, &m)) { 422 if (matchString_RegExp(remoteSourceTag, &bm->tags, &m)) {
351 appendChar_String(&item->meta, 0x2913); 423 appendChar_String(&item->meta, 0x2913);
352 item->isBold = iTrue; 424 item->isBold = iTrue;
@@ -374,8 +446,11 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
374 { "---", 0, 0, NULL }, 446 { "---", 0, 0, NULL },
375 { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, 447 { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" },
376 { "---", 0, 0, NULL }, 448 { "---", 0, 0, NULL },
377 { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, 449 { add_Icon " ${menu.newfolder}", 0, 0, "bookmark.addfolder" },
378 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);
379 break; 454 break;
380 } 455 }
381 case history_SidebarMode: { 456 case history_SidebarMode: {
@@ -550,6 +625,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
550#endif 625#endif
551 arrange_Widget(d->actions); 626 arrange_Widget(d->actions);
552 arrange_Widget(as_Widget(d)); 627 arrange_Widget(as_Widget(d));
628 updateMouseHover_ListWidget(d->list);
553} 629}
554 630
555static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) { 631static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) {
@@ -579,6 +655,11 @@ iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
579 return iTrue; 655 return iTrue;
580} 656}
581 657
658void setClosedFolders_SidebarWidget(iSidebarWidget *d, const iIntSet *closedFolders) {
659 delete_IntSet(d->closedFolders);
660 d->closedFolders = copy_IntSet(closedFolders);
661}
662
582enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { 663enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) {
583 return d ? d->mode : 0; 664 return d ? d->mode : 0;
584} 665}
@@ -587,6 +668,10 @@ float width_SidebarWidget(const iSidebarWidget *d) {
587 return d ? d->widthAsGaps : 0; 668 return d ? d->widthAsGaps : 0;
588} 669}
589 670
671const iIntSet *closedFolders_SidebarWidget(const iSidebarWidget *d) {
672 return d->closedFolders;
673}
674
590static const char *normalModeLabels_[max_SidebarMode] = { 675static const char *normalModeLabels_[max_SidebarMode] = {
591 book_Icon " ${sidebar.bookmarks}", 676 book_Icon " ${sidebar.bookmarks}",
592 star_Icon " ${sidebar.feeds}", 677 star_Icon " ${sidebar.feeds}",
@@ -641,17 +726,17 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
641 d->mode = -1; 726 d->mode = -1;
642 d->feedsMode = all_FeedsMode; 727 d->feedsMode = all_FeedsMode;
643 d->numUnreadEntries = 0; 728 d->numUnreadEntries = 0;
644 d->buttonFont = uiLabel_FontId; 729 d->buttonFont = uiLabel_FontId; /* wiil be changed later */
645 d->itemFonts[0] = uiContent_FontId; 730 d->itemFonts[0] = uiContent_FontId;
646 d->itemFonts[1] = uiContentBold_FontId; 731 d->itemFonts[1] = uiContentBold_FontId;
647#if defined (iPlatformAppleMobile) 732#if defined (iPlatformMobile)
648 if (deviceType_App() == phone_AppDeviceType) { 733 if (deviceType_App() == phone_AppDeviceType) {
649 d->itemFonts[0] = defaultBig_FontId; 734 d->itemFonts[0] = defaultBig_FontId;
650 d->itemFonts[1] = defaultBigBold_FontId; 735 d->itemFonts[1] = defaultBigBold_FontId;
651 } 736 }
652 d->widthAsGaps = 73; 737 d->widthAsGaps = 73.0f;
653#else 738#else
654 d->widthAsGaps = 60; 739 d->widthAsGaps = 60.0f;
655#endif 740#endif
656 setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue); 741 setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue);
657 iWidget *vdiv = makeVDiv_Widget(); 742 iWidget *vdiv = makeVDiv_Widget();
@@ -660,11 +745,13 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
660 d->resizer = NULL; 745 d->resizer = NULL;
661 d->list = NULL; 746 d->list = NULL;
662 d->actions = NULL; 747 d->actions = NULL;
748 d->closedFolders = new_IntSet();
663 /* On a phone, the right sidebar is used exclusively for Identities. */ 749 /* On a phone, the right sidebar is used exclusively for Identities. */
664 const iBool isPhone = deviceType_App() == phone_AppDeviceType; 750 const iBool isPhone = deviceType_App() == phone_AppDeviceType;
665 if (!isPhone || d->side == left_SidebarSide) { 751 if (!isPhone || d->side == left_SidebarSide) {
666 iWidget *buttons = new_Widget(); 752 iWidget *buttons = new_Widget();
667 setId_Widget(buttons, "buttons"); 753 setId_Widget(buttons, "buttons");
754 setDrawBufferEnabled_Widget(buttons, iTrue);
668 for (int i = 0; i < max_SidebarMode; i++) { 755 for (int i = 0; i < max_SidebarMode; i++) {
669 if (deviceType_App() == phone_AppDeviceType && i == identities_SidebarMode) { 756 if (deviceType_App() == phone_AppDeviceType && i == identities_SidebarMode) {
670 continue; 757 continue;
@@ -742,16 +829,21 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) {
742 829
743void deinit_SidebarWidget(iSidebarWidget *d) { 830void deinit_SidebarWidget(iSidebarWidget *d) {
744 deinit_String(&d->cmdPrefix); 831 deinit_String(&d->cmdPrefix);
832 delete_IntSet(d->closedFolders);
745} 833}
746 834
747void setButtonFont_SidebarWidget(iSidebarWidget *d, int font) { 835iBool setButtonFont_SidebarWidget(iSidebarWidget *d, int font) {
748 d->buttonFont = font; 836 if (d->buttonFont != font) {
749 for (int i = 0; i < max_SidebarMode; i++) { 837 d->buttonFont = font;
750 if (d->modeButtons[i]) { 838 for (int i = 0; i < max_SidebarMode; i++) {
751 setFont_LabelWidget(d->modeButtons[i], font); 839 if (d->modeButtons[i]) {
840 setFont_LabelWidget(d->modeButtons[i], font);
841 }
752 } 842 }
843 updateMetrics_SidebarWidget_(d);
844 return iTrue;
753 } 845 }
754 updateMetrics_SidebarWidget_(d); 846 return iFalse;
755} 847}
756 848
757static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { 849static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) {
@@ -785,6 +877,17 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si
785 break; 877 break;
786 } 878 }
787 case bookmarks_SidebarMode: 879 case bookmarks_SidebarMode:
880 if (isEmpty_String(&item->url)) /* a folder */ {
881 if (contains_IntSet(d->closedFolders, item->id)) {
882 remove_IntSet(d->closedFolders, item->id);
883 }
884 else {
885 insert_IntSet(d->closedFolders, item->id);
886 }
887 updateItems_SidebarWidget_(d);
888 break;
889 }
890 /* fall through */
788 case history_SidebarMode: { 891 case history_SidebarMode: {
789 if (!isEmpty_String(&item->url)) { 892 if (!isEmpty_String(&item->url)) {
790 postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s", 893 postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s",
@@ -826,8 +929,10 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
826 if (d->itemFonts[0] != fonts[0]) { 929 if (d->itemFonts[0] != fonts[0]) {
827 d->itemFonts[0] = fonts[0]; 930 d->itemFonts[0] = fonts[0];
828 d->itemFonts[1] = fonts[1]; 931 d->itemFonts[1] = fonts[1];
829 updateMetrics_SidebarWidget_(d); 932// updateMetrics_SidebarWidget_(d);
933 updateItemHeight_SidebarWidget_(d);
830 } 934 }
935 setButtonFont_SidebarWidget(d, isPortrait_App() ? defaultBig_FontId : uiLabel_FontId);
831 } 936 }
832 const iBool isTight = 937 const iBool isTight =
833 (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth); 938 (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth);
@@ -857,17 +962,15 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) {
857void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { 962void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) {
858 iWidget *w = as_Widget(d); 963 iWidget *w = as_Widget(d);
859 const iBool isFixedWidth = deviceType_App() == phone_AppDeviceType; 964 const iBool isFixedWidth = deviceType_App() == phone_AppDeviceType;
860 int width = widthAsGaps * gap_UI; 965 int width = widthAsGaps * gap_UI; /* in pixels */
861 if (!isFixedWidth) { 966 if (!isFixedWidth) {
862 /* Even less space if the other sidebar is visible, too. */ 967 /* Even less space if the other sidebar is visible, too. */
863 const int otherWidth = 968 const iWidget *other = findWidget_App(d->side == left_SidebarSide ? "sidebar2" : "sidebar");
864 width_Widget(findWidget_App(d->side == left_SidebarSide ? "sidebar2" : "sidebar")); 969 const int otherWidth = isVisible_Widget(other) ? width_Widget(other) : 0;
865 width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth); 970 width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth);
866 } 971 }
867 d->widthAsGaps = (float) width / (float) gap_UI; 972 d->widthAsGaps = (float) width / (float) gap_UI;
868 if (isVisible_Widget(w)) { 973 w->rect.size.x = width;
869 w->rect.size.x = width;
870 }
871 arrange_Widget(findWidget_Root("stack")); 974 arrange_Widget(findWidget_Root("stack"));
872 checkModeButtonLayout_SidebarWidget_(d); 975 checkModeButtonLayout_SidebarWidget_(d);
873 updateItemHeight_SidebarWidget_(d); 976 updateItemHeight_SidebarWidget_(d);
@@ -934,6 +1037,7 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
934 if (wasChanged) { 1037 if (wasChanged) {
935 postCommandf_App("%s.mode.changed arg:%d", cstr_String(id_Widget(w)), d->mode); 1038 postCommandf_App("%s.mode.changed arg:%d", cstr_String(id_Widget(w)), d->mode);
936 } 1039 }
1040 refresh_Widget(findChild_Widget(w, "buttons"));
937 return iTrue; 1041 return iTrue;
938 } 1042 }
939 else if (equal_Command(cmd, "toggle")) { 1043 else if (equal_Command(cmd, "toggle")) {
@@ -989,6 +1093,43 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char *
989 return iFalse; 1093 return iFalse;
990} 1094}
991 1095
1096static void bookmarkMoved_SidebarWidget_(iSidebarWidget *d, size_t index, size_t beforeIndex) {
1097 const iSidebarItem *movingItem = item_ListWidget(d->list, index);
1098 const iBool isLast = (beforeIndex == numItems_ListWidget(d->list));
1099 const iSidebarItem *dstItem = item_ListWidget(d->list,
1100 isLast ? numItems_ListWidget(d->list) - 1
1101 : beforeIndex);
1102 const iBookmark *dst = get_Bookmarks(bookmarks_App(), dstItem->id);
1103 if (hasParent_Bookmark(dst, movingItem->id)) {
1104 return;
1105 }
1106 reorder_Bookmarks(bookmarks_App(), movingItem->id, dst->order + (isLast ? 1 : 0));
1107 get_Bookmarks(bookmarks_App(), movingItem->id)->parentId = dst->parentId;
1108 updateItems_SidebarWidget_(d);
1109 /* Don't confuse the user: keep the dragged item in hover state. */
1110 setHoverItem_ListWidget(d->list, index < beforeIndex ? beforeIndex - 1 : beforeIndex);
1111 postCommandf_App("bookmarks.changed nosidebar:%p", d); /* skip this sidebar since we updated already */
1112}
1113
1114static void bookmarkMovedOntoFolder_SidebarWidget_(iSidebarWidget *d, size_t index,
1115 size_t folderIndex) {
1116 const iSidebarItem *movingItem = item_ListWidget(d->list, index);
1117 const iSidebarItem *dstItem = item_ListWidget(d->list, folderIndex);
1118 iBookmark *bm = get_Bookmarks(bookmarks_App(), movingItem->id);
1119 bm->parentId = dstItem->id;
1120 postCommand_App("bookmarks.changed");
1121}
1122
1123static size_t numBookmarks_(const iPtrArray *bmList) {
1124 size_t num = 0;
1125 iConstForEach(PtrArray, i, bmList) {
1126 if (!isFolder_Bookmark(i.ptr) && !hasTag_Bookmark(i.ptr, remote_BookmarkTag)) {
1127 num++;
1128 }
1129 }
1130 return num;
1131}
1132
992static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { 1133static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) {
993 iWidget *w = as_Widget(d); 1134 iWidget *w = as_Widget(d);
994 /* Handle commands. */ 1135 /* Handle commands. */
@@ -1040,7 +1181,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1040 } 1181 }
1041 else if (equal_Command(cmd, "bookmarks.changed") && (d->mode == bookmarks_SidebarMode || 1182 else if (equal_Command(cmd, "bookmarks.changed") && (d->mode == bookmarks_SidebarMode ||
1042 d->mode == feeds_SidebarMode)) { 1183 d->mode == feeds_SidebarMode)) {
1043 updateItems_SidebarWidget_(d); 1184 if (pointerLabel_Command(cmd, "nosidebar") != d) {
1185 updateItems_SidebarWidget_(d);
1186 }
1044 } 1187 }
1045 else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) { 1188 else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) {
1046 updateItems_SidebarWidget_(d); 1189 updateItems_SidebarWidget_(d);
@@ -1096,6 +1239,21 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1096 d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg")); 1239 d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg"));
1097 return iTrue; 1240 return iTrue;
1098 } 1241 }
1242 else if (isCommand_Widget(w, ev, "list.dragged")) {
1243 iAssert(d->mode == bookmarks_SidebarMode);
1244 if (hasLabel_Command(cmd, "before")) {
1245 bookmarkMoved_SidebarWidget_(d,
1246 argU32Label_Command(cmd, "arg"),
1247 argU32Label_Command(cmd, "before"));
1248 }
1249 else {
1250 /* Dragged onto a folder. */
1251 bookmarkMovedOntoFolder_SidebarWidget_(d,
1252 argU32Label_Command(cmd, "arg"),
1253 argU32Label_Command(cmd, "onto"));
1254 }
1255 return iTrue;
1256 }
1099 else if (isCommand_Widget(w, ev, "menu.closed")) { 1257 else if (isCommand_Widget(w, ev, "menu.closed")) {
1100 // invalidateItem_ListWidget(d->list, d->contextIndex); 1258 // invalidateItem_ListWidget(d->list, d->contextIndex);
1101 } 1259 }
@@ -1128,15 +1286,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1128 setText_InputWidget(findChild_Widget(dlg, "bmed.icon"), 1286 setText_InputWidget(findChild_Widget(dlg, "bmed.icon"),
1129 collect_String(newUnicodeN_String(&bm->icon, 1))); 1287 collect_String(newUnicodeN_String(&bm->icon, 1)));
1130 } 1288 }
1131 setFlags_Widget(findChild_Widget(dlg, "bmed.tag.home"), 1289 setToggle_Widget(findChild_Widget(dlg, "bmed.tag.home"),
1132 selected_WidgetFlag, 1290 hasTag_Bookmark(bm, homepage_BookmarkTag));
1133 hasTag_Bookmark(bm, homepage_BookmarkTag)); 1291 setToggle_Widget(findChild_Widget(dlg, "bmed.tag.remote"),
1134 setFlags_Widget(findChild_Widget(dlg, "bmed.tag.remote"), 1292 hasTag_Bookmark(bm, remoteSource_BookmarkTag));
1135 selected_WidgetFlag, 1293 setToggle_Widget(findChild_Widget(dlg, "bmed.tag.linksplit"),
1136 hasTag_Bookmark(bm, remoteSource_BookmarkTag)); 1294 hasTag_Bookmark(bm, linkSplit_BookmarkTag));
1137 setFlags_Widget(findChild_Widget(dlg, "bmed.tag.linksplit"),
1138 selected_WidgetFlag,
1139 hasTag_Bookmark(bm, linkSplit_BookmarkTag));
1140 setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_); 1295 setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_);
1141 setFocus_Widget(findChild_Widget(dlg, "bmed.title")); 1296 setFocus_Widget(findChild_Widget(dlg, "bmed.title"));
1142 } 1297 }
@@ -1177,9 +1332,60 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1177 } 1332 }
1178 else if (isCommand_Widget(w, ev, "bookmark.delete")) { 1333 else if (isCommand_Widget(w, ev, "bookmark.delete")) {
1179 const iSidebarItem *item = d->contextItem; 1334 const iSidebarItem *item = d->contextItem;
1180 if (d->mode == bookmarks_SidebarMode && item && remove_Bookmarks(bookmarks_App(), item->id)) { 1335 if (d->mode == bookmarks_SidebarMode && item) {
1181 removeEntries_Feeds(item->id); 1336 iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id);
1182 postCommand_App("bookmarks.changed"); 1337 if (isFolder_Bookmark(bm)) {
1338 const iPtrArray *list = list_Bookmarks(bookmarks_App(), NULL,
1339 filterInsideFolder_Bookmark, bm);
1340 /* Folder deletion requires confirmation because folders can contain
1341 any number of bookmarks and other folders. */
1342 if (argLabel_Command(cmd, "confirmed") || isEmpty_PtrArray(list)) {
1343 iConstForEach(PtrArray, i, list) {
1344 removeEntries_Feeds(id_Bookmark(i.ptr));
1345 }
1346 remove_Bookmarks(bookmarks_App(), item->id);
1347 postCommand_App("bookmarks.changed");
1348 }
1349 else {
1350 const size_t numBookmarks = numBookmarks_(list);
1351 makeQuestion_Widget(uiHeading_ColorEscape "${heading.confirm.bookmarks.delete}",
1352 formatCStrs_Lang("dlg.confirm.bookmarks.delete.n", numBookmarks),
1353 (iMenuItem[]){
1354 { "${cancel}" },
1355 { format_CStr(uiTextCaution_ColorEscape "%s",
1356 formatCStrs_Lang("dlg.bookmarks.delete.n", numBookmarks)),
1357 0, 0, format_CStr("!bookmark.delete confirmed:1 ptr:%p", d) },
1358 }, 2);
1359 }
1360 }
1361 else {
1362 /* TODO: Move it to a Trash folder? */
1363 if (remove_Bookmarks(bookmarks_App(), item->id)) {
1364 removeEntries_Feeds(item->id);
1365 postCommand_App("bookmarks.changed");
1366 }
1367 }
1368 }
1369 return iTrue;
1370 }
1371 else if (isCommand_Widget(w, ev, "bookmark.addfolder")) {
1372 const iSidebarItem *item = d->contextItem;
1373 if (d->mode == bookmarks_SidebarMode) {
1374 postCommandf_App("bookmarks.addfolder parent:%zu",
1375 !item ? 0
1376 : item->listItem.isDropTarget
1377 ? item->id
1378 : get_Bookmarks(bookmarks_App(), item->id)->parentId);
1379 }
1380 return iTrue;
1381 }
1382 else if (isCommand_Widget(w, ev, "bookmark.sortfolder")) {
1383 const iSidebarItem *item = d->contextItem;
1384 if (d->mode == bookmarks_SidebarMode && item) {
1385 postCommandf_App("bookmarks.sort arg:%zu",
1386 item->listItem.isDropTarget
1387 ? item->id
1388 : get_Bookmarks(bookmarks_App(), item->id)->parentId);
1183 } 1389 }
1184 return iTrue; 1390 return iTrue;
1185 } 1391 }
@@ -1449,40 +1655,29 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1449 if (d->mode == bookmarks_SidebarMode && d->contextItem) { 1655 if (d->mode == bookmarks_SidebarMode && d->contextItem) {
1450 const iBookmark *bm = get_Bookmarks(bookmarks_App(), d->contextItem->id); 1656 const iBookmark *bm = get_Bookmarks(bookmarks_App(), d->contextItem->id);
1451 if (bm) { 1657 if (bm) {
1452 iLabelWidget *menuItem = findMenuItem_Widget(d->menu, 1658 setMenuItemLabel_Widget(d->menu,
1453 "bookmark.tag tag:homepage"); 1659 "bookmark.tag tag:homepage",
1454 if (menuItem) { 1660 hasTag_Bookmark(bm, homepage_BookmarkTag)
1455 setTextCStr_LabelWidget(menuItem, 1661 ? home_Icon " ${bookmark.untag.home}"
1456 hasTag_Bookmark(bm, homepage_BookmarkTag) 1662 : home_Icon " ${bookmark.tag.home}");
1457 ? home_Icon " ${bookmark.untag.home}" 1663 setMenuItemLabel_Widget(d->menu,
1458 : home_Icon " ${bookmark.tag.home}"); 1664 "bookmark.tag tag:subscribed",
1459 checkIcon_LabelWidget(menuItem); 1665 hasTag_Bookmark(bm, subscribed_BookmarkTag)
1460 } 1666 ? star_Icon " ${bookmark.untag.sub}"
1461 menuItem = findMenuItem_Widget(d->menu, "bookmark.tag tag:subscribed"); 1667 : star_Icon " ${bookmark.tag.sub}");
1462 if (menuItem) { 1668 setMenuItemLabel_Widget(d->menu,
1463 setTextCStr_LabelWidget(menuItem, 1669 "bookmark.tag tag:remotesource",
1464 hasTag_Bookmark(bm, subscribed_BookmarkTag) 1670 hasTag_Bookmark(bm, remoteSource_BookmarkTag)
1465 ? star_Icon " ${bookmark.untag.sub}" 1671 ? downArrowBar_Icon " ${bookmark.untag.remote}"
1466 : star_Icon " ${bookmark.tag.sub}"); 1672 : downArrowBar_Icon " ${bookmark.tag.remote}");
1467 checkIcon_LabelWidget(menuItem);
1468 }
1469 menuItem = findMenuItem_Widget(d->menu, "bookmark.tag tag:remotesource");
1470 if (menuItem) {
1471 setTextCStr_LabelWidget(menuItem,
1472 hasTag_Bookmark(bm, remoteSource_BookmarkTag)
1473 ? downArrowBar_Icon " ${bookmark.untag.remote}"
1474 : downArrowBar_Icon " ${bookmark.tag.remote}");
1475 checkIcon_LabelWidget(menuItem);
1476 }
1477 } 1673 }
1478 } 1674 }
1479 else if (d->mode == feeds_SidebarMode && d->contextItem) { 1675 else if (d->mode == feeds_SidebarMode && d->contextItem) {
1480 iLabelWidget *menuItem = findMenuItem_Widget(d->menu, "feed.entry.toggleread");
1481 const iBool isRead = d->contextItem->indent == 0; 1676 const iBool isRead = d->contextItem->indent == 0;
1482 setTextCStr_LabelWidget(menuItem, 1677 setMenuItemLabel_Widget(d->menu,
1678 "feed.entry.toggleread",
1483 isRead ? circle_Icon " ${feeds.entry.markunread}" 1679 isRead ? circle_Icon " ${feeds.entry.markunread}"
1484 : circleWhite_Icon " ${feeds.entry.markread}"); 1680 : circleWhite_Icon " ${feeds.entry.markread}");
1485 checkIcon_LabelWidget(menuItem);
1486 } 1681 }
1487 else if (d->mode == identities_SidebarMode) { 1682 else if (d->mode == identities_SidebarMode) {
1488 const iGmIdentity *ident = constHoverIdentity_SidebarWidget_(d); 1683 const iGmIdentity *ident = constHoverIdentity_SidebarWidget_(d);
@@ -1555,7 +1750,7 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) {
1555 const iRect bounds = bounds_Widget(w); 1750 const iRect bounds = bounds_Widget(w);
1556 iPaint p; 1751 iPaint p;
1557 init_Paint(&p); 1752 init_Paint(&p);
1558 if (deviceType_App() != phone_AppDeviceType) { 1753 if (!isPortraitPhone_App()) { /* this would erase page contents during transition on the phone */
1559 if (flags_Widget(w) & visualOffset_WidgetFlag && 1754 if (flags_Widget(w) & visualOffset_WidgetFlag &&
1560 flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { 1755 flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) {
1561 fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); 1756 fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId);
@@ -1576,12 +1771,14 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1576 const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), 1771 const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list),
1577 &Class_SidebarWidget); 1772 &Class_SidebarWidget);
1578 const iBool isMenuVisible = isVisible_Widget(sidebar->menu); 1773 const iBool isMenuVisible = isVisible_Widget(sidebar->menu);
1579 const iBool isPressing = isMouseDown_ListWidget(list); 1774 const iBool isDragging = constDragItem_ListWidget(list) == d;
1775 const iBool isPressing = isMouseDown_ListWidget(list) && !isDragging;
1580 const iBool isHover = 1776 const iBool isHover =
1581 (!isMenuVisible && 1777 (!isMenuVisible &&
1582 isHover_Widget(constAs_Widget(list)) && 1778 isHover_Widget(constAs_Widget(list)) &&
1583 constHoverItem_ListWidget(list) == d) || 1779 constHoverItem_ListWidget(list) == d) ||
1584 (isMenuVisible && sidebar->contextItem == d); 1780 (isMenuVisible && sidebar->contextItem == d) ||
1781 isDragging;
1585 const int scrollBarWidth = scrollBarWidth_ListWidget(list); 1782 const int scrollBarWidth = scrollBarWidth_ListWidget(list);
1586#if defined (iPlatformApple) 1783#if defined (iPlatformApple)
1587 const int blankWidth = 0; 1784 const int blankWidth = 0;
@@ -1605,7 +1802,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1605 fillRect_Paint(p, itemRect, bg); 1802 fillRect_Paint(p, itemRect, bg);
1606 } 1803 }
1607 else if (sidebar->mode == bookmarks_SidebarMode) { 1804 else if (sidebar->mode == bookmarks_SidebarMode) {
1608 if (d->icon == 0x2913) { /* TODO: Remote icon; meaning: is this in a folder? */ 1805 if (d->indent) /* remote icon */ {
1609 bg = uiBackgroundFolder_ColorId; 1806 bg = uiBackgroundFolder_ColorId;
1610 fillRect_Paint(p, itemRect, bg); 1807 fillRect_Paint(p, itemRect, bg);
1611 } 1808 }
@@ -1707,11 +1904,13 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1707 } 1904 }
1708 else if (sidebar->mode == bookmarks_SidebarMode) { 1905 else if (sidebar->mode == bookmarks_SidebarMode) {
1709 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) 1906 const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId)
1710 : uiText_ColorId; 1907 : d->listItem.isDropTarget ? uiHeading_ColorId : uiText_ColorId;
1908 /* The icon. */
1711 iString str; 1909 iString str;
1712 init_String(&str); 1910 init_String(&str);
1713 appendChar_String(&str, d->icon ? d->icon : 0x1f588); 1911 appendChar_String(&str, d->icon ? d->icon : 0x1f588);
1714 const iRect iconArea = { addX_I2(pos, gap_UI), 1912 const int leftIndent = d->indent * gap_UI * 4;
1913 const iRect iconArea = { addX_I2(pos, gap_UI + leftIndent),
1715 init_I2(1.75f * lineHeight_Text(font), itemHeight) }; 1914 init_I2(1.75f * lineHeight_Text(font), itemHeight) };
1716 drawCentered_Text(font, 1915 drawCentered_Text(font,
1717 iconArea, 1916 iconArea,
@@ -1732,12 +1931,14 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
1732 metaIconWidth 1931 metaIconWidth
1733 - 2 * gap_UI - (blankWidth ? blankWidth - 1.5f * gap_UI : (gap_UI / 2)), 1932 - 2 * gap_UI - (blankWidth ? blankWidth - 1.5f * gap_UI : (gap_UI / 2)),
1734 textPos.y); 1933 textPos.y);
1735 fillRect_Paint(p, 1934 if (!isDragging) {
1736 init_Rect(metaPos.x, 1935 fillRect_Paint(p,
1737 top_Rect(itemRect), 1936 init_Rect(metaPos.x,
1738 right_Rect(itemRect) - metaPos.x, 1937 top_Rect(itemRect),
1739 height_Rect(itemRect)), 1938 right_Rect(itemRect) - metaPos.x,
1740 bg); 1939 height_Rect(itemRect)),
1940 bg);
1941 }
1741 iInt2 mpos = metaPos; 1942 iInt2 mpos = metaPos;
1742 iStringConstIterator iter; 1943 iStringConstIterator iter;
1743 init_StringConstIterator(&iter, &d->meta); 1944 init_StringConstIterator(&iter, &d->meta);