Bug fix #229: File Compression and Archiving
[geeqie.git] / src / view_file / view_file.c
index de8b14b..1d4eaf3 100644 (file)
 #include "collect.h"
 #include "collect-table.h"
 #include "editors.h"
+#include "history_list.h"
 #include "layout.h"
 #include "menu.h"
+#include "misc.h"
+#include "pixbuf_util.h"
 #include "thumb.h"
 #include "ui_menu.h"
 #include "ui_fileops.h"
+#include "ui_misc.h"
 #include "utilops.h"
 #include "view_file/view_file_list.h"
 #include "view_file/view_file_icon.h"
+#include "window.h"
 
 /*
  *-----------------------------------------------------------------------------
@@ -73,11 +78,16 @@ FileData *vf_index_get_data(ViewFile *vf, gint row)
 
 gint vf_index_by_fd(ViewFile *vf, FileData *fd)
 {
+       gint ret;
+
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_index_by_fd(vf, fd);
-       case FILEVIEW_ICON: return vficon_index_by_fd(vf, fd);
+       case FILEVIEW_LIST: ret = vflist_index_by_fd(vf, fd); break;
+       case FILEVIEW_ICON: ret = vficon_index_by_fd(vf, fd); break;
+       default: ret = 0;
        }
+
+       return ret;
 }
 
 guint vf_count(ViewFile *vf, gint64 *bytes)
@@ -124,12 +134,16 @@ GList *vf_get_list(ViewFile *vf)
 static gboolean vf_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
        ViewFile *vf = data;
+       gboolean ret;
 
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_press_key_cb(widget, event, data);
-       case FILEVIEW_ICON: return vficon_press_key_cb(widget, event, data);
+       case FILEVIEW_LIST: ret = vflist_press_key_cb(widget, event, data); break;
+       case FILEVIEW_ICON: ret = vficon_press_key_cb(widget, event, data); break;
+       default: ret = FALSE;
        }
+
+       return ret;
 }
 
 /*
@@ -141,23 +155,31 @@ static gboolean vf_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer
 static gboolean vf_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
 {
        ViewFile *vf = data;
+       gboolean ret;
 
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_press_cb(widget, bevent, data);
-       case FILEVIEW_ICON: return vficon_press_cb(widget, bevent, data);
+       case FILEVIEW_LIST: ret = vflist_press_cb(widget, bevent, data); break;
+       case FILEVIEW_ICON: ret = vficon_press_cb(widget, bevent, data); break;
+       default: ret = FALSE;
        }
+
+       return ret;
 }
 
 static gboolean vf_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
 {
        ViewFile *vf = data;
+       gboolean ret;
 
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_release_cb(widget, bevent, data);
-       case FILEVIEW_ICON: return vficon_release_cb(widget, bevent, data);
+       case FILEVIEW_LIST: ret = vflist_release_cb(widget, bevent, data); break;
+       case FILEVIEW_ICON: ret = vficon_release_cb(widget, bevent, data); break;
+       default: ret = FALSE;
        }
+
+       return ret;
 }
 
 
@@ -167,41 +189,46 @@ static gboolean vf_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointe
  *-----------------------------------------------------------------------------
  */
 
-gboolean vf_index_is_selected(ViewFile *vf, gint row)
-{
-       switch (vf->type)
-       {
-       case FILEVIEW_LIST: return vflist_index_is_selected(vf, row);
-       case FILEVIEW_ICON: return vficon_index_is_selected(vf, row);
-       }
-}
-
-
 guint vf_selection_count(ViewFile *vf, gint64 *bytes)
 {
+       guint ret;
+
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_selection_count(vf, bytes);
-       case FILEVIEW_ICON: return vficon_selection_count(vf, bytes);
+       case FILEVIEW_LIST: ret = vflist_selection_count(vf, bytes); break;
+       case FILEVIEW_ICON: ret = vficon_selection_count(vf, bytes); break;
+       default: ret = 0;
        }
+
+       return ret;
 }
 
 GList *vf_selection_get_list(ViewFile *vf)
 {
+       GList *ret;
+
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_selection_get_list(vf);
-       case FILEVIEW_ICON: return vficon_selection_get_list(vf);
+       case FILEVIEW_LIST: ret = vflist_selection_get_list(vf); break;
+       case FILEVIEW_ICON: ret = vficon_selection_get_list(vf); break;
+       default: ret = NULL;
        }
+
+       return ret;
 }
 
 GList *vf_selection_get_list_by_index(ViewFile *vf)
 {
+       GList *ret;
+
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_selection_get_list_by_index(vf);
-       case FILEVIEW_ICON: return vficon_selection_get_list_by_index(vf);
+       case FILEVIEW_LIST: ret = vflist_selection_get_list_by_index(vf); break;
+       case FILEVIEW_ICON: ret = vficon_selection_get_list_by_index(vf); break;
+       default: ret = NULL;
        }
