Lagrange [work/v1.7]

Bookmarks: TOML syntax; field for manual order

=> 27ebfc904b9b3eac3ef5479ed0d0ba166fac1543

diff --git a/src/bookmarks.c b/src/bookmarks.c
index c27efbfe..94f4be4e 100644
--- a/src/bookmarks.c
+++ b/src/bookmarks.c
@@ -31,13 +31,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 #include 
 #include 
 #include 
+#include 
 
 void init_Bookmark(iBookmark *d) {
     init_String(&d->url);
     init_String(&d->title);
     init_String(&d->tags);
     iZap(d->when);
-    d->sourceId = 0;
+    d->parentId = 0;
+    d->order = 0;
 }
 
 void deinit_Bookmark(iBookmark *d) {
@@ -83,7 +85,8 @@ static int cmpTitleAscending_Bookmark_(const iBookmark **a, const iBookmark **b)
 
 /*----------------------------------------------------------------------------------------------*/
 
-static const char *fileName_Bookmarks_ = "bookmarks.txt";
+static const char *oldFileName_Bookmarks_ = "bookmarks.txt";
+static const char *fileName_Bookmarks_    = "bookmarks.ini"; /* since v1.7 (TOML subset) */
 
 struct Impl_Bookmarks {
     iMutex *  mtx;
@@ -123,16 +126,19 @@ void clear_Bookmarks(iBookmarks *d) {
     unlock_Mutex(d->mtx);
 }
 
+static void insertId_Bookmarks_(iBookmarks *d, iBookmark *bookmark, int id) {
+    bookmark->node.key = id;
+    insert_Hash(&d->bookmarks, &bookmark->node);
+}
+
 static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) {
     lock_Mutex(d->mtx);
-    bookmark->node.key = ++d->idEnum;
-    insert_Hash(&d->bookmarks, &bookmark->node);
+    insertId_Bookmarks_(d, bookmark, ++d->idEnum);
     unlock_Mutex(d->mtx);
 }
 
-void load_Bookmarks(iBookmarks *d, const char *dirPath) {
-    clear_Bookmarks(d);
-    iFile *f = newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_));
+static void loadOldFormat_Bookmarks(iBookmarks *d, const char *dirPath) {
+    iFile *f = newCStr_File(concatPath_CStr(dirPath, oldFileName_Bookmarks_));
     if (open_File(f, readOnly_FileMode | text_FileMode)) {
         const iRangecc src = range_Block(collect_Block(readAll_File(f)));
         iRangecc line = iNullRange;
@@ -170,6 +176,101 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) {
     iRelease(f);
 }
 
+/*----------------------------------------------------------------------------------------------*/
+
+iDeclareType(BookmarkLoader)
+    
+struct Impl_BookmarkLoader {
+    iTomlParser *toml;
+    iBookmarks * bookmarks;
+    iBookmark *  bm;
+};
+
+static void handleTable_BookmarkLoader_(void *context, const iString *table, iBool isStart) {
+    iBookmarkLoader *d = context;
+    if (isStart) {
+        iAssert(!d->bm);
+        d->bm = new_Bookmark();
+        const int id = toInt_String(table);
+        d->bookmarks->idEnum = iMax(d->bookmarks->idEnum, id);
+        insertId_Bookmarks_(d->bookmarks, d->bm, id);
+    }
+    else {
+        d->bm = NULL;
+    }
+}
+
+static void handleKeyValue_BookmarkLoader_(void *context, const iString *table, const iString *key,
+                                           const iTomlValue *tv) {
+    iBookmarkLoader *d = context;
+    iBookmark *bm = d->bm;
+    if (bm) {
+        iUnused(table); /* it's the current one */
+        if (!cmp_String(key, "url") && tv->type == string_TomlType) {
+            set_String(&bm->url, tv->value.string);
+        }
+        else if (!cmp_String(key, "title") && tv->type == string_TomlType) {
+            set_String(&bm->title, tv->value.string);
+        }
+        else if (!cmp_String(key, "tags") && tv->type == string_TomlType) {
+            set_String(&bm->tags, tv->value.string);
+        }
+        else if (!cmp_String(key, "icon") && tv->type == int64_TomlType) {
+            bm->icon = (iChar) tv->value.int64;
+        }
+        else if (!cmp_String(key, "created") && tv->type == int64_TomlType) {
+            initSeconds_Time(&bm->when, tv->value.int64);
+        }
+        else if (!cmp_String(key, "parent") && tv->type == int64_TomlType) {
+            bm->parentId = tv->value.int64;
+        }
+        else if (!cmp_String(key, "order") && tv->type == int64_TomlType) {
+            bm->order = tv->value.int64;
+        }
+    }
+}
+
+static void init_BookmarkLoader(iBookmarkLoader *d, iBookmarks *bookmarks) {
+    d->toml = new_TomlParser();
+    setHandlers_TomlParser(d->toml, handleTable_BookmarkLoader_, handleKeyValue_BookmarkLoader_, d);
+    d->bookmarks = bookmarks;
+    d->bm = NULL;
+}
+
+static void deinit_BookmarkLoader(iBookmarkLoader *d) {
+    delete_TomlParser(d->toml);
+}
+
+static void load_BookmarkLoader(iBookmarkLoader *d, iFile *file) {
+    if (!parse_TomlParser(d->toml, collect_String(readString_File(file)))) {
+        fprintf(stderr, "[Bookmarks] syntax error(s) in %s\n", cstr_String(path_File(file)));
+    }    
+}
+
+iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b)
+    
+/*----------------------------------------------------------------------------------------------*/
+    
+void load_Bookmarks(iBookmarks *d, const char *dirPath) {
+    clear_Bookmarks(d);
+    /* Load new .ini bookmarks, if present. */
+    iFile *f = iClob(newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_)));
+    if (!open_File(f, readOnly_FileMode | text_FileMode)) {
+        /* As a fallback, try loading the v1.6 bookmarks file. */
+        loadOldFormat_Bookmarks(d, dirPath);
+        /* Set ordering based on titles. */
+        iConstForEach(PtrArray, i, list_Bookmarks(d, cmpTitleAscending_Bookmark_, NULL, NULL)) {
+            iBookmark *bm = i.ptr;
+            bm->order = index_PtrArrayConstIterator(&i) + 1;
+        }
+        return;
+    }
+    iBookmarkLoader loader;
+    init_BookmarkLoader(&loader, d);
+    load_BookmarkLoader(&loader, f);
+    deinit_BookmarkLoader(&loader);
+}
+
 void save_Bookmarks(const iBookmarks *d, const char *dirPath) {
     lock_Mutex(d->mtx);
     iRegExp *remotePattern = iClob(new_RegExp("\\bremote\\b", caseSensitive_RegExpOption));
@@ -185,12 +286,26 @@ void save_Bookmarks(const iBookmarks *d, const char *dirPath) {
                 continue;
             }
             format_String(str,
-                          "%08x %.0lf %s\n%s\n%s\n",
+                          "[%d]\n"
+                          "url = \"%s\"\n"
+                          "title = \"%s\"\n"
+                          "tags = \"%s\"\n"
+                          "icon = 0x%x\n"
+                          "created = %.0f  # %s\n",
+                          id_Bookmark(bm),
+                          cstrCollect_String(quote_String(&bm->url, iFalse)),
+                          cstrCollect_String(quote_String(&bm->title, iFalse)),
+                          cstrCollect_String(quote_String(&bm->tags, iFalse)),
                           bm->icon,
                           seconds_Time(&bm->when),
-                          cstr_String(&bm->url),
-                          cstr_String(&bm->title),
-                          cstr_String(&bm->tags));
+                          cstrCollect_String(format_Time(&bm->when, "%Y-%m-%d")));
+            if (bm->parentId) {
+                appendFormat_String(str, "parent = %d\n", bm->parentId);
+            }
+            if (bm->order) {
+                appendFormat_String(str, "order = %d\n", bm->order);
+            }
+            appendCStr_String(str, "\n");
             writeData_File(f, cstr_String(str), size_String(str));
         }
     }
