Initialise parent class in sheets
[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   JmdRange *range = JMD_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 (JmdSheet *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   JmdRange *range = JMD_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   JmdRange *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   JmdRange *range = JMD_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, JMD_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
366 static void
367 psppire_data_sheet_init (PsppireDataSheet *sheet)
368 {
369   sheet->data_sheet_cases_column_popup =
370     create_data_column_header_popup_menu (sheet);
371
372   sheet->data_sheet_cases_row_popup =
373     create_data_row_header_popup_menu (sheet);
374
375   g_signal_connect (sheet, "selection-changed",
376                     G_CALLBACK (set_menu_items_sensitivity), sheet);
377
378   g_signal_connect (sheet, "column-header-pressed",
379                     G_CALLBACK (show_cases_column_popup), sheet);
380
381   g_signal_connect (sheet, "row-header-pressed",
382                     G_CALLBACK (show_cases_row_popup), sheet);
383
384   g_signal_connect (sheet, "value-changed",
385                     G_CALLBACK (change_data_value), NULL);
386 }