Merge remote-tracking branch 'origin/master' into sheet
[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 static gchar *
106 data_store_value_to_string (SswSheet *data_sheet, PsppireDataStore *store, gint col, gint row, const GValue *v)
107 {
108   return psppire_data_store_value_to_string (store, col, row, v);
109 }
110
111 gboolean myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in,
112                     GValue *out);
113
114
115 \f
116
117 static void
118 show_cases_row_popup (PsppireDataSheet *sheet, int row,
119                       uint button, uint state, gpointer p)
120 {
121   GListModel *vmodel = NULL;
122   g_object_get (sheet, "vmodel", &vmodel, NULL);
123   if (vmodel == NULL)
124     return;
125
126   guint n_items = g_list_model_get_n_items (vmodel);
127
128   if (row >= n_items)
129     return;
130
131   if (button != 3)
132     return;
133
134   g_object_set_data (G_OBJECT (sheet->data_sheet_cases_row_popup), "item",
135                      GINT_TO_POINTER (row));
136
137   gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_row_popup), NULL);
138 }
139
140
141 static void
142 insert_new_case (PsppireDataSheet *sheet)
143 {
144   PsppireDataStore *data_store = NULL;
145   g_object_get (sheet, "data-model", &data_store, NULL);
146
147   gint posn = GPOINTER_TO_INT (g_object_get_data
148                                 (G_OBJECT (sheet->data_sheet_cases_row_popup), "item"));
149
150   psppire_data_store_insert_new_case (data_store, posn);
151
152   gtk_widget_queue_draw (GTK_WIDGET (sheet));
153 }
154
155 static void
156 delete_cases (PsppireDataSheet *sheet)
157 {
158   SswRange *range = SSW_SHEET(sheet)->selection;
159
160   PsppireDataStore *data_store = NULL;
161   g_object_get (sheet, "data-model", &data_store, NULL);
162
163   psppire_data_store_delete_cases (data_store, range->start_y,
164                                    range->end_y - range->start_y + 1);
165
166   gtk_widget_queue_draw (GTK_WIDGET (sheet));
167 }
168
169 static GtkWidget *
170 create_data_row_header_popup_menu (PsppireDataSheet *sheet)
171 {
172   GtkWidget *menu = gtk_menu_new ();
173
174   GtkWidget *item =
175     gtk_menu_item_new_with_mnemonic  (_("_Insert Case"));
176
177   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), sheet);
178   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
179
180   item = gtk_separator_menu_item_new ();
181   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
182
183   sheet->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
184   gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, FALSE);
185   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_cases_menu_item);
186   g_signal_connect_swapped (sheet->data_clear_cases_menu_item, "activate",
187                             G_CALLBACK (delete_cases), sheet);
188
189   gtk_widget_show_all (menu);
190   return menu;
191 }
192
193
194 static void
195 show_cases_column_popup (PsppireDataSheet *sheet, int column, uint button, uint state,
196                          gpointer p)
197 {
198   GListModel *hmodel = NULL;
199   g_object_get (sheet, "hmodel", &hmodel, NULL);
200   if (hmodel == NULL)
201     return;
202
203   guint n_items = g_list_model_get_n_items (hmodel);
204
205   if (column >= n_items)
206     return;
207
208   if (button != 3)
209     return;
210
211   g_object_set_data (G_OBJECT (sheet->data_sheet_cases_column_popup), "item",
212                      GINT_TO_POINTER (column));
213
214   gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_column_popup), NULL);
215 }
216
217 static void
218 insert_new_variable (PsppireDataSheet *sheet)
219 {
220   PsppireDataStore *data_store = NULL;
221   g_object_get (sheet, "data-model", &data_store, NULL);
222
223   gint posn = GPOINTER_TO_INT (g_object_get_data
224                                 (G_OBJECT (sheet->data_sheet_cases_column_popup),
225                                  "item"));
226
227   const struct variable *v = psppire_dict_insert_variable (data_store->dict,
228                                                            posn, NULL);
229
230   psppire_data_store_insert_value (data_store, var_get_width(v),
231                                    var_get_case_index (v));
232
233   gtk_widget_queue_draw (GTK_WIDGET (sheet));
234 }
235
236 static void
237 set_menu_items_sensitivity (PsppireDataSheet *sheet, gpointer selection, gpointer p)
238 {
239   SswRange *range = selection;
240
241   PsppireDataStore *data_store = NULL;
242   g_object_get (sheet, "data-model", &data_store, NULL);
243
244
245   gint width = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (data_store));
246   gint length = psppire_data_store_get_case_count (data_store);
247
248
249   gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
250   gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, whole_row_selected);
251
252   gboolean whole_column_selected =
253     (range->start_y == 0 && range->end_y == length - 1);
254   gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item,
255                             whole_column_selected);
256   gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item,
257                             whole_column_selected);
258   gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item,
259                             whole_column_selected);
260 }
261
262 static void
263 delete_variables (PsppireDataSheet *sheet)
264 {
265   SswRange *range = SSW_SHEET(sheet)->selection;
266
267   PsppireDataStore *data_store = NULL;
268   g_object_get (sheet, "data-model", &data_store, NULL);
269
270   psppire_dict_delete_variables (data_store->dict, range->start_x,
271                                  (range->end_x - range->start_x + 1));
272
273   gtk_widget_queue_draw (GTK_WIDGET (sheet));
274 }
275
276
277
278 static GtkWidget *
279 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
280 {
281   GtkWidget *menu = gtk_menu_new ();
282
283   GtkWidget *item =
284     gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
285   g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
286                             sheet);
287   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
288
289   item = gtk_separator_menu_item_new ();
290   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
291
292   sheet->data_clear_variables_menu_item =
293     gtk_menu_item_new_with_mnemonic  (_("Cl_ear Variables"));
294   g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
295                             G_CALLBACK (delete_variables),
296                             sheet);
297   gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
298   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
299
300   item = gtk_separator_menu_item_new ();
301   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
302
303
304   sheet->data_sort_ascending_menu_item =
305     gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
306   g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
307                             G_CALLBACK (sort_ascending), sheet);
308   gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
309   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
310
311   sheet->data_sort_descending_menu_item =
312     gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
313   g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
314                             G_CALLBACK (sort_descending), sheet);
315   gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
316   gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
317
318   gtk_widget_show_all (menu);
319   return menu;
320 }
321
322
323 \f
324
325 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
326
327 static GObjectClass * parent_class = NULL;
328 static gboolean dispose_has_run = FALSE;
329
330 static void
331 psppire_data_sheet_dispose (GObject *obj)
332 {
333   //  PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
334
335   if (dispose_has_run)
336     return;
337
338   dispose_has_run = TRUE;
339
340   /* Chain up to the parent class */
341   G_OBJECT_CLASS (parent_class)->dispose (obj);
342 }
343
344 static void
345 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
346 {
347   GObjectClass *object_class = G_OBJECT_CLASS (class);
348   object_class->dispose = psppire_data_sheet_dispose;
349
350   parent_class = g_type_class_peek_parent (class);
351 }
352
353 GtkWidget*
354 psppire_data_sheet_new (void)
355 {
356   GObject *obj =
357     g_object_new (PSPPIRE_TYPE_DATA_SHEET,
358                   "forward-conversion", data_store_value_to_string,
359                   "reverse-conversion", myreversefunc,
360                   NULL);
361
362   return GTK_WIDGET (obj);
363 }
364
365 static void
366 set_dictionary (PsppireDataSheet *sheet)
367 {
368   GtkTreeModel *data_model = NULL;
369   g_object_get (sheet, "data-model", &data_model, NULL);
370
371   PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
372   g_object_set (sheet, "hmodel", store->dict, NULL);
373 }
374
375 static void
376 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
377 {
378   PsppireDataStore *data_store = NULL;
379   g_object_get (sheet, "data-model", &data_store, NULL);
380
381   if (data_store == NULL)
382     return;
383
384   PsppireDict *dict = data_store->dict;
385   struct variable *var = psppire_dict_get_variable (dict, from);
386
387   if (var == NULL)
388     return;
389   gint new_pos = to;
390   /* The index refers to the final position, so if the source
391      is less than the destination, then we must subtract 1, to
392      account for the position vacated by the source */
393   if (from < to)
394     new_pos--;
395   dict_reorder_var (dict->dict, var, new_pos);
396 }
397
398 static void
399 psppire_data_sheet_init (PsppireDataSheet *sheet)
400 {
401   sheet->data_sheet_cases_column_popup =
402     create_data_column_header_popup_menu (sheet);
403
404   sheet->data_sheet_cases_row_popup =
405     create_data_row_header_popup_menu (sheet);
406
407   g_signal_connect (sheet, "selection-changed",
408                     G_CALLBACK (set_menu_items_sensitivity), sheet);
409
410   g_signal_connect (sheet, "column-header-pressed",
411                     G_CALLBACK (show_cases_column_popup), sheet);
412
413   g_signal_connect (sheet, "row-header-pressed",
414                     G_CALLBACK (show_cases_row_popup), sheet);
415
416   g_signal_connect (sheet, "value-changed",
417                     G_CALLBACK (change_data_value), NULL);
418
419   g_signal_connect (sheet, "notify::data-model",
420                     G_CALLBACK (set_dictionary), NULL);
421
422   g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);
423 }