057e94338ecb7691e5f4f29bbd94a19749adc33f
[pspp-builds.git] / src / ui / gui / sort-cases-dialog.c
1 /*
2     PSPPIRE --- A Graphical User Interface for PSPP
3     Copyright (C) 2006  Free Software Foundation
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18     02110-1301, USA. */
19
20 /*  This module describes the behaviour of the Sort Cases dialog box. */
21
22
23 /* This code is rather quick and dirty.  Some of the issues are:
24
25    1. Character set conversion when displaying dictionary tree view.
26
27    2. The interaction between the dictionary treeview, the criteria
28       list treeview and the button, needs to be abstracted and made
29       available as an external interface.
30
31    3. There's no destroy function for this dialog.
32
33    4. Some of the functionality might be better implemented with
34       GtkAction.
35
36    5. Double clicking the tree view rows should insert/delete them
37       from the criteria list.
38
39    6. Changing the Ascending/Descending flag ought to be possible for
40       a criteria already in the criteria tree view.
41
42    7. Variables which are in the criteria tree view should not be
43       shown in the dictionary treeview.
44
45    8. The dialog box structure itself ought to be a GtkWindow and
46       abstracted better.
47 */
48
49
50 #include <config.h>
51 #include "helper.h"
52 #include "sort-cases-dialog.h"
53 #include "psppire-dict.h"
54 #include <math/sort.h>
55
56 #include <gettext.h>
57 #define _(msgid) gettext (msgid)
58 #define N_(msgid) msgid
59
60
61 enum {CRIT_TVM_IDX = 0, CRIT_TVM_DIR};
62
63 /* Occurs when the dictionary tree view selection changes */
64 static void
65 dictionary_selection_changed (GtkTreeSelection *selection,
66                               gpointer data)
67 {
68   GtkTreeSelection *otherselection ;
69   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) data;
70
71   if ( 0 == gtk_tree_selection_count_selected_rows (selection) )
72     return ;
73
74   gtk_arrow_set (dialog->arrow, GTK_ARROW_RIGHT, GTK_SHADOW_OUT);
75   dialog->button_state = VAR_SELECT;
76
77   otherselection = gtk_tree_view_get_selection (dialog->criteria_view);
78
79   gtk_tree_selection_unselect_all (otherselection);
80 }
81
82
83 /* Occurs when the sort criteria tree view selection changes */
84 static void
85 criteria_selection_changed (GtkTreeSelection *selection,
86                             gpointer data)
87 {
88   GtkTreeSelection *otherselection ;
89   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) data;
90
91   if ( 0 == gtk_tree_selection_count_selected_rows (selection) )
92     return ;
93
94   otherselection = gtk_tree_view_get_selection (dialog->dict_view);
95
96   gtk_arrow_set (dialog->arrow, GTK_ARROW_LEFT, GTK_SHADOW_OUT);
97   dialog->button_state = VAR_DESELECT;
98
99   gtk_tree_selection_unselect_all (otherselection);
100 }
101
102
103 /* Occurs when the dialog box is deleted (eg: closed via the title bar) */
104 static gint
105 delete_event_callback (GtkWidget *widget,
106                       GdkEvent  *event,
107                       gpointer   data)
108 {
109   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) data;
110
111   g_main_loop_quit (dialog->loop);
112
113   gtk_widget_hide_on_delete (widget);
114
115   dialog->response = GTK_RESPONSE_DELETE_EVENT;
116
117   return TRUE;
118 }
119
120 /* Occurs when the cancel button is clicked */
121 static void
122 sort_cases_cancel_callback (GObject *obj, gpointer data)
123 {
124   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) data;
125
126   gtk_widget_hide (dialog->window);
127
128   g_main_loop_quit (dialog->loop);
129
130   dialog->response = GTK_RESPONSE_CANCEL;
131 }
132
133 /* Occurs when the reset button is clicked */
134 static void
135 sort_cases_reset_callback (GObject *obj, gpointer data)
136 {
137   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) data;
138
139   gtk_arrow_set (dialog->arrow, GTK_ARROW_RIGHT, GTK_SHADOW_OUT);
140   dialog->button_state = VAR_SELECT;
141
142   gtk_list_store_clear (dialog->criteria_list);
143 }
144
145
146 /* Add variables currently selected in the dictionary tree view to the
147    list of criteria */
148 static void
149 select_criteria (GtkTreeModel *model, GtkTreePath *path,
150                 GtkTreeIter *iter, gpointer data)
151 {
152   GtkTreeIter new_iter;
153   gint index;
154   gint dir;
155   struct variable *variable;
156   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) data;
157
158   /* Get the variable from the dictionary */
159   gtk_tree_model_get (model, iter,
160                       DICT_TVM_COL_VAR, &variable,
161                       -1);
162
163   index = var_get_dict_index (variable);
164
165   dir = gtk_toggle_button_get_active (dialog->ascending_button) ?
166     SRT_ASCEND:SRT_DESCEND;
167
168   /* Append to the list of criteria */
169   gtk_list_store_append (dialog->criteria_list, &new_iter);
170   gtk_list_store_set (dialog->criteria_list,
171                      &new_iter, CRIT_TVM_IDX, index, -1);
172   gtk_list_store_set (dialog->criteria_list,
173                      &new_iter, CRIT_TVM_DIR, dir, -1);
174 }
175
176 /* Create a list of the RowRefs which are to be removed from the
177    criteria list */
178 static void
179 path_to_row_ref (GtkTreeModel *model, GtkTreePath *path,
180                 GtkTreeIter *iter, gpointer data)
181 {
182   GList **rrlist = data;
183   GtkTreeRowReference *rowref = gtk_tree_row_reference_new (model, path);
184
185   *rrlist = g_list_append (*rrlist, rowref);
186 }
187
188
189 /* Remove a row from the list of criteria */
190 static void
191 deselect_criteria (gpointer data,
192                   gpointer user_data)
193 {
194   GtkTreeIter iter;
195   GtkTreeRowReference *row_ref = data;
196   GtkTreePath *path;
197   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) user_data;
198
199   path = gtk_tree_row_reference_get_path (row_ref);
200
201   gtk_tree_model_get_iter (GTK_TREE_MODEL (dialog->criteria_list), &iter, path);
202
203   gtk_list_store_remove (dialog->criteria_list, &iter);
204
205   gtk_tree_row_reference_free (row_ref);
206 }
207
208
209
210 /* Callback which occurs when the button to remove variables from the list
211    of criteria is clicked. */
212 static void
213 sort_cases_button_callback (GObject *obj, gpointer data)
214 {
215   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) data;
216
217   if ( dialog->button_state == VAR_SELECT) /* Right facing arrow */
218     {
219       GtkTreeSelection *selection =
220         gtk_tree_view_get_selection (dialog->dict_view);
221
222       gtk_tree_selection_selected_foreach (selection, select_criteria, dialog);
223     }
224   else   /* Left facing arrow */
225     {
226       GList *selectedRows = NULL;
227       GtkTreeSelection *selection =
228         gtk_tree_view_get_selection (dialog->criteria_view);
229
230       /* Make a list of rows to be deleted */
231       gtk_tree_selection_selected_foreach (selection, path_to_row_ref,
232                                           &selectedRows);
233
234       /* ... and delete them */
235       g_list_foreach (selectedRows, deselect_criteria, dialog);
236
237       g_list_free (selectedRows);
238     }
239 }
240
241
242 /* Callback which occurs when the OK button is clicked */
243 static void
244 sort_cases_ok_callback (GObject *obj, gpointer data)
245 {
246   struct sort_cases_dialog *dialog = (struct sort_cases_dialog*) data;
247
248   gtk_widget_hide (dialog->window);
249   g_main_loop_quit (dialog->loop);
250
251   dialog->response = GTK_RESPONSE_OK;
252 }
253
254
255 /* This function is responsible for rendering a criterion in the
256    criteria list */
257 static void
258 criteria_render_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
259                      GtkTreeModel *model, GtkTreeIter *iter,
260                      gpointer data)
261 {
262   gint var_index;
263   struct variable *variable ;
264   gint direction;
265   gchar *buf;
266   gchar *varname;
267   PsppireDict *dict  = data;
268
269   gtk_tree_model_get (model, iter,
270                      CRIT_TVM_IDX, &var_index,
271                      CRIT_TVM_DIR, &direction, -1);
272
273   variable = psppire_dict_get_variable (dict, var_index);
274
275   varname = pspp_locale_to_utf8 (var_get_name (variable),
276                                 -1, 0);
277
278   if ( direction == SRT_ASCEND)
279       buf = g_strdup_printf ("%s: %s", varname, _("Ascending"));
280   else
281       buf = g_strdup_printf ("%s: %s", varname, _("Descending"));
282
283   g_free (varname);
284
285   g_object_set (renderer, "text", buf, NULL);
286
287   g_free (buf);
288 }
289
290
291 /* Create the dialog */
292 struct sort_cases_dialog *
293 sort_cases_dialog_create (GladeXML *xml)
294 {
295   struct sort_cases_dialog *dialog = g_malloc (sizeof (*dialog));
296
297   dialog->loop = g_main_loop_new (NULL, FALSE);
298
299   dialog->window = get_widget_assert (xml, "sort-cases-dialog");
300
301   dialog->dict_view = GTK_TREE_VIEW (get_widget_assert
302                                     (xml, "sort-cases-treeview-dict"));
303   dialog->criteria_view = GTK_TREE_VIEW (get_widget_assert
304                                   (xml, "sort-cases-treeview-criteria"));
305
306   dialog->arrow = GTK_ARROW (get_widget_assert (xml, "sort-cases-arrow"));
307   dialog->button = GTK_BUTTON (get_widget_assert (xml, "sort-cases-button"));
308
309   dialog->ascending_button =
310     GTK_TOGGLE_BUTTON (get_widget_assert (xml, "sort-cases-button-ascending"));
311
312   g_signal_connect (dialog->window, "delete-event",
313                    G_CALLBACK (delete_event_callback), dialog);
314
315   g_signal_connect (get_widget_assert (xml, "sort-cases-cancel"),
316                    "clicked", G_CALLBACK (sort_cases_cancel_callback), dialog);
317
318   g_signal_connect (get_widget_assert (xml, "sort-cases-ok"),
319                    "clicked", G_CALLBACK (sort_cases_ok_callback), dialog);
320
321
322   g_signal_connect (get_widget_assert (xml, "sort-cases-reset"),
323                    "clicked", G_CALLBACK (sort_cases_reset_callback), dialog);
324
325
326   g_signal_connect (get_widget_assert (xml, "sort-cases-button"),
327                    "clicked", G_CALLBACK (sort_cases_button_callback), dialog);
328
329
330   {
331   /* Set up the dictionary treeview */
332     GtkTreeViewColumn *col;
333
334     GtkTreeSelection *selection =
335       gtk_tree_view_get_selection (dialog->dict_view);
336
337     GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
338
339     col = gtk_tree_view_column_new_with_attributes (_("Var"),
340                                                    renderer,
341                                                    "text",
342                                                    0,
343                                                    NULL);
344
345     /* FIXME: make this a value in terms of character widths */
346     g_object_set (col, "min-width",  100, NULL);
347
348     gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED);
349
350     gtk_tree_view_append_column (dialog->dict_view, col);
351
352     gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
353
354     g_signal_connect (selection, "changed",
355                      G_CALLBACK (dictionary_selection_changed), dialog);
356   }
357
358   {
359     /* Set up the variable list treeview */
360     GtkTreeSelection *selection =
361       gtk_tree_view_get_selection (dialog->criteria_view);
362
363     gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
364
365     dialog->crit_renderer = gtk_cell_renderer_text_new ();
366
367     dialog->crit_col = gtk_tree_view_column_new_with_attributes (_("Criteria"),
368                                                    dialog->crit_renderer,
369                                                    "text",
370                                                    0,
371                                                    NULL);
372
373     gtk_tree_view_column_set_sizing (dialog->crit_col, GTK_TREE_VIEW_COLUMN_FIXED);
374
375     gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->criteria_view),
376                                 dialog->crit_col);
377
378     g_signal_connect (selection, "changed",
379                      G_CALLBACK (criteria_selection_changed), dialog);
380   }
381
382   {
383     /* Create the list of criteria */
384     dialog->criteria_list = gtk_list_store_new (2,
385                             G_TYPE_INT, /* index of the variable */
386                             G_TYPE_INT  /* Ascending/Descending */
387                             );
388
389     gtk_tree_view_set_model (dialog->criteria_view,
390                             GTK_TREE_MODEL (dialog->criteria_list));
391   }
392
393   dialog->response = GTK_RESPONSE_NONE;
394
395   return dialog;
396 }
397
398
399 static void
400 convert_list_store_to_criteria (GtkListStore *list,
401                                PsppireDict *dict,
402                                struct sort_criteria *criteria);
403
404
405 /* Run the dialog.
406    If the return value is GTK_RESPONSE_OK, then CRITERIA gets filled
407    with a valid sort criteria which can be used to sort the data.
408    This structure and its contents must be freed by the caller. */
409 gint
410 sort_cases_dialog_run (struct sort_cases_dialog *dialog,
411                       PsppireDict *dict,
412                       struct sort_criteria *criteria
413                       )
414 {
415   g_assert (! g_main_loop_is_running (dialog->loop));
416
417   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dict_view),
418                           GTK_TREE_MODEL (dict));
419
420
421   gtk_tree_view_column_set_cell_data_func (dialog->crit_col,
422                                           dialog->crit_renderer,
423                                           criteria_render_func, dict, 0);
424
425   gtk_list_store_clear (dialog->criteria_list);
426
427   gtk_arrow_set (dialog->arrow, GTK_ARROW_RIGHT, GTK_SHADOW_OUT);
428   dialog->button_state = VAR_SELECT;
429
430   gtk_widget_show (dialog->window);
431
432   g_main_loop_run (dialog->loop);
433
434   if ( GTK_RESPONSE_OK == dialog->response)
435     convert_list_store_to_criteria (dialog->criteria_list,
436                                    dict, criteria);
437
438   return dialog->response;
439 }
440
441
442
443 /* Convert the GtkListStore to a struct sort_criteria*/
444 static void
445 convert_list_store_to_criteria (GtkListStore *list,
446                                PsppireDict *dict,
447                                struct sort_criteria *criteria)
448 {
449   GtkTreeIter iter;
450   gboolean valid;
451   gint n = 0;
452
453   GtkTreeModel *model = GTK_TREE_MODEL (list);
454
455   criteria->crit_cnt = gtk_tree_model_iter_n_children (model, NULL);
456
457   criteria->crits = g_malloc (sizeof (struct sort_criterion) *
458                              criteria->crit_cnt);
459
460   for (valid = gtk_tree_model_get_iter_first (model, &iter);
461       valid;
462       valid = gtk_tree_model_iter_next (model, &iter))
463     {
464       struct variable *variable;
465       gint index;
466       struct sort_criterion *scn = &criteria->crits[n];
467       g_assert ( n < criteria->crit_cnt);
468       n++;
469
470       gtk_tree_model_get (model, &iter,
471                          CRIT_TVM_IDX, &index,
472                          CRIT_TVM_DIR, &scn->dir,
473                          -1);
474
475       variable = psppire_dict_get_variable (dict, index);
476
477       scn->fv    = var_get_case_index (variable);
478       scn->width = var_get_width (variable);
479     }
480 }
481