+
+       return ret;
 }
 
 void vf_select_all(ViewFile *vf)
@@ -240,6 +267,15 @@ void vf_select_by_fd(ViewFile *vf, FileData *fd)
        }
 }
 
+void vf_select_list(ViewFile *vf, GList *list)
+{
+       switch (vf->type)
+       {
+       case FILEVIEW_LIST: vflist_select_list(vf, list); break;
+       case FILEVIEW_ICON: vficon_select_list(vf, list); break;
+       }
+}
+
 void vf_mark_to_selection(ViewFile *vf, gint mark, MarkToSelectionMode mode)
 {
        switch (vf->type)
@@ -282,20 +318,30 @@ static void vf_dnd_init(ViewFile *vf)
 
 GList *vf_pop_menu_file_list(ViewFile *vf)
 {
+       GList *ret;
+
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_pop_menu_file_list(vf);
-       case FILEVIEW_ICON: return vficon_pop_menu_file_list(vf);
+       case FILEVIEW_LIST: ret = vflist_pop_menu_file_list(vf); break;
+       case FILEVIEW_ICON: ret = vficon_pop_menu_file_list(vf); break;
+       default: ret = NULL;
        }
+
+       return ret;
 }
 
 GList *vf_selection_get_one(ViewFile *vf, FileData *fd)
 {
+       GList *ret;
+
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_selection_get_one(vf, fd);
-       case FILEVIEW_ICON: return vficon_selection_get_one(vf, fd);
+       case FILEVIEW_LIST: ret = vflist_selection_get_one(vf, fd); break;
+       case FILEVIEW_ICON: ret = vficon_selection_get_one(vf, fd); break;
+       default: ret = NULL;
        }
+
+       return ret;
 }
 
 static void vf_pop_menu_edit_cb(GtkWidget *widget, gpointer data)
@@ -321,6 +367,36 @@ static void vf_pop_menu_view_cb(GtkWidget *widget, gpointer data)
        }
 }
 
+static void vf_pop_menu_open_archive_cb(GtkWidget *widget, gpointer data)
+{
+       ViewFile *vf = data;
+       LayoutWindow *lw_new;
+       FileData *fd;
+       gchar *dest_dir;
+
+       switch (vf->type)
+       {
+       case FILEVIEW_LIST:
+               fd = (VFLIST(vf)->click_fd);
+               break;
+       case FILEVIEW_ICON:
+               fd = (VFICON(vf)->click_fd);
+               break;
+       }
+
+       dest_dir = open_archive(fd);
+       if (dest_dir)
+               {
+               lw_new = layout_new_from_default();
+               layout_set_path(lw_new, dest_dir);
+               g_free(dest_dir);
+               }
+       else
+               {
+               warning_dialog(_("Cannot open archive file"), _("See the Log Window"), GTK_STOCK_DIALOG_WARNING, NULL);
+               }
+}
+
 static void vf_pop_menu_copy_cb(GtkWidget *widget, gpointer data)
 {
        ViewFile *vf = data;
@@ -350,6 +426,15 @@ static void vf_pop_menu_delete_cb(GtkWidget *widget, gpointer data)
 {
        ViewFile *vf = data;
 
+       options->file_ops.safe_delete_enable = FALSE;
+       file_util_delete(NULL, vf_pop_menu_file_list(vf), vf->listview);
+}
+
+static void vf_pop_menu_move_to_trash_cb(GtkWidget *widget, gpointer data)
+{
+       ViewFile *vf = data;
+
+       options->file_ops.safe_delete_enable = TRUE;
        file_util_delete(NULL, vf_pop_menu_file_list(vf), vf->listview);
 }
 
@@ -357,7 +442,14 @@ static void vf_pop_menu_copy_path_cb(GtkWidget *widget, gpointer data)
 {
        ViewFile *vf = data;
 
-       file_util_copy_path_list_to_clipboard(vf_pop_menu_file_list(vf));
+       file_util_copy_path_list_to_clipboard(vf_pop_menu_file_list(vf), TRUE);
+}
+
+static void vf_pop_menu_copy_path_unquoted_cb(GtkWidget *widget, gpointer data)
+{
+       ViewFile *vf = data;
+
+       file_util_copy_path_list_to_clipboard(vf_pop_menu_file_list(vf), FALSE);
 }
 
 static void vf_pop_menu_enable_grouping_cb(GtkWidget *widget, gpointer data)
@@ -395,6 +487,11 @@ static void vf_pop_menu_sort_cb(GtkWidget *widget, gpointer data)
 
        type = (SortType)GPOINTER_TO_INT(data);
 
+       if (type == SORT_EXIFTIME || type == SORT_EXIFTIMEDIGITIZED || type == SORT_RATING)
+               {
+               vf_read_metadata_in_idle(vf);
+               }
+
        if (vf->layout)
                {
                layout_sort_set(vf->layout, type, vf->sort_ascend);
@@ -520,15 +617,18 @@ GtkWidget *vf_pop_menu(ViewFile *vf)
        GtkWidget *item;
        GtkWidget *submenu;
        gboolean active = FALSE;
+       gboolean class_archive = FALSE;
 
        switch (vf->type)
        {
        case FILEVIEW_LIST:
                vflist_color_set(vf, VFLIST(vf)->click_fd, TRUE);
                active = (VFLIST(vf)->click_fd != NULL);
+               class_archive = (VFLIST(vf)->click_fd != NULL && VFLIST(vf)->click_fd->format_class == FORMAT_CLASS_ARCHIVE);
                break;
        case FILEVIEW_ICON:
                active = (VFICON(vf)->click_fd != NULL);
+               class_archive = (VFICON(vf)->click_fd != NULL && VFICON(vf)->click_fd->format_class == FORMAT_CLASS_ARCHIVE);
                break;
        }
 
@@ -591,6 +691,8 @@ GtkWidget *vf_pop_menu(ViewFile *vf)
        menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
                                      G_CALLBACK(vf_pop_menu_view_cb), vf);
 
+       menu_item_add_stock_sensitive(menu, _("Open archive"), GTK_STOCK_OPEN, active & class_archive, G_CALLBACK(vf_pop_menu_open_archive_cb), vf);
+
        menu_item_add_divider(menu);
        menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
                                      G_CALLBACK(vf_pop_menu_copy_cb), vf);
