Include a Other Software section in Help file
[geeqie.git] / src / collect.c
index e3c0157..2d4ed65 100644 (file)
@@ -1,16 +1,24 @@
 /*
- * Geeqie
- * (C) 2006 John Ellis
- * Copyright (C) 2008 - 2012 The Geeqie Team
+ * Copyright (C) 2006 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
  *
  * Author: John Ellis
  *
- * This software is released under the GNU General Public License (GNU GPL).
- * Please read the included file COPYING for more information.
- * This software comes with no warranty of any kind, use at your own risk!
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
-
 #include "main.h"
 #include "collect.h"
 
@@ -22,6 +30,7 @@
 #include "img-view.h"
 #include "layout.h"
 #include "layout_image.h"
+#include "layout_util.h"
 #include "misc.h"
 #include "pixbuf_util.h"
 #include "print.h"
 #define COLLECT_DEF_WIDTH 440
 #define COLLECT_DEF_HEIGHT 450
 
+/**
+ *  list of paths to collections */
+
+/**
+ * @brief  List of currently open Collections.
+ * 
+ * Type ::_CollectionData 
+ */
 static GList *collection_list = NULL;
+
+/**
+ * @brief  List of currently open Collection windows.
+ * 
+ * Type ::_CollectWindow
+ */
 static GList *collection_window_list = NULL;
 
 static void collection_window_get_geometry(CollectWindow *cw);
@@ -141,9 +164,30 @@ static gint collection_list_sort_cb(gconstpointer a, gconstpointer b)
                        if (cia->fd->date > cib->fd->date) return 1;
                        return 0;
                        break;
+               case SORT_CTIME:
+                       if (cia->fd->cdate < cib->fd->cdate) return -1;
+                       if (cia->fd->cdate > cib->fd->cdate) return 1;
+                       return 0;
+                       break;
+               case SORT_EXIFTIME:
+                       if (cia->fd->exifdate < cib->fd->exifdate) return -1;
+                       if (cia->fd->exifdate > cib->fd->exifdate) return 1;
+                       break;
+               case SORT_EXIFTIMEDIGITIZED:
+                       if (cia->fd->exifdate_digitized < cib->fd->exifdate_digitized) return -1;
+                       if (cia->fd->exifdate_digitized > cib->fd->exifdate_digitized) return 1;
+                       break;
+               case SORT_RATING:
+                       if (cia->fd->rating < cib->fd->rating) return -1;
+                       if (cia->fd->rating > cib->fd->rating) return 1;
+                       break;
                case SORT_PATH:
                        return utf8_compare(cia->fd->path, cib->fd->path, options->file_sort.case_sensitive);
                        break;
+               case SORT_CLASS:
+                       if (cia->fd->format_class < cib->fd->format_class) return -1;
+                       if (cia->fd->format_class > cib->fd->format_class) return 1;
+                       break;
 #ifdef HAVE_STRVERSCMP
                case SORT_NUMBER:
                        return strverscmp(cia->fd->name, cib->fd->name);
@@ -294,6 +338,129 @@ CollectWindow *collection_window_find_by_path(const gchar *path)
        return NULL;
 }
 
