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"
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 ();
166 /* gtk_menu_shell_append does not sink/ref this object,
167 so we must do it ourselves (and remember to unref it). */
168 g_object_ref_sink (menu);
171 gtk_menu_item_new_with_mnemonic (_("_Insert Case"));
173 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), sheet);
174 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
176 item = gtk_separator_menu_item_new ();
177 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
179 sheet->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
180 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, FALSE);
181 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_cases_menu_item);
182 g_signal_connect_swapped (sheet->data_clear_cases_menu_item, "activate",
183 G_CALLBACK (delete_cases), sheet);
185 gtk_widget_show_all (menu);
191 show_cases_column_popup (PsppireDataSheet *sheet, int column, guint button, guint state,
194 GListModel *hmodel = NULL;
195 g_object_get (sheet, "hmodel", &hmodel, NULL);
199 guint n_items = g_list_model_get_n_items (hmodel);
201 if (column >= n_items)
207 g_object_set_data (G_OBJECT (sheet->data_sheet_cases_column_popup), "item",
208 GINT_TO_POINTER (column));
210 gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_column_popup), NULL);
213 /* Insert a new variable before the variable at POSN. */
215 psppire_data_sheet_insert_new_variable_at_posn (PsppireDataSheet *sheet,
218 PsppireDataStore *data_store = NULL;
219 g_object_get (sheet, "data-model", &data_store, NULL);
221 const struct variable *v = psppire_dict_insert_variable (data_store->dict,
224 psppire_data_store_insert_value (data_store, var_get_width(v),
225 var_get_case_index (v));
227 ssw_sheet_scroll_to (SSW_SHEET (sheet), posn, -1);
229 gtk_widget_queue_draw (GTK_WIDGET (sheet));
233 insert_new_variable (PsppireDataSheet *sheet)
235 PsppireDataStore *data_store = NULL;
236 g_object_get (sheet, "data-model", &data_store, NULL);
238 gint posn = GPOINTER_TO_INT (g_object_get_data
239 (G_OBJECT (sheet->data_sheet_cases_column_popup),
242 psppire_data_sheet_insert_new_variable_at_posn (sheet, posn);
247 set_menu_items_sensitivity (PsppireDataSheet *sheet, gpointer selection, gpointer p)
249 SswRange *range = selection;
251 PsppireDataStore *data_store = NULL;
252 g_object_get (sheet, "data-model", &data_store, NULL);
255 gint width = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (data_store));
256 gint length = psppire_data_store_get_case_count (data_store);
259 gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
260 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, whole_row_selected);
262 gboolean whole_column_selected =
263 (range->start_y == 0 && range->end_y == length - 1);
264 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item,
265 whole_column_selected);
266 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item,
267 whole_column_selected);
268 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item,
269 whole_column_selected);
273 psppire_data_sheet_delete_variables (PsppireDataSheet *sheet)
275 SswRange *range = SSW_SHEET(sheet)->selection;
277 PsppireDataStore *data_store = NULL;
278 g_object_get (sheet, "data-model", &data_store, NULL);
280 if (range->start_x > range->end_x)
282 gint temp = range->start_x;
283 range->start_x = range->end_x;
287 psppire_dict_delete_variables (data_store->dict, range->start_x,
288 (range->end_x - range->start_x + 1));
290 ssw_sheet_scroll_to (SSW_SHEET (sheet), range->start_x, -1);
292 gtk_widget_queue_draw (GTK_WIDGET (sheet));
296 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
298 GtkWidget *menu = gtk_menu_new ();
300 /* gtk_menu_shell_append does not sink/ref this object,
301 so we must do it ourselves (and remember to unref it). */
302 g_object_ref_sink (menu);
305 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
306 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
308 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
310 item = gtk_separator_menu_item_new ();
311 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
313 sheet->data_clear_variables_menu_item =
314 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
315 g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
316 G_CALLBACK (psppire_data_sheet_delete_variables),
318 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
319 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
321 item = gtk_separator_menu_item_new ();
322 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
325 sheet->data_sort_ascending_menu_item =
326 gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
327 g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
328 G_CALLBACK (sort_ascending), sheet);
329 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
330 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
332 sheet->data_sort_descending_menu_item =
333 gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
334 g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
335 G_CALLBACK (sort_descending), sheet);
336 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
337 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
339 gtk_widget_show_all (menu);
346 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
348 static GObjectClass * parent_class = NULL;
349 static gboolean dispose_has_run = FALSE;
352 psppire_data_sheet_finalize (GObject *obj)
354 /* Chain up to the parent class */
355 G_OBJECT_CLASS (parent_class)->finalize (obj);
359 psppire_data_sheet_dispose (GObject *obj)
361 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
366 dispose_has_run = TRUE;
368 g_object_unref (sheet->data_sheet_cases_column_popup);
369 g_object_unref (sheet->data_sheet_cases_row_popup);
371 /* Chain up to the parent class */
372 G_OBJECT_CLASS (parent_class)->dispose (obj);
377 psppire_data_sheet_realize (GtkWidget *widget)
379 g_object_set (widget,
380 "forward-conversion", psppire_data_store_value_to_string,
381 "reverse-conversion", psppire_data_store_string_to_value,
383 "horizontal-draggable", TRUE,
386 /* Chain up to the parent class */
387 GTK_WIDGET_CLASS (parent_class)->realize (widget);
391 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
393 GObjectClass *object_class = G_OBJECT_CLASS (class);
394 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
396 widget_class->realize = psppire_data_sheet_realize;
397 object_class->dispose = psppire_data_sheet_dispose;
398 object_class->finalize = psppire_data_sheet_finalize;
400 parent_class = g_type_class_peek_parent (class);
404 psppire_data_sheet_new (void)
406 return g_object_new (PSPPIRE_TYPE_DATA_SHEET, NULL);
411 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
413 guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
415 if (!psppire_data_store_filtered (store, row))
418 /* Draw a diagonal line through the widget */
419 guint width = gtk_widget_get_allocated_width (widget);
420 guint height = gtk_widget_get_allocated_height (widget);
422 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
423 gtk_render_line (sc, cr, 0, 0, width, height);
429 button_post_create (GtkWidget *button, guint i, gpointer user_data)
431 PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
433 g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
434 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 PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (sheet));
446 PangoLayout *layout = pango_layout_new (context);
449 pango_layout_set_text (layout, "M", 1);
450 pango_layout_get_extents (layout, NULL, &rect);
452 gdouble width_of_M = rect.width / (gdouble) PANGO_SCALE;
454 g_object_unref (G_OBJECT (layout));
455 g_object_unref (G_OBJECT (context));
457 gint Ms = round ((size / width_of_M) - 0.25);
458 struct variable *var = psppire_dict_get_variable (dict, pos);
459 g_return_val_if_fail (var, TRUE);
460 var_set_display_width (var, Ms);
465 set_dictionary (PsppireDataSheet *sheet)
467 GtkTreeModel *data_model = NULL;
468 g_object_get (sheet, "data-model", &data_model, NULL);
470 g_return_if_fail (data_model);
472 PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
473 g_object_set (sheet, "hmodel", store->dict, NULL);
475 g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
478 SswAxisModel *vmodel = NULL;
479 g_object_get (sheet, "vmodel", &vmodel, NULL);
480 g_assert (SSW_IS_AXIS_MODEL (vmodel));
482 g_object_set (vmodel,
483 "post-button-create-func", button_post_create,
484 "post-button-create-func-data", store,
489 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
491 PsppireDataStore *data_store = NULL;
492 g_object_get (sheet, "data-model", &data_store, NULL);
494 if (data_store == NULL)
497 PsppireDict *dict = data_store->dict;
498 struct variable *var = psppire_dict_get_variable (dict, from);
503 /* The index refers to the final position, so if the source
504 is less than the destination, then we must subtract 1, to
505 account for the position vacated by the source */
508 dict_reorder_var (dict->dict, var, new_pos);
512 psppire_data_sheet_init (PsppireDataSheet *sheet)
514 sheet->data_sheet_cases_column_popup =
515 create_data_column_header_popup_menu (sheet);
517 sheet->data_sheet_cases_row_popup =
518 create_data_row_header_popup_menu (sheet);
520 g_signal_connect (sheet, "selection-changed",
521 G_CALLBACK (set_menu_items_sensitivity), sheet);
523 g_signal_connect (sheet, "column-header-pressed",
524 G_CALLBACK (show_cases_column_popup), sheet);
526 g_signal_connect (sheet, "row-header-pressed",
527 G_CALLBACK (show_cases_row_popup), sheet);
529 g_signal_connect (sheet, "value-changed",
530 G_CALLBACK (change_data_value), NULL);
532 g_signal_connect (sheet, "notify::data-model",
533 G_CALLBACK (set_dictionary), NULL);
535 g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);