@@ -600,8 +702,17 @@ GtkWidget *vf_pop_menu(ViewFile *vf)
                                G_CALLBACK(vf_pop_menu_rename_cb), vf);
        menu_item_add_sensitive(menu, _("_Copy path"), active,
                                G_CALLBACK(vf_pop_menu_copy_path_cb), vf);
-       menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
-                                     G_CALLBACK(vf_pop_menu_delete_cb), vf);
+       menu_item_add_sensitive(menu, _("_Copy path unquoted"), active,
+                               G_CALLBACK(vf_pop_menu_copy_path_unquoted_cb), vf);
+       menu_item_add_divider(menu);
+       menu_item_add_stock_sensitive(menu,
+                               options->file_ops.confirm_move_to_trash ? _("Move to Trash...") :
+                                       _("Move to Trash"), PIXBUF_INLINE_ICON_TRASH, active,
+                               G_CALLBACK(vf_pop_menu_move_to_trash_cb), vf);
+       menu_item_add_stock_sensitive(menu,
+                               options->file_ops.confirm_delete ? _("_Delete...") :
+                                       _("_Delete"), GTK_STOCK_DELETE, active,
+                               G_CALLBACK(vf_pop_menu_delete_cb), vf);
        menu_item_add_divider(menu);
 
        menu_item_add_sensitive(menu, _("Enable file _grouping"), active,
@@ -646,6 +757,18 @@ GtkWidget *vf_pop_menu(ViewFile *vf)
                break;
        }
 
+       switch (vf->type)
+       {
+       case FILEVIEW_LIST:
+               menu_item_add_check(menu, _("Show star rating"), options->show_star_rating,
+                                   G_CALLBACK(vflist_pop_menu_show_star_rating_cb), vf);
+               break;
+       case FILEVIEW_ICON:
+               menu_item_add_check(menu, _("Show star rating"), options->show_star_rating,
+                                   G_CALLBACK(vficon_pop_menu_show_star_rating_cb), vf);
+               break;
+       }
+
        menu_item_add_stock(menu, _("Re_fresh"), GTK_STOCK_REFRESH, G_CALLBACK(vf_pop_menu_refresh_cb), vf);
 
        return menu;
@@ -653,20 +776,30 @@ GtkWidget *vf_pop_menu(ViewFile *vf)
 
 gboolean vf_refresh(ViewFile *vf)
 {
+       gboolean ret;
+
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_refresh(vf);
-       case FILEVIEW_ICON: return vficon_refresh(vf);
+       case FILEVIEW_LIST: ret = vflist_refresh(vf); break;
+       case FILEVIEW_ICON: ret = vficon_refresh(vf); break;
+       default: ret = FALSE;
        }
+
+       return ret;
 }
 
 gboolean vf_set_fd(ViewFile *vf, FileData *dir_fd)
 {
+       gboolean ret;
+
        switch (vf->type)
        {
-       case FILEVIEW_LIST: return vflist_set_fd(vf, dir_fd);
-       case FILEVIEW_ICON: return vficon_set_fd(vf, dir_fd);
+       case FILEVIEW_LIST: ret = vflist_set_fd(vf, dir_fd); break;
+       case FILEVIEW_ICON: ret = vficon_set_fd(vf, dir_fd); break;
+       default: ret = FALSE;
        }
+
+       return ret;
 }
 
 static void vf_destroy_cb(GtkWidget *widget, gpointer data)
