1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2017, 2019, 2020 Free Software Foundation
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"
34 do_sort (PsppireDataSheet *sheet, GtkSortType order)
36 SswRange *range = SSW_SHEET(sheet)->selection;
38 PsppireDataStore *data_store = NULL;
39 g_object_get (sheet, "data-model", &data_store, NULL);
44 PsppireDataWindow *pdw =
45 psppire_data_window_for_data_store (data_store);
47 GString *syntax = g_string_new ("SORT CASES BY");
48 for (i = range->start_x ; i <= range->end_x; ++i)
50 const struct variable *var = psppire_dict_get_variable (data_store->dict, i);
53 g_string_append_printf (syntax, " %s", var_get_name (var));
59 if (order == GTK_SORT_DESCENDING)
60 g_string_append (syntax, " (DOWN)");
61 g_string_append_c (syntax, '.');
62 execute_const_syntax_string (pdw, syntax->str);
64 g_string_free (syntax, TRUE);
69 sort_ascending (PsppireDataSheet *sheet)
71 do_sort (sheet, GTK_SORT_ASCENDING);
73 gtk_widget_queue_draw (GTK_WIDGET (sheet));
77 sort_descending (PsppireDataSheet *sheet)
79 do_sort (sheet, GTK_SORT_DESCENDING);
81 gtk_widget_queue_draw (GTK_WIDGET (sheet));
87 change_data_value (PsppireDataSheet *sheet, gint col, gint row, GValue *value)
89 PsppireDataStore *store = NULL;
90 g_object_get (sheet, "data-model", &store, NULL);
92 const struct variable *var = psppire_dict_get_variable (store->dict, col);
99 GVariant *vrnt = g_value_get_variant (value);
101 value_variant_get (&v, vrnt);
103 psppire_data_store_set_value (store, row, var, &v);
105 value_destroy_from_variant (&v, vrnt);
111 show_cases_row_popup (PsppireDataSheet *sheet, int row,
112 guint button, guint state, gpointer p)
114 GListModel *vmodel = NULL;
115 g_object_get (sheet, "vmodel", &vmodel, NULL);
119 guint n_items = g_list_model_get_n_items (vmodel);
127 g_object_set_data (G_OBJECT (sheet->data_sheet_cases_row_popup), "item",
128 GINT_TO_POINTER (row));
130 gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_row_popup), NULL);
135 insert_new_case (PsppireDataSheet *sheet)
137 PsppireDataStore *data_store = NULL;
138 g_object_get (sheet, "data-model", &data_store, NULL);
140 gint posn = GPOINTER_TO_INT (g_object_get_data
141 (G_OBJECT (sheet->data_sheet_cases_row_popup), "item"));
143 psppire_data_store_insert_new_case (data_store, posn);
145 gtk_widget_queue_draw (GTK_WIDGET (sheet));
149 delete_cases (PsppireDataSheet *sheet)
151 SswRange *range = SSW_SHEET(sheet)->selection;
153 PsppireDataStore *data_store = NULL;
154 g_object_get (sheet, "data-model", &data_store, NULL);
156 psppire_data_store_delete_cases (data_store, range->start_y,
157 range->end_y - range->start_y + 1);
159 gtk_widget_queue_draw (GTK_WIDGET (sheet));
163 create_data_row_header_popup_menu (PsppireDataSheet *sheet)
165 GtkWidget *menu = gtk_menu_new ();
167 /* gtk_menu_shell_append does not sink/ref this object,
168 so we must do it ourselves (and remember to unref it). */
169 g_object_ref_sink (menu);
172 gtk_menu_item_new_with_mnemonic (_("_Insert Case"));
174 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), sheet);
175 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
177 item = gtk_separator_menu_item_new ();
178 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
180 sheet->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
181 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, FALSE);
182 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_cases_menu_item);
183 g_signal_connect_swapped (sheet->data_clear_cases_menu_item, "activate",
184 G_CALLBACK (delete_cases), sheet);
186 gtk_widget_show_all (menu);
192 show_cases_column_popup (PsppireDataSheet *sheet, int column, guint button, guint state,
195 GListModel *hmodel = NULL;
196 g_object_get (sheet, "hmodel", &hmodel, NULL);
200 guint n_items = g_list_model_get_n_items (hmodel);
202 if (column >= n_items)
208 g_object_set_data (G_OBJECT (sheet->data_sheet_cases_column_popup), "item",
209 GINT_TO_POINTER (column));
211 gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_column_popup), NULL);
214 /* Insert a new variable before the variable at POSN. */
216 psppire_data_sheet_insert_new_variable_at_posn (PsppireDataSheet *sheet,
219 PsppireDataStore *data_store = NULL;
220 g_object_get (sheet, "data-model", &data_store, NULL);
222 const struct variable *v = psppire_dict_insert_variable (data_store->dict,
225 psppire_data_store_insert_value (data_store, var_get_width(v),
226 var_get_dict_index (v));
228 ssw_sheet_scroll_to (SSW_SHEET (sheet), posn, -1);
230 gtk_widget_queue_draw (GTK_WIDGET (sheet));
234 insert_new_variable (PsppireDataSheet *sheet)
236 PsppireDataStore *data_store = NULL;
237 g_object_get (sheet, "data-model", &data_store, NULL);
239 gint posn = GPOINTER_TO_INT (g_object_get_data
240 (G_OBJECT (sheet->data_sheet_cases_column_popup),
243 psppire_data_sheet_insert_new_variable_at_posn (sheet, posn);
248 set_menu_items_sensitivity (PsppireDataSheet *sheet, gpointer selection, gpointer p)
250 SswRange *range = selection;
252 PsppireDataStore *data_store = NULL;
253 g_object_get (sheet, "data-model", &data_store, NULL);
256 gint width = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (data_store));
257 gint length = psppire_data_store_get_case_count (data_store);
260 gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
261 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, whole_row_selected);
263 gboolean whole_column_selected =
264 (range->start_y == 0 && range->end_y == length - 1);
265 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item,
266 whole_column_selected);
267 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item,
268 whole_column_selected);
269 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item,
270 whole_column_selected);
274 psppire_data_sheet_delete_variables (PsppireDataSheet *sheet)
276 SswRange *range = SSW_SHEET(sheet)->selection;
278 PsppireDataStore *data_store = NULL;
279 g_object_get (sheet, "data-model", &data_store, NULL);
281 if (range->start_x > range->end_x)
283 gint temp = range->start_x;
284 range->start_x = range->end_x;
288 psppire_dict_delete_variables (data_store->dict, range->start_x,
289 (range->end_x - range->start_x + 1));
291 ssw_sheet_scroll_to (SSW_SHEET (sheet), range->start_x, -1);
293 gtk_widget_queue_draw (GTK_WIDGET (sheet));
297 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
299 GtkWidget *menu = gtk_menu_new ();
301 /* gtk_menu_shell_append does not sink/ref this object,
302 so we must do it ourselves (and remember to unref it). */
303 g_object_ref_sink (menu);
306 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
307 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
309 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
311 item = gtk_separator_menu_item_new ();
312 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
314 sheet->data_clear_variables_menu_item =
315 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
316 g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
317 G_CALLBACK (psppire_data_sheet_delete_variables),
319 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
320 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
322 item = gtk_separator_menu_item_new ();
323 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
326 sheet->data_sort_ascending_menu_item =
327 gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
328 g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
329 G_CALLBACK (sort_ascending), sheet);
330 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
331 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
333 sheet->data_sort_descending_menu_item =
334 gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
335 g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
336 G_CALLBACK (sort_descending), sheet);
337 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
338 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
340 gtk_widget_show_all (menu);
347 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
349 static GObjectClass * parent_class = NULL;
350 static gboolean dispose_has_run = FALSE;
353 psppire_data_sheet_finalize (GObject *obj)
355 /* Chain up to the parent class */
356 G_OBJECT_CLASS (parent_class)->finalize (obj);
360 psppire_data_sheet_dispose (GObject *obj)
362 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
367 dispose_has_run = TRUE;
369 g_object_unref (sheet->data_sheet_cases_column_popup);
370 g_object_unref (sheet->data_sheet_cases_row_popup);
372 /* Chain up to the parent class */
373 G_OBJECT_CLASS (parent_class)->dispose (obj);
378 psppire_data_sheet_realize (GtkWidget *widget)
380 g_object_set (widget,
381 "forward-conversion", psppire_data_store_value_to_string,
382 "reverse-conversion", psppire_data_store_string_to_value,
384 "horizontal-draggable", TRUE,
387 /* Chain up to the parent class */
388 GTK_WIDGET_CLASS (parent_class)->realize (widget);
392 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
394 GObjectClass *object_class = G_OBJECT_CLASS (class);
395 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
397 widget_class->realize = psppire_data_sheet_realize;
398 object_class->dispose = psppire_data_sheet_dispose;
399 object_class->finalize = psppire_data_sheet_finalize;
401 parent_class = g_type_class_peek_parent (class);
405 psppire_data_sheet_new (void)
407 return g_object_new (PSPPIRE_TYPE_DATA_SHEET, NULL);
412 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
414 guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
416 if (!psppire_data_store_filtered (store, row))
419 /* Draw a diagonal line through the widget */
420 guint width = gtk_widget_get_allocated_width (widget);
421 guint height = gtk_widget_get_allocated_height (widget);
423 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
424 gtk_render_line (sc, cr, 0, 0, width, height);
430 button_post_create (GtkWidget *button, guint i, gpointer user_data)
432 PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
434 g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
435 g_signal_connect_after (button, "draw", G_CALLBACK (indicate_filtered_case), data_store);
439 resize_display_width (PsppireDict *dict, gint pos, gint size, gpointer user_data)
444 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (user_data);
445 gdouble wm = width_of_m (GTK_WIDGET (sheet));
447 gint Ms = round ((size / wm) - 0.25);
448 struct variable *var = psppire_dict_get_variable (dict, pos);
449 g_return_val_if_fail (var, TRUE);
450 var_set_display_width (var, Ms);
455 set_dictionary (PsppireDataSheet *sheet)
457 GtkTreeModel *data_model = NULL;
458 g_object_get (sheet, "data-model", &data_model, NULL);
460 g_return_if_fail (data_model);
462 PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
463 g_object_set (sheet, "hmodel", store->dict, NULL);
465 g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
468 SswAxisModel *vmodel = NULL;
469 g_object_get (sheet, "vmodel", &vmodel, NULL);
470 g_assert (SSW_IS_AXIS_MODEL (vmodel));
472 g_object_set (vmodel,
473 "post-button-create-func", button_post_create,
474 "post-button-create-func-data", store,
479 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
481 PsppireDataStore *data_store = NULL;
482 g_object_get (sheet, "data-model", &data_store, NULL);
484 if (data_store == NULL)
487 PsppireDict *dict = data_store->dict;
488 struct variable *var = psppire_dict_get_variable (dict, from);
493 /* The index refers to the final position, so if the source
494 is less than the destination, then we must subtract 1, to
495 account for the position vacated by the source */
498 dict_reorder_var (dict->dict, var, new_pos);
502 psppire_data_sheet_init (PsppireDataSheet *sheet)
504 sheet->data_sheet_cases_column_popup =
505 create_data_column_header_popup_menu (sheet);
507 sheet->data_sheet_cases_row_popup =
508 create_data_row_header_popup_menu (sheet);
510 g_signal_connect (sheet, "selection-changed",
511 G_CALLBACK (set_menu_items_sensitivity), sheet);
513 g_signal_connect (sheet, "column-header-pressed",
514 G_CALLBACK (show_cases_column_popup), sheet);
516 g_signal_connect (sheet, "row-header-pressed",
517 G_CALLBACK (show_cases_row_popup), sheet);
519 g_signal_connect (sheet, "value-changed",
520 G_CALLBACK (change_data_value), NULL);
522 g_signal_connect (sheet, "notify::data-model",
523 G_CALLBACK (set_dictionary), NULL);
525 g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);