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