@@ -686,6 +819,10 @@ static void vf_destroy_cb(GtkWidget *widget, gpointer data)
                gtk_widget_destroy(vf->popup);
                }
 
+       if (vf->read_metadata_in_idle_id)
+               {
+               g_idle_remove_by_data(vf);
+               }
        file_data_unref(vf->dir_fd);
        g_free(vf->info);
        g_free(vf);
@@ -697,6 +834,179 @@ static void vf_marks_filter_toggle_cb(GtkWidget *widget, gpointer data)
        vf_refresh_idle(vf);
 }
 
+typedef struct _MarksTextEntry MarksTextEntry;
+struct _MarksTextEntry {
+       GenericDialog *gd;
+       gint mark_no;
+       GtkWidget *edit_widget;
+       gchar *text_entry;
+       GtkWidget *parent;
+};
+
+static void vf_marks_tooltip_cancel_cb(GenericDialog *gd, gpointer data)
+{
+       MarksTextEntry *mte = data;
+
+       g_free(mte->text_entry);
+       generic_dialog_close(gd);
+}
+
+static void vf_marks_tooltip_ok_cb(GenericDialog *gd, gpointer data)
+{
+       MarksTextEntry *mte = data;
+
+       g_free(options->marks_tooltips[mte->mark_no]);
+       options->marks_tooltips[mte->mark_no] = g_strdup(gtk_entry_get_text(GTK_ENTRY(mte->edit_widget)));
+
+       gtk_widget_set_tooltip_text(mte->parent, options->marks_tooltips[mte->mark_no]);
+
+       g_free(mte->text_entry);
+       generic_dialog_close(gd);
+}
+
+void vf_marks_filter_on_icon_press(GtkEntry *entry, GtkEntryIconPosition pos,
+                                                                       GdkEvent *event, gpointer userdata)
+{
+       MarksTextEntry *mte = userdata;
+
+       g_free(mte->text_entry);
+       mte->text_entry = g_strdup("");
+       gtk_entry_set_text(GTK_ENTRY(mte->edit_widget), "");
+}
+
+static void vf_marks_tooltip_help_cb(GenericDialog *gd, gpointer data)
+{
+       help_window_show("GuideImageMarks.html");
+}
+
+static gboolean vf_marks_tooltip_cb(GtkWidget *widget,
+                                                                               GdkEventButton *event,
+                                                                               gpointer user_data)
+{
+       GtkWidget *table;
+       gint i = GPOINTER_TO_INT(user_data);
+       MarksTextEntry *mte;
+
+       if (event->button == MOUSE_BUTTON_RIGHT)
+               {
+               mte = g_new0(MarksTextEntry, 1);
+               mte->mark_no = i;
+               mte->text_entry = g_strdup(options->marks_tooltips[i]);
+               mte->parent = widget;
+
+               mte->gd = generic_dialog_new(_("Mark text"), "mark_text",
+                                               widget, FALSE,
+                                               vf_marks_tooltip_cancel_cb, mte);
+               generic_dialog_add_message(mte->gd, GTK_STOCK_DIALOG_QUESTION, _("Set mark text"),
+                                           _("This will set or clear the mark text."), FALSE);
+               generic_dialog_add_button(mte->gd, GTK_STOCK_OK, NULL,
+                                                       vf_marks_tooltip_ok_cb, TRUE);
+               generic_dialog_add_button(mte->gd, GTK_STOCK_HELP, NULL,
+                                               vf_marks_tooltip_help_cb, FALSE);
+
+               table = pref_table_new(mte->gd->vbox, 3, 1, FALSE, TRUE);
+               pref_table_label(table, 0, 0, g_strdup_printf("%s%d", _("Mark "), mte->mark_no + 1), 1.0);
+               mte->edit_widget = gtk_entry_new();
+               gtk_widget_set_size_request(mte->edit_widget, 300, -1);
+               if (mte->text_entry)
+                       {
+                       gtk_entry_set_text(GTK_ENTRY(mte->edit_widget), mte->text_entry);
+                       }
+               gtk_table_attach_defaults(GTK_TABLE(table), mte->edit_widget, 1, 2, 0, 1);
+               generic_dialog_attach_default(mte->gd, mte->edit_widget);
+
+               gtk_entry_set_icon_from_stock(GTK_ENTRY(mte->edit_widget),
+                                                       GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR);
+               gtk_entry_set_icon_tooltip_text (GTK_ENTRY(mte->edit_widget),
+                                                       GTK_ENTRY_ICON_SECONDARY, "Clear");
+               g_signal_connect(GTK_ENTRY(mte->edit_widget), "icon-press",
+                                                       G_CALLBACK(vf_marks_filter_on_icon_press), mte);
+
+               gtk_widget_show(mte->edit_widget);
+               gtk_widget_grab_focus(mte->edit_widget);
+               gtk_widget_show(GTK_WIDGET(mte->gd->dialog));
+
+               return TRUE;
+               }
+
+       return FALSE;
+}
+
+static void vf_file_filter_save_cb(GtkWidget *widget, gpointer data)
+{
+       ViewFile *vf = data;
+       gchar *entry_text;
+       gchar *remove_text = NULL;
+       gchar *index_text = NULL;
+       gboolean text_found = FALSE;
+       gint i;
+
+       entry_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(vf->file_filter.combo)))));
+
+       if (entry_text[0] == '\0' && vf->file_filter.last_selected >= 0)
+               {
+               gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), vf->file_filter.last_selected);
+               remove_text = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo));
+               history_list_item_remove("file_filter", remove_text);
+               gtk_combo_box_text_remove(GTK_COMBO_BOX_TEXT(vf->file_filter.combo), vf->file_filter.last_selected);
+               g_free(remove_text);
+
+               gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), -1);
+               vf->file_filter.last_selected = - 1;
+               gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(vf->file_filter.combo))), "");
+               vf->file_filter.count--;
+               }
+       else
+               {
+               if (entry_text[0] != '\0')
+                       {
+                       for (i = 0; i < vf->file_filter.count; i++)
+                               {
+                               if (index_text)
+                                       {
+                                       g_free(index_text);
+                                       }
+                               gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), i);
+                               index_text = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo));
+
+                               if (g_strcmp0(index_text, entry_text) == 0)
+                                       {
+                                       text_found = TRUE;
+                                       break;
+                                       }
+                               }
+
+                       g_free(index_text);
+                       if (!text_found)
+                               {
+                               history_list_add_to_key("file_filter", entry_text, 10);
+                               gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo), entry_text);
+                               vf->file_filter.count++;
+                               gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), vf->file_filter.count - 1);
+                               }
+                       }
+               }
+       vf_refresh(vf);
+
+       g_free(entry_text);
+}
+
+static void vf_file_filter_cb(GtkWidget *widget, gpointer data)
+{
+       ViewFile *vf = data;
+
+       vf_refresh(vf);
+}
+
+static gboolean vf_file_filter_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
+{
+       ViewFile *vf = data;
+       vf->file_filter.last_selected = gtk_combo_box_get_active(GTK_COMBO_BOX(vf->file_filter.combo));
+
+       gtk_widget_grab_focus(widget);
+
+       return TRUE;
+}
 
 static GtkWidget *vf_marks_filter_init(ViewFile *vf)
 {
@@ -711,6 +1021,9 @@ static GtkWidget *vf_marks_filter_init(ViewFile *vf)
                gtk_box_pack_start(GTK_BOX(hbox), check, FALSE, FALSE, 0);
                g_signal_connect(G_OBJECT(check), "toggled",
                         G_CALLBACK(vf_marks_filter_toggle_cb), vf);
+               g_signal_connect(G_OBJECT(check), "button_press_event",
+                        G_CALLBACK(vf_marks_tooltip_cb), GINT_TO_POINTER(i));
+               gtk_widget_set_tooltip_text(check, options->marks_tooltips[i]);
 
                gtk_widget_show(check);
                vf->filter_check[i] = check;
@@ -720,6 +1033,183 @@ static GtkWidget *vf_marks_filter_init(ViewFile *vf)
        return frame;
 }
 