@@ -223,7 +338,7 @@ iBool remove_Bookmarks(iBookmarks *d, uint32_t id) {
         if (hasTag_Bookmark(bm, remoteSource_BookmarkTag)) {
             iForEach(Hash, i, &d->bookmarks) {
                 iBookmark *j = (iBookmark *) i.value;
-                if (j->sourceId == id_Bookmark(bm)) {
+                if (j->parentId == id_Bookmark(bm)) {
                     remove_HashIterator(&i);
                     delete_Bookmark(j);
                 }
@@ -452,7 +567,7 @@ void requestFinished_Bookmarks(iBookmarks *d, iGmRequest *req) {
                     }
                     const uint32_t bmId = add_Bookmarks(d, absUrl, titleStr, remoteTag, 0x2913);
                     iBookmark *bm = get_Bookmarks(d, bmId);
-                    bm->sourceId = *(uint32_t *) userData_Object(req);
+                    bm->parentId = *(uint32_t *) userData_Object(req);
                     delete_String(titleStr);
                 }
                 delete_String(urlStr);
diff --git a/src/bookmarks.h b/src/bookmarks.h
index 353b4197..dc7eca9a 100644
--- a/src/bookmarks.h
+++ b/src/bookmarks.h
@@ -47,7 +47,8 @@ struct Impl_Bookmark {
     iString tags;
     iChar icon;
     iTime when;
-    uint32_t sourceId; /* remote */
+    uint32_t parentId; /* remote source or folder */
+    int order;         /* sort order */
 };
 
 iLocalDef uint32_t  id_Bookmark (const iBookmark *d) { return d->node.key; }
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index ffedfeeb..3463f1a5 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -116,24 +116,26 @@ static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) {
     return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0;
 }
 
-static int cmpTitle_Bookmark_(const iBookmark **a, const iBookmark **b) {
+static int cmpTree_Bookmark_(const iBookmark **a, const iBookmark **b) {
     const iBookmark *bm1 = *a, *bm2 = *b;
-    if (bm2->sourceId == id_Bookmark(bm1)) {
+    if (bm2->parentId == id_Bookmark(bm1)) {
         return -1;
     }
-    if (bm1->sourceId == id_Bookmark(bm2)) {
+    if (bm1->parentId == id_Bookmark(bm2)) {
         return 1;
     }
-    if (bm1->sourceId == bm2->sourceId) {
-        return cmpStringCase_String(&bm1->title, &bm2->title);
+    if (bm1->parentId == bm2->parentId) {
+        //return cmpStringCase_String(&bm1->title, &bm2->title);
+        return iCmp(bm1->order, bm2->order);
     }
-    if (bm1->sourceId) {
-        bm1 = get_Bookmarks(bookmarks_App(), bm1->sourceId);
+    if (bm1->parentId) {
+        bm1 = get_Bookmarks(bookmarks_App(), bm1->parentId);
     }
-    if (bm2->sourceId) {
-        bm2 = get_Bookmarks(bookmarks_App(), bm2->sourceId);
+    if (bm2->parentId) {
+        bm2 = get_Bookmarks(bookmarks_App(), bm2->parentId);
     }
-    return cmpStringCase_String(&bm1->title, &bm2->title);
+//    return cmpStringCase_String(&bm1->title, &bm2->title);
+    return iCmp(bm1->order, bm2->order);
 }
 
 static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const char *label,
@@ -331,9 +333,10 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
             iRegExp *subTag          = iClob(new_RegExp("\\b" subscribed_BookmarkTag "\\b", caseSensitive_RegExpOption));
             iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption));
             iRegExp *linkSplitTag    = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption));
-            iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTitle_Bookmark_, NULL, NULL)) {
+            iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark_, NULL, NULL)) {
                 const iBookmark *bm = i.ptr;
                 iSidebarItem *item = new_SidebarItem();
+                item->listItem.isDraggable = iTrue;
                 item->id = id_Bookmark(bm);
                 item->icon = bm->icon;
                 set_String(&item->url, &bm->url);
@@ -1573,12 +1576,14 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
     const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list),
                                                            &Class_SidebarWidget);
     const iBool isMenuVisible = isVisible_Widget(sidebar->menu);
