geany/src/dialogs.c

1436 lines
44 KiB
C
Raw Normal View History

/*
* dialogs.c - this file is part of Geany, a fast and lightweight IDE
*
2012-06-18 01:13:05 +02:00
* Copyright 2005-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
* Copyright 2006-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
*
* 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.
*
2012-08-24 19:25:57 +02:00
* 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 what you use This is a mega-commit - because most of it had to be done in one go otherwise some commits would fail to compile - that attempts to fix a few problems with Geany's includes as well as various other related cleanups. After this change it's easier to use includes and there's little worry about which order things are included in or who includes what. Overview of changes: * Include config.h at the start of each source file if HAVE_CONFIG_H is defined (and never in headers). * Go through each source file and make the includes section generally like this: - Always config.h first as above - Then if the file has a header with the same name, include that - Then include in alphabetical order each other internal/geany header. - Then include standard headers - Then include non-standard system headers - Then include GLib/GTK+ related stuff * Doing as above makes it easier to find implicit header include dependencies and it exposed quite a few weird problems with includes or forward declarations, fix those. * Make geany.h contain not much besides some defines. - Add a little header file "app.h" for GeanyApp and move it there - Move "app" global to new "app.h" file - Move "ignore_callback" global to "callbacks.h" - Move "geany_object" global to "geanyobject.h" * Add an include in "geany.h" for "app.h" since GeanyApp used to be defined there and some plugins included this header to access GeanyApp. * Include "gtkcompat.h" everywhere instead of gtk/gtk.h so that everywhere sees the same definitions (not a problem in practice AFAIK so this could be changed back if better that way. * Remove forward declarations from previous commits as some people apparently consider this bad style, despite that it reduces inter- header dependencies. TODO: * As always, to test on win32 * As always, to test with not Autotools * Test plugins better, both builtin and geany-plugins, likely API/ABI bump * Test with various defines/flags that may change what is included * win32.[ch] not really touched since I couldn't test
2014-05-18 17:31:51 -07:00
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "dialogs.h"
#include "app.h"
#include "build.h"
#include "document.h"
#include "encodings.h"
#include "encodingsprivate.h"
Include what you use This is a mega-commit - because most of it had to be done in one go otherwise some commits would fail to compile - that attempts to fix a few problems with Geany's includes as well as various other related cleanups. After this change it's easier to use includes and there's little worry about which order things are included in or who includes what. Overview of changes: * Include config.h at the start of each source file if HAVE_CONFIG_H is defined (and never in headers). * Go through each source file and make the includes section generally like this: - Always config.h first as above - Then if the file has a header with the same name, include that - Then include in alphabetical order each other internal/geany header. - Then include standard headers - Then include non-standard system headers - Then include GLib/GTK+ related stuff * Doing as above makes it easier to find implicit header include dependencies and it exposed quite a few weird problems with includes or forward declarations, fix those. * Make geany.h contain not much besides some defines. - Add a little header file "app.h" for GeanyApp and move it there - Move "app" global to new "app.h" file - Move "ignore_callback" global to "callbacks.h" - Move "geany_object" global to "geanyobject.h" * Add an include in "geany.h" for "app.h" since GeanyApp used to be defined there and some plugins included this header to access GeanyApp. * Include "gtkcompat.h" everywhere instead of gtk/gtk.h so that everywhere sees the same definitions (not a problem in practice AFAIK so this could be changed back if better that way. * Remove forward declarations from previous commits as some people apparently consider this bad style, despite that it reduces inter- header dependencies. TODO: * As always, to test on win32 * As always, to test with not Autotools * Test plugins better, both builtin and geany-plugins, likely API/ABI bump * Test with various defines/flags that may change what is included * win32.[ch] not really touched since I couldn't test
2014-05-18 17:31:51 -07:00
#include "filetypes.h"
#include "main.h"
#include "support.h"
#include "utils.h"
#include "ui_utils.h"
2014-08-01 12:00:20 +01:00
#include "win32.h"
Include what you use This is a mega-commit - because most of it had to be done in one go otherwise some commits would fail to compile - that attempts to fix a few problems with Geany's includes as well as various other related cleanups. After this change it's easier to use includes and there's little worry about which order things are included in or who includes what. Overview of changes: * Include config.h at the start of each source file if HAVE_CONFIG_H is defined (and never in headers). * Go through each source file and make the includes section generally like this: - Always config.h first as above - Then if the file has a header with the same name, include that - Then include in alphabetical order each other internal/geany header. - Then include standard headers - Then include non-standard system headers - Then include GLib/GTK+ related stuff * Doing as above makes it easier to find implicit header include dependencies and it exposed quite a few weird problems with includes or forward declarations, fix those. * Make geany.h contain not much besides some defines. - Add a little header file "app.h" for GeanyApp and move it there - Move "app" global to new "app.h" file - Move "ignore_callback" global to "callbacks.h" - Move "geany_object" global to "geanyobject.h" * Add an include in "geany.h" for "app.h" since GeanyApp used to be defined there and some plugins included this header to access GeanyApp. * Include "gtkcompat.h" everywhere instead of gtk/gtk.h so that everywhere sees the same definitions (not a problem in practice AFAIK so this could be changed back if better that way. * Remove forward declarations from previous commits as some people apparently consider this bad style, despite that it reduces inter- header dependencies. TODO: * As always, to test on win32 * As always, to test with not Autotools * Test plugins better, both builtin and geany-plugins, likely API/ABI bump * Test with various defines/flags that may change what is included * win32.[ch] not really touched since I couldn't test
2014-05-18 17:31:51 -07:00
#include "gtkcompat.h"
#include <gdk/gdkkeysyms.h>
#include <string.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#include <time.h>
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
/* gstdio.h also includes sys/stat.h */
#include <glib/gstdio.h>
enum
{
GEANY_RESPONSE_RENAME,
GEANY_RESPONSE_VIEW
};
2011-06-10 22:13:29 +00:00
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 */
-1, /* default filetype is detect from extension */
FALSE,
FALSE
}
2011-06-10 22:13:29 +00:00
};
static gint filetype_combo_box_get_active_filetype(GtkComboBox *combo);
2011-06-10 22:13:29 +00:00
/* 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;
}
2011-06-10 22:13:29 +00:00
/* sets the current file filter from its ID */
static void file_chooser_set_filter_idx(GtkFileChooser *chooser, guint idx)
{
2011-06-10 22:13:29 +00:00
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 gboolean open_file_dialog_handle_response(GtkWidget *dialog, gint response)
2011-06-10 22:13:29 +00:00
{
gboolean ret = TRUE;
if (response == GTK_RESPONSE_ACCEPT || response == GEANY_RESPONSE_VIEW)
{
GSList *filelist;
GeanyFiletype *ft = NULL;
const gchar *charset = NULL;
2011-06-10 22:13:29 +00:00
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 */
2011-06-10 22:13:29 +00:00
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 = filetype_combo_box_get_active_filetype(GTK_COMBO_BOX(filetype_combo));
2011-06-10 22:13:29 +00:00
/* ignore detect from file item */
if (filesel_state.open.filetype_idx >= 0)
ft = filetypes_index(filesel_state.open.filetype_idx);
filesel_state.open.encoding_idx = ui_encodings_combo_box_get_active_encoding(GTK_COMBO_BOX(encoding_combo));
if (filesel_state.open.encoding_idx >= 0 && filesel_state.open.encoding_idx < GEANY_ENCODINGS_MAX)
charset = encodings[filesel_state.open.encoding_idx].charset;
2011-06-10 22:13:29 +00:00
filelist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
if (filelist != NULL)
{
const gchar *first = filelist->data;
// When there's only one filename it may have been typed manually
if (!filelist->next && !g_file_test(first, G_FILE_TEST_EXISTS))
{
dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("\"%s\" was not found."), first);
ret = FALSE;
}
else
{
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))
2011-06-10 22:13:29 +00:00
gtk_file_chooser_remove_shortcut_folder(GTK_FILE_CHOOSER(dialog),
app->project->base_path, NULL);
return ret;
}
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
2011-06-10 22:13:29 +00:00
on_file_open_check_hidden_toggled(GtkToggleButton *togglebutton, GtkWidget *dialog)
{
2011-06-10 22:13:29 +00:00
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 void filetype_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);
gchar *text;
gtk_tree_model_get(tree_model, iter, 1, &text, -1);
g_object_set(cell, "sensitive", sensitive, "text", text, NULL);
g_free(text);
}
static GtkWidget *create_filetype_combo_box(void)
{
GtkTreeStore *store;
GtkTreeIter iter_compiled, iter_script, iter_markup, iter_misc, iter_detect;
GtkTreeIter *iter_parent;
GtkWidget *combo;
GtkCellRenderer *renderer;
GSList *node;
store = gtk_tree_store_new(2, G_TYPE_INT, G_TYPE_STRING);
gtk_tree_store_insert_with_values(store, &iter_detect, NULL, -1,
0, -1 /* auto-detect */, 1, _("Detect from file"), -1);
gtk_tree_store_insert_with_values(store, &iter_compiled, NULL, -1,
0, -1, 1, _("Programming Languages"), -1);
gtk_tree_store_insert_with_values(store, &iter_script, NULL, -1,
0, -1, 1, _("Scripting Languages"), -1);
gtk_tree_store_insert_with_values(store, &iter_markup, NULL, -1,
0, -1, 1, _("Markup Languages"), -1);
gtk_tree_store_insert_with_values(store, &iter_misc, NULL, -1,
0, -1, 1, _("Miscellaneous"), -1);
foreach_slist (node, filetypes_by_title)
{
GeanyFiletype *ft = node->data;
switch (ft->group)
{
case GEANY_FILETYPE_GROUP_COMPILED: iter_parent = &iter_compiled; break;
case GEANY_FILETYPE_GROUP_SCRIPT: iter_parent = &iter_script; break;
case GEANY_FILETYPE_GROUP_MARKUP: iter_parent = &iter_markup; break;
case GEANY_FILETYPE_GROUP_MISC: iter_parent = &iter_misc; break;
case GEANY_FILETYPE_GROUP_NONE:
default: iter_parent = NULL;
}
gtk_tree_store_insert_with_values(store, NULL, iter_parent, -1,
0, (gint) ft->id, 1, ft->title, -1);
}
combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter_detect);
renderer = gtk_cell_renderer_text_new();
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer,
filetype_combo_cell_data_func, NULL, NULL);
g_object_unref(store);
return combo;
}
/* the filetype, or -1 for auto-detect */
static gint filetype_combo_box_get_active_filetype(GtkComboBox *combo)
{
gint id = -1;
GtkTreeIter iter;
if (gtk_combo_box_get_active_iter(combo, &iter))
{
GtkTreeModel *model = gtk_combo_box_get_model(combo);
gtk_tree_model_get(model, &iter, 0, &id, -1);
}
return id;
}
static gboolean filetype_combo_box_set_active_filetype(GtkComboBox *combo, const gint id)
{
GtkTreeModel *model = gtk_combo_box_get_model(combo);
GtkTreeIter iter;
if (gtk_tree_model_get_iter_first(model, &iter))
{
do
{
gint row_id;
gtk_tree_model_get(model, &iter, 0, &row_id, -1);
if (id == row_id)
{
gtk_combo_box_set_active_iter(combo, &iter);
return TRUE;
}
}
while (ui_tree_model_iter_any_next(model, &iter, TRUE));
}
return FALSE;
}
2011-06-10 22:13:29 +00:00
static GtkWidget *add_file_open_extra_widget(GtkWidget *dialog)
{
2011-06-10 22:13:29 +00:00
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 = ui_create_encodings_combo_box(TRUE, GEANY_ENCODINGS_MAX);
gtk_widget_set_tooltip_text(encoding_ebox,
2011-06-10 22:13:29 +00:00
_("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 = create_filetype_combo_box();
gtk_widget_set_tooltip_text(filetype_ebox,
2011-06-10 22:13:29 +00:00
_("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 *viewbtn;
GSList *node;
2011-06-10 22:13:29 +00:00
dialog = gtk_file_chooser_dialog_new(_("Open File"), GTK_WINDOW(main_widgets.window),
GTK_FILE_CHOOSER_ACTION_OPEN, NULL, NULL);
2011-06-10 22:13:29 +00:00
gtk_widget_set_name(dialog, "GeanyDialog");
viewbtn = gtk_dialog_add_button(GTK_DIALOG(dialog), C_("Open dialog action", "_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."));
2011-06-10 22:13:29 +00:00
gtk_dialog_add_buttons(GTK_DIALOG(dialog),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
2011-06-10 22:13:29 +00:00
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);
2011-10-28 10:25:58 -07:00
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
/* add checkboxes and filename entry */
2011-06-10 22:13:29 +00:00
gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), add_file_open_extra_widget(dialog));
/* add FileFilters(start with "All Files") */
2011-06-10 22:13:29 +00:00
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
filetypes_create_file_filter(filetypes[GEANY_FILETYPES_NONE]));
/* now create meta filter "All Source" */
2011-06-10 22:13:29 +00:00
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;
2011-06-10 22:13:29 +00:00
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filetypes_create_file_filter(ft));
}
g_signal_connect(dialog, "notify::show-hidden",
G_CALLBACK(on_file_open_show_hidden_notify), NULL);
2011-06-10 22:13:29 +00:00
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 *encoding_combo = ui_lookup_widget(dialog, "encoding_combo");
2011-06-10 22:13:29 +00:00
GtkWidget *expander = ui_lookup_widget(dialog, "more_options_expander");
/* we can't know the initial position of combo boxes, so retrieve it the first time */
2011-06-10 22:13:29 +00:00
if (! initialized)
{
filesel_state.open.filter_idx = file_chooser_get_filter_idx(GTK_FILE_CHOOSER(dialog));
initialized = TRUE;
}
else
{
file_chooser_set_filter_idx(GTK_FILE_CHOOSER(dialog), filesel_state.open.filter_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);
ui_encodings_combo_box_set_active_encoding(GTK_COMBO_BOX(encoding_combo), filesel_state.open.encoding_idx);
filetype_combo_box_set_active_filetype(GTK_COMBO_BOX(filetype_combo), filesel_state.open.filetype_idx);
}
/* This shows the file selection dialog to open a file. */
2011-06-10 22:13:29 +00:00
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 initially 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
{
2011-06-10 22:13:29 +00:00
GtkWidget *dialog = create_open_file_dialog();
2011-06-10 22:13:29 +00:00
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))
2011-06-10 22:13:29 +00:00
gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog),
app->project->base_path, NULL);
while (!open_file_dialog_handle_response(dialog,
gtk_dialog_run(GTK_DIALOG(dialog))));
2011-06-10 22:13:29 +00:00
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);
}
if (doc->tm_file)
{
/* create a new tm_source_file object otherwise tagmanager won't work correctly */
tm_workspace_remove_source_file(doc->tm_file);
tm_source_file_free(doc->tm_file);
doc->tm_file = NULL;
}
}
success = document_save_file_as(doc, utf8_filename);
build_menu_update(doc);
return success;
}
2011-06-10 22:13:29 +00:00
static gboolean save_as_dialog_handle_response(GtkWidget *dialog, gint response)
{
gboolean rename_file = FALSE;
gboolean success = FALSE;
2011-06-10 22:13:29 +00:00
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;
}
2011-06-10 22:13:29 +00:00
case GTK_RESPONSE_DELETE_EVENT:
case GTK_RESPONSE_CANCEL:
success = TRUE;
break;
}
g_free(new_filename);
2011-06-10 22:13:29 +00:00
return success;
}
static GtkWidget *create_save_file_dialog(GeanyDocument *doc)
{
GtkWidget *dialog, *rename_btn;
const gchar *initdir;
2011-06-10 22:13:29 +00:00
dialog = gtk_file_chooser_dialog_new(_("Save File"), GTK_WINDOW(main_widgets.window),
GTK_FILE_CHOOSER_ACTION_SAVE, NULL, NULL);
2011-06-10 22:13:29 +00:00
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);
2011-06-10 22:13:29 +00:00
gtk_dialog_add_buttons(GTK_DIALOG(dialog),
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
2011-06-10 22:13:29 +00:00
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
2011-06-10 22:13:29 +00:00
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
2011-10-28 10:25:58 -07:00
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);
2011-06-10 22:13:29 +00:00
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), linitdir);
g_free(linitdir);
}
2011-06-10 22:13:29 +00:00
return dialog;
}
static gboolean show_save_as_gtk(GeanyDocument *doc)
{
2011-06-10 22:13:29 +00:00
GtkWidget *dialog;
gint resp;
g_return_val_if_fail(DOC_VALID(doc), 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);
2011-06-10 22:13:29 +00:00
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
2011-06-10 22:13:29 +00:00
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);
2011-06-10 22:13:29 +00:00
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), fname);
g_free(fname);
}
if (app->project && !EMPTY(app->project->base_path))
2011-06-10 22:13:29 +00:00
gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog),
app->project->base_path, NULL);
/* Run the dialog synchronously, pausing this function call */
2011-06-10 22:13:29 +00:00
do
{
resp = gtk_dialog_run(GTK_DIALOG(dialog));
}
while (! save_as_dialog_handle_response(dialog, resp));
if (app->project && !EMPTY(app->project->base_path))
2011-06-10 22:13:29 +00:00
gtk_file_chooser_remove_shortcut_folder(GTK_FILE_CHOOSER(dialog),
app->project->base_path, NULL);
2011-06-10 22:13:29 +00:00
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.
**/
GEANY_API_SYMBOL
gboolean dialogs_show_save_as(void)
{
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);
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.
**/
GEANY_API_SYMBOL
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;
2011-10-11 16:19:24 +01:00
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);
2011-10-11 16:19:24 +01:00
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);
2011-10-11 16:19:24 +01:00
response = run_unsaved_dialog(msg, msg2);
g_free(msg);
2011-10-11 16:19:24 +01:00
switch (response)
{
case GTK_RESPONSE_YES:
/* document_save_file() returns the status if the file could be saved */
return document_save_file(doc, FALSE);
2011-10-11 16:19:24 +01:00
case GTK_RESPONSE_NO:
return TRUE;
case GTK_RESPONSE_CANCEL: /* fall through to default and leave the function */
2011-10-11 16:19:24 +01:00
default:
return FALSE;
}
}
/* 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);
}
/* This shows the font selection dialog to choose a font. */
void dialogs_show_open_font(void)
{
#ifdef G_OS_WIN32
if (interface_prefs.use_native_windows_dialogs)
{
win32_show_font_dialog();
return;
}
#endif
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");
apply_button = gtk_dialog_get_widget_for_response(GTK_DIALOG(ui_widgets.open_fontsel), GTK_RESPONSE_APPLY);
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));
}
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);
}
2014-08-11 16:52:26 +02:00
typedef struct
{
2014-08-11 16:52:26 +02:00
GtkWidget *entry;
GtkWidget *combo;
GeanyInputCallback callback;
gpointer data;
}
InputDialogData;
2014-08-11 16:52:26 +02:00
static void
on_input_dialog_response(GtkDialog *dialog, gint response, InputDialogData *data)
{
if (response == GTK_RESPONSE_ACCEPT)
{
2014-08-11 16:52:26 +02:00
const gchar *str = gtk_entry_get_text(GTK_ENTRY(data->entry));
2014-08-11 16:52:26 +02:00
if (data->combo != NULL)
ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(data->combo), str, 0);
data->callback(str, data->data);
}
gtk_widget_hide(GTK_WIDGET(dialog));
}
/* 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, gpointer input_cb_data,
GCallback insert_text_cb, gpointer insert_text_cb_data)
{
2014-08-11 16:52:26 +02:00
GtkWidget *dialog, *vbox;
InputDialogData *data = g_malloc(sizeof *data);
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);
2014-08-11 16:52:26 +02:00
data->combo = NULL;
data->entry = NULL;
data->callback = input_cb;
data->data = input_cb_data;
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 */
{
2014-08-11 16:52:26 +02:00
data->combo = gtk_combo_box_text_new_with_entry();
data->entry = gtk_bin_get_child(GTK_BIN(data->combo));
ui_entry_add_clear_icon(GTK_ENTRY(data->entry));
gtk_container_add(GTK_CONTAINER(vbox), data->combo);
}
else
{
2014-08-11 16:52:26 +02:00
data->entry = gtk_entry_new();
ui_entry_add_clear_icon(GTK_ENTRY(data->entry));
gtk_container_add(GTK_CONTAINER(vbox), data->entry);
}
if (default_text != NULL)
{
2014-08-11 16:52:26 +02:00
gtk_entry_set_text(GTK_ENTRY(data->entry), default_text);
}
2014-08-11 16:52:26 +02:00
gtk_entry_set_max_length(GTK_ENTRY(data->entry), 255);
gtk_entry_set_width_chars(GTK_ENTRY(data->entry), 30);
if (insert_text_cb != NULL)
2014-08-11 16:52:26 +02:00
g_signal_connect(data->entry, "insert-text", insert_text_cb, insert_text_cb_data);
g_signal_connect(data->entry, "activate", G_CALLBACK(on_input_entry_activate), dialog);
g_signal_connect(dialog, "show", G_CALLBACK(on_input_dialog_show), data->entry);
g_signal_connect_data(dialog, "response", G_CALLBACK(on_input_dialog_response), data, (GClosureNotify)g_free, 0);
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, gpointer input_cb_data)
{
return dialogs_show_input_full(title, parent, label_text, default_text, TRUE, input_cb, input_cb_data, NULL, NULL);
}
static void on_dialog_input(const gchar *str, gpointer data)
{
gchar **dialog_input = data;
*dialog_input = g_strdup(str);
}
/** Asks the user for text input.
* @param title Dialog title.
2016-02-28 02:12:35 +01:00
* @param parent @nullable The currently focused window, usually @c geany->main_widgets->window.
* @c NULL can be used but is discouraged due to window manager effects.
2016-02-28 02:12:35 +01:00
* @param label_text @nullable Label text, or @c NULL.
* @param default_text @nullable Text to display in the input field, or @c NULL.
* @return @nullable New copy of user input or @c NULL if cancelled.
* @since 0.20. */
GEANY_API_SYMBOL
gchar *dialogs_show_input(const gchar *title, GtkWindow *parent, const gchar *label_text,
const gchar *default_text)
{
gchar *dialog_input = NULL;
dialogs_show_input_full(title, parent, label_text, default_text, FALSE, on_dialog_input, &dialog_input, NULL, 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)
{
gchar *dialog_input = NULL;
dialogs_show_input_full(
title, parent, label_text, default_text, FALSE, on_dialog_input, &dialog_input,
G_CALLBACK(ui_editable_insert_text_callback), NULL);
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
**/
GEANY_API_SYMBOL
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;
#ifdef HAVE_SYS_TYPES_H
GStatBuf 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
g_return_if_fail(doc == NULL || doc->is_valid);
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");
gtk_image_set_from_gicon(GTK_IMAGE(image), doc->file_type->icon,
GTK_ICON_SIZE_BUTTON);
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, response_3, or GTK_RESPONSE_DELETE_EVENT if the dialog was discarded */
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);
btn = gtk_dialog_add_button(GTK_DIALOG(dialog), btn_2, 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);
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.
**/
GEANY_API_SYMBOL
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;
}