+void vf_file_filter_set(ViewFile *vf, gboolean enable)
+{
+       if (enable)
+               {
+               gtk_widget_show(vf->file_filter.combo);
+               gtk_widget_show(vf->file_filter.frame);
+               }
+       else
+               {
+               gtk_widget_hide(vf->file_filter.combo);
+               gtk_widget_hide(vf->file_filter.frame);
+               }
+
+       vf_refresh(vf);
+}
+
+static gboolean vf_file_filter_class_cb(GtkWidget *widget, gpointer data)
+{
+       ViewFile *vf = data;
+       gint i;
+
+       gboolean state = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
+
+       for (i = 0; i < FILE_FORMAT_CLASSES; i++)
+               {
+               if (g_strcmp0(format_class_list[i], gtk_menu_item_get_label(GTK_MENU_ITEM(widget))) == 0)
+                       {
+                       options->class_filter[i] = state;
+                       }
+               }
+       vf_refresh(vf);
+
+       return TRUE;
+}
+
+static gboolean vf_file_filter_class_set_all_cb(GtkWidget *widget, gpointer data)
+{
+       ViewFile *vf = data;
+       GtkWidget *parent;
+       GList *children;
+       GtkWidget *child;
+       gint i;
+       gboolean state;
+
+       if (g_strcmp0(_("Select all"), gtk_menu_item_get_label(GTK_MENU_ITEM(widget))) == 0)
+               {
+               state = TRUE;
+               }
+       else
+               {
+               state = FALSE;
+               }
+
+       for (i = 0; i < FILE_FORMAT_CLASSES; i++)
+               {
+               options->class_filter[i] = state;
+               }
+
+       i = 0;
+       parent = gtk_widget_get_parent(widget);
+       children = gtk_container_get_children(GTK_CONTAINER(parent));
+       while (children)
+               {
+               child = children->data;
+               if (i < FILE_FORMAT_CLASSES)
+                       {
+                       gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(child), state);
+                       }
+               i++;
+               children = children->next;
+               }
+       g_list_free(children);
+       vf_refresh(vf);
+
+       return TRUE;
+}
+
+static GtkWidget *class_filter_menu (ViewFile *vf)
+{
+       GtkWidget *menu;
+       GtkWidget *menu_item;
+       int i;
+
+       menu = gtk_menu_new();
+
+       for (i = 0; i < FILE_FORMAT_CLASSES; i++)
+           {
+               menu_item = gtk_check_menu_item_new_with_label(format_class_list[i]);
+               gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), options->class_filter[i]);
+               g_signal_connect(G_OBJECT(menu_item), "toggled", G_CALLBACK(vf_file_filter_class_cb), vf);
+               gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
+               gtk_widget_show(menu_item);
+               }
+
+       menu_item = gtk_menu_item_new_with_label(_("Select all"));
+       gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
+       gtk_widget_show(menu_item);
+       g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(vf_file_filter_class_set_all_cb), vf);
+
+       menu_item = gtk_menu_item_new_with_label(_("Select none"));
+       gtk_menu_shell_append(GTK_MENU_SHELL (menu), menu_item);
+       gtk_widget_show(menu_item);
+       g_signal_connect(G_OBJECT(menu_item), "activate", G_CALLBACK(vf_file_filter_class_set_all_cb), vf);
+
+       return menu;
+}
+
+static void case_sensitive_cb(GtkWidget *widget, gpointer data)
+{
+       ViewFile *vf = data;
+
+       vf->file_filter.case_sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+       vf_refresh(vf);
+}
+
+static GtkWidget *vf_file_filter_init(ViewFile *vf)
+{
+       GtkWidget *frame = gtk_frame_new(NULL);
+       GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
+       GList *work;
+       gint n = 0;
+       GtkWidget *combo_entry;
+       GtkWidget *menubar;
+       GtkWidget *menuitem;
+       GtkWidget *case_sensitive;
+
+       vf->file_filter.combo = gtk_combo_box_text_new_with_entry();
+       combo_entry = gtk_bin_get_child(GTK_BIN(vf->file_filter.combo));
+       gtk_widget_show(gtk_bin_get_child(GTK_BIN(vf->file_filter.combo)));
+       gtk_widget_show((GTK_WIDGET(vf->file_filter.combo)));
+       gtk_widget_set_tooltip_text(GTK_WIDGET(vf->file_filter.combo), "Use regular expressions");
+
+       work = history_list_get_by_key("file_filter");
+       while (work)
+               {
+               gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo), (gchar *)work->data);
+               work = work->next;
+               n++;
+               vf->file_filter.count = n;
+               }
+       gtk_combo_box_set_active(GTK_COMBO_BOX(vf->file_filter.combo), 0);
+
+       g_signal_connect(G_OBJECT(combo_entry), "activate",
+               G_CALLBACK(vf_file_filter_save_cb), vf);
+               
+       g_signal_connect(G_OBJECT(vf->file_filter.combo), "changed",
+               G_CALLBACK(vf_file_filter_cb), vf);
+
+       g_signal_connect(G_OBJECT(combo_entry), "button_press_event",
+                        G_CALLBACK(vf_file_filter_press_cb), vf);
+
+       gtk_box_pack_start(GTK_BOX(hbox), vf->file_filter.combo, FALSE, FALSE, 0);
+       gtk_widget_show(vf->file_filter.combo);
+       gtk_container_add(GTK_CONTAINER(frame), hbox);
+       gtk_widget_show(hbox);
+
+       case_sensitive = gtk_check_button_new_with_label("Case");
+       gtk_box_pack_start(GTK_BOX(hbox), case_sensitive, FALSE, FALSE, 0);
+       gtk_widget_set_tooltip_text(GTK_WIDGET(case_sensitive), _("Case sensitive"));
+       g_signal_connect(G_OBJECT(case_sensitive), "clicked", G_CALLBACK(case_sensitive_cb), vf);
+       gtk_widget_show(case_sensitive);
+
+       menubar = gtk_menu_bar_new();
+       gtk_box_pack_start(GTK_BOX(hbox), menubar, FALSE, TRUE, 0);
+       gtk_widget_show(menubar);
+
+       menuitem = gtk_menu_item_new_with_label(_("Class"));
+       gtk_widget_set_tooltip_text(GTK_WIDGET(menuitem), _("Select Class filter"));
+       gtk_menu_item_set_submenu(GTK_MENU_ITEM (menuitem), class_filter_menu(vf));
+       gtk_menu_shell_append(GTK_MENU_SHELL (menubar), menuitem);
+       gtk_widget_show(menuitem);
+
+       gtk_widget_show(menuitem);
+
+       return frame;
+}
+
 void vf_mark_filter_toggle(ViewFile *vf, gint mark)
 {
        gint n = mark - 1;
@@ -736,6 +1226,7 @@ ViewFile *vf_new(FileViewType type, FileData *dir_fd)
        vf->type = type;
        vf->sort_method = SORT_NAME;
        vf->sort_ascend = TRUE;
+       vf->read_metadata_in_idle_id = 0;
 
        vf->scrolled = gtk_scrolled_window_new(NULL, NULL);
        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vf->scrolled), GTK_SHADOW_IN);
