/* vi:set et ai sw=2 sts=2 ts=2: */ /*- * Copyright (c) 2005-2011 Benedikt Meurer * Copyright (c) 2009-2011 Jannis Pohlmann * * 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., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_MEMORY_H #include #endif #ifdef HAVE_STDARG_H #include #endif #ifdef HAVE_STRING_H #include #endif #include "thunar/thunar-dialogs.h" #include "thunar/thunar-gtk-extensions.h" #include "thunar/thunar-icon-factory.h" #include "thunar/thunar-io-jobs.h" #include "thunar/thunar-job.h" #include "thunar/thunar-pango-extensions.h" #include "thunar/thunar-preferences.h" #include "thunar/thunar-private.h" #include "thunar/thunar-util.h" #include static void thunar_dialogs_select_filename (GtkWidget *entry); static void thunar_dialogs_shrink_height (GtkWidget *dialog); static void thunar_dialogs_shrink_height (GtkWidget *dialog) { gint width; gint height; /* Shrinks the dialog to it's minimum height */ gtk_window_get_size (GTK_WINDOW (dialog), &width, &height); gtk_window_resize (GTK_WINDOW (dialog), width, 1); } /** * thunar_dialogs_show_create: * @parent : a #GdkScreen, a #GtkWidget or %NULL. * @content_type : the content type of the file or folder to create. * @filename : the suggested filename or %NULL. * @title : the dialog title. * * Displays a Thunar create dialog with the specified * parameters that asks the user to enter a name for a new file or * folder. * * The caller is responsible to free the returned filename using * g_free() when no longer needed. * * Return value: the filename entered by the user or %NULL if the user * cancelled the dialog. **/ gchar * thunar_dialogs_show_create (gpointer parent, const gchar *content_type, const gchar *filename, const gchar *title) { GtkWidget *dialog; GtkWindow *window; GdkScreen *screen; GError *error = NULL; gchar *name = NULL; GtkWidget *label; GtkWidget *grid; GtkWidget *image; XfceFilenameInput *filename_input; GIcon *icon = NULL; gint row = 0; _thunar_return_val_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent), NULL); /* parse the parent window and screen */ screen = thunar_util_parse_parent (parent, &window); /* create a new dialog window */ dialog = gtk_dialog_new_with_buttons (title, window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, _("_Cancel"), GTK_RESPONSE_CANCEL, _("C_reate"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE); gtk_window_set_default_size (GTK_WINDOW (dialog), 300, -1); grid = gtk_grid_new (); gtk_grid_set_column_spacing (GTK_GRID (grid), 6); gtk_grid_set_row_spacing (GTK_GRID (grid), 3); gtk_container_set_border_width (GTK_CONTAINER (grid), 6); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), grid, TRUE, TRUE, 0); gtk_widget_show (grid); /* try to load the icon */ if (G_LIKELY (content_type != NULL)) icon = g_content_type_get_icon (content_type); /* setup the image */ if (G_LIKELY (icon != NULL)) { image = g_object_new (GTK_TYPE_IMAGE, "xpad", 6, "ypad", 6, NULL); gtk_image_set_from_gicon (GTK_IMAGE (image), icon, GTK_ICON_SIZE_DIALOG); gtk_grid_attach (GTK_GRID (grid), image, 0, row, 1, 2); gtk_widget_set_valign (GTK_WIDGET (image), GTK_ALIGN_START); g_object_unref (icon); gtk_widget_show (image); } label = g_object_new (GTK_TYPE_LABEL, "label", _("Enter the name:"), "xalign", 0.0f, "hexpand", TRUE, NULL); gtk_grid_attach (GTK_GRID (grid), label, 1, row, 1, 1); gtk_widget_show (label); /* set up the widget for entering the filename */ filename_input = g_object_new (XFCE_TYPE_FILENAME_INPUT, "original-filename", filename, NULL); gtk_widget_set_hexpand (GTK_WIDGET (filename_input), TRUE); gtk_widget_set_valign (GTK_WIDGET (filename_input), GTK_ALIGN_CENTER); /* connect to signals so that the sensitivity of the Create button is updated according to whether there * is a valid file name entered */ g_signal_connect_swapped (filename_input, "text-invalid", G_CALLBACK (xfce_filename_input_desensitise_widget), gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK)); g_signal_connect_swapped (filename_input, "text-valid", G_CALLBACK (xfce_filename_input_sensitise_widget), gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK)); /* next row */ row++; gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (filename_input), 1, row, 1, 1); thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), GTK_WIDGET (xfce_filename_input_get_entry (filename_input))); gtk_widget_show_all (GTK_WIDGET (filename_input)); /* ensure that the sensitivity of the Create button is set correctly */ xfce_filename_input_check (filename_input); /* select the filename without the extension */ thunar_dialogs_select_filename (GTK_WIDGET (xfce_filename_input_get_entry (filename_input))); if (screen != NULL) gtk_window_set_screen (GTK_WINDOW (dialog), screen); if (window != NULL) gtk_window_set_transient_for (GTK_WINDOW (dialog), window); /* shrink the dialog again, after some filename error was fixed */ g_signal_connect_swapped (filename_input, "text-valid", G_CALLBACK (thunar_dialogs_shrink_height), dialog); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) { /* determine the chosen filename */ filename = xfce_filename_input_get_text (filename_input); /* convert the UTF-8 filename to the local file system encoding */ name = g_filename_from_utf8 (filename, -1, NULL, NULL, &error); if (G_UNLIKELY (name == NULL)) { /* display an error message */ thunar_dialogs_show_error (dialog, error, _("Cannot convert filename \"%s\" to the local encoding"), filename); /* release the error */ g_error_free (error); } } /* destroy the dialog */ gtk_widget_destroy (dialog); return name; } /** * thunar_dialogs_show_rename_file: * @parent : a #GtkWidget on which the error dialog should be shown, or a #GdkScreen * if no #GtkWidget is known. May also be %NULL, in which case the default * #GdkScreen will be used. * @file : the #ThunarFile we're going to rename. * * Displays the Thunar rename dialog for a single file rename. * * Return value: The #ThunarJob responsible for renaming the file or * %NULL if there was no renaming required. **/ ThunarJob * thunar_dialogs_show_rename_file (gpointer parent, ThunarFile *file, ThunarOperationLogMode log_mode) { ThunarIconFactory *icon_factory; GtkIconTheme *icon_theme; gint scale_factor; const gchar *filename; const gchar *text; ThunarJob *job = NULL; GtkWidget *dialog; GtkWidget *label; GtkWidget *image; GtkWidget *grid; XfceFilenameInput *filename_input; GtkEntry *filename_input_entry; GtkWindow *window; GdkScreen *screen; GdkPixbuf *icon; cairo_surface_t *surface; gchar *title; gint response; gint dialog_width; gint parent_width = 500; gint row = 0; _thunar_return_val_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WINDOW (parent), FALSE); _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); /* parse the parent window and screen */ screen = thunar_util_parse_parent (parent, &window); /* get the filename of the file */ filename = thunar_file_get_basename (file); /* create a new dialog window */ title = g_strdup_printf (_("Rename \"%s\""), filename); dialog = gtk_dialog_new_with_buttons (title, window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Rename"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); g_free (title); /* move the dialog to the appropriate screen */ if (G_UNLIKELY (window == NULL && screen != NULL)) gtk_window_set_screen (GTK_WINDOW (dialog), screen); if (window != NULL) scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (window)); else scale_factor = gtk_widget_get_scale_factor (dialog); grid = gtk_grid_new (); gtk_grid_set_column_spacing (GTK_GRID (grid), 6); gtk_grid_set_row_spacing (GTK_GRID (grid), 3); gtk_container_set_border_width (GTK_CONTAINER (grid), 6); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), grid, TRUE, TRUE, 0); gtk_widget_show (grid); icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (dialog)); icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme); icon = thunar_icon_factory_load_file_icon (icon_factory, file, THUNAR_FILE_ICON_STATE_DEFAULT, 48, scale_factor, FALSE, NULL); surface = gdk_cairo_surface_create_from_pixbuf (icon, scale_factor, NULL); g_object_unref (G_OBJECT (icon_factory)); image = gtk_image_new_from_surface (surface); gtk_widget_set_margin_start (GTK_WIDGET (image), 6); gtk_widget_set_margin_end (GTK_WIDGET (image), 6); gtk_widget_set_margin_top (GTK_WIDGET (image), 6); gtk_widget_set_margin_bottom (GTK_WIDGET (image), 6); gtk_widget_set_valign (GTK_WIDGET (image), GTK_ALIGN_START); gtk_grid_attach (GTK_GRID (grid), image, 0, row, 1, 2); g_object_unref (G_OBJECT (icon)); cairo_surface_destroy (surface); gtk_widget_show (image); label = gtk_label_new (_("Enter the new name:")); gtk_label_set_xalign (GTK_LABEL (label), 0.0f); gtk_widget_set_hexpand (label, TRUE); gtk_grid_attach (GTK_GRID (grid), label, 1, row, 1, 1); gtk_widget_show (label); /* set up the widget for entering the filename */ filename_input = g_object_new (XFCE_TYPE_FILENAME_INPUT, "original-filename", filename, NULL); filename_input_entry = xfce_filename_input_get_entry (filename_input); gtk_widget_set_hexpand (GTK_WIDGET (filename_input), TRUE); gtk_widget_set_valign (GTK_WIDGET (filename_input), GTK_ALIGN_CENTER); /* connect to signals so that the sensitivity of the Create button is updated according to whether there * is a valid file name entered */ g_signal_connect_swapped (filename_input, "text-invalid", G_CALLBACK (xfce_filename_input_desensitise_widget), gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK)); g_signal_connect_swapped (filename_input, "text-valid", G_CALLBACK (xfce_filename_input_sensitise_widget), gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK)); /* next row */ row++; gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (filename_input), 1, row, 1, 1); thunar_gtk_label_set_a11y_relation (GTK_LABEL (label), GTK_WIDGET (filename_input_entry)); gtk_widget_show_all (GTK_WIDGET (filename_input)); /* ensure that the sensitivity of the Create button is set correctly */ xfce_filename_input_check (filename_input); /* If it is no directory, select the filename without the extension */ if (thunar_file_is_directory (file)) gtk_editable_select_region (GTK_EDITABLE (filename_input_entry), 0, -1); else thunar_dialogs_select_filename (GTK_WIDGET (filename_input_entry)); /* change the width of the input entry to be about the right size for N chars (filename length) */ gtk_entry_set_max_width_chars (filename_input_entry, g_utf8_strlen (filename, -1)); /* dialog window width */ gtk_window_get_size (GTK_WINDOW (dialog), &dialog_width, NULL); /* parent window width */ if (G_LIKELY (window != NULL)) { /* keep below 90% of the parent window width */ gtk_window_get_size (window, &parent_width, NULL); parent_width *= 0.90f; } /* resize the dialog to make long names fit as much as possible */ gtk_window_set_default_size (GTK_WINDOW (dialog), CLAMP (dialog_width, 300, parent_width), -1); /* automatically close the dialog when the file is destroyed */ g_signal_connect_swapped (G_OBJECT (file), "destroy", G_CALLBACK (gtk_widget_destroy), dialog); /* shrink the dialog again, after some filename error was fixed */ g_signal_connect_swapped (filename_input, "text-valid", G_CALLBACK (thunar_dialogs_shrink_height), dialog); /* run the dialog */ response = gtk_dialog_run (GTK_DIALOG (dialog)); if (G_LIKELY (response == GTK_RESPONSE_OK)) { /* hide the dialog */ gtk_widget_hide (dialog); /* determine the new filename */ text = xfce_filename_input_get_text (filename_input); /* check if we have a new name here */ if (G_LIKELY (g_strcmp0 (filename, text)) != 0) { /* try to rename the file */ job = thunar_io_jobs_rename_file (file, text, log_mode); exo_job_launch (EXO_JOB (job)); } } /* cleanup */ if (G_LIKELY (response != GTK_RESPONSE_NONE)) { /* unregister handler */ g_signal_handlers_disconnect_by_func (G_OBJECT (file), gtk_widget_destroy, dialog); gtk_widget_destroy (dialog); } return job; } /** * thunar_dialogs_show_about: * @parent : the parent #GtkWindow or %NULL. * @title : the software title. * @format : the printf()-style format for the main text in the about dialog. * @... : argument list for the @format. * * Displays the Thunar about dialog with @format as main text. **/ void thunar_dialogs_show_about (GtkWindow *parent, const gchar *title, const gchar *format, ...) { static const gchar *artists[] = { "Young Hahn ", NULL, }; static const gchar *authors[] = { "Benedikt Meurer ", "Jannis Pohlmann ", "Nick Schermer ", NULL, }; static const gchar *documenters[] = { "Benedikt Meurer ", "Jannis Pohlmann ", "Nick Schermer ", NULL, }; va_list args; gchar *comments; _thunar_return_if_fail (parent == NULL || GTK_IS_WINDOW (parent)); /* determine the comments */ va_start (args, format); comments = g_strdup_vprintf (format, args); va_end (args); /* open the about dialog */ gtk_show_about_dialog (parent, "artists", artists, "authors", authors, "comments", comments, "copyright", "Copyright \302\251 2004-2011 Benedikt Meurer\n" "Copyright \302\251 2009-2011 Jannis Pohlmann\n" "Copyright \302\251 2009-2012 Nick Schermer\n" "Copyright \302\251 2017-2022 Andre Miranda\n" "Copyright \302\251 2017-2024 Alexander Schwinn", "destroy-with-parent", TRUE, "documenters", documenters, "license", XFCE_LICENSE_GPL, "logo-icon-name", "org.xfce.thunar", "program-name", title, "translator-credits", _("translator-credits"), "version", PACKAGE_VERSION, "website", "https://docs.xfce.org/xfce/thunar/start", NULL); /* cleanup */ g_free (comments); } /** * thunar_dialogs_show_error: * @parent : a #GtkWidget on which the error dialog should be shown, or a #GdkScreen * if no #GtkWidget is known. May also be %NULL, in which case the default * #GdkScreen will be used. * @error : a #GError, which gives a more precise description of the problem or %NULL. * @format : the printf()-style format for the primary problem description. * @... : argument list for the @format. * * Displays an error dialog on @widget using the @format as primary message and optionally * displaying @error as secondary error text. * * If @widget is not %NULL and @widget is part of a #GtkWindow, the function makes sure * that the toplevel window is visible prior to displaying the error dialog. **/ void thunar_dialogs_show_error (gpointer parent, const GError *error, const gchar *format, ...) { GtkWidget *dialog; GtkWindow *window; GdkScreen *screen; va_list args; gchar *primary_text; GList *children; GList *lp; _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); /* do not display error dialog for already handled errors */ if (error && error->code == G_IO_ERROR_FAILED_HANDLED) return; /* parse the parent pointer */ screen = thunar_util_parse_parent (parent, &window); /* determine the primary error text */ va_start (args, format); primary_text = g_strdup_vprintf (format, args); va_end (args); /* allocate the error dialog */ dialog = gtk_message_dialog_new (window, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s.", primary_text); gtk_window_set_title (GTK_WINDOW (dialog), _("Error")); /* move the dialog to the appropriate screen */ if (G_UNLIKELY (window == NULL && screen != NULL)) gtk_window_set_screen (GTK_WINDOW (dialog), screen); /* set secondary text if an error is provided */ if (G_LIKELY (error != NULL)) gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s.", error->message); children = gtk_container_get_children ( GTK_CONTAINER (gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog)))); /* enable wrap for labels */ for (lp = children; lp != NULL; lp = lp->next) if (GTK_IS_LABEL (lp->data)) gtk_label_set_line_wrap_mode (GTK_LABEL (lp->data), PANGO_WRAP_WORD_CHAR); /* display the dialog */ gtk_dialog_run (GTK_DIALOG (dialog)); /* cleanup */ gtk_widget_destroy (dialog); g_free (primary_text); g_list_free (children); } /** * thunar_dialogs_show_job_ask: * @parent : the parent #GtkWindow or %NULL. * @question : the question text. * @choices : possible responses. * * Utility function to display a question dialog for the ThunarJob::ask * signal. * * Return value: the #ThunarJobResponse. **/ ThunarJobResponse thunar_dialogs_show_job_ask (GtkWindow *parent, const gchar *question, ThunarJobResponse choices) { const gchar *separator; const gchar *mnemonic; GtkWidget *message; GtkWidget *button; GString *secondary = g_string_sized_new (256); GString *primary = g_string_sized_new (256); gint response; gint n; gboolean has_cancel = FALSE; _thunar_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), THUNAR_JOB_RESPONSE_CANCEL); _thunar_return_val_if_fail (g_utf8_validate (question, -1, NULL), THUNAR_JOB_RESPONSE_CANCEL); /* try to separate the question into primary and secondary parts */ separator = strstr (question, ": "); if (G_LIKELY (separator != NULL)) { /* primary is everything before the colon, plus a dot */ g_string_append_len (primary, question, separator - question); g_string_append_c (primary, '.'); /* secondary is everything after the colon (skipping whitespace) */ do ++separator; while (g_ascii_isspace (*separator)); g_string_append (secondary, separator); } else { /* otherwise separate based on the \n\n */ separator = strstr (question, "\n\n"); if (G_LIKELY (separator != NULL)) { /* primary is everything before the newlines */ g_string_append_len (primary, question, separator - question); /* secondary is everything after the newlines (skipping whitespace) */ while (g_ascii_isspace (*separator)) ++separator; g_string_append (secondary, separator); } else { /* everything is primary */ g_string_append (primary, question); } } /* allocate the question message dialog */ message = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", primary->str); gtk_window_set_title (GTK_WINDOW (message), _("Attention")); if (G_LIKELY (*secondary->str != '\0')) gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), "%s", secondary->str); /* add the buttons based on the possible choices */ for (n = THUNAR_JOB_RESPONSE_MAX_INT; n >= 0; --n) { /* check if the response is set */ response = choices & (1 << n); if (response == 0) continue; switch (response) { case THUNAR_JOB_RESPONSE_YES: mnemonic = _("_Yes"); break; case THUNAR_JOB_RESPONSE_YES_ALL: mnemonic = _("Yes to _all"); break; case THUNAR_JOB_RESPONSE_REPLACE: mnemonic = _("_Replace"); break; case THUNAR_JOB_RESPONSE_REPLACE_ALL: mnemonic = _("Replace _All"); break; case THUNAR_JOB_RESPONSE_SKIP: mnemonic = _("_Skip"); break; case THUNAR_JOB_RESPONSE_SKIP_ALL: mnemonic = _("S_kip All"); break; case THUNAR_JOB_RESPONSE_RENAME: mnemonic = _("Re_name"); break; case THUNAR_JOB_RESPONSE_RENAME_ALL: mnemonic = _("Rena_me All"); break; case THUNAR_JOB_RESPONSE_NO: mnemonic = _("_No"); break; case THUNAR_JOB_RESPONSE_NO_ALL: mnemonic = _("N_o to all"); break; case THUNAR_JOB_RESPONSE_RETRY: mnemonic = _("_Retry"); break; case THUNAR_JOB_RESPONSE_FORCE: mnemonic = _("Copy _Anyway"); break; case THUNAR_JOB_RESPONSE_CANCEL: /* cancel is always the last option */ has_cancel = TRUE; continue; default: g_assert_not_reached (); break; } button = gtk_button_new_with_mnemonic (mnemonic); gtk_widget_set_can_default (button, TRUE); gtk_dialog_add_action_widget (GTK_DIALOG (message), button, response); gtk_widget_show (button); gtk_dialog_set_default_response (GTK_DIALOG (message), response); } if (has_cancel) { button = gtk_button_new_with_mnemonic (_("_Cancel")); gtk_widget_set_can_default (button, TRUE); gtk_dialog_add_action_widget (GTK_DIALOG (message), button, GTK_RESPONSE_CANCEL); gtk_widget_show (button); gtk_dialog_set_default_response (GTK_DIALOG (message), GTK_RESPONSE_CANCEL); } /* run the question dialog */ response = gtk_dialog_run (GTK_DIALOG (message)); gtk_widget_destroy (message); /* transform the result as required */ if (G_UNLIKELY (response <= 0)) response = THUNAR_JOB_RESPONSE_CANCEL; /* cleanup */ g_string_free (secondary, TRUE); g_string_free (primary, TRUE); return response; } static void thunar_dialog_image_redraw (GtkWidget *image, ThunarThumbnailSize size, ThunarFile *file) { ThunarIconFactory *icon_factory; GtkIconTheme *icon_theme; gint scale_factor; GdkPixbuf *icon; cairo_surface_t *surface; /* determine the icon factory to use */ icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (image)); icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme); scale_factor = gtk_widget_get_scale_factor (image); icon = thunar_icon_factory_load_file_icon (icon_factory, file, THUNAR_FILE_ICON_STATE_DEFAULT, 48, scale_factor, FALSE, NULL); surface = gdk_cairo_surface_create_from_pixbuf (icon, scale_factor, NULL); gtk_image_set_from_surface (GTK_IMAGE (image), surface); /* cleanup */ g_object_unref (G_OBJECT (icon)); cairo_surface_destroy (surface); g_object_unref (G_OBJECT (icon_factory)); } /** * thunar_dialogs_show_job_ask_replace: * @parent : the parent #GtkWindow or %NULL. * @src_file : the #ThunarFile of the source file. * @dst_file : the #ThunarFile of the destination file that * may be replaced with the source file. * * Asks the user whether to replace the destination file with the * source file identified by @src_file. * * Return value: the selected #ThunarJobResponse. **/ ThunarJobResponse thunar_dialogs_show_job_ask_replace (GtkWindow *parent, ThunarFile *src_file, ThunarFile *dst_file, gboolean multiple_files) { ThunarIconFactory *icon_factory; ThunarPreferences *preferences; ThunarDateStyle date_style; ThunarFile *parent_file; GtkIconTheme *icon_theme; GtkWidget *dialog; GtkWidget *grid; GtkWidget *image, *src_image, *dst_image; GtkWidget *label; GtkWidget *content_area; GtkWidget *action_area; GtkWidget *check_button; GdkPixbuf *icon; cairo_surface_t *surface; const gchar *parent_string = ""; gchar *date_custom_style; gchar *date_string; gchar *size_string; gchar *text; gint response; gboolean file_size_binary; gint row = 0; gint scale_factor; gint width; _thunar_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), THUNAR_JOB_RESPONSE_CANCEL); _thunar_return_val_if_fail (THUNAR_IS_FILE (src_file), THUNAR_JOB_RESPONSE_CANCEL); _thunar_return_val_if_fail (THUNAR_IS_FILE (dst_file), THUNAR_JOB_RESPONSE_CANCEL); /* determine the style used to format dates */ preferences = thunar_preferences_get (); g_object_get (G_OBJECT (preferences), "misc-date-style", &date_style, NULL); g_object_get (G_OBJECT (preferences), "misc-date-custom-style", &date_custom_style, NULL); g_object_get (G_OBJECT (preferences), "misc-file-size-binary", &file_size_binary, NULL); g_object_unref (G_OBJECT (preferences)); /* setup the confirmation dialog */ dialog = gtk_dialog_new_with_buttons (_("Confirm to replace files"), parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, _("_Cancel"), THUNAR_JOB_RESPONSE_CANCEL, _("_Skip"), THUNAR_JOB_RESPONSE_SKIP, _("Re_name"), THUNAR_JOB_RESPONSE_RENAME, _("_Replace"), THUNAR_JOB_RESPONSE_REPLACE, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), THUNAR_JOB_RESPONSE_REPLACE); content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); G_GNUC_BEGIN_IGNORE_DEPRECATIONS action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog)); G_GNUC_END_IGNORE_DEPRECATIONS gtk_button_box_set_layout (GTK_BUTTON_BOX (action_area), GTK_BUTTONBOX_SPREAD); gtk_widget_set_margin_bottom (GTK_WIDGET (action_area), 3); if (parent != NULL) scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (parent)); else scale_factor = gtk_widget_get_scale_factor (dialog); /* determine the icon factory to use */ icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (dialog)); icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme); grid = gtk_grid_new (); gtk_grid_set_column_spacing (GTK_GRID (grid), 5); gtk_grid_set_row_spacing (GTK_GRID (grid), 6); gtk_container_set_border_width (GTK_CONTAINER (grid), 10); gtk_box_pack_start (GTK_BOX (content_area), grid, TRUE, FALSE, 0); gtk_widget_show (grid); image = gtk_image_new_from_icon_name ("stock_folder-copy", GTK_ICON_SIZE_BUTTON); gtk_widget_set_halign (image, GTK_ALIGN_CENTER); gtk_widget_set_valign (image, GTK_ALIGN_START); gtk_widget_set_margin_start (GTK_WIDGET (image), 6); gtk_widget_set_margin_end (GTK_WIDGET (image), 6); gtk_widget_set_margin_top (GTK_WIDGET (image), 6); gtk_widget_set_margin_bottom (GTK_WIDGET (image), 6); gtk_widget_set_vexpand (image, TRUE); gtk_grid_attach (GTK_GRID (grid), image, 0, row, 1, 1); gtk_widget_show (image); if (thunar_file_is_symlink (dst_file)) { text = g_strdup_printf (_("This folder already contains a symbolic link \"%s\"."), thunar_file_get_display_name (dst_file)); } else if (thunar_file_is_directory (dst_file)) { text = g_strdup_printf (_("This folder already contains a folder \"%s\"."), thunar_file_get_display_name (dst_file)); } else { if (g_strcmp0 (thunar_file_get_display_name (dst_file), thunar_file_get_basename (dst_file)) != 0) text = g_strdup_printf (_("This folder already contains a file \"%s\" (%s)."), thunar_file_get_display_name (dst_file), thunar_file_get_basename (dst_file)); else text = g_strdup_printf (_("This folder already contains a file \"%s\"."), thunar_file_get_display_name (dst_file)); } label = gtk_label_new (text); gtk_label_set_xalign (GTK_LABEL (label), 0.0f); gtk_label_set_attributes (GTK_LABEL (label), thunar_pango_attr_list_big_bold ()); gtk_label_set_selectable (GTK_LABEL (label), TRUE); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_CHAR); thunar_gtk_label_disable_hyphens (GTK_LABEL (label)); gtk_widget_set_hexpand (label, TRUE); gtk_grid_attach (GTK_GRID (grid), label, 1, row, 2, 1); gtk_widget_show (label); g_free (text); parent_file = thunar_file_get_parent (dst_file, NULL); if (parent_file != NULL) parent_string = thunar_file_get_basename (parent_file); if (thunar_file_is_symlink (dst_file)) /* TRANSLATORS: First part of replace dialog sentence */ text = g_strdup_printf (_("Replace the link in \"%s\""), parent_string); else if (thunar_file_is_directory (dst_file)) /* TRANSLATORS: First part of replace dialog sentence */ text = g_strdup_printf (_("Replace the existing folder in \"%s\""), parent_string); else /* TRANSLATORS: First part of replace dialog sentence */ text = g_strdup_printf (_("Replace the existing file in \"%s\""), parent_string); if (parent_file != NULL) g_object_unref (parent_file); /* next row */ row++; label = gtk_label_new (text); gtk_label_set_xalign (GTK_LABEL (label), 0.0f); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE); gtk_widget_set_hexpand (label, TRUE); gtk_grid_attach (GTK_GRID (grid), label, 1, row, 2, 1); gtk_widget_show (label); g_free (text); /* next row */ row++; icon = thunar_icon_factory_load_file_icon (icon_factory, dst_file, THUNAR_FILE_ICON_STATE_DEFAULT, 48, scale_factor, FALSE, NULL); surface = gdk_cairo_surface_create_from_pixbuf (icon, scale_factor, NULL); dst_image = gtk_image_new_from_surface (surface); gtk_widget_set_margin_start (GTK_WIDGET (dst_image), 6); gtk_widget_set_margin_end (GTK_WIDGET (dst_image), 6); gtk_widget_set_margin_top (GTK_WIDGET (dst_image), 6); gtk_widget_set_margin_bottom (GTK_WIDGET (dst_image), 6); gtk_grid_attach (GTK_GRID (grid), dst_image, 1, row, 1, 1); g_object_unref (G_OBJECT (icon)); cairo_surface_destroy (surface); gtk_widget_show (dst_image); g_signal_connect_swapped (G_OBJECT (dst_file), "thumbnail-updated", G_CALLBACK (thunar_dialog_image_redraw), dst_image); thunar_file_request_thumbnail (dst_file, thunar_icon_size_to_thumbnail_size (48 * scale_factor)); size_string = thunar_file_get_size_string_long (dst_file, file_size_binary); date_string = thunar_file_get_date_string (dst_file, THUNAR_FILE_DATE_MODIFIED, date_style, date_custom_style); text = g_strdup_printf ("%s %s\n%s %s", _("Size:"), size_string, _("Modified:"), date_string); label = gtk_label_new (text); gtk_label_set_xalign (GTK_LABEL (label), 0.0f); gtk_widget_set_hexpand (label, TRUE); gtk_grid_attach (GTK_GRID (grid), label, 2, row, 1, 1); gtk_widget_show (label); g_free (size_string); g_free (date_string); g_free (text); if (thunar_file_is_symlink (src_file)) /* TRANSLATORS: Second part of replace dialog sentence */ text = g_strdup_printf (_("with the following link?")); else if (thunar_file_is_directory (src_file)) /* TRANSLATORS: Second part of replace dialog sentence */ text = g_strdup_printf (_("with the following folder?")); else /* TRANSLATORS: Second part of replace dialog sentence */ text = g_strdup_printf (_("with the following file?")); /* next row */ row++; label = gtk_label_new (text); gtk_label_set_xalign (GTK_LABEL (label), 0.0f); gtk_widget_set_hexpand (label, TRUE); gtk_grid_attach (GTK_GRID (grid), label, 1, row, 2, 1); gtk_widget_show (label); g_free (text); /* next row */ row++; icon = thunar_icon_factory_load_file_icon (icon_factory, src_file, THUNAR_FILE_ICON_STATE_DEFAULT, 48, scale_factor, FALSE, NULL); surface = gdk_cairo_surface_create_from_pixbuf (icon, scale_factor, NULL); src_image = gtk_image_new_from_surface (surface); gtk_widget_set_margin_start (GTK_WIDGET (src_image), 6); gtk_widget_set_margin_end (GTK_WIDGET (src_image), 6); gtk_widget_set_margin_top (GTK_WIDGET (src_image), 6); gtk_widget_set_margin_bottom (GTK_WIDGET (src_image), 6); gtk_grid_attach (GTK_GRID (grid), src_image, 1, row, 1, 1); g_object_unref (G_OBJECT (icon)); cairo_surface_destroy (surface); gtk_widget_show (src_image); g_signal_connect_swapped (G_OBJECT (src_file), "thumbnail-updated", G_CALLBACK (thunar_dialog_image_redraw), src_image); thunar_file_request_thumbnail (src_file, thunar_icon_size_to_thumbnail_size (48 * scale_factor)); size_string = thunar_file_get_size_string_long (src_file, file_size_binary); date_string = thunar_file_get_date_string (src_file, THUNAR_FILE_DATE_MODIFIED, date_style, date_custom_style); text = g_strdup_printf ("%s %s\n%s %s", _("Size:"), size_string, _("Modified:"), date_string); label = gtk_label_new (text); gtk_label_set_xalign (GTK_LABEL (label), 0.0f); gtk_widget_set_hexpand (label, TRUE); gtk_grid_attach (GTK_GRID (grid), label, 2, row, 1, 1); gtk_widget_show (label); g_free (size_string); g_free (date_string); g_free (text); g_free (date_custom_style); if (multiple_files) { /* next row */ row++; check_button = gtk_check_button_new_with_mnemonic (_("_Apply the action to all files and folders")); gtk_widget_set_margin_top (check_button, 6); gtk_grid_attach (GTK_GRID (grid), check_button, 0, row, 3, 1); gtk_widget_show (check_button); } /* prevent long filenames from pushing the dialog width over a certain value */ gtk_window_get_size (GTK_WINDOW (dialog), &width, NULL); if (width > 600) gtk_window_resize (GTK_WINDOW (dialog), 600, 1); /* run the dialog */ response = gtk_dialog_run (GTK_DIALOG (dialog)); /* translate GTK responses */ if (G_UNLIKELY (response < 0)) response = THUNAR_JOB_RESPONSE_CANCEL; if (multiple_files) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_button))) { if (response == THUNAR_JOB_RESPONSE_SKIP) response = THUNAR_JOB_RESPONSE_SKIP_ALL; else if (response == THUNAR_JOB_RESPONSE_REPLACE) response = THUNAR_JOB_RESPONSE_REPLACE_ALL; else if (response == THUNAR_JOB_RESPONSE_RENAME) response = THUNAR_JOB_RESPONSE_RENAME_ALL; } } g_signal_handlers_disconnect_by_data (src_file, src_image); g_signal_handlers_disconnect_by_data (dst_file, dst_image); gtk_widget_destroy (dialog); /* cleanup */ g_object_unref (G_OBJECT (icon_factory)); return response; } gboolean thunar_dialogs_show_insecure_program (gpointer parent, const gchar *primary, ThunarFile *file, const gchar *command) { GdkScreen *screen; GtkWindow *window; gint response; GtkWidget *dialog; GString *secondary; GError *err = NULL; gchar *file_name; gchar *executable; _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); _thunar_return_val_if_fail (g_utf8_validate (command, -1, NULL), FALSE); /* parse the parent window and screen */ screen = thunar_util_parse_parent (parent, &window); /* create the secondary text */ if (g_strcmp0 (thunar_file_get_display_name (file), thunar_file_get_basename (file)) != 0) file_name = g_strdup_printf ("\"%s\" (%s)", thunar_file_get_display_name (file), thunar_file_get_basename (file)); else file_name = g_strdup_printf ("\"%s\"", thunar_file_get_display_name (file)); if (g_file_info_get_attribute_boolean (thunar_file_get_info (file), G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) == FALSE) /* TRANSLATORS: This string will be inserted in the following sentence in the second appearance of %s: * "The desktop file %s is in an insecure location and not marked as secure%s." */ executable = g_strdup (_("/executable")); else executable = g_strdup (""); secondary = g_string_new (NULL); g_string_printf (secondary, _("The desktop file %s is in an insecure location and not marked as secure%s. " "If you do not trust this program, click Cancel.\n\n"), file_name, executable); g_free (file_name); g_free (executable); if (g_uri_is_valid (command, G_URI_FLAGS_NONE, NULL)) g_string_append_printf (secondary, G_KEY_FILE_DESKTOP_KEY_URL "=%s", command); else g_string_append_printf (secondary, G_KEY_FILE_DESKTOP_KEY_EXEC "=%s", command); /* allocate and display the error message dialog */ dialog = gtk_message_dialog_new (window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s", primary); gtk_window_set_title (GTK_WINDOW (dialog), _("Attention")); gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Launch Anyway"), GTK_RESPONSE_OK); if (thunar_file_is_chmodable (file)) gtk_dialog_add_button (GTK_DIALOG (dialog), _("Mark As _Secure And Launch"), GTK_RESPONSE_APPLY); gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); if (screen != NULL && window == NULL) gtk_window_set_screen (GTK_WINDOW (dialog), screen); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", secondary->str); g_string_free (secondary, TRUE); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); /* check if we can set the file executable */ if (response == GTK_RESPONSE_APPLY) { if (thunar_g_file_set_executable_flags (thunar_file_get_file (file), &err)) { thunar_dialogs_show_error (parent, err, ("Unable to mark launcher executable")); g_clear_error (&err); } if (thunar_g_vfs_metadata_is_supported ()) if (!xfce_g_file_set_trusted (thunar_file_get_file (file), TRUE, NULL, &err)) { thunar_dialogs_show_error (parent, err, ("Unable to mark launcher as trusted")); g_clear_error (&err); } /* just launch */ response = GTK_RESPONSE_OK; } return (response == GTK_RESPONSE_OK); } static void thunar_dialogs_select_filename (GtkWidget *entry) { const gchar *filename; const gchar *ext; glong offset; GtkEntryBuffer *buffer; buffer = gtk_entry_get_buffer (GTK_ENTRY (entry)); filename = gtk_entry_buffer_get_text (buffer); /* check if the filename contains an extension */ ext = thunar_util_str_get_extension (filename); if (G_UNLIKELY (ext == NULL)) return; /* grab focus to the entry first, else the selection will be altered later */ gtk_widget_grab_focus (entry); /* determine the UTF-8 char offset */ offset = g_utf8_pointer_to_offset (filename, ext); /* select the text prior to the dot */ if (G_LIKELY (offset > 0)) gtk_editable_select_region (GTK_EDITABLE (entry), 0, offset); } /** * thunar_dialog_confirm_close_split_pane_tabs: * @parent : (allow-none): transient parent of the dialog, or %NULL. * * Runs a dialog to ask the user whether they want to close a split pane with multiple tabs * * Return value: #GTK_RESPONSE_CANCEL if cancelled, #GTK_RESPONSE_CLOSE if the user * wants to close all tabs of the other split pane */ gint thunar_dialog_confirm_close_split_pane_tabs (GtkWindow *parent) { GtkWidget *dialog, *checkbutton, *vbox; const gchar *primary_text, *warning_icon; gchar *secondary_text; ThunarPreferences *preferences; gint response; g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), GTK_RESPONSE_NONE); primary_text = _("Close split pane with multiple tabs?"); secondary_text = g_strdup (_("The other split pane has multiple open tabs. Closing it " "will close all these tabs.")); warning_icon = "dialog-warning"; dialog = xfce_message_dialog_new (parent, _("Warning"), warning_icon, primary_text, secondary_text, XFCE_BUTTON_TYPE_MIXED, NULL, _("_Cancel"), GTK_RESPONSE_CANCEL, XFCE_BUTTON_TYPE_MIXED, NULL, _("Close Pane"), GTK_RESPONSE_CLOSE, NULL); checkbutton = gtk_check_button_new_with_mnemonic (_("Do _not ask me again")); vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); gtk_box_pack_end (GTK_BOX (vbox), checkbutton, FALSE, FALSE, 5); g_object_set (G_OBJECT (checkbutton), "halign", GTK_ALIGN_END, "margin-start", 6, "margin-end", 6, NULL); gtk_widget_set_hexpand (checkbutton, TRUE); gtk_widget_show_all (dialog); response = gtk_dialog_run (GTK_DIALOG (dialog)); /* if the user requested not to be asked again, store this preference */ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbutton))) { preferences = thunar_preferences_get (); g_object_set (G_OBJECT (preferences), "misc-confirm-close-multiple-tabs", FALSE, NULL); g_object_unref (G_OBJECT (preferences)); } gtk_widget_destroy (dialog); g_free (secondary_text); return response; } /** * thunar_dialog_ask_execute: * @file : a #ThunarFile pointer * @parent : a #GtkWidget on which the error dialog should be shown, or a #GdkScreen * if no #GtkWidget is known. May also be %NULL, in which case the default * #GdkScreen will be used. * @allow_open : if set, the "Open" button is visible. * @single_file : if set, the file name is visible in dialog message. Otherwise there is * generic message about selected files. * * Shows a dialog where the user is asked if and how he wants to run the executable script. * * Return value: One of #ThunarFileAskExecuteResponse enum value. **/ gint thunar_dialog_ask_execute (const ThunarFile *file, gpointer parent, gboolean allow_open, gboolean single_file) { GtkWidget *dialog; GtkWindow *window; GdkScreen *screen; gint response; gchar *dialog_text; GtkWidget *button; _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); _thunar_return_val_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent), THUNAR_FILE_ASK_EXECUTE_RESPONSE_OPEN); /* parse the parent window and screen */ screen = thunar_util_parse_parent (parent, &window); if (single_file) { gchar *basename = g_file_get_basename (thunar_file_get_file (file)); dialog_text = g_strdup_printf (_("The file \"%s\" seems to be executable. What do you want to do with it?"), basename); g_free (basename); } else { dialog_text = g_strdup (_("The selected files seem to be executable. What do you want to do with them?")); } dialog = gtk_message_dialog_new (window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", dialog_text); gtk_window_set_title (GTK_WINDOW (dialog), _("Open Shell Script")); g_free (dialog_text); button = gtk_button_new_with_mnemonic (_("Run In _Terminal")); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, THUNAR_FILE_ASK_EXECUTE_RESPONSE_RUN_IN_TERMINAL); gtk_widget_show (button); button = gtk_button_new_with_mnemonic (_("_Execute")); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, THUNAR_FILE_ASK_EXECUTE_RESPONSE_RUN); gtk_widget_show (button); if (allow_open) { button = gtk_button_new_with_mnemonic (_("_Open")); gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, THUNAR_FILE_ASK_EXECUTE_RESPONSE_OPEN); gtk_widget_show (button); gtk_widget_set_can_default (button, TRUE); gtk_dialog_set_default_response (GTK_DIALOG (dialog), THUNAR_FILE_ASK_EXECUTE_RESPONSE_OPEN); } else { gtk_widget_set_can_default (button, TRUE); gtk_dialog_set_default_response (GTK_DIALOG (dialog), THUNAR_FILE_ASK_EXECUTE_RESPONSE_RUN); } if (G_UNLIKELY (window == NULL && screen != NULL)) gtk_window_set_screen (GTK_WINDOW (dialog), screen); response = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); return response; } /** * thunar_dialog_show_launcher_props: * @launcher : a #ThunarFile. * @parent : a #GdkScreen, a #GtkWidget or %NULL. If %NULL is passed then the default screen will be used. * * Calls the exo-desktop-item-edit command to edit the properties of a .desktop file. * A dialog with the current properties will appear. **/ void thunar_dialog_show_launcher_props (ThunarFile *launcher, gpointer parent) { const gchar *display_name; gchar *cmd = NULL, *uri = NULL; GError *error = NULL; GdkScreen *screen; _thunar_return_if_fail (launcher == NULL || THUNAR_IS_FILE (launcher)); _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); screen = thunar_util_parse_parent (parent, NULL); uri = thunar_file_dup_uri (launcher); display_name = gdk_display_get_name (gdk_screen_get_display (screen)); cmd = g_strdup_printf ("exo-desktop-item-edit \"--display=%s\" \"%s\"", display_name, uri); if (xfce_spawn_command_line (NULL, cmd, FALSE, FALSE, FALSE, &error) == FALSE) thunar_dialogs_show_error (screen, error, _("Failed to edit launcher via command \"%s\""), cmd); g_free (cmd); g_free (uri); g_clear_error (&error); }