pspp-sheet-view: Make Home and End go to left and right of sheet.
[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
308 static void
309 on_expression_toggle (GtkToggleButton *button, gpointer data)
310 {
311   struct compute_dialog *cd = data;
312
313   GtkWidget *entry =
314     get_widget_assert (cd->xml, "type-and-label-label-entry");
315
316   if ( gtk_toggle_button_get_active (button))
317     {
318       gtk_entry_set_text (GTK_ENTRY (entry), "");
319       gtk_widget_set_sensitive (entry, FALSE);
320     }
321   else
322     {
323       const char *label;
324       struct variable *target_var;
325       const gchar *target_name = gtk_entry_get_text
326         (GTK_ENTRY (get_widget_assert (cd->xml, "compute-entry1")));
327
328       target_var = psppire_dict_lookup_var (cd->dict, target_name);
329       if ( target_var )
330         {
331           label = var_get_label (target_var);
332
333           if ( label )
334             gtk_entry_set_text (GTK_ENTRY (entry), label);
335         }
336       else
337         gtk_entry_set_text (GTK_ENTRY (entry), "");
338
339       gtk_widget_set_sensitive (entry, TRUE);
340     }
341 }
342
343
344 /* Return TRUE if the dialog box's widgets' state are such that clicking OK
345    might not result in erroneous syntax being generated */
346 static gboolean
347 contents_plausible (gpointer data)
348 {
349   struct compute_dialog *cd = data;
350
351   GtkWidget *target      = get_widget_assert (cd->xml, "compute-entry1");
352   GtkWidget *syntax_area = get_widget_assert (cd->xml, "compute-textview1");
353   GtkTextBuffer *buffer  =
354     gtk_text_view_get_buffer (GTK_TEXT_VIEW (syntax_area));
355
356   if ( 0 == strcmp ("", gtk_entry_get_text (GTK_ENTRY (target))))
357     return FALSE;
358
359   if ( gtk_text_buffer_get_char_count (buffer) == 0 )
360     return FALSE;
361
362   return TRUE;
363 }
364
365 /* Pops up the Compute dialog box */
366 void
367 compute_dialog (PsppireDataWindow *de)
368 {
369   gint response;
370
371   struct compute_dialog scd;
372
373   GtkBuilder *xml = builder_new ("compute.ui");
374
375   GtkWidget *dialog = get_widget_assert   (xml, "compute-variable-dialog");
376
377   GtkWidget *dict_view = get_widget_assert (xml, "compute-treeview1");
378   GtkWidget *functions = get_widget_assert (xml, "compute-treeview2");
379   GtkWidget *keypad    = get_widget_assert (xml, "psppire-keypad1");
380   GtkWidget *target    = get_widget_assert (xml, "compute-entry1");
381   GtkWidget *var_selector = get_widget_assert (xml, "compute-selector1");
382   GtkWidget *func_selector = get_widget_assert (xml, "compute-selector2");
383   GtkWidget *type_and_label = get_widget_assert (xml, "compute-button1");
384
385   GtkWidget *expression =
386         get_widget_assert (xml, "radio-button-expression-label");
387
388
389   g_object_get (de->data_editor, "dictionary", &scd.dict, NULL);
390   scd.use_type = FALSE;
391
392   g_signal_connect (expression, "toggled",
393                     G_CALLBACK(on_expression_toggle), &scd);
394
395   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
396
397   
398   g_object_set (dict_view, "model", scd.dict,
399                 "selection-mode", GTK_SELECTION_SINGLE,
400                 NULL);
401
402   psppire_selector_set_select_func (PSPPIRE_SELECTOR (var_selector),
403                                     insert_source_row_into_text_view, NULL);
404
405   function_list_populate (GTK_TREE_VIEW (functions));
406
407   psppire_selector_set_select_func (PSPPIRE_SELECTOR (func_selector),
408                                     insert_function_into_syntax_area, NULL);
409
410   scd.xml = xml;
411
412   psppire_dialog_set_valid_predicate (PSPPIRE_DIALOG (dialog),
413                                       contents_plausible, &scd);
414
415   g_signal_connect (target, "changed", G_CALLBACK (on_target_change), &scd);
416
417   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &scd);
418
419   g_signal_connect (keypad, "insert-syntax",
420                     G_CALLBACK (on_keypad_button),  xml);
421
422   g_signal_connect (keypad, "erase",
423                     G_CALLBACK (erase),  xml);
424
425
426   g_signal_connect (type_and_label, "clicked",
427                     G_CALLBACK (run_type_label_dialog),  &scd);
428
429
430
431   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
432
433
434   switch (response)
435     {
436     case GTK_RESPONSE_OK:
437       g_free (execute_syntax_string (de, generate_syntax (&scd)));
438       break;
439     case PSPPIRE_RESPONSE_PASTE:
440       g_free (paste_syntax_to_window (generate_syntax (&scd)));
441       break;
442     default:
443       break;
444     }
445
446   g_object_unref (xml);
447 }
448
449
450 enum {
451   COMPUTE_COL_NAME,
452   COMPUTE_COL_USAGE,
453   COMPUTE_COL_ARITY
454 };
455
456
457 static void
458 function_list_populate (GtkTreeView *tv)
459 {
460   GtkListStore *liststore;
461   GtkTreeIter iter;
462   gint i;
463
464   const gint n_funcs = expr_get_function_cnt ();
465
466   liststore = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
467
468   for (i = 0 ; i < n_funcs ; ++i)
469     {
470       const struct operation *op = expr_get_function (i);
471
472       gtk_list_store_append (liststore, &iter);
473
474       gtk_list_store_set (liststore, &iter,
475                           COMPUTE_COL_NAME, expr_operation_get_name (op),
476                           COMPUTE_COL_USAGE, expr_operation_get_prototype (op),
477                           COMPUTE_COL_ARITY, expr_operation_get_arg_cnt (op),
478                           -1);
479     }
480
481
482
483   /* Set the cell rendering */
484
485   {
486     GtkTreeViewColumn *col;
487     GtkCellRenderer *renderer;
488
489
490     col = gtk_tree_view_column_new ();
491
492     gtk_tree_view_append_column (tv, col);
493
494     renderer = gtk_cell_renderer_text_new ();
495
496     gtk_tree_view_column_pack_start (col, renderer, TRUE);
497
498     gtk_tree_view_column_add_attribute (col, renderer, "text", COMPUTE_COL_USAGE);
499   }
500
501   gtk_tree_view_set_model (tv, GTK_TREE_MODEL (liststore));
502   g_object_unref (liststore);
503 }
504
505
506
507
508 static void
509 insert_function_into_syntax_area (GtkTreeIter iter,
510                                   GtkWidget *text_view,
511                                   GtkTreeModel *model,
512                                   gpointer data
513                                   )
514 {
515   GString *string;
516   GValue name_value = {0};
517   GValue arity_value = {0};
518   gint arity;
519   gint i;
520
521   GtkTextBuffer *buffer ;
522
523   g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
524
525   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
526
527   gtk_tree_model_get_value (model, &iter, COMPUTE_COL_NAME, &name_value);
528   gtk_tree_model_get_value (model, &iter, COMPUTE_COL_ARITY, &arity_value);
529
530   arity = g_value_get_int (&arity_value);
531
532   string = g_string_new (g_value_get_string (&name_value));
533
534   g_string_append (string, "(");
535   for ( i = 0 ; i < arity -1 ; ++i )
536     {
537       g_string_append (string, "?,");
538     }
539   g_string_append (string, "?)");
540
541
542   erase_selection (buffer);
543
544   gtk_text_buffer_insert_at_cursor (buffer, string->str, string->len);
545
546   g_value_unset (&name_value);
547   g_value_unset (&arity_value);
548   g_string_free (string, TRUE);
549
550   /* Now position the cursor over the first '?' */
551   {
552     GtkTextIter insert;
553     GtkTextIter selectbound;
554     GtkTextMark *cursor = gtk_text_buffer_get_insert (buffer);
555     gtk_text_buffer_get_iter_at_mark (buffer, &insert, cursor);
556     for ( i = 0 ; i < arity ; ++i )
557       {
558         gtk_text_iter_backward_cursor_position (&insert);
559         gtk_text_iter_backward_cursor_position (&insert);
560       }
561     selectbound = insert;
562     gtk_text_iter_forward_cursor_position (&selectbound);
563
564     gtk_text_buffer_select_range (buffer, &insert, &selectbound);
565   }
566
567 }
568
569 /* Inserts the name of the selected variable into the destination widget.
570    The destination widget must be a GtkTextView
571  */
572 static void
573 insert_source_row_into_text_view (GtkTreeIter iter,
574                                   GtkWidget *dest,
575                                   GtkTreeModel *model,
576                                   gpointer data
577                                   )
578 {
579   GtkTreePath *path;
580   PsppireDict *dict;
581   gint *idx;
582   struct variable *var;
583   GtkTreeIter dict_iter;
584   GtkTextBuffer *buffer;
585
586   g_return_if_fail (GTK_IS_TEXT_VIEW (dest));
587
588   if ( GTK_IS_TREE_MODEL_FILTER (model))
589     {
590       dict = PSPPIRE_DICT (gtk_tree_model_filter_get_model
591                            (GTK_TREE_MODEL_FILTER(model)));
592
593       gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER
594                                                         (model),
595                                                         &dict_iter, &iter);
596     }
597   else
598     {
599       dict = PSPPIRE_DICT (model);
600       dict_iter = iter;
601     }
602
603   path = gtk_tree_model_get_path (GTK_TREE_MODEL (dict), &dict_iter);
604
605   idx = gtk_tree_path_get_indices (path);
606
607   var =  psppire_dict_get_variable (dict, *idx);
608
609   gtk_tree_path_free (path);
610
611   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (dest));
612
613   erase_selection (buffer);
614
615   gtk_text_buffer_insert_at_cursor (buffer, var_get_name (var), -1);
616
617 }