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