Fix #751: Allow to make layouts persistent and selectable
authorColin Clark <colin.clark@cclark.uk>
Sun, 22 Mar 2020 10:54:51 +0000 (10:54 +0000)
committerColin Clark <colin.clark@cclark.uk>
Sun, 22 Mar 2020 10:54:51 +0000 (10:54 +0000)
https://github.com/BestImageViewer/geeqie/issues/751

New top-level menu item "Windows" to permit saving and recalling window
layouts.

22 files changed:
doc/docbook/GuideMainWindow.xml
doc/docbook/GuideMainWindowMenus.xml
doc/docbook/GuideOptionsLayout.xml
doc/docbook/GuideReferenceCommandLine.xml
doc/docbook/GuideReferenceConfig.xml
geeqie.1
src/image.c
src/image.h
src/layout.c
src/layout.h
src/layout_image.c
src/layout_util.c
src/main.c
src/main.h
src/options.c
src/rcfile.c
src/rcfile.h
src/remote.c
src/toolbar.c
src/typedefs.h
src/ui_fileops.c
src/ui_fileops.h

index c681e56..217575e 100644 (file)
@@ -7,15 +7,18 @@
     , the\r
     <link linkend="GuideMainWindowFolderPane" endterm="titleGuideMainWindowFolderPane" />\r
     , and the\r
-    <link linkend="GuideMainWindowImagePane" endterm="titleGuideMainWindowImagePane" /> (optionally bordered by <link linkend="GuideSidebars" endterm="titleGuideSidebars"></link>)\r
-    , all bordered by \r
+    <link linkend="GuideMainWindowImagePane" endterm="titleGuideMainWindowImagePane" />\r
+    (optionally bordered by\r
+    <link linkend="GuideSidebars" endterm="titleGuideSidebars" />\r
+    )\r
+    , all bordered by\r
     <link linkend="GuideMainWindowMenus" endterm="titleGuideMainWindowMenus" />\r
     and\r
     <link linkend="GuideMainWindowStatusBar" endterm="titleGuideMainWindowStatusBar" />\r
     .\r
   </para>\r
   <para>\r
-    Multiple instances of the main window may be started via the \r
+    Multiple instances of the main window may be started via the\r
     <menuchoice>\r
       <shortcut>\r
         <keycombo>\r
@@ -23,7 +26,7 @@
           <keycap>N</keycap>\r
         </keycombo>\r
       </shortcut>\r
-      <guimenu>File</guimenu>\r
+      <guimenu>Windows</guimenu>\r
       <guimenuitem>New Window</guimenuitem>\r
     </menuchoice>\r
     menu item, and the\r
     dialogue.\r
   </para>\r
   <para>\r
-    If several instances of the main window are open, they may be closed individually or simultaneously as defined in\r
-    <link linkend="GuideOptionsLayout" endterm="titleGuideOptionsLayout" />\r
+    If several instances of the main window are open, they may be closed individually or simultaneously. If\r
+    <menuchoice>\r
+      <shortcut>\r
+        <keycombo>\r
+          <keycap>Ctrl</keycap>\r
+          <keycap>Q</keycap>\r
+        </keycombo>\r
+      </shortcut>\r
+      <guimenu>File</guimenu>\r
+      <guimenuitem>Quit</guimenuitem>\r
+    </menuchoice>\r
+    is pressed, all windows will be closed. When Geeqie is re-opened the same windows will be re-displayed.\r
+    <para />\r
+    If\r
+    <menuchoice>\r
+      <shortcut>\r
+        <keycombo>\r
+          <keycap>Ctrl</keycap>\r
+          <keycap>W</keycap>\r
+        </keycombo>\r
+      </shortcut>\r
+      <guimenu>Windows</guimenu>\r
+      <guimenuitem>Close Window</guimenuitem>\r
+    </menuchoice>\r
+    is pressed, only that window will be closed unless that window is the "main" window, in which case all windows will be closed. When Geeqie is restarted only the main window will be displayed.\r
+    <para />\r
+    The main window may be recognised by the word "Geeqie" in the title bar, as opposed to other windows which have the word "geeqie".\r
+    <para />\r
+    Several window layouts may be defined and displayed at any time, as described in the\r
+    <link linkend="Windowmenu">Windows menu</link>\r
     .\r
   </para>\r
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideMainWindowFilePane.xml" />\r
@@ -45,4 +76,4 @@
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideMainWindowStatusBar.xml" />\r
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideMainWindowLayout.xml" />\r
   <para />\r
-</chapter>
+</chapter>\r
index caba6ab..2b277aa 100644 (file)
     </variablelist>\r
     <para />\r
   </section>\r
+  <section id="Windowmenu">\r
+    <title>Windows menu</title>\r
+    <para>This menu permits you to define several different window layouts that can be recalled at any time.</para>\r
+    <variablelist>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <shortcut>\r
+              <keycombo>\r
+                <keycap>Ctrl</keycap>\r
+                <keycap>N</keycap>\r
+              </keycombo>\r
+            </shortcut>\r
+            <guimenu>New window</guimenu>\r
+            <guimenuitem>default</guimenuitem>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Opens a new window with the default layout. The window is given a name in the form "lw&lt;n&gt;".</para>\r
+        </listitem>\r
+      </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <guimenu>New window</guimenu>\r
+            <guimenuitem>from current</guimenuitem>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Opens a new window with the layout of the window that has focus.  The window is given a name in the form "lw&lt;n&gt;".</para>\r
+        </listitem>\r
+      </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <guimenu>Rename window</guimenu>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>If a window is given a name that does not begin with the characters "lw", the layout will be saved and be available for loading at a later time..</para>\r
+        </listitem>\r
+      </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <guimenu>Delete window</guimenu>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Delete a saved window layout from the list.</para>\r
+        </listitem>\r
+      </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <guimenu>Close window</guimenu>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>Close the window. If the window is the "main" window, close all windows.</para>\r
+        </listitem>\r
+      </varlistentry>\r
+    </variablelist>\r
+  </section>\r
   <section id="Helpmenu">\r
     <title>Help menu</title>\r
     <variablelist>\r
           <para>Opens the Geeqie user manual in a new browser window.</para>\r
         </listitem>\r
       </varlistentry>\r
