Enable the bracket matching
[pspp] / src / ui / gui / psppire-syntax-window.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010  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 <gtk/gtksignal.h>
20 #include <gtk/gtkbox.h>
21 #include "executor.h"
22 #include "helper.h"
23
24 #include <gtksourceview/gtksourcebuffer.h>
25 #include <gtksourceview/gtksourcelanguage.h>
26 #include <gtksourceview/gtksourcelanguagemanager.h>
27
28 #include <libpspp/message.h>
29 #include <stdlib.h>
30
31 #include "psppire.h"
32
33 #include "psppire-data-window.h"
34 #include "psppire-window-register.h"
35 #include "psppire.h"
36 #include "help-menu.h"
37 #include "psppire-syntax-window.h"
38 #include "syntax-editor-source.h"
39 #include <language/lexer/lexer.h>
40
41 #include "xalloc.h"
42
43 #include <gettext.h>
44 #define _(msgid) gettext (msgid)
45 #define N_(msgid) msgid
46
47 static void psppire_syntax_window_base_finalize (PsppireSyntaxWindowClass *, gpointer);
48 static void psppire_syntax_window_base_init     (PsppireSyntaxWindowClass *class);
49 static void psppire_syntax_window_class_init    (PsppireSyntaxWindowClass *class);
50 static void psppire_syntax_window_init          (PsppireSyntaxWindow      *syntax_editor);
51
52
53 static void psppire_syntax_window_iface_init (PsppireWindowIface *iface);
54
55
56 GType
57 psppire_syntax_window_get_type (void)
58 {
59   static GType psppire_syntax_window_type = 0;
60
61   if (!psppire_syntax_window_type)
62     {
63       static const GTypeInfo psppire_syntax_window_info =
64       {
65         sizeof (PsppireSyntaxWindowClass),
66         (GBaseInitFunc) psppire_syntax_window_base_init,
67         (GBaseFinalizeFunc) psppire_syntax_window_base_finalize,
68         (GClassInitFunc)psppire_syntax_window_class_init,
69         (GClassFinalizeFunc) NULL,
70         NULL,
71         sizeof (PsppireSyntaxWindow),
72         0,
73         (GInstanceInitFunc) psppire_syntax_window_init,
74       };
75
76       static const GInterfaceInfo window_interface_info =
77         {
78           (GInterfaceInitFunc) psppire_syntax_window_iface_init,
79           NULL,
80           NULL
81         };
82
83       psppire_syntax_window_type =
84         g_type_register_static (PSPPIRE_TYPE_WINDOW, "PsppireSyntaxWindow",
85                                 &psppire_syntax_window_info, 0);
86
87       g_type_add_interface_static (psppire_syntax_window_type,
88                                    PSPPIRE_TYPE_WINDOW_MODEL,
89                                    &window_interface_info);
90     }
91
92   return psppire_syntax_window_type;
93 }
94
95 static GObjectClass *parent_class ;
96
97 static void
98 psppire_syntax_window_finalize (GObject *object)
99 {
100   if (G_OBJECT_CLASS (parent_class)->finalize)
101     (*G_OBJECT_CLASS (parent_class)->finalize) (object);
102 }
103
104
105 static void
106 psppire_syntax_window_class_init (PsppireSyntaxWindowClass *class)
107 {
108   GtkSourceLanguageManager *lm = gtk_source_language_manager_get_default ();
109
110   const gchar * const *existing_paths =  gtk_source_language_manager_get_search_path (lm);
111
112   const gchar **new_paths = g_strdupv (existing_paths);
113
114   int n = g_strv_length (existing_paths);
115
116   new_paths = g_realloc (new_paths, (n+1) * sizeof (*new_paths));
117   new_paths[n] = g_strdup (PKGDATADIR);
118   new_paths[n+1] = NULL;
119
120   lm = gtk_source_language_manager_new ();
121   gtk_source_language_manager_set_search_path (lm, new_paths);
122
123   class->lan = gtk_source_language_manager_get_language (lm, "pspp");
124
125   if (class->lan == NULL)
126     g_warning ("pspp.lang file not found.  Syntax highlighting will not be available.");
127
128   parent_class = g_type_class_peek_parent (class);
129 }
130
131
132 static void
133 psppire_syntax_window_base_init (PsppireSyntaxWindowClass *class)
134 {
135   GObjectClass *object_class = G_OBJECT_CLASS (class);
136   object_class->finalize = psppire_syntax_window_finalize;
137 }
138
139
140
141 static void
142 psppire_syntax_window_base_finalize (PsppireSyntaxWindowClass *class,
143                                      gpointer class_data)
144 {
145 }
146
147
148 static void
149 editor_execute_syntax (const PsppireSyntaxWindow *sw, GtkTextIter start,
150                        GtkTextIter stop)
151 {
152   PsppireWindow *win = PSPPIRE_WINDOW (sw);
153   const gchar *name = psppire_window_get_filename (win);
154   execute_syntax (create_syntax_editor_source (sw->buffer, start, stop, name));
155 }
156
157
158 /* Parse and execute all the text in the buffer */
159 static void
160 on_run_all (GtkMenuItem *menuitem, gpointer user_data)
161 {
162   GtkTextIter begin, end;
163   PsppireSyntaxWindow *se = PSPPIRE_SYNTAX_WINDOW (user_data);
164
165   gtk_text_buffer_get_iter_at_offset (se->buffer, &begin, 0);
166   gtk_text_buffer_get_iter_at_offset (se->buffer, &end, -1);
167
168   editor_execute_syntax (se, begin, end);
169 }
170
171 /* Parse and execute the currently selected text */
172 static void
173 on_run_selection (GtkMenuItem *menuitem, gpointer user_data)
174 {
175   GtkTextIter begin, end;
176   PsppireSyntaxWindow *se = PSPPIRE_SYNTAX_WINDOW (user_data);
177
178   if ( gtk_text_buffer_get_selection_bounds (se->buffer, &begin, &end) )
179     editor_execute_syntax (se, begin, end);
180 }
181
182
183 /* Parse and execute the from the current line, to the end of the
184    buffer */
185 static void
186 on_run_to_end (GtkMenuItem *menuitem, gpointer user_data)
187 {
188   GtkTextIter begin, end;
189   GtkTextIter here;
190   gint line;
191
192   PsppireSyntaxWindow *se = PSPPIRE_SYNTAX_WINDOW (user_data);
193
194   /* Get the current line */
195   gtk_text_buffer_get_iter_at_mark (se->buffer,
196                                     &here,
197                                     gtk_text_buffer_get_insert (se->buffer)
198                                     );
199
200   line = gtk_text_iter_get_line (&here) ;
201
202   /* Now set begin and end to the start of this line, and end of buffer
203      respectively */
204   gtk_text_buffer_get_iter_at_line (se->buffer, &begin, line);
205   gtk_text_buffer_get_iter_at_line (se->buffer, &end, -1);
206
207   editor_execute_syntax (se, begin, end);
208 }
209
210
211
212 /* Parse and execute the current line */
213 static void
214 on_run_current_line (GtkMenuItem *menuitem, gpointer user_data)
215 {
216   GtkTextIter begin, end;
217   GtkTextIter here;
218   gint line;
219
220   PsppireSyntaxWindow *se = PSPPIRE_SYNTAX_WINDOW (user_data);
221
222   /* Get the current line */
223   gtk_text_buffer_get_iter_at_mark (se->buffer,
224                                     &here,
225                                     gtk_text_buffer_get_insert (se->buffer)
226                                     );
227
228   line = gtk_text_iter_get_line (&here) ;
229
230   /* Now set begin and end to the start of this line, and start of
231      following line respectively */
232   gtk_text_buffer_get_iter_at_line (se->buffer, &begin, line);
233   gtk_text_buffer_get_iter_at_line (se->buffer, &end, line + 1);
234
235   editor_execute_syntax (se, begin, end);
236 }
237
238
239
240 /* Append ".sps" to FILENAME if necessary.
241    The returned result must be freed when no longer required.
242  */
243 static gchar *
244 append_suffix (const gchar *filename)
245 {
246   if ( ! g_str_has_suffix (filename, ".sps" ) &&
247        ! g_str_has_suffix (filename, ".SPS" ) )
248     {
249       return g_strdup_printf ("%s.sps", filename);
250     }
251
252   return xstrdup (filename);
253 }
254
255 /*
256   Save BUFFER to the file called FILENAME.
257   FILENAME must be encoded in Glib filename encoding.
258   If successful, clears the buffer's modified flag.
259 */
260 static gboolean
261 save_editor_to_file (PsppireSyntaxWindow *se,
262                      const gchar *filename,
263                      GError **err)
264 {
265   GtkTextBuffer *buffer = se->buffer;
266   gboolean result ;
267   GtkTextIter start, stop;
268   gchar *text;
269
270   gchar *suffixedname;
271   g_assert (filename);
272
273   suffixedname = append_suffix (filename);
274
275   gtk_text_buffer_get_iter_at_line (buffer, &start, 0);
276   gtk_text_buffer_get_iter_at_offset (buffer, &stop, -1);
277
278   text = gtk_text_buffer_get_text (buffer, &start, &stop, FALSE);
279
280   result =  g_file_set_contents (suffixedname, text, -1, err);
281
282   g_free (suffixedname);
283
284   if ( result )
285     {
286       char *fn = g_filename_display_name (filename);
287       gchar *msg = g_strdup_printf (_("Saved file \"%s\""), fn);
288       g_free (fn);
289       gtk_statusbar_push (GTK_STATUSBAR (se->sb), se->text_context, msg);
290       gtk_text_buffer_set_modified (buffer, FALSE);
291       g_free (msg);
292     }
293
294   return result;
295 }
296
297
298 /* Callback for the File->SaveAs menuitem */
299 static void
300 syntax_save_as (PsppireWindow *se)
301 {
302   GtkFileFilter *filter;
303   gint response;
304
305   GtkWidget *dialog =
306     gtk_file_chooser_dialog_new (_("Save Syntax"),
307                                  GTK_WINDOW (se),
308                                  GTK_FILE_CHOOSER_ACTION_SAVE,
309                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
310                                  GTK_STOCK_SAVE,   GTK_RESPONSE_ACCEPT,
311                                  NULL);
312
313   filter = gtk_file_filter_new ();
314   gtk_file_filter_set_name (filter, _("Syntax Files (*.sps) "));
315   gtk_file_filter_add_pattern (filter, "*.sps");
316   gtk_file_filter_add_pattern (filter, "*.SPS");
317   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
318
319   filter = gtk_file_filter_new ();
320   gtk_file_filter_set_name (filter, _("All Files"));
321   gtk_file_filter_add_pattern (filter, "*");
322   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
323
324   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
325                                                   TRUE);
326   response = gtk_dialog_run (GTK_DIALOG (dialog));
327
328   if ( response == GTK_RESPONSE_ACCEPT )
329     {
330       GError *err = NULL;
331       char *filename =
332         gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog) );
333
334       if ( ! save_editor_to_file (PSPPIRE_SYNTAX_WINDOW (se), filename, &err) )
335         {
336           msg ( ME, "%s", err->message );
337           g_error_free (err);
338         }
339
340       free (filename);
341     }
342
343   gtk_widget_destroy (dialog);
344 }
345
346
347 /* Callback for the File->Save menuitem */
348 static void
349 syntax_save (PsppireWindow *se)
350 {
351   const gchar *filename = psppire_window_get_filename (se);
352
353   if ( filename == NULL )
354     syntax_save_as (se);
355   else
356     {
357       GError *err = NULL;
358       save_editor_to_file (PSPPIRE_SYNTAX_WINDOW (se), filename, &err);
359       if ( err )
360         {
361           msg (ME, "%s", err->message);
362           g_error_free (err);
363         }
364     }
365 }
366
367
368 /* Callback for the File->Quit menuitem */
369 static gboolean
370 on_quit (GtkMenuItem *menuitem, gpointer    user_data)
371 {
372   psppire_quit ();
373
374   return FALSE;
375 }
376
377
378 void
379 create_syntax_window (void)
380 {
381   GtkWidget *w = psppire_syntax_window_new ();
382   gtk_widget_show (w);
383 }
384
385 void
386 open_syntax_window (const char *file_name)
387 {
388   GtkWidget *se = psppire_syntax_window_new ();
389
390   if ( psppire_window_load (PSPPIRE_WINDOW (se), file_name) )
391     gtk_widget_show (se);
392   else
393     gtk_widget_destroy (se);
394 }
395
396 static void
397 on_text_changed (GtkTextBuffer *buffer, PsppireSyntaxWindow *window)
398 {
399   gtk_statusbar_pop (GTK_STATUSBAR (window->sb), window->text_context);
400 }
401
402 static void
403 on_modified_changed (GtkTextBuffer *buffer, PsppireWindow *window)
404 {
405   if (gtk_text_buffer_get_modified (buffer))
406     psppire_window_set_unsaved (window);
407 }
408
409 extern struct source_stream *the_source_stream ;
410
411 static void
412 psppire_syntax_window_init (PsppireSyntaxWindow *window)
413 {
414   GtkBuilder *xml = builder_new ("syntax-editor.ui");
415   GtkWidget *box = gtk_vbox_new (FALSE, 0);
416
417   GtkWidget *menubar = get_widget_assert (xml, "menubar");
418   GtkWidget *sw = get_widget_assert (xml, "scrolledwindow8");
419
420   GtkWidget *text_view = get_widget_assert (xml, "syntax_text_view");
421
422   PsppireSyntaxWindowClass *class
423     = PSPPIRE_SYNTAX_WINDOW_CLASS (G_OBJECT_GET_CLASS (window));
424
425   if (class->lan)
426     window->buffer = GTK_TEXT_BUFFER (gtk_source_buffer_new_with_language (class->lan));
427   else
428     window->buffer = GTK_TEXT_BUFFER (gtk_source_buffer_new (NULL));
429
430   gtk_text_view_set_buffer (GTK_TEXT_VIEW (text_view), window->buffer);
431
432   g_object_set (window->buffer,
433                 "highlight-matching-brackets", TRUE,
434                 NULL);
435
436
437   g_object_set (text_view,
438                 "show-line-numbers", TRUE,
439                 "show-line-marks", TRUE,
440                 "auto-indent", TRUE,
441                 "indent-width", 4,
442                 "highlight-current-line", TRUE,
443                 NULL);
444   window->lexer = lex_create (the_source_stream);
445
446   window->sb = get_widget_assert (xml, "statusbar2");
447   window->text_context = gtk_statusbar_get_context_id (GTK_STATUSBAR (window->sb), "Text Context");
448
449   g_signal_connect (window->buffer, "changed", G_CALLBACK (on_text_changed), window);
450
451   g_signal_connect (window->buffer, "modified-changed",
452                     G_CALLBACK (on_modified_changed), window);
453
454   connect_help (xml);
455
456   gtk_container_add (GTK_CONTAINER (window), box);
457
458   g_object_ref (menubar);
459
460   g_object_ref (sw);
461
462   g_object_ref (window->sb);
463
464
465   gtk_box_pack_start (GTK_BOX (box), menubar, FALSE, TRUE, 0);
466   gtk_box_pack_start (GTK_BOX (box), sw, TRUE, TRUE, 0);
467   gtk_box_pack_start (GTK_BOX (box), window->sb, FALSE, TRUE, 0);
468
469   gtk_widget_show_all (box);
470
471   g_signal_connect_swapped (get_action_assert (xml,"file_new_syntax"), "activate", G_CALLBACK (create_syntax_window), NULL);
472
473 #if 0
474   g_signal_connect (get_action_assert (xml,"file_new_data"),
475                     "activate",
476                     G_CALLBACK (create_data_window),
477                     window);
478 #endif
479
480   g_signal_connect_swapped (get_action_assert (xml, "file_save"),
481                     "activate",
482                     G_CALLBACK (syntax_save),
483                     window);
484
485   g_signal_connect_swapped (get_action_assert (xml, "file_save_as"),
486                     "activate",
487                     G_CALLBACK (syntax_save_as),
488                     window);
489
490   g_signal_connect (get_action_assert (xml,"file_quit"),
491                     "activate",
492                     G_CALLBACK (on_quit),
493                     window);
494
495   g_signal_connect (get_action_assert (xml,"run_all"),
496                     "activate",
497                     G_CALLBACK (on_run_all),
498                     window);
499
500
501   g_signal_connect (get_action_assert (xml,"run_selection"),
502                     "activate",
503                     G_CALLBACK (on_run_selection),
504                     window);
505
506   g_signal_connect (get_action_assert (xml,"run_current_line"),
507                     "activate",
508                     G_CALLBACK (on_run_current_line),
509                     window);
510
511   g_signal_connect (get_action_assert (xml,"run_to_end"),
512                     "activate",
513                     G_CALLBACK (on_run_to_end),
514                     window);
515
516   g_signal_connect (get_action_assert (xml,"windows_minimise_all"),
517                     "activate",
518                     G_CALLBACK (psppire_window_minimise_all), NULL);
519
520
521   {
522   GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (xml, "uimanager1", GTK_TYPE_UI_MANAGER));
523
524   merge_help_menu (uim);
525
526   PSPPIRE_WINDOW (window)->menu =
527     GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar/windows/windows_minimise_all")->parent);
528   }
529
530   g_object_unref (xml);
531 }
532
533
534 GtkWidget*
535 psppire_syntax_window_new (void)
536 {
537   return GTK_WIDGET (g_object_new (psppire_syntax_window_get_type (),
538                                    "filename", "Syntax",
539                                    "description", _("Syntax Editor"),
540                                    NULL));
541 }
542
543 static void
544 error_dialog (GtkWindow *w, const gchar *filename,  GError *err)
545 {
546   gchar *fn = g_filename_display_basename (filename);
547
548   GtkWidget *dialog =
549     gtk_message_dialog_new (w,
550                             GTK_DIALOG_DESTROY_WITH_PARENT,
551                             GTK_MESSAGE_ERROR,
552                             GTK_BUTTONS_CLOSE,
553                             _("Cannot load syntax file '%s'"),
554                             fn);
555
556   g_free (fn);
557
558   g_object_set (dialog, "icon-name", "psppicon", NULL);
559
560   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
561                                             "%s", err->message);
562
563   gtk_dialog_run (GTK_DIALOG (dialog));
564
565   gtk_widget_destroy (dialog);
566 }
567
568 /*
569   Loads the buffer from the file called FILENAME
570 */
571 gboolean
572 syntax_load (PsppireWindow *window, const gchar *filename)
573 {
574   GError *err = NULL;
575   gchar *text_locale = NULL;
576   gchar *text_utf8 = NULL;
577   gsize len_locale = -1;
578   gsize len_utf8 = -1;
579   GtkTextIter iter;
580   PsppireSyntaxWindow *sw = PSPPIRE_SYNTAX_WINDOW (window);
581
582   /* FIXME: What if it's a very big file ? */
583   if ( ! g_file_get_contents (filename, &text_locale, &len_locale, &err) )
584     {
585       error_dialog (GTK_WINDOW (window), filename, err);
586       g_clear_error (&err);
587       return FALSE;
588     }
589
590   text_utf8 = g_locale_to_utf8 (text_locale, len_locale, NULL, &len_utf8, &err);
591
592   free (text_locale);
593
594   if ( text_utf8 == NULL )
595     {
596       error_dialog (GTK_WINDOW (window), filename, err);
597       g_clear_error (&err);
598       return FALSE;
599     }
600
601   gtk_text_buffer_get_iter_at_line (sw->buffer, &iter, 0);
602
603   gtk_text_buffer_insert (sw->buffer, &iter, text_utf8, len_utf8);
604
605   gtk_text_buffer_set_modified (sw->buffer, FALSE);
606
607   free (text_utf8);
608
609   return TRUE;
610 }
611
612 \f
613
614 static void
615 psppire_syntax_window_iface_init (PsppireWindowIface *iface)
616 {
617   iface->save = syntax_save;
618   iface->load = syntax_load;
619 }