diff options
Diffstat (limited to 'src/ui/sidebarwidget.c')
-rw-r--r-- | src/ui/sidebarwidget.c | 379 |
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 | ||
113 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) | 114 | iDefineObjectConstructionArgs(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 | ||
119 | static int cmpTitle_Bookmark_(const iBookmark **a, const iBookmark **b) { | 120 | iBookmark *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 | |||
128 | iBool 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 | |||
139 | int 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 | |||
148 | int 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 | ||
256 | static 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 | |||
210 | static void updateItems_SidebarWidget_(iSidebarWidget *d) { | 266 | static 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 | ||
555 | static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) { | 631 | static 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 | ||
658 | void setClosedFolders_SidebarWidget(iSidebarWidget *d, const iIntSet *closedFolders) { | ||
659 | delete_IntSet(d->closedFolders); | ||
660 | d->closedFolders = copy_IntSet(closedFolders); | ||
661 | } | ||
662 | |||
582 | enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { | 663 | enum 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 | ||
671 | const iIntSet *closedFolders_SidebarWidget(const iSidebarWidget *d) { | ||
672 | return d->closedFolders; | ||
673 | } | ||
674 | |||
590 | static const char *normalModeLabels_[max_SidebarMode] = { | 675 | static 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 | ||
743 | void deinit_SidebarWidget(iSidebarWidget *d) { | 830 | void deinit_SidebarWidget(iSidebarWidget *d) { |
744 | deinit_String(&d->cmdPrefix); | 831 | deinit_String(&d->cmdPrefix); |
832 | delete_IntSet(d->closedFolders); | ||
745 | } | 833 | } |
746 | 834 | ||
747 | void setButtonFont_SidebarWidget(iSidebarWidget *d, int font) { | 835 | iBool 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 | ||
757 | static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { | 849 | static 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) { | |||
857 | void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { | 962 | void 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 | ||
1096 | static 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 | |||
1114 | static 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 | |||
1123 | static 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 | |||
992 | static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { | 1133 | static 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); |