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