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