Mercurial > geeqie.yaz
diff src/utilops.c @ 112:b15d4c18168f
Fri Nov 17 19:06:19 2006 John Ellis <johne@verizon.net>
* ui_fileops.[ch]: Add path_list_lstat() to obtain a path listing that
does not follow symbolic links.
* utilops.[ch]: Add file_util_delete_dir(), and support functions.
* view_dir_list.c: Add delete to folder popup menu.
* view_dir_tree.c: Add delete to folder popup menu, and set rename and
delete sensitive only when parent folder is writable.
author | gqview |
---|---|
date | Sat, 18 Nov 2006 00:12:22 +0000 |
parents | a10fc0308c12 |
children | 53b2bfdcff69 |
line wrap: on
line diff
--- a/src/utilops.c Wed Nov 15 07:19:16 2006 +0000 +++ b/src/utilops.c Sat Nov 18 00:12:22 2006 +0000 @@ -14,6 +14,7 @@ #include "utilops.h" +#include "cache.h" #include "cache_maint.h" #include "collect.h" #include "dupe.h" @@ -22,6 +23,7 @@ #include "img-view.h" #include "layout.h" #include "search.h" +#include "thumb_standard.h" #include "ui_bookmark.h" #include "ui_fileops.h" #include "ui_misc.h" @@ -139,7 +141,8 @@ #define DIALOG_DEF_IMAGE_DIM_X 200 #define DIALOG_DEF_IMAGE_DIM_Y 150 -static void generic_dialog_add_image(GenericDialog *gd, const gchar *path1, const gchar *header1, +static void generic_dialog_add_image(GenericDialog *gd, GtkWidget *box, + const gchar *path1, const gchar *header1, const gchar *path2, const gchar *header2, gint show_filename) { @@ -148,9 +151,11 @@ GtkWidget *vbox; GtkWidget *label = NULL; + if (!box) box = gd->vbox; + if (path2) { - hbox = pref_box_new(gd->vbox, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE); + hbox = pref_box_new(box, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE); } /* image 1 */ @@ -168,7 +173,7 @@ } else { - gtk_box_pack_start(GTK_BOX(gd->vbox), vbox, TRUE, TRUE, PREF_PAD_GAP); + gtk_box_pack_start(GTK_BOX(box), vbox, TRUE, TRUE, PREF_PAD_GAP); } gtk_widget_show(vbox); @@ -182,6 +187,7 @@ } imd = image_new(FALSE); + g_object_set(G_OBJECT(imd->pr), "zoom_expand", FALSE, NULL); gtk_widget_set_size_request(imd->widget, DIALOG_DEF_IMAGE_DIM_X, DIALOG_DEF_IMAGE_DIM_Y); gtk_box_pack_start(GTK_BOX(vbox), imd->widget, TRUE, TRUE, 0); image_change_path(imd, path1, 0.0); @@ -213,6 +219,7 @@ } imd = image_new(FALSE); + g_object_set(G_OBJECT(imd->pr), "zoom_expand", FALSE, NULL); gtk_widget_set_size_request(imd->widget, DIALOG_DEF_IMAGE_DIM_X, DIALOG_DEF_IMAGE_DIM_Y); gtk_box_pack_start(GTK_BOX(vbox), imd->widget, TRUE, TRUE, 0); image_change_path(imd, path2, 0.0); @@ -509,7 +516,7 @@ file_util_move_multiple_all_cb, FALSE); generic_dialog_add_button(gd, GTK_STOCK_GOTO_LAST, _("S_kip all"), file_util_move_multiple_skip_all_cb, FALSE); generic_dialog_add_button(gd, GTK_STOCK_GO_FORWARD, _("_Skip"), file_util_move_multiple_skip_cb, FALSE); - generic_dialog_add_image(gd, fdm->dest, _("Existing file"), fdm->source, _("New file"), TRUE); + generic_dialog_add_image(gd, NULL, fdm->dest, _("Existing file"), fdm->source, _("New file"), TRUE); /* rename option */ @@ -826,7 +833,7 @@ pref_spacer(gd->vbox, 0); generic_dialog_add_button(gd, GTK_STOCK_OK, _("_Overwrite"), file_util_move_single_ok_cb, TRUE); - generic_dialog_add_image(gd, fds->dest, _("Existing file"), fds->source, _("New file"), TRUE); + generic_dialog_add_image(gd, NULL, fds->dest, _("Existing file"), fds->source, _("New file"), TRUE); /* rename option */ @@ -1478,7 +1485,7 @@ generic_dialog_add_message(gd, NULL, _("Delete multiple files"), NULL); - generic_dialog_add_image(gd, NULL, NULL, NULL, NULL, TRUE); + generic_dialog_add_image(gd, NULL, NULL, NULL, NULL, NULL, TRUE); imd = g_object_get_data(G_OBJECT(gd->dialog), "img_image"); image_set_button_func(imd, file_util_delete_multiple_review_button_cb, gd); image_set_scroll_func(imd, file_util_delete_multiple_review_scroll_cb, gd); @@ -1567,7 +1574,7 @@ pref_table_label(table, 1, 1, base, 0.0); g_free(base); - generic_dialog_add_image(gd, path, NULL, NULL, NULL, FALSE); + generic_dialog_add_image(gd, NULL, path, NULL, NULL, NULL, FALSE); box_append_safe_delete_status(gd); @@ -1716,7 +1723,9 @@ pref_spacer(gd->vbox, 0); generic_dialog_add_button(gd, GTK_STOCK_OK, _("_Overwrite"), file_util_rename_multiple_ok_cb, TRUE); - generic_dialog_add_image(gd, fd->dest_path, _("Existing file"), fd->source_path, _("New file"), TRUE); + generic_dialog_add_image(gd, NULL, + fd->dest_path, _("Existing file"), + fd->source_path, _("New file"), TRUE); gtk_widget_hide(GENERIC_DIALOG(fd)->dialog); @@ -2301,6 +2310,7 @@ path_list_free(source_list); rd->imd = image_new(TRUE); + g_object_set(G_OBJECT(rd->imd->pr), "zoom_expand", FALSE, NULL); gtk_widget_set_size_request(rd->imd->widget, DIALOG_DEF_IMAGE_DIM_X, DIALOG_DEF_IMAGE_DIM_Y); gtk_paned_pack2(GTK_PANED(pane), rd->imd->widget, FALSE, TRUE); gtk_widget_show(rd->imd->widget); @@ -2435,7 +2445,9 @@ pref_spacer(gd->vbox, 0); generic_dialog_add_button(gd, GTK_STOCK_OK, _("_Overwrite"), file_util_rename_single_ok_cb, TRUE); - generic_dialog_add_image(gd, fds->dest, _("Existing file"), fds->source, _("New file"), TRUE); + generic_dialog_add_image(gd, NULL, + fds->dest, _("Existing file"), + fds->source, _("New file"), TRUE); gtk_widget_show(gd->dialog); @@ -2492,7 +2504,7 @@ file_util_rename_single_close_cb, NULL); generic_dialog_add_message(GENERIC_DIALOG(fd), NULL, _("Rename file"), NULL); - generic_dialog_add_image(GENERIC_DIALOG(fd), source_path, NULL, NULL, NULL, FALSE); + generic_dialog_add_image(GENERIC_DIALOG(fd), NULL, source_path, NULL, NULL, NULL, FALSE); file_dialog_add_button(fd, GTK_STOCK_OK, _("_Rename"), file_util_rename_single_cb, TRUE); @@ -2670,3 +2682,449 @@ return TRUE; } +/* + *-------------------------------------------------------------------------- + * Delete directory routines + *-------------------------------------------------------------------------- + */ + +/* The plan is to eventually make all of utilops.c + * use UtilityData and UtilityType. + * And clean up the above mess someday. + */ + +typedef enum { + UTILITY_TYPE_NONE = 0, + UTILITY_TYPE_COPY, + UTILITY_TYPE_MOVE, + UTILITY_TYPE_RENAME, + UTILITY_TYPE_DELETE, + UTILITY_TYPE_DELETE_LINK, + UTILITY_TYPE_DELETE_FOLDER +} UtilityType; + +typedef struct _UtilityData UtilityData; +struct _UtilityData { + UtilityType type; + gchar *source_path; + GList *dlist; + GList *flist; + + GenericDialog *gd; +}; + +#define UTILITY_LIST_MIN_WIDTH 250 +#define UTILITY_LIST_MIN_HEIGHT 150 + +/* thumbnail spec has a max depth of 4 (.thumb??/fail/appname/??.png) */ +#define UTILITY_DELETE_MAX_DEPTH 5 + + +static void file_util_data_free(UtilityData *ud) +{ + if (!ud) return; + + g_free(ud->source_path); + path_list_free(ud->dlist); + path_list_free(ud->flist); + + if (ud->gd) generic_dialog_close(ud->gd); + + g_free(ud); +} + +static GtkTreeViewColumn *file_util_dialog_add_list_column(GtkWidget *view, const gchar *text, gint n) +{ + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, text); + gtk_tree_view_column_set_min_width(column, 4); + gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_GROW_ONLY); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(column, renderer, TRUE); + gtk_tree_view_column_add_attribute(column, renderer, "text", n); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + + return column; +} + +static GtkWidget *file_util_dialog_add_list(GtkWidget *box, GList *list, gint full_paths) +{ + GtkWidget *scrolled; + GtkWidget *view; + GtkListStore *store; + + scrolled = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_box_pack_start(GTK_BOX(box), scrolled, TRUE, TRUE, 0); + gtk_widget_show(scrolled); + + store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); + view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); + g_object_unref(store); + + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), TRUE); + gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), FALSE); + + if (full_paths) + { + file_util_dialog_add_list_column(view, _("Location"), 0); + } + else + { + file_util_dialog_add_list_column(view, _("Name"), 1); + } + + gtk_widget_set_size_request(view, UTILITY_LIST_MIN_WIDTH, UTILITY_LIST_MIN_HEIGHT); + gtk_container_add(GTK_CONTAINER(scrolled), view); + gtk_widget_show(view); + + while (list) + { + gchar *path = list->data; + GtkTreeIter iter; + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, path, + 1, filename_from_path(path), -1); + + list = list->next; + } + + return view; +} + +static gboolean file_util_delete_dir_preview_cb(GtkTreeSelection *selection, GtkTreeModel *store, + GtkTreePath *tpath, gboolean path_currently_selected, + gpointer data) +{ + UtilityData *ud = data; + GtkTreeIter iter; + gchar *path = NULL; + + if (path_currently_selected || + !gtk_tree_model_get_iter(store, &iter, tpath)) return TRUE; + + gtk_tree_model_get(store, &iter, 0, &path, -1); + generic_dialog_image_set(ud->gd, path); + g_free(path); + + return TRUE; +} + +static void file_util_delete_dir_cancel_cb(GenericDialog *gd, gpointer data) +{ + UtilityData *ud = data; + + ud->gd = NULL; + file_util_data_free(ud); +} + +static gchar *file_util_delete_dir_empty_path(const gchar *path, gint real_content, gint level) +{ + GList *dlist = NULL; + GList *flist = NULL; + GList *work; + gchar *fail = NULL; + + if (debug) printf("deltree into: %s\n", path); + + level++; + if (level > UTILITY_DELETE_MAX_DEPTH) + { + printf("folder recursion depth past %d, giving up\n", UTILITY_DELETE_MAX_DEPTH); + return g_strdup(path); + } + + if (!path_list_lstat(path, &flist, &dlist)) return g_strdup(path); + + work = dlist; + while (work && !fail) + { + gchar *name; + + name = work->data; + work = work->next; + + fail = file_util_delete_dir_empty_path(name, real_content, level); + } + + work = flist; + while (work && !fail) + { + gchar *name; + + name = work->data; + work = work->next; + + if (debug) printf("deltree child: %s\n", name); + + if (real_content && !islink(name)) + { + if (!file_util_unlink(name)) fail = g_strdup(name); + } + else + { + if (!unlink_file(name)) fail = g_strdup(name); + } + } + + path_list_free(dlist); + path_list_free(flist); + + if (!fail && !rmdir_utf8(path)) + { + fail = g_strdup(path); + } + + if (debug) printf("deltree done: %s\n", path); + + return fail; +} + +static void file_util_delete_dir_ok_cb(GenericDialog *gd, gpointer data) +{ + UtilityData *ud = data; + + ud->gd = NULL; + + if (ud->type == UTILITY_TYPE_DELETE_LINK) + { + if (!unlink_file(ud->source_path)) + { + gchar *text; + + text = g_strdup_printf("Unable to remove symbolic link:\n %s", ud->source_path); + file_util_warning_dialog(_("Delete failed"), text, + GTK_STOCK_DIALOG_ERROR, NULL); + g_free(text); + } + } + else + { + gchar *fail = NULL; + GList *work; + + work = ud->dlist; + while (work && !fail) + { + gchar *path; + + path = work->data; + work = work->next; + + fail = file_util_delete_dir_empty_path(path, FALSE, 0); + } + + work = ud->flist; + while (work && !fail) + { + gchar *path; + + path = work->data; + work = work->next; + + if (debug) printf("deltree unlink: %s\n", path); + + if (islink(path)) + { + if (!unlink_file(path)) fail = g_strdup(path); + } + else + { + if (!file_util_unlink(path)) fail = g_strdup(path); + } + } + + if (!fail) + { + if (!rmdir_utf8(ud->source_path)) fail = g_strdup(ud->source_path); + } + + if (fail) + { + gchar *text; + + text = g_strdup_printf(_("Unable to delete folder:\n\n%s"), ud->source_path); + gd = file_util_warning_dialog(_("Delete failed"), text, GTK_STOCK_DIALOG_ERROR, NULL); + g_free(text); + + if (strcmp(fail, ud->source_path) != 0) + { + pref_spacer(gd->vbox, PREF_PAD_GROUP); + text = g_strdup_printf(_("Removal of folder contents failed at this file:\n\n%s"), + fail); + pref_label_new(gd->vbox, text); + g_free(text); + } + + g_free(fail); + } + } + + file_util_data_free(ud); +} + +static GList *file_util_delete_dir_remaining_folders(GList *dlist) +{ + GList *rlist = NULL; + + while (dlist) + { + gchar *path; + const gchar *name; + + path = dlist->data; + dlist = dlist->next; + + name = filename_from_path(path); + if (!name || + (strcmp(name, THUMB_FOLDER_GLOBAL) != 0 && + strcmp(name, THUMB_FOLDER_LOCAL) != 0 && + strcmp(name, GQVIEW_CACHE_LOCAL_METADATA) != 0) ) + { + rlist = g_list_prepend(rlist, path); + } + } + + return g_list_reverse(rlist); +} + +void file_util_delete_dir(const gchar *path, GtkWidget *parent) +{ + GList *dlist = NULL; + GList *flist = NULL; + GList *rlist; + + if (!isdir(path)) return; + + if (islink(path)) + { + UtilityData *ud; + gchar *text; + + ud = g_new0(UtilityData, 1); + ud->type = UTILITY_TYPE_DELETE_LINK; + ud->source_path = g_strdup(path); + ud->dlist = NULL; + ud->flist = NULL; + + ud->gd = file_util_gen_dlg(_("Delete folder"), "GQview", "dlg_confirm", + parent, TRUE, + file_util_delete_dir_cancel_cb, ud); + + text = g_strdup_printf(_("This will delete the symbolic link:\n\n%s\n\n" + "The folder this link points to will not be deleted."), + path); + generic_dialog_add_message(ud->gd, GTK_STOCK_DIALOG_QUESTION, + _("Delete symbolic link to folder?"), + text); + g_free(text); + + generic_dialog_add_button(ud->gd, GTK_STOCK_DELETE, NULL, file_util_delete_dir_ok_cb, TRUE); + + gtk_widget_show(ud->gd->dialog); + + return; + } + + if (!access_file(path, W_OK | X_OK)) + { + gchar *text; + + text = g_strdup_printf(_("Unable to remove folder %s\n" + "Permissions do not allow writing to the folder."), path); + file_util_warning_dialog(_("Delete failed"), text, GTK_STOCK_DIALOG_ERROR, parent); + g_free(text); + + return; + } + + if (!path_list_lstat(path, &flist, &dlist)) + { + gchar *text; + + text = g_strdup_printf(_("Unable to list contents of folder %s"), path); + file_util_warning_dialog(_("Delete failed"), text, GTK_STOCK_DIALOG_ERROR, parent); + g_free(text); + + return; + } + + rlist = file_util_delete_dir_remaining_folders(dlist); + if (rlist) + { + GenericDialog *gd; + GtkWidget *box; + gchar *text; + + gd = file_util_gen_dlg(_("Folder contains subfolders"), "GQview", "dlg_warning", + parent, TRUE, NULL, NULL); + generic_dialog_add_button(gd, GTK_STOCK_CLOSE, NULL, NULL, TRUE); + + text = g_strdup_printf(_("Unable to delete the folder:\n\n%s\n\n" + "This folder contains subfolders which must be moved before it can be deleted."), + path); + box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_WARNING, + _("Folder contains subfolders"), + text); + g_free(text); + + box = pref_group_new(box, TRUE, _("Subfolders:"), GTK_ORIENTATION_VERTICAL); + + rlist = path_list_sort(rlist); + file_util_dialog_add_list(box, rlist, FALSE); + + gtk_widget_show(gd->dialog); + } + else + { + UtilityData *ud; + GtkWidget *box; + GtkWidget *view; + GtkTreeSelection *selection; + gchar *text; + + ud = g_new0(UtilityData, 1); + ud->type = UTILITY_TYPE_DELETE_FOLDER; + ud->source_path = g_strdup(path); + ud->dlist = dlist; + dlist = NULL; + ud->flist = path_list_sort(flist); + flist = NULL; + + ud->gd = file_util_gen_dlg(_("Delete folder"), "GQview", "dlg_confirm", + parent, TRUE, file_util_delete_dir_cancel_cb, ud); + generic_dialog_add_button(ud->gd, GTK_STOCK_DELETE, NULL, file_util_delete_dir_ok_cb, TRUE); + + text = g_strdup_printf(_("This will delete the folder:\n\n%s\n\n" + "The contents of this folder will also be deleted."), + path); + box = generic_dialog_add_message(ud->gd, GTK_STOCK_DIALOG_QUESTION, + _("Delete folder?"), + text); + g_free(text); + + box = pref_group_new(box, TRUE, _("Contents:"), GTK_ORIENTATION_HORIZONTAL); + + view = file_util_dialog_add_list(box, ud->flist, FALSE); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE); + gtk_tree_selection_set_select_function(selection, file_util_delete_dir_preview_cb, ud, NULL); + + generic_dialog_add_image(ud->gd, box, NULL, NULL, NULL, NULL, FALSE); + + box_append_safe_delete_status(ud->gd); + + gtk_widget_show(ud->gd->dialog); + } + + g_list_free(rlist); + path_list_free(dlist); + path_list_free(flist); +} +