@@ -743,9 +1234,11 @@ ViewFile *vf_new(FileViewType type, FileData *dir_fd)
                                       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 
        vf->filter = vf_marks_filter_init(vf);
+       vf->file_filter.frame = vf_file_filter_init(vf);
 
        vf->widget = gtk_vbox_new(FALSE, 0);
        gtk_box_pack_start(GTK_BOX(vf->widget), vf->filter, FALSE, FALSE, 0);
+       gtk_box_pack_start(GTK_BOX(vf->widget), vf->file_filter.frame, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(vf->widget), vf->scrolled, TRUE, TRUE, 0);
        gtk_widget_show(vf->scrolled);
 
@@ -814,6 +1307,20 @@ static gdouble vf_thumb_progress(ViewFile *vf)
        return (gdouble)done / count;
 }
 
+static gdouble vf_read_metadata_in_idle_progress(ViewFile *vf)
+{
+       gint count = 0;
+       gint done = 0;
+
+       switch (vf->type)
+               {
+               case FILEVIEW_LIST: vflist_read_metadata_progress_count(vf->list, &count, &done); break;
+               case FILEVIEW_ICON: vficon_read_metadata_progress_count(vf->list, &count, &done); break;
+               }
+
+       return (gdouble)done / count;
+}
+
 static void vf_set_thumb_fd(ViewFile *vf, FileData *fd)
 {
        switch (vf->type)
@@ -957,6 +1464,97 @@ void vf_thumb_update(ViewFile *vf)
        while (vf_thumb_next(vf));
 }
 