+/**
+ * @brief Checks string for existence of Collection.
+ * @param[in] param Filename, with or without extension of any collection
+ * @returns full pathname if found or NULL
+ * 
+ * Return value must be freed with g_free()
+ */
+gchar *collection_path(const gchar *param)
+{
+       gchar *path = NULL;
+       gchar *full_name = NULL;
+
+       if (file_extension_match(param, GQ_COLLECTION_EXT))
+               {
+               path = g_build_filename(get_collections_dir(), param, NULL);
+               }
+       else if (file_extension_match(param, NULL))
+               {
+               full_name = g_strconcat(param, GQ_COLLECTION_EXT, NULL);
+               path = g_build_filename(get_collections_dir(), full_name, NULL);
+               }
+
+       if (!isfile(path))
+               {
+               g_free(path);
+               path = NULL;
+               }
+
+       g_free(full_name);
+       return path;
+}
+
+/**
+ * @brief Checks input string for existence of Collection.
+ * @param[in] param Filename with or without extension of any collection
+ * @returns TRUE if found
+ * 
+ * 
+ */
+gboolean is_collection(const gchar *param)
+{
+       gchar *name = NULL;
+
+       name = collection_path(param);
+       if (name)
+               {
+               g_free(name);
+               return TRUE;
+               }
+       return FALSE;
+}
+
+/**
+ * @brief Creates a text list of the image paths of the contents of a Collection
+ * @param[in] name The name of the collection, with or without extension
+ * @param[inout] contents A GString to which the image paths are appended
+ * 
+ * 
+ */
+void collection_contents(const gchar *name, GString **contents)
+{
+       gchar *path;
+       CollectionData *cd;
+       CollectInfo *ci;
+       GList *work;
+       FileData *fd;
+
+       if (is_collection(name))
+               {
+               path = collection_path(name);
+               cd = collection_new("");
+               collection_load(cd, path, COLLECTION_LOAD_APPEND);
+               work = cd->list;
+               while (work)
+                       {
+                       ci = work->data;
+                       fd = ci->fd;
+                       *contents = g_string_append(*contents, g_strdup(fd->path));
+                       *contents = g_string_append(*contents, "\n");
+
+                       work = work->next;
+                       }
+               g_free(path);
+               collection_free(cd);
+               }
+}
+
+/**
+ * @brief Returns a list of filedatas of the contents of a Collection
+ * @param[in] name The name of the collection, with or without extension
+ * 
+ * 
+ */
+GList *collection_contents_fd(const gchar *name)
+{
+       gchar *path;
+       CollectionData *cd;
+       CollectInfo *ci;
+       GList *work;
+       FileData *fd;
+       GList *list = NULL;
+
+       if (is_collection(name))
+               {
+               path = collection_path(name);
+               cd = collection_new("");
+               collection_load(cd, path, COLLECTION_LOAD_APPEND);
+               work = cd->list;
+               while (work)
+                       {
+                       ci = work->data;
+                       fd = ci->fd;
+                       list = g_list_append(list, ci->fd);
+
+                       work = work->next;
+                       }
+               g_free(path);
+               collection_free(cd);
+               }
+
+       return list;
+}
+
 /*
  *-------------------------------------------------------------------
  * please use these to actually add/remove stuff
@@ -827,6 +994,7 @@ static gboolean collection_window_keypress(GtkWidget *widget, GdkEventKey *event
                                file_util_rename(NULL, collection_table_selection_get_list(cw->table), cw->window);
                                break;
                        case 'D': case 'd':
+                               options->file_ops.safe_delete_enable = TRUE;
                                file_util_delete(NULL, collection_table_selection_get_list(cw->table), cw->window);
                                break;
                        case 'S': case 's':
@@ -895,6 +1063,12 @@ static gboolean collection_window_keypress(GtkWidget *widget, GdkEventKey *event
                                        collection_set_sort_method(cw->cd, SORT_PATH);
                                        }
                                break;
+                       case 'R': case 'r':
+                               if (event->state & GDK_MOD1_MASK)
+                                       {
+                                               options->collections.rectangular_selection = !(options->collections.rectangular_selection);
+                                       }
+                               break;
                        case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
                                list = g_list_copy(cw->table->selection);
                                if (list)
@@ -912,6 +1086,12 @@ static gboolean collection_window_keypress(GtkWidget *widget, GdkEventKey *event
                                break;
                        }
                }
+       if (!stop_signal && is_help_key(event))
+               {
+               help_window_show("GuideCollections.html");
+               stop_signal = TRUE;
+               }
+
        return stop_signal;
 }
 
@@ -1077,7 +1257,7 @@ static void collection_close_dlg_show(CollectWindow *cw)
                                collection_close_cancel_cb, cw);
        generic_dialog_add_message(gd, GTK_STOCK_DIALOG_QUESTION,
                                   _("Close collection"),
-                                  _("Collection has been modified.\nSave first?"));
+                                  _("Collection has been modified.\nSave first?"), TRUE);
 
        generic_dialog_add_button(gd, GTK_STOCK_SAVE, NULL, collection_close_save_cb, TRUE);
        generic_dialog_add_button(gd, GTK_STOCK_DELETE, _("_Discard"), collection_close_close_cb, FALSE);
@@ -1106,19 +1286,39 @@ void collection_window_close_by_collection(CollectionData *cd)
        if (cw) collection_window_close_final(cw);
 }
 
+/**
+ * @brief Check if any Collection windows have unsaved data
+ * @returns TRUE if unsaved data exists
+ * 
+ * Also saves window geometry for Collection windows that have
+ * no unsaved data
+ */
 gboolean collection_window_modified_exists(void)
 {
        GList *work;
+       gboolean ret;
+
+       ret = FALSE;
 
        work = collection_window_list;
        while (work)
                {
                CollectWindow *cw = work->data;
-               if (cw->cd->changed) return TRUE;
+               if (cw->cd->changed)
+                       {
+                       ret = TRUE;
+                       }
+               else
+                       {
+                       if (!collection_save(cw->table->cd, cw->table->cd->path))
+                               {
+                               log_printf("failed saving to collection path: %s\n", cw->table->cd->path);
+                               }
+                       }
                work = work->next;
                }
 
-       return FALSE;
+       return ret;
 }
 
 static gboolean collection_window_delete(GtkWidget *widget, GdkEvent *event, gpointer data)