-    const iBool isPressing   = isMouseDown_ListWidget(list);
+    const iBool isDragging   = constDragItem_ListWidget(list) == d;
+    const iBool isPressing   = isMouseDown_ListWidget(list) && !isDragging;
     const iBool isHover      =
             (!isMenuVisible &&
             isHover_Widget(constAs_Widget(list)) &&
             constHoverItem_ListWidget(list) == d) ||
-            (isMenuVisible && sidebar->contextItem == d);
+            (isMenuVisible && sidebar->contextItem == d) ||
+            isDragging;
     const int scrollBarWidth = scrollBarWidth_ListWidget(list);
 #if defined (iPlatformApple)
     const int blankWidth     = 0;
@@ -1729,12 +1734,14 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
                             metaIconWidth
                         - 2 * gap_UI - (blankWidth ? blankWidth - 1.5f * gap_UI : (gap_UI / 2)),
                     textPos.y);
-        fillRect_Paint(p,
-                       init_Rect(metaPos.x,
-                                 top_Rect(itemRect),
-                                 right_Rect(itemRect) - metaPos.x,
-                                 height_Rect(itemRect)),
-                       bg);
+        if (!isDragging) {
+            fillRect_Paint(p,
+                           init_Rect(metaPos.x,
+                                     top_Rect(itemRect),
+                                     right_Rect(itemRect) - metaPos.x,
+                                     height_Rect(itemRect)),
+                           bg);
+        }
         iInt2 mpos = metaPos;
         iStringConstIterator iter;
         init_StringConstIterator(&iter, &d->meta);
Proxy Information
Original URL
gemini://git.skyjake.fi/lagrange/work%2Fv1.7/cdiff/27ebfc904b9b3eac3ef5479ed0d0ba166fac1543
Status Code
Success (20)
Meta
text/gemini; charset=utf-8
Capsule Response Time
65.477697 milliseconds
Gemini-to-HTML Time
0.524737 milliseconds

This content has been proxied by September (3851b).