1 /*
2  *      dialogs.c - this file is part of Geany, a fast and lightweight IDE
3  *
4  *      Copyright 2005 The Geany contributors
5  *
6  *      This program is free software; you can redistribute it and/or modify
7  *      it under the terms of the GNU General Public License as published by
8  *      the Free Software Foundation; either version 2 of the License, or
9  *      (at your option) any later version.
10  *
11  *      This program is distributed in the hope that it will be useful,
12  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *      GNU General Public License for more details.
15  *
16  *      You should have received a copy of the GNU General Public License along
17  *      with this program; if not, write to the Free Software Foundation, Inc.,
18  *      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 /*
22  * File related dialogs, miscellaneous dialogs, font dialog.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28 
29 #include "dialogs.h"
30 
31 #include "app.h"
32 #include "build.h"
33 #include "document.h"
34 #include "encodings.h"
35 #include "encodingsprivate.h"
36 #include "filetypes.h"
37 #include "main.h"
38 #include "support.h"
39 #include "utils.h"
40 #include "ui_utils.h"
41 #include "win32.h"
42 
43 #include <gtk/gtk.h>
44 #include <gdk/gdkkeysyms.h>
45 #include <string.h>
46 
47 #ifdef HAVE_SYS_TIME_H
48 # include <sys/time.h>
49 #endif
50 #include <time.h>
51 
52 #ifdef HAVE_SYS_TYPES_H
53 # include <sys/types.h>
54 #endif
55 
56 /* gstdio.h also includes sys/stat.h */
57 #include <glib/gstdio.h>
58 
59 
60 enum
61 {
62 	GEANY_RESPONSE_RENAME,
63 	GEANY_RESPONSE_VIEW
64 };
65 
66 
67 static struct FileSelState
68 {
69 	struct
70 	{
71 		guint filter_idx;
72 		gint encoding_idx;
73 		gint filetype_idx;
74 		gboolean show_hidden;
75 		gboolean more_options_visible;
76 	} open;
77 }
78 filesel_state = {
79 	{
80 		0,
81 		GEANY_ENCODINGS_MAX, /* default encoding is detect from file */
82 		-1, /* default filetype is detect from extension */
83 		FALSE,
84 		FALSE
85 	}
86 };
87 
88 
89 static gint filetype_combo_box_get_active_filetype(GtkComboBox *combo);
90 
91 
92 /* gets the ID of the current file filter */
file_chooser_get_filter_idx(GtkFileChooser * chooser)93 static guint file_chooser_get_filter_idx(GtkFileChooser *chooser)
94 {
95 	guint idx = 0;
96 	GtkFileFilter *current;
97 	GSList *filters, *item;
98 
99 	current = gtk_file_chooser_get_filter(chooser);
100 	filters = gtk_file_chooser_list_filters(chooser);
101 	foreach_slist(item, filters)
102 	{
103 		if (item->data == current)
104 			break;
105 		idx ++;
106 	}
107 	g_slist_free(filters);
108 	return idx;
109 }
110 
111 
112 /* sets the current file filter from its ID */
file_chooser_set_filter_idx(GtkFileChooser * chooser,guint idx)113 static void file_chooser_set_filter_idx(GtkFileChooser *chooser, guint idx)
114 {
115 	GtkFileFilter *current;
116 	GSList *filters;
117 
118 	filters = gtk_file_chooser_list_filters(chooser);
119 	current = g_slist_nth_data(filters, idx);
120 	g_slist_free(filters);
121 	gtk_file_chooser_set_filter(chooser, current);
122 }
123 
124 
open_file_dialog_handle_response(GtkWidget * dialog,gint response)125 static gboolean open_file_dialog_handle_response(GtkWidget *dialog, gint response)
126 {
127 	gboolean ret = TRUE;
128 
129 	if (response == GTK_RESPONSE_ACCEPT || response == GEANY_RESPONSE_VIEW)
130 	{
131 		GSList *filelist;
132 		GeanyFiletype *ft = NULL;
133 		const gchar *charset = NULL;
134 		GtkWidget *expander = ui_lookup_widget(dialog, "more_options_expander");
135 		GtkWidget *filetype_combo = ui_lookup_widget(dialog, "filetype_combo");
136 		GtkWidget *encoding_combo = ui_lookup_widget(dialog, "encoding_combo");
137 		gboolean ro = (response == GEANY_RESPONSE_VIEW);	/* View clicked */
138 
139 		filesel_state.open.more_options_visible = gtk_expander_get_expanded(GTK_EXPANDER(expander));
140 		filesel_state.open.filter_idx = file_chooser_get_filter_idx(GTK_FILE_CHOOSER(dialog));
141 		filesel_state.open.filetype_idx = filetype_combo_box_get_active_filetype(GTK_COMBO_BOX(filetype_combo));
142 
143 		/* ignore detect from file item */
144 		if (filesel_state.open.filetype_idx >= 0)
145 			ft = filetypes_index(filesel_state.open.filetype_idx);
146 
147 		filesel_state.open.encoding_idx = ui_encodings_combo_box_get_active_encoding(GTK_COMBO_BOX(encoding_combo));
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;
150 
151 		filelist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
152 		if (filelist != NULL)
153 		{
154 			const gchar *first = filelist->data;
155 
156 			// When there's only one filename it may have been typed manually
157 			if (!filelist->next && !g_file_test(first, G_FILE_TEST_EXISTS))
158 			{
159 				dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("\"%s\" was not found."), first);
160 				ret = FALSE;
161 			}
162 			else
163 			{
164 				document_open_files(filelist, ro, ft, charset);
165 			}
166 			g_slist_foreach(filelist, (GFunc) g_free, NULL);	/* free filenames */
167 		}
168 		g_slist_free(filelist);
169 	}
170 	if (app->project && !EMPTY(app->project->base_path))
171 		gtk_file_chooser_remove_shortcut_folder(GTK_FILE_CHOOSER(dialog),
172 			app->project->base_path, NULL);
173 	return ret;
174 }
175 
176 
on_file_open_show_hidden_notify(GObject * filechooser,GParamSpec * pspec,gpointer data)177 static void on_file_open_show_hidden_notify(GObject *filechooser,
178 	GParamSpec *pspec, gpointer data)
179 {
180 	GtkWidget *toggle_button;
181 
182 	toggle_button = ui_lookup_widget(GTK_WIDGET(filechooser), "check_hidden");
183 
184 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_button),
185 		gtk_file_chooser_get_show_hidden(GTK_FILE_CHOOSER(filechooser)));
186 }
187 
188 
189 static void
on_file_open_check_hidden_toggled(GtkToggleButton * togglebutton,GtkWidget * dialog)190 on_file_open_check_hidden_toggled(GtkToggleButton *togglebutton, GtkWidget *dialog)
191 {
192 	filesel_state.open.show_hidden = gtk_toggle_button_get_active(togglebutton);
193 	gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), filesel_state.open.show_hidden);
194 }
195 
196 
filetype_combo_cell_data_func(GtkCellLayout * cell_layout,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)197 static void filetype_combo_cell_data_func(GtkCellLayout *cell_layout, GtkCellRenderer *cell,
198 		GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
199 {
200 	gboolean sensitive = !gtk_tree_model_iter_has_child(tree_model, iter);
201 	gchar *text;
202 
203 	gtk_tree_model_get(tree_model, iter, 1, &text, -1);
204 	g_object_set(cell, "sensitive", sensitive, "text", text, NULL);
205 	g_free(text);
206 }
207 
208 
create_filetype_combo_box(void)209 static GtkWidget *create_filetype_combo_box(void)
210 {
211 	GtkTreeStore *store;
212 	GtkTreeIter iter_compiled, iter_script, iter_markup, iter_misc, iter_detect;
213 	GtkTreeIter *iter_parent;
214 	GtkWidget *combo;
215 	GtkCellRenderer *renderer;
216 	GSList *node;
217 
218 	store = gtk_tree_store_new(2, G_TYPE_INT, G_TYPE_STRING);
219 
220 	gtk_tree_store_insert_with_values(store, &iter_detect, NULL, -1,
221 			0, -1 /* auto-detect */, 1, _("Detect from file"), -1);
222 
223 	gtk_tree_store_insert_with_values(store, &iter_compiled, NULL, -1,
224 			0, -1, 1, _("Programming Languages"), -1);
225 	gtk_tree_store_insert_with_values(store, &iter_script, NULL, -1,
226 			0, -1, 1, _("Scripting Languages"), -1);
227 	gtk_tree_store_insert_with_values(store, &iter_markup, NULL, -1,
228 			0, -1, 1, _("Markup Languages"), -1);
229 	gtk_tree_store_insert_with_values(store, &iter_misc, NULL, -1,
230 			0, -1, 1, _("Miscellaneous"), -1);
231 
232 	foreach_slist (node, filetypes_by_title)
233 	{
234 		GeanyFiletype *ft = node->data;
235 
236 		switch (ft->group)
237 		{
238 			case GEANY_FILETYPE_GROUP_COMPILED: iter_parent = &iter_compiled; break;
239 			case GEANY_FILETYPE_GROUP_SCRIPT: iter_parent = &iter_script; break;
240 			case GEANY_FILETYPE_GROUP_MARKUP: iter_parent = &iter_markup; break;
241 			case GEANY_FILETYPE_GROUP_MISC: iter_parent = &iter_misc; break;
242 			case GEANY_FILETYPE_GROUP_NONE:
243 			default: iter_parent = NULL;
244 		}
245 		gtk_tree_store_insert_with_values(store, NULL, iter_parent, -1,
246 				0, (gint) ft->id, 1, ft->title, -1);
247 	}
248 
249 	combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
250 	gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo), &iter_detect);
251 	renderer = gtk_cell_renderer_text_new();
252 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
253 	gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo), renderer,
254 			filetype_combo_cell_data_func, NULL, NULL);
255 
256 	g_object_unref(store);
257 
258 	return combo;
259 }
260 
261 
262 /* the filetype, or -1 for auto-detect */
filetype_combo_box_get_active_filetype(GtkComboBox * combo)263 static gint filetype_combo_box_get_active_filetype(GtkComboBox *combo)
264 {
265 	gint id = -1;
266 	GtkTreeIter iter;
267 
268 	if (gtk_combo_box_get_active_iter(combo, &iter))
269 	{
270 		GtkTreeModel *model = gtk_combo_box_get_model(combo);
271 		gtk_tree_model_get(model, &iter, 0, &id, -1);
272 	}
273 	return id;
274 }
275 
276 
filetype_combo_box_set_active_filetype(GtkComboBox * combo,const gint id)277 static gboolean filetype_combo_box_set_active_filetype(GtkComboBox *combo, const gint id)
278 {
279 	GtkTreeModel *model = gtk_combo_box_get_model(combo);
280 	GtkTreeIter iter;
281 
282 	if (gtk_tree_model_get_iter_first(model, &iter))
283 	{
284 		do
285 		{
286 			gint row_id;
287 			gtk_tree_model_get(model, &iter, 0, &row_id, -1);
288 			if (id == row_id)
289 			{
290 				gtk_combo_box_set_active_iter(combo, &iter);
291 				return TRUE;
292 			}
293 		}
294 		while (ui_tree_model_iter_any_next(model, &iter, TRUE));
295 	}
296 	return FALSE;
297 }
298 
299 
add_file_open_extra_widget(GtkWidget * dialog)300 static GtkWidget *add_file_open_extra_widget(GtkWidget *dialog)
301 {
302 	GtkWidget *expander, *vbox, *table, *check_hidden;
303 	GtkWidget *filetype_ebox, *filetype_label, *filetype_combo;
304 	GtkWidget *encoding_ebox, *encoding_label, *encoding_combo;
305 
306 	expander = gtk_expander_new_with_mnemonic(_("_More Options"));
307 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
308 	gtk_container_add(GTK_CONTAINER(expander), vbox);
309 
310 	table = gtk_table_new(2, 4, FALSE);
311 
312 	/* line 1 with checkbox and encoding combo */
313 	check_hidden = gtk_check_button_new_with_mnemonic(_("Show _hidden files"));
314 	gtk_widget_show(check_hidden);
315 	gtk_table_attach(GTK_TABLE(table), check_hidden, 0, 1, 0, 1,
316 					(GtkAttachOptions) (GTK_FILL | GTK_EXPAND),
317 					(GtkAttachOptions) (0), 0, 5);
318 
319 	/* spacing */
320 	gtk_table_attach(GTK_TABLE(table), gtk_label_new(""), 1, 2, 0, 1,
321 					(GtkAttachOptions) (GTK_FILL),
322 					(GtkAttachOptions) (0), 5, 5);
323 
324 	encoding_label = gtk_label_new(_("Set encoding:"));
325 	gtk_misc_set_alignment(GTK_MISC(encoding_label), 1, 0);
326 	gtk_table_attach(GTK_TABLE(table), encoding_label, 2, 3, 0, 1,
327 					(GtkAttachOptions) (GTK_FILL),
328 					(GtkAttachOptions) (0), 4, 5);
329 	/* the ebox is for the tooltip, because gtk_combo_box can't show tooltips */
330 	encoding_ebox = gtk_event_box_new();
331 	encoding_combo = ui_create_encodings_combo_box(TRUE, GEANY_ENCODINGS_MAX);
332 	gtk_widget_set_tooltip_text(encoding_ebox,
333 		_("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."));
334 	gtk_container_add(GTK_CONTAINER(encoding_ebox), encoding_combo);
335 	gtk_table_attach(GTK_TABLE(table), encoding_ebox, 3, 4, 0, 1,
336 					(GtkAttachOptions) (GTK_FILL),
337 					(GtkAttachOptions) (0), 0, 5);
338 
339 	/* line 2 with filetype combo */
340 	filetype_label = gtk_label_new(_("Set filetype:"));
341 	gtk_misc_set_alignment(GTK_MISC(filetype_label), 1, 0);
342 	gtk_table_attach(GTK_TABLE(table), filetype_label, 2, 3, 1, 2,
343 					(GtkAttachOptions) (GTK_FILL),
344 					(GtkAttachOptions) (0), 4, 5);
345 	/* the ebox is for the tooltip, because gtk_combo_box can't show tooltips */
346 	filetype_ebox = gtk_event_box_new();
347 	filetype_combo = create_filetype_combo_box();
348 	gtk_widget_set_tooltip_text(filetype_ebox,
349 		_("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."));
350 	gtk_container_add(GTK_CONTAINER(filetype_ebox), filetype_combo);
351 	gtk_table_attach(GTK_TABLE(table), filetype_ebox, 3, 4, 1, 2,
352 					(GtkAttachOptions) (GTK_FILL),
353 					(GtkAttachOptions) (0), 0, 5);
354 
355 	gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
356 	gtk_widget_show_all(vbox);
357 
358 	g_signal_connect(check_hidden, "toggled", G_CALLBACK(on_file_open_check_hidden_toggled), dialog);
359 
360 	ui_hookup_widget(dialog, expander, "more_options_expander");
361 	ui_hookup_widget(dialog, check_hidden, "check_hidden");
362 	ui_hookup_widget(dialog, filetype_combo, "filetype_combo");
363 	ui_hookup_widget(dialog, encoding_combo, "encoding_combo");
364 
365 	return expander;
366 }
367 
368 
create_open_file_dialog(void)369 static GtkWidget *create_open_file_dialog(void)
370 {
371 	GtkWidget *dialog;
372 	GtkWidget *viewbtn;
373 	GSList *node;
374 
375 	dialog = gtk_file_chooser_dialog_new(_("Open File"), GTK_WINDOW(main_widgets.window),
376 			GTK_FILE_CHOOSER_ACTION_OPEN, NULL, NULL);
377 	gtk_widget_set_name(dialog, "GeanyDialog");
378 
379 	viewbtn = gtk_dialog_add_button(GTK_DIALOG(dialog), C_("Open dialog action", "_View"), GEANY_RESPONSE_VIEW);
380 	gtk_widget_set_tooltip_text(viewbtn,
381 		_("Opens the file in read-only mode. If you choose more than one file to open, all files will be opened read-only."));
382 
383 	gtk_dialog_add_buttons(GTK_DIALOG(dialog),
384 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
385 		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
386 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
387 
388 	gtk_widget_set_size_request(dialog, -1, 460);
389 	gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
390 	gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
391 	gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
392 	gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
393 	gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main_widgets.window));
394 	gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
395 	gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
396 
397 	/* add checkboxes and filename entry */
398 	gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), add_file_open_extra_widget(dialog));
399 
400 	/* add FileFilters(start with "All Files") */
401 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
402 				filetypes_create_file_filter(filetypes[GEANY_FILETYPES_NONE]));
403 	/* now create meta filter "All Source" */
404 	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
405 				filetypes_create_file_filter_all_source());
406 	foreach_slist(node, filetypes_by_title)
407 	{
408 		GeanyFiletype *ft = node->data;
409 
410 		if (G_UNLIKELY(ft->id == GEANY_FILETYPES_NONE))
411 			continue;
412 		gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filetypes_create_file_filter(ft));
413 	}
414 
415 	g_signal_connect(dialog, "notify::show-hidden",
416 		G_CALLBACK(on_file_open_show_hidden_notify), NULL);
417 
418 	return dialog;
419 }
420 
421 
open_file_dialog_apply_settings(GtkWidget * dialog)422 static void open_file_dialog_apply_settings(GtkWidget *dialog)
423 {
424 	static gboolean initialized = FALSE;
425 	GtkWidget *check_hidden = ui_lookup_widget(dialog, "check_hidden");
426 	GtkWidget *filetype_combo = ui_lookup_widget(dialog, "filetype_combo");
427 	GtkWidget *encoding_combo = ui_lookup_widget(dialog, "encoding_combo");
428 	GtkWidget *expander = ui_lookup_widget(dialog, "more_options_expander");
429 
430 	/* we can't know the initial position of combo boxes, so retrieve it the first time */
431 	if (! initialized)
432 	{
433 		filesel_state.open.filter_idx = file_chooser_get_filter_idx(GTK_FILE_CHOOSER(dialog));
434 
435 		initialized = TRUE;
436 	}
437 	else
438 	{
439 		file_chooser_set_filter_idx(GTK_FILE_CHOOSER(dialog), filesel_state.open.filter_idx);
440 	}
441 	gtk_expander_set_expanded(GTK_EXPANDER(expander), filesel_state.open.more_options_visible);
442 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_hidden), filesel_state.open.show_hidden);
443 	ui_encodings_combo_box_set_active_encoding(GTK_COMBO_BOX(encoding_combo), filesel_state.open.encoding_idx);
444 	filetype_combo_box_set_active_filetype(GTK_COMBO_BOX(filetype_combo), filesel_state.open.filetype_idx);
445 }
446 
447 
448 /* This shows the file selection dialog to open a file. */
dialogs_show_open_file(void)449 void dialogs_show_open_file(void)
450 {
451 	gchar *initdir;
452 
453 	/* set dialog directory to the current file's directory, if present */
454 	initdir = utils_get_current_file_dir_utf8();
455 
456 	/* use project or default startup directory (if set) if no files are open */
457 	/** TODO should it only be used when initially open the dialog and not on every show? */
458 	if (! initdir)
459 		initdir = g_strdup(utils_get_default_dir_utf8());
460 
461 	SETPTR(initdir, utils_get_locale_from_utf8(initdir));
462 
463 #ifdef G_OS_WIN32
464 	if (interface_prefs.use_native_windows_dialogs)
465 		win32_show_document_open_dialog(GTK_WINDOW(main_widgets.window), _("Open File"), initdir);
466 	else
467 #endif
468 	{
469 		GtkWidget *dialog = create_open_file_dialog();
470 
471 		open_file_dialog_apply_settings(dialog);
472 
473 		if (initdir != NULL && g_path_is_absolute(initdir))
474 				gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), initdir);
475 
476 		if (app->project && !EMPTY(app->project->base_path))
477 			gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog),
478 					app->project->base_path, NULL);
479 
480 		while (!open_file_dialog_handle_response(dialog,
481 			gtk_dialog_run(GTK_DIALOG(dialog))));
482 		gtk_widget_destroy(dialog);
483 	}
484 	g_free(initdir);
485 }
486 
487 
handle_save_as(const gchar * utf8_filename,gboolean rename_file)488 static gboolean handle_save_as(const gchar *utf8_filename, gboolean rename_file)
489 {
490 	GeanyDocument *doc = document_get_current();
491 	gboolean success = FALSE;
492 
493 	g_return_val_if_fail(doc != NULL, FALSE);
494 	g_return_val_if_fail(!EMPTY(utf8_filename), FALSE);
495 
496 	if (doc->file_name != NULL)
497 	{
498 		if (rename_file)
499 		{
500 			document_rename_file(doc, utf8_filename);
501 		}
502 		if (doc->tm_file)
503 		{
504 			/* create a new tm_source_file object otherwise tagmanager won't work correctly */
505 			tm_workspace_remove_source_file(doc->tm_file);
506 			tm_source_file_free(doc->tm_file);
507 			doc->tm_file = NULL;
508 		}
509 	}
510 	success = document_save_file_as(doc, utf8_filename);
511 
512 	build_menu_update(doc);
513 	return success;
514 }
515 
516 
save_as_dialog_handle_response(GtkWidget * dialog,gint response)517 static gboolean save_as_dialog_handle_response(GtkWidget *dialog, gint response)
518 {
519 	gboolean rename_file = FALSE;
520 	gboolean success = FALSE;
521 	gchar *new_filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
522 
523 	switch (response)
524 	{
525 		case GEANY_RESPONSE_RENAME:
526 			/* rename doesn't check for empty filename or overwriting */
527 			if (G_UNLIKELY(EMPTY(new_filename)))
528 			{
529 				utils_beep();
530 				break;
531 			}
532 			if (g_file_test(new_filename, G_FILE_TEST_EXISTS) &&
533 				!dialogs_show_question_full(NULL, NULL, NULL,
534 					_("Overwrite?"),
535 					_("Filename already exists!")))
536 				break;
537 			rename_file = TRUE;
538 			/* fall through */
539 		case GTK_RESPONSE_ACCEPT:
540 		{
541 			gchar *utf8_filename;
542 
543 			utf8_filename = utils_get_utf8_from_locale(new_filename);
544 			success = handle_save_as(utf8_filename, rename_file);
545 			g_free(utf8_filename);
546 			break;
547 		}
548 		case GTK_RESPONSE_DELETE_EVENT:
549 		case GTK_RESPONSE_CANCEL:
550 			success = TRUE;
551 			break;
552 	}
553 	g_free(new_filename);
554 
555 	return success;
556 }
557 
558 
create_save_file_dialog(GeanyDocument * doc)559 static GtkWidget *create_save_file_dialog(GeanyDocument *doc)
560 {
561 	GtkWidget *dialog, *rename_btn;
562 	const gchar *initdir;
563 
564 	dialog = gtk_file_chooser_dialog_new(_("Save File"), GTK_WINDOW(main_widgets.window),
565 				GTK_FILE_CHOOSER_ACTION_SAVE, NULL, NULL);
566 	gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
567 	gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
568 	gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
569 	gtk_window_set_type_hint(GTK_WINDOW(dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
570 	gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main_widgets.window));
571 	gtk_widget_set_name(dialog, "GeanyDialog");
572 
573 	rename_btn = gtk_dialog_add_button(GTK_DIALOG(dialog), _("R_ename"), GEANY_RESPONSE_RENAME);
574 	gtk_widget_set_tooltip_text(rename_btn, _("Save the file and rename it"));
575 	/* disable rename unless file exists on disk */
576 	gtk_widget_set_sensitive(rename_btn, doc->real_path != NULL);
577 
578 	gtk_dialog_add_buttons(GTK_DIALOG(dialog),
579 		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
580 		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
581 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
582 
583 	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
584 	gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
585 
586 	/* set the folder by default to the project base dir or the global pref for opening files */
587 	initdir = utils_get_default_dir_utf8();
588 	if (initdir)
589 	{
590 		gchar *linitdir = utils_get_locale_from_utf8(initdir);
591 		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), linitdir);
592 		g_free(linitdir);
593 	}
594 	return dialog;
595 }
596 
597 
show_save_as_gtk(GeanyDocument * doc)598 static gboolean show_save_as_gtk(GeanyDocument *doc)
599 {
600 	GtkWidget *dialog;
601 	gint resp;
602 
603 	g_return_val_if_fail(DOC_VALID(doc), FALSE);
604 
605 	dialog = create_save_file_dialog(doc);
606 
607 	if (doc->file_name != NULL)
608 	{
609 		if (g_path_is_absolute(doc->file_name))
610 		{
611 			gchar *locale_filename = utils_get_locale_from_utf8(doc->file_name);
612 			gchar *locale_basename = g_path_get_basename(locale_filename);
613 			gchar *locale_dirname = g_path_get_dirname(locale_filename);
614 
615 			gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), locale_dirname);
616 			gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), locale_basename);
617 
618 			g_free(locale_filename);
619 			g_free(locale_basename);
620 			g_free(locale_dirname);
621 		}
622 		else
623 			gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), doc->file_name);
624 	}
625 	else
626 	{
627 		gchar *fname = NULL;
628 
629 		if (doc->file_type != NULL && doc->file_type->extension != NULL)
630 			fname = g_strconcat(GEANY_STRING_UNTITLED, ".",
631 								doc->file_type->extension, NULL);
632 		else
633 			fname = g_strdup(GEANY_STRING_UNTITLED);
634 
635 		gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), fname);
636 
637 		g_free(fname);
638 	}
639 
640 	if (app->project && !EMPTY(app->project->base_path))
641 		gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog),
642 			app->project->base_path, NULL);
643 
644 	/* Run the dialog synchronously, pausing this function call */
645 	do
646 	{
647 		resp = gtk_dialog_run(GTK_DIALOG(dialog));
648 	}
649 	while (! save_as_dialog_handle_response(dialog, resp));
650 
651 	if (app->project && !EMPTY(app->project->base_path))
652 		gtk_file_chooser_remove_shortcut_folder(GTK_FILE_CHOOSER(dialog),
653 			app->project->base_path, NULL);
654 
655 	gtk_widget_destroy(dialog);
656 
657 	return (resp == GTK_RESPONSE_ACCEPT);
658 }
659 
660 
661 /**
662  *  Shows the Save As dialog for the current notebook page.
663  *
664  *  @return @c TRUE if the file was saved, otherwise @c FALSE.
665  **/
666 GEANY_API_SYMBOL
dialogs_show_save_as(void)667 gboolean dialogs_show_save_as(void)
668 {
669 	GeanyDocument *doc = document_get_current();
670 	gboolean result = FALSE;
671 
672 	g_return_val_if_fail(doc, FALSE);
673 
674 #ifdef G_OS_WIN32
675 	if (interface_prefs.use_native_windows_dialogs)
676 	{
677 		gchar *utf8_name = win32_show_document_save_as_dialog(GTK_WINDOW(main_widgets.window),
678 						_("Save File"), doc);
679 		if (utf8_name != NULL)
680 			result = handle_save_as(utf8_name, FALSE);
681 	}
682 	else
683 #endif
684 	result = show_save_as_gtk(doc);
685 	return result;
686 }
687 
688 
689 #ifndef G_OS_WIN32
show_msgbox_dialog(GtkWidget * dialog,GtkMessageType type,GtkWindow * parent)690 static void show_msgbox_dialog(GtkWidget *dialog, GtkMessageType type, GtkWindow *parent)
691 {
692 	const gchar *title;
693 	switch (type)
694 	{
695 		case GTK_MESSAGE_ERROR:
696 			title = _("Error");
697 			break;
698 		case GTK_MESSAGE_QUESTION:
699 			title = _("Question");
700 			break;
701 		case GTK_MESSAGE_WARNING:
702 			title = _("Warning");
703 			break;
704 		default:
705 			title = _("Information");
706 			break;
707 	}
708 	gtk_window_set_title(GTK_WINDOW(dialog), title);
709 	gtk_window_set_icon_name(GTK_WINDOW(dialog), "geany");
710 	gtk_widget_set_name(dialog, "GeanyDialog");
711 
712 	gtk_dialog_run(GTK_DIALOG(dialog));
713 	gtk_widget_destroy(dialog);
714 }
715 #endif
716 
717 
718 /**
719  *  Shows a message box of the type @a type with @a text.
720  *  On Unix-like systems a GTK message dialog box is shown, on Win32 systems a native Windows
721  *  message dialog box is shown.
722  *
723  *  @param type A @c GtkMessageType, e.g. @c GTK_MESSAGE_INFO, @c GTK_MESSAGE_WARNING,
724  *              @c GTK_MESSAGE_QUESTION, @c GTK_MESSAGE_ERROR.
725  *  @param text Printf()-style format string.
726  *  @param ... Arguments for the @a text format string.
727  **/
728 GEANY_API_SYMBOL
dialogs_show_msgbox(GtkMessageType type,const gchar * text,...)729 void dialogs_show_msgbox(GtkMessageType type, const gchar *text, ...)
730 {
731 #ifndef G_OS_WIN32
732 	GtkWidget *dialog;
733 #endif
734 	gchar *string;
735 	va_list args;
736 	GtkWindow *parent = (main_status.main_window_realized) ? GTK_WINDOW(main_widgets.window) : NULL;
737 
738 	va_start(args, text);
739 	string = g_strdup_vprintf(text, args);
740 	va_end(args);
741 
742 #ifdef G_OS_WIN32
743 	win32_message_dialog(GTK_WIDGET(parent), type, string);
744 #else
745 	dialog = gtk_message_dialog_new(parent, GTK_DIALOG_DESTROY_WITH_PARENT,
746 			type, GTK_BUTTONS_OK, "%s", string);
747 	show_msgbox_dialog(dialog, type, parent);
748 #endif
749 	g_free(string);
750 }
751 
752 
dialogs_show_msgbox_with_secondary(GtkMessageType type,const gchar * text,const gchar * secondary)753 void dialogs_show_msgbox_with_secondary(GtkMessageType type, const gchar *text, const gchar *secondary)
754 {
755 	GtkWindow *parent = (main_status.main_window_realized) ? GTK_WINDOW(main_widgets.window) : NULL;
756 #ifdef G_OS_WIN32
757 	/* put the two strings together because Windows message boxes don't support secondary texts */
758 	gchar *string = g_strconcat(text, "\n", secondary, NULL);
759 	win32_message_dialog(GTK_WIDGET(parent), type, string);
760 	g_free(string);
761 #else
762 	GtkWidget *dialog;
763 	dialog = gtk_message_dialog_new(parent, GTK_DIALOG_DESTROY_WITH_PARENT,
764 			type, GTK_BUTTONS_OK, "%s", text);
765 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", secondary);
766 	show_msgbox_dialog(dialog, type, parent);
767 #endif
768 }
769 
770 
run_unsaved_dialog(const gchar * msg,const gchar * msg2)771 static gint run_unsaved_dialog(const gchar *msg, const gchar *msg2)
772 {
773 	GtkWidget *dialog, *button;
774 	gint ret;
775 
776 	dialog = gtk_message_dialog_new(GTK_WINDOW(main_widgets.window), GTK_DIALOG_DESTROY_WITH_PARENT,
777 			GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", msg);
778 	gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
779 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", msg2);
780 	gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
781 
782 	button = ui_button_new_with_image(GTK_STOCK_CLEAR, _("_Don't save"));
783 	gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, GTK_RESPONSE_NO);
784 	gtk_widget_show(button);
785 
786 	gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_SAVE, GTK_RESPONSE_YES);
787 
788 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES);
789 	ret = gtk_dialog_run(GTK_DIALOG(dialog));
790 
791 	gtk_widget_destroy(dialog);
792 
793 	return ret;
794 }
795 
796 
dialogs_show_unsaved_file(GeanyDocument * doc)797 gboolean dialogs_show_unsaved_file(GeanyDocument *doc)
798 {
799 	gchar *msg, *short_fn = NULL;
800 	const gchar *msg2;
801 	gint response;
802 	gboolean old_quitting_state = main_status.quitting;
803 
804 	/* display the file tab to remind the user of the document */
805 	main_status.quitting = FALSE;
806 	document_show_tab(doc);
807 	main_status.quitting = old_quitting_state;
808 
809 	short_fn = document_get_basename_for_display(doc, -1);
810 
811 	msg = g_strdup_printf(_("The file '%s' is not saved."), short_fn);
812 	msg2 = _("Do you want to save it before closing?");
813 	g_free(short_fn);
814 
815 	response = run_unsaved_dialog(msg, msg2);
816 	g_free(msg);
817 
818 	switch (response)
819 	{
820 		case GTK_RESPONSE_YES:
821 			/* document_save_file() returns the status if the file could be saved */
822 			return document_save_file(doc, FALSE);
823 
824 		case GTK_RESPONSE_NO:
825 			return TRUE;
826 
827 		case GTK_RESPONSE_CANCEL: /* fall through to default and leave the function */
828 		default:
829 			return FALSE;
830 	}
831 }
832 
833 
834 /* Use GtkFontChooserDialog on GTK3.2 for consistency, and because
835  * GtkFontSelectionDialog is somewhat broken on 3.4 */
836 #if GTK_CHECK_VERSION(3, 2, 0)
837 #	undef GTK_FONT_SELECTION_DIALOG
838 #	define GTK_FONT_SELECTION_DIALOG				GTK_FONT_CHOOSER_DIALOG
839 
840 #	define gtk_font_selection_dialog_new(title) \
841 		gtk_font_chooser_dialog_new((title), NULL)
842 #	define gtk_font_selection_dialog_get_font_name(dlg) \
843 		gtk_font_chooser_get_font(GTK_FONT_CHOOSER(dlg))
844 #	define gtk_font_selection_dialog_set_font_name(dlg, font) \
845 		gtk_font_chooser_set_font(GTK_FONT_CHOOSER(dlg), (font))
846 #endif
847 
848 static void
on_font_dialog_response(GtkDialog * dialog,gint response,gpointer user_data)849 on_font_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
850 {
851 	gboolean close = TRUE;
852 
853 	switch (response)
854 	{
855 		case GTK_RESPONSE_APPLY:
856 		case GTK_RESPONSE_OK:
857 		{
858 			gchar *fontname;
859 
860 			fontname = gtk_font_selection_dialog_get_font_name(
861 				GTK_FONT_SELECTION_DIALOG(ui_widgets.open_fontsel));
862 			ui_set_editor_font(fontname);
863 			g_free(fontname);
864 
865 			close = (response == GTK_RESPONSE_OK);
866 			break;
867 		}
868 	}
869 
870 	if (close)
871 		gtk_widget_hide(ui_widgets.open_fontsel);
872 }
873 
874 
875 /* This shows the font selection dialog to choose a font. */
dialogs_show_open_font(void)876 void dialogs_show_open_font(void)
877 {
878 #ifdef G_OS_WIN32
879 	if (interface_prefs.use_native_windows_dialogs)
880 	{
881 		win32_show_font_dialog();
882 		return;
883 	}
884 #endif
885 
886 	if (ui_widgets.open_fontsel == NULL)
887 	{
888 		GtkWidget *apply_button;
889 
890 		ui_widgets.open_fontsel = gtk_font_selection_dialog_new(_("Choose font"));;
891 		gtk_container_set_border_width(GTK_CONTAINER(ui_widgets.open_fontsel), 4);
892 		gtk_window_set_modal(GTK_WINDOW(ui_widgets.open_fontsel), TRUE);
893 		gtk_window_set_destroy_with_parent(GTK_WINDOW(ui_widgets.open_fontsel), TRUE);
894 		gtk_window_set_skip_taskbar_hint(GTK_WINDOW(ui_widgets.open_fontsel), TRUE);
895 		gtk_window_set_type_hint(GTK_WINDOW(ui_widgets.open_fontsel), GDK_WINDOW_TYPE_HINT_DIALOG);
896 		gtk_widget_set_name(ui_widgets.open_fontsel, "GeanyDialog");
897 
898 		apply_button = gtk_dialog_get_widget_for_response(GTK_DIALOG(ui_widgets.open_fontsel), GTK_RESPONSE_APPLY);
899 
900 		if (apply_button)
901 			gtk_widget_show(apply_button);
902 
903 		g_signal_connect(ui_widgets.open_fontsel,
904 					"delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
905 		g_signal_connect(ui_widgets.open_fontsel,
906 					"response", G_CALLBACK(on_font_dialog_response), NULL);
907 
908 		gtk_window_set_transient_for(GTK_WINDOW(ui_widgets.open_fontsel), GTK_WINDOW(main_widgets.window));
909 	}
910 	gtk_font_selection_dialog_set_font_name(
911 		GTK_FONT_SELECTION_DIALOG(ui_widgets.open_fontsel), interface_prefs.editor_font);
912 	/* We make sure the dialog is visible. */
913 	gtk_window_present(GTK_WINDOW(ui_widgets.open_fontsel));
914 }
915 
916 
917 static void
on_input_dialog_show(GtkDialog * dialog,GtkWidget * entry)918 on_input_dialog_show(GtkDialog *dialog, GtkWidget *entry)
919 {
920 	gtk_widget_grab_focus(entry);
921 }
922 
923 
924 static void
on_input_entry_activate(GtkEntry * entry,GtkDialog * dialog)925 on_input_entry_activate(GtkEntry *entry, GtkDialog *dialog)
926 {
927 	gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT);
928 }
929 
930 
931 static void
on_input_numeric_activate(GtkEntry * entry,GtkDialog * dialog)932 on_input_numeric_activate(GtkEntry *entry, GtkDialog *dialog)
933 {
934 	gtk_dialog_response(dialog, GTK_RESPONSE_ACCEPT);
935 }
936 
937 
938 typedef struct
939 {
940 	GtkWidget *entry;
941 	GtkWidget *combo;
942 
943 	GeanyInputCallback callback;
944 	gpointer data;
945 }
946 InputDialogData;
947 
948 
949 static void
on_input_dialog_response(GtkDialog * dialog,gint response,InputDialogData * data)950 on_input_dialog_response(GtkDialog *dialog, gint response, InputDialogData *data)
951 {
952 	if (response == GTK_RESPONSE_ACCEPT)
953 	{
954 		const gchar *str = gtk_entry_get_text(GTK_ENTRY(data->entry));
955 
956 		if (data->combo != NULL)
957 			ui_combo_box_add_to_history(GTK_COMBO_BOX_TEXT(data->combo), str, 0);
958 
959 		data->callback(str, data->data);
960 	}
961 	gtk_widget_hide(GTK_WIDGET(dialog));
962 }
963 
964 
965 /* Create and display an input dialog.
966  * persistent: whether to remember previous entry text in a combo box;
967  * 	in this case the dialog returned is not destroyed on a response,
968  * 	and can be reshown.
969  * Returns: the dialog widget. */
970 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)971 dialogs_show_input_full(const gchar *title, GtkWindow *parent,
972 						const gchar *label_text, const gchar *default_text,
973 						gboolean persistent, GeanyInputCallback input_cb, gpointer input_cb_data,
974 						GCallback insert_text_cb, gpointer insert_text_cb_data)
975 {
976 	GtkWidget *dialog, *vbox;
977 	InputDialogData *data = g_malloc(sizeof *data);
978 
979 	dialog = gtk_dialog_new_with_buttons(title, parent,
980 		GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
981 		GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
982 	vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
983 	gtk_widget_set_name(dialog, "GeanyDialog");
984 	gtk_box_set_spacing(GTK_BOX(vbox), 6);
985 
986 	data->combo = NULL;
987 	data->entry = NULL;
988 	data->callback = input_cb;
989 	data->data = input_cb_data;
990 
991 	if (label_text)
992 	{
993 		GtkWidget *label = gtk_label_new(label_text);
994 		gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
995 		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
996 		gtk_container_add(GTK_CONTAINER(vbox), label);
997 	}
998 
999 	if (persistent)	/* remember previous entry text in a combo box */
1000 	{
1001 		data->combo = gtk_combo_box_text_new_with_entry();
1002 		data->entry = gtk_bin_get_child(GTK_BIN(data->combo));
1003 		ui_entry_add_clear_icon(GTK_ENTRY(data->entry));
1004 		gtk_container_add(GTK_CONTAINER(vbox), data->combo);
1005 	}
1006 	else
1007 	{
1008 		data->entry = gtk_entry_new();
1009 		ui_entry_add_clear_icon(GTK_ENTRY(data->entry));
1010 		gtk_container_add(GTK_CONTAINER(vbox), data->entry);
1011 	}
1012 
1013 	if (default_text != NULL)
1014 	{
1015 		gtk_entry_set_text(GTK_ENTRY(data->entry), default_text);
1016 	}
1017 	gtk_entry_set_max_length(GTK_ENTRY(data->entry), 255);
1018 	gtk_entry_set_width_chars(GTK_ENTRY(data->entry), 30);
1019 
1020 	if (insert_text_cb != NULL)
1021 		g_signal_connect(data->entry, "insert-text", insert_text_cb, insert_text_cb_data);
1022 	g_signal_connect(data->entry, "activate", G_CALLBACK(on_input_entry_activate), dialog);
1023 	g_signal_connect(dialog, "show", G_CALLBACK(on_input_dialog_show), data->entry);
1024 	g_signal_connect_data(dialog, "response", G_CALLBACK(on_input_dialog_response), data, (GClosureNotify)g_free, 0);
1025 
1026 	if (persistent)
1027 	{
1028 		/* override default handler */
1029 		g_signal_connect(dialog, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
1030 		gtk_widget_show_all(dialog);
1031 		return dialog;
1032 	}
1033 	gtk_widget_show_all(dialog);
1034 	gtk_dialog_run(GTK_DIALOG(dialog));
1035 	gtk_widget_destroy(dialog);
1036 	return NULL;
1037 }
1038 
1039 
1040 /* Remember previous entry text in a combo box.
1041  * Returns: the dialog widget. */
1042 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)1043 dialogs_show_input_persistent(const gchar *title, GtkWindow *parent,
1044 		const gchar *label_text, const gchar *default_text,
1045 		GeanyInputCallback input_cb, gpointer input_cb_data)
1046 {
1047 	return dialogs_show_input_full(title, parent, label_text, default_text, TRUE, input_cb, input_cb_data, NULL, NULL);
1048 }
1049 
1050 
on_dialog_input(const gchar * str,gpointer data)1051 static void on_dialog_input(const gchar *str, gpointer data)
1052 {
1053 	gchar **dialog_input = data;
1054 	*dialog_input = g_strdup(str);
1055 }
1056 
1057 
1058 /** Asks the user for text input.
1059  * @param title Dialog title.
1060  * @param parent @nullable The currently focused window, usually @c geany->main_widgets->window.
1061  * 	@c NULL can be used but is discouraged due to window manager effects.
1062  * @param label_text @nullable Label text, or @c NULL.
1063  * @param default_text @nullable Text to display in the input field, or @c NULL.
1064  * @return @nullable New copy of user input or @c NULL if cancelled.
1065  * @since 0.20. */
1066 GEANY_API_SYMBOL
dialogs_show_input(const gchar * title,GtkWindow * parent,const gchar * label_text,const gchar * default_text)1067 gchar *dialogs_show_input(const gchar *title, GtkWindow *parent, const gchar *label_text,
1068 	const gchar *default_text)
1069 {
1070 	gchar *dialog_input = NULL;
1071 	dialogs_show_input_full(title, parent, label_text, default_text, FALSE, on_dialog_input, &dialog_input, NULL, NULL);
1072 	return dialog_input;
1073 }
1074 
1075 
1076 /* Note: could be changed to dialogs_show_validated_input with argument for callback. */
1077 /* Returns: newly allocated copy of the entry text or NULL on cancel.
1078  * Specialised variant for Goto Line dialog. */
dialogs_show_input_goto_line(const gchar * title,GtkWindow * parent,const gchar * label_text,const gchar * default_text)1079 gchar *dialogs_show_input_goto_line(const gchar *title, GtkWindow *parent, const gchar *label_text,
1080 	const gchar *default_text)
1081 {
1082 	gchar *dialog_input = NULL;
1083 	dialogs_show_input_full(
1084 		title, parent, label_text, default_text, FALSE, on_dialog_input, &dialog_input,
1085 		G_CALLBACK(ui_editable_insert_text_callback), NULL);
1086 	return dialog_input;
1087 }
1088 
1089 
1090 /**
1091  *  Shows an input box to enter a numerical value using a GtkSpinButton.
1092  *  If the dialog is aborted, @a value remains untouched.
1093  *
1094  *  @param title The dialog title.
1095  *  @param label_text The shown dialog label.
1096  *  @param value The default value for the spin button and the return location of the entered value.
1097  * 				 Must be non-NULL.
1098  *  @param min Minimum allowable value (see documentation for @c gtk_spin_button_new_with_range()).
1099  *  @param max Maximum allowable value (see documentation for @c gtk_spin_button_new_with_range()).
1100  *  @param step Increment added or subtracted by spinning the widget
1101  * 				(see documentation for @c gtk_spin_button_new_with_range()).
1102  *
1103  *  @return @c TRUE if a value was entered and the dialog closed with 'OK'. @c FALSE otherwise.
1104  *
1105  *  @since 0.16
1106  **/
1107 GEANY_API_SYMBOL
dialogs_show_input_numeric(const gchar * title,const gchar * label_text,gdouble * value,gdouble min,gdouble max,gdouble step)1108 gboolean dialogs_show_input_numeric(const gchar *title, const gchar *label_text,
1109 									gdouble *value, gdouble min, gdouble max, gdouble step)
1110 {
1111 	GtkWidget *dialog, *label, *spin, *vbox;
1112 	gboolean res = FALSE;
1113 
1114 	g_return_val_if_fail(title != NULL, FALSE);
1115 	g_return_val_if_fail(label_text != NULL, FALSE);
1116 	g_return_val_if_fail(value != NULL, FALSE);
1117 
1118 	dialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(main_widgets.window),
1119 										GTK_DIALOG_DESTROY_WITH_PARENT,
1120 										GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1121 										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
1122 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
1123 	vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog));
1124 	gtk_widget_set_name(dialog, "GeanyDialog");
1125 
1126 	label = gtk_label_new(label_text);
1127 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1128 
1129 	spin = gtk_spin_button_new_with_range(min, max, step);
1130 	ui_entry_add_clear_icon(GTK_ENTRY(spin));
1131 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), *value);
1132 	g_signal_connect(spin, "activate", G_CALLBACK(on_input_numeric_activate), dialog);
1133 
1134 	gtk_container_add(GTK_CONTAINER(vbox), label);
1135 	gtk_container_add(GTK_CONTAINER(vbox), spin);
1136 	gtk_widget_show_all(vbox);
1137 
1138 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1139 	{
1140 		*value = gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
1141 		res = TRUE;
1142 	}
1143 	gtk_widget_destroy(dialog);
1144 
1145 	return res;
1146 }
1147 
1148 
dialogs_show_file_properties(GeanyDocument * doc)1149 void dialogs_show_file_properties(GeanyDocument *doc)
1150 {
1151 	GtkWidget *dialog, *label, *image, *check;
1152 	gchar *file_size, *title, *base_name, *time_changed, *time_modified, *time_accessed, *enctext;
1153 	gchar *short_name;
1154 #ifdef HAVE_SYS_TYPES_H
1155 	GStatBuf st;
1156 	off_t filesize;
1157 	mode_t mode;
1158 	gchar *locale_filename;
1159 #else
1160 	gint filesize = 0;
1161 	gint mode = 0;
1162 #endif
1163 
1164 /* define this ones, to avoid later trouble */
1165 #ifndef S_IRUSR
1166 # define S_IRUSR 0
1167 # define S_IWUSR 0
1168 # define S_IXUSR 0
1169 #endif
1170 #ifndef S_IRGRP
1171 # define S_IRGRP 0
1172 # define S_IWGRP 0
1173 # define S_IXGRP 0
1174 # define S_IROTH 0
1175 # define S_IWOTH 0
1176 # define S_IXOTH 0
1177 #endif
1178 
1179 	g_return_if_fail(doc == NULL || doc->is_valid);
1180 
1181 	if (doc == NULL || doc->file_name == NULL)
1182 	{
1183 		dialogs_show_msgbox(GTK_MESSAGE_ERROR,
1184 		_("An error occurred or file information could not be retrieved (e.g. from a new file)."));
1185 		return;
1186 	}
1187 
1188 
1189 #ifdef HAVE_SYS_TYPES_H
1190 	locale_filename = utils_get_locale_from_utf8(doc->file_name);
1191 	if (g_stat(locale_filename, &st) == 0)
1192 	{
1193 		/* first copy the returned string and the trim it, to not modify the static glibc string
1194 		 * g_strchomp() is used to remove trailing EOL chars, which are there for whatever reason */
1195 		time_changed  = g_strchomp(g_strdup(ctime(&st.st_ctime)));
1196 		time_modified = g_strchomp(g_strdup(ctime(&st.st_mtime)));
1197 		time_accessed = g_strchomp(g_strdup(ctime(&st.st_atime)));
1198 		filesize = st.st_size;
1199 		mode = st.st_mode;
1200 	}
1201 	else
1202 	{
1203 		time_changed  = g_strdup(_("unknown"));
1204 		time_modified = g_strdup(_("unknown"));
1205 		time_accessed = g_strdup(_("unknown"));
1206 		filesize = (off_t) 0;
1207 		mode = (mode_t) 0;
1208 	}
1209 	g_free(locale_filename);
1210 #else
1211 	time_changed  = g_strdup(_("unknown"));
1212 	time_modified = g_strdup(_("unknown"));
1213 	time_accessed = g_strdup(_("unknown"));
1214 #endif
1215 
1216 	base_name = g_path_get_basename(doc->file_name);
1217 	short_name = utils_str_middle_truncate(base_name, 30);
1218 	title = g_strdup_printf(_("%s Properties"), short_name);
1219 	dialog = ui_builder_get_object("properties_dialog");
1220 	gtk_window_set_title(GTK_WINDOW(dialog), title);
1221 	g_free(short_name);
1222 	g_free(title);
1223 	gtk_widget_set_name(dialog, "GeanyDialog");
1224 
1225 	label = ui_lookup_widget(dialog, "file_name_label");
1226 	gtk_label_set_text(GTK_LABEL(label), base_name);
1227 
1228 	image = ui_lookup_widget(dialog, "file_type_image");
1229 	gtk_image_set_from_gicon(GTK_IMAGE(image), doc->file_type->icon,
1230 			GTK_ICON_SIZE_BUTTON);
1231 
1232 	label = ui_lookup_widget(dialog, "file_type_label");
1233 	gtk_label_set_text(GTK_LABEL(label), doc->file_type->title);
1234 
1235 	label = ui_lookup_widget(dialog, "file_size_label");
1236 	file_size = utils_make_human_readable_str(filesize, 1, 0);
1237 	gtk_label_set_text(GTK_LABEL(label), file_size);
1238 	g_free(file_size);
1239 
1240 	label = ui_lookup_widget(dialog, "file_location_label");
1241 	gtk_label_set_text(GTK_LABEL(label), doc->file_name);
1242 
1243 	check = ui_lookup_widget(dialog, "file_read_only_check");
1244 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), doc->readonly);
1245 
1246 	label = ui_lookup_widget(dialog, "file_encoding_label");
1247 	enctext = g_strdup_printf("%s %s",
1248 		doc->encoding,
1249 		(encodings_is_unicode_charset(doc->encoding)) ?
1250 			((doc->has_bom) ? _("(with BOM)") : _("(without BOM)")) : "");
1251 	gtk_label_set_text(GTK_LABEL(label), enctext);
1252 	g_free(enctext);
1253 
1254 	label = ui_lookup_widget(dialog, "file_modified_label");
1255 	gtk_label_set_text(GTK_LABEL(label), time_modified);
1256 	label = ui_lookup_widget(dialog, "file_changed_label");
1257 	gtk_label_set_text(GTK_LABEL(label), time_changed);
1258 	label = ui_lookup_widget(dialog, "file_accessed_label");
1259 	gtk_label_set_text(GTK_LABEL(label), time_accessed);
1260 
1261 	/* permissions */
1262 	check = ui_lookup_widget(dialog, "file_perm_owner_r_check");
1263 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IRUSR);
1264 	check = ui_lookup_widget(dialog, "file_perm_owner_w_check");
1265 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWUSR);
1266 	check = ui_lookup_widget(dialog, "file_perm_owner_x_check");
1267 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXUSR);
1268 	check = ui_lookup_widget(dialog, "file_perm_group_r_check");
1269 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IRGRP);
1270 	check = ui_lookup_widget(dialog, "file_perm_group_w_check");
1271 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWGRP);
1272 	check = ui_lookup_widget(dialog, "file_perm_group_x_check");
1273 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXGRP);
1274 	check = ui_lookup_widget(dialog, "file_perm_other_r_check");
1275 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IROTH);
1276 	check = ui_lookup_widget(dialog, "file_perm_other_w_check");
1277 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IWOTH);
1278 	check = ui_lookup_widget(dialog, "file_perm_other_x_check");
1279 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), mode & S_IXOTH);
1280 
1281 	g_free(base_name);
1282 	g_free(time_changed);
1283 	g_free(time_modified);
1284 	g_free(time_accessed);
1285 
1286 	gtk_widget_show(dialog);
1287 }
1288 
1289 
1290 /* extra_text can be NULL; otherwise it is displayed below main_text.
1291  * if parent is NULL, main_widgets.window will be used
1292  * btn_1, btn_2, btn_3 can be NULL.
1293  * returns response_1, response_2, response_3, or GTK_RESPONSE_DELETE_EVENT if the dialog was discarded */
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)1294 static gint show_prompt(GtkWidget *parent,
1295 		const gchar *btn_1, GtkResponseType response_1,
1296 		const gchar *btn_2, GtkResponseType response_2,
1297 		const gchar *btn_3, GtkResponseType response_3,
1298 		const gchar *question_text, const gchar *extra_text)
1299 {
1300 	gboolean ret = FALSE;
1301 	GtkWidget *dialog;
1302 	GtkWidget *btn;
1303 
1304 	if (btn_2 == NULL)
1305 	{
1306 		btn_2 = GTK_STOCK_NO;
1307 		response_2 = GTK_RESPONSE_NO;
1308 	}
1309 	if (btn_3 == NULL)
1310 	{
1311 		btn_3 = GTK_STOCK_YES;
1312 		response_3 = GTK_RESPONSE_YES;
1313 	}
1314 
1315 #ifdef G_OS_WIN32
1316 	/* our native dialog code doesn't support custom buttons */
1317 	if (utils_str_equal(btn_3, GTK_STOCK_YES) &&
1318 		utils_str_equal(btn_2, GTK_STOCK_NO) && btn_1 == NULL)
1319 	{
1320 		gchar *string = (extra_text == NULL) ? g_strdup(question_text) :
1321 			g_strconcat(question_text, "\n\n", extra_text, NULL);
1322 
1323 		ret = win32_message_dialog(parent, GTK_MESSAGE_QUESTION, string);
1324 		ret = ret ? response_3 : response_2;
1325 		g_free(string);
1326 		return ret;
1327 	}
1328 #endif
1329 	if (parent == NULL && main_status.main_window_realized)
1330 		parent = main_widgets.window;
1331 
1332 	dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
1333 		GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION,
1334 		GTK_BUTTONS_NONE, "%s", question_text);
1335 	gtk_widget_set_name(dialog, "GeanyDialog");
1336 	gtk_window_set_title(GTK_WINDOW(dialog), _("Question"));
1337 	gtk_window_set_icon_name(GTK_WINDOW(dialog), "geany");
1338 
1339 	/* question_text will be in bold if optional extra_text used */
1340 	if (extra_text != NULL)
1341 		gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1342 			"%s", extra_text);
1343 
1344 	if (btn_1 != NULL)
1345 		gtk_dialog_add_button(GTK_DIALOG(dialog), btn_1, response_1);
1346 
1347 	btn = gtk_dialog_add_button(GTK_DIALOG(dialog), btn_2, response_2);
1348 	/* we don't want a default, but we need to override the apply button as default */
1349 	gtk_widget_grab_default(btn);
1350 	gtk_dialog_add_button(GTK_DIALOG(dialog), btn_3, response_3);
1351 
1352 	ret = gtk_dialog_run(GTK_DIALOG(dialog));
1353 	gtk_widget_destroy(dialog);
1354 
1355 	return ret;
1356 }
1357 
1358 
1359 /**
1360  *  Shows a question message box with @a text and Yes/No buttons.
1361  *  On Unix-like systems a GTK message dialog box is shown, on Win32 systems a native Windows
1362  *  message dialog box is shown.
1363  *
1364  *  @param text Printf()-style format string.
1365  *  @param ... Arguments for the @a text format string.
1366  *
1367  *  @return @c TRUE if the user answered with Yes, otherwise @c FALSE.
1368  **/
1369 GEANY_API_SYMBOL
dialogs_show_question(const gchar * text,...)1370 gboolean dialogs_show_question(const gchar *text, ...)
1371 {
1372 	gchar *string;
1373 	va_list args;
1374 	GtkWidget *parent = (main_status.main_window_realized) ? main_widgets.window : NULL;
1375 	gint result;
1376 
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);
1387 }
1388 
1389 
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. */
dialogs_show_question_full(GtkWidget * parent,const gchar * yes_btn,const gchar * no_btn,const gchar * extra_text,const gchar * main_text,...)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, ...)
1395 {
1396 	gint result;
1397 	gchar *string;
1398 	va_list args;
1399 
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);
1410 }
1411 
1412 
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 */
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,...)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, ...)
1422 {
1423 	gchar *string;
1424 	va_list args;
1425 	gint result;
1426 
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;
1434 }
1435