+      <varlistentry>\r
+        <term>\r
+          <menuchoice>\r
+            <shortcut>\r
+              <keycombo>\r
+                <keycap>/</keycap>\r
+              </keycombo>\r
+            </shortcut>\r
+            <guimenu>Search and Run Commands</guimenu>\r
+          </menuchoice>\r
+        </term>\r
+        <listitem>\r
+          <para>\r
+            Opens a text box in which you may type characters to find a command. The characters you type will be matched to the first characters of any word in all the commands in Geeqie - both built-in commands and plugins - and matching actions will be displayed in a pop-up. You may scroll to any of the matching commands and press\r
+            <code>Enter</code>\r
+            to execute the command. Pressing\r
+            <code>Enter</code>\r
+            without selecting from the list will execute the first action in the list. Press\r
+            <code>Esc</code>\r
+            to close the box.\r
+          </para>\r
+        </listitem>\r
+      </varlistentry>\r
       <varlistentry>\r
         <term>\r
           <menuchoice>\r
     </variablelist>\r
     <para />\r
   </section>\r
-  <section id="Sarmenu">\r
-    <title>SaR menu</title>\r
-    <variablelist>\r
-      <varlistentry>\r
-        <term>\r
-          <menuchoice>\r
-            <shortcut>\r
-              <keycombo>\r
-                <keycap>/</keycap>\r
-              </keycombo>\r
-            </shortcut>\r
-            <guimenu>Search and Run Commands</guimenu>\r
-          </menuchoice>\r
-        </term>\r
-        <listitem>\r
-          <para>Opens a text box in which you may type characters to find a command. The characters you type will be matched to the first characters of any word in all the commands in Geeqie - both built-in commands and plugins - and matching actions will be displayed in a pop-up. You may scroll to any of the matching commands and press <code>Enter</code> to execute the command. Pressing <code>Enter</code> without selecting from the list will execute the first action in the list. Press <code>Esc</code> to close the box.</para>\r
-        </listitem>\r
-      </varlistentry>\r
-     </variablelist>\r
-    <para />\r
-  </section>\r
   <section id="Tearoffmenus">\r
     <title>Tear off menus</title>\r
     <para>Menus can be permanently displayed by selecting the dotted line item at the top of each menu category. A tear off menu will behave the same as an ordinary window. To close a tear off menu, select the dotted line item at the top of the menu.</para>\r
index 43589d7..7daa989 100644 (file)
         <para>The folder pane will show dates.</para>\r
       </listitem>\r
     </varlistentry>\r
-    <varlistentry>\r
-      <term>\r
-        <guilabel>Exit program when this window is closed</guilabel>\r
-      </term>\r
-      <listitem>\r
-        <para>\r
-          If multiple instances of Geeqie have been started via\r
-          <menuchoice>\r
-            <guimenu>File</guimenu>\r
-            <guimenuitem>New Window</guimenuitem>\r
-          </menuchoice>\r
-          , if this check box is clicked all instances will be closed when this window is exited.\r
-        </para>\r
-        <note>\r
-          <para>When a window with this check box selected is closed, the existence of all other open Geeqie windows will be remembered. When Geeqie is started again, all those windows will be re-opened.</para>\r
-        </note>\r
-      </listitem>\r
-    </varlistentry>\r
     <varlistentry>\r
       <term>\r
         <guilabel>Start-up directory</guilabel>\r
index 833da34..e407955 100644 (file)
           </row>\r
           <row>\r
             <entry />\r
-            <entry>--config-load:&lt;file&gt;</entry>\r
-            <entry>Load configuration from &lt;file&gt;.</entry>\r
+            <entry>--config-load:&lt;file&gt;|layout ID</entry>\r
+            <entry>Load configuration from &lt;file&gt;. Use either a full path, or a saved window layout ID.</entry>\r
           </row>\r
           <row>\r
             <entry />\r
index 6ad0cdc..dc12937 100644 (file)
     An alternative configuration file may be used by executing:\r
     <programlisting xml:space="preserve">geeqie -r --config-load:&lt;filename&gt;</programlisting>\r
   </para>\r
+  <para>\r
+    Saved window layout files are in the folder:\r
+    <programlisting xml:space="preserve">.../layouts</programlisting>\r
+  </para>\r
   <para>\r
     Geeqie-created desktop files used by\r
     <link linkend="GuideImageManagementPlugins">Plugins</link>\r
index 7d4e775..b2c9b25 100644 (file)
--- a/geeqie.1
+++ b/geeqie.1
@@ -148,7 +148,7 @@ Hide tools.
 Quit.
 .br
 .B
-.IP \-\-config-load:<FILE>
+.IP \-\-config-load:<FILE>|layout\ ID
 Load configuration from FILE.
 .br
 .B
index a36f21d..ad474d6 100644 (file)
@@ -45,7 +45,7 @@
 
 static GList *image_list = NULL;
 
-static void image_update_title(ImageWindow *imd);
+void image_update_title(ImageWindow *imd);
 static void image_read_ahead_start(ImageWindow *imd);
 static void image_cache_set(ImageWindow *imd, FileData *fd);
 
@@ -333,7 +333,7 @@ static void image_zoom_cb(PixbufRenderer *pr, gdouble zoom, gpointer data)
  *-------------------------------------------------------------------
  */
 