@@ -1138,6 +1338,13 @@ CollectWindow *collection_window_new(const gchar *path)
        GtkWidget *extra_label;
        GdkGeometry geometry;
 
+       /* If the collection is already opened in another window, return that one */
+       cw = collection_window_find_by_path(path);
+       if (cw)
+               {
+               return cw;
+               }
+
        cw = g_new0(CollectWindow, 1);
 
        collection_window_list = g_list_append(collection_window_list, cw);
@@ -1145,6 +1352,7 @@ CollectWindow *collection_window_new(const gchar *path)
        cw->cd = collection_new(path);
 
        cw->window = window_new(GTK_WINDOW_TOPLEVEL, "collection", PIXBUF_INLINE_ICON_BOOK, NULL, NULL);
+       DEBUG_NAME(cw->window);
 
        geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
        geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
@@ -1153,11 +1361,15 @@ CollectWindow *collection_window_new(const gchar *path)
        gtk_window_set_geometry_hints(GTK_WINDOW(cw->window), NULL, &geometry,
                                      GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
 
+       if (options->collections_on_top)
+               {
+               gtk_window_set_keep_above(GTK_WINDOW(cw->window), TRUE);
+               }
 
        if (options->save_window_positions && path && collection_load_only_geometry(cw->cd, path))
                {
-               /* FIXME: x, y is not implemented */
                gtk_window_set_default_size(GTK_WINDOW(cw->window), cw->cd->window_w, cw->cd->window_h);
+               gtk_window_move(GTK_WINDOW(cw->window), cw->cd->window_x, cw->cd->window_y);
                }
        else
                {
@@ -1187,6 +1399,7 @@ CollectWindow *collection_window_new(const gchar *path)
        gtk_widget_show(cw->status_box);
 
        frame = gtk_frame_new(NULL);
+       DEBUG_NAME(frame);
        gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
        gtk_box_pack_start(GTK_BOX(cw->status_box), frame, TRUE, TRUE, 0);
        gtk_widget_show(frame);
@@ -1197,6 +1410,10 @@ CollectWindow *collection_window_new(const gchar *path)
 
        extra_label = gtk_progress_bar_new();
        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(extra_label), 0.0);
+#if GTK_CHECK_VERSION(3,0,0)
+       gtk_progress_bar_set_text(GTK_PROGRESS_BAR(extra_label), "");
+       gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(extra_label), TRUE);
+#endif
        gtk_box_pack_start(GTK_BOX(cw->status_box), extra_label, TRUE, TRUE, 0);
        gtk_widget_show(extra_label);