gui: Speed up initial file load with many columns
[pspp] / src / ui / gui / psppire.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2004, 2005, 2006, 2009, 2010, 2011, 2012  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <assert.h>
20 #include <gsl/gsl_errno.h>
21 #include <gtk/gtk.h>
22 #include <libintl.h>
23 #include <unistd.h>
24
25 #include "data/any-reader.h"
26 #include "data/casereader.h"
27 #include "data/dataset.h"
28 #include "data/datasheet.h"
29 #include "data/file-handle-def.h"
30 #include "data/file-name.h"
31 #include "data/por-file-reader.h"
32 #include "data/session.h"
33 #include "data/settings.h"
34 #include "data/sys-file-reader.h"
35
36 #include "language/lexer/lexer.h"
37 #include "libpspp/i18n.h"
38 #include "libpspp/message.h"
39 #include "libpspp/version.h"
40
41 #include "output/driver.h"
42 #include "output/journal.h"
43 #include "output/message-item.h"
44
45 #include "ui/gui/dict-display.h"
46 #include "ui/gui/executor.h"
47 #include "ui/gui/psppire-data-store.h"
48 #include "ui/gui/psppire-data-window.h"
49 #include "ui/gui/psppire-dict.h"
50 #include "ui/gui/psppire.h"
51 #include "ui/gui/psppire-output-window.h"
52 #include "ui/gui/psppire-syntax-window.h"
53 #include "ui/gui/psppire-selector.h"
54 #include "ui/gui/psppire-var-store.h"
55 #include "ui/gui/psppire-var-view.h"
56 #include "ui/gui/psppire-window-register.h"
57 #include "ui/gui/widgets.h"
58 #include "ui/source-init-opts.h"
59 #include "ui/syntax-gen.h"
60
61 #include "gl/configmake.h"
62 #include "gl/xalloc.h"
63 #include "gl/relocatable.h"
64
65 static void inject_renamed_icons (void);
66 static void create_icon_factory (void);
67 static gchar *local_to_filename_encoding (const char *fn);
68
69
70 #define _(msgid) gettext (msgid)
71 #define N_(msgid) msgid
72
73
74 void
75 initialize (const char *data_file)
76 {
77   i18n_init ();
78
79   preregister_widgets ();
80
81   gsl_set_error_handler_off ();
82   settings_init ();
83   fh_init ();
84
85   psppire_set_lexer (NULL);
86
87   bind_textdomain_codeset (PACKAGE, "UTF-8");
88
89   inject_renamed_icons ();
90   create_icon_factory ();
91
92   psppire_output_window_setup ();
93
94   journal_enable ();
95   textdomain (PACKAGE);
96
97   psppire_selector_set_default_selection_func (GTK_TYPE_ENTRY, insert_source_row_into_entry);
98   psppire_selector_set_default_selection_func (PSPPIRE_VAR_VIEW_TYPE, insert_source_row_into_tree_view);
99   psppire_selector_set_default_selection_func (GTK_TYPE_TREE_VIEW, insert_source_row_into_tree_view);
100
101   if (data_file)
102     {
103       gchar *filename = local_to_filename_encoding (data_file);
104
105       /* Check to see if the file is a .sav or a .por file.  If not
106          assume that it is a syntax file */
107       if ( any_reader_may_open (filename))
108         open_data_window (NULL, filename);
109       else
110         {
111           create_data_window ();
112           open_syntax_window (filename, NULL);
113         }
114
115       g_free (filename);
116     }
117   else
118     create_data_window ();
119 }
120
121
122 void
123 de_initialize (void)
124 {
125   settings_done ();
126   output_close ();
127   i18n_done ();
128 }
129
130 static void
131 func (gpointer key, gpointer value, gpointer data)
132 {
133   gboolean rv;
134   PsppireWindow *window = PSPPIRE_WINDOW (value);
135
136   g_signal_emit_by_name (window, "delete-event", 0, &rv);
137 }
138
139 void
140 psppire_quit (void)
141 {
142   PsppireWindowRegister *reg = psppire_window_register_new ();
143   psppire_window_register_foreach (reg, func, NULL);
144
145   gtk_main_quit ();
146 }
147
148 static void
149 inject_renamed_icon (const char *icon, const char *substitute)
150 {
151   GtkIconTheme *theme = gtk_icon_theme_get_default ();
152   if (!gtk_icon_theme_has_icon (theme, icon)
153       && gtk_icon_theme_has_icon (theme, substitute))
154     {
155       gint *sizes = gtk_icon_theme_get_icon_sizes (theme, substitute);
156       gint *p;
157
158       for (p = sizes; *p != 0; p++)
159         {
160           gint size = *p;
161           GdkPixbuf *pb;
162
163           pb = gtk_icon_theme_load_icon (theme, substitute, size, 0, NULL);
164           if (pb != NULL)
165             {
166               GdkPixbuf *copy = gdk_pixbuf_copy (pb);
167               if (copy != NULL)
168                 gtk_icon_theme_add_builtin_icon (icon, size, copy);
169             }
170         }
171     }
172 }
173
174 /* Avoid a bug in GTK+ 2.22 that can cause a segfault at startup time.  Earlier
175    and later versions of GTK+ do not have the bug.  Bug #31511.
176
177    Based on this patch against Inkscape:
178    https://launchpadlibrarian.net/60175914/copy_renamed_icons.patch */
179 static void
180 inject_renamed_icons (void)
181 {
182   if (gtk_major_version == 2 && gtk_minor_version == 22)
183     {
184       inject_renamed_icon ("gtk-file", "document-x-generic");
185       inject_renamed_icon ("gtk-directory", "folder");
186     }
187 }
188
189 struct icon_info
190 {
191   const char *file_name;
192   const gchar *id;
193 };
194
195
196 static const struct icon_info icons[] =
197   {
198     {PKGDATADIR "/value-labels.png",    "pspp-value-labels"},
199     {PKGDATADIR "/weight-cases.png",    "pspp-weight-cases"},
200     {PKGDATADIR "/goto-variable.png",   "pspp-goto-variable"},
201     {PKGDATADIR "/insert-variable.png", "pspp-insert-variable"},
202     {PKGDATADIR "/insert-case.png",     "pspp-insert-case"},
203     {PKGDATADIR "/split-file.png",      "pspp-split-file"},
204     {PKGDATADIR "/select-cases.png",    "pspp-select-cases"},
205     {PKGDATADIR "/recent-dialogs.png",  "pspp-recent-dialogs"},
206     {PKGDATADIR "/nominal.png",         "var-nominal"},
207     {PKGDATADIR "/ordinal.png",         "var-ordinal"},
208     {PKGDATADIR "/scale.png",           "var-scale"},
209     {PKGDATADIR "/string.png",          "var-string"},
210     {PKGDATADIR "/date-scale.png",      "var-date-scale"}
211   };
212
213 static void
214 create_icon_factory (void)
215 {
216   gint i;
217   GtkIconFactory *factory = gtk_icon_factory_new ();
218
219   for (i = 0 ; i < sizeof (icons) / sizeof(icons[0]); ++i)
220     {
221       GError *err = NULL;
222       GdkPixbuf *pixbuf =
223         gdk_pixbuf_new_from_file (relocate (icons[i].file_name), &err);
224
225       if ( pixbuf )
226         {
227           GtkIconSet *icon_set = gtk_icon_set_new_from_pixbuf (pixbuf);
228           g_object_unref (pixbuf);
229           gtk_icon_factory_add ( factory, icons[i].id, icon_set);
230         }
231       else
232         {
233           g_warning ("Cannot create icon: %s", err->message);
234           g_clear_error (&err);
235         }
236     }
237
238   {
239     /* Create our own "pspp-stock-reset" item, using the
240        GTK_STOCK_REFRESH icon set */
241
242     GtkStockItem items[] = {
243       {"pspp-stock-reset", N_("_Reset"), 0, 0, PACKAGE},
244       {"pspp-stock-select", N_("_Select"), 0, 0, PACKAGE}
245     };
246
247
248     gtk_stock_add (items, 2);
249     gtk_icon_factory_add (factory, "pspp-stock-reset",
250                           gtk_icon_factory_lookup_default (GTK_STOCK_REFRESH)
251                           );
252
253     gtk_icon_factory_add (factory, "pspp-stock-select",
254                           gtk_icon_factory_lookup_default (GTK_STOCK_INDEX)
255                           );
256   }
257
258   gtk_icon_factory_add_default (factory);
259 }
260 \f
261 /* 
262    Convert a filename from the local encoding into "filename" encoding.
263    The return value will be allocated on the heap.  It is the responsibility
264    of the caller to free it.
265  */
266 static gchar *
267 local_to_filename_encoding (const char *fn)
268 {
269   gchar *filename = NULL;
270   gchar *utf8 = NULL;
271   const gchar *local_encoding = NULL;
272   gsize written = -1;
273   const gboolean local_is_utf8 = g_get_charset (&local_encoding);
274
275   /* There seems to be no Glib function to convert from local encoding
276      to filename encoding.  Therefore it has to be done in two steps:
277      the intermediate encoding is UTF8.
278
279      Either step could fail.  However, in many cases the file can still
280      be loaded even if the conversion fails. So in those cases, after showing
281      a warning, we simply copy the locally encoded filename to the destination
282      and hope for the best.
283   */
284
285   if ( local_is_utf8)
286     {
287       utf8 = xstrdup (fn);
288     }
289   else
290     {
291       GError *err = NULL;
292       utf8 = g_locale_to_utf8 (fn, -1, NULL, &written, &err);
293       if ( NULL == utf8)
294         {
295           g_warning ("Cannot convert filename from local encoding `%s' to UTF-8: %s",
296                      local_encoding,
297                      err->message);
298           g_clear_error (&err);
299         }
300     }
301
302   if ( NULL != utf8)
303     {
304       GError *err = NULL;
305       filename = g_filename_from_utf8 (utf8, written, NULL, NULL, &err);
306       if ( NULL == filename)
307         {
308           g_warning ("Cannot convert filename from UTF8 to filename encoding: %s",
309                      err->message);
310           g_clear_error (&err);
311         }
312     }
313
314   g_free (utf8);
315
316   if ( filename == NULL)
317     filename = xstrdup (fn);
318
319   return filename;
320 }
321
322 static void
323 handle_msg (const struct msg *m_, void *lexer_)
324 {
325   struct lexer *lexer = lexer_;
326   struct msg m = *m_;
327
328   if (lexer != NULL && m.file_name == NULL)
329     {
330       m.file_name = CONST_CAST (char *, lex_get_file_name (lexer));
331       m.first_line = lex_get_first_line_number (lexer, 0);
332       m.last_line = lex_get_last_line_number (lexer, 0);
333       m.first_column = lex_get_first_column (lexer, 0);
334       m.last_column = lex_get_last_column (lexer, 0);
335     }
336
337   message_item_submit (message_item_create (&m));
338 }
339
340 void
341 psppire_set_lexer (struct lexer *lexer)
342 {
343   msg_set_handler (handle_msg, lexer);
344 }