4ed38b3f357fb165c112f78d5da3f651fc588796
[pspp] / src / ui / gui / main.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2004, 2005, 2006, 2010, 2011, 2012, 2013, 2014, 2015,
3    2016, 2020  Free Software Foundation
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19
20 #include "pre-initialisation.h"
21
22 #include "ui/gui/psppire.h"
23
24 #include <gtk/gtk.h>
25 #include <stdlib.h>
26
27 #include "language/lexer/include-path.h"
28 #include "libpspp/argv-parser.h"
29 #include "libpspp/array.h"
30 #include "libpspp/assertion.h"
31 #include "libpspp/cast.h"
32 #include "libpspp/copyleft.h"
33 #include "libpspp/message.h"
34 #include "libpspp/str.h"
35 #include "libpspp/string-array.h"
36 #include "libpspp/version.h"
37 #include "ui/source-init-opts.h"
38 #include "ui/gui/psppire-syntax-window.h"
39 #include "ui/gui/psppire-data-window.h"
40 #include "ui/gui/psppire-output-window.h"
41
42 #include "gl/configmake.h"
43 #include "gl/progname.h"
44 #include "gl/relocatable.h"
45 #include "gl/version-etc.h"
46 #include "gl/xalloc.h"
47
48 #include "gettext.h"
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) msgid
51
52
53
54 static gboolean
55 show_version_and_exit (void)
56 {
57   version_etc (stdout, "psppire", PACKAGE_NAME, PACKAGE_VERSION,
58                "Ben Pfaff", "John Darrington", "Jason Stover", NULL_SENTINEL);
59
60   exit (0);
61
62   return TRUE;
63 }
64
65 \f
66
67 static gboolean
68 init_prepare (GSource * source, gint * timeout_)
69 {
70   return TRUE;
71 }
72
73 static gboolean
74 init_check (GSource * source)
75 {
76   return TRUE;
77 }
78
79 static gboolean
80 init_dispatch (GSource * ss, GSourceFunc callback, gpointer user_data)
81 {
82   struct init_source *is = (struct init_source *) ss;
83
84   bool finished = initialize (is);
85   is->state++;
86
87   if (finished)
88     {
89       g_main_loop_quit (is->loop);
90       return FALSE;
91     }
92
93   return TRUE;
94 }
95
96 static GSourceFuncs init_funcs =
97   { init_prepare, init_check, init_dispatch, NULL, NULL, NULL };
98
99 static GtkWidget *wsplash = 0;
100 static gint64 start_time = 0;
101
102
103 static GtkWidget *
104 create_splash_window (void)
105 {
106   GtkWidget *sp = gtk_window_new (GTK_WINDOW_TOPLEVEL);
107
108   const gchar *filename = PKGDATADIR "/splash.png";
109   const char *relocated_filename = relocate (filename);
110   GtkWidget *l = gtk_image_new_from_file (relocated_filename);
111   if (filename != relocated_filename)
112     free (CONST_CAST (char *, relocated_filename));
113
114   gtk_container_add (GTK_CONTAINER (sp), l);
115   gtk_window_set_type_hint (GTK_WINDOW (sp),
116                             GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
117   gtk_window_set_position (GTK_WINDOW (sp), GTK_WIN_POS_CENTER);
118   gtk_window_set_skip_pager_hint (GTK_WINDOW (sp), TRUE);
119   gtk_window_set_skip_taskbar_hint (GTK_WINDOW (sp), TRUE);
120   gtk_window_set_focus_on_map (GTK_WINDOW (sp), FALSE);
121   gtk_window_set_accept_focus (GTK_WINDOW (sp), FALSE);
122
123   GdkGeometry hints;
124   hints.max_height = 100;
125   hints.max_width = 200;
126   gtk_window_set_geometry_hints (GTK_WINDOW (sp),
127                                  NULL, &hints, GDK_HINT_MAX_SIZE);
128
129
130   gtk_window_set_gravity (GTK_WINDOW (sp), GDK_GRAVITY_CENTER);
131
132   gtk_window_set_modal (GTK_WINDOW (sp), TRUE);
133   gtk_window_set_decorated (GTK_WINDOW (sp), FALSE);
134   gtk_window_set_keep_above (GTK_WINDOW (sp), TRUE);
135   gtk_widget_show_all (sp);
136   return sp;
137 }
138
139
140 static gint
141 on_local_options (GApplication * application,
142                   GVariantDict * options, gpointer user_data)
143 {
144   {
145     GVariant *b =
146       g_variant_dict_lookup_value (options, "no-unique",
147                                    G_VARIANT_TYPE_BOOLEAN);
148     if (b)
149       {
150         GApplicationFlags flags =  g_application_get_flags (application);
151         flags |= G_APPLICATION_NON_UNIQUE;
152         g_application_set_flags (application, flags);
153         g_variant_unref (b);
154       }
155   }
156   {
157     GVariant *b =
158       g_variant_dict_lookup_value (options, "no-splash",
159                                    G_VARIANT_TYPE_BOOLEAN);
160     if (b)
161       g_variant_unref (b);
162     else
163       start_time = g_get_monotonic_time ();
164   }
165
166
167   return -1;
168 }
169
170
171 static void
172 on_startup (GApplication * app, gpointer ud)
173 {
174   GMainContext *context = g_main_context_new ();
175
176   if (start_time != 0)
177     {
178       wsplash = create_splash_window ();
179       gtk_application_add_window (GTK_APPLICATION (app),
180                                   GTK_WINDOW (wsplash));
181     }
182
183   GMainLoop *loop = g_main_loop_new (context, FALSE);
184
185   GSource *ss = g_source_new (&init_funcs, sizeof (struct init_source));
186
187   ((struct init_source *) ss)->loop = loop;
188   ((struct init_source *) ss)->state = 0;
189
190   g_source_set_priority (ss, G_PRIORITY_DEFAULT);
191
192   g_source_attach (ss, context);
193   g_main_loop_run (loop);
194 }
195
196
197 static void
198 post_initialise (GApplication * app)
199 {
200   register_selection_functions ();
201   psppire_output_window_setup ();
202
203   GSimpleAction *quit = g_simple_action_new ("quit", NULL);
204   g_signal_connect_swapped (quit, "activate", G_CALLBACK (psppire_quit), app);
205   g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (quit));
206 }
207
208
209 #define SPLASH_DURATION 1000
210
211 static gboolean
212 destroy_splash (gpointer ud)
213 {
214   GtkWidget *sp = GTK_WIDGET (ud);
215   gtk_widget_destroy (sp);
216   wsplash = NULL;
217   return G_SOURCE_REMOVE;
218 }
219
220
221 static void
222 wait_for_splash (GApplication *app, GtkWindow *x)
223 {
224   if (wsplash)
225     {
226       gtk_window_set_transient_for (GTK_WINDOW (wsplash), x);
227       gtk_application_add_window (GTK_APPLICATION (app), GTK_WINDOW (wsplash));
228       gtk_window_set_keep_above (GTK_WINDOW (wsplash), TRUE);
229       gtk_window_present (GTK_WINDOW (wsplash));
230
231       /* Remove the splash screen after SPLASH_DURATION milliseconds */
232       gint64 elapsed_time = (g_get_monotonic_time () - start_time) / 1000;
233       if (SPLASH_DURATION - elapsed_time <= 0)
234         destroy_splash (wsplash);
235       else
236         g_timeout_add (SPLASH_DURATION - elapsed_time, destroy_splash, wsplash);
237     }
238 }
239
240 static GtkWidget *fatal_error_dialog = NULL;
241 static GtkWidget *fatal_error_label;
242 static const char *diagnostic_info;
243
244 static void
245 fatal_error_handler (int sig)
246 {
247   /* Reset SIG to its default handling so that if it happens again we won't
248      recurse. */
249   signal (sig, SIG_DFL);
250
251   static char message [1024];
252   strcpy (message, "proximate cause:    ");
253   switch (sig)
254     {
255     case SIGABRT:
256       strcat (message, "Assertion Failure/Abort");
257       break;
258     case SIGFPE:
259       strcat (message, "Floating Point Exception");
260       break;
261     case SIGSEGV:
262       strcat (message, "Segmentation Violation");
263       break;
264     default:
265       strcat (message, "Unknown");
266       break;
267     }
268   strcat (message, "\n");
269   strcat (message, diagnostic_info);
270
271   g_object_set (fatal_error_label,
272                 "label", message,
273                 NULL);
274
275   gtk_dialog_run (GTK_DIALOG (fatal_error_dialog));
276
277   /* Re-raise the signal so that we terminate with the correct status. */
278   raise (sig);
279 }
280
281 static void
282 on_activate (GApplication * app, gpointer ud)
283 {
284   struct sigaction fatal_error_action;
285   sigset_t sigset;
286   g_return_if_fail (0 == sigemptyset (&sigset));
287   fatal_error_dialog =
288     gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
289                             _("Psppire: Fatal Error"));
290
291   diagnostic_info = prepare_diagnostic_information ();
292
293   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (fatal_error_dialog),
294                                             _("You have discovered a bug in PSPP.  "
295                                               "Please report this to %s including all of the following information, "
296                                               "and a description of what you were doing when this happened."),
297                                             PACKAGE_BUGREPORT);
298
299   g_return_if_fail (fatal_error_dialog != NULL);
300
301   GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (fatal_error_dialog));
302   fatal_error_label = gtk_label_new ("");
303   g_object_set (fatal_error_label,
304                 "selectable", TRUE,
305                 "wrap", TRUE,
306                 NULL);
307   gtk_container_add (GTK_CONTAINER (content_area), fatal_error_label);
308
309   gtk_widget_show_all (content_area);
310
311   fatal_error_action.sa_handler = fatal_error_handler;
312   fatal_error_action.sa_mask = sigset;
313   fatal_error_action.sa_flags = 0;
314
315   post_initialise (app);
316
317   GtkWindow *x = create_data_window ();
318   gtk_application_add_window (GTK_APPLICATION (app), x);
319
320   wait_for_splash (app, x);
321   sigaction (SIGABRT, &fatal_error_action, NULL);
322   sigaction (SIGSEGV, &fatal_error_action, NULL);
323   sigaction (SIGFPE,  &fatal_error_action, NULL);
324 }
325
326 static GtkWindow *
327 find_empty_data_window (GApplication *app)
328 {
329   GList *wl = gtk_application_get_windows (GTK_APPLICATION (app));
330   while (wl)
331     {
332       if (wl->data && PSPPIRE_IS_DATA_WINDOW (GTK_WINDOW (wl->data)) &&
333           psppire_data_window_is_empty (PSPPIRE_DATA_WINDOW (wl->data)))
334         return GTK_WINDOW (wl->data);
335       wl = wl->next;
336     }
337   return NULL;
338 }
339
340 static GtkWindow *
341 find_psppire_window (GApplication *app)
342 {
343   GList *wl = gtk_application_get_windows (GTK_APPLICATION (app));
344   while (wl)
345     {
346       if (wl->data && PSPPIRE_IS_WINDOW (GTK_WINDOW (wl->data)))
347         return GTK_WINDOW (wl->data);
348       wl = wl->next;
349     }
350   return NULL;
351 }
352
353 static void
354 on_open (GApplication *app, GFile **files, gint n_files, gchar * hint,
355          gpointer ud)
356 {
357   /* If the application is already open and we open another file
358      via xdg-open on GNU/Linux or via the file manager, then open is
359      called. Check if we already have a psppire window. */
360   if (find_psppire_window (app) == NULL)
361     post_initialise (app);
362
363   /* When a new data file is opened, then try to find an empty
364      data window which will then be replaced as in the open file
365      dialog */
366   GtkWindow *victim = find_empty_data_window (app);
367
368   gchar *file = g_file_get_parse_name (files[0]);
369   GtkWindow *x = psppire_preload_file (file, victim);
370   g_free (file);
371
372   wait_for_splash (app, x);
373 }
374
375
376 /* These are arguments which must be processed BEFORE the X server has been initialised */
377 static void
378 process_pre_start_arguments (int *argc, char ***argv)
379 {
380   GOptionEntry oe[] = {
381     {"version", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
382      show_version_and_exit, N_("Show version information and exit"), 0},
383     {NULL, 0, 0, 0, NULL, "", 0}
384   };
385
386   GOptionContext *oc = g_option_context_new ("");
387   g_option_context_set_help_enabled (oc, FALSE);
388   g_option_context_set_ignore_unknown_options (oc, FALSE);
389   g_option_context_add_main_entries (oc, oe, NULL);
390   g_option_context_parse (oc, argc, argv, NULL);
391   g_option_context_free (oc);
392 }
393
394 int
395 main (int argc, char *argv[])
396 {
397   /* Some operating systems need to munge the arguments.  */
398   pre_initialisation (&argc, argv);
399
400   set_program_name (argv[0]);
401
402   GtkApplication *app =
403     gtk_application_new ("gnu.pspp", G_APPLICATION_HANDLES_OPEN);
404
405   process_pre_start_arguments (&argc, &argv);
406
407   GOptionEntry oe[] = {
408     {"no-splash", 'q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL,
409       N_("Do not display the splash screen"), 0},
410     {"no-unique", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, NULL,
411       N_("Do not attempt single instance negotiation"), 0},
412     {NULL}
413   };
414
415   g_application_add_main_option_entries (G_APPLICATION (app), oe);
416
417   g_signal_connect (app, "startup", G_CALLBACK (on_startup), NULL);
418   g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
419   g_signal_connect (app, "handle-local-options",
420                     G_CALLBACK (on_local_options), NULL);
421   g_signal_connect (app, "open", G_CALLBACK (on_open), NULL);
422
423   {
424     GSimpleAction *act_new_syntax = g_simple_action_new ("new-syntax", NULL);
425     g_signal_connect_swapped (act_new_syntax, "activate",
426                               G_CALLBACK (create_syntax_window), NULL);
427     g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_new_syntax));
428   }
429
430   {
431     GSimpleAction *act_new_data = g_simple_action_new ("new-data", NULL);
432     g_signal_connect_swapped (act_new_data, "activate",
433                               G_CALLBACK (create_data_window), NULL);
434     g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (act_new_data));
435   }
436
437   g_object_set (G_OBJECT (app), "register-session", TRUE, NULL);
438   return g_application_run (G_APPLICATION (app), argc, argv);
439 }