Lagrange [release]

Preferences: Add bookmarks to bottom/top

65ae29b56803c1ba779ab9b0a6b09333482ee13a
diff --git a/po/en.po b/po/en.po
index 9b09ec9c..dcbcc67f 100644
--- a/po/en.po
+++ b/po/en.po
@@ -1314,6 +1314,9 @@ msgstr "Vertical centering:"
 msgid "prefs.collapsepreonload"
 msgstr "Collapse preformatted:"
 
+msgid "prefs.bookmarks.addbottom"
+msgstr "Add bookmarks to bottom:"
+
 # User preference that controls whether index.gmi pages get automatically opened when browsing the contents of a directory inside a compressed archive.
 msgid "prefs.archive.openindex"
 msgstr "Open archive indices:"
diff --git a/src/app.c b/src/app.c
index 5fc01a5b..b7db6dce 100644
--- a/src/app.c
+++ b/src/app.c
@@ -238,6 +238,7 @@ static iString *serializePrefs_App_(const iApp *d) {
     appendFormat_String(str, "prefs.centershort.changed arg:%d\n", d->prefs.centerShortDocs);
     appendFormat_String(str, "prefs.collapsepreonload.changed arg:%d\n", d->prefs.collapsePreOnLoad);
     appendFormat_String(str, "prefs.hoverlink.changed arg:%d\n", d->prefs.hoverLink);
+    appendFormat_String(str, "prefs.bookmarks.addbottom.changed arg:%d\n", d->prefs.addBookmarksToBottom);
     appendFormat_String(str, "prefs.archive.openindex.changed arg:%d\n", d->prefs.openArchiveIndexPages);
     appendFormat_String(str, "quoteicon.set arg:%d\n", d->prefs.quoteIcon ? 1 : 0);
     appendFormat_String(str, "theme.set arg:%d auto:1\n", d->prefs.theme);
@@ -2358,6 +2359,10 @@ iBool handleCommand_App(const char *cmd) {
         d->prefs.openArchiveIndexPages = arg_Command(cmd) != 0;
         return iTrue;
     }
+    else if (equal_Command(cmd, "prefs.bookmarks.addbottom.changed")) {
+        d->prefs.addBookmarksToBottom = arg_Command(cmd) != 0;
+        return iTrue;
+    }
     else if (equal_Command(cmd, "prefs.animate.changed")) {
         d->prefs.uiAnimations = arg_Command(cmd) != 0;
         return iTrue;
@@ -2654,6 +2659,7 @@ iBool handleCommand_App(const char *cmd) {
         setToggle_Widget(findChild_Widget(dlg, "prefs.smoothscroll"), d->prefs.smoothScrolling);
         setToggle_Widget(findChild_Widget(dlg, "prefs.imageloadscroll"), d->prefs.loadImageInsteadOfScrolling);
         setToggle_Widget(findChild_Widget(dlg, "prefs.hidetoolbarscroll"), d->prefs.hideToolbarOnScroll);
+        setToggle_Widget(findChild_Widget(dlg, "prefs.bookmarks.addbottom"), d->prefs.addBookmarksToBottom);
         setToggle_Widget(findChild_Widget(dlg, "prefs.archive.openindex"), d->prefs.openArchiveIndexPages);
         setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme);
         setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame);
diff --git a/src/bookmarks.c b/src/bookmarks.c
index 67c6a25f..1a260f3a 100644
--- a/src/bookmarks.c
+++ b/src/bookmarks.c
@@ -327,11 +327,18 @@ void save_Bookmarks(const iBookmarks *d, const char *dirPath) {
     unlock_Mutex(d->mtx);
 }
 
-static int maxOrder_Bookmarks_(const iBookmarks *d) {
-    int ord = 0;
+static iRangei orderRange_Bookmarks_(const iBookmarks *d) {
+    iRangei ord = { 0, 0 };
     iConstForEach(Hash, i, &d->bookmarks) {
         const iBookmark *bm = (const iBookmark *) i.value;
-        ord = iMax(ord, bm->order);
+        if (isEmpty_Range(&ord)) {
+            ord.start = bm->order;
+            ord.end = bm->order + 1;
+        }
+        else {
+            ord.start = iMin(ord.start, bm->order);
+            ord.end   = iMax(ord.end, bm->order + 1);
+        }
     }
     return ord;
 }
@@ -349,7 +356,13 @@ uint32_t add_Bookmarks(iBookmarks *d, const iString *url, const iString *title,
     }
     bm->icon = icon;
     initCurrent_Time(&bm->when);
-    bm->order = maxOrder_Bookmarks_(d) + 1; /* Last in lists. */
+    const iRangei ord = orderRange_Bookmarks_(d);
+    if (prefs_App()->addBookmarksToBottom) {
+        bm->order = ord.end; /* Last in lists. */
+    }
+    else {
+        bm->order = ord.start - 1; /* First in lists. */
+    }
     insert_Bookmarks_(d, bm);
     unlock_Mutex(d->mtx);
     return id_Bookmark(bm);
diff --git a/src/prefs.c b/src/prefs.c
index 088cc7bc..5fed0a76 100644
--- a/src/prefs.c
+++ b/src/prefs.c
@@ -47,6 +47,7 @@ void init_Prefs(iPrefs *d) {
     d->loadImageInsteadOfScrolling = iFalse;
     d->collapsePreOnLoad = iFalse;
     d->openArchiveIndexPages = iTrue;
+    d->addBookmarksToBottom  = iTrue;
     d->decodeUserVisibleURLs = iTrue;
     d->maxCacheSize      = 10;
     d->maxMemorySize     = 200;
diff --git a/src/prefs.h b/src/prefs.h
index 37aeb260..a04cda70 100644
--- a/src/prefs.h
+++ b/src/prefs.h
@@ -63,6 +63,7 @@ struct Impl_Prefs {
     iBool            collapsePreOnLoad;
     iString          searchUrl;
     iBool            openArchiveIndexPages;
+    iBool            addBookmarksToBottom;
     /* Network */
     iString          caFile;
     iString          caPath;
diff --git a/src/ui/util.c b/src/ui/util.c
index adca6269..b18a3292 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -2349,6 +2349,7 @@ iWidget *makePreferences_Widget(void) {
         setUrlContent_InputWidget(searchUrl, iTrue);
         addDialogPadding_(headings, values);
         addDialogToggle_(headings, values, "${prefs.hoverlink}", "prefs.hoverlink");
+        addDialogToggle_(headings, values, "${prefs.bookmarks.addbottom}", "prefs.bookmarks.addbottom");
         addDialogToggle_(headings, values, "${prefs.archive.openindex}", "prefs.archive.openindex");
         if (deviceType_App() != phone_AppDeviceType) {
             addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}")));