/* * dialogs.c - this file is part of Geany, a fast and lightweight IDE * * Copyright 2005-2012 Enrico Tröger * Copyright 2006-2012 Nick Treleaven * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* * File related dialogs, miscellaneous dialogs, font dialog. */ #include "geany.h" #include #include #ifdef HAVE_SYS_TIME_H # include #endif #include #ifdef HAVE_SYS_TYPES_H # include #endif /* gstdio.h also includes sys/stat.h */ #include #include "dialogs.h" #include "callbacks.h" #include "document.h" #include "filetypes.h" #include "win32.h" #include "sciwrappers.h" #include "support.h" #include "utils.h" #include "ui_utils.h" #include "keybindings.h" #include "encodings.h" #include "build.h" #include "main.h" #include "project.h" #include "gtkcompat.h" enum { GEANY_RESPONSE_RENAME, GEANY_RESPONSE_VIEW }; static struct FileSelState { struct { guint filter_idx; gint encoding_idx; gint filetype_idx; gboolean show_hidden; gboolean more_options_visible; } open; } filesel_state = { { 0, GEANY_ENCODINGS_MAX, /* default encoding is detect from file */ 0, FALSE, FALSE } }; /* gets the ID of the current file filter */ static guint file_chooser_get_filter_idx(GtkFileChooser *chooser) { guint idx = 0; GtkFileFilter *current; GSList *filters, *item; current = gtk_file_chooser_get_filter(chooser); filters = gtk_file_chooser_list_filters(chooser); foreach_slist(item, filters) { if (item->data == current) break; idx ++; } g_slist_free(filters); return idx; } /* sets the current file filter from its ID */ static void file_chooser_set_filter_idx(GtkFileChooser *chooser, guint idx) { GtkFileFilter *current; GSList *filters; filters = gtk_file_chooser_list_filters(chooser); current = g_slist_nth_data(filters, idx); g_slist_free(filters); gtk_file_chooser_set_filter(chooser, current); } static void open_file_dialog_handle_response(GtkWidget *dialog, gint response) { if (response == GTK_RESPONSE_ACCEPT || response == GEANY_RESPONSE_VIEW) { GSList *filelist; GtkTreeModel *encoding_model; GtkTreeIter encoding_iter; GeanyFiletype *ft = NULL; const gchar *charset = NULL; GtkWidget *expander = ui_lookup_widget(dialog, "more_options_expander"); GtkWidget *filetype_combo = ui_lookup_widget(dialog, "filetype_combo"); GtkWidget *encoding_combo = ui_lookup_widget(dialog, "encoding_combo"); gboolean ro = (response == GEANY_RESPONSE_VIEW); /* View clicked */ filesel_state.open.more_options_visible = gtk_expander_get_expanded(GTK_EXPANDER(expander)); filesel_state.open.filter_idx = file_chooser_get_filter_idx(GTK_FILE_CHOOSER(dialog)); filesel_state.open.filetype_idx = gtk_combo_box_get_active(GTK_COMBO_BOX(filetype_combo)); /* ignore detect from file item */ if (filesel_state.open.filetype_idx > 0) ft = g_slist_nth_data(filetypes_by_title, (guint) filesel_state.open.filetype_idx); encoding_model = gtk_combo_box_get_model(GTK_COMBO_BOX(encoding_combo)); gtk_combo_box_get_active_iter(GTK_COMBO_BOX(encoding_combo), &encoding_iter); gtk_tree_model_get(encoding_model, &encoding_iter, 0, &filesel_state.open.encoding_idx, -1); if (filesel_state.open.encoding_idx >= 0 && filesel_state.open.encoding_idx < GEANY_ENCODINGS_MAX) charset = encodings[filesel_state.open.encoding_idx].charset; filelist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); if (filelist != NULL) { document_open_files(filelist, ro, ft, charset); g_slist_foreach(filelist, (GFunc) g_free, NULL); /* free filenames */ } g_slist_free(filelist); } if (app->project && !EMPTY(app->project->base_path)) gtk_file_chooser_remove_shortcut_folder(GTK_FILE_CHOOSER(dialog), app->project->base_path, NULL); } static void on_file_open_show_hidden_notify(GObject *filechooser, GParamSpec *pspec, gpointer data) { GtkWidget *toggle_button; toggle_button = ui_lookup_widget(GTK_WIDGET(filechooser), "check_hidden"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_button), gtk_file_chooser_get_show_hidden(GTK_FILE_CHOOSER(filechooser))); } static void on_file_open_check_hidden_toggled(GtkToggleButton *togglebutton, GtkWidget *dialog) { filesel_state.open.show_hidden = gtk_toggle_button_get_active(togglebutton); gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), filesel_state.open.show_hidden); } static gint encoding_combo_store_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) { gboolean a_has_child = gtk_tree_model_iter_has_child(model, a); gboolean b_has_child = gtk_tree_model_iter_has_child(model, b); gchar *a_string; gchar *b_string; gint cmp_res; if (a_has_child != b_has_child) return a_has_child ? -1 : 1; gtk_tree_model_get(model, a, 1, &a_string, -1); gtk_tree_model_get(model, b, 1, &b_string, -1); cmp_res = strcmp(a_string, b_string); g_free(a_string); g_free(b_string); return cmp_res; } static GtkTreeStore *create_encoding_combo_store(GtkTreeIter *iter_detect) { GtkTreeStore *store; GtkTreeIter iter_current, iter_westeuro, iter_easteuro, iter_eastasian, iter_asian, iter_utf8, iter_middleeast; GtkTreeIter *iter_parent; gchar *encoding_string; gint i; store = gtk_tree_store_new(2, G_TYPE_INT, G_TYPE_STRING); gtk_tree_store_append(store, iter_detect, NULL); gtk_tree_store_set(store, iter_detect, 0, GEANY_ENCODINGS_MAX, 1, _("Detect from file"), -1); gtk_tree_store_append(store, &iter_westeuro, NULL); gtk_tree_store_set(store, &iter_westeuro, 0, -1, 1, _("West European"), -1); gtk_tree_store_append(store, &iter_easteuro, NULL); gtk_tree_store_set(store, &iter_easteuro, 0, -1, 1, _("East European"), -1); gtk_tree_store_append(store, &iter_eastasian, NULL); gtk_tree_store_set(store, &iter_eastasian, 0, -1, 1, _("East Asian"), -1); gtk_tree_store_append(store, &iter_asian, NULL); gtk_tree_store_set(store, &iter_asian, 0, -1, 1, _("SE & SW Asian"), -1); gtk_tree_store_append(store, &iter_middleeast, NULL); gtk_tree_store_set(store, &iter_middleeast, 0, -1, 1, _("Middle Eastern"), -1); gtk_tree_store_append(store, &iter_utf8, NULL); gtk_tree_store_set(store, &iter_utf8, 0, -1, 1, _("Unicode"), -1); for (i = 0; i < GEANY_ENCODINGS_MAX; i++) { switch (encodings[i].group) { case WESTEUROPEAN: iter_parent = &iter_westeuro; break; case EASTEUROPEAN: iter_parent = &iter_easteuro; break; case EASTASIAN: iter_parent = &iter_eastasian; break; case ASIAN: iter_parent = &iter_asian; break; case MIDDLEEASTERN: iter_parent = &iter_middleeast; break; case UNICODE: iter_parent = &iter_utf8; break; case NONE: default: iter_parent = NULL; } gtk_tree_store_append(store, &iter_current, iter_parent); encoding_string = encodings_to_string(&encodings[i]); gtk_tree_store_set(store, &iter_current, 0, i, 1, encoding_string, -1); g_free(encoding_string); /* restore the saved state */ if (i == filesel_state.open.encoding_idx) *iter_detect = iter_current; } gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), 1, GTK_SORT_ASCENDING); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), 1, encoding_combo_store_sort_func, NULL, NULL); return store; } static void encoding_combo_cell_data_func(GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { gboolean sensitive = !gtk_tree_model_iter_has_child(tree_model, iter); g_object_set(cell, "sensitive", sensitive, NULL); } static GtkWidget *add_file_open_extra_widget(GtkWidget *dialog) { GtkWidget *expander, *vbox, *table, *check_hidden; GtkWidget *filetype_ebox, *filetype_label, *filetype_combo; GtkWidget *encoding_ebox, *encoding_label, *encoding_combo; expander = gtk_expander_new_with_mnemonic(_("_More Options")); vbox = gtk_vbox_new(FALSE, 6); gtk_container_add(GTK_CONTAINER(expander), vbox); table = gtk_table_new(2, 4, FALSE); /* line 1 with checkbox and encoding combo */ check_hidden = gtk_check_button_new_with_mnemonic(_("Show _hidden files")); gtk_widget_show(check_hidden); gtk_table_attach(GTK_TABLE(table), check_hidden, 0, 1, 0, 1, (GtkAttachOptions) (GTK_FILL | GTK_EXPAND), (GtkAttachOptions) (0), 0, 5); /* spacing */ gtk_table_attach(GTK_TABLE(table), gtk_label_new(""), 1, 2, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 5, 5); encoding_label = gtk_label_new(_("Set encoding:")); gtk_misc_set_alignment(GTK_MISC(encoding_label), 1, 0); gtk_table_attach(GTK_TABLE(table), encoding_label, 2, 3, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 4, 5); /* the ebox is for the tooltip, because gtk_combo_box can't show tooltips */ encoding_ebox = gtk_event_box_new(); encoding_combo = gtk_combo_box_new(); gtk_widget_set_tooltip_text(encoding_ebox, _("Explicitly defines an encoding for the file, if it would not be detected. This is useful when you know that the encoding of a file cannot be detected correctly by Geany.\nNote if you choose multiple files, they will all be opened with the chosen encoding.")); gtk_container_add(GTK_CONTAINER(encoding_ebox), encoding_combo); gtk_table_attach(GTK_TABLE(table), encoding_ebox, 3, 4, 0, 1, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 5); /* line 2 with filetype combo */ filetype_label = gtk_label_new(_("Set filetype:")); gtk_misc_set_alignment(GTK_MISC(filetype_label), 1, 0); gtk_table_attach(GTK_TABLE(table), filetype_label, 2, 3, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 4, 5); /* the ebox is for the tooltip, because gtk_combo_box can't show tooltips */ filetype_ebox = gtk_event_box_new(); filetype_combo = gtk_combo_box_text_new(); gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(filetype_combo), 2); gtk_widget_set_tooltip_text(filetype_ebox, _("Explicitly defines a filetype for the file, if it would not be detected by filename extension.\nNote if you choose multiple files, they will all be opened with the chosen filetype.")); gtk_container_add(GTK_CONTAINER(filetype_ebox), filetype_combo); gtk_table_attach(GTK_TABLE(table), filetype_ebox, 3, 4, 1, 2, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), 0, 5); gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); gtk_widget_show_all(vbox); g_signal_connect(check_hidden, "toggled", G_CALLBACK(on_file_open_check_hidden_toggled), dialog); ui_hookup_widget(dialog, expander, "more_options_expander"); ui_hookup_widget(dialog, check_hidden, "check_hidden"); ui_hookup_widget(dialog, filetype_combo, "filetype_combo"); ui_hookup_widget(dialog, encoding_combo, "encoding_combo"); return expander; } static GtkWidget *create_open_file_dialog(void) { GtkWidget *dialog; GtkWidget *filetype_combo, *encoding_combo; GtkWidget *viewbtn; GtkCellRenderer *encoding_renderer; GtkTreeIter encoding_iter; GSList *node; dialog = gtk_file_chooser_dialog_new(_("Open File"), GTK_WINDOW(main_widgets.window), GTK_FILE_CHOOSER_ACTION_OPEN, NULL, NULL); gtk_widget_set_name(dialog, "GeanyDialog"); viewbtn = gtk_dialog_add_button(GTK_DIALOG(dialog), _("_View"), GEANY_RESPONSE_VIEW); gtk_widget_set_tooltip_text(viewbtn, _("Opens the file in read-only mode. If you choose more than one file to open, all files will be opened read-only.")); gtk_dialog_add_buttons(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); gtk_widget_set_size_request(dialog, -1, 460); gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE); gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE); gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main_widgets.window)); gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE); /* add checkboxes and filename entry */ gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), add_file_open_extra_widget(dialog)); filetype_combo = ui_lookup_widget(dialog, "filetype_combo"); gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(filetype_combo), _("Detect by file extension")); /* add FileFilters(start with "All Files") */ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filetypes_create_file_filter(filetypes[GEANY_FILETYPES_NONE])); /* now create meta filter "All Source" */ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filetypes_create_file_filter_all_source()); foreach_slist(node, filetypes_by_title) { GeanyFiletype *ft = node->data; if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE)) continue; gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(filetype_combo), ft->title); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filetypes_create_file_filter(ft)); } gtk_combo_box_set_active(GTK_COMBO_BOX(filetype_combo), 0); /* fill encoding combo box */ encoding_combo = ui_lookup_widget(dialog, "encoding_combo"); gtk_combo_box_set_model(GTK_COMBO_BOX(encoding_combo), GTK_TREE_MODEL( create_encoding_combo_store(&encoding_iter))); gtk_combo_box_set_active_iter(GTK_COMBO_BOX(encoding_combo), &encoding_iter); encoding_renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(encoding_combo), encoding_renderer, TRUE); gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(encoding_combo), encoding_renderer, "text", 1); gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(encoding_combo), encoding_renderer, encoding_combo_cell_data_func, NULL, NULL); g_signal_connect(dialog, "notify::show-hidden", G_CALLBACK(on_file_open_show_hidden_notify), NULL); return dialog; } static void open_file_dialog_apply_settings(GtkWidget *dialog) { static gboolean initialized = FALSE; GtkWidget *check_hidden = ui_lookup_widget(dialog, "check_hidden"); GtkWidget *filetype_combo = ui_lookup_widget(dialog, "filetype_combo"); GtkWidget *expander = ui_lookup_widget(dialog, "more_options_expander"); /* we can't know the initial position of combo boxes, so retreive it the first time */ if (! initialized) { filesel_state.open.filter_idx = file_chooser_get_filter_idx(GTK_FILE_CHOOSER(dialog)); filesel_state.open.filetype_idx = gtk_combo_box_get_active(GTK_COMBO_BOX(filetype_combo)); initialized = TRUE; } else { file_chooser_set_filter_idx(GTK_FILE_CHOOSER(dialog), filesel_state.open.filter_idx); gtk_combo_box_set_active(GTK_COMBO_BOX(filetype_combo), filesel_state.open.filetype_idx); } gtk_expander_set_expanded(GTK_EXPANDER(expander), filesel_state.open.more_options_visible); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_hidden), filesel_state.open.show_hidden); /* encoding combo is restored at creating time, see create_encoding_combo_store() */ } /* This shows the file selection dialog to open a file. */ void dialogs_show_open_file(void) { gchar *initdir; /* set dialog directory to the current file's directory, if present */ initdir = utils_get_current_file_dir_utf8(); /* use project or default startup directory (if set) if no files are open */ /** TODO should it only be used when initally open the dialog and not on every show? */ if (! initdir) initdir = g_strdup(utils_get_default_dir_utf8()); SETPTR(initdir, utils_get_locale_from_utf8(initdir)); #ifdef G_OS_WIN32 if (interface_prefs.use_native_windows_dialogs) win32_show_document_open_dialog(GTK_WINDOW(main_widgets.window), _("Open File"), initdir); else #endif { GtkWidget *dialog = create_open_file_dialog(); gint response; open_file_dialog_apply_settings(dialog); if (initdir != NULL && g_path_is_absolute(initdir)) gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), initdir); if (app->project && !EMPTY(app->project->base_path)) gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), app->project->base_path, NULL); response = gtk_dialog_run(GTK_DIALOG(dialog)); open_file_dialog_handle_response(dialog, response); gtk_widget_destroy(dialog); } g_free(initdir); } static gboolean handle_save_as(const gchar *utf8_filename, gboolean rename_file) { GeanyDocument *doc = document_get_current(); gboolean success = FALSE; g_return_val_if_fail(!EMPTY(utf8_filename), FALSE); if (doc->file_name != NULL) { if (rename_file) { document_rename_file(doc, utf8_filename); } /* create a new tm_source_file object otherwise tagmanager won't work correctly */ tm_workspace_remove_object(doc->tm_file, TRUE, TRUE); doc->tm_file = NULL; } success = document_save_file_as(doc, utf8_filename); build_menu_update(doc); return success; } static gboolean save_as_dialog_handle_response(GtkWidget *dialog, gint response) { gboolean rename_file = FALSE; gboolean success = FALSE; gchar *new_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); switch (response) { case GEANY_RESPONSE_RENAME: /* rename doesn't check for empty filename or overwriting */ if (G_UNLIKELY(EMPTY(new_filename))) { utils_beep(); break; } if (g_file_test(new_filename, G_FILE_TEST_EXISTS) && !dialogs_show_question_full(NULL, NULL, NULL, _("Overwrite?"), _("Filename already exists!"))) break; rename_file = TRUE; /* fall through */ case GTK_RESPONSE_ACCEPT: { gchar *utf8_filename; utf8_filename = utils_get_utf8_from_locale(new_filename); success = handle_save_as(utf8_filename, rename_file); g_free(utf8_filename); break; } case GTK_RESPONSE_DELETE_EVENT: case GTK_RESPONSE_CANCEL: success = TRUE; break; } g_free(new_filename); return success; } static GtkWidget *create_save_file_dialog(GeanyDocument *doc) { GtkWidget *dialog, *rename_btn; const gchar *initdir; dialog = gtk_file_chooser_dialog_new(_("Save File"), GTK_WINDOW(main_widgets.window), GTK_FILE_CHOOSER_ACTION_SAVE, NULL, NULL); gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE); gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE); gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main_widgets.window)); gtk_widget_set_name(dialog, "GeanyDialog"); rename_btn = gtk_dialog_add_button(GTK_DIALOG(dialog), _("R_ename"), GEANY_RESPONSE_RENAME); gtk_widget_set_tooltip_text(rename_btn, _("Save the file and rename it")); /* disable rename unless file exists on disk */ gtk_widget_set_sensitive(rename_btn, doc->real_path != NULL); gtk_dialog_add_buttons(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE); /* set the folder by default to the project base dir or the global pref for opening files */ initdir = utils_get_default_dir_utf8(); if (initdir) { gchar *linitdir = utils_get_locale_from_utf8(initdir); gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), linitdir); g_free(linitdir); } return dialog; } static gboolean show_save_as_gtk(GeanyDocument *doc) { GtkWidget *dialog; gint resp; g_return_val_if_fail(doc != NULL, FALSE); dialog = create_save_file_dialog(doc); if (doc->file_name != NULL) { if (g_path_is_absolute(doc->file_name)) { gchar *locale_filename = utils_get_locale_from_utf8(doc->file_name); gchar *locale_basename = g_path_get_basename(locale_filename); gchar *locale_dirname = g_path_get_dirname(locale_filename); gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_dirname); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), locale_basename); g_free(locale_filename); g_free(locale_basename); g_free(locale_dirname); } else gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), doc->file_name); } else { gchar *fname = NULL; if (doc->file_type != NULL && doc->file_type->extension != NULL) fname = g_strconcat(GEANY_STRING_UNTITLED, ".", doc->file_type->extension, NULL); else fname = g_strdup(GEANY_STRING_UNTITLED); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), fname); g_free(fname); } if (app->project && !EMPTY(app->project->base_path)) gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), app->project->base_path, NULL); /* Run the dialog synchronously, pausing this function call */ do { resp = gtk_dialog_run(GTK_DIALOG(dialog)); } while (! save_as_dialog_handle_response(dialog, resp)); if (app->project && !EMPTY(app->project->base_path)) gtk_file_chooser_remove_shortcut_folder(GTK_FILE_CHOOSER(dialog), app->project->base_path, NULL); gtk_widget_destroy(dialog); return (resp == GTK_RESPONSE_ACCEPT); } /** * Shows the Save As dialog for the current notebook page. * * @return @c TRUE if the file was saved, otherwise @c FALSE. **/ gboolean dialogs_show_save_as() { GeanyDocument *doc = document_get_current(); gboolean result = FALSE; g_return_val_if_fail(doc, FALSE); #ifdef G_OS_WIN32 if (interface_prefs.use_native_windows_dialogs) { gchar *utf8_name = win32_show_document_save_as_dialog(GTK_WINDOW(main_widgets.window), _("Save File"), DOC_FILENAME(doc)); if (utf8_name != NULL) result = handle_save_as(utf8_name, FALSE); } else #endif result = show_save_as_gtk(doc); return result; } #ifndef G_OS_WIN32 static void show_msgbox_dialog(GtkWidget *dialog, GtkMessageType type, GtkWindow *parent) { const gchar *title; switch (type) { case GTK_MESSAGE_ERROR: title = _("Error"); break; case GTK_MESSAGE_QUESTION: title = _("Question"); break; case GTK_MESSAGE_WARNING: title = _("Warning"); break; default: title = _("Information"); break; } gtk_window_set_title(GTK_WINDOW(dialog), title); gtk_window_set_icon_name(GTK_WINDOW(dialog), "geany"); gtk_widget_set_name(dialog, "GeanyDialog"); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } #endif /** * Shows a message box of the type @a type with @a text. * On Unix-like systems a GTK message dialog box is shown, on Win32 systems a native Windows * message dialog box is shown. * * @param type A @c GtkMessageType, e.g. @c GTK_MESSAGE_INFO, @c GTK_MESSAGE_WARNING, * @c GTK_MESSAGE_QUESTION, @c GTK_MESSAGE_ERROR. * @param text Printf()-style format string. * @param ... Arguments for the @a text format string. **/ void dialogs_show_msgbox(GtkMessageType type, const gchar *text, ...) { #ifndef G_OS_WIN32 GtkWidget *dialog; #endif gchar *string; va_list args; GtkWindow *parent = (main_status.main_window_realized) ? GTK_WINDOW(main_widgets.window) : NULL; va_start(args, text); string = g_strdup_vprintf(text, args); va_end(args); #ifdef G_OS_WIN32 win32_message_dialog(GTK_WIDGET(parent), type, string); #else dialog = gtk_message_dialog_new(parent, GTK_DIALOG_DESTROY_WITH_PARENT, type, GTK_BUTTONS_OK, "%s", string); show_msgbox_dialog(dialog, type, parent); #endif g_free(string); } void dialogs_show_msgbox_with_secondary(GtkMessageType type, const gchar *text, const gchar *secondary) { GtkWindow *parent = (main_status.main_window_realized) ? GTK_WINDOW(main_widgets.window) : NULL; #ifdef G_OS_WIN32 /* put the two strings together because Windows message boxes don't support secondary texts */ gchar *string = g_strconcat(text, "\n", secondary, NULL); win32_message_dialog(GTK_WIDGET(parent), type, string); g_free(string); #else GtkWidget *dialog; dialog = gtk_message_dialog_new(parent, GTK_DIALOG_DESTROY_WITH_PARENT, type, GTK_BUTTONS_OK, "%s", text); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary); show_msgbox_dialog(dialog, type, parent); #endif } static gint run_unsaved_dialog(const gchar *msg, const gchar *msg2) { GtkWidget *dialog, *button; gint ret; dialog = gtk_message_dialog_new(GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", msg); gtk_window_set_title(GTK_WINDOW(dialog), _("Question")); gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", msg2); gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); button = ui_button_new_with_image(GTK_STOCK_CLEAR, _("_Don't save")); gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, GTK_RESPONSE_NO); gtk_widget_show(button); gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_SAVE, GTK_RESPONSE_YES); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES); ret = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return ret; } gboolean dialogs_show_unsaved_file(GeanyDocument *doc) { gchar *msg, *short_fn = NULL; const gchar *msg2; gint response; gboolean old_quitting_state = main_status.quitting; /* display the file tab to remind the user of the document */ main_status.quitting = FALSE; document_show_tab(doc); main_status.quitting = old_quitting_state; short_fn = document_get_basename_for_display(doc, -1); msg = g_strdup_printf(_("The file '%s' is not saved."), short_fn); msg2 = _("Do you want to save it before closing?"); g_free(short_fn); response = run_unsaved_dialog(msg, msg2); g_free(msg); switch (response) { case GTK_RESPONSE_YES: /* document_save_file() returns the status if the file could be saved */ return document_save_file(doc, FALSE); case GTK_RESPONSE_NO: return TRUE; case GTK_RESPONSE_CANCEL: /* fall through to default and leave the function */ default: return FALSE; } } #ifndef G_OS_WIN32 /* Use GtkFontChooserDialog on GTK3.2 for consistency, and because * GtkFontSelectionDialog is somewhat broken on 3.4 */ #if GTK_CHECK_VERSION(3, 2, 0) # undef GTK_FONT_SELECTION_DIALOG # define GTK_FONT_SELECTION_DIALOG GTK_FONT_CHOOSER_DIALOG # define gtk_font_selection_dialog_new(title) \ gtk_font_chooser_dialog_new((title), NULL) # define gtk_font_selection_dialog_get_font_name(dlg) \ gtk_font_chooser_get_font(GTK_FONT_CHOOSER(dlg)) # define gtk_font_selection_dialog_set_font_name(dlg, font) \ gtk_font_chooser_set_font(GTK_FONT_CHOOSER(dlg), (font)) #endif static void on_font_dialog_response(GtkDialog *dialog, gint response, gpointer user_data) { gboolean close = TRUE; switch (response) { case GTK_RESPONSE_APPLY: case GTK_RESPONSE_OK: { gchar *fontname; fontname = gtk_font_selection_dialog_get_font_name( GTK_FONT_SELECTION_DIALOG(ui_widgets.open_fontsel)); ui_set_editor_font(fontname); g_free(fontname); close = (response == GTK_RESPONSE_OK); break; } } if (close) gtk_widget_hide(ui_widgets.open_fontsel); } #endif /* This shows the font selection dialog to choose a font. */ void dialogs_show_open_font() { #ifdef G_OS_WIN32 win32_show_font_dialog(); #else if (ui_widgets.open_fontsel == NULL) { GtkWidget *apply_button; ui_widgets.open_fontsel = gtk_font_selection_dialog_new(_("Choose font"));; gtk_container_set_border_width(GTK_CONTAINER(ui_widgets.open_fontsel), 4); gtk_window_set_modal(GTK_WINDOW(ui_widgets.open_fontsel), TRUE); gtk_window_set_destroy_with_parent(GTK_WINDOW(ui_widgets.open_fontsel), TRUE); gtk_window_set_skip_taskbar_hint(GTK_WINDOW(ui_widgets.open_fontsel), TRUE); gtk_window_set_type_hint(GTK_WINDOW(ui_widgets.open_fontsel), GDK_WINDOW_TYPE_HINT_DIALOG); gtk_widget_set_name(ui_widgets.open_fontsel, "GeanyDialog"); #if GTK_CHECK_VERSION(2, 20, 0) /* apply button doesn't have a getter and is hidden by default, but we'd like to show it */ apply_button = gtk_dialog_get_widget_for_response(GTK_DIALOG(ui_widgets.open_fontsel), GTK_RESPONSE_APPLY); #else apply_button = GTK_FONT_SELECTION_DIALOG(ui_widgets.open_fontsel)->apply_button; #endif if (apply_button) gtk_widget_show(apply_button); g_signal_connect(ui_widgets.open_fontsel, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); g_signal_connect(ui_widgets.open_fontsel, "response", G_CALLBACK(on_font_dialog_response), NULL); gtk_window_set_transient_for(GTK_WINDOW(ui_widgets.open_fontsel), GTK_WINDOW(main_widgets.window)); } gtk_font_selection_dialog_set_font_name( GTK_FONT_SELECTION_DIALOG(ui_widgets.open_fontsel), interface_prefs.editor_font); /* We make sure the dialog is visible. */ gtk_window_present(GTK_WINDOW(ui_widgets.open_fontsel)); #endif } static void on_input_dialog_show(GtkDialog *dialog, GtkWidget *entry) { gtk_widget_grab_focus(entry); } static void on_input_entry_activate(GtkEntry *entry, GtkDialog *dialog) { gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT); } static void on_input_numeric_activate(GtkEntry *entry, GtkDialog *dialog) { gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT); } static void on_input_dialog_response(GtkDialog *dialog, gint response, GtkWidget *entry) { gboolean persistent = (gboolean) GPOINTER_TO_INT(g_object_get_data(G_OBJECT(dialog), "has_combo")); if (response == GTK_RESPONSE_ACCEPT) { const gchar *str = gtk_entry_get_text(GTK_ENTRY(entry)); GeanyInputCallback input_cb = (GeanyInputCallback) g_object_get_data(G_OBJECT(dialog), "input_cb"); if (persistent) { GtkWidget *combo = (GtkWidget *) g_object_get_data(G_OBJECT(dialog), "combo"); ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(combo), str, 0); } input_cb(str); } gtk_widget_hide(GTK_WIDGET(dialog)); } static void add_input_widgets(GtkWidget *dialog, GtkWidget *vbox, const gchar *label_text, const gchar *default_text, gboolean persistent, GCallback insert_text_cb) { GtkWidget *entry; if (label_text) { GtkWidget *label = gtk_label_new(label_text); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_container_add(GTK_CONTAINER(vbox), label); } if (persistent) /* remember previous entry text in a combo box */ { GtkWidget *combo = gtk_combo_box_text_new_with_entry(); entry = gtk_bin_get_child(GTK_BIN(combo)); ui_entry_add_clear_icon(GTK_ENTRY(entry)); g_object_set_data(G_OBJECT(dialog), "combo", combo); gtk_container_add(GTK_CONTAINER(vbox), combo); } else { entry = gtk_entry_new(); ui_entry_add_clear_icon(GTK_ENTRY(entry)); gtk_container_add(GTK_CONTAINER(vbox), entry); } if (default_text != NULL) { gtk_entry_set_text(GTK_ENTRY(entry), default_text); } gtk_entry_set_max_length(GTK_ENTRY(entry), 255); gtk_entry_set_width_chars(GTK_ENTRY(entry), 30); if (insert_text_cb != NULL) g_signal_connect(entry, "insert-text", insert_text_cb, NULL); g_signal_connect(entry, "activate", G_CALLBACK(on_input_entry_activate), dialog); g_signal_connect(dialog, "show", G_CALLBACK(on_input_dialog_show), entry); g_signal_connect(dialog, "response", G_CALLBACK(on_input_dialog_response), entry); } /* Create and display an input dialog. * persistent: whether to remember previous entry text in a combo box; * in this case the dialog returned is not destroyed on a response, * and can be reshown. * Returns: the dialog widget. */ static GtkWidget * dialogs_show_input_full(const gchar *title, GtkWindow *parent, const gchar *label_text, const gchar *default_text, gboolean persistent, GeanyInputCallback input_cb, GCallback insert_text_cb) { GtkWidget *dialog, *vbox; dialog = gtk_dialog_new_with_buttons(title, parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog)); gtk_widget_set_name(dialog, "GeanyDialog"); gtk_box_set_spacing(GTK_BOX(vbox), 6); g_object_set_data(G_OBJECT(dialog), "has_combo", GINT_TO_POINTER(persistent)); g_object_set_data(G_OBJECT(dialog), "input_cb", (gpointer) input_cb); add_input_widgets(dialog, vbox, label_text, default_text, persistent, insert_text_cb); if (persistent) { /* override default handler */ g_signal_connect(dialog, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); gtk_widget_show_all(dialog); return dialog; } gtk_widget_show_all(dialog); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return NULL; } /* Remember previous entry text in a combo box. * Returns: the dialog widget. */ GtkWidget * dialogs_show_input_persistent(const gchar *title, GtkWindow *parent, const gchar *label_text, const gchar *default_text, GeanyInputCallback input_cb) { return dialogs_show_input_full(title, parent, label_text, default_text, TRUE, input_cb, NULL); } /* ugly hack - user_data not supported for callback */ static gchar *dialog_input = NULL; static void on_dialog_input(const gchar *str) { dialog_input = g_strdup(str); } /** Asks the user for text input. * @param title Dialog title. * @param parent The currently focused window, usually @c geany->main_widgets->window. * @c NULL can be used but is discouraged due to window manager effects. * @param label_text Label text, or @c NULL. * @param default_text Text to display in the input field, or @c NULL. * @return New copy of user input or @c NULL if cancelled. * @since 0.20. */ gchar *dialogs_show_input(const gchar *title, GtkWindow *parent, const gchar *label_text, const gchar *default_text) { dialog_input = NULL; dialogs_show_input_full(title, parent, label_text, default_text, FALSE, on_dialog_input, NULL); return dialog_input; } /* Note: could be changed to dialogs_show_validated_input with argument for callback. */ /* Returns: newly allocated copy of the entry text or NULL on cancel. * Specialised variant for Goto Line dialog. */ gchar *dialogs_show_input_goto_line(const gchar *title, GtkWindow *parent, const gchar *label_text, const gchar *default_text) { dialog_input = NULL; dialogs_show_input_full( title, parent, label_text, default_text, FALSE, on_dialog_input, G_CALLBACK(ui_editable_insert_text_callback)); return dialog_input; } /** * Shows an input box to enter a numerical value using a GtkSpinButton. * If the dialog is aborted, @a value remains untouched. * * @param title The dialog title. * @param label_text The shown dialog label. * @param value The default value for the spin button and the return location of the entered value. * Must be non-NULL. * @param min Minimum allowable value (see documentation for @c gtk_spin_button_new_with_range()). * @param max Maximum allowable value (see documentation for @c gtk_spin_button_new_with_range()). * @param step Increment added or subtracted by spinning the widget * (see documentation for @c gtk_spin_button_new_with_range()). * * @return @c TRUE if a value was entered and the dialog closed with 'OK'. @c FALSE otherwise. * * @since 0.16 **/ gboolean dialogs_show_input_numeric(const gchar *title, const gchar *label_text, gdouble *value, gdouble min, gdouble max, gdouble step) { GtkWidget *dialog, *label, *spin, *vbox; gboolean res = FALSE; g_return_val_if_fail(title != NULL, FALSE); g_return_val_if_fail(label_text != NULL, FALSE); g_return_val_if_fail(value != NULL, FALSE); dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL); vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog)); gtk_widget_set_name(dialog, "GeanyDialog"); label = gtk_label_new(label_text); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); spin = gtk_spin_button_new_with_range(min, max, step); ui_entry_add_clear_icon(GTK_ENTRY(spin)); gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), *value); g_signal_connect(spin, "activate", G_CALLBACK(on_input_numeric_activate), dialog); gtk_container_add(GTK_CONTAINER(vbox), label); gtk_container_add(GTK_CONTAINER(vbox), spin); gtk_widget_show_all(vbox); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { *value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin)); res = TRUE; } gtk_widget_destroy(dialog); return res; } void dialogs_show_file_properties(GeanyDocument *doc) { GtkWidget *dialog, *label, *image, *check; gchar *file_size, *title, *base_name, *time_changed, *time_modified, *time_accessed, *enctext; gchar *short_name; GdkPixbuf *pixbuf; #ifdef HAVE_SYS_TYPES_H struct stat st; off_t filesize; mode_t mode; gchar *locale_filename; #else gint filesize = 0; gint mode = 0; #endif /* define this ones, to avoid later trouble */ #ifndef S_IRUSR # define S_IRUSR 0 # define S_IWUSR 0 # define S_IXUSR 0 #endif #ifndef S_IRGRP # define S_IRGRP 0 # define S_IWGRP 0 # define S_IXGRP 0 # define S_IROTH 0 # define S_IWOTH 0 # define S_IXOTH 0 #endif if (doc == NULL || doc->file_name == NULL) { dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("An error occurred or file information could not be retrieved (e.g. from a new file).")); return; } #ifdef HAVE_SYS_TYPES_H locale_filename = utils_get_locale_from_utf8(doc->file_name); if (g_stat(locale_filename, &st) == 0) { /* first copy the returned string and the trim it, to not modify the static glibc string * g_strchomp() is used to remove trailing EOL chars, which are there for whatever reason */ time_changed = g_strchomp(g_strdup(ctime(&st.st_ctime))); time_modified = g_strchomp(g_strdup(ctime(&st.st_mtime))); time_accessed = g_strchomp(g_strdup(ctime(&st.st_atime))); filesize = st.st_size; mode = st.st_mode; } else { time_changed = g_strdup(_("unknown")); time_modified = g_strdup(_("unknown")); time_accessed = g_strdup(_("unknown")); filesize = (off_t) 0; mode = (mode_t) 0; } g_free(locale_filename); #else time_changed = g_strdup(_("unknown")); time_modified = g_strdup(_("unknown")); time_accessed = g_strdup(_("unknown")); #endif base_name = g_path_get_basename(doc->file_name); short_name = utils_str_middle_truncate(base_name, 30); title = g_strdup_printf(_("%s Properties"), short_name); dialog = ui_builder_get_object("properties_dialog"); gtk_window_set_title(GTK_WINDOW(dialog), title); g_free(short_name); g_free(title); gtk_widget_set_name(dialog, "GeanyDialog"); label = ui_lookup_widget(dialog, "file_name_label"); gtk_label_set_text(GTK_LABEL(label), base_name); image = ui_lookup_widget(dialog, "file_type_image"); pixbuf = ui_get_mime_icon(doc->file_type->mime_type, GTK_ICON_SIZE_BUTTON); gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf); g_object_unref(pixbuf); label = ui_lookup_widget(dialog, "file_type_label"); gtk_label_set_text(GTK_LABEL(label), doc->file_type->title); label = ui_lookup_widget(dialog, "file_size_label"); file_size = utils_make_human_readable_str(filesize, 1, 0); gtk_label_set_text(GTK_LABEL(label), file_size); g_free(file_size); label = ui_lookup_widget(dialog, "file_location_label"); gtk_label_set_text(GTK_LABEL(label), doc->file_name); check = ui_lookup_widget(dialog, "file_read_only_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), doc->readonly); label = ui_lookup_widget(dialog, "file_encoding_label"); enctext = g_strdup_printf("%s %s", doc->encoding, (encodings_is_unicode_charset(doc->encoding)) ? ((doc->has_bom) ? _("(with BOM)") : _("(without BOM)")) : ""); gtk_label_set_text(GTK_LABEL(label), enctext); g_free(enctext); label = ui_lookup_widget(dialog, "file_modified_label"); gtk_label_set_text(GTK_LABEL(label), time_modified); label = ui_lookup_widget(dialog, "file_changed_label"); gtk_label_set_text(GTK_LABEL(label), time_changed); label = ui_lookup_widget(dialog, "file_accessed_label"); gtk_label_set_text(GTK_LABEL(label), time_accessed); /* permissions */ check = ui_lookup_widget(dialog, "file_perm_owner_r_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IRUSR); check = ui_lookup_widget(dialog, "file_perm_owner_w_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWUSR); check = ui_lookup_widget(dialog, "file_perm_owner_x_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXUSR); check = ui_lookup_widget(dialog, "file_perm_group_r_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IRGRP); check = ui_lookup_widget(dialog, "file_perm_group_w_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWGRP); check = ui_lookup_widget(dialog, "file_perm_group_x_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXGRP); check = ui_lookup_widget(dialog, "file_perm_other_r_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IROTH); check = ui_lookup_widget(dialog, "file_perm_other_w_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWOTH); check = ui_lookup_widget(dialog, "file_perm_other_x_check"); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXOTH); g_free(base_name); g_free(time_changed); g_free(time_modified); g_free(time_accessed); gtk_widget_show(dialog); } /* extra_text can be NULL; otherwise it is displayed below main_text. * if parent is NULL, main_widgets.window will be used * btn_1, btn_2, btn_3 can be NULL. * returns response_1, response_2 or response_3 */ static gint show_prompt(GtkWidget *parent, const gchar *btn_1, GtkResponseType response_1, const gchar *btn_2, GtkResponseType response_2, const gchar *btn_3, GtkResponseType response_3, const gchar *question_text, const gchar *extra_text) { gboolean ret = FALSE; GtkWidget *dialog; GtkWidget *btn; if (btn_2 == NULL) { btn_2 = GTK_STOCK_NO; response_2 = GTK_RESPONSE_NO; } if (btn_3 == NULL) { btn_3 = GTK_STOCK_YES; response_3 = GTK_RESPONSE_YES; } #ifdef G_OS_WIN32 /* our native dialog code doesn't support custom buttons */ if (utils_str_equal(btn_3, GTK_STOCK_YES) && utils_str_equal(btn_2, GTK_STOCK_NO) && btn_1 == NULL) { gchar *string = (extra_text == NULL) ? g_strdup(question_text) : g_strconcat(question_text, "\n\n", extra_text, NULL); ret = win32_message_dialog(parent, GTK_MESSAGE_QUESTION, string); ret = ret ? response_3 : response_2; g_free(string); return ret; } #endif if (parent == NULL && main_status.main_window_realized) parent = main_widgets.window; dialog = gtk_message_dialog_new(GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", question_text); gtk_widget_set_name(dialog, "GeanyDialog"); gtk_window_set_title(GTK_WINDOW(dialog), _("Question")); gtk_window_set_icon_name(GTK_WINDOW(dialog), "geany"); /* question_text will be in bold if optional extra_text used */ if (extra_text != NULL) gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", extra_text); if (btn_1 != NULL) gtk_dialog_add_button(GTK_DIALOG(dialog), btn_1, response_1); /* For a cancel button, use cancel response so user can press escape to cancel */ btn = gtk_dialog_add_button(GTK_DIALOG(dialog), btn_2, utils_str_equal(btn_2, GTK_STOCK_CANCEL) ? GTK_RESPONSE_CANCEL : response_2); /* we don't want a default, but we need to override the apply button as default */ gtk_widget_grab_default(btn); gtk_dialog_add_button(GTK_DIALOG(dialog), btn_3, response_3); ret = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if (ret == GTK_RESPONSE_CANCEL) ret = response_2; return ret; } /** * Shows a question message box with @a text and Yes/No buttons. * On Unix-like systems a GTK message dialog box is shown, on Win32 systems a native Windows * message dialog box is shown. * * @param text Printf()-style format string. * @param ... Arguments for the @a text format string. * * @return @c TRUE if the user answered with Yes, otherwise @c FALSE. **/ gboolean dialogs_show_question(const gchar *text, ...) { gchar *string; va_list args; GtkWidget *parent = (main_status.main_window_realized) ? main_widgets.window : NULL; gint result; va_start(args, text); string = g_strdup_vprintf(text, args); va_end(args); result = show_prompt(parent, NULL, GTK_RESPONSE_NONE, GTK_STOCK_NO, GTK_RESPONSE_NO, GTK_STOCK_YES, GTK_RESPONSE_YES, string, NULL); g_free(string); return (result == GTK_RESPONSE_YES); } /* extra_text can be NULL; otherwise it is displayed below main_text. * if parent is NULL, main_widgets.window will be used * yes_btn, no_btn can be NULL. */ gboolean dialogs_show_question_full(GtkWidget *parent, const gchar *yes_btn, const gchar *no_btn, const gchar *extra_text, const gchar *main_text, ...) { gint result; gchar *string; va_list args; va_start(args, main_text); string = g_strdup_vprintf(main_text, args); va_end(args); result = show_prompt(parent, NULL, GTK_RESPONSE_NONE, no_btn, GTK_RESPONSE_NO, yes_btn, GTK_RESPONSE_YES, string, extra_text); g_free(string); return (result == GTK_RESPONSE_YES); } /* extra_text can be NULL; otherwise it is displayed below main_text. * if parent is NULL, main_widgets.window will be used * btn_1, btn_2, btn_3 can be NULL. * returns response_1, response_2 or response_3 */ gint dialogs_show_prompt(GtkWidget *parent, const gchar *btn_1, GtkResponseType response_1, const gchar *btn_2, GtkResponseType response_2, const gchar *btn_3, GtkResponseType response_3, const gchar *extra_text, const gchar *main_text, ...) { gchar *string; va_list args; gint result; va_start(args, main_text); string = g_strdup_vprintf(main_text, args); va_end(args); result = show_prompt(parent, btn_1, response_1, btn_2, response_2, btn_3, response_3, string, extra_text); g_free(string); return result; }