+void vf_star_cleanup(ViewFile *vf)
+{
+       if (vf->stars_id != 0)
+               {
+               g_source_remove(vf->stars_id);
+               }
+
+       vf->stars_id = 0;
+       vf->stars_filedata = NULL;
+}
+
+void vf_star_stop(ViewFile *vf)
+{
+        vf_star_cleanup(vf);
+}
+
+static void vf_set_star_fd(ViewFile *vf, FileData *fd)
+{
+       switch (vf->type)
+               {
+               case FILEVIEW_LIST: vflist_set_star_fd(vf, fd); break;
+               case FILEVIEW_ICON: vficon_set_star_fd(vf, fd); break;
+               default: break;
+               }
+}
+
+static void vf_star_do(ViewFile *vf, FileData *fd)
+{
+       if (!fd) return;
+
+       vf_set_star_fd(vf, fd);
+}
+
+static gboolean vf_star_next(ViewFile *vf)
+{
+       FileData *fd = NULL;
+
+       switch (vf->type)
+               {
+               case FILEVIEW_LIST: fd = vflist_star_next_fd(vf); break;
+               case FILEVIEW_ICON: fd = vficon_star_next_fd(vf); break;
+               default: break;
+               }
+
+       if (!fd)
+               {
+               /* done */
+               vf_star_cleanup(vf);
+               return FALSE;
+               }
+
+       return TRUE;
+}
+
+gboolean vf_stars_cb(gpointer data)
+{
+       ViewFile *vf = data;
+       FileData *fd = vf->stars_filedata;
+
+       if (fd)
+               {
+               read_rating_data(fd);
+
+               vf_star_do(vf, fd);
+
+               if (vf_star_next(vf))
+                       {
+                       return TRUE;
+                       }
+               else
+                       {
+                       vf->stars_filedata = NULL;
+                       vf->stars_id = 0;
+                       return FALSE;
+                       }
+               }
+
+       return FALSE;
+}
+
+void vf_star_update(ViewFile *vf)
+{
+       vf_star_stop(vf);
+
+       if (!options->show_star_rating)
+               {
+               return;
+               }
+
+       vf_star_next(vf);
+}
 
 void vf_marks_set(ViewFile *vf, gboolean enable)
 {
@@ -977,6 +1575,19 @@ void vf_marks_set(ViewFile *vf, gboolean enable)
        vf_refresh_idle(vf);
 }
 