-static void image_update_title(ImageWindow *imd)
+void image_update_title(ImageWindow *imd)
 {
        gchar *title = NULL;
        gchar *zoom = NULL;
index 9933aa2..014dd1b 100644 (file)
@@ -153,6 +153,6 @@ void image_set_image_as_tiles(ImageWindow *imd, gint width, gint height,
 void image_options_sync(void);
 
 void image_get_rectangle(gint *x1, gint *y1, gint *x2, gint *y2);
-
+void image_update_title(ImageWindow *imd);
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index 6f5e209..bc97159 100644 (file)
@@ -150,6 +150,23 @@ LayoutWindow *layout_find_by_layout_id(const gchar *id)
        return NULL;
 }
 
+gchar *layout_get_unique_id()
+{
+       char id[10];
+       gint i;
+
+       i = 1;
+       while (TRUE)
+               {
+               g_snprintf(id, sizeof(id), "lw%d", i);
+               if (!layout_find_by_layout_id(id))
+                       {
+                       return g_strdup(id);
+                       }
+               i++;
+               }
+}
+
 static void layout_set_unique_id(LayoutWindow *lw)
 {
        char id[10];
@@ -2274,9 +2291,6 @@ void layout_show_config_window(LayoutWindow *lw)
        pref_checkbox_new_int(group, _("Show date in directories list view"),
                              lc->options.show_directory_date, &lc->options.show_directory_date);
 
-       pref_checkbox_new_int(group, _("Exit program when this window is closed"),
-                             lc->options.exit_on_close, &lc->options.exit_on_close);
-
        group = pref_group_new(vbox, FALSE, _("Start-up directory:"), GTK_ORIENTATION_VERTICAL);
 
        button = pref_radiobutton_new(group, NULL, _("No change"),
@@ -2356,12 +2370,53 @@ void layout_apply_options(LayoutWindow *lw, LayoutOptions *lop)
        if (refresh_lists) layout_refresh(lw);
 }
 
+void save_layout(LayoutWindow *lw)
+{
+       gchar *path;
+       gchar *xml_name;
+
+       if (!g_str_has_prefix(lw->options.id, "lw") && !g_str_equal(lw->options.id, "main"))
+               {
+               xml_name = g_strdup_printf("%s.xml", lw->options.id);
+               path = g_build_filename(get_window_layouts_dir(), xml_name, NULL);
+               save_config_to_file(path, options, lw);
+
+               g_free(xml_name);
+               g_free(path);
+               }
+}
 
 void layout_close(LayoutWindow *lw)
 {
-       if (!lw->options.exit_on_close && layout_window_list && layout_window_list->next)
+       GList *list;
+       LayoutWindow *tmp_lw;
+
+       if (layout_window_list && layout_window_list->next)
                {
-               layout_free(lw);
+               if (g_strcmp0(lw->options.id, "main") == 0)
+                       {
+                       while (layout_window_list && layout_window_list->next)
+                               {
+                               list = layout_window_list;
+                               while (list)
+                                       {
+                                       tmp_lw = list->data;
+                                       if (g_strcmp0(tmp_lw->options.id, "main") != 0)
+                                               {
+                                               save_layout(list->data);
+                                               layout_free(list->data);
+                                               break;
+                                               }
+                                       list = list->next;
+                                       }
+                               }
+                       exit_program();
+                       }
+               else
+                       {
+                       save_layout(lw);
+                       layout_free(lw);
+                       }
                }
        else
                {
@@ -2585,7 +2640,6 @@ void layout_write_attributes(LayoutOptions *layout, GString *outstr, gint indent
        WRITE_NL(); WRITE_CHAR(*layout, home_path);
        WRITE_NL(); WRITE_CHAR(*layout, last_path);
        WRITE_NL(); WRITE_UINT(*layout, startup_path);
-       WRITE_NL(); WRITE_BOOL(*layout, exit_on_close);
        WRITE_SEPARATOR();
 
        WRITE_NL(); WRITE_INT(*layout, main_window.x);
@@ -2686,7 +2740,6 @@ void layout_load_attributes(LayoutOptions *layout, const gchar **attribute_names
                if (READ_CHAR(*layout, home_path)) continue;
                if (READ_CHAR(*layout, last_path)) continue;
                if (READ_UINT_CLAMP(*layout, startup_path, 0, STARTUP_PATH_HOME)) continue;
-               if (READ_BOOL(*layout, exit_on_close)) continue;
 
                /* window positions */
 
index b78b6d6..78bbfaf 100644 (file)
@@ -124,5 +124,7 @@ void layout_info_pixel_set(LayoutWindow *lw, gboolean show);
 
 void layout_split_change(LayoutWindow *lw, ImageSplitMode mode);
 
+void save_layout(LayoutWindow *lw);
+gchar *layout_get_unique_id();
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index aaf7956..7db1803 100644 (file)
@@ -2128,7 +2128,7 @@ void layout_image_activate(LayoutWindow *lw, gint i, gboolean force)
        layout_image_set_buttons(lw);
        image_set_drag_func(lw->image, layout_image_drag_cb, lw);
 
-       image_attach_window(lw->image, lw->window, NULL, GQ_APPNAME, FALSE);
+       image_attach_window(lw->image, lw->window, NULL, g_strcmp0(lw->options.id, "main") == 0 ? GQ_APPNAME : GQ_APPNAME_LC, FALSE);
 
        /* do not hilight selected image in SPLIT_NONE */
        /* maybe the image should be selected always and hilight should be controled by
index 1a8b069..cf425de 100644 (file)
@@ -35,6 +35,7 @@
 #include "editors.h"
 #include "filedata.h"
 #include "history_list.h"
+#include "image.h"
 #include "image-overlay.h"
 #include "histogram.h"
 #include "img-view.h"
@@ -277,11 +278,6 @@ static void layout_menu_clear_marks_cb(GtkAction *action, gpointer data)
        gtk_widget_show(gd->dialog);
 }
 
-static void layout_menu_new_window_cb(GtkAction *action, gpointer data)
-{
-       layout_menu_new_window(action, data);
-}
-
 static void layout_menu_new_cb(GtkAction *action, gpointer data)
 {
        LayoutWindow *lw = data;
@@ -1930,6 +1926,455 @@ void layout_recent_add_path(const gchar *path)
        layout_recent_update_all();
 }
 
+/*
+ *-----------------------------------------------------------------------------
+ * window layout menu
+ *-----------------------------------------------------------------------------
+ */
+typedef struct _WindowNames WindowNames;
+struct _WindowNames
+{
+       gboolean displayed;
+       gchar *name;
+       gchar *path;
+};
+
+typedef struct _RenameWindow RenameWindow;
+struct _RenameWindow
+{
+       GenericDialog *gd;
+       LayoutWindow *lw;
+
+       GtkWidget *button_ok;
+       GtkWidget *window_name_entry;
+};
+
+typedef struct _DeleteWindow DeleteWindow;
+struct _DeleteWindow
+{
+       GenericDialog *gd;
+       LayoutWindow *lw;
+
+       GtkWidget *button_ok;
+       GtkWidget *group;
+};
+
+static gint layout_window_menu_list_sort_cb(gconstpointer a, gconstpointer b)
+{
+       const WindowNames *wna = a;
+       const WindowNames *wnb = b;
+
+       return g_strcmp0((gchar *)wna->name, (gchar *)wnb->name);
+}
+
+static GList *layout_window_menu_list(GList *listin)
+{
+       GList *list;
+       WindowNames *wn;
+       gboolean dupe;
+       DIR *dp;
+       struct dirent *dir;
+       gchar *pathl;
+
+       pathl = path_from_utf8(get_window_layouts_dir());
+       dp = opendir(pathl);
+       if (!dp)
+               {
+               /* dir not found */
+               g_free(pathl);
+               return listin;
+               }
+
+       while ((dir = readdir(dp)) != NULL)
+               {
+               gchar *name_file = dir->d_name;
+
+               if (g_str_has_suffix(name_file, ".xml"))
+                       {
+                       LayoutWindow *lw_tmp ;
+                       gchar *name_utf8 = path_to_utf8(name_file);
+                       gchar *name_base = g_strndup(name_utf8, strlen(name_utf8) - 4);
+                       list = layout_window_list;
+                       dupe = FALSE;
+                       while (list)
+                               {
+                               lw_tmp = list->data;
+                               if (g_strcmp0(lw_tmp->options.id, name_base) == 0)
+                                       {
+                                       dupe = TRUE;
+                                       }
+                               list = list->next;
+                               }
+                       gchar *dpath = g_build_filename(pathl, name_utf8, NULL);
+                       wn  = g_new0(WindowNames, 1);
+                       wn->displayed = dupe;
+                       wn->name = g_strdup(name_base);
+                       wn->path = g_strdup(dpath);
+                       listin = g_list_append(listin, wn);
+
+                       g_free(dpath);
+                       g_free(name_utf8);
+                       g_free(name_base);
+                       }
+               }
+       closedir(dp);
+
+       g_free(pathl);
+
+       return g_list_sort(listin, layout_window_menu_list_sort_cb);
+}
+
+static void layout_menu_new_window_cb(GtkWidget *widget, gpointer data)
+{
+       LayoutWindow *nw = NULL;
+       gint n;
+
+       n = GPOINTER_TO_INT(data);
+       GList *menulist = NULL;
+
+       menulist = layout_window_menu_list(menulist);
+       WindowNames *wn = g_list_nth(menulist, n )->data;
+
+       if (wn->path)
+               {
+               load_config_from_file(wn->path, FALSE);
+               }
+       else
+               {
+               log_printf(_("Error: window layout name: %s does not exist\n"), wn->path);
+               }
+}
+
+static void layout_menu_new_window_update(LayoutWindow *lw)
+{
+       GtkWidget *menu;
+       GtkWidget *sub_menu;
+       GtkWidget *item;
+       GList *children, *iter;
+       gint n;
+       GList *list = NULL;
+       gint i = 0;
+       WindowNames *wn;
+
+       if (!lw->ui_manager) return;
+
+       list = layout_window_menu_list(list);
+
+       menu = gtk_ui_manager_get_widget(lw->ui_manager, "/MainMenu/WindowsMenu/NewWindow");
+       sub_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu));
+
+       children = gtk_container_get_children(GTK_CONTAINER(sub_menu));
+       for (iter = children; iter != NULL; iter = g_list_next(iter), i++)
+               {
+               if (i >= 4) // separator, default, from current, separator
+                       {
+                       gtk_widget_destroy(GTK_WIDGET(iter->data));
+                       }
+               }
+       g_list_free(children);
+
+       menu_item_add_divider(sub_menu);
+
+       n = 0;
+       while (list)
+               {
+               wn = list->data;
+               item = menu_item_add_simple(sub_menu, wn->name, G_CALLBACK(layout_menu_new_window_cb), GINT_TO_POINTER(n));
+               if (wn->displayed)
+                       {
+                       gtk_widget_set_sensitive(item, FALSE);
+                       }
+               list = list->next;
+               n++;
+               }
+}
+
+static void window_rename_cancel_cb(GenericDialog *gd, gpointer data)
+{
+       RenameWindow *rw = data;
+
+       generic_dialog_close(rw->gd);
+       g_free(rw);
+}
+
+static void window_rename_ok(GenericDialog *gd, gpointer data)
+{
+       RenameWindow *rw = data;
+       gchar *path;
+       gboolean window_layout_name_exists = FALSE;
+       GList *list = NULL;
+       gchar *xml_name;
+       gchar *new_id;
+
+       new_id = g_strdup(gtk_entry_get_text(GTK_ENTRY(rw->window_name_entry)));
+
+       list = layout_window_menu_list(list);
+       while (list)
+               {
+               WindowNames *ln = list->data;
+               if (g_strcmp0(ln->name, new_id) == 0)
+                       {
+                       gchar *buf;
+                       buf = g_strdup_printf(_("Window layout name \"%s\" already exists."), new_id);
+                       warning_dialog(_("Rename window"), buf, GTK_STOCK_DIALOG_WARNING, rw->gd->dialog);
+                       g_free(buf);
+                       window_layout_name_exists = TRUE;
+                       break;
+                       }
+               list = list->next;
+               }
+
+       if (!window_layout_name_exists)
+               {
+               xml_name = g_strdup_printf("%s.xml", rw->lw->options.id);
+               path = g_build_filename(get_window_layouts_dir(), xml_name, NULL);
+
+               if (isfile(path))
+                       {
+                       unlink_file(path);
+                       }
+               g_free(xml_name);
+               g_free(path);
+
+               g_free(rw->lw->options.id);
+               rw->lw->options.id = g_strdup(new_id);
+               layout_menu_new_window_update(rw->lw);
+               layout_refresh(rw->lw);
+               image_update_title(rw->lw->image);
+               }
+
+       save_layout(rw->lw);
+
+       g_free(new_id);
+       generic_dialog_close(rw->gd);
+       g_free(rw);
+}
+
+static void window_rename_ok_cb(GenericDialog *gd, gpointer data)
+{
+       RenameWindow *rw = data;
+
+       window_rename_ok(gd, rw);
+}
+
+static gboolean window_rename_entry_activate_cb(GenericDialog *gd, gpointer data)
+{
+       RenameWindow *rw = data;
+
+       window_rename_ok(gd, rw);
+}
+
+static void window_delete_cancel_cb(GenericDialog *gd, gpointer data)
+{
+       DeleteWindow *dw = data;
+
+       g_free(dw);
+}
+
+static void window_delete_ok_cb(GenericDialog *gd, gpointer data)
+{
+       DeleteWindow *dw = data;
+       gchar *path;
+       gchar *xml_name;
+
+       xml_name = g_strdup_printf("%s.xml", dw->lw->options.id);
+       path = g_build_filename(get_window_layouts_dir(), xml_name, NULL);
+
+       layout_close(dw->lw);
+       g_free(dw);
+
+       if (isfile(path))
+               {
+               unlink_file(path);
+               }
+       g_free(xml_name);
+       g_free(path);
+}
+
+static void layout_menu_window_default_cb(GtkWidget *widget, gpointer data)
+{
+       layout_new_from_config(NULL, NULL, TRUE);
+}
+
+static void layout_menu_windows_menu_cb(GtkWidget *widget, gpointer data)
+{
+       LayoutWindow *lw = data;
+       GtkWidget *menu;
+       GtkWidget *sub_menu;
+       gchar *menu_label;
+       GList *children, *iter;
+       gint i;
+
+       menu = gtk_ui_manager_get_widget(lw->ui_manager, "/MainMenu/WindowsMenu/");
+       sub_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu));
+
+       /* disable Rename and Delete for main window */
+       if (g_strcmp0(lw->options.id, "main") == 0)
+               {
+               i = 0;
+               children = gtk_container_get_children(GTK_CONTAINER(sub_menu));
+               for (iter = children; iter != NULL; iter = g_list_next(iter), i++)
+                       {
+                       menu_label = g_strdup(gtk_menu_item_get_label(GTK_MENU_ITEM(iter->data)));
+                       if (g_strcmp0(menu_label, _("Delete window")) == 0 || g_strcmp0(menu_label, _("Rename window")) == 0)
+                               {
+                               gtk_widget_set_sensitive(GTK_WIDGET(iter->data), FALSE);
+                               }
+                       g_free(menu_label);
+                       }
+               g_list_free(children);
+               }
+}
+
+static void change_window_id(const gchar *infile, const gchar *outfile)
+{
+       GFile *in_file;
+       GFile *out_file;
+       GFileInputStream *in_file_stream;
+       GFileOutputStream *out_file_stream;
+       GDataInputStream *in_data_stream;
+       GDataOutputStream *out_data_stream;
+       gchar *line;
+       gchar *id_name;
+
+       id_name = layout_get_unique_id();
+
+       in_file = g_file_new_for_path(infile);
+       in_file_stream = g_file_read(in_file, NULL, NULL);
+       in_data_stream = g_data_input_stream_new(G_INPUT_STREAM(in_file_stream));
+
+       out_file = g_file_new_for_path(outfile);
+       out_file_stream = g_file_append_to(out_file, G_FILE_CREATE_PRIVATE, NULL, NULL);
+       out_data_stream = g_data_output_stream_new(G_OUTPUT_STREAM(out_file_stream));
+
+       while (line = g_data_input_stream_read_line(in_data_stream, NULL, NULL, NULL))
+               {
+               if (g_str_has_suffix(line, "<layout"))
+                       {
+                       g_data_output_stream_put_string(out_data_stream, line, NULL, NULL);
+                       g_data_output_stream_put_string(out_data_stream, "\n", NULL, NULL);
+                       g_free(line);
+
+                       line = g_data_input_stream_read_line(in_data_stream, NULL, NULL, NULL);
+                       g_data_output_stream_put_string(out_data_stream, "id = \"", NULL, NULL);
+                       g_data_output_stream_put_string(out_data_stream, id_name, NULL, NULL);
+                       g_data_output_stream_put_string(out_data_stream, "\"\n", NULL, NULL);
+                       }
+               else
+                       {
+                       g_data_output_stream_put_string(out_data_stream, line, NULL, NULL);
+                       g_data_output_stream_put_string(out_data_stream, "\n", NULL, NULL);
+                       }
+               g_free(line);
+               }
+
+       g_free(id_name);
+       g_object_unref(out_data_stream);
+       g_object_unref(in_data_stream);
+       g_object_unref(out_file_stream);
+       g_object_unref(in_file_stream);
+       g_object_unref(out_file);
+       g_object_unref(in_file);
+}
+
+static void layout_menu_window_from_current_cb(GtkWidget *widget, gpointer data)
+{
+       LayoutWindow *lw = data;
+       gint fd_in = -1;
+       gint fd_out = -1;
+       char * tmp_file_in;
+       char * tmp_file_out;
+       GError *error = NULL;
+
+       fd_in = g_file_open_tmp("geeqie_layout_name_XXXXXX.xml", &tmp_file_in, &error);
+       if (error)
+               {
+               log_printf("Error: Window layout - cannot create file:%s\n",error->message);
+               g_error_free(error);
+               return;
+               }
+       close(fd_in);
+       fd_out = g_file_open_tmp("geeqie_layout_name_XXXXXX.xml", &tmp_file_out, &error);
+       if (error)
+               {
+               log_printf("Error: Window layout - cannot create file:%s\n",error->message);
+               g_error_free(error);
+               return;
+               }
+       close(fd_out);
+
+       save_config_to_file(tmp_file_in, options, lw);
+       change_window_id(tmp_file_in, tmp_file_out);
+       load_config_from_file(tmp_file_out, FALSE);
+
+       unlink_file(tmp_file_in);
+       unlink_file(tmp_file_out);
+       g_free(tmp_file_in);
+       g_free(tmp_file_out);
+}
+
+static void layout_menu_window_cb(GtkWidget *widget, gpointer data)
+{
+       LayoutWindow *lw = data;
+
+       layout_menu_new_window_update(lw);
+}
+
+static void layout_menu_window_rename_cb(GtkWidget *widget, gpointer data)
+{
+       LayoutWindow *lw = data;
+       RenameWindow *rw;
+       GtkWidget *hbox;
+
+       rw = g_new0(RenameWindow, 1);
+       rw->lw = lw;
+
+       rw->gd = generic_dialog_new(_("Rename window"), "rename_window", NULL, FALSE, window_rename_cancel_cb, rw);
+       rw->button_ok = generic_dialog_add_button(rw->gd, GTK_STOCK_OK, _("OK"), window_rename_ok_cb, TRUE);
+
+       generic_dialog_add_message(rw->gd, NULL, _("rename window"), NULL, FALSE);
+
+       hbox = pref_box_new(rw->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
+       pref_spacer(hbox, PREF_PAD_INDENT);
+
+       hbox = pref_box_new(rw->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
+
+       rw->window_name_entry = gtk_entry_new();
+       gtk_widget_set_can_focus(rw->window_name_entry, TRUE);
+       gtk_editable_set_editable(GTK_EDITABLE(rw->window_name_entry), TRUE);
+       gtk_entry_set_text(GTK_ENTRY(rw->window_name_entry), lw->options.id);
+       gtk_box_pack_start(GTK_BOX(hbox), rw->window_name_entry, TRUE, TRUE, 0);
+       gtk_widget_grab_focus(GTK_WIDGET(rw->window_name_entry));
+       gtk_widget_show(rw->window_name_entry);
+       g_signal_connect(rw->window_name_entry, "activate", G_CALLBACK(window_rename_entry_activate_cb), rw);
+
+       gtk_widget_show(rw->gd->dialog);
+}
+
+static void layout_menu_window_delete_cb(GtkWidget *widget, gpointer data)
+{
+       LayoutWindow *lw = data;
+       DeleteWindow *dw;
+       GtkWidget *hbox;
+
+       dw = g_new0(DeleteWindow, 1);
+       dw->lw = lw;
+
+       dw->gd = generic_dialog_new(_("Delete window"), "delete_window", NULL, TRUE, window_delete_cancel_cb, dw);
+       dw->button_ok = generic_dialog_add_button(dw->gd, GTK_STOCK_OK, _("OK"), window_delete_ok_cb, TRUE);
+
+       generic_dialog_add_message(dw->gd, NULL, _("Delete window layout"), NULL, FALSE);
+
+       hbox = pref_box_new(dw->gd->vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
+       pref_spacer(hbox, PREF_PAD_INDENT);
+       dw->group = pref_box_new(hbox, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
+
+       hbox = pref_box_new(dw->group, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
+       pref_label_new(hbox, (lw->options.id));
+
+       gtk_widget_show(dw->gd->dialog);
+}
+
 /*
  *-----------------------------------------------------------------------------
  * menu
@@ -1955,7 +2400,7 @@ static GtkActionEntry menu_entries[] = {
   { "StereoMenu",      NULL,                   N_("Stere_o"),                          NULL,                   NULL,                                   NULL },
   { "OverlayMenu",     NULL,                   N_("Image _Overlay"),                   NULL,                   NULL,                                   NULL },
   { "PluginsMenu",     NULL,                   N_("_Plugins"),                         NULL,                   NULL,                                   NULL },
-  { "SarMenu",         NULL,                   N_("S_aR"),                             NULL,                   NULL,                                   NULL },
+  { "WindowsMenu",             NULL,           N_("_Windows"),                         NULL,                   NULL,                                   CB(layout_menu_windows_menu_cb)  },
   { "HelpMenu",                NULL,                   N_("_Help"),                            NULL,                   NULL,                                   NULL },
 
   { "FirstImage",      GTK_STOCK_GOTO_TOP,     N_("_First Image"),                     "Home",                 N_("First Image"),                      CB(layout_menu_image_first_cb) },
@@ -1980,8 +2425,11 @@ static GtkActionEntry menu_entries[] = {
   { "Forward", GTK_STOCK_GO_FORWARD,   N_("_Forward"),         NULL,   N_("Forward in folder history"),        CB(layout_menu_forward_cb) },
   { "Home",            GTK_STOCK_HOME,         N_("_Home"),                    NULL,   N_("Home"),                             CB(layout_menu_home_cb) },
   { "Up",              GTK_STOCK_GO_UP,        N_("_Up"),                              NULL,   N_("Up one folder"),                            CB(layout_menu_up_cb) },
-
-  { "NewWindow",       GTK_STOCK_NEW,          N_("New _window"),                      "<control>N",           N_("New window"),                       CB(layout_menu_new_window_cb) },
+  { "NewWindow",       NULL,           N_("New window"),                       NULL,           N_("New window"),       CB(layout_menu_window_cb) },
+  { "NewWindowDefault",        NULL,   N_("default"),                  "<control>N",           N_("default"),  CB(layout_menu_window_default_cb)  },
+  { "NewWindowFromCurrent",    NULL,   N_("from current"),                     NULL,           N_("from current"),     CB(layout_menu_window_from_current_cb)  },
+  { "RenameWindow",    GTK_STOCK_EDIT,         N_("Rename window"),    NULL,   N_("Rename window"),    CB(layout_menu_window_rename_cb) },
+  { "DeleteWindow",    GTK_STOCK_DELETE,               N_("Delete window"),    NULL,   N_("Delete window"),    CB(layout_menu_window_delete_cb) },
   { "NewCollection",   GTK_STOCK_INDEX,        N_("_New collection"),                  "C",                    N_("New collection"),                   CB(layout_menu_new_cb) },
   { "OpenCollection",  GTK_STOCK_OPEN,         N_("_Open collection..."),              "O",                    N_("Open collection..."),               CB(layout_menu_open_cb) },
   { "OpenRecent",      NULL,                   N_("Open recen_t"),                     NULL,                   N_("Open recent collection"),                   NULL },
@@ -2168,7 +2616,6 @@ static const gchar *menu_ui_description =
 "<ui>"
 "  <menubar name='MainMenu'>"
 "    <menu action='FileMenu'>"
-"      <menuitem action='NewWindow'/>"
 "      <menuitem action='NewCollection'/>"
 "      <menuitem action='OpenCollection'/>"
 "      <menuitem action='OpenRecent'/>"
@@ -2192,7 +2639,6 @@ static const gchar *menu_ui_description =
 "      <placeholder name='FileOpsSection'/>"
 "      <separator/>"
 "      <placeholder name='QuitSection'/>"
-"      <menuitem action='CloseWindow'/>"
 "      <menuitem action='Quit'/>"
 "      <separator/>"
 "    </menu>"
@@ -2394,12 +2840,20 @@ static const gchar *menu_ui_description =
 "      <placeholder name='SlideShowSection'/>"
 "      <separator/>"
 "    </menu>"
-"    <menu action='SarMenu'>"
-"      <menuitem action='SearchAndRunCommand'/>"
+"    <menu action='WindowsMenu'>"
+"      <menu action='NewWindow'>"
+"        <menuitem action='NewWindowDefault'/>"
+"        <menuitem action='NewWindowFromCurrent'/>"
+"        <separator/>"
+"       </menu>"
+"      <menuitem action='RenameWindow'/>"
+"      <menuitem action='DeleteWindow'/>"
+"      <menuitem action='CloseWindow'/>"
 "    </menu>"
 "    <menu action='HelpMenu'>"
 "      <separator/>"
 "      <menuitem action='HelpContents'/>"
+"      <menuitem action='SearchAndRunCommand'/>"
 "      <menuitem action='HelpSearch'/>"
 "      <menuitem action='HelpShortcuts'/>"
 "      <menuitem action='HelpKbd'/>"
index 3fcb811..404c04c 100644 (file)
@@ -923,6 +923,7 @@ gint main(gint argc, gchar *argv[])
        mkdir_if_not_exists(get_collections_dir());
        mkdir_if_not_exists(get_thumbnails_cache_dir());
        mkdir_if_not_exists(get_metadata_cache_dir());
+       mkdir_if_not_exists(get_window_layouts_dir());
 
        setup_env_path();
 
index b1976e4..957331a 100644 (file)
@@ -83,6 +83,7 @@
 #define GQ_RC_DIR              "." GQ_APPNAME_LC
 #define GQ_COLLECTIONS_DIR     "collections"
 #define GQ_TRASH_DIR           "trash"
+#define GQ_WINDOW_LAYOUTS_DIR  "layouts"
 
 #define GQ_SYSTEM_WIDE_DIR    "/etc/" GQ_APPNAME_LC
 
index 3be6905..a28b7f5 100644 (file)
@@ -341,7 +341,7 @@ void save_options(ConfOptions *options)
        sync_options_with_current_state(options);
 
        rc_path = g_build_filename(get_rc_dir(), RC_FILE_NAME, NULL);
-       save_config_to_file(rc_path, options);
+       save_config_to_file(rc_path, options, NULL);
        g_free(rc_path);
 }
 
index 899e9c2..541278d 100644 (file)
@@ -619,7 +619,7 @@ static void write_disabled_plugins(GString *outstr, gint indent)
  *-----------------------------------------------------------------------------
  */
 
-gboolean save_config_to_file(const gchar *utf8_path, ConfOptions *options)
+gboolean save_config_to_file(const gchar *utf8_path, ConfOptions *options, LayoutWindow *lw)
 {
        SecureSaveInfo *ssi;
        gchar *rc_pathl;
@@ -652,42 +652,51 @@ gboolean save_config_to_file(const gchar *utf8_path, ConfOptions *options)
        WRITE_STRING("<gq>\n");
        indent++;
 
-       WRITE_NL(); WRITE_STRING("<global\n");
-       indent++;
-       write_global_attributes(outstr, indent + 1);
-       indent--;
-       WRITE_STRING(">\n");
+       if (!lw)
+               {
+               WRITE_NL(); WRITE_STRING("<global\n");
+               indent++;
+               write_global_attributes(outstr, indent + 1);
+               indent--;
+               WRITE_STRING(">\n");
 
-       indent++;
+               indent++;
 
-       write_color_profile(outstr, indent);
+               write_color_profile(outstr, indent);
 
-       WRITE_SEPARATOR();
-       filter_write_list(outstr, indent);
+               WRITE_SEPARATOR();
+               filter_write_list(outstr, indent);
 
-       WRITE_SEPARATOR();
-       write_marks_tooltips(outstr, indent);
+               WRITE_SEPARATOR();
+               write_marks_tooltips(outstr, indent);
 
-       WRITE_SEPARATOR();
-       write_disabled_plugins(outstr, indent);
+               WRITE_SEPARATOR();
+               write_disabled_plugins(outstr, indent);
 
-       WRITE_SEPARATOR();
-       write_class_filter(outstr, indent);
-
-       WRITE_SEPARATOR();
-       keyword_tree_write_config(outstr, indent);
-       indent--;
-       WRITE_NL(); WRITE_STRING("</global>\n");
+               WRITE_SEPARATOR();
+               write_class_filter(outstr, indent);
 
+               WRITE_SEPARATOR();
+               keyword_tree_write_config(outstr, indent);
+               indent--;
+               WRITE_NL(); WRITE_STRING("</global>\n");
+               }
        WRITE_SEPARATOR();
 
        /* Layout Options */
-       work = layout_window_list;
-       while (work)
+       if (!lw)
+               {
+               work = layout_window_list;
+               while (work)
+                       {
+                       LayoutWindow *lw = work->data;
+                       layout_write_config(lw, outstr, indent);
+                       work = work->next;
+                       }
+               }
+       else
                {
-               LayoutWindow *lw = work->data;
                layout_write_config(lw, outstr, indent);
-               work = work->next;
                }
 
        indent--;
index ae53e1c..919139b 100644 (file)
@@ -83,7 +83,7 @@ void options_parse_func_pop(GQParserData *parser_data);
 void options_parse_func_set_data(GQParserData *parser_data, gpointer data);
 
 
-gboolean save_config_to_file(const gchar *utf8_path, ConfOptions *options);
+gboolean save_config_to_file(const gchar *utf8_path, ConfOptions *options, LayoutWindow *lw);
 
 gboolean load_config_from_buf(const gchar *buf, gsize size, gboolean startup);
 gboolean load_config_from_file(const gchar *utf8_path, gboolean startup);
index 0c903cc..cc30630 100644 (file)
@@ -1058,10 +1058,58 @@ static void gr_file_info(const gchar *text, GIOChannel *channel, gpointer data)
                }
 }
 
+static gchar *config_file_path(const gchar *param)
+{
+       gchar *path = NULL;
+       gchar *full_name = NULL;
+
+       if (file_extension_match(param, ".xml"))
+               {
+               path = g_build_filename(get_window_layouts_dir(), param, NULL);
+               }
+       else if (file_extension_match(param, NULL))
+               {
+               full_name = g_strconcat(param, ".xml", NULL);
+               path = g_build_filename(get_window_layouts_dir(), full_name, NULL);
+               }
+
+       if (!isfile(path))
+               {
+               g_free(path);
+               path = NULL;
+               }
+
+       g_free(full_name);
+       return path;
+}
+
+static gboolean is_config_file(const gchar *param)
+{
+       gchar *name = NULL;
+
+       name = config_file_path(param);
+       if (name)
+               {
+               g_free(name);
+               return TRUE;
+               }
+       return FALSE;
+}
+
 static void gr_config_load(const gchar *text, GIOChannel *channel, gpointer data)
 {
        gchar *filename = expand_tilde(text);
 
+       if (!g_strstr_len(filename, -1, G_DIR_SEPARATOR_S))
+               {
+               if (is_config_file(filename))
+                       {
+                       gchar *tmp = config_file_path(filename);
+                       g_free(filename);
+                       filename = tmp;
+                       }
+               }
+
        if (isfile(filename))
                {
                load_config_from_file(filename, FALSE);
@@ -1251,7 +1299,7 @@ static RemoteCommandEntry remote_commands[] = {
        { "+t", "--tools-show",         gr_tools_show,          FALSE, TRUE,  NULL, N_("show tools") },
        { "-t", "--tools-hide",         gr_tools_hide,          FALSE, TRUE,  NULL, N_("hide tools") },
        { "-q", "--quit",               gr_quit,                FALSE, FALSE, NULL, N_("quit") },
-       { NULL, "--config-load:",       gr_config_load,         TRUE,  FALSE, N_("<FILE>"), N_("load configuration from FILE") },
+       { NULL, "--config-load:",       gr_config_load,         TRUE,  FALSE, N_("<FILE>|layout ID"), N_("load configuration from FILE") },
        { NULL, "--get-sidecars:",      gr_get_sidecars,        TRUE,  FALSE, N_("<FILE>"), N_("get list of sidecars of FILE") },
        { NULL, "--get-destination:",   gr_get_destination,     TRUE,  FALSE, N_("<FILE>"), N_("get destination path of FILE") },
        { NULL, "file:",                gr_file_load,           TRUE,  FALSE, N_("<FILE>"), N_("open FILE, bring Geeqie window to the top") },
index 472429d..20f0565 100644 (file)
@@ -85,7 +85,7 @@ static const UseableToolbarItems useable_toolbar_items[] = {
        {"PrevPage",    N_("Previous Page"), GTK_STOCK_MEDIA_REWIND},
        {"ImageForward",        N_("Image Forward"), GTK_STOCK_GOTO_LAST},
        {"ImageBack",   N_("Image Back"), GTK_STOCK_GOTO_FIRST},
-       {"NewWindow",   N_("New _window"), GTK_STOCK_NEW},
+       {"NewWindow",   N_("New window"), GTK_STOCK_NEW},
        {"NewCollection",       N_("New collection"), GTK_STOCK_INDEX},
        {"OpenCollection",      N_("Open collection"), GTK_STOCK_OPEN},
        {"Search",      N_("Search"), GTK_STOCK_FIND},
index fab68a8..9a4dfdb 100644 (file)
@@ -720,8 +720,6 @@ struct _LayoutOptions
 
        StartUpPath startup_path;
 
-       gboolean exit_on_close;
-
        gboolean animate;
 
        SortActionType action;
index 0d8de3b..bd1c81a 100644 (file)
@@ -302,6 +302,24 @@ const gchar *get_trash_dir(void)
        return trash_dir;
 }
 
+const gchar *get_window_layouts_dir(void)
+{
+       static gchar *window_layouts_dir = NULL;
+
+       if (window_layouts_dir) return window_layouts_dir;
+
+       if (USE_XDG)
+               {
+               window_layouts_dir = g_build_filename(xdg_config_home_get(), GQ_APPNAME_LC, GQ_WINDOW_LAYOUTS_DIR, NULL);
+               }
+       else
+               {
+               window_layouts_dir = g_build_filename(get_rc_dir(), GQ_WINDOW_LAYOUTS_DIR, NULL);
+               }
+
+       return window_layouts_dir;
+}
+
 gboolean stat_utf8(const gchar *s, struct stat *st)
 {
        gchar *sl;
index d680255..0eba9d0 100644 (file)
@@ -56,6 +56,7 @@ const gchar *homedir(void);
 const gchar *get_rc_dir(void);
 const gchar *get_collections_dir(void);
 const gchar *get_trash_dir(void);
+const gchar *get_window_layouts_dir(void);
 
 gboolean stat_utf8(const gchar *s, struct stat *st);
 gboolean lstat_utf8(const gchar *s, struct stat *st);