1 
2 /*
3  * dialog-quit.c:
4  *   Dialog for quit (selecting what to save)
5  *
6  * Author:
7  *   Morten Welinder (terra@gnome.org)
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, see <https://www.gnu.org/licenses/>.
21  */
22 
23 #include <gnumeric-config.h>
24 #include <gnumeric.h>
25 #include <dialogs/dialogs.h>
26 #include <gui-file.h>
27 #include <gui-util.h>
28 #include <workbook.h>
29 #include <wbc-gtk.h>
30 #include <gui-clipboard.h>
31 #include <application.h>
32 
33 #include <goffice/goffice.h>
34 
35 #include <glib/gi18n-lib.h>
36 
37 #include <string.h>
38 
39 enum {
40 	RESPONSE_ALL = 1,
41 	RESPONSE_NONE = 2
42 };
43 
44 enum {
45 	QUIT_COL_CHECK,
46 	QUIT_COL_DOC
47 };
48 
49 /* ------------------------------------------------------------------------- */
50 
51 static void
url_renderer_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)52 url_renderer_func (GtkTreeViewColumn *tree_column,
53 		   GtkCellRenderer   *cell,
54 		   GtkTreeModel      *model,
55 		   GtkTreeIter       *iter,
56 		   gpointer           user_data)
57 {
58 	GODoc *doc = NULL;
59 	const char *uri;
60 	char *markup, *shortname, *filename, *longname, *duri;
61 
62 	gtk_tree_model_get (model, iter,
63 			    QUIT_COL_DOC, &doc,
64 			    -1);
65 	g_return_if_fail (GO_IS_DOC (doc));
66 
67 	uri = go_doc_get_uri (doc);
68 	filename = go_filename_from_uri (uri);
69 	if (filename) {
70 		shortname = g_filename_display_basename (filename);
71 	} else {
72 		shortname = g_filename_display_basename (uri);
73 	}
74 
75 	duri = g_uri_unescape_string (uri, NULL);
76 	longname = duri
77 		? g_filename_display_name (duri)
78 		: g_strdup (uri);
79 
80 	markup = g_markup_printf_escaped (_("<b>%s</b>\n"
81 					    "<small>Location: %s</small>"),
82 					  shortname,
83 					  longname);
84 	g_object_set (cell, "markup", markup, NULL);
85 	g_free (markup);
86 	g_free (shortname);
87 	g_free (longname);
88 	g_free (duri);
89 	g_free (filename);
90 	g_object_unref (doc);
91 }
92 
93 static void
age_renderer_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)94 age_renderer_func (GtkTreeViewColumn *tree_column,
95 		   GtkCellRenderer   *cell,
96 		   GtkTreeModel      *model,
97 		   GtkTreeIter       *iter,
98 		   gpointer           user_data)
99 {
100 	GODoc *doc = NULL;
101 
102 	gtk_tree_model_get (model, iter,
103 			    QUIT_COL_DOC, &doc,
104 			    -1);
105 	g_return_if_fail (GO_IS_DOC (doc));
106 
107 	if (go_doc_is_dirty (doc)) {
108 		time_t quitting_time = GPOINTER_TO_SIZE
109 			(g_object_get_data (G_OBJECT (tree_column),
110 					    "quitting_time"));
111 		int age = quitting_time -
112 			go_doc_get_dirty_time (doc) / 1000000;
113 		char *agestr;
114 		if (age < 0)
115 			agestr = g_strdup (_("unknown"));
116 		else if (age < 60)
117 			agestr = g_strdup_printf
118 				(ngettext ("%d second", "%d seconds", age),
119 				 age);
120 		else if (age < 60 * 60) {
121 			int mins = age / 60;
122 			agestr = g_strdup_printf
123 				(ngettext ("%d minute", "%d minutes", mins),
124 				 mins);
125 		} else
126 			agestr = g_strdup (_("a long time"));
127 
128 		g_object_set (cell, "text", agestr, NULL);
129 		g_free (agestr);
130 	} else {
131 		/* What are we doing here? */
132 		g_object_set (cell, "text", "", NULL);
133 	}
134 	g_object_unref (doc);
135 }
136 
137 static gboolean
foreach_is_file_set(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gboolean * data)138 foreach_is_file_set (GtkTreeModel *model, GtkTreePath *path,
139 		     GtkTreeIter *iter, gboolean *data)
140 {
141 	gboolean value;
142 
143 	gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
144 			    QUIT_COL_CHECK, &value, -1);
145 
146 	*data = value;
147 
148 	return value;
149 }
150 
151 static gboolean
files_set(GtkTreeModel * model)152 files_set (GtkTreeModel *model)
153 {
154 	gboolean files_set_state = FALSE;
155 
156 	gtk_tree_model_foreach (GTK_TREE_MODEL (model),
157 				(GtkTreeModelForeachFunc) foreach_is_file_set,
158 				&files_set_state);
159 
160 	return files_set_state;
161 }
162 
163 static void
cb_toggled_save(GtkCellRendererToggle * cell,gchar * path_string,gpointer data)164 cb_toggled_save (GtkCellRendererToggle *cell,
165 		 gchar                 *path_string,
166 		 gpointer               data)
167 {
168 	GtkTreeModel *model = GTK_TREE_MODEL (data);
169 	GtkTreeIter iter;
170 	GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
171 
172 	if (gtk_tree_model_get_iter (model, &iter, path)) {
173 		gboolean value;
174 		gtk_tree_model_get (model, &iter,
175 				    QUIT_COL_CHECK, &value, -1);
176 		gtk_list_store_set (GTK_LIST_STORE (model), &iter,
177 				    QUIT_COL_CHECK, !value, -1);
178 	} else {
179 		g_warning ("Did not get a valid iterator");
180 	}
181 
182 	gtk_tree_path_free (path);
183 }
184 
185 static void
set_all(GtkTreeModel * model,gboolean value)186 set_all (GtkTreeModel *model, gboolean value)
187 {
188 	GtkTreeIter iter;
189 	gboolean ok = gtk_tree_model_get_iter_first (model, &iter);
190 
191 	while (ok) {
192 		gtk_list_store_set (GTK_LIST_STORE (model), &iter,
193 				    QUIT_COL_CHECK, value, -1);
194 		ok = gtk_tree_model_iter_next (model, &iter);
195 	}
196 }
197 
198 static void
cb_select_all(G_GNUC_UNUSED GtkWidget * button,GtkTreeModel * model)199 cb_select_all (G_GNUC_UNUSED GtkWidget *button,
200 	       GtkTreeModel *model)
201 {
202 	set_all (model, TRUE);
203 }
204 
205 static void
cb_clear_all(G_GNUC_UNUSED GtkWidget * button,GtkTreeModel * model)206 cb_clear_all (G_GNUC_UNUSED GtkWidget *button,
207 	      GtkTreeModel *model)
208 {
209 	set_all (model, FALSE);
210 }
211 
212 static void
cb_list_row_changed_save_sensitivity(GtkListStore * list,GtkTreePath * path_string,GtkTreeIter * iter,GtkWidget * widget)213 cb_list_row_changed_save_sensitivity (GtkListStore *list, GtkTreePath *path_string,
214 				      GtkTreeIter *iter, GtkWidget *widget)
215 {
216 	GtkTreeModel *model = GTK_TREE_MODEL (list);
217 
218 	if (files_set (model) == TRUE)
219 		gtk_widget_set_sensitive (GTK_WIDGET (widget), TRUE);
220 	else
221 		gtk_widget_set_sensitive (GTK_WIDGET (widget), FALSE);
222 }
223 
224 static void
cb_list_row_changed_discard_sensitivity(GtkListStore * list,GtkTreePath * path_string,GtkTreeIter * iter,GtkWidget * widget)225 cb_list_row_changed_discard_sensitivity (GtkListStore *list,
226 					 GtkTreePath *path_string,
227 					 GtkTreeIter *iter,
228 					 GtkWidget *widget)
229 {
230 	gtk_widget_set_sensitive (GTK_WIDGET (widget),
231 				  !files_set (GTK_TREE_MODEL (list)));
232 }
233 
234 static gboolean
show_quit_dialog(GList * dirty,WBCGtk * wbcg)235 show_quit_dialog (GList *dirty, WBCGtk *wbcg)
236 {
237 	GtkBuilder *gui;
238 	GtkDialog *dialog;
239 	gboolean multiple = (dirty->next != NULL);
240 	GObject *model;
241 	GtkWidget *save_selected_button;
242 	GtkCellRenderer *save_renderer;
243 	GList *l;
244 	int res;
245 	gboolean quit;
246 	GObject *age_column;
247 	time_t quitting_time = g_get_real_time () / 1000000;
248 
249 	gui = gnm_gtk_builder_load ("res:ui/quit.ui", NULL, GO_CMD_CONTEXT (wbcg));
250         if (gui == NULL)
251                 return FALSE;
252 
253 	dialog = GTK_DIALOG (go_gtk_builder_get_widget (gui, "quit_dialog"));
254 	model = gtk_builder_get_object (gui, "quit_model");
255 	save_selected_button = go_gtk_builder_get_widget (gui, "save_selected_button");
256 	save_renderer = GTK_CELL_RENDERER (gtk_builder_get_object (gui, "save_renderer"));
257 
258 	if (multiple) {
259 		GObject *model = gtk_builder_get_object (gui, "quit_model");
260 		g_signal_connect (model,
261 				  "row-changed",
262 				  G_CALLBACK (cb_list_row_changed_discard_sensitivity),
263 				  gtk_builder_get_object (gui, "discard_all_button"));
264 
265 		g_signal_connect (model,
266 				  "row-changed",
267 				  G_CALLBACK (cb_list_row_changed_save_sensitivity),
268 				  save_selected_button);
269 
270 		gtk_widget_destroy (go_gtk_builder_get_widget (gui, "save_button"));
271 
272 		g_signal_connect (gtk_builder_get_object (gui, "select_all_button"),
273 				  "clicked", G_CALLBACK (cb_select_all),
274 				  model);
275 		g_signal_connect (gtk_builder_get_object (gui, "clear_all_button"),
276 				  "clicked", G_CALLBACK (cb_clear_all),
277 				  model);
278 
279 		g_signal_connect (G_OBJECT (save_renderer),
280 				  "toggled",
281 				  G_CALLBACK (cb_toggled_save), model);
282 
283 	} else {
284 		gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (gui, "save_column")),
285 						  FALSE);
286 		gtk_widget_destroy (save_selected_button);
287 		gtk_widget_destroy (go_gtk_builder_get_widget (gui, "selection_box"));
288 	}
289 
290 	gtk_tree_view_column_set_cell_data_func
291 		(GTK_TREE_VIEW_COLUMN (gtk_builder_get_object (gui, "url_column")),
292 		 GTK_CELL_RENDERER (gtk_builder_get_object (gui, "url_renderer")),
293 		 url_renderer_func,
294 		 NULL,
295 		 NULL);
296 
297 	age_column = gtk_builder_get_object (gui, "age_column");
298 	g_object_set_data (age_column, "quitting_time",
299 			   GSIZE_TO_POINTER (quitting_time));
300 	gtk_tree_view_column_set_cell_data_func
301 		(GTK_TREE_VIEW_COLUMN (age_column),
302 		 GTK_CELL_RENDERER (gtk_builder_get_object (gui, "age_renderer")),
303 		 age_renderer_func,
304 		 NULL,
305 		 NULL);
306 
307 	gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
308 
309 	/* ---------------------------------------- */
310 	/* Size the dialog.  */
311 
312 	{
313 		GtkWidget *w = GTK_WIDGET (wbcg_toplevel (wbcg));
314 		int width, height, vsep;
315 		PangoLayout *layout =
316 			gtk_widget_create_pango_layout (w, "Mg19");
317 
318 		gtk_widget_style_get (go_gtk_builder_get_widget (gui, "docs_treeview"),
319 				      "vertical_separator", &vsep,
320 				      NULL);
321 
322 		pango_layout_get_pixel_size (layout, &width, &height);
323 		gtk_widget_set_size_request (go_gtk_builder_get_widget (gui, "docs_scrolledwindow"),
324 					     width * 60 / 4,
325 					     (2 * height + vsep) * (4 + 1));
326 		g_object_unref (layout);
327 	}
328 
329 	/* ---------------------------------------- */
330 	/* Populate the model.  */
331 
332 	for (l = dirty; l; l = l->next) {
333 		GODoc *doc = l->data;
334 		GtkTreeIter iter;
335 		GtkListStore *list = GTK_LIST_STORE (model);
336 
337 		gtk_list_store_append (list, &iter);
338 		gtk_list_store_set (list,
339 				    &iter,
340 				    QUIT_COL_CHECK, TRUE,
341 				    QUIT_COL_DOC, doc,
342 				    -1);
343 	}
344 
345 	/* ---------------------------------------- */
346 
347 	atk_object_set_role (gtk_widget_get_accessible (GTK_WIDGET (dialog)),
348 			     ATK_ROLE_ALERT);
349 
350 	res = go_gtk_dialog_run (dialog, wbcg_toplevel (wbcg));
351 	switch (res) {
352 	case GTK_RESPONSE_CANCEL:
353 	case GTK_RESPONSE_DELETE_EVENT:
354 		quit = FALSE;
355 		break;
356 
357 	case GTK_RESPONSE_NO:
358 		quit = TRUE;
359 		break;
360 
361 	default: {
362 		GtkTreeModel *tmodel = GTK_TREE_MODEL (model);
363 		GtkTreeIter iter;
364 		gboolean ok = gtk_tree_model_get_iter_first (tmodel, &iter);
365 
366 		g_return_val_if_fail (ok, FALSE);
367 		quit = TRUE;
368 		do {
369 			gboolean save = TRUE;
370 			GODoc *doc = NULL;
371 
372 			gtk_tree_model_get (tmodel, &iter,
373 					    QUIT_COL_CHECK, &save,
374 					    QUIT_COL_DOC, &doc,
375 					    -1);
376 			if (save) {
377 				gboolean ok;
378 				Workbook *wb = WORKBOOK (doc);
379 				WBCGtk *wbcg2 = wbcg_find_for_workbook (wb, wbcg, NULL, NULL);
380 
381 				ok = wbcg2 && gui_file_save (wbcg2, wb_control_view (GNM_WBC (wbcg2)));
382 				if (!ok)
383 					quit = FALSE;
384 			}
385 			g_object_unref (doc);
386 
387 			ok = gtk_tree_model_iter_next (tmodel, &iter);
388 		} while (ok);
389 		break;
390 	}
391 	}
392 
393 	g_object_unref (gui);
394 
395 	return quit;
396 }
397 
398 /* ------------------------------------------------------------------------- */
399 
400 static gint
doc_order(gconstpointer a_,gconstpointer b_)401 doc_order (gconstpointer a_, gconstpointer b_)
402 {
403 	GODoc *a = (GODoc *)a_;
404 	GODoc *b = (GODoc *)b_;
405 
406 	/* Primitive, but will work for now.  */
407 	return go_str_compare (go_doc_get_uri (a), go_doc_get_uri (b));
408 }
409 
410 void
dialog_quit(WBCGtk * wbcg)411 dialog_quit (WBCGtk *wbcg)
412 {
413 	GList *l, *dirty = NULL;
414 	gboolean quit;
415 
416 	for (l = gnm_app_workbook_list (); l; l = l->next) {
417 		GODoc *doc = l->data;
418 		if (go_doc_is_dirty (GO_DOC (doc)))
419 			dirty = g_list_prepend (dirty, doc);
420 	}
421 
422 	if (dirty) {
423 		dirty = g_list_sort (dirty, doc_order);
424 		quit = show_quit_dialog (dirty, wbcg);
425 		g_list_free (dirty);
426 		if (!quit)
427 			return;
428 	}
429 
430 	l = g_list_copy (gnm_app_workbook_list ());
431 	while (l) {
432 		Workbook *wb = l->data;
433 		l = g_list_remove (l, wb);
434 		go_doc_set_dirty (GO_DOC (wb), FALSE);
435 
436 		gnm_x_store_clipboard_if_needed (wb);
437 
438 		/* This is how we kill it?  Ugh!  */
439 		g_object_unref (wb);
440 	}
441 }
442 
443 /* ------------------------------------------------------------------------- */
444