089ab8f4795c5a8cf365fdd4f29c8c138fc88670
[pspp] / src / ui / gui / psppire-data-sheet.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2017  John Darrington
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 #include "psppire-data-sheet.h"
20
21 #include <gettext.h>
22 #define _(msgid) gettext (msgid)
23 #define P_(X) (X)
24
25 #include "value-variant.h"
26
27 #include "ui/gui/executor.h"
28 #include "psppire-data-window.h"
29
30 static void
31 do_sort (PsppireDataSheet *sheet, GtkSortType order)
32 {
33   SswRange *range = SSW_SHEET(sheet)->selection;
34
35   PsppireDataStore *data_store = NULL;
36   g_object_get (sheet, "data-model", &data_store, NULL);
37
38   int n_vars = 0;
39   int i;
40
41   PsppireDataWindow *pdw =
42      psppire_data_window_for_data_store (data_store);
43
44   GString *syntax = g_string_new ("SORT CASES BY");
45   for (i = range->start_x ; i <= range->end_x; ++i)
46     {
47       const struct variable *var = psppire_dict_get_variable (data_store->dict, i);
48       if (var != NULL)
49         {
50           g_string_append_printf (syntax, " %s", var_get_name (var));
51           n_vars++;
52         }
53     }
54   if (n_vars > 0)
55     {
56       if (order == GTK_SORT_DESCENDING)
57         g_string_append (syntax, " (DOWN)");
58       g_string_append_c (syntax, '.');
59       execute_const_syntax_string (pdw, syntax->str);
60     }
61   g_string_free (syntax, TRUE);
62 }
63
64
65 static void
66 sort_ascending (PsppireDataSheet *sheet)
67 {
68   do_sort (sheet, GTK_SORT_ASCENDING);
69
70   gtk_widget_queue_draw (GTK_WIDGET (sheet));
71 }
72
73 static void
74 sort_descending (PsppireDataSheet *sheet)
75 {
76   do_sort (sheet, GTK_SORT_DESCENDING);
77
78   gtk_widget_queue_draw (GTK_WIDGET (sheet));
79 }
80
81 \f
82
83 static void
84 change_data_value (PsppireDataSheet *sheet, gint col, gint row, GValue *value)
85 {
86   PsppireDataStore *store = NULL;
87   g_object_get (sheet, "data-model", &store, NULL);
88
89   const struct variable *var = psppire_dict_get_variable (store->dict, col);
90
91   if (NULL == var)
92     return;
93
94   union value v;
95
96   GVariant *vrnt = g_value_get_variant (value);
97
98   value_variant_get (&v, vrnt);
99
100   psppire_data_store_set_value (store, row, var, &v);
101
102   value_destroy_from_variant (&v, vrnt);
103 }
104
105 gboolean myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in,
106                     GValue *out);
107
108 \f
109
110 static void
111 show_cases_row_popup (PsppireDataSheet *sheet, int row,
112                       uint button, uint state, gpointer p)
113 {
114   GListModel *vmodel = NULL;
115   g_object_get (sheet, "vmodel", &vmodel, NULL);
116   if (vmodel == NULL)
117     return;
118
119   guint n_items = g_list_model_get_n_items (vmodel);
120
121   if (row >= n_items)
122     return;
123
124   if (button != 3)
125     return;
126
127   g_object_set_data (G_OBJECT (sheet->data_sheet_cases_row_popup), "item",
128                      GINT_TO_POINTER (row));
129
130   gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_row_popup), NULL);
131 }
132
133
134 static void
135 insert_new_case (PsppireDataSheet *sheet)
136 {
137   PsppireDataStore *data_store = NULL;
138   g_object_get (sheet, "data-model", &data_store, NULL);
139
140   gint posn = GPOINTER_TO_INT (g_object_get_data
141                                 (G_OBJECT (sheet->data_sheet_cases_row_popup), "item"));
142
143   psppire_data_store_insert_new_case (data_store, posn);
144
145   gtk_widget_queue_draw (GTK_WIDGET (sheet));
146 }
147
148 static void
149 delete_cases (PsppireDataSheet *sheet)
150 {
151   SswRange *range = SSW_SHEET(sheet)->selection;
152
153   PsppireDataStore *data_store = NULL;
154   g_object_get (sheet, "data-model", &data_store, NULL);
155
156   psppire_data_store_delete_cases (data_store, range->start_y,
157                                    range->end_y - range->start_y + 1);
158
159   gtk_widget_queue_draw (GTK_WIDGET (sheet));
160 }
161
162 static GtkWidget *
163 create_data_row_header_popup_menu (PsppireDataSheet *sheet)
164 {
165   GtkWidget *menu = gtk_menu_new ();
166
167   GtkWidget *item =
168     gtk_menu_item_new_with_mnemonic  (_("_Insert Case"));
169
170   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), sheet);
171   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
172
173   item = gtk_separator_menu_item_new ();
174   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
175
176   sheet->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
177   gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, FALSE);
178   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_cases_menu_item);
179   g_signal_connect_swapped (sheet->data_clear_cases_menu_item, "activate",
180                             G_CALLBACK (delete_cases), sheet);
181
182   gtk_widget_show_all (menu);
183   return menu;
184 }
185
186
187 static void
188 show_cases_column_popup (PsppireDataSheet *sheet, int column, uint button, uint state,
189                          gpointer p)
190 {
191   GListModel *hmodel = NULL;
192   g_object_get (sheet, "hmodel", &hmodel, NULL);
193   if (hmodel == NULL)
194     return;
195
196   guint n_items = g_list_model_get_n_items (hmodel);
197
198   if (column >= n_items)
199     return;
200
201   if (button != 3)
202     return;
203
204   g_object_set_data (G_OBJECT (sheet->data_sheet_cases_column_popup), "item",
205                      GINT_TO_POINTER (column));
206
207   gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_column_popup), NULL);
208 }
209
210 static void
211 insert_new_variable (PsppireDataSheet *sheet)
212 {
213   PsppireDataStore *data_store = NULL;
214   g_object_get (sheet, "data-model", &data_store, NULL);
215
216   gint posn = GPOINTER_TO_INT (g_object_get_data
217                                 (G_OBJECT (sheet->data_sheet_cases_column_popup),
218                                  "item"));
219
220   const struct variable *v = psppire_dict_insert_variable (data_store->dict,
221                                                            posn, NULL);
222
223   psppire_data_store_insert_value (data_store, var_get_width(v),
224                                    var_get_case_index (v));
225
226   gtk_widget_queue_draw (GTK_WIDGET (sheet));
227 }
228
229 static void
230 set_menu_items_sensitivity (PsppireDataSheet *sheet, gpointer selection, gpointer p)
231 {
232   SswRange *range = selection;
233
234   PsppireDataStore *data_store = NULL;
235   g_object_get (sheet, "data-model", &data_store, NULL);
236
237
238   gint width = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (data_store));
239   gint length = psppire_data_store_get_case_count (data_store);
240
241
242   gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
243   gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, whole_row_selected);
244
245   gboolean whole_column_selected =
246     (range->start_y == 0 && range->end_y == length - 1);
247   gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item,
248                             whole_column_selected);
249   gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item,
250                             whole_column_selected);
251   gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item,
252                             whole_column_selected);
253 }
254
255 static void
256 delete_variables (PsppireDataSheet *sheet)
257 {
258   SswRange *range = SSW_SHEET(sheet)->selection;
259
260   PsppireDataStore *data_store = NULL;
261   g_object_get (sheet, "data-model", &data_store, NULL);
262
263   psppire_dict_delete_variables (data_store->dict, range->start_x,
264                                  (range->end_x - range->start_x + 1));
265
266   gtk_widget_queue_draw (GTK_WIDGET (sheet));
267 }
268
269
270
271 static GtkWidget *
272 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
273 {
274   GtkWidget *menu = gtk_menu_new ();
275
276   GtkWidget *item =
277     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
278   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
279                             sheet);
280   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
281
282   item = gtk_separator_menu_item_new ();
283   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
284
285   sheet->data_clear_variables_menu_item =
286     gtk_menu_item_new_with_mnemonic  (_("Cl_ear Variables"));
287   g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
288                             G_CALLBACK (delete_variables),
289                             sheet);
290   gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
291   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
292
293   item = gtk_separator_menu_item_new ();
294   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
295
296
297   sheet->data_sort_ascending_menu_item =
298     gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
299   g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
300                             G_CALLBACK (sort_ascending), sheet);
301   gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
302   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
303
304   sheet->data_sort_descending_menu_item =
305     gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
306   g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
307                             G_CALLBACK (sort_descending), sheet);
308   gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
309   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
310
311   gtk_widget_show_all (menu);
312   return menu;
313 }
314
315
316 \f
317
318 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
319
320 static GObjectClass * parent_class = NULL;
321 static gboolean dispose_has_run = FALSE;
322
323 static void
324 psppire_data_sheet_dispose (GObject *obj)
325 {
326   //  PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
327
328   if (dispose_has_run)
329     return;
330
331   dispose_has_run = TRUE;
332
333   /* Chain up to the parent class */
334   G_OBJECT_CLASS (parent_class)->dispose (obj);
335 }
336
337 static void
338 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
339 {
340   GObjectClass *object_class = G_OBJECT_CLASS (class);
341   object_class->dispose = psppire_data_sheet_dispose;
342
343   parent_class = g_type_class_peek_parent (class);
344 }
345
346 GtkWidget*
347 psppire_data_sheet_new (void)
348 {
349   GObject *obj =
350     g_object_new (PSPPIRE_TYPE_DATA_SHEET,
351                   "forward-conversion", psppire_data_store_value_to_string,
352                   "reverse-conversion", myreversefunc,
353                   NULL);
354
355   return GTK_WIDGET (obj);
356 }
357
358 static void
359 set_dictionary (PsppireDataSheet *sheet)
360 {
361   GtkTreeModel *data_model = NULL;
362   g_object_get (sheet, "data-model", &data_model, NULL);
363
364   PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
365   g_object_set (sheet, "hmodel", store->dict, NULL);
366 }
367
368 static void
369 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
370 {
371   PsppireDataStore *data_store = NULL;
372   g_object_get (sheet, "data-model", &data_store, NULL);
373
374   if (data_store == NULL)
375     return;
376
377   PsppireDict *dict = data_store->dict;
378   struct variable *var = psppire_dict_get_variable (dict, from);
379
380   if (var == NULL)
381     return;
382   gint new_pos = to;
383   /* The index refers to the final position, so if the source
384      is less than the destination, then we must subtract 1, to
385      account for the position vacated by the source */
386   if (from < to)
387     new_pos--;
388   dict_reorder_var (dict->dict, var, new_pos);
389 }
390
391 static void
392 psppire_data_sheet_init (PsppireDataSheet *sheet)
393 {
394   sheet->data_sheet_cases_column_popup =
395     create_data_column_header_popup_menu (sheet);
396
397   sheet->data_sheet_cases_row_popup =
398     create_data_row_header_popup_menu (sheet);
399
400   g_signal_connect (sheet, "selection-changed",
401                     G_CALLBACK (set_menu_items_sensitivity), sheet);
402
403   g_signal_connect (sheet, "column-header-pressed",
404                     G_CALLBACK (show_cases_column_popup), sheet);
405
406   g_signal_connect (sheet, "row-header-pressed",
407                     G_CALLBACK (show_cases_row_popup), sheet);
408
409   g_signal_connect (sheet, "value-changed",
410                     G_CALLBACK (change_data_value), NULL);
411
412   g_signal_connect (sheet, "notify::data-model",
413                     G_CALLBACK (set_dictionary), NULL);
414
415   g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);
416 }