1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2017, 2019 John Darrington
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.
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.
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/>.
19 #include "psppire-data-sheet.h"
23 #define _(msgid) gettext (msgid)
26 #include "value-variant.h"
28 #include "ui/gui/executor.h"
29 #include "psppire-data-window.h"
30 #include "ssw-axis-model.h"
33 do_sort (PsppireDataSheet *sheet, GtkSortType order)
35 SswRange *range = SSW_SHEET(sheet)->selection;
37 PsppireDataStore *data_store = NULL;
38 g_object_get (sheet, "data-model", &data_store, NULL);
43 PsppireDataWindow *pdw =
44 psppire_data_window_for_data_store (data_store);
46 GString *syntax = g_string_new ("SORT CASES BY");
47 for (i = range->start_x ; i <= range->end_x; ++i)
49 const struct variable *var = psppire_dict_get_variable (data_store->dict, i);
52 g_string_append_printf (syntax, " %s", var_get_name (var));
58 if (order == GTK_SORT_DESCENDING)
59 g_string_append (syntax, " (DOWN)");
60 g_string_append_c (syntax, '.');
61 execute_const_syntax_string (pdw, syntax->str);
63 g_string_free (syntax, TRUE);
68 sort_ascending (PsppireDataSheet *sheet)
70 do_sort (sheet, GTK_SORT_ASCENDING);
72 gtk_widget_queue_draw (GTK_WIDGET (sheet));
76 sort_descending (PsppireDataSheet *sheet)
78 do_sort (sheet, GTK_SORT_DESCENDING);
80 gtk_widget_queue_draw (GTK_WIDGET (sheet));
86 change_data_value (PsppireDataSheet *sheet, gint col, gint row, GValue *value)
88 PsppireDataStore *store = NULL;
89 g_object_get (sheet, "data-model", &store, NULL);
91 const struct variable *var = psppire_dict_get_variable (store->dict, col);
98 GVariant *vrnt = g_value_get_variant (value);
100 value_variant_get (&v, vrnt);
102 psppire_data_store_set_value (store, row, var, &v);
104 value_destroy_from_variant (&v, vrnt);
110 show_cases_row_popup (PsppireDataSheet *sheet, int row,
111 guint button, guint state, gpointer p)
113 GListModel *vmodel = NULL;
114 g_object_get (sheet, "vmodel", &vmodel, NULL);
118 guint n_items = g_list_model_get_n_items (vmodel);
126 g_object_set_data (G_OBJECT (sheet->data_sheet_cases_row_popup), "item",
127 GINT_TO_POINTER (row));
129 gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_row_popup), NULL);
134 insert_new_case (PsppireDataSheet *sheet)
136 PsppireDataStore *data_store = NULL;
137 g_object_get (sheet, "data-model", &data_store, NULL);
139 gint posn = GPOINTER_TO_INT (g_object_get_data
140 (G_OBJECT (sheet->data_sheet_cases_row_popup), "item"));
142 psppire_data_store_insert_new_case (data_store, posn);
144 gtk_widget_queue_draw (GTK_WIDGET (sheet));
148 delete_cases (PsppireDataSheet *sheet)
150 SswRange *range = SSW_SHEET(sheet)->selection;
152 PsppireDataStore *data_store = NULL;
153 g_object_get (sheet, "data-model", &data_store, NULL);
155 psppire_data_store_delete_cases (data_store, range->start_y,
156 range->end_y - range->start_y + 1);
158 gtk_widget_queue_draw (GTK_WIDGET (sheet));
162 create_data_row_header_popup_menu (PsppireDataSheet *sheet)
164 GtkWidget *menu = gtk_menu_new ();
167 gtk_menu_item_new_with_mnemonic (_("_Insert Case"));
169 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), sheet);
170 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
172 item = gtk_separator_menu_item_new ();
173 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
175 sheet->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
176 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, FALSE);
177 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_cases_menu_item);
178 g_signal_connect_swapped (sheet->data_clear_cases_menu_item, "activate",
179 G_CALLBACK (delete_cases), sheet);
181 gtk_widget_show_all (menu);
187 show_cases_column_popup (PsppireDataSheet *sheet, int column, guint button, guint state,
190 GListModel *hmodel = NULL;
191 g_object_get (sheet, "hmodel", &hmodel, NULL);
195 guint n_items = g_list_model_get_n_items (hmodel);
197 if (column >= n_items)
203 g_object_set_data (G_OBJECT (sheet->data_sheet_cases_column_popup), "item",
204 GINT_TO_POINTER (column));
206 gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_column_popup), NULL);
209 /* Insert a new variable before the variable at POSN. */
211 psppire_data_sheet_insert_new_variable_at_posn (PsppireDataSheet *sheet,
214 PsppireDataStore *data_store = NULL;
215 g_object_get (sheet, "data-model", &data_store, NULL);
217 const struct variable *v = psppire_dict_insert_variable (data_store->dict,
220 psppire_data_store_insert_value (data_store, var_get_width(v),
221 var_get_case_index (v));
223 ssw_sheet_scroll_to (SSW_SHEET (sheet), posn, -1);
225 gtk_widget_queue_draw (GTK_WIDGET (sheet));
229 insert_new_variable (PsppireDataSheet *sheet)
231 PsppireDataStore *data_store = NULL;
232 g_object_get (sheet, "data-model", &data_store, NULL);
234 gint posn = GPOINTER_TO_INT (g_object_get_data
235 (G_OBJECT (sheet->data_sheet_cases_column_popup),
238 psppire_data_sheet_insert_new_variable_at_posn (sheet, posn);
243 set_menu_items_sensitivity (PsppireDataSheet *sheet, gpointer selection, gpointer p)
245 SswRange *range = selection;
247 PsppireDataStore *data_store = NULL;
248 g_object_get (sheet, "data-model", &data_store, NULL);
251 gint width = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (data_store));
252 gint length = psppire_data_store_get_case_count (data_store);
255 gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
256 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, whole_row_selected);
258 gboolean whole_column_selected =
259 (range->start_y == 0 && range->end_y == length - 1);
260 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item,
261 whole_column_selected);
262 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item,
263 whole_column_selected);
264 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item,
265 whole_column_selected);
269 psppire_data_sheet_delete_variables (PsppireDataSheet *sheet)
271 SswRange *range = SSW_SHEET(sheet)->selection;
273 PsppireDataStore *data_store = NULL;
274 g_object_get (sheet, "data-model", &data_store, NULL);
276 psppire_dict_delete_variables (data_store->dict, range->start_x,
277 (range->end_x - range->start_x + 1));
279 ssw_sheet_scroll_to (SSW_SHEET (sheet), range->start_x, -1);
281 gtk_widget_queue_draw (GTK_WIDGET (sheet));
287 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
289 GtkWidget *menu = gtk_menu_new ();
292 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
293 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
295 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
297 item = gtk_separator_menu_item_new ();
298 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
300 sheet->data_clear_variables_menu_item =
301 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
302 g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
303 G_CALLBACK (psppire_data_sheet_delete_variables),
305 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
306 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
308 item = gtk_separator_menu_item_new ();
309 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
312 sheet->data_sort_ascending_menu_item =
313 gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
314 g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
315 G_CALLBACK (sort_ascending), sheet);
316 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
317 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
319 sheet->data_sort_descending_menu_item =
320 gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
321 g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
322 G_CALLBACK (sort_descending), sheet);
323 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
324 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
326 gtk_widget_show_all (menu);
333 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
335 static GObjectClass * parent_class = NULL;
336 static gboolean dispose_has_run = FALSE;
339 psppire_data_sheet_dispose (GObject *obj)
341 // PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
346 dispose_has_run = TRUE;
348 /* Chain up to the parent class */
349 G_OBJECT_CLASS (parent_class)->dispose (obj);
353 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
355 GObjectClass *object_class = G_OBJECT_CLASS (class);
356 object_class->dispose = psppire_data_sheet_dispose;
358 parent_class = g_type_class_peek_parent (class);
362 psppire_data_sheet_new (void)
365 g_object_new (PSPPIRE_TYPE_DATA_SHEET,
366 "forward-conversion", psppire_data_store_value_to_string,
367 "reverse-conversion", psppire_data_store_string_to_value,
369 "horizontal-draggable", TRUE,
372 return GTK_WIDGET (obj);
377 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
379 guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
381 if (!psppire_data_store_filtered (store, row))
384 /* Draw a diagonal line through the widget */
385 guint width = gtk_widget_get_allocated_width (widget);
386 guint height = gtk_widget_get_allocated_height (widget);
388 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
389 gtk_render_line (sc, cr, 0, 0, width, height);
395 button_post_create (GtkWidget *button, guint i, gpointer user_data)
397 PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
399 g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
400 g_signal_connect_after (button, "draw", G_CALLBACK (indicate_filtered_case), data_store);
405 resize_display_width (PsppireDict *dict, gint pos, gint size, gpointer user_data)
410 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (user_data);
411 PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (sheet));
412 PangoLayout *layout = pango_layout_new (context);
415 pango_layout_set_text (layout, "M", 1);
416 pango_layout_get_extents (layout, NULL, &rect);
418 gdouble width_of_M = rect.width / (gdouble) PANGO_SCALE;
420 g_object_unref (G_OBJECT (layout));
421 g_object_unref (G_OBJECT (context));
423 gint Ms = round ((size / width_of_M) - 0.25);
424 struct variable *var = psppire_dict_get_variable (dict, pos);
425 g_return_val_if_fail (var, TRUE);
426 var_set_display_width (var, Ms);
431 set_dictionary (PsppireDataSheet *sheet)
433 GtkTreeModel *data_model = NULL;
434 g_object_get (sheet, "data-model", &data_model, NULL);
436 PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
437 g_object_set (sheet, "hmodel", store->dict, NULL);
439 g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
442 SswAxisModel *vmodel = NULL;
443 g_object_get (sheet, "vmodel", &vmodel, NULL);
444 g_assert (SSW_IS_AXIS_MODEL (vmodel));
446 g_object_set (vmodel,
447 "post-button-create-func", button_post_create,
448 "post-button-create-func-data", store,
453 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
455 PsppireDataStore *data_store = NULL;
456 g_object_get (sheet, "data-model", &data_store, NULL);
458 if (data_store == NULL)
461 PsppireDict *dict = data_store->dict;
462 struct variable *var = psppire_dict_get_variable (dict, from);
467 /* The index refers to the final position, so if the source
468 is less than the destination, then we must subtract 1, to
469 account for the position vacated by the source */
472 dict_reorder_var (dict->dict, var, new_pos);
476 psppire_data_sheet_init (PsppireDataSheet *sheet)
478 sheet->data_sheet_cases_column_popup =
479 create_data_column_header_popup_menu (sheet);
481 sheet->data_sheet_cases_row_popup =
482 create_data_row_header_popup_menu (sheet);
484 g_signal_connect (sheet, "selection-changed",
485 G_CALLBACK (set_menu_items_sensitivity), sheet);
487 g_signal_connect (sheet, "column-header-pressed",
488 G_CALLBACK (show_cases_column_popup), sheet);
490 g_signal_connect (sheet, "row-header-pressed",
491 G_CALLBACK (show_cases_row_popup), sheet);
493 g_signal_connect (sheet, "value-changed",
494 G_CALLBACK (change_data_value), NULL);
496 g_signal_connect (sheet, "notify::data-model",
497 G_CALLBACK (set_dictionary), NULL);
499 g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);