+void vf_star_rating_set(ViewFile *vf, gboolean enable)
+{
+       if (options->show_star_rating == enable) return;
+       options->show_star_rating = enable;
+
+       switch (vf->type)
+               {
+               case FILEVIEW_LIST: vflist_star_rating_set(vf, enable); break;
+               case FILEVIEW_ICON: vficon_star_rating_set(vf, enable); break;
+               }
+       vf_refresh_idle(vf);
+}
+
 guint vf_marks_get_filter(ViewFile *vf)
 {
        guint ret = 0;
@@ -993,6 +1604,60 @@ guint vf_marks_get_filter(ViewFile *vf)
        return ret;
 }
 
+GRegex *vf_file_filter_get_filter(ViewFile *vf)
+{
+       GRegex *ret = NULL;
+       GError *error = NULL;
+       gchar *file_filter_text = NULL;
+
+       if (!gtk_widget_get_visible(vf->file_filter.combo))
+               {
+               return g_regex_new("", 0, 0, NULL);
+               }
+
+       file_filter_text = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(vf->file_filter.combo));
+
+       if (file_filter_text[0] != '\0')
+               {
+               ret = g_regex_new(file_filter_text, vf->file_filter.case_sensitive ? 0 : G_REGEX_CASELESS, 0, &error);
+               if (error)
+                       {
+                       log_printf("Error: could not compile regular expression %s\n%s\n", file_filter_text, error->message);
+                       g_error_free(error);
+                       error = NULL;
+                       ret = g_regex_new("", 0, 0, NULL);
+                       }
+               g_free(file_filter_text);
+               }
+       else
+               {
+               ret = g_regex_new("", 0, 0, NULL);
+               }
+
+       return ret;
+}
+
+guint vf_class_get_filter(ViewFile *vf)
+{
+       guint ret = 0;
+       gint i;
+
+       if (!gtk_widget_get_visible(vf->file_filter.combo))
+               {
+               return G_MAXUINT;
+               }
+
+       for ( i = 0; i < FILE_FORMAT_CLASSES; i++)
+               {
+               if (options->class_filter[i])
+                       {
+                       ret |= 1 << i;
+                       }
+               }
+
+       return ret;
+}
+
 void vf_set_layout(ViewFile *vf, LayoutWindow *layout)
 {
        vf->layout = layout;
@@ -1048,7 +1713,7 @@ void vf_notify_cb(FileData *fd, NotifyType type, gpointer data)
 
        NotifyType interested = NOTIFY_CHANGE | NOTIFY_REREAD | NOTIFY_GROUPING;
        if (vf->marks_enabled) interested |= NOTIFY_MARKS | NOTIFY_METADATA;
-       /* FIXME: NOTIFY_METADATA should be checked by the keyword-to-mark functions and converted to NOTIFY_MARKS only if there was a change */
+       /** @FIXME NOTIFY_METADATA should be checked by the keyword-to-mark functions and converted to NOTIFY_MARKS only if there was a change */
 
        if (!(type & interested) || vf->refresh_idle_id || !vf->dir_fd) return;
 
@@ -1085,4 +1750,70 @@ void vf_notify_cb(FileData *fd, NotifyType type, gpointer data)
                }
 }
 
+static gboolean vf_read_metadata_in_idle_cb(gpointer data)
+{
+       FileData *fd;
+       ViewFile *vf = data;
+       GList *work;
+
+       vf_thumb_status(vf, vf_read_metadata_in_idle_progress(vf), _("Loading meta..."));
+
+       work = vf->list;
+
+       while (work)
+               {
+               fd = work->data;
+
+               if (fd && !fd->metadata_in_idle_loaded)
+                       {
+                       if (!fd->exifdate)
+                               {
+                               read_exif_time_data(fd);
+                               }
+                       if (!fd->exifdate_digitized)
+                               {
+                               read_exif_time_digitized_data(fd);
+                               }
+                       if (fd->rating == STAR_RATING_NOT_READ)
+                               {
+                               read_rating_data(fd);
+                               }
+                       fd->metadata_in_idle_loaded = TRUE;
+                       return TRUE;
+                       }
+               work = work->next;
+               }
+
+       vf_thumb_status(vf, 0.0, NULL);
+       vf->read_metadata_in_idle_id = 0;
+       vf_refresh(vf);
+       return FALSE;
+}
+
+static void vf_read_metadata_in_idle_finished_cb(gpointer data)
+{
+       ViewFile *vf = data;
+
+       vf_thumb_status(vf, 0.0, "Loading meta...");
+       vf->read_metadata_in_idle_id = 0;
+}
+
+void vf_read_metadata_in_idle(ViewFile *vf)
+{
+       if (!vf) return;
+
+       if (vf->read_metadata_in_idle_id)
+               {
+               g_idle_remove_by_data(vf);
+               }
+       vf->read_metadata_in_idle_id = 0;
+
+       if (vf->list)
+               {
+               vf->read_metadata_in_idle_id = g_idle_add_full(G_PRIORITY_LOW, vf_read_metadata_in_idle_cb, vf, vf_read_metadata_in_idle_finished_cb);
+               }
+
+       return;
+}
+
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */