Merge 'master' into 'psppsheet'.
[pspp] / src / ui / gui / compute-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 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 #include <gtk/gtk.h>
19 #include "compute-dialog.h"
20 #include "builder-wrapper.h"
21 #include "psppire-dialog.h"
22 #include "psppire-keypad.h"
23 #include "psppire-data-window.h"
24 #include "psppire-selector.h"
25 #include "dialog-common.h"
26 #include <libpspp/i18n.h>
27
28
29 #include <language/expressions/public.h>
30 #include "executor.h"
31 #include "helper.h"
32
33
34 static void function_list_populate (GtkTreeView *tv);
35
36 static void insert_function_into_syntax_area (GtkTreeIter iter,
37                                               GtkWidget *text_view,
38                                               GtkTreeModel *model,
39                                               gpointer data
40                                               );
41
42 static void insert_source_row_into_text_view (GtkTreeIter iter,
43                                               GtkWidget *dest,
44                                               GtkTreeModel *model,
45                                               gpointer data
46                                               );
47
48
49
50 struct compute_dialog
51 {
52   GtkBuilder *xml;  /* The xml that generated the widgets */
53   PsppireDict *dict;
54   gboolean use_type;
55 };
56
57
58 static void
59 on_target_change (GObject *obj, struct compute_dialog *cd)
60 {
61   GtkWidget *target = get_widget_assert (cd->xml, "compute-entry1");
62   GtkWidget *type_and_label = get_widget_assert (cd->xml, "compute-button1");
63
64   const gchar *var_name = gtk_entry_get_text (GTK_ENTRY (target));
65   gboolean valid = var_name && strcmp ("", var_name);
66
67   gtk_widget_set_sensitive (type_and_label, valid);
68 }
69
70 static void
71 refresh (GObject *obj, const struct compute_dialog *cd)
72 {
73   GtkTextIter start, end;
74   GtkWidget *target = get_widget_assert (cd->xml, "compute-entry1");
75   GtkWidget *syntax_area = get_widget_assert (cd->xml, "compute-textview1");
76   GtkWidget *varlist = get_widget_assert (cd->xml, "compute-treeview1");
77   GtkWidget *funclist = get_widget_assert (cd->xml, "compute-treeview2");
78
79   GtkTextBuffer *buffer =
80     gtk_text_view_get_buffer (GTK_TEXT_VIEW (syntax_area));
81
82   GtkTreeSelection *selection;
83
84   /* Clear the target variable entry box */
85   gtk_entry_set_text (GTK_ENTRY (target), "");
86   g_signal_emit_by_name (target, "changed");
87
88   /* Clear the syntax area textbuffer */
89   gtk_text_buffer_get_start_iter (buffer, &start);
90   gtk_text_buffer_get_end_iter (buffer, &end);
91   gtk_text_buffer_delete (buffer, &start, &end);
92
93   /* Unselect all items in the treeview */
94   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (varlist));
95   gtk_tree_selection_unselect_all (selection);
96
97   /* And the other one */
98   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (funclist));
99   gtk_tree_selection_unselect_all (selection);
100 }
101
102
103 static void
104 erase_selection (GtkTextBuffer *buffer)
105 {
106   GtkTextIter start, end;
107
108   gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
109
110   gtk_text_buffer_delete (buffer, &start, &end);
111 }
112
113
114
115 static void
116 on_keypad_button (PsppireKeypad *kp, const gchar *syntax, gpointer data)
117 {
118   GtkBuilder *xml = data;
119
120   GtkWidget *rhs = get_widget_assert (xml, "compute-textview1");
121
122   GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (rhs));
123
124   erase_selection (buffer);
125
126   gtk_text_buffer_insert_at_cursor (buffer, syntax, strlen (syntax));
127
128   if (0 == strcmp (syntax, "()"))
129     {
130       GtkTextIter iter;
131       GtkTextMark *cursor = gtk_text_buffer_get_insert (buffer);
132       gtk_text_buffer_get_iter_at_mark (buffer, &iter, cursor);
133       gtk_text_iter_backward_cursor_position (&iter);
134       gtk_text_buffer_move_mark (buffer, cursor, &iter);
135     }
136
137 }
138
139 static void
140 erase (PsppireKeypad *kp, gpointer data)
141 {
142   GtkBuilder *xml = data;
143
144   GtkWidget *rhs = get_widget_assert (xml, "compute-textview1");
145
146   GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (rhs));
147
148   erase_selection (buffer);
149 }
150
151 static char *
152 generate_syntax (const struct compute_dialog *cd)
153 {
154   gchar *text;
155   GString *string ;
156   const gchar *target_name ;
157   gchar *expression;
158   const gchar *label;
159   GtkTextIter start, end;
160   GtkWidget *target = get_widget_assert   (cd->xml, "compute-entry1");
161   GtkWidget *syntax_area = get_widget_assert (cd->xml, "compute-textview1");
162   GtkWidget *string_toggle = get_widget_assert (cd->xml, "radio-button-string");
163   GtkWidget *user_label_toggle =
164     get_widget_assert (cd->xml, "radio-button-user-label");
165   GtkWidget *width_entry = get_widget_assert (cd->xml, "type-and-label-width");
166   GtkWidget *label_entry = get_widget_assert (cd->xml,
167                                               "type-and-label-label-entry");
168
169
170   GtkTextBuffer *buffer =
171     gtk_text_view_get_buffer (GTK_TEXT_VIEW (syntax_area));
172
173   gtk_text_buffer_get_start_iter (buffer, &start);
174   gtk_text_buffer_get_end_iter (buffer, &end);
175
176   target_name = gtk_entry_get_text (GTK_ENTRY (target));
177
178   expression = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
179
180   string = g_string_sized_new (64);
181
182   if ( cd-> use_type &&
183        NULL == psppire_dict_lookup_var (cd->dict, target_name ))
184     {
185       if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (string_toggle)))
186         {
187           const char *w = gtk_entry_get_text (GTK_ENTRY(width_entry));
188           g_string_append_printf (string,
189                                   "STRING %s (a%s).\n", target_name, w);
190         }
191       else
192         g_string_append_printf (string, "NUMERIC %s.\n", target_name);
193     }
194
195   if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (user_label_toggle)))
196     label = gtk_entry_get_text (GTK_ENTRY (label_entry));
197   else
198     label = expression;
199
200   if ( strlen (label) > 0 )
201     g_string_append_printf (string, "VARIABLE LABEL %s '%s'.\n",
202                             target_name,
203                             label);
204
205   g_string_append_printf (string, "COMPUTE %s = %s.\n",
206                           target_name,
207                           expression
208                           );
209
210   g_string_append (string, "EXECUTE.\n");
211
212
213   g_free (expression);
214
215   text = string->str;
216
217   g_string_free (string, FALSE);
218
219   return text;
220 }
221
222 static void
223 reset_type_label_dialog (struct compute_dialog *cd)
224 {
225   const gchar *target_name;
226   struct variable *target_var;
227
228   GtkWidget *width_entry =
229     get_widget_assert (cd->xml, "type-and-label-width");
230
231   GtkWidget *label_entry =
232     get_widget_assert (cd->xml, "type-and-label-label-entry");
233
234   GtkWidget *numeric_target =
235     get_widget_assert (cd->xml, "radio-button-numeric");
236
237   GtkWidget *string_target =
238     get_widget_assert (cd->xml, "radio-button-string");
239
240
241   target_name = gtk_entry_get_text
242     (GTK_ENTRY (get_widget_assert (cd->xml, "compute-entry1")));
243
244
245   if ( (target_var = psppire_dict_lookup_var (cd->dict, target_name)) )
246     {
247       /* Existing Variable */
248       const gchar *label ;
249       GtkWidget *user_label =
250         get_widget_assert (cd->xml, "radio-button-user-label");
251
252       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (user_label), TRUE);
253
254       label = var_get_label (target_var);
255
256       if ( label )
257         {
258           gtk_entry_set_text (GTK_ENTRY (label_entry), label);
259         }
260
261       gtk_widget_set_sensitive (width_entry, FALSE);
262
263       if ( var_is_numeric (target_var))
264         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (numeric_target),
265                                       TRUE);
266       else
267         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (string_target),
268                                       TRUE);
269
270       gtk_widget_set_sensitive (numeric_target, FALSE);
271       gtk_widget_set_sensitive (string_target, FALSE);
272     }
273   else
274     {
275       GtkWidget *expression =
276         get_widget_assert (cd->xml, "radio-button-expression-label");
277
278       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (expression), TRUE);
279
280       gtk_widget_set_sensitive (width_entry, TRUE);
281       gtk_widget_set_sensitive (numeric_target, TRUE);
282       gtk_widget_set_sensitive (string_target, TRUE);
283
284       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (numeric_target),
285                                     TRUE);
286     }
287
288 }
289
290 static void
291 run_type_label_dialog (GtkButton *b, gpointer data)
292 {
293   struct compute_dialog *cd = data;
294   gint response;
295
296   GtkWidget *subdialog = get_widget_assert (cd->xml, "type-and-label-dialog");
297   GtkWidget *dialog = get_widget_assert (cd->xml, "compute-variable-dialog");
298
299   gtk_window_set_transient_for (GTK_WINDOW (subdialog), GTK_WINDOW (dialog));
300
301   reset_type_label_dialog (cd);
302   response = psppire_dialog_run (PSPPIRE_DIALOG (subdialog));
303   if ( response == PSPPIRE_RESPONSE_CONTINUE)
304     cd->use_type = TRUE;
305 }
306
307 static void
308 on_type_toggled (GtkToggleButton *button, gpointer data)
309 {
310   struct compute_dialog *cd = data;
311
312   GtkWidget *entry =
313     get_widget_assert (cd->xml, "type-and-label-width");
314
315   if ( gtk_toggle_button_get_active (button))
316     {
317       gtk_widget_set_sensitive (entry, TRUE);
318       gtk_widget_grab_focus (entry);
319     }
320   else
321     {
322       gtk_widget_set_sensitive (entry, FALSE);
323     }
324 }
325
326 static void
327 on_expression_toggle (GtkToggleButton *button, gpointer data)
328 {
329   struct compute_dialog *cd = data;
330
331   GtkWidget *entry =
332     get_widget_assert (cd->xml, "type-and-label-label-entry");
333
334   if ( gtk_toggle_button_get_active (button))
335     {
336       gtk_entry_set_text (GTK_ENTRY (entry), "");
337       gtk_widget_set_sensitive (entry, FALSE);
338     }
339   else
340     {
341       struct variable *target_var;
342       const gchar *target_name = gtk_entry_get_text
343         (GTK_ENTRY (get_widget_assert (cd->xml, "compute-entry1")));
344
345       target_var = psppire_dict_lookup_var (cd->dict, target_name);
346       if ( target_var )
347         {
348           const char *label = var_get_label (target_var);
349
350           if ( label )
351             gtk_entry_set_text (GTK_ENTRY (entry), label);
352         }
353       else
354         gtk_entry_set_text (GTK_ENTRY (entry), "");
355
356       gtk_widget_set_sensitive (entry, TRUE);
357       gtk_widget_grab_focus (entry);
358     }
359 }
360
361
362 /* Return TRUE if the dialog box's widgets' state are such that clicking OK
363    might not result in erroneous syntax being generated */
364 static gboolean
365 contents_plausible (gpointer data)
366 {
367   struct compute_dialog *cd = data;
368
369   GtkWidget *target      = get_widget_assert (cd->xml, "compute-entry1");
370   GtkWidget *syntax_area = get_widget_assert (cd->xml, "compute-textview1");
371   GtkTextBuffer *buffer  =
372     gtk_text_view_get_buffer (GTK_TEXT_VIEW (syntax_area));
373
374   if ( 0 == strcmp ("", gtk_entry_get_text (GTK_ENTRY (target))))
375     return FALSE;
376
377   if ( gtk_text_buffer_get_char_count (buffer) == 0 )
378     return FALSE;
379
380   return TRUE;
381 }
382
383 /* Pops up the Compute dialog box */
384 void
385 compute_dialog (PsppireDataWindow *de)
386 {
387   gint response;
388
389   struct compute_dialog scd;
390
391   GtkBuilder *xml = builder_new ("compute.ui");
392
393   GtkWidget *dialog = get_widget_assert   (xml, "compute-variable-dialog");
394
395   GtkWidget *dict_view = get_widget_assert (xml, "compute-treeview1");
396   GtkWidget *functions = get_widget_assert (xml, "compute-treeview2");
397   GtkWidget *keypad    = get_widget_assert (xml, "psppire-keypad1");
398   GtkWidget *target    = get_widget_assert (xml, "compute-entry1");
399   GtkWidget *var_selector = get_widget_assert (xml, "compute-selector1");
400   GtkWidget *func_selector = get_widget_assert (xml, "compute-selector2");
401   GtkWidget *type_and_label = get_widget_assert (xml, "compute-button1");
402
403   GtkWidget *expression =
404         get_widget_assert (xml, "radio-button-expression-label");
405
406   GtkWidget *str_btn =
407         get_widget_assert (xml, "radio-button-string");
408
409
410   g_object_get (de->data_editor, "dictionary", &scd.dict, NULL);
411   scd.use_type = FALSE;
412
413   g_signal_connect (expression, "toggled",
414                     G_CALLBACK(on_expression_toggle), &scd);
415
416   g_signal_connect (str_btn, "toggled",
417                     G_CALLBACK(on_type_toggled), &scd);
418
419   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
420
421   
422   g_object_set (dict_view, "model", scd.dict,
423                 "selection-mode", GTK_SELECTION_SINGLE,
424                 NULL);
425
426   psppire_selector_set_select_func (PSPPIRE_SELECTOR (var_selector),
427                                     insert_source_row_into_text_view, NULL);
428
429   function_list_populate (GTK_TREE_VIEW (functions));
430
431   psppire_selector_set_select_func (PSPPIRE_SELECTOR (func_selector),
432                                     insert_function_into_syntax_area, NULL);
433
434   scd.xml = xml;
435
436   psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (dialog),
437                                       contents_plausible, &scd);
438
439   g_signal_connect (target, "changed", G_CALLBACK (on_target_change), &scd);
440
441   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &scd);
442
443   g_signal_connect (keypad, "insert-syntax",
444                     G_CALLBACK (on_keypad_button),  xml);
445
446   g_signal_connect (keypad, "erase",
447                     G_CALLBACK (erase),  xml);
448
449
450   g_signal_connect (type_and_label, "clicked",
451                     G_CALLBACK (run_type_label_dialog),  &scd);
452
453
454
455   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
456
457
458   switch (response)
459     {
460     case GTK_RESPONSE_OK:
461       g_free (execute_syntax_string (de, generate_syntax (&scd)));
462       break;
463     case PSPPIRE_RESPONSE_PASTE:
464       g_free (paste_syntax_to_window (generate_syntax (&scd)));
465       break;
466     default:
467       break;
468     }
469
470   g_object_unref (xml);
471 }
472
473
474 enum {
475   COMPUTE_COL_NAME,
476   COMPUTE_COL_USAGE,
477   COMPUTE_COL_ARITY
478 };
479
480
481 static void
482 function_list_populate (GtkTreeView *tv)
483 {
484   GtkListStore *liststore;
485   GtkTreeIter iter;
486   gint i;
487
488   const gint n_funcs = expr_get_function_cnt ();
489
490   liststore = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
491
492   for (i = 0 ; i < n_funcs ; ++i)
493     {
494       const struct operation *op = expr_get_function (i);
495
496       gtk_list_store_append (liststore, &iter);
497
498       gtk_list_store_set (liststore, &iter,
499                           COMPUTE_COL_NAME, expr_operation_get_name (op),
500                           COMPUTE_COL_USAGE, expr_operation_get_prototype (op),
501                           COMPUTE_COL_ARITY, expr_operation_get_arg_cnt (op),
502                           -1);
503     }
504
505
506
507   /* Set the cell rendering */
508
509   {
510     GtkTreeViewColumn *col;
511     GtkCellRenderer *renderer;
512
513
514     col = gtk_tree_view_column_new ();
515
516     gtk_tree_view_append_column (tv, col);
517
518     renderer = gtk_cell_renderer_text_new ();
519
520     gtk_tree_view_column_pack_start (col, renderer, TRUE);
521
522     gtk_tree_view_column_add_attribute (col, renderer, "text", COMPUTE_COL_USAGE);
523   }
524
525   gtk_tree_view_set_model (tv, GTK_TREE_MODEL (liststore));
526   g_object_unref (liststore);
527 }
528
529
530
531
532 static void
533 insert_function_into_syntax_area (GtkTreeIter iter,
534                                   GtkWidget *text_view,
535                                   GtkTreeModel *model,
536                                   gpointer data
537                                   )
538 {
539   GString *string;
540   GValue name_value = {0};
541   GValue arity_value = {0};
542   gint arity;
543   gint i;
544
545   GtkTextBuffer *buffer ;
546
547   g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
548
549   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
550
551   gtk_tree_model_get_value (model, &iter, COMPUTE_COL_NAME, &name_value);
552   gtk_tree_model_get_value (model, &iter, COMPUTE_COL_ARITY, &arity_value);
553
554   arity = g_value_get_int (&arity_value);
555
556   string = g_string_new (g_value_get_string (&name_value));
557
558   g_string_append (string, "(");
559   for ( i = 0 ; i < arity -1 ; ++i )
560     {
561       g_string_append (string, "?,");
562     }
563   g_string_append (string, "?)");
564
565
566   erase_selection (buffer);
567
568   gtk_text_buffer_insert_at_cursor (buffer, string->str, string->len);
569
570   g_value_unset (&name_value);
571   g_value_unset (&arity_value);
572   g_string_free (string, TRUE);
573
574   /* Now position the cursor over the first '?' */
575   {
576     GtkTextIter insert;
577     GtkTextIter selectbound;
578     GtkTextMark *cursor = gtk_text_buffer_get_insert (buffer);
579     gtk_text_buffer_get_iter_at_mark (buffer, &insert, cursor);
580     for ( i = 0 ; i < arity ; ++i )
581       {
582         gtk_text_iter_backward_cursor_position (&insert);
583         gtk_text_iter_backward_cursor_position (&insert);
584       }
585     selectbound = insert;
586     gtk_text_iter_forward_cursor_position (&selectbound);
587
588     gtk_text_buffer_select_range (buffer, &insert, &selectbound);
589   }
590
591 }
592
593 /* Inserts the name of the selected variable into the destination widget.
594    The destination widget must be a GtkTextView
595  */
596 static void
597 insert_source_row_into_text_view (GtkTreeIter iter,
598                                   GtkWidget *dest,
599                                   GtkTreeModel *model,
600                                   gpointer data
601                                   )
602 {
603   GtkTreePath *path;
604   PsppireDict *dict;
605   gint *idx;
606   struct variable *var;
607   GtkTreeIter dict_iter;
608   GtkTextBuffer *buffer;
609
610   g_return_if_fail (GTK_IS_TEXT_VIEW (dest));
611
612   if ( GTK_IS_TREE_MODEL_FILTER (model))
613     {
614       dict = PSPPIRE_DICT (gtk_tree_model_filter_get_model
615                            (GTK_TREE_MODEL_FILTER(model)));
616
617       gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER
618                                                         (model),
619                                                         &dict_iter, &iter);
620     }
621   else
622     {
623       dict = PSPPIRE_DICT (model);
624       dict_iter = iter;
625     }
626
627   path = gtk_tree_model_get_path (GTK_TREE_MODEL (dict), &dict_iter);
628
629   idx = gtk_tree_path_get_indices (path);
630
631   var =  psppire_dict_get_variable (dict, *idx);
632
633   gtk_tree_path_free (path);
634
635   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (dest));
636
637   erase_selection (buffer);
638
639   gtk_text_buffer_insert_at_cursor (buffer, var_get_name (var), -1);
640
641 }