1
2 /*
3 * io-context-gtk.c : gtk based io error context.
4 * It may be used e.g. for displaying progress and error messages
5 * before the first workbook is displayed.
6 *
7 * Author:
8 * Jon K Hellan <hellan@acm.org>
9 *
10 * (C) 2002 Jon K Hellan
11 */
12 #include <gnumeric-config.h>
13 #include <gnumeric.h>
14 #include <gui-util.h>
15 #include <io-context-gtk.h>
16 #include <goffice/goffice.h>
17 #include <application.h>
18 #include <libgnumeric.h>
19 #include <dialogs/dialogs.h>
20 #include <gnm-i18n.h>
21
22 #include <gsf/gsf-impl-utils.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #define ICG_POPUP_DELAY 3.0
27
28 #define IO_CONTEXT_GTK_CLASS(klass) \
29 (G_TYPE_CHECK_CLASS_CAST ((klass), GNM_TYPE_IO_CONTEXT_GTK, GnmIOContextGtk))
30 #define GNM_IS_IO_CONTEXT_GTK_CLASS(klass) \
31 (G_TYPE_CHECK_CLASS_TYPE ((klass), GNM_TYPE_IO_CONTEXT_GTK))
32
33 struct GnmIOContextGtk_ {
34 GOIOContext parent;
35 GtkWindow *window;
36 GtkWindow *parent_window;
37 GtkProgressBar *file_bar;
38 GtkProgressBar *work_bar;
39 GTimer *timer;
40 guint files_total;
41 guint files_done;
42
43 double progress;
44 char *progress_msg;
45 gdouble latency;
46
47 gboolean interrupted;
48
49 gboolean show_splash;
50 gboolean show_warnings;
51 };
52
53 struct GnmIOContextGtkClass_ {
54 GOIOContextClass parent_class;
55 };
56
57 enum {
58 PROP_0,
59 PROP_SHOW_SPLASH,
60 PROP_SHOW_WARNINGS
61 };
62
63 static void
cb_icg_window_destroyed(GObject * window,GnmIOContextGtk * icg)64 cb_icg_window_destroyed (GObject *window, GnmIOContextGtk *icg)
65 {
66 icg->window = NULL;
67 icg->parent_window = NULL;
68 icg->work_bar = NULL;
69 icg->file_bar = NULL;
70 if (icg->files_done == 0) {
71 gnm_shutdown (); /* Pretend to be well behaved */
72 gnm_pre_parse_shutdown ();
73 exit (0); /* Stop pretending */
74 } else
75 icg->interrupted = TRUE;
76 }
77
78 static gboolean
cb_hide_splash(G_GNUC_UNUSED GtkWidget * widget,G_GNUC_UNUSED GdkEventButton * event,GnmIOContextGtk * icg)79 cb_hide_splash (G_GNUC_UNUSED GtkWidget *widget,
80 G_GNUC_UNUSED GdkEventButton *event,
81 GnmIOContextGtk *icg)
82 {
83 gtk_widget_hide (GTK_WIDGET (icg->window));
84 return TRUE;
85 }
86
87 static void
cb_realize(GtkWindow * window,void * dummy)88 cb_realize (GtkWindow *window, void *dummy)
89 {
90 int sx, sy;
91 GdkWindowHints hints;
92 GtkAllocation allocation;
93 GdkGeometry geom;
94 GdkRectangle rect;
95
96 /* In a Xinerama setup, we want the geometry of the actual display
97 * unit, if available. See bug 59902. */
98 gdk_screen_get_monitor_geometry (gtk_window_get_screen (window),
99 0, &rect);
100 sx = rect.width;
101 sy = rect.height;
102 gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
103
104 geom.base_width = allocation.width;
105 geom.base_height = allocation.height;
106 geom.min_width = geom.max_width = geom.base_width;
107 geom.min_height = geom.max_height = geom.base_height;
108
109 gtk_window_move (window,
110 sx / 2 - geom.min_width / 2,
111 sy / 2 - geom.min_height / 2);
112 hints = GDK_HINT_POS | GDK_HINT_USER_POS |
113 GDK_HINT_BASE_SIZE | GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE |
114 GDK_HINT_USER_SIZE;
115
116 gtk_window_set_geometry_hints (window, NULL, &geom, hints);
117 gtk_window_set_decorated (window, FALSE);
118 }
119
120 static void
icg_show_gui(GnmIOContextGtk * icg)121 icg_show_gui (GnmIOContextGtk *icg)
122 {
123 GtkBox *box;
124 GtkWidget *frame;
125
126 box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
127 if (icg->show_splash)
128 gtk_box_pack_start (box, gtk_image_new_from_resource ("/org/gnumeric/gnumeric/images/gnumeric_splash_1.4.png"),
129 TRUE, FALSE, 0);
130
131 /* Don't show this unless we need it. */
132 if (icg->files_total > 1) {
133 double f = icg->files_done / (double)icg->files_total;
134 icg->file_bar = GTK_PROGRESS_BAR
135 (g_object_new (GTK_TYPE_PROGRESS_BAR,
136 "text", "Files",
137 "show-text", TRUE,
138 "fraction", f,
139 "inverted", FALSE,
140 NULL));
141 gtk_box_pack_start (box, GTK_WIDGET (icg->file_bar),
142 FALSE, FALSE, 0);
143 }
144
145 icg->work_bar = GTK_PROGRESS_BAR
146 (g_object_new (GTK_TYPE_PROGRESS_BAR,
147 "inverted", FALSE,
148 "text", icg->progress_msg,
149 "show-text", TRUE,
150 "fraction", icg->progress,
151 NULL));
152 gtk_box_pack_start (box, GTK_WIDGET (icg->work_bar),
153 FALSE, FALSE, 0);
154
155 icg->window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
156 gtk_window_set_type_hint (GTK_WINDOW (icg->window),
157 GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
158 g_signal_connect (G_OBJECT (icg->window),
159 "button_release_event",
160 G_CALLBACK (cb_hide_splash), NULL);
161 g_signal_connect (G_OBJECT (icg->window),
162 "destroy",
163 G_CALLBACK (cb_icg_window_destroyed), icg);
164
165 frame = gtk_frame_new (NULL);
166 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
167 gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (box));
168 gtk_container_add (GTK_CONTAINER (icg->window), frame);
169
170 g_signal_connect (G_OBJECT (icg->window), "realize",
171 G_CALLBACK (cb_realize), NULL);
172
173 if (icg->parent_window)
174 gnm_io_context_gtk_set_transient_for (icg, icg->parent_window);
175
176 gtk_widget_show_all (GTK_WIDGET (icg->window));
177 }
178
179 static gboolean
icg_user_is_impatient(GnmIOContextGtk * icg)180 icg_user_is_impatient (GnmIOContextGtk *icg)
181 {
182 gdouble t = g_timer_elapsed (icg->timer, NULL);
183 double progress = icg->progress;
184 double forecast_delay = ICG_POPUP_DELAY / 3.0;
185 gboolean ret = FALSE;
186
187 if (icg->progress == 0. && icg->files_done == 0)
188 icg->latency = t;
189
190 if (t >= forecast_delay) {
191 if (icg->files_total > 1) {
192 progress += icg->files_done;
193 progress /= icg->files_total;
194 }
195 if (progress <= 0.0) {
196 /* We're likely to be back shortly. */
197 ret = (t > ICG_POPUP_DELAY * 0.8);
198 } else {
199 double forecast = icg->latency;
200 forecast += (t - icg->latency) / progress;
201 ret = (forecast > ICG_POPUP_DELAY);
202 }
203 }
204
205 return ret;
206 }
207
208 static char *
icg_get_password(GOCmdContext * cc,char const * filename)209 icg_get_password (GOCmdContext *cc, char const *filename)
210 {
211 GnmIOContextGtk *icg = GNM_IO_CONTEXT_GTK (cc);
212 return icg->show_warnings ?
213 dialog_get_password (icg->window, filename) : NULL;
214 }
215
216 static void
icg_progress_set(GOCmdContext * cc,double val)217 icg_progress_set (GOCmdContext *cc, double val)
218 {
219 GnmIOContextGtk *icg = GNM_IO_CONTEXT_GTK (cc);
220
221 if (!icg->show_splash)
222 return;
223
224 if (icg->window == NULL) {
225 icg->progress = val;
226 if (!icg_user_is_impatient (icg))
227 return;
228 icg_show_gui (icg);
229 }
230 gtk_progress_bar_set_fraction (icg->work_bar, val);
231 }
232
233 static void
icg_progress_message_set(GOCmdContext * cc,gchar const * msg)234 icg_progress_message_set (GOCmdContext *cc, gchar const *msg)
235 {
236 GnmIOContextGtk *icg = GNM_IO_CONTEXT_GTK (cc);
237
238 if (!icg->show_splash)
239 return;
240
241 if (icg->window == NULL) {
242 if (!icg_user_is_impatient (icg)) {
243 g_free (icg->progress_msg);
244 icg->progress_msg = g_strdup (msg);
245 return;
246 }
247 icg_show_gui (icg);
248 }
249 gtk_progress_bar_set_text (icg->work_bar, msg);
250 }
251
252 static void
icg_error_error_info(GOCmdContext * cc,GOErrorInfo * error)253 icg_error_error_info (GOCmdContext *cc, GOErrorInfo *error)
254 {
255 GnmIOContextGtk *icg = GNM_IO_CONTEXT_GTK (cc);
256 if (icg->show_warnings) {
257 GtkWidget *dialog = gnm_go_error_info_dialog_create (error);
258 gtk_widget_show_all (GTK_WIDGET (dialog));
259 gtk_dialog_run (GTK_DIALOG (dialog));
260 gtk_widget_destroy (dialog);
261 }
262 }
263
264 static void
icg_error_error_info_list(GOCmdContext * cc,GSList * error)265 icg_error_error_info_list (GOCmdContext *cc, GSList *error)
266 {
267 GnmIOContextGtk *icg = GNM_IO_CONTEXT_GTK (cc);
268 if (icg->show_warnings && error != NULL && error->data != NULL) {
269 GtkWidget *dialog = gnm_go_error_info_dialog_create
270 (error->data);
271 gtk_widget_show_all (GTK_WIDGET (dialog));
272 gtk_dialog_run (GTK_DIALOG (dialog));
273 gtk_widget_destroy (dialog);
274 }
275 }
276
277 static void
icg_set_num_files(GOIOContext * icg,guint files_total)278 icg_set_num_files (GOIOContext *icg, guint files_total)
279 {
280 GNM_IO_CONTEXT_GTK (icg)->files_total = files_total;
281 }
282
283 static void
icg_processing_file(GOIOContext * ioc,char const * file)284 icg_processing_file (GOIOContext *ioc, char const *file)
285 {
286 GnmIOContextGtk *icg = GNM_IO_CONTEXT_GTK (ioc);
287
288 g_return_if_fail (icg->files_done < icg->files_total);
289
290 icg->files_done++;
291 if (icg->window != NULL && icg->file_bar != NULL) {
292 int len = strlen (file);
293 int maxlen = 40;
294
295 if (icg->files_total > 0)
296 gtk_progress_bar_set_fraction
297 (icg->file_bar,
298 icg->files_done / (double)icg->files_total);
299
300 gtk_progress_bar_set_fraction (icg->work_bar, 0.0);
301
302 if (len <= maxlen)
303 gtk_progress_bar_set_text (icg->file_bar, file);
304 else {
305 char *shown_text = g_strdup (file);
306 char *p = shown_text + len;
307
308 while (1) {
309 char *last_p = p;
310 while (p > shown_text && G_IS_DIR_SEPARATOR (p[-1]))
311 p--;
312 if (p > shown_text && shown_text + len - p < maxlen) {
313 p--;
314 continue;
315 }
316 p = g_strdup_printf ("...%s", last_p);
317 gtk_progress_bar_set_text (icg->file_bar, p);
318 g_free (p);
319 break;
320 }
321
322 g_free (shown_text);
323 }
324 }
325 }
326
327 static void
icg_finalize(GObject * obj)328 icg_finalize (GObject *obj)
329 {
330 GnmIOContextGtk *icg = GNM_IO_CONTEXT_GTK (obj);
331
332 gnm_io_context_gtk_discharge_splash (icg);
333 g_free (icg->progress_msg);
334 G_OBJECT_CLASS (g_type_class_peek (GO_TYPE_IO_CONTEXT))->finalize (obj);
335 }
336
337 static void
icg_set_property(GObject * obj,guint property_id,GValue const * value,GParamSpec * pspec)338 icg_set_property (GObject *obj, guint property_id,
339 GValue const *value, GParamSpec *pspec)
340 {
341 GnmIOContextGtk *icg = GNM_IO_CONTEXT_GTK (obj);
342
343 switch (property_id) {
344 case PROP_SHOW_SPLASH:
345 icg->show_splash = g_value_get_boolean (value);
346 break;
347 case PROP_SHOW_WARNINGS:
348 icg->show_warnings = g_value_get_boolean (value);
349 break;
350 default:
351 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
352 break;
353 }
354 }
355 static void
icg_gnm_cmd_context_init(GOCmdContextClass * cc_class)356 icg_gnm_cmd_context_init (GOCmdContextClass *cc_class)
357 {
358 cc_class->get_password = icg_get_password;
359 cc_class->progress_set = icg_progress_set;
360 cc_class->progress_message_set = icg_progress_message_set;
361 cc_class->error.error_info = icg_error_error_info;
362 cc_class->error.error_info_list = icg_error_error_info_list;
363 }
364
365 static void
icg_class_init(GObjectClass * gobj_klass)366 icg_class_init (GObjectClass *gobj_klass)
367 {
368 GOIOContextClass *ioc_klass = (GOIOContextClass *)gobj_klass;
369
370 gobj_klass->finalize = icg_finalize;
371 gobj_klass->set_property = icg_set_property;
372
373 g_object_class_install_property (gobj_klass, PROP_SHOW_SPLASH,
374 g_param_spec_boolean ("show-splash",
375 P_("Show splash"),
376 P_("Show a splash screen if loading takes more than a moment"),
377 TRUE,
378 GSF_PARAM_STATIC | G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
379 g_object_class_install_property (gobj_klass, PROP_SHOW_WARNINGS,
380 g_param_spec_boolean ("show-warnings",
381 P_("Show warnings"),
382 P_("Show warning and password dialogs"),
383 TRUE,
384 GSF_PARAM_STATIC | G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
385
386 ioc_klass->set_num_files = icg_set_num_files;
387 ioc_klass->processing_file = icg_processing_file;
388 }
389
390 static void
icg_init(GnmIOContextGtk * icg)391 icg_init (GnmIOContextGtk *icg)
392 {
393 icg->show_splash = TRUE;
394 icg->show_warnings = TRUE;
395
396 icg->window = NULL;
397 icg->work_bar = NULL;
398 icg->file_bar = NULL;
399 icg->files_total = 0;
400 icg->files_done = 0;
401 icg->progress = 0.;
402 icg->progress_msg = NULL;
403 icg->latency = 0.;
404 icg->interrupted = FALSE;
405 icg->timer = g_timer_new ();
406 g_timer_start (icg->timer);
407 }
408
409 GSF_CLASS_FULL (GnmIOContextGtk, gnm_io_context_gtk,
410 NULL, NULL, icg_class_init, NULL,
411 icg_init, GO_TYPE_IO_CONTEXT, 0,
412 GSF_INTERFACE (icg_gnm_cmd_context_init, GO_TYPE_CMD_CONTEXT))
413
414 void
gnm_io_context_gtk_set_transient_for(GnmIOContextGtk * icg,GtkWindow * parent_window)415 gnm_io_context_gtk_set_transient_for (GnmIOContextGtk *icg, GtkWindow *parent_window)
416 {
417 icg->parent_window = parent_window;
418 if (icg->window)
419 go_gtk_window_set_transient (parent_window, icg->window);
420 }
421
422 gboolean
gnm_io_context_gtk_get_interrupted(GnmIOContextGtk * icg)423 gnm_io_context_gtk_get_interrupted (GnmIOContextGtk *icg)
424 {
425 return icg->interrupted;
426 }
427
428 void
gnm_io_context_gtk_discharge_splash(GnmIOContextGtk * icg)429 gnm_io_context_gtk_discharge_splash (GnmIOContextGtk *icg)
430 {
431 if (icg->window) {
432 g_signal_handlers_disconnect_by_func (
433 G_OBJECT (icg->window),
434 G_CALLBACK (cb_icg_window_destroyed), icg);
435 gtk_window_set_focus (icg->window, NULL);
436 gtk_window_set_default (icg->window, NULL);
437 gtk_widget_destroy (GTK_WIDGET (icg->window));
438 icg->window = NULL;
439 icg->work_bar = NULL;
440 icg->file_bar = NULL;
441 }
442
443 if (icg->timer) {
444 g_timer_destroy (icg->timer);
445 icg->timer = NULL;
446 }
447 }
448