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