Put Makefile comments at start of line.
[geany-mirror.git] / src / dialogs.c
blob7586ad1261be5ee1d0668591eb62ed0afd51db4a
1 /*
2 * dialogs.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005-2012 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2006-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * File related dialogs, miscellaneous dialogs, font dialog.
26 #include "geany.h"
28 #include <gdk/gdkkeysyms.h>
29 #include <string.h>
31 #ifdef HAVE_SYS_TIME_H
32 # include <sys/time.h>
33 #endif
34 #include <time.h>
36 #ifdef HAVE_SYS_TYPES_H
37 # include <sys/types.h>
38 #endif
40 /* gstdio.h also includes sys/stat.h */
41 #include <glib/gstdio.h>
43 #include "dialogs.h"
45 #include "callbacks.h"
46 #include "document.h"
47 #include "filetypes.h"
48 #include "win32.h"
49 #include "sciwrappers.h"
50 #include "support.h"
51 #include "utils.h"
52 #include "ui_utils.h"
53 #include "keybindings.h"
54 #include "encodings.h"
55 #include "build.h"
56 #include "main.h"
57 #include "project.h"
58 #include "gtkcompat.h"
61 enum
63 GEANY_RESPONSE_RENAME,
64 GEANY_RESPONSE_VIEW
68 static struct FileSelState
70 struct
72 guint filter_idx;
73 gint encoding_idx;
74 gint filetype_idx;
75 gboolean show_hidden;
76 gboolean more_options_visible;
77 } open;
79 filesel_state = {
82 GEANY_ENCODINGS_MAX, /* default encoding is detect from file */
84 FALSE,
85 FALSE
90 /* gets the ID of the current file filter */
91 static guint file_chooser_get_filter_idx(GtkFileChooser *chooser)
93 guint idx = 0;
94 GtkFileFilter *current;
95 GSList *filters, *item;
97 current = gtk_file_chooser_get_filter(chooser);
98 filters = gtk_file_chooser_list_filters(chooser);
99 foreach_slist(item, filters)
101 if (item->data == current)
102 break;
103 idx ++;
105 g_slist_free(filters);
106 return idx;
110 /* sets the current file filter from its ID */
111 static void file_chooser_set_filter_idx(GtkFileChooser *chooser, guint idx)
113 GtkFileFilter *current;
114 GSList *filters;
116 filters = gtk_file_chooser_list_filters(chooser);
117 current = g_slist_nth_data(filters, idx);
118 g_slist_free(filters);
119 gtk_file_chooser_set_filter(chooser, current);
123 static void open_file_dialog_handle_response(GtkWidget *dialog, gint response)
125 if (response == GTK_RESPONSE_ACCEPT || response == GEANY_RESPONSE_VIEW)
127 GSList *filelist;
128 GtkTreeModel *encoding_model;
129 GtkTreeIter encoding_iter;
130 GeanyFiletype *ft = NULL;
131 const gchar *charset = NULL;
132 GtkWidget *expander = ui_lookup_widget(dialog, "more_options_expander");
133 GtkWidget *filetype_combo = ui_lookup_widget(dialog, "filetype_combo");
134 GtkWidget *encoding_combo = ui_lookup_widget(dialog, "encoding_combo");
135 gboolean ro = (response == GEANY_RESPONSE_VIEW); /* View clicked */
137 filesel_state.open.more_options_visible = gtk_expander_get_expanded(GTK_EXPANDER(expander));
138 filesel_state.open.filter_idx = file_chooser_get_filter_idx(GTK_FILE_CHOOSER(dialog));
139 filesel_state.open.filetype_idx = gtk_combo_box_get_active(GTK_COMBO_BOX(filetype_combo));
141 /* ignore detect from file item */
142 if (filesel_state.open.filetype_idx > 0)
143 ft = g_slist_nth_data(filetypes_by_title, (guint) filesel_state.open.filetype_idx);
145 encoding_model = gtk_combo_box_get_model(GTK_COMBO_BOX(encoding_combo));
146 gtk_combo_box_get_active_iter(GTK_COMBO_BOX(encoding_combo), &encoding_iter);
147 gtk_tree_model_get(encoding_model, &encoding_iter, 0, &filesel_state.open.encoding_idx, -1);
148 if (filesel_state.open.encoding_idx >= 0 && filesel_state.open.encoding_idx < GEANY_ENCODINGS_MAX)
149 charset = encodings[filesel_state.open.encoding_idx].charset;
151 filelist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
152 if (filelist != NULL)
154 document_open_files(filelist, ro, ft, charset);
155 g_slist_foreach(filelist, (GFunc) g_free, NULL); /* free filenames */
157 g_slist_free(filelist);
159 if (app->project && !EMPTY(app->project->base_path))
160 gtk_file_chooser_remove_shortcut_folder(GTK_FILE_CHOOSER(dialog),
161 app->project->base_path, NULL);
165 static void on_file_open_show_hidden_notify(GObject *filechooser,
166 GParamSpec *pspec, gpointer data)
168 GtkWidget *toggle_button;
170 toggle_button = ui_lookup_widget(GTK_WIDGET(filechooser), "check_hidden");
172 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_button),
173 gtk_file_chooser_get_show_hidden(GTK_FILE_CHOOSER(filechooser)));
177 static void
178 on_file_open_check_hidden_toggled(GtkToggleButton *togglebutton, GtkWidget *dialog)
180 filesel_state.open.show_hidden = gtk_toggle_button_get_active(togglebutton);
181 gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), filesel_state.open.show_hidden);
185 static gint encoding_combo_store_sort_func(GtkTreeModel *model,
186 GtkTreeIter *a,
187 GtkTreeIter *b,
188 gpointer data)
190 gboolean a_has_child = gtk_tree_model_iter_has_child(model, a);
191 gboolean b_has_child = gtk_tree_model_iter_has_child(model, b);
192 gchar *a_string;
193 gchar *b_string;
194 gint cmp_res;
196 if (a_has_child != b_has_child)
197 return a_has_child ? -1 : 1;
199 gtk_tree_model_get(model, a, 1, &a_string, -1);
200 gtk_tree_model_get(model, b, 1, &b_string, -1);
201 cmp_res = strcmp(a_string, b_string);
202 g_free(a_string);
203 g_free(b_string);
204 return cmp_res;
208 static GtkTreeStore *create_encoding_combo_store(GtkTreeIter *iter_detect)
210 GtkTreeStore *store;
211 GtkTreeIter iter_current, iter_westeuro, iter_easteuro, iter_eastasian, iter_asian,
212 iter_utf8, iter_middleeast;
213 GtkTreeIter *iter_parent;
214 gchar *encoding_string;
215 gint i;
217 store = gtk_tree_store_new(2, G_TYPE_INT, G_TYPE_STRING);
219 gtk_tree_store_append(store, iter_detect, NULL);
220 gtk_tree_store_set(store, iter_detect, 0, GEANY_ENCODINGS_MAX, 1, _("Detect from file"), -1);
222 gtk_tree_store_append(store, &iter_westeuro, NULL);
223 gtk_tree_store_set(store, &iter_westeuro, 0, -1, 1, _("West European"), -1);
224 gtk_tree_store_append(store, &iter_easteuro, NULL);
225 gtk_tree_store_set(store, &iter_easteuro, 0, -1, 1, _("East European"), -1);
226 gtk_tree_store_append(store, &iter_eastasian, NULL);
227 gtk_tree_store_set(store, &iter_eastasian, 0, -1, 1, _("East Asian"), -1);
228 gtk_tree_store_append(store, &iter_asian, NULL);
229 gtk_tree_store_set(store, &iter_asian, 0, -1, 1, _("SE & SW Asian"), -1);
230 gtk_tree_store_append(store, &iter_middleeast, NULL);
231 gtk_tree_store_set(store, &iter_middleeast, 0, -1, 1, _("Middle Eastern"), -1);
232 gtk_tree_store_append(store, &iter_utf8, NULL);
233 gtk_tree_store_set(store, &iter_utf8, 0, -1, 1, _("Unicode"), -1);
235 for (i = 0; i < GEANY_ENCODINGS_MAX; i++)
237 switch (encodings[i].group)
239 case WESTEUROPEAN: iter_parent = &iter_westeuro; break;
240 case EASTEUROPEAN: iter_parent = &iter_easteuro; break;
241 case EASTASIAN: iter_parent = &iter_eastasian; break;
242 case ASIAN: iter_parent = &iter_asian; break;
243 case MIDDLEEASTERN: iter_parent = &iter_middleeast; break;
244 case UNICODE: iter_parent = &iter_utf8; break;
245 case NONE:
246 default: iter_parent = NULL;
248 gtk_tree_store_append(store, &iter_current, iter_parent);
249 encoding_string = encodings_to_string(&encodings[i]);
250 gtk_tree_store_set(store, &iter_current, 0, i, 1, encoding_string, -1);
251 g_free(encoding_string);
252 /* restore the saved state */
253 if (i == filesel_state.open.encoding_idx)
254 *iter_detect = iter_current;
257 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), 1, GTK_SORT_ASCENDING);
258 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(store), 1, encoding_combo_store_sort_func, NULL, NULL);
260 return store;
264 static void encoding_combo_cell_data_func(GtkCellLayout *cell_layout,
265 GtkCellRenderer *cell,
266 GtkTreeModel *tree_model,
267 GtkTreeIter *iter,
268 gpointer data)
270 gboolean sensitive = !gtk_tree_model_iter_has_child(tree_model, iter);
272 g_object_set(cell, "sensitive", sensitive, NULL);
276 static GtkWidget *add_file_open_extra_widget(GtkWidget *dialog)
278 GtkWidget *expander, *vbox, *table, *check_hidden;
279 GtkWidget *filetype_ebox, *filetype_label, *filetype_combo;
280 GtkWidget *encoding_ebox, *encoding_label, *encoding_combo;
282 expander = gtk_expander_new_with_mnemonic(_("_More Options"));
283 vbox = gtk_vbox_new(FALSE, 6);
284 gtk_container_add(GTK_CONTAINER(expander), vbox);
286 table = gtk_table_new(2, 4, FALSE);
288 /* line 1 with checkbox and encoding combo */
289 check_hidden = gtk_check_button_new_with_mnemonic(_("Show _hidden files"));
290 gtk_widget_show(check_hidden);
291 gtk_table_attach(GTK_TABLE(table), check_hidden, 0, 1, 0, 1,
292 (GtkAttachOptions) (GTK_FILL | GTK_EXPAND),
293 (GtkAttachOptions) (0), 0, 5);
295 /* spacing */
296 gtk_table_attach(GTK_TABLE(table), gtk_label_new(""), 1, 2, 0, 1,
297 (GtkAttachOptions) (GTK_FILL),
298 (GtkAttachOptions) (0), 5, 5);
300 encoding_label = gtk_label_new(_("Set encoding:"));
301 gtk_misc_set_alignment(GTK_MISC(encoding_label), 1, 0);
302 gtk_table_attach(GTK_TABLE(table), encoding_label, 2, 3, 0, 1,
303 (GtkAttachOptions) (GTK_FILL),
304 (GtkAttachOptions) (0), 4, 5);
305 /* the ebox is for the tooltip, because gtk_combo_box can't show tooltips */
306 encoding_ebox = gtk_event_box_new();
307 encoding_combo = gtk_combo_box_new();
308 gtk_widget_set_tooltip_text(encoding_ebox,
309 _("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."));
310 gtk_container_add(GTK_CONTAINER(encoding_ebox), encoding_combo);
311 gtk_table_attach(GTK_TABLE(table), encoding_ebox, 3, 4, 0, 1,
312 (GtkAttachOptions) (GTK_FILL),
313 (GtkAttachOptions) (0), 0, 5);
315 /* line 2 with filetype combo */
316 filetype_label = gtk_label_new(_("Set filetype:"));
317 gtk_misc_set_alignment(GTK_MISC(filetype_label), 1, 0);
318 gtk_table_attach(GTK_TABLE(table), filetype_label, 2, 3, 1, 2,
319 (GtkAttachOptions) (GTK_FILL),
320 (GtkAttachOptions) (0), 4, 5);
321 /* the ebox is for the tooltip, because gtk_combo_box can't show tooltips */
322 filetype_ebox = gtk_event_box_new();
323 filetype_combo = gtk_combo_box_text_new();
324 gtk_widget_set_tooltip_text(filetype_ebox,
325 _("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."));
326 gtk_container_add(GTK_CONTAINER(filetype_ebox), filetype_combo);
327 gtk_table_attach(GTK_TABLE(table), filetype_ebox, 3, 4, 1, 2,
328 (GtkAttachOptions) (GTK_FILL),
329 (GtkAttachOptions) (0), 0, 5);
331 gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
332 gtk_widget_show_all(vbox);
334 g_signal_connect(check_hidden, "toggled", G_CALLBACK(on_file_open_check_hidden_toggled), dialog);
336 ui_hookup_widget(dialog, expander, "more_options_expander");
337 ui_hookup_widget(dialog, check_hidden, "check_hidden");
338 ui_hookup_widget(dialog, filetype_combo, "filetype_combo");
339 ui_hookup_widget(dialog, encoding_combo, "encoding_combo");
341 return expander;
345 static GtkWidget *create_open_file_dialog(void)
347 GtkWidget *dialog;
348 GtkWidget *filetype_combo, *encoding_combo;
349 GtkWidget *viewbtn;
350 GtkCellRenderer *encoding_renderer;
351 GtkTreeIter encoding_iter;
352 GSList *node;
354 dialog = gtk_file_chooser_dialog_new(_("Open File"), GTK_WINDOW(main_widgets.window),
355 GTK_FILE_CHOOSER_ACTION_OPEN, NULL, NULL);
356 gtk_widget_set_name(dialog, "GeanyDialog");
358 viewbtn = gtk_dialog_add_button(GTK_DIALOG(dialog), _("_View"), GEANY_RESPONSE_VIEW);
359 gtk_widget_set_tooltip_text(viewbtn,
360 _("Opens the file in read-only mode. If you choose more than one file to open, all files will be opened read-only."));
362 gtk_dialog_add_buttons(GTK_DIALOG(dialog),
363 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
364 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
365 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
367 gtk_widget_set_size_request(dialog, -1, 460);
368 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
369 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
370 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
371 gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
372 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main_widgets.window));
373 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
374 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
376 /* add checkboxes and filename entry */
377 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), add_file_open_extra_widget(dialog));
378 filetype_combo = ui_lookup_widget(dialog, "filetype_combo");
380 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(filetype_combo), _("Detect by file extension"));
381 /* add FileFilters(start with "All Files") */
382 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
383 filetypes_create_file_filter(filetypes[GEANY_FILETYPES_NONE]));
384 /* now create meta filter "All Source" */
385 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
386 filetypes_create_file_filter_all_source());
387 foreach_slist(node, filetypes_by_title)
389 GeanyFiletype *ft = node->data;
391 if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE))
392 continue;
393 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(filetype_combo), ft->title);
394 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filetypes_create_file_filter(ft));
396 gtk_combo_box_set_wrap_width(GTK_COMBO_BOX(filetype_combo), 3);
397 gtk_combo_box_set_active(GTK_COMBO_BOX(filetype_combo), 0);
399 /* fill encoding combo box */
400 encoding_combo = ui_lookup_widget(dialog, "encoding_combo");
401 gtk_combo_box_set_model(GTK_COMBO_BOX(encoding_combo), GTK_TREE_MODEL(
402 create_encoding_combo_store(&encoding_iter)));
403 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(encoding_combo), &encoding_iter);
404 encoding_renderer = gtk_cell_renderer_text_new();
405 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(encoding_combo), encoding_renderer, TRUE);
406 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(encoding_combo), encoding_renderer, "text", 1);
407 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(encoding_combo), encoding_renderer,
408 encoding_combo_cell_data_func, NULL, NULL);
410 g_signal_connect(dialog, "notify::show-hidden",
411 G_CALLBACK(on_file_open_show_hidden_notify), NULL);
413 return dialog;
417 static void open_file_dialog_apply_settings(GtkWidget *dialog)
419 static gboolean initialized = FALSE;
420 GtkWidget *check_hidden = ui_lookup_widget(dialog, "check_hidden");
421 GtkWidget *filetype_combo = ui_lookup_widget(dialog, "filetype_combo");
422 GtkWidget *expander = ui_lookup_widget(dialog, "more_options_expander");
424 /* we can't know the initial position of combo boxes, so retreive it the first time */
425 if (! initialized)
427 filesel_state.open.filter_idx = file_chooser_get_filter_idx(GTK_FILE_CHOOSER(dialog));
428 filesel_state.open.filetype_idx = gtk_combo_box_get_active(GTK_COMBO_BOX(filetype_combo));
430 initialized = TRUE;
432 else
434 file_chooser_set_filter_idx(GTK_FILE_CHOOSER(dialog), filesel_state.open.filter_idx);
435 gtk_combo_box_set_active(GTK_COMBO_BOX(filetype_combo), filesel_state.open.filetype_idx);
437 gtk_expander_set_expanded(GTK_EXPANDER(expander), filesel_state.open.more_options_visible);
438 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_hidden), filesel_state.open.show_hidden);
439 /* encoding combo is restored at creating time, see create_encoding_combo_store() */
443 /* This shows the file selection dialog to open a file. */
444 void dialogs_show_open_file(void)
446 gchar *initdir;
448 /* set dialog directory to the current file's directory, if present */
449 initdir = utils_get_current_file_dir_utf8();
451 /* use project or default startup directory (if set) if no files are open */
452 /** TODO should it only be used when initally open the dialog and not on every show? */
453 if (! initdir)
454 initdir = g_strdup(utils_get_default_dir_utf8());
456 SETPTR(initdir, utils_get_locale_from_utf8(initdir));
458 #ifdef G_OS_WIN32
459 if (interface_prefs.use_native_windows_dialogs)
460 win32_show_document_open_dialog(GTK_WINDOW(main_widgets.window), _("Open File"), initdir);
461 else
462 #endif
464 GtkWidget *dialog = create_open_file_dialog();
465 gint response;
467 open_file_dialog_apply_settings(dialog);
469 if (initdir != NULL && g_path_is_absolute(initdir))
470 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), initdir);
472 if (app->project && !EMPTY(app->project->base_path))
473 gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog),
474 app->project->base_path, NULL);
476 response = gtk_dialog_run(GTK_DIALOG(dialog));
477 open_file_dialog_handle_response(dialog, response);
478 gtk_widget_destroy(dialog);
480 g_free(initdir);
484 static gboolean handle_save_as(const gchar *utf8_filename, gboolean rename_file)
486 GeanyDocument *doc = document_get_current();
487 gboolean success = FALSE;
489 g_return_val_if_fail(!EMPTY(utf8_filename), FALSE);
491 if (doc->file_name != NULL)
493 if (rename_file)
495 document_rename_file(doc, utf8_filename);
497 /* create a new tm_source_file object otherwise tagmanager won't work correctly */
498 tm_workspace_remove_object(doc->tm_file, TRUE, TRUE);
499 doc->tm_file = NULL;
501 success = document_save_file_as(doc, utf8_filename);
503 build_menu_update(doc);
504 return success;
508 static gboolean save_as_dialog_handle_response(GtkWidget *dialog, gint response)
510 gboolean rename_file = FALSE;
511 gboolean success = FALSE;
512 gchar *new_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
514 switch (response)
516 case GEANY_RESPONSE_RENAME:
517 /* rename doesn't check for empty filename or overwriting */
518 if (G_UNLIKELY(EMPTY(new_filename)))
520 utils_beep();
521 break;
523 if (g_file_test(new_filename, G_FILE_TEST_EXISTS) &&
524 !dialogs_show_question_full(NULL, NULL, NULL,
525 _("Overwrite?"),
526 _("Filename already exists!")))
527 break;
528 rename_file = TRUE;
529 /* fall through */
530 case GTK_RESPONSE_ACCEPT:
532 gchar *utf8_filename;
534 utf8_filename = utils_get_utf8_from_locale(new_filename);
535 success = handle_save_as(utf8_filename, rename_file);
536 g_free(utf8_filename);
537 break;
539 case GTK_RESPONSE_DELETE_EVENT:
540 case GTK_RESPONSE_CANCEL:
541 success = TRUE;
542 break;
544 g_free(new_filename);
546 return success;
550 static GtkWidget *create_save_file_dialog(GeanyDocument *doc)
552 GtkWidget *dialog, *rename_btn;
553 const gchar *initdir;
555 dialog = gtk_file_chooser_dialog_new(_("Save File"), GTK_WINDOW(main_widgets.window),
556 GTK_FILE_CHOOSER_ACTION_SAVE, NULL, NULL);
557 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
558 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
559 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
560 gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
561 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main_widgets.window));
562 gtk_widget_set_name(dialog, "GeanyDialog");
564 rename_btn = gtk_dialog_add_button(GTK_DIALOG(dialog), _("R_ename"), GEANY_RESPONSE_RENAME);
565 gtk_widget_set_tooltip_text(rename_btn, _("Save the file and rename it"));
566 /* disable rename unless file exists on disk */
567 gtk_widget_set_sensitive(rename_btn, doc->real_path != NULL);
569 gtk_dialog_add_buttons(GTK_DIALOG(dialog),
570 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
571 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
572 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
574 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
575 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
577 /* set the folder by default to the project base dir or the global pref for opening files */
578 initdir = utils_get_default_dir_utf8();
579 if (initdir)
581 gchar *linitdir = utils_get_locale_from_utf8(initdir);
582 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), linitdir);
583 g_free(linitdir);
585 return dialog;
589 static gboolean show_save_as_gtk(GeanyDocument *doc)
591 GtkWidget *dialog;
592 gint resp;
594 g_return_val_if_fail(DOC_VALID(doc), FALSE);
596 dialog = create_save_file_dialog(doc);
598 if (doc->file_name != NULL)
600 if (g_path_is_absolute(doc->file_name))
602 gchar *locale_filename = utils_get_locale_from_utf8(doc->file_name);
603 gchar *locale_basename = g_path_get_basename(locale_filename);
604 gchar *locale_dirname = g_path_get_dirname(locale_filename);
606 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_dirname);
607 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), locale_basename);
609 g_free(locale_filename);
610 g_free(locale_basename);
611 g_free(locale_dirname);
613 else
614 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), doc->file_name);
616 else
618 gchar *fname = NULL;
620 if (doc->file_type != NULL && doc->file_type->extension != NULL)
621 fname = g_strconcat(GEANY_STRING_UNTITLED, ".",
622 doc->file_type->extension, NULL);
623 else
624 fname = g_strdup(GEANY_STRING_UNTITLED);
626 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), fname);
628 g_free(fname);
631 if (app->project && !EMPTY(app->project->base_path))
632 gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog),
633 app->project->base_path, NULL);
635 /* Run the dialog synchronously, pausing this function call */
638 resp = gtk_dialog_run(GTK_DIALOG(dialog));
640 while (! save_as_dialog_handle_response(dialog, resp));
642 if (app->project && !EMPTY(app->project->base_path))
643 gtk_file_chooser_remove_shortcut_folder(GTK_FILE_CHOOSER(dialog),
644 app->project->base_path, NULL);
646 gtk_widget_destroy(dialog);
648 return (resp == GTK_RESPONSE_ACCEPT);
653 * Shows the Save As dialog for the current notebook page.
655 * @return @c TRUE if the file was saved, otherwise @c FALSE.
657 gboolean dialogs_show_save_as(void)
659 GeanyDocument *doc = document_get_current();
660 gboolean result = FALSE;
662 g_return_val_if_fail(doc, FALSE);
664 #ifdef G_OS_WIN32
665 if (interface_prefs.use_native_windows_dialogs)
667 gchar *utf8_name = win32_show_document_save_as_dialog(GTK_WINDOW(main_widgets.window),
668 _("Save File"), doc);
669 if (utf8_name != NULL)
670 result = handle_save_as(utf8_name, FALSE);
672 else
673 #endif
674 result = show_save_as_gtk(doc);
675 return result;
679 #ifndef G_OS_WIN32
680 static void show_msgbox_dialog(GtkWidget *dialog, GtkMessageType type, GtkWindow *parent)
682 const gchar *title;
683 switch (type)
685 case GTK_MESSAGE_ERROR:
686 title = _("Error");
687 break;
688 case GTK_MESSAGE_QUESTION:
689 title = _("Question");
690 break;
691 case GTK_MESSAGE_WARNING:
692 title = _("Warning");
693 break;
694 default:
695 title = _("Information");
696 break;
698 gtk_window_set_title(GTK_WINDOW(dialog), title);
699 gtk_window_set_icon_name(GTK_WINDOW(dialog), "geany");
700 gtk_widget_set_name(dialog, "GeanyDialog");
702 gtk_dialog_run(GTK_DIALOG(dialog));
703 gtk_widget_destroy(dialog);
705 #endif
709 * Shows a message box of the type @a type with @a text.
710 * On Unix-like systems a GTK message dialog box is shown, on Win32 systems a native Windows
711 * message dialog box is shown.
713 * @param type A @c GtkMessageType, e.g. @c GTK_MESSAGE_INFO, @c GTK_MESSAGE_WARNING,
714 * @c GTK_MESSAGE_QUESTION, @c GTK_MESSAGE_ERROR.
715 * @param text Printf()-style format string.
716 * @param ... Arguments for the @a text format string.
718 void dialogs_show_msgbox(GtkMessageType type, const gchar *text, ...)
720 #ifndef G_OS_WIN32
721 GtkWidget *dialog;
722 #endif
723 gchar *string;
724 va_list args;
725 GtkWindow *parent = (main_status.main_window_realized) ? GTK_WINDOW(main_widgets.window) : NULL;
727 va_start(args, text);
728 string = g_strdup_vprintf(text, args);
729 va_end(args);
731 #ifdef G_OS_WIN32
732 win32_message_dialog(GTK_WIDGET(parent), type, string);
733 #else
734 dialog = gtk_message_dialog_new(parent, GTK_DIALOG_DESTROY_WITH_PARENT,
735 type, GTK_BUTTONS_OK, "%s", string);
736 show_msgbox_dialog(dialog, type, parent);
737 #endif
738 g_free(string);
742 void dialogs_show_msgbox_with_secondary(GtkMessageType type, const gchar *text, const gchar *secondary)
744 GtkWindow *parent = (main_status.main_window_realized) ? GTK_WINDOW(main_widgets.window) : NULL;
745 #ifdef G_OS_WIN32
746 /* put the two strings together because Windows message boxes don't support secondary texts */
747 gchar *string = g_strconcat(text, "\n", secondary, NULL);
748 win32_message_dialog(GTK_WIDGET(parent), type, string);
749 g_free(string);
750 #else
751 GtkWidget *dialog;
752 dialog = gtk_message_dialog_new(parent, GTK_DIALOG_DESTROY_WITH_PARENT,
753 type, GTK_BUTTONS_OK, "%s", text);
754 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
755 show_msgbox_dialog(dialog, type, parent);
756 #endif
760 static gint run_unsaved_dialog(const gchar *msg, const gchar *msg2)
762 GtkWidget *dialog, *button;
763 gint ret;
765 dialog = gtk_message_dialog_new(GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
766 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", msg);
767 gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
768 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", msg2);
769 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
771 button = ui_button_new_with_image(GTK_STOCK_CLEAR, _("_Don't save"));
772 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, GTK_RESPONSE_NO);
773 gtk_widget_show(button);
775 gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_SAVE, GTK_RESPONSE_YES);
777 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES);
778 ret = gtk_dialog_run(GTK_DIALOG(dialog));
780 gtk_widget_destroy(dialog);
782 return ret;
786 gboolean dialogs_show_unsaved_file(GeanyDocument *doc)
788 gchar *msg, *short_fn = NULL;
789 const gchar *msg2;
790 gint response;
791 gboolean old_quitting_state = main_status.quitting;
793 /* display the file tab to remind the user of the document */
794 main_status.quitting = FALSE;
795 document_show_tab(doc);
796 main_status.quitting = old_quitting_state;
798 short_fn = document_get_basename_for_display(doc, -1);
800 msg = g_strdup_printf(_("The file '%s' is not saved."), short_fn);
801 msg2 = _("Do you want to save it before closing?");
802 g_free(short_fn);
804 response = run_unsaved_dialog(msg, msg2);
805 g_free(msg);
807 switch (response)
809 case GTK_RESPONSE_YES:
810 /* document_save_file() returns the status if the file could be saved */
811 return document_save_file(doc, FALSE);
813 case GTK_RESPONSE_NO:
814 return TRUE;
816 case GTK_RESPONSE_CANCEL: /* fall through to default and leave the function */
817 default:
818 return FALSE;
823 #ifndef G_OS_WIN32
824 /* Use GtkFontChooserDialog on GTK3.2 for consistency, and because
825 * GtkFontSelectionDialog is somewhat broken on 3.4 */
826 #if GTK_CHECK_VERSION(3, 2, 0)
827 # undef GTK_FONT_SELECTION_DIALOG
828 # define GTK_FONT_SELECTION_DIALOG GTK_FONT_CHOOSER_DIALOG
830 # define gtk_font_selection_dialog_new(title) \
831 gtk_font_chooser_dialog_new((title), NULL)
832 # define gtk_font_selection_dialog_get_font_name(dlg) \
833 gtk_font_chooser_get_font(GTK_FONT_CHOOSER(dlg))
834 # define gtk_font_selection_dialog_set_font_name(dlg, font) \
835 gtk_font_chooser_set_font(GTK_FONT_CHOOSER(dlg), (font))
836 #endif
838 static void
839 on_font_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
841 gboolean close = TRUE;
843 switch (response)
845 case GTK_RESPONSE_APPLY:
846 case GTK_RESPONSE_OK:
848 gchar *fontname;
850 fontname = gtk_font_selection_dialog_get_font_name(
851 GTK_FONT_SELECTION_DIALOG(ui_widgets.open_fontsel));
852 ui_set_editor_font(fontname);
853 g_free(fontname);
855 close = (response == GTK_RESPONSE_OK);
856 break;
860 if (close)
861 gtk_widget_hide(ui_widgets.open_fontsel);
863 #endif
866 /* This shows the font selection dialog to choose a font. */
867 void dialogs_show_open_font(void)
869 #ifdef G_OS_WIN32
870 win32_show_font_dialog();
871 #else
873 if (ui_widgets.open_fontsel == NULL)
875 GtkWidget *apply_button;
877 ui_widgets.open_fontsel = gtk_font_selection_dialog_new(_("Choose font"));;
878 gtk_container_set_border_width(GTK_CONTAINER(ui_widgets.open_fontsel), 4);
879 gtk_window_set_modal(GTK_WINDOW(ui_widgets.open_fontsel), TRUE);
880 gtk_window_set_destroy_with_parent(GTK_WINDOW(ui_widgets.open_fontsel), TRUE);
881 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(ui_widgets.open_fontsel), TRUE);
882 gtk_window_set_type_hint(GTK_WINDOW(ui_widgets.open_fontsel), GDK_WINDOW_TYPE_HINT_DIALOG);
883 gtk_widget_set_name(ui_widgets.open_fontsel, "GeanyDialog");
885 #if GTK_CHECK_VERSION(2, 20, 0)
886 /* apply button doesn't have a getter and is hidden by default, but we'd like to show it */
887 apply_button = gtk_dialog_get_widget_for_response(GTK_DIALOG(ui_widgets.open_fontsel), GTK_RESPONSE_APPLY);
888 #else
889 apply_button = GTK_FONT_SELECTION_DIALOG(ui_widgets.open_fontsel)->apply_button;
890 #endif
891 if (apply_button)
892 gtk_widget_show(apply_button);
894 g_signal_connect(ui_widgets.open_fontsel,
895 "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
896 g_signal_connect(ui_widgets.open_fontsel,
897 "response", G_CALLBACK(on_font_dialog_response), NULL);
899 gtk_window_set_transient_for(GTK_WINDOW(ui_widgets.open_fontsel), GTK_WINDOW(main_widgets.window));
901 gtk_font_selection_dialog_set_font_name(
902 GTK_FONT_SELECTION_DIALOG(ui_widgets.open_fontsel), interface_prefs.editor_font);
903 /* We make sure the dialog is visible. */
904 gtk_window_present(GTK_WINDOW(ui_widgets.open_fontsel));
905 #endif
909 static void
910 on_input_dialog_show(GtkDialog *dialog, GtkWidget *entry)
912 gtk_widget_grab_focus(entry);
916 static void
917 on_input_entry_activate(GtkEntry *entry, GtkDialog *dialog)
919 gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT);
923 static void
924 on_input_numeric_activate(GtkEntry *entry, GtkDialog *dialog)
926 gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT);
930 static void
931 on_input_dialog_response(GtkDialog *dialog, gint response, GtkWidget *entry)
933 gboolean persistent = (gboolean) GPOINTER_TO_INT(g_object_get_data(G_OBJECT(dialog), "has_combo"));
935 if (response == GTK_RESPONSE_ACCEPT)
937 const gchar *str = gtk_entry_get_text(GTK_ENTRY(entry));
938 GeanyInputCallback input_cb =
939 (GeanyInputCallback) g_object_get_data(G_OBJECT(dialog), "input_cb");
941 if (persistent)
943 GtkWidget *combo = (GtkWidget *) g_object_get_data(G_OBJECT(dialog), "combo");
944 ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(combo), str, 0);
946 input_cb(str);
948 gtk_widget_hide(GTK_WIDGET(dialog));
952 static void add_input_widgets(GtkWidget *dialog, GtkWidget *vbox,
953 const gchar *label_text, const gchar *default_text, gboolean persistent,
954 GCallback insert_text_cb)
956 GtkWidget *entry;
958 if (label_text)
960 GtkWidget *label = gtk_label_new(label_text);
961 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
962 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
963 gtk_container_add(GTK_CONTAINER(vbox), label);
966 if (persistent) /* remember previous entry text in a combo box */
968 GtkWidget *combo = gtk_combo_box_text_new_with_entry();
970 entry = gtk_bin_get_child(GTK_BIN(combo));
971 ui_entry_add_clear_icon(GTK_ENTRY(entry));
972 g_object_set_data(G_OBJECT(dialog), "combo", combo);
973 gtk_container_add(GTK_CONTAINER(vbox), combo);
975 else
977 entry = gtk_entry_new();
978 ui_entry_add_clear_icon(GTK_ENTRY(entry));
979 gtk_container_add(GTK_CONTAINER(vbox), entry);
982 if (default_text != NULL)
984 gtk_entry_set_text(GTK_ENTRY(entry), default_text);
986 gtk_entry_set_max_length(GTK_ENTRY(entry), 255);
987 gtk_entry_set_width_chars(GTK_ENTRY(entry), 30);
989 if (insert_text_cb != NULL)
990 g_signal_connect(entry, "insert-text", insert_text_cb, NULL);
991 g_signal_connect(entry, "activate", G_CALLBACK(on_input_entry_activate), dialog);
992 g_signal_connect(dialog, "show", G_CALLBACK(on_input_dialog_show), entry);
993 g_signal_connect(dialog, "response", G_CALLBACK(on_input_dialog_response), entry);
997 /* Create and display an input dialog.
998 * persistent: whether to remember previous entry text in a combo box;
999 * in this case the dialog returned is not destroyed on a response,
1000 * and can be reshown.
1001 * Returns: the dialog widget. */
1002 static GtkWidget *
1003 dialogs_show_input_full(const gchar *title, GtkWindow *parent,
1004 const gchar *label_text, const gchar *default_text,
1005 gboolean persistent, GeanyInputCallback input_cb, GCallback insert_text_cb)
1007 GtkWidget *dialog, *vbox;
1009 dialog = gtk_dialog_new_with_buttons(title, parent,
1010 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1011 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
1012 vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
1013 gtk_widget_set_name(dialog, "GeanyDialog");
1014 gtk_box_set_spacing(GTK_BOX(vbox), 6);
1016 g_object_set_data(G_OBJECT(dialog), "has_combo", GINT_TO_POINTER(persistent));
1017 g_object_set_data(G_OBJECT(dialog), "input_cb", (gpointer) input_cb);
1019 add_input_widgets(dialog, vbox, label_text, default_text, persistent, insert_text_cb);
1021 if (persistent)
1023 /* override default handler */
1024 g_signal_connect(dialog, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1025 gtk_widget_show_all(dialog);
1026 return dialog;
1028 gtk_widget_show_all(dialog);
1029 gtk_dialog_run(GTK_DIALOG(dialog));
1030 gtk_widget_destroy(dialog);
1031 return NULL;
1035 /* Remember previous entry text in a combo box.
1036 * Returns: the dialog widget. */
1037 GtkWidget *
1038 dialogs_show_input_persistent(const gchar *title, GtkWindow *parent,
1039 const gchar *label_text, const gchar *default_text,
1040 GeanyInputCallback input_cb)
1042 return dialogs_show_input_full(title, parent, label_text, default_text, TRUE, input_cb, NULL);
1046 /* ugly hack - user_data not supported for callback */
1047 static gchar *dialog_input = NULL;
1049 static void on_dialog_input(const gchar *str)
1051 dialog_input = g_strdup(str);
1055 /** Asks the user for text input.
1056 * @param title Dialog title.
1057 * @param parent The currently focused window, usually @c geany->main_widgets->window.
1058 * @c NULL can be used but is discouraged due to window manager effects.
1059 * @param label_text Label text, or @c NULL.
1060 * @param default_text Text to display in the input field, or @c NULL.
1061 * @return New copy of user input or @c NULL if cancelled.
1062 * @since 0.20. */
1063 gchar *dialogs_show_input(const gchar *title, GtkWindow *parent, const gchar *label_text,
1064 const gchar *default_text)
1066 dialog_input = NULL;
1067 dialogs_show_input_full(title, parent, label_text, default_text, FALSE, on_dialog_input, NULL);
1068 return dialog_input;
1072 /* Note: could be changed to dialogs_show_validated_input with argument for callback. */
1073 /* Returns: newly allocated copy of the entry text or NULL on cancel.
1074 * Specialised variant for Goto Line dialog. */
1075 gchar *dialogs_show_input_goto_line(const gchar *title, GtkWindow *parent, const gchar *label_text,
1076 const gchar *default_text)
1078 dialog_input = NULL;
1079 dialogs_show_input_full(
1080 title, parent, label_text, default_text, FALSE, on_dialog_input,
1081 G_CALLBACK(ui_editable_insert_text_callback));
1082 return dialog_input;
1087 * Shows an input box to enter a numerical value using a GtkSpinButton.
1088 * If the dialog is aborted, @a value remains untouched.
1090 * @param title The dialog title.
1091 * @param label_text The shown dialog label.
1092 * @param value The default value for the spin button and the return location of the entered value.
1093 * Must be non-NULL.
1094 * @param min Minimum allowable value (see documentation for @c gtk_spin_button_new_with_range()).
1095 * @param max Maximum allowable value (see documentation for @c gtk_spin_button_new_with_range()).
1096 * @param step Increment added or subtracted by spinning the widget
1097 * (see documentation for @c gtk_spin_button_new_with_range()).
1099 * @return @c TRUE if a value was entered and the dialog closed with 'OK'. @c FALSE otherwise.
1101 * @since 0.16
1103 gboolean dialogs_show_input_numeric(const gchar *title, const gchar *label_text,
1104 gdouble *value, gdouble min, gdouble max, gdouble step)
1106 GtkWidget *dialog, *label, *spin, *vbox;
1107 gboolean res = FALSE;
1109 g_return_val_if_fail(title != NULL, FALSE);
1110 g_return_val_if_fail(label_text != NULL, FALSE);
1111 g_return_val_if_fail(value != NULL, FALSE);
1113 dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(main_widgets.window),
1114 GTK_DIALOG_DESTROY_WITH_PARENT,
1115 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1116 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
1117 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
1118 vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
1119 gtk_widget_set_name(dialog, "GeanyDialog");
1121 label = gtk_label_new(label_text);
1122 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1124 spin = gtk_spin_button_new_with_range(min, max, step);
1125 ui_entry_add_clear_icon(GTK_ENTRY(spin));
1126 gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), *value);
1127 g_signal_connect(spin, "activate", G_CALLBACK(on_input_numeric_activate), dialog);
1129 gtk_container_add(GTK_CONTAINER(vbox), label);
1130 gtk_container_add(GTK_CONTAINER(vbox), spin);
1131 gtk_widget_show_all(vbox);
1133 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1135 *value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
1136 res = TRUE;
1138 gtk_widget_destroy(dialog);
1140 return res;
1144 void dialogs_show_file_properties(GeanyDocument *doc)
1146 GtkWidget *dialog, *label, *image, *check;
1147 gchar *file_size, *title, *base_name, *time_changed, *time_modified, *time_accessed, *enctext;
1148 gchar *short_name;
1149 GdkPixbuf *pixbuf;
1150 #ifdef HAVE_SYS_TYPES_H
1151 struct stat st;
1152 off_t filesize;
1153 mode_t mode;
1154 gchar *locale_filename;
1155 #else
1156 gint filesize = 0;
1157 gint mode = 0;
1158 #endif
1160 /* define this ones, to avoid later trouble */
1161 #ifndef S_IRUSR
1162 # define S_IRUSR 0
1163 # define S_IWUSR 0
1164 # define S_IXUSR 0
1165 #endif
1166 #ifndef S_IRGRP
1167 # define S_IRGRP 0
1168 # define S_IWGRP 0
1169 # define S_IXGRP 0
1170 # define S_IROTH 0
1171 # define S_IWOTH 0
1172 # define S_IXOTH 0
1173 #endif
1175 g_return_if_fail(doc == NULL || doc->is_valid);
1177 if (doc == NULL || doc->file_name == NULL)
1179 dialogs_show_msgbox(GTK_MESSAGE_ERROR,
1180 _("An error occurred or file information could not be retrieved (e.g. from a new file)."));
1181 return;
1185 #ifdef HAVE_SYS_TYPES_H
1186 locale_filename = utils_get_locale_from_utf8(doc->file_name);
1187 if (g_stat(locale_filename, &st) == 0)
1189 /* first copy the returned string and the trim it, to not modify the static glibc string
1190 * g_strchomp() is used to remove trailing EOL chars, which are there for whatever reason */
1191 time_changed = g_strchomp(g_strdup(ctime(&st.st_ctime)));
1192 time_modified = g_strchomp(g_strdup(ctime(&st.st_mtime)));
1193 time_accessed = g_strchomp(g_strdup(ctime(&st.st_atime)));
1194 filesize = st.st_size;
1195 mode = st.st_mode;
1197 else
1199 time_changed = g_strdup(_("unknown"));
1200 time_modified = g_strdup(_("unknown"));
1201 time_accessed = g_strdup(_("unknown"));
1202 filesize = (off_t) 0;
1203 mode = (mode_t) 0;
1205 g_free(locale_filename);
1206 #else
1207 time_changed = g_strdup(_("unknown"));
1208 time_modified = g_strdup(_("unknown"));
1209 time_accessed = g_strdup(_("unknown"));
1210 #endif
1212 base_name = g_path_get_basename(doc->file_name);
1213 short_name = utils_str_middle_truncate(base_name, 30);
1214 title = g_strdup_printf(_("%s Properties"), short_name);
1215 dialog = ui_builder_get_object("properties_dialog");
1216 gtk_window_set_title(GTK_WINDOW(dialog), title);
1217 g_free(short_name);
1218 g_free(title);
1219 gtk_widget_set_name(dialog, "GeanyDialog");
1221 label = ui_lookup_widget(dialog, "file_name_label");
1222 gtk_label_set_text(GTK_LABEL(label), base_name);
1224 image = ui_lookup_widget(dialog, "file_type_image");
1225 pixbuf = ui_get_mime_icon(doc->file_type->mime_type, GTK_ICON_SIZE_BUTTON);
1226 gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
1227 g_object_unref(pixbuf);
1229 label = ui_lookup_widget(dialog, "file_type_label");
1230 gtk_label_set_text(GTK_LABEL(label), doc->file_type->title);
1232 label = ui_lookup_widget(dialog, "file_size_label");
1233 file_size = utils_make_human_readable_str(filesize, 1, 0);
1234 gtk_label_set_text(GTK_LABEL(label), file_size);
1235 g_free(file_size);
1237 label = ui_lookup_widget(dialog, "file_location_label");
1238 gtk_label_set_text(GTK_LABEL(label), doc->file_name);
1240 check = ui_lookup_widget(dialog, "file_read_only_check");
1241 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), doc->readonly);
1243 label = ui_lookup_widget(dialog, "file_encoding_label");
1244 enctext = g_strdup_printf("%s %s",
1245 doc->encoding,
1246 (encodings_is_unicode_charset(doc->encoding)) ?
1247 ((doc->has_bom) ? _("(with BOM)") : _("(without BOM)")) : "");
1248 gtk_label_set_text(GTK_LABEL(label), enctext);
1249 g_free(enctext);
1251 label = ui_lookup_widget(dialog, "file_modified_label");
1252 gtk_label_set_text(GTK_LABEL(label), time_modified);
1253 label = ui_lookup_widget(dialog, "file_changed_label");
1254 gtk_label_set_text(GTK_LABEL(label), time_changed);
1255 label = ui_lookup_widget(dialog, "file_accessed_label");
1256 gtk_label_set_text(GTK_LABEL(label), time_accessed);
1258 /* permissions */
1259 check = ui_lookup_widget(dialog, "file_perm_owner_r_check");
1260 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IRUSR);
1261 check = ui_lookup_widget(dialog, "file_perm_owner_w_check");
1262 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWUSR);
1263 check = ui_lookup_widget(dialog, "file_perm_owner_x_check");
1264 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXUSR);
1265 check = ui_lookup_widget(dialog, "file_perm_group_r_check");
1266 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IRGRP);
1267 check = ui_lookup_widget(dialog, "file_perm_group_w_check");
1268 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWGRP);
1269 check = ui_lookup_widget(dialog, "file_perm_group_x_check");
1270 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXGRP);
1271 check = ui_lookup_widget(dialog, "file_perm_other_r_check");
1272 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IROTH);
1273 check = ui_lookup_widget(dialog, "file_perm_other_w_check");
1274 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWOTH);
1275 check = ui_lookup_widget(dialog, "file_perm_other_x_check");
1276 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXOTH);
1278 g_free(base_name);
1279 g_free(time_changed);
1280 g_free(time_modified);
1281 g_free(time_accessed);
1283 gtk_widget_show(dialog);
1287 /* extra_text can be NULL; otherwise it is displayed below main_text.
1288 * if parent is NULL, main_widgets.window will be used
1289 * btn_1, btn_2, btn_3 can be NULL.
1290 * returns response_1, response_2 or response_3 */
1291 static gint show_prompt(GtkWidget *parent,
1292 const gchar *btn_1, GtkResponseType response_1,
1293 const gchar *btn_2, GtkResponseType response_2,
1294 const gchar *btn_3, GtkResponseType response_3,
1295 const gchar *question_text, const gchar *extra_text)
1297 gboolean ret = FALSE;
1298 GtkWidget *dialog;
1299 GtkWidget *btn;
1301 if (btn_2 == NULL)
1303 btn_2 = GTK_STOCK_NO;
1304 response_2 = GTK_RESPONSE_NO;
1306 if (btn_3 == NULL)
1308 btn_3 = GTK_STOCK_YES;
1309 response_3 = GTK_RESPONSE_YES;
1312 #ifdef G_OS_WIN32
1313 /* our native dialog code doesn't support custom buttons */
1314 if (utils_str_equal(btn_3, GTK_STOCK_YES) &&
1315 utils_str_equal(btn_2, GTK_STOCK_NO) && btn_1 == NULL)
1317 gchar *string = (extra_text == NULL) ? g_strdup(question_text) :
1318 g_strconcat(question_text, "\n\n", extra_text, NULL);
1320 ret = win32_message_dialog(parent, GTK_MESSAGE_QUESTION, string);
1321 ret = ret ? response_3 : response_2;
1322 g_free(string);
1323 return ret;
1325 #endif
1326 if (parent == NULL && main_status.main_window_realized)
1327 parent = main_widgets.window;
1329 dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
1330 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION,
1331 GTK_BUTTONS_NONE, "%s", question_text);
1332 gtk_widget_set_name(dialog, "GeanyDialog");
1333 gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
1334 gtk_window_set_icon_name(GTK_WINDOW(dialog), "geany");
1336 /* question_text will be in bold if optional extra_text used */
1337 if (extra_text != NULL)
1338 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1339 "%s", extra_text);
1341 if (btn_1 != NULL)
1342 gtk_dialog_add_button(GTK_DIALOG(dialog), btn_1, response_1);
1344 /* For a cancel button, use cancel response so user can press escape to cancel */
1345 btn = gtk_dialog_add_button(GTK_DIALOG(dialog), btn_2,
1346 utils_str_equal(btn_2, GTK_STOCK_CANCEL) ? GTK_RESPONSE_CANCEL : response_2);
1347 /* we don't want a default, but we need to override the apply button as default */
1348 gtk_widget_grab_default(btn);
1349 gtk_dialog_add_button(GTK_DIALOG(dialog), btn_3, response_3);
1351 ret = gtk_dialog_run(GTK_DIALOG(dialog));
1352 gtk_widget_destroy(dialog);
1354 if (ret == GTK_RESPONSE_CANCEL)
1355 ret = response_2;
1356 return ret;
1361 * Shows a question message box with @a text and Yes/No buttons.
1362 * On Unix-like systems a GTK message dialog box is shown, on Win32 systems a native Windows
1363 * message dialog box is shown.
1365 * @param text Printf()-style format string.
1366 * @param ... Arguments for the @a text format string.
1368 * @return @c TRUE if the user answered with Yes, otherwise @c FALSE.
1370 gboolean dialogs_show_question(const gchar *text, ...)
1372 gchar *string;
1373 va_list args;
1374 GtkWidget *parent = (main_status.main_window_realized) ? main_widgets.window : NULL;
1375 gint result;
1377 va_start(args, text);
1378 string = g_strdup_vprintf(text, args);
1379 va_end(args);
1380 result = show_prompt(parent,
1381 NULL, GTK_RESPONSE_NONE,
1382 GTK_STOCK_NO, GTK_RESPONSE_NO,
1383 GTK_STOCK_YES, GTK_RESPONSE_YES,
1384 string, NULL);
1385 g_free(string);
1386 return (result == GTK_RESPONSE_YES);
1390 /* extra_text can be NULL; otherwise it is displayed below main_text.
1391 * if parent is NULL, main_widgets.window will be used
1392 * yes_btn, no_btn can be NULL. */
1393 gboolean dialogs_show_question_full(GtkWidget *parent, const gchar *yes_btn, const gchar *no_btn,
1394 const gchar *extra_text, const gchar *main_text, ...)
1396 gint result;
1397 gchar *string;
1398 va_list args;
1400 va_start(args, main_text);
1401 string = g_strdup_vprintf(main_text, args);
1402 va_end(args);
1403 result = show_prompt(parent,
1404 NULL, GTK_RESPONSE_NONE,
1405 no_btn, GTK_RESPONSE_NO,
1406 yes_btn, GTK_RESPONSE_YES,
1407 string, extra_text);
1408 g_free(string);
1409 return (result == GTK_RESPONSE_YES);
1413 /* extra_text can be NULL; otherwise it is displayed below main_text.
1414 * if parent is NULL, main_widgets.window will be used
1415 * btn_1, btn_2, btn_3 can be NULL.
1416 * returns response_1, response_2 or response_3 */
1417 gint dialogs_show_prompt(GtkWidget *parent,
1418 const gchar *btn_1, GtkResponseType response_1,
1419 const gchar *btn_2, GtkResponseType response_2,
1420 const gchar *btn_3, GtkResponseType response_3,
1421 const gchar *extra_text, const gchar *main_text, ...)
1423 gchar *string;
1424 va_list args;
1425 gint result;
1427 va_start(args, main_text);
1428 string = g_strdup_vprintf(main_text, args);
1429 va_end(args);
1430 result = show_prompt(parent, btn_1, response_1, btn_2, response_2, btn_3, response_3,
1431 string, extra_text);
1432 g_free(string);
1433 return result;