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