1 /* Bluefish HTML Editor
2  * file_dialogs.c - file dialogs
3  *
4  * Copyright (C) 2005-2018 Olivier Sessink
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 3 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
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /* indented with indent -ts4 -kr -l110   */
21 /*#define DEBUG*/
22 
23 #include <gtk/gtk.h>
24 #include <string.h>				/* memcpy */
25 #include <time.h>				/* strftime() */
26 
27 #include "bluefish.h"
28 #include "file_dialogs.h"
29 #include "bfwin.h"
30 #include "bookmark.h"
31 #include "dialog_utils.h"
32 #include "document.h"
33 #include "doc_text_tools.h"
34 #include "file_autosave.h"
35 #include "file.h"
36 #include "filebrowser2.h"
37 #include "gtk_easy.h"
38 #include "snr3.h"				/* snr3_run_extern_replace() */
39 #include "stringlist.h"
40 #include "undo_redo.h"
41 
42 static gchar *modified_on_disk_warning_string(const gchar * filename, GFileInfo * oldfinfo,
43 											  GFileInfo * newfinfo);
44 
45 /**************************************************************************/
46 /* the start of the callback functions for the menu, acting on a document */
47 /**************************************************************************/
48 typedef struct {
49 	GtkWidget *dialog;
50 	GtkWidget *basedir;
51 	GtkWidget *find_pattern;
52 	GtkWidget *matchname;
53 	GtkWidget *recursive;
54 	GtkWidget *max_recursion;
55 	GtkWidget *grep_pattern;
56 	GtkWidget *is_regex;
57 	GtkWidget *regexwarn;
58 	Tbfwin *bfwin;
59 } Tfiles_advanced;
60 
61 static void
files_advanced_win_findpattern_changed(GtkComboBox * combobox,Tfiles_advanced * tfs)62 files_advanced_win_findpattern_changed(GtkComboBox * combobox, Tfiles_advanced * tfs)
63 {
64 	if (strlen(gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(tfs->find_pattern))))) > 0) {
65 		gtk_dialog_set_response_sensitive(GTK_DIALOG(tfs->dialog), GTK_RESPONSE_ACCEPT, TRUE);
66 	} else {
67 		gtk_dialog_set_response_sensitive(GTK_DIALOG(tfs->dialog), GTK_RESPONSE_ACCEPT, FALSE);
68 	}
69 }
70 
71 static gboolean
files_advanced_win_ok_clicked(Tfiles_advanced * tfs)72 files_advanced_win_ok_clicked(Tfiles_advanced * tfs)
73 {
74 	GFile *baseuri;
75 	gchar *basedir, *content_filter, *extension_filter;
76 	gboolean retval;
77 	GError *gerror = NULL;
78 	extension_filter =
79 		gtk_editable_get_chars(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(tfs->find_pattern))), 0, -1);
80 	basedir = gtk_editable_get_chars(GTK_EDITABLE(tfs->basedir), 0, -1);
81 	baseuri = g_file_new_for_uri(basedir);
82 	content_filter = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(tfs->grep_pattern));
83 	if (content_filter && content_filter[0]!='\0')
84 		tfs->bfwin->session->searchlist = add_to_history_stringlist(tfs->bfwin->session->searchlist, content_filter, TRUE);
85 	if (extension_filter && extension_filter[0] != '\0')
86 		tfs->bfwin->session->filegloblist = add_to_history_stringlist(tfs->bfwin->session->filegloblist, extension_filter, TRUE);
87 
88 	retval =
89 		open_advanced(tfs->bfwin, baseuri, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tfs->recursive))
90 					  , 500, !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tfs->matchname))
91 					  ,
92 					  strlen(extension_filter) == 0 ? NULL : extension_filter,
93 					  strlen(content_filter) == 0 ? NULL : content_filter,
94 					  gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tfs->is_regex)), &gerror);
95 	if (!retval && gerror) {
96 		gtk_label_set_line_wrap(GTK_LABEL(tfs->regexwarn), TRUE);
97 		gtk_label_set_text(GTK_LABEL(tfs->regexwarn), gerror->message);
98 		g_error_free(gerror);
99 	}
100 	g_free(basedir);
101 	g_free(content_filter);
102 	g_free(extension_filter);
103 	g_object_unref(baseuri);
104 
105 	tfs->bfwin->session->adv_open_recursive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tfs->recursive));
106 	tfs->bfwin->session->adv_open_matchname =
107 		!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tfs->matchname));
108 	if (retval)
109 		bfwin_statusbar_message(tfs->bfwin,_("Started open advanced..."), 1);
110 	return retval;
111 }
112 
113 static void
files_advanced_win_select_basedir_lcb(GtkWidget * widget,Tfiles_advanced * tfs)114 files_advanced_win_select_basedir_lcb(GtkWidget * widget, Tfiles_advanced * tfs)
115 {
116 	gchar *newdir = NULL;
117 	GtkWidget *dialog;
118 
119 	dialog =
120 		file_chooser_dialog(tfs->bfwin, _("Select basedir"), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, (gchar *)
121 							gtk_entry_get_text(GTK_ENTRY(tfs->basedir)), TRUE, FALSE, NULL, FALSE);
122 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
123 		newdir = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
124 	}
125 	gtk_widget_destroy(dialog);
126 
127 	if (newdir) {
128 		gtk_entry_set_text(GTK_ENTRY(tfs->basedir), newdir);
129 		g_free(newdir);
130 	}
131 }
132 
133 void
files_advanced_win(Tbfwin * bfwin,gchar * basedir)134 files_advanced_win(Tbfwin * bfwin, gchar * basedir)
135 {
136 	GtkWidget *alignment, *button, *carea, *table, *vbox, *vbox2;
137 	Tfiles_advanced *tfs;
138 
139 	tfs = g_new(Tfiles_advanced, 1);
140 	tfs->bfwin = bfwin;
141 
142 	tfs->dialog = gtk_dialog_new_with_buttons(_("Advanced open file selector"),
143 											  GTK_WINDOW(tfs->bfwin->main_window),
144 											  GTK_DIALOG_DESTROY_WITH_PARENT,
145 											  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
146 											  GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
147 
148 #if !GTK_CHECK_VERSION(3, 0, 0)
149 	gtk_dialog_set_has_separator(GTK_DIALOG(tfs->dialog), FALSE);
150 #endif /* gtk3 */
151 	carea = gtk_dialog_get_content_area(GTK_DIALOG(tfs->dialog));
152 
153 	alignment = gtk_alignment_new(0, 0, 1, 1);
154 	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 12, 0, 12, 6);
155 	gtk_box_pack_start(GTK_BOX(carea), alignment, FALSE, FALSE, 0);
156 	vbox = gtk_vbox_new(FALSE, 0);
157 	gtk_container_add(GTK_CONTAINER(alignment), vbox);
158 
159 	vbox2 = dialog_vbox_labeled(_("<b>Files</b>"), vbox);
160 
161 	table = dialog_table_in_vbox(2, 6, 0, vbox2, FALSE, FALSE, 6);
162 
163 	if (!basedir) {
164 		tfs->basedir = dialog_entry_in_table(bfwin->session->opendir, table, 1, 5, 0, 1);
165 	} else {
166 		tfs->basedir = dialog_entry_in_table(basedir, table, 1, 5, 0, 1);
167 	}
168 	dialog_mnemonic_label_in_table(_("Base _Dir:"), tfs->basedir, table, 0, 1, 0, 1);
169 
170 	button =
171 		dialog_button_new_with_image_in_table(NULL, GTK_STOCK_OPEN, G_CALLBACK(files_advanced_win_select_basedir_lcb), tfs,
172 						TRUE, FALSE,
173 						table, 5, 6, 0,1);
174 
175 
176 	/*lstore = gtk_list_store_new(1, G_TYPE_STRING);
177 	for (tmplist = g_list_first(bfwin->session->filegloblist); tmplist; tmplist = g_list_next(tmplist)) {
178 		gtk_list_store_append(GTK_LIST_STORE(lstore), &iter);
179 		gtk_list_store_set(GTK_LIST_STORE(lstore), &iter, 0, tmplist->data, -1);
180 	}
181 	tfs->find_pattern = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(lstore), 0);
182 	g_object_unref(lstore);*/
183 	tfs->find_pattern = combobox_with_popdown("", bfwin->session->filegloblist, TRUE);
184 	dialog_mnemonic_label_in_table(_("_Pattern:"), tfs->find_pattern, table, 0, 1, 1, 2);
185 	gtk_table_attach_defaults(GTK_TABLE(table), tfs->find_pattern, 1, 5, 1, 2);
186 	g_signal_connect(G_OBJECT(tfs->find_pattern), "changed",
187 					 G_CALLBACK(files_advanced_win_findpattern_changed), tfs);
188 
189 	table = dialog_table_in_vbox(3, 2, 0, vbox2, FALSE, FALSE, 0);
190 
191 	tfs->matchname = checkbut_with_value(NULL, tfs->bfwin ? !tfs->bfwin->session->adv_open_matchname : FALSE);
192 	dialog_mnemonic_label_in_table(_("Pattern matches _full path:"), tfs->matchname, table, 0, 1, 0, 1);
193 	gtk_table_attach(GTK_TABLE(table), tfs->matchname, 1, 2, 0, 1, GTK_SHRINK, GTK_SHRINK, 0, 0);
194 
195 	tfs->recursive = checkbut_with_value(NULL, tfs->bfwin ? tfs->bfwin->session->adv_open_recursive : TRUE);
196 	dialog_mnemonic_label_in_table(_("_Recursive:"), tfs->recursive, table, 0, 1, 1, 2);
197 	gtk_table_attach(GTK_TABLE(table), tfs->recursive, 1, 2, 1, 2, GTK_SHRINK, GTK_SHRINK, 0, 0);
198 
199 	tfs->max_recursion = spinbut_with_value("100", 1, 100000, 1, 10);
200 	dialog_mnemonic_label_in_table(_("Ma_x recursion:"), tfs->max_recursion, table, 0, 1, 2, 3);
201 	gtk_table_attach(GTK_TABLE(table), tfs->max_recursion, 1, 2, 2, 3, GTK_SHRINK, GTK_SHRINK, 0, 0);
202 
203 	alignment = gtk_alignment_new(0, 0, 1, 1);
204 	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 12, 18, 12, 6);
205 	gtk_box_pack_start(GTK_BOX(carea), alignment, FALSE, FALSE, 0);
206 	vbox = gtk_vbox_new(FALSE, 0);
207 	gtk_container_add(GTK_CONTAINER(alignment), vbox);
208 
209 	vbox2 = dialog_vbox_labeled(_("<b>Contains</b>"), vbox);
210 
211 	table = dialog_table_in_vbox(2, 4, 0, vbox2, FALSE, FALSE, 6);
212 
213 	tfs->grep_pattern = combobox_with_popdown("", bfwin->session->searchlist, TRUE);
214 	dialog_mnemonic_label_in_table(_("Pa_ttern:"), tfs->grep_pattern, table, 0, 1, 0, 1);
215 	gtk_table_attach_defaults(GTK_TABLE(table), tfs->grep_pattern, 1, 4, 0, 1);
216 
217 	tfs->is_regex = checkbut_with_value(NULL, 0);
218 	dialog_mnemonic_label_in_table(_("Is rege_x:"), tfs->is_regex, table, 0, 1, 1, 2);
219 	gtk_table_attach(GTK_TABLE(table), tfs->is_regex, 1, 2, 1, 2, GTK_FILL, GTK_SHRINK, 0, 0);
220 
221 	tfs->regexwarn = gtk_label_new(NULL);
222 	gtk_table_attach(GTK_TABLE(table), tfs->regexwarn, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
223 
224 	gtk_dialog_set_response_sensitive(GTK_DIALOG(tfs->dialog), GTK_RESPONSE_ACCEPT, FALSE);
225 	gtk_widget_show_all(carea);
226 
227 	while (gtk_dialog_run(GTK_DIALOG(tfs->dialog)) == GTK_RESPONSE_ACCEPT) {
228 		if (files_advanced_win_ok_clicked(tfs))
229 			break;
230 	}
231 
232 	gtk_widget_destroy(tfs->dialog);
233 	g_free(tfs);
234 }
235 
236 void
file_open_advanced_cb(GtkWidget * widget,Tbfwin * bfwin)237 file_open_advanced_cb(GtkWidget * widget, Tbfwin * bfwin)
238 {
239 	files_advanced_win(bfwin, NULL);
240 }
241 
242 /*************** end of advanced open code *************/
243 
244 static void
file_open_ok_lcb(GtkDialog * dialog,gint response,Tbfwin * bfwin)245 file_open_ok_lcb(GtkDialog * dialog, gint response, Tbfwin * bfwin)
246 {
247 	if (response == GTK_RESPONSE_ACCEPT) {
248 		GSList *slist, *tmpslist;
249 		GtkComboBox *combo;
250 		bfwin->focus_next_new_doc = TRUE;
251 		combo = g_object_get_data(G_OBJECT(dialog), "encodings");
252 		if (combo) {
253 			GtkTreeIter iter;
254 			if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) {
255 				GtkTreeModel *model;
256 				gchar **arr;
257 				model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
258 				gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 1, &arr, -1);
259 				if (bfwin->session->encoding)
260 					g_free(bfwin->session->encoding);
261 				if (arr) {
262 					bfwin->session->encoding = g_strdup(arr[1]);
263 				} else {
264 					bfwin->session->encoding = NULL;
265 				}
266 				DEBUG_MSG("file_open_ok_lcb, session encoding is set to %s\n", bfwin->session->encoding);
267 			}
268 		}
269 
270 		tmpslist = slist = gtk_file_chooser_get_files(GTK_FILE_CHOOSER(dialog));
271 		while (tmpslist) {
272 			doc_new_from_uri(bfwin, (GFile *) tmpslist->data, NULL, (slist->next != NULL), FALSE, -1, -1, -1, TRUE, FALSE);
273 			g_object_unref((GFile *) tmpslist->data);
274 			tmpslist = tmpslist->next;
275 		}
276 		g_slist_free(slist);
277 	}
278 	gtk_widget_destroy(GTK_WIDGET(dialog));
279 }
280 
281 void
file_open_doc(Tbfwin * bfwin)282 file_open_doc(Tbfwin * bfwin)
283 {
284 	GtkWidget *dialog;
285 
286 	dialog =
287 		file_chooser_dialog(bfwin, _("Select files"), GTK_FILE_CHOOSER_ACTION_OPEN, NULL, FALSE, TRUE, NULL,
288 							TRUE);
289 	g_signal_connect(dialog, "response", G_CALLBACK(file_open_ok_lcb), bfwin);
290 	gtk_widget_show_all(dialog);
291 }
292 
293 /**
294  * file_open_cb:
295  * @widget: unused #GtkWidget
296  * @bfwin: #Tbfwin* with the current window
297  *
298  * Prompt user for files to open.
299  *
300  * Return value: void
301  **/
302 void
file_open_cb(GtkWidget * widget,Tbfwin * bfwin)303 file_open_cb(GtkWidget * widget, Tbfwin * bfwin)
304 {
305 	GtkWidget *dialog;
306 	dialog =
307 		file_chooser_dialog(bfwin, _("Select files"), GTK_FILE_CHOOSER_ACTION_OPEN, NULL, FALSE, TRUE, NULL,
308 							TRUE);
309 	g_signal_connect(dialog, "response", G_CALLBACK(file_open_ok_lcb), bfwin);
310 	gtk_widget_show_all(dialog);
311 /*  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
312     GSList *slist;
313     bfwin->focus_next_new_doc = TRUE;
314     slist = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(dialog));
315     docs_new_from_uris(bfwin, slist, FALSE);
316     g_slist_free(slist);
317   }
318   gtk_widget_destroy(dialog);*/
319 }
320 
321 typedef struct {
322 	Tbfwin *bfwin;
323 	GtkWidget *win;
324 	GtkWidget *entry;
325 } Tou;
326 static void
open_url_destroy_lcb(GtkWidget * widget,Tou * ou)327 open_url_destroy_lcb(GtkWidget * widget, Tou * ou)
328 {
329 	g_free(ou);
330 }
331 
332 static void
open_url_cancel_lcb(GtkWidget * widget,Tou * ou)333 open_url_cancel_lcb(GtkWidget * widget, Tou * ou)
334 {
335 	gtk_widget_destroy(ou->win);
336 }
337 
338 static void
open_url_ok_lcb(GtkWidget * widget,Tou * ou)339 open_url_ok_lcb(GtkWidget * widget, Tou * ou)
340 {
341 	gchar *url = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(ou->entry));
342 	DEBUG_MSG("open_url_ok_lcb, url=%s\n", url);
343 	doc_new_from_input(ou->bfwin, url, FALSE, FALSE, -1);
344 	g_free(url);
345 	gtk_widget_destroy(ou->win);
346 }
347 
348 /**
349  * file_open_url_cb:
350  * @widget: #GtkWidget* ignored
351  * @bfwin: #Tbfwin* bfwin pointer
352  *
353  * opens a dialog where you can enter an URL to open of any kind
354  * supported by gnome-vfs
355  *
356  * Return value: void
357  **/
358 void
file_open_url_cb(GtkAction * action,Tbfwin * bfwin)359 file_open_url_cb(GtkAction * action, Tbfwin * bfwin)
360 {
361 	GtkWidget *align, *vbox, *hbox, *but;
362 	Tou *ou;
363 	GList *urlhistory = NULL, *tmplist = NULL;
364 	ou = g_new(Tou, 1);
365 	ou->bfwin = bfwin;
366 	ou->win =
367 		window_full2(_("Open URL"), GTK_WIN_POS_CENTER_ON_PARENT, 12, G_CALLBACK(open_url_destroy_lcb), ou,
368 					 TRUE, bfwin->main_window);
369 	gtk_widget_set_size_request(ou->win, 450, -1);
370 	vbox = gtk_vbox_new(FALSE, 5);
371 /*	gtk_box_pack_start(GTK_BOX(vbox), bf_label_with_markup(_("<b>Open URL</b>")), FALSE, FALSE, 5);*/
372 	gtk_container_add(GTK_CONTAINER(ou->win), vbox);
373 	tmplist = g_list_first(bfwin->session->recent_files);
374 	while (tmplist) {
375 		if (tmplist->data && strlen(tmplist->data) > 5 && strncmp(tmplist->data, "file:", 5) != 0) {
376 			urlhistory = g_list_prepend(urlhistory, g_strdup(tmplist->data));
377 		}
378 		tmplist = g_list_next(tmplist);
379 	}
380 	ou->entry = boxed_combobox_with_popdown("", urlhistory, TRUE, vbox);
381 	free_stringlist(urlhistory);
382 /*  ou->entry = boxed_entry_with_text("", 255, vbox); */
383 
384 	align = gtk_alignment_new(0.0, 1.0, 1.0, 0.0);
385 	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 12, 0 ,0, 0);
386 	gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, FALSE, 0);
387 
388 #if GTK_CHECK_VERSION(3,0,0)
389 	hbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
390 #else
391 	hbox = gtk_hbutton_box_new();
392 #endif
393 	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
394 	gtk_box_set_spacing(GTK_BOX(hbox), 6);
395 	gtk_container_add(GTK_CONTAINER(align), hbox);
396 	but = bf_stock_cancel_button(G_CALLBACK(open_url_cancel_lcb), ou);
397 	gtk_box_pack_start(GTK_BOX(hbox), but, FALSE, TRUE, 0);
398 	but = bf_stock_ok_button(G_CALLBACK(open_url_ok_lcb), ou);
399 	gtk_box_pack_start(GTK_BOX(hbox), but, FALSE, TRUE, 0);
400 	gtk_window_set_default(GTK_WINDOW(ou->win), but);
401 	gtk_widget_show_all(ou->win);
402 }
403 
404 /***********************************/
405 /*        async save code          */
406 
407 typedef struct {
408 	Tdocument *doc;
409 	GFile *unlink_uri;
410 	GFile *fbrefresh_uri;
411 	Tdocsave_mode savemode;
412 } Tdocsavebackend;
413 
414 static void
docsavebackend_cleanup(Tdocsavebackend * dsb)415 docsavebackend_cleanup(Tdocsavebackend * dsb)
416 {
417 	if (dsb->unlink_uri)
418 		g_object_unref(dsb->unlink_uri);
419 	if (dsb->fbrefresh_uri)
420 		g_object_unref(dsb->fbrefresh_uri);
421 	g_free(dsb);
422 }
423 
424 static void
docsavebackend_async_unlink_lcb(gpointer data)425 docsavebackend_async_unlink_lcb(gpointer data)
426 {
427 	Tdocsavebackend *dsb = data;
428 	fb2_refresh_parent_of_uri(dsb->unlink_uri);
429 	docsavebackend_cleanup(dsb);
430 }
431 
432 static TcheckNsave_return
doc_checkNsave_lcb(TcheckNsave_status status,GError * gerror,gpointer data)433 doc_checkNsave_lcb(TcheckNsave_status status, GError * gerror, gpointer data)
434 {
435 	Tdocsavebackend *dsb = data;
436 	Tdocument *doc = dsb->doc;
437 	gchar *errmessage;
438 	DEBUG_MSG("doc_checkNsave_lcb, doc=%p, status=%d, doc->uri=%p, simplesearch_snr3run=%p\n", doc, status, doc->uri, BFWIN(doc->bfwin)->simplesearch_snr3run);
439 	switch (status) {
440 	case CHECKANDSAVE_ERROR_NOBACKUP:
441 		if (main_v->props.backup_abort_action == 0) {
442 			return CHECKNSAVE_CONT;
443 		} else if (main_v->props.backup_abort_action == 1) {
444 			doc->save = NULL;
445 			gtk_text_view_set_editable(GTK_TEXT_VIEW(doc->view), TRUE);
446 			return CHECKNSAVE_STOP;
447 		} else {				/* if (main_v->props.backup_abort_action == 2) */
448 
449 			/* we have to ask the user */
450 			const gchar *buttons[] = { _("_Abort save"), _("_Continue save"), NULL };
451 			gint retval;
452 			gchar *tmpstr =
453 				g_strdup_printf(_
454 								("A backupfile for %s could not be created. If you continue, this file will be overwritten."),
455 gtk_label_get_text(GTK_LABEL(doc->tab_label)));
456 			retval =
457 				message_dialog_new_multi(BFWIN(doc->bfwin)->main_window, GTK_MESSAGE_WARNING, buttons,
458 										 _("File backup failure"), tmpstr);
459 			g_free(tmpstr);
460 			DEBUG_MSG("doc_checkNsave_lcb, retval=%d, returning %d\n", retval,
461 					  (retval == 0) ? CHECKNSAVE_STOP : CHECKNSAVE_CONT);
462 			if (retval == 0) {
463 				doc->save = NULL;
464 				gtk_text_view_set_editable(GTK_TEXT_VIEW(doc->view), TRUE);
465 				return CHECKNSAVE_STOP;
466 			}
467 			return CHECKNSAVE_CONT;
468 		}
469 		break;
470 	case CHECKANDSAVE_ERROR:
471 	case CHECKANDSAVE_ERROR_NOWRITE:
472 		{
473 			errmessage =
474 				g_strconcat(_("Could not save file "), gtk_label_get_text(GTK_LABEL(doc->tab_label)), NULL);
475 			message_dialog_new(BFWIN(doc->bfwin)->main_window, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
476 							   errmessage, gerror->message);
477 			g_free(errmessage);
478 		}
479 		/* no break - fall through */
480 	case CHECKANDSAVE_ERROR_CANCELLED:
481 		doc->save = NULL;
482 		gtk_text_view_set_editable(GTK_TEXT_VIEW(doc->view), TRUE);
483 		docsavebackend_cleanup(dsb);
484 		break;
485 	case CHECKANDSAVE_ERROR_MODIFIED:
486 		{
487 			/* we have to ask the user what to do */
488 			const gchar *buttons[] = { _("_Abort save"), _("_Continue save"), NULL };
489 			GFileInfo *newfinfo;
490 			GError *gerror = NULL;
491 			gint retval;
492 			gchar *tmpstr, *utf8uri;
493 
494 			newfinfo =
495 				g_file_query_info(doc->uri, G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_TIME_MODIFIED,
496 								  0, NULL, &gerror);
497 			if (gerror) {
498 				g_warning("file was modified on disk, but now an error??");
499 				g_error_free(gerror);
500 				return CHECKNSAVE_CONT;
501 			}
502 			utf8uri = g_file_get_uri(doc->uri);
503 			tmpstr = modified_on_disk_warning_string(utf8uri, doc->fileinfo, newfinfo);
504 			/*g_strdup_printf(_("File %s has been modified on disk, overwrite?"), utf8uri); */
505 			g_free(utf8uri);
506 			g_object_unref(newfinfo);
507 			retval =
508 				message_dialog_new_multi(BFWIN(doc->bfwin)->main_window, GTK_MESSAGE_WARNING, buttons,
509 										 _("File changed on disk\n"), tmpstr);
510 			g_free(tmpstr);
511 			if (retval == 0) {
512 				doc->save = NULL;
513 				gtk_text_view_set_editable(GTK_TEXT_VIEW(doc->view), TRUE);
514 				return CHECKNSAVE_STOP;
515 			}
516 			return CHECKNSAVE_CONT;
517 		}
518 	case CHECKANDSAVE_FINISHED:
519 		if (dsb->unlink_uri) {
520 			file_delete_async(dsb->unlink_uri, FALSE, docsavebackend_async_unlink_lcb, dsb);
521 		}
522 		/* if the user wanted to close the doc we should do very diffferent things here !! */
523 		doc->save = NULL;
524 		if (doc->close_doc) {
525 			Tbfwin *bfwin = doc->bfwin;
526 			gboolean close_window = doc->close_window;
527 			doc_destroy(doc, doc->close_window);
528 			if (close_window && test_only_empty_doc_left(bfwin->documentlist)) {
529 				bfwin_destroy_and_cleanup(bfwin);
530 			}
531 			return CHECKNSAVE_STOP;	/* it actually doesn't matter what we return, this was the last callback anyway */
532 		} else {
533 			/* YES! we're done! update the fileinfo ! */
534 			gtk_text_view_set_editable(GTK_TEXT_VIEW(doc->view), TRUE);
535 			if (dsb->savemode != docsave_copy) {
536 				DEBUG_MSG("doc_checkNsave_lcb, re-set async doc->fileinfo (current=%p) for uri %p\n", doc->fileinfo, doc->uri);
537 				if (doc->fileinfo)
538 					g_object_unref(doc->fileinfo);
539 				doc->fileinfo = NULL;
540 				file_doc_fill_fileinfo(doc, doc->uri);
541 				if (main_v->props.clear_undo_on_save) {
542 					doc_unre_clear_all(doc);
543 				} else {
544 					doc_unre_clear_not_modified(doc);
545 				}
546 				doc_set_modified(doc, 0);
547 			}
548 			/* in fact the filebrowser should also be refreshed if the document was closed, but
549 			   when a document is closed, the filebrowser is anyway refreshed (hmm perhaps only if
550 			   'follow document focus' is set). */
551 			if (dsb->unlink_uri && dsb->fbrefresh_uri) {
552 				GFile *parent1, *parent2;
553 				parent1 = g_file_get_parent(dsb->unlink_uri);
554 				parent2 = g_file_get_parent(dsb->fbrefresh_uri);
555 				if (!g_file_equal(parent1, parent2)) {
556 					/* if they are equal, the directory will be refreshed by the unlink callback */
557 					filetreemodel_refresh_uri_async(FB2CONFIG(main_v->fb2config)->ftm, parent2);
558 				}
559 				g_object_unref(parent1);
560 				g_object_unref(parent2);
561 			} else if (dsb->fbrefresh_uri) {
562 				fb2_refresh_parent_of_uri(dsb->fbrefresh_uri);
563 			}
564 		}
565 		if (!dsb->unlink_uri) {
566 			/* if there is an unlink uri, that means the unlink callback will free the dsb structure */
567 			docsavebackend_cleanup(dsb);
568 		}
569 		break;
570 	}
571 	return CHECKNSAVE_CONT;
572 }
573 
574 /**
575  * ask_new_filename:
576  * @bfwin: #Tbfwin* mainly used to set the dialog transient
577  * @oldfilename: #gchar* with the old filename
578  * @gui_name: #const gchar* with the name of the file used in the GUI
579  * @is_move: #gboolean if the title should be move or save as
580  *
581  * returns a newly allocated string with a new filename
582  *
583  * if a file with the selected name name was
584  * open already it will ask the user what to do, return NULL if
585  * the user wants to abort, or will remove the name of the other file if the user wants
586  * to continue
587  *
588  * Return value: gchar* with newly allocated string, or NULL on failure or abort
589  **/
590 gchar *
ask_new_filename(Tbfwin * bfwin,const gchar * old_curi,const gchar * dialogtext)591 ask_new_filename(Tbfwin * bfwin, const gchar * old_curi, const gchar *dialogtext)
592 {
593 	Tdocument *exdoc;
594 	GList *alldocs;
595 	gchar *new_curi = NULL;
596 	GFile *uri;
597 	GtkWidget *dialog;
598 
599 
600 	dialog =
601 		file_chooser_dialog(bfwin, dialogtext, GTK_FILE_CHOOSER_ACTION_SAVE, old_curi, FALSE, FALSE, NULL,
602 							FALSE);
603 	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
604 		new_curi = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(dialog));
605 	}
606 	gtk_widget_destroy(dialog);
607 
608 	if (!new_curi)
609 		return NULL;
610 	if (old_curi && strcmp(old_curi, new_curi) == 0) {
611 		g_free(new_curi);
612 		return NULL;
613 	}
614 
615 	alldocs = return_allwindows_documentlist();
616 	uri = g_file_new_for_uri(new_curi);
617 	exdoc = documentlist_return_document_from_uri(alldocs, uri);
618 	g_object_unref(uri);
619 	g_list_free(alldocs);
620 	DEBUG_MSG("ask_new_filename, exdoc=%p, newfilename=%s\n", exdoc, new_curi);
621 	if (exdoc) {
622 		gchar *tmpstr;
623 		gint retval;
624 		const gchar *buttons[] = { _("_Cancel"), _("_Overwrite"), NULL };
625 		tmpstr = g_strdup_printf(_("File %s exists and is open, overwrite?"), new_curi);
626 		retval =
627 			message_dialog_new_multi(bfwin->main_window, GTK_MESSAGE_WARNING, buttons, tmpstr,
628 									 _("The file you have selected is being edited in Bluefish."));
629 		g_free(tmpstr);
630 		if (retval == 0) {
631 			g_free(new_curi);
632 			return NULL;
633 		} else {
634 			document_unset_filename(exdoc);
635 		}
636 	} else {
637 		GFile *tmp;
638 		gboolean exists;
639 		tmp = g_file_new_for_uri(new_curi);
640 		exists = g_file_query_exists(tmp, NULL);
641 		g_object_unref(tmp);
642 		if (exists) {
643 			gchar *tmpstr;
644 			gint retval;
645 			const gchar *buttons[] = { _("_Cancel"), _("_Overwrite"), NULL };
646 			tmpstr = g_strdup_printf(_("A file named \"%s\" already exists."), new_curi);
647 			retval =
648 				message_dialog_new_multi(bfwin->main_window, GTK_MESSAGE_WARNING, buttons, tmpstr,
649 										 _("Do you want to replace the existing file?"));
650 			g_free(tmpstr);
651 			if (retval == 0) {
652 				g_free(new_curi);
653 				return NULL;
654 			}
655 		}
656 	}
657 	return new_curi;
658 }
659 
660 static void
session_set_savedir(Tbfwin * bfwin,GFile * uri)661 session_set_savedir(Tbfwin * bfwin, GFile *uri)
662 {
663 	if (uri) {
664 		GFile *parent = g_file_get_parent(uri);
665 		if (bfwin->session->savedir)
666 			g_free(bfwin->session->savedir);
667 		bfwin->session->savedir = g_file_get_uri(parent);
668 	}
669 }
670 
671 void
doc_save_backend(Tdocument * doc,Tdocsave_mode savemode,gboolean close_doc,gboolean close_window)672 doc_save_backend(Tdocument * doc, Tdocsave_mode savemode, gboolean close_doc,
673 				 gboolean close_window)
674 {
675 	gchar *tmp;
676 	Trefcpointer *buffer;
677 	gsize numbytes=0;
678 	Tdocsavebackend *dsb;
679 	GFile *dest_uri=NULL;
680 	GFileInfo *dest_finfo=NULL;
681 	gboolean firstsave;
682 	DEBUG_MSG
683 		("doc_save_backend, started for doc %p, mode=%d, close_doc=%d, close_window=%d\n", doc,
684 		 savemode, close_doc, close_window);
685 
686 	if (doc->readonly && savemode == docsave_normal) {
687 		g_print("Cannot save readonly document !?!?");
688 		return;
689 	}
690 
691 	dsb = g_new0(Tdocsavebackend, 1);
692 	dsb->doc = doc;
693 	dsb->savemode = savemode;
694 	if (main_v->props.editor_spacingtoclick) {
695 		bluefish_text_view_remove_spacingtoclick(BLUEFISH_TEXT_VIEW(doc->view));
696 	}
697 
698 	/* should be moved to a plugin interface, because this is HTML specific */
699 	/* update author meta tag */
700 	if (main_v->props.auto_update_meta_author) {
701 		const gchar *realname = g_get_real_name();
702 		if (realname && strlen(realname) > 0) {
703 			gchar *author_tmp;
704 			author_tmp = g_strconcat("<meta name=\"author\" content=\"", realname, "\" ", NULL);
705 			snr3_run_extern_replace(doc,
706 									"<meta[ \t\n]+name[ \t\n]*=[ \t\n]*\"author\"[ \t\n]+content[ \t\n]*=[ \t\n]*\"[^\"]*\"[ \t\n]*",
707 									snr3scope_doc, snr3type_pcre, FALSE, author_tmp, FALSE, FALSE);
708 			g_free(author_tmp);
709 		}
710 	}
711 
712 	/* update date meta tag */
713 	if (main_v->props.auto_update_meta_date) {
714 		time_t time_var;
715 		struct tm *time_struct;
716 		gchar isotime[60];
717 		gchar *date_tmp;
718 
719 		time_var = time(NULL);
720 		time_struct = localtime(&time_var);
721 #ifdef WIN32
722 		{
723 			glong hours, mins;
724 			gchar gmtsign;
725 			gchar tmptime[50];
726 
727 			strftime(tmptime, 30, "%Y-%m-%dT%H:%M:%S", time_struct);
728 			gmtsign = _timezone > 0 ? '-' : '+';
729 			hours = abs(_timezone) / 3600;
730 			mins = (abs(_timezone) % 3600) / 60;
731 			sprintf(isotime, "%s%c%02ld%02ld", tmptime, gmtsign, hours, mins);
732 		}
733 #else							/* WIN32 */
734 		strftime(isotime, 30, "%Y-%m-%dT%H:%M:%S%z", time_struct);
735 #endif							/* WIN32 */
736 		DEBUG_MSG("doc_save_backend, ISO-8601 time %s\n", isotime);
737 
738 		date_tmp = g_strconcat("<meta name=\"date\" content=\"", isotime, "\" ", NULL);
739 		snr3_run_extern_replace(doc,
740 								"<meta[ \t\n]+name[ \t\n]*=[ \t\n]*\"date\"[ \t\n]+content[ \t\n]*=[ \t\n]*\"[^\"]*\"[ \t\n]*",
741 								snr3scope_doc, snr3type_pcre, FALSE, date_tmp, FALSE, FALSE);
742 		g_free(date_tmp);
743 	}
744 
745 	/* update generator meta tag */
746 	if (main_v->props.auto_update_meta_generator) {
747 		snr3_run_extern_replace(doc,
748 								"<meta[ \t\n]+name[ \t\n]*=[ \t\n]*\"generator\"[ \t\n]+content[ \t\n]*=[ \t\n]*\"[^\"]*\"[ \t\n]*",
749 								snr3scope_doc, snr3type_pcre, FALSE,
750 								"<meta name=\"generator\" content=\"Bluefish " VERSION "\" ", FALSE, FALSE);
751 	}
752 	if (main_v->props.strip_trailing_spaces_on_save) {
753 		strip_trailing_spaces(doc);
754 	}
755 
756 	if (doc->save) {
757 		gchar *errmessage;
758 		/* this message is not in very nice english I'm afraid */
759 		errmessage =
760 			g_strconcat(_("File:\n\""), gtk_label_get_text(GTK_LABEL(doc->tab_label)),
761 						_("\" save is in progress"), NULL);
762 		message_dialog_new(BFWIN(doc->bfwin)->main_window, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
763 						   _("Save in progress!"), errmessage);
764 		g_free(errmessage);
765 		g_free(dsb);
766 		DEBUG_MSG("doc_save_backend, already save in progress, return\n");
767 		return;
768 	}
769 	firstsave = (doc->uri == NULL && savemode == docsave_normal);
770 	if (firstsave || savemode != docsave_normal) {
771 		gchar *newfilename, *curi, *dialogtext;
772 		const gchar *gui_name = gtk_label_get_text(GTK_LABEL(doc->tab_label));
773 		DEBUG_MSG("doc_save_backend, no uri (doc->uri=%p), or saveas/copy/move (savemode=%d)\n", doc->uri, savemode);
774 		curi = doc->uri ? g_file_get_uri(doc->uri) : g_strdup(gui_name); /* SaveFile dialog shows weird behavior if NULL is passed as suggested filename */
775 		if (savemode == docsave_normal) {
776 			dialogtext = g_strdup(_("Save"));
777 		} else if (savemode == docsave_saveas){
778 			dialogtext = g_strdup_printf(_("Save %s as"), gui_name);
779 		} else if (savemode == docsave_move){
780 			dialogtext = g_strdup_printf(_("Move/rename %s to"), gui_name);
781 		} else {
782 			dialogtext = g_strdup_printf(_("Save copy of %s"), gui_name);
783 		}
784 		newfilename =
785 			ask_new_filename(BFWIN(doc->bfwin), curi, dialogtext);
786 		g_free(dialogtext);
787 		g_free(curi);
788 		if (!newfilename) {
789 			DEBUG_MSG("doc_save_backend, no newfilename, return\n");
790 			g_free(dsb);
791 			return;
792 		}
793 		dest_uri = g_file_new_for_uri(newfilename);
794 		if (doc->uri == NULL || savemode == docsave_saveas || savemode == docsave_move) {
795 			if (doc->uri) {
796 				if (savemode == docsave_move) {
797 					dsb->unlink_uri = doc->uri;	/* unlink and refresh this uri later */
798 					g_object_ref(dsb->unlink_uri);
799 				}
800 			}
801 			doc_set_uri(doc, dest_uri, FALSE);
802 			dest_finfo = doc->fileinfo;
803 		}
804 		DEBUG_MSG("doc_save_backend, newfilename=%s, dest_uri=%p, doc->uri=%p\n",newfilename, dest_uri, doc->uri);
805 		g_free(newfilename);
806 	} else {
807 		DEBUG_MSG("doc_save_backend, have uri and normal save\n");
808 		dest_uri = doc->uri;
809 		dest_finfo = doc->fileinfo;
810 		g_object_ref(dest_uri);
811 	}
812 	DEBUG_MSG("doc_save_backend, dest_uri=%p\n",dest_uri);
813 	if ((firstsave || savemode != docsave_normal)&& dest_uri) {
814 		dsb->fbrefresh_uri = dest_uri;	/* refresh this uri later */
815 		g_object_ref(dest_uri);
816 	}
817 
818 	session_set_savedir(doc->bfwin, dest_uri);
819 
820 	tmp = doc_get_buffer_in_encoding(doc, &numbytes);
821 	if (!tmp) {
822 		DEBUG_MSG("doc_save_backend, got NULL after encoding, ABORT\n");
823 		g_warning("got NULL buffer after encoding, abort save\n");
824 		g_free(dsb);
825 		return;
826 	}
827 
828 	buffer = refcpointer_new(tmp);
829 	doc->close_doc = close_doc;
830 	doc->close_window = close_window;
831 	gtk_text_view_set_editable(GTK_TEXT_VIEW(doc->view), FALSE);
832 	DEBUG_MSG("doc_save_backend, calling file_checkNsave_uri_async with uri %p for %zd bytes\n", dest_uri, strlen(buffer->data));
833 	doc->save =
834 		file_checkNsave_uri_async(dest_uri, dest_finfo, buffer, numbytes, (savemode == docsave_normal && main_v->props.check_for_modified_on_disk!=0),
835 								  main_v->props.backup_file, doc_checkNsave_lcb, dsb, doc->bfwin);
836 
837 	if (firstsave || savemode == docsave_saveas || savemode == docsave_move) {
838 		if(doc->readonly) {
839 			doc_set_readonly(doc, FALSE);
840 		}
841 		doc_reset_filetype(doc, doc->uri, buffer->data, numbytes);
842 		doc_set_title(doc, NULL);
843 		doc_force_activate(doc);
844 	}
845 	g_object_unref(dest_uri);
846 	refcpointer_unref(buffer);
847 }
848 
849 /**
850  * file_save_cb:
851  * @widget: unused #GtkWidget
852  * @bfwin: #Tbfwin* with the current window
853  *
854  * Save the current document.
855  *
856  * Return value: void
857  **/
858 void
file_save_cb(GtkWidget * widget,Tbfwin * bfwin)859 file_save_cb(GtkWidget * widget, Tbfwin * bfwin)
860 {
861 	if (bfwin->current_document)
862 		doc_save_backend(bfwin->current_document, docsave_normal, FALSE, FALSE);
863 }
864 
865 /**
866  * file_save_as_cb:
867  * @widget: unused #GtkWidget
868  * @bfwin: #Tbfwin* with the current window
869  *
870  * Save current document, let user choose filename.
871  *
872  * Return value: void
873  **/
874 void
file_save_as_cb(GtkWidget * widget,Tbfwin * bfwin)875 file_save_as_cb(GtkWidget * widget, Tbfwin * bfwin)
876 {
877 	if (bfwin->current_document)
878 		doc_save_backend(bfwin->current_document, docsave_saveas, FALSE, FALSE);
879 }
880 
881 /**
882  * file_move_to_cb:
883  * @widget: unused #GtkWidget
884  * @bfwin: #Tbfwin* with the current window
885  *
886  * Move current document, let user choose filename.
887  *
888  * Return value: void
889  **/
890 void
file_move_to_cb(GtkWidget * widget,Tbfwin * bfwin)891 file_move_to_cb(GtkWidget * widget, Tbfwin * bfwin)
892 {
893 	if (bfwin->current_document)
894 		doc_save_backend(bfwin->current_document, docsave_move, FALSE, FALSE);
895 }
896 
897 void
file_save_all(Tbfwin * bfwin)898 file_save_all(Tbfwin * bfwin)
899 {
900 	GList *tmplist;
901 	Tdocument *tmpdoc;
902 
903 	tmplist = g_list_first(bfwin->documentlist);
904 	while (tmplist) {
905 		tmpdoc = (Tdocument *) tmplist->data;
906 		if (tmpdoc->modified) {
907 			doc_save_backend(tmpdoc, docsave_normal, FALSE, FALSE);
908 		}
909 		tmplist = g_list_next(tmplist);
910 	}
911 }
912 
913 /**
914  * file_save_all_cb:
915  * @widget: unused #GtkWidget
916  * @bfwin: the #Tbfwin* window pointer
917  *
918  *  Save all editor notebooks
919  *
920  * Return value: void
921  **/
922 void
file_save_all_cb(GtkWidget * widget,Tbfwin * bfwin)923 file_save_all_cb(GtkWidget * widget, Tbfwin * bfwin)
924 {
925 	GList *tmplist;
926 	Tdocument *tmpdoc;
927 
928 	tmplist = g_list_first(bfwin->documentlist);
929 	while (tmplist) {
930 		tmpdoc = (Tdocument *) tmplist->data;
931 		if (tmpdoc->modified) {
932 			doc_save_backend(tmpdoc, docsave_normal, FALSE, FALSE);
933 		}
934 		tmplist = g_list_next(tmplist);
935 	}
936 }
937 
938 gint
doc_modified_dialog(Tdocument * doc)939 doc_modified_dialog(Tdocument * doc)
940 {
941 	const gchar *buttons[] = { _("Close _Without Saving"), GTK_STOCK_CANCEL, GTK_STOCK_SAVE, NULL };
942 	gchar *text;
943 	gint retval;
944 	text = g_strdup_printf(_("Save changes to \"%s\" before closing?"),
945 						   gtk_label_get_text(GTK_LABEL(doc->tab_label)));
946 	retval = message_dialog_new_multi(BFWIN(doc->bfwin)->main_window, GTK_MESSAGE_QUESTION, buttons, text,
947 									  _("If you don't save your changes they will be lost."));
948 	g_free(text);
949 	return retval;
950 }
951 
952 gint
project_not_found_dialog(Tbfwin * bfwin,GFile * uri)953 project_not_found_dialog(Tbfwin * bfwin, GFile * uri)
954 {
955 	gchar *tmpstr;
956 	gint retval;
957 	const gchar *buttons[] = { GTK_STOCK_CANCEL, _("_Reopen"), NULL };
958 	gchar *path = g_file_get_parse_name(uri);
959 	tmpstr = g_strdup_printf(_("Project \"%s\" failed to load"), path);
960 	retval =	message_dialog_new_multi(bfwin->main_window, GTK_MESSAGE_WARNING, buttons, tmpstr,
961 										 _("Do you want to reopen project from another location?"));
962 	g_free(tmpstr);
963 	g_free(path);
964 	return retval;
965 }
966 
967 Tclose_mode
multiple_files_modified_dialog(Tbfwin * bfwin)968 multiple_files_modified_dialog(Tbfwin * bfwin)
969 {
970 	const gchar *buttons[] = { _("Choose per _File"), _("Close _All"), _("_Cancel"), _("_Save All"), NULL };
971 	int retval = message_dialog_new_multi(bfwin->main_window,
972 										  GTK_MESSAGE_QUESTION, buttons,
973 										  _("One or more open files have been changed."),
974 										  _("If you don't save your changes they will be lost."));
975 	return (Tclose_mode) retval;
976 }
977 
978 /* return TRUE if all are either closed or saved
979 return FALSE on cancel*/
980 gboolean
choose_per_file(Tbfwin * bfwin,gboolean close_window)981 choose_per_file(Tbfwin * bfwin, gboolean close_window)
982 {
983 	GList *duplist, *tmplist;
984 	duplist = g_list_copy(bfwin->documentlist);
985 	tmplist = g_list_first(duplist);
986 	while (tmplist) {
987 		gint retval;
988 		Tdocument *tmpdoc = (Tdocument *) tmplist->data;
989 		DEBUG_MSG("choose_per_file, tmpdoc=%p\n", tmpdoc);
990 		if (tmpdoc->modified) {
991 			retval = doc_modified_dialog(tmpdoc);
992 			switch (retval) {
993 			case 0:			/* close */
994 				DEBUG_MSG("choose_per_file, call doc_close\n");
995 				tmpdoc->modified = FALSE;
996 				doc_close_single_backend(tmpdoc, TRUE, close_window);
997 				break;
998 			case 1:			/* cancel */
999 				return FALSE;
1000 				break;
1001 			case 2:			/* save */
1002 				DEBUG_MSG("choose_per_file, call doc_save\n");
1003 				doc_save_backend(tmpdoc, docsave_normal, TRUE, close_window);
1004 				break;
1005 			}
1006 		} else {
1007 			doc_close_single_backend(tmpdoc, TRUE, close_window);
1008 		}
1009 		tmplist = g_list_next(tmplist);
1010 	}
1011 	g_list_free(duplist);
1012 	return TRUE;
1013 }
1014 
1015 gboolean
doc_close_single_backend(Tdocument * doc,gboolean delay_activate,gboolean close_window)1016 doc_close_single_backend(Tdocument * doc, gboolean delay_activate, gboolean close_window)
1017 {
1018 	Tbfwin *bfwin = doc->bfwin;
1019 	if (doc->checkmodified)
1020 		checkmodified_cancel(doc->checkmodified);
1021 	if (doc->autosave_progress || doc->autosaved || doc->need_autosave)
1022 		remove_autosave(doc);
1023 	if (doc->load != NULL || doc->info != NULL) {
1024 		/* we should cancel the action now..., and then let the callbacks close it...
1025 		   the order is important, because the info callback will not close the document,
1026 		   only the load callback will call doc_close_single_backend */
1027 		doc->close_doc = TRUE;
1028 		doc->close_window = close_window;
1029 		if (doc->info)
1030 			file_asyncfileinfo_cancel(doc->info);
1031 		if (doc->load)
1032 			file2doc_cancel(doc->load);
1033 		/* we will not cancel save operations, because it might corrupt the file, let
1034 		   them just timeout */
1035 		DEBUG_MSG("doc_close_single_backend, cancelled load/info and set close_doc to TRUE, returning now\n");
1036 		return FALSE;
1037 	}
1038 	if (doc->autosaved || doc->autosave_progress || doc->need_autosave) {
1039 		remove_autosave(doc);
1040 	}
1041 	if (doc_is_empty_non_modified_and_nameless(doc)
1042 		&& g_list_length(BFWIN(doc->bfwin)->documentlist) <= 1) {
1043 		if (close_window) {
1044 			bfwin_destroy_and_cleanup(BFWIN(doc->bfwin));
1045 		}
1046 		DEBUG_MSG("doc_close_single_backend, doc_is_empty_non_modified_and_nameless returned TRUE, return TRUE\n");
1047 		return TRUE;
1048 	}
1049 	if (doc->modified) {
1050 		gint retval = doc_modified_dialog(doc);
1051 		switch (retval) {
1052 		case 0:
1053 			doc_destroy(doc, close_window || delay_activate);
1054 			break;
1055 		case 1:
1056 			return FALSE;
1057 			break;
1058 		case 2:
1059 			doc_save_backend(doc, docsave_normal, TRUE, close_window);
1060 			break;
1061 		}
1062 	} else {
1063 		doc_destroy(doc, close_window || delay_activate);
1064 	}
1065 	if (close_window && bfwin->documentlist == NULL) {	/* the documentlist is empty */
1066 		bfwin_destroy_and_cleanup(bfwin);
1067 	}
1068 	DEBUG_MSG("doc_close_single_backend, finished!\n");
1069 	return TRUE;
1070 }
1071 
1072 void
doc_save_all_close(Tbfwin * bfwin)1073 doc_save_all_close(Tbfwin * bfwin)
1074 {
1075 	GList *tmplist, *duplist;
1076 	Tdocument *tmpdoc;
1077 	duplist = g_list_copy(bfwin->documentlist); /* Copy ducumentlist first, since using just tmplist causes unexpected behavior as docs are destroyed */
1078 	tmplist = g_list_first(duplist);
1079 	while (tmplist) {
1080 		Tdocument *tmpdoc = (Tdocument *) tmplist->data;
1081 #ifdef MAC_INTEGRATION
1082 		if (tmpdoc->uri == NULL && main_v->osx_status == 1 ) { /* if osx app is terminating we do not save untitled files that does not have uri */
1083 		DEBUG_MSG("bfwin_osx_terminate_event, closing untitled document\n");
1084 		tmpdoc->modified = FALSE;
1085 		doc_close_single_backend(tmpdoc, TRUE, TRUE); }
1086 		else {
1087 		DEBUG_MSG("bfwin_osx_terminate_event, saving document\n");
1088 		doc_save_backend(tmpdoc, docsave_normal, TRUE, TRUE);
1089 		}
1090 #else
1091 		doc_save_backend(tmpdoc, docsave_normal, TRUE, TRUE);
1092 #endif
1093 		tmplist = g_list_next(tmplist);
1094 	}
1095 	g_list_free(duplist);
1096 }
1097 
1098 /**
1099  * file_close_cb:
1100  * @widget: unused #GtkWidget
1101  * @data: unused #gpointer
1102  *
1103  * Close the current document.
1104  *
1105  * Return value: void
1106  **/
1107 void
file_close_cb(GtkWidget * widget,Tbfwin * bfwin)1108 file_close_cb(GtkWidget * widget, Tbfwin * bfwin)
1109 {
1110 	if (bfwin->current_document)
1111 		doc_close_single_backend(bfwin->current_document, FALSE, FALSE);
1112 }
1113 
1114 void
doc_close_multiple_backend(Tbfwin * bfwin,gboolean close_window,Tclose_mode close_mode)1115 doc_close_multiple_backend(Tbfwin * bfwin, gboolean close_window, Tclose_mode close_mode)
1116 {
1117 	GList *tmplist, *duplist;
1118 	Tdocument *tmpdoc;
1119 
1120 /*	if (g_list_length(bfwin->documentlist) == 1) {
1121 		return doc_close_single_backend(bfwin->current_document, FALSE, close_window);
1122 	}
1123 	if (have_modified_documents(bfwin->documentlist)) {
1124 		retval = multiple_files_modified_dialog(bfwin);
1125 		if (retval == 2) {
1126 			return FALSE;
1127 		}
1128 	}*/
1129 
1130 	/* we duplicate the documentlist so we can safely walk trough the list, in
1131 	   our duplicate list there is no chance that the list is changed during the time
1132 	   we walk the list */
1133 	duplist = g_list_copy(bfwin->documentlist);
1134 	tmplist = g_list_first(duplist);
1135 	while (tmplist) {
1136 		tmpdoc = (Tdocument *) tmplist->data;
1137 		if (close_mode == close_mode_close_all) {
1138 			/* fake that this document was not modified */
1139 			tmpdoc->modified = FALSE;
1140 			doc_close_single_backend(tmpdoc, TRUE, close_window);
1141 		} else if (close_mode == close_mode_save_all) {
1142 			doc_save_backend(tmpdoc, docsave_normal, TRUE, close_window);
1143 		}
1144 		tmplist = g_list_next(tmplist);
1145 	}
1146 	g_list_free(duplist);
1147 	DEBUG_MSG("doc_close_multiple_backend, finished\n");
1148 	if (!close_window)
1149 		bfwin_notebook_changed(bfwin, -1);
1150 }
1151 
1152 void
file_close_all(Tbfwin * bfwin)1153 file_close_all(Tbfwin * bfwin)
1154 {
1155 	if (have_modified_documents(bfwin->documentlist)) {
1156 		Tclose_mode retval = multiple_files_modified_dialog(bfwin);
1157 		switch (retval) {
1158 		case close_mode_cancel:
1159 			return;
1160 			break;
1161 		case close_mode_per_file:
1162 			choose_per_file(bfwin, FALSE);
1163 			break;
1164 		case close_mode_save_all:
1165 		case close_mode_close_all:
1166 			doc_close_multiple_backend(bfwin, FALSE, retval);
1167 			break;
1168 		}
1169 	} else {
1170 		doc_close_multiple_backend(bfwin, FALSE, close_mode_close_all);
1171 	}
1172 }
1173 
1174 /**
1175  * file_close_all_cb:
1176  * @widget: unused #GtkWidget
1177  * @bfwin: #Tbfwin*
1178  *
1179  * Close all open files. Prompt user when neccessary.
1180  *
1181  * Return value: void
1182  **/
1183 void
file_close_all_cb(GtkWidget * widget,Tbfwin * bfwin)1184 file_close_all_cb(GtkWidget * widget, Tbfwin * bfwin)
1185 {
1186 	if (have_modified_documents(bfwin->documentlist)) {
1187 		Tclose_mode retval = multiple_files_modified_dialog(bfwin);
1188 		switch (retval) {
1189 		case close_mode_cancel:
1190 			return;
1191 			break;
1192 		case close_mode_per_file:
1193 			choose_per_file(bfwin, FALSE);
1194 			break;
1195 		case close_mode_save_all:
1196 		case close_mode_close_all:
1197 			doc_close_multiple_backend(bfwin, FALSE, retval);
1198 			break;
1199 		}
1200 	} else {
1201 		doc_close_multiple_backend(bfwin, FALSE, close_mode_close_all);
1202 	}
1203 }
1204 
1205 void
file_new_doc(Tbfwin * bfwin)1206 file_new_doc(Tbfwin * bfwin)
1207 {
1208 	Tdocument *doc;
1209 	GFile *template = NULL;
1210 
1211 	if (bfwin->session->template && bfwin->session->template[0]) {
1212 		template = g_file_new_for_commandline_arg(bfwin->session->template);
1213 		bfwin->focus_next_new_doc = TRUE;
1214 	}
1215 	doc = doc_new_with_template(bfwin, template, TRUE);
1216 	if (!template && doc != bfwin->current_document) {
1217 		/* for a template this will be done by the template loader callback */
1218 		bfwin_switch_to_document_by_pointer(bfwin, doc);
1219 	}
1220 }
1221 
1222 /**
1223  * file_new_cb:
1224  * @windget: #GtkWidget* ignored
1225  * @bfwin: Tbfwin* where to open the new document
1226  *
1227  * Create a new, empty file in window bfwin
1228  *
1229  * Return value: void
1230  **/
1231 void
file_new_cb(GtkWidget * widget,Tbfwin * bfwin)1232 file_new_cb(GtkWidget * widget, Tbfwin * bfwin)
1233 {
1234 	file_new_doc(bfwin);
1235 }
1236 
1237 static void
file_reload_all_modified_check_lcb(Tcheckmodified_status status,GError * gerror,GFileInfo * orig,GFileInfo * new,gpointer user_data)1238 file_reload_all_modified_check_lcb(Tcheckmodified_status status, GError * gerror,
1239 								   GFileInfo * orig, GFileInfo * new, gpointer user_data)
1240 {
1241 	if (status == CHECKMODIFIED_MODIFIED) {
1242 		DEBUG_MSG("file_reload_all_modified_check_lcb, reload %p\n", user_data);
1243 		doc_reload(DOCUMENT(user_data), new, FALSE);
1244 	}
1245 }
1246 
1247 void
file_reload_all_modified(Tbfwin * bfwin)1248 file_reload_all_modified(Tbfwin * bfwin)
1249 {
1250 	GList *tmplist = g_list_first(bfwin->documentlist);
1251 	while (tmplist) {
1252 		if (DOCUMENT(tmplist->data)->uri && DOCUMENT(tmplist->data)->status == DOC_STATUS_COMPLETE) {
1253 			DEBUG_MSG("file_reload_all_modified, check %p\n", tmplist->data);
1254 			file_checkmodified_uri_async(DOCUMENT(tmplist->data)->uri, DOCUMENT(tmplist->data)->fileinfo,
1255 										 file_reload_all_modified_check_lcb, tmplist->data);
1256 		}
1257 		tmplist = g_list_next(tmplist);
1258 	}
1259 }
1260 
1261 typedef struct {
1262 	GtkWidget *dialog;
1263 	Tbfwin *bfwin;
1264 	GtkWidget *entry_local;
1265 	GtkWidget *entry_remote;
1266 	GtkWidget *delete_deprecated;
1267 	GtkWidget *include_hidden;
1268 	GtkWidget *include_backup;
1269 	GtkWidget *progress;
1270 	GtkWidget *messagelabel;
1271 	gulong signal_id;
1272 } Tsyncdialog;
1273 
1274 static void
sync_progress(GFile * uri,gint total,gint done,gint failed,gpointer user_data)1275 sync_progress(GFile *uri, gint total, gint done, gint failed, gpointer user_data)
1276 {
1277 	Tsyncdialog *sd = user_data;
1278 	if (total > 0) {
1279 		gchar *text;
1280 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sd->progress), 1.0 * done / total);
1281 		if (uri) {
1282 			gchar * curi = g_file_get_uri(uri);
1283 			text = g_strdup_printf("%s (%d / %d)", curi, done, total);
1284 			g_free(curi);
1285 		} else {
1286 			text = g_strdup_printf("%d / %d", done, total);
1287 		}
1288 		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(sd->progress), text);
1289 /*		g_print("%s\n",text);*/
1290 		g_free(text);
1291 		if (failed > 0) {
1292 			text =
1293 				g_strdup_printf(ngettext
1294 								("<span color=\"red\">%d failure</span>",
1295 								 "<span color=\"red\">%d failures</span>", failed), failed);
1296 			gtk_label_set_markup(GTK_LABEL(sd->messagelabel), text);
1297 			gtk_widget_show(sd->messagelabel);
1298 			g_free(text);
1299 		}
1300 	} else if (total == -1) {
1301 		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(sd->progress), 1);
1302 		if (failed > 0) {
1303 			gtk_progress_bar_set_text(GTK_PROGRESS_BAR(sd->progress), _("incomplete finished"));
1304 		} else {
1305 			gtk_progress_bar_set_text(GTK_PROGRESS_BAR(sd->progress), _("completed"));
1306 		}
1307 		g_signal_handler_unblock(sd->dialog, sd->signal_id);
1308 	}
1309 }
1310 
1311 static void
sync_dialog_response_lcb(GtkDialog * dialog,gint response_id,gpointer user_data)1312 sync_dialog_response_lcb(GtkDialog * dialog, gint response_id, gpointer user_data)
1313 {
1314 	Tsyncdialog *sd = user_data;
1315 	DEBUG_MSG("sync_dialog_response_lcb, response=%d\n", response_id);
1316 	if (response_id > 0) {
1317 		GFile *local, *remote;
1318 		gtk_label_set_text(GTK_LABEL(sd->messagelabel), "");
1319 		gtk_widget_hide(sd->messagelabel);
1320 		local = g_file_new_for_commandline_arg(gtk_entry_get_text(GTK_ENTRY(sd->entry_local)));
1321 		remote = g_file_new_for_commandline_arg(gtk_entry_get_text(GTK_ENTRY(sd->entry_remote)));
1322 		if (response_id == 1) {
1323 			sync_directory(local, remote,
1324 						   gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->delete_deprecated)),
1325 						   gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->include_hidden)),
1326 						   gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->include_backup)), sync_progress,
1327 						   sd);
1328 		} else if (response_id == 2) {
1329 			sync_directory(remote, local,
1330 						   gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->delete_deprecated)),
1331 						   gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->include_hidden)),
1332 						   gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->include_backup)), sync_progress,
1333 						   sd);
1334 		}
1335 		sd->bfwin->session->sync_delete_deprecated =
1336 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->delete_deprecated));
1337 		sd->bfwin->session->sync_include_hidden =
1338 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->include_hidden));
1339 		sd->bfwin->session->sync_include_backup =
1340 			gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sd->include_backup));
1341 		g_signal_handler_block(sd->dialog, sd->signal_id);
1342 		g_free(sd->bfwin->session->sync_local_uri);
1343 		sd->bfwin->session->sync_local_uri = g_file_get_uri(local);
1344 		g_free(sd->bfwin->session->sync_remote_uri);
1345 		sd->bfwin->session->sync_remote_uri = g_file_get_uri(remote);
1346 
1347 		g_object_unref(local);
1348 		g_object_unref(remote);
1349 	} else {
1350 		gtk_widget_destroy(sd->dialog);
1351 		g_slice_free(Tsyncdialog, sd);
1352 	}
1353 }
1354 
1355 void
sync_dialog(Tbfwin * bfwin)1356 sync_dialog(Tbfwin * bfwin)
1357 {
1358 	Tsyncdialog *sd;
1359 	GtkWidget *carea, *table;
1360 
1361 	sd = g_slice_new0(Tsyncdialog);
1362 	sd->bfwin = bfwin;
1363 	sd->dialog = gtk_dialog_new_with_buttons(_("Upload / Download"),
1364 											 GTK_WINDOW(bfwin->main_window),
1365 											 GTK_DIALOG_DESTROY_WITH_PARENT,
1366 											 _("Upload"), 1, _("Download"), 2,
1367 											 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
1368 
1369 	carea = gtk_dialog_get_content_area(GTK_DIALOG(sd->dialog));
1370 	table = dialog_table_in_vbox(5, 3, 6, carea, TRUE,TRUE, 3);
1371 
1372 	sd->entry_local = dialog_entry_in_table(NULL, table, 1, 2,0, 1);
1373 	dialog_mnemonic_label_in_table(_("Local directory"), sd->entry_local, table,
1374 									0, 1, 0,1);
1375 	gtk_table_attach(GTK_TABLE(table), file_but_new2(sd->entry_local, 1, bfwin, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER),
1376 									2, 3, 0, 1, GTK_FILL, GTK_FILL, 3, 3);
1377 
1378 	sd->entry_remote = dialog_entry_in_table(NULL, table, 1, 2,1, 2);
1379 	dialog_mnemonic_label_in_table(_("Remote directory"), sd->entry_remote, table,
1380 									0, 1, 1,2);
1381 	gtk_table_attach(GTK_TABLE(table), file_but_new2(sd->entry_remote, 1, bfwin, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER),
1382 									2, 3, 1, 2, GTK_FILL, GTK_FILL, 3, 3);
1383 
1384 	sd->delete_deprecated = dialog_check_button_in_table(_("Delete deprecated files"),
1385 						bfwin->session->sync_delete_deprecated, table,
1386 						0, 3, 2, 3);
1387 
1388 	sd->include_hidden = dialog_check_button_in_table(_("Include hidden files"),
1389 						bfwin->session->sync_include_hidden, table,
1390 						0, 3, 3, 4);
1391 	sd->include_backup = dialog_check_button_in_table(_("Include backup files"),
1392 						bfwin->session->sync_include_backup, table,
1393 						0, 3, 4, 5);
1394 
1395 	sd->messagelabel = gtk_label_new(NULL);
1396 	gtk_box_pack_start(GTK_BOX(carea), sd->messagelabel, FALSE, FALSE, 4);
1397 
1398 	sd->progress = gtk_progress_bar_new();
1399 	gtk_progress_bar_set_ellipsize(GTK_PROGRESS_BAR(sd->progress), PANGO_ELLIPSIZE_MIDDLE);
1400 	#if GTK_CHECK_VERSION(3, 0, 0)
1401 	gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR(sd->progress), TRUE);
1402 	#endif
1403 	gtk_box_pack_start(GTK_BOX(carea), sd->progress, FALSE, FALSE, 4);
1404 
1405 	if (bfwin->session->sync_local_uri && bfwin->session->sync_local_uri[0] != '\0') {
1406 		gtk_entry_set_text(GTK_ENTRY(sd->entry_local), bfwin->session->sync_local_uri);
1407 	} else if (bfwin->session->recent_dirs && bfwin->session->recent_dirs->data
1408 			   && *(gchar *) bfwin->session->recent_dirs->data != '\0') {
1409 		gtk_entry_set_text(GTK_ENTRY(sd->entry_local), bfwin->session->recent_dirs->data);
1410 	}
1411 
1412 	if (bfwin->session->sync_remote_uri && bfwin->session->sync_remote_uri[0] != '\0') {
1413 		gtk_entry_set_text(GTK_ENTRY(sd->entry_remote), bfwin->session->sync_remote_uri);
1414 	}
1415 
1416 	sd->signal_id = g_signal_connect(sd->dialog, "response", G_CALLBACK(sync_dialog_response_lcb), sd);
1417 	gtk_widget_show_all(sd->dialog);
1418 	gtk_widget_hide(sd->messagelabel);
1419 }
1420 
1421 static gchar *
modified_on_disk_warning_string(const gchar * filename,GFileInfo * oldfinfo,GFileInfo * newfinfo)1422 modified_on_disk_warning_string(const gchar * filename, GFileInfo * oldfinfo, GFileInfo * newfinfo)
1423 {
1424 	gchar *tmpstr, *oldtimestr, *newtimestr;
1425 	time_t newtime, oldtime;
1426 	goffset oldsize, newsize;
1427 	gchar *strnewsize, *stroldsize;
1428 
1429 	newtime = (time_t) g_file_info_get_attribute_uint64(newfinfo, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1430 	oldtime = (time_t) g_file_info_get_attribute_uint64(oldfinfo, G_FILE_ATTRIBUTE_TIME_MODIFIED);
1431 	newtimestr = bf_portable_time(&newtime);
1432 	oldtimestr = bf_portable_time(&oldtime);
1433 	newsize = g_file_info_get_size(newfinfo);
1434 	oldsize = g_file_info_get_size(oldfinfo);
1435 	strnewsize = g_strdup_printf("%"G_GOFFSET_FORMAT"", newsize);
1436 	stroldsize = g_strdup_printf("%"G_GOFFSET_FORMAT"", oldsize);
1437 	/*g_print("oldtimestr=%s, newtimestr=%s\n",oldtimestr,newtimestr); */
1438 	tmpstr = g_strdup_printf(_("Filename:%s changed on disk.\n\n"
1439 							   "Original modification time was %s\n"
1440 							   "New modification time is %s\n"
1441 							   "Original size was %s bytes\n"
1442 							   "New size is %s bytes"), filename, oldtimestr, newtimestr, stroldsize, strnewsize);
1443 	g_free(newtimestr);
1444 	g_free(oldtimestr);
1445 	g_free(strnewsize);
1446 	g_free(stroldsize);
1447 	return tmpstr;
1448 }
1449 
1450 static void
doc_activate_modified_lcb(Tcheckmodified_status status,GError * gerror,GFileInfo * orig,GFileInfo * new,gpointer callback_data)1451 doc_activate_modified_lcb(Tcheckmodified_status status, GError * gerror, GFileInfo * orig, GFileInfo * new,
1452 						  gpointer callback_data)
1453 {
1454 	Tdocument *doc = callback_data;
1455 	switch (status) {
1456 	case CHECKMODIFIED_ERROR:
1457 		DEBUG_MSG("doc_activate_modified_lcb, CHECKMODIFIED_ERROR ??\n");
1458 		if (gerror->code == G_IO_ERROR_NOT_FOUND) {
1459 			gchar *tmpstr;
1460 			gint retval;
1461 			const gchar *buttons[] = { _("_Unset file name"), _("_Save"), NULL };
1462 			/* file is deleted on disk, what do we do now ? */
1463 			tmpstr = g_strdup_printf(_("File name: %s"), gtk_label_get_text(GTK_LABEL(doc->tab_menu)));
1464 			retval = message_dialog_new_multi(BFWIN(doc->bfwin)->main_window,
1465 											  GTK_MESSAGE_WARNING,
1466 											  buttons, _("File disappeared from disk\n"), tmpstr);
1467 			g_free(tmpstr);
1468 			if (retval == 1) {	/* save */
1469 				doc_save_backend(doc, docsave_normal, FALSE, FALSE);
1470 			} else {			/* unset */
1471 				document_unset_filename(doc);
1472 			}
1473 		} else {
1474 			/* TODO: warn the user */
1475 		}
1476 		break;
1477 	case CHECKMODIFIED_CANCELLED:
1478 		DEBUG_MSG("doc_activate_modified_lcb, CHECKMODIFIED_CANCELLED\n");
1479 		break;
1480 	case CHECKMODIFIED_MODIFIED:
1481 		{
1482 			gchar *tmpstr /*, *oldtimestr, *newtimestr */ ;
1483 			gint retval;
1484 			const gchar *buttons[] =
1485 				{ _("_Ignore"), _("_Reload"), _("Check and reload all documents"), NULL };
1486 			/*time_t newtime,origtime;
1487 
1488 			   newtime = (time_t)g_file_info_get_attribute_uint64(new,G_FILE_ATTRIBUTE_TIME_MODIFIED);
1489 			   origtime = (time_t)g_file_info_get_attribute_uint64(orig,G_FILE_ATTRIBUTE_TIME_MODIFIED);
1490 			   g_print("doc_activate_modified_lcb, newtime=%ld,%d origtime=%ld,%d newsize=%"G_GOFFSET_FORMAT" origsize=%"G_GOFFSET_FORMAT"\n",
1491 			   newtime,g_file_info_get_attribute_uint32(new,G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC),
1492 			   origtime,g_file_info_get_attribute_uint32(orig,G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC),
1493 			   g_file_info_get_size(new),g_file_info_get_size(orig));
1494 			   newtimestr = bf_portable_time(&newtime);
1495 			   oldtimestr = bf_portable_time(&origtime);
1496 
1497 			   tmpstr = g_strdup_printf(_("Filename: %s\n\nNew modification time is: %s\nOld modification time is: %s"), gtk_label_get_text(GTK_LABEL(doc->tab_menu)), newtimestr, oldtimestr);
1498 			 */
1499 			tmpstr = modified_on_disk_warning_string(gtk_label_get_text(GTK_LABEL(doc->tab_menu)), orig, new);
1500 			retval = message_dialog_new_multi(BFWIN(doc->bfwin)->main_window,
1501 											  GTK_MESSAGE_WARNING,
1502 											  buttons, _("File changed on disk\n"), tmpstr);
1503 			g_free(tmpstr);
1504 			/*g_free(newtimestr);
1505 			   g_free(oldtimestr); */
1506 			if (retval == 0) {	/* ignore */
1507 				/*if (doc->fileinfo) {
1508 				   g_object_unref(doc->fileinfo);
1509 				   }
1510 				   doc->fileinfo = new;
1511 				   g_object_ref(doc->fileinfo); */
1512 				GTimeVal mtime;
1513 				g_file_info_set_size(doc->fileinfo, g_file_info_get_size(new));
1514 				g_file_info_get_modification_time(new, &mtime);
1515 				g_file_info_set_modification_time(doc->fileinfo, &mtime);
1516 				g_file_info_set_attribute_string(doc->fileinfo, "etag::value",
1517 												 g_file_info_get_attribute_string(new, "etag::value"));
1518 				doc_set_tooltip(doc);
1519 			} else if (retval == 1) {	/* reload */
1520 				doc_reload(doc, new, FALSE);
1521 			} else {			/* reload all modified documents */
1522 				file_reload_all_modified(doc->bfwin);
1523 			}
1524 		}
1525 		break;
1526 	case CHECKMODIFIED_OK:
1527 		/* do nothing */
1528 		break;
1529 	}
1530 	doc->checkmodified = NULL;
1531 }
1532 
1533 void
doc_start_modified_check(Tdocument * doc)1534 doc_start_modified_check(Tdocument * doc)
1535 {
1536 	if (doc->uri && doc->fileinfo && !doc->checkmodified && !doc->save) {	/* don't check during another check, or during save */
1537 		doc->checkmodified =
1538 			file_checkmodified_uri_async(doc->uri, doc->fileinfo, doc_activate_modified_lcb, doc);
1539 	}
1540 }
1541 
1542 static gboolean
modified_on_disk_check_lcb(gpointer data)1543 modified_on_disk_check_lcb(gpointer data)
1544 {
1545 	GList *tmplist = g_list_first(main_v->bfwinlist);
1546 	while (tmplist) {
1547 		Tbfwin *bfwin = tmplist->data;
1548 		if (bfwin->current_document) {
1549 			doc_start_modified_check(bfwin->current_document);
1550 		}
1551 		tmplist = g_list_next(tmplist);
1552 	}
1553 	return TRUE;
1554 }
1555 
1556 void
modified_on_disk_check_init(void)1557 modified_on_disk_check_init(void)
1558 {
1559 	if (main_v->props.check_for_modified_on_disk==1 && !main_v->periodic_check_id)
1560 		main_v->periodic_check_id =
1561 			g_timeout_add_seconds_full(G_PRIORITY_LOW, 15, modified_on_disk_check_lcb, NULL, NULL);
1562 	else if (main_v->props.check_for_modified_on_disk!=1 && main_v->periodic_check_id) {
1563 		g_source_remove(main_v->periodic_check_id);
1564 		main_v->periodic_check_id = 0;
1565 	}
1566 }
1567