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);
210 psppire_data_sheet_insert_new_variable_at_posn (PsppireDataSheet *sheet, gint posn)
212 PsppireDataStore *data_store = NULL;
213 g_object_get (sheet, "data-model", &data_store, NULL);
215 const struct variable *v = psppire_dict_insert_variable (data_store->dict,
218 psppire_data_store_insert_value (data_store, var_get_width(v),
219 var_get_case_index (v));
221 ssw_sheet_scroll_to (SSW_SHEET (sheet), posn, -1);
223 gtk_widget_queue_draw (GTK_WIDGET (sheet));
227 insert_new_variable (PsppireDataSheet *sheet)
229 PsppireDataStore *data_store = NULL;
230 g_object_get (sheet, "data-model", &data_store, NULL);
232 gint posn = GPOINTER_TO_INT (g_object_get_data
233 (G_OBJECT (sheet->data_sheet_cases_column_popup),
236 psppire_data_sheet_insert_new_variable_at_posn (sheet, posn);
241 set_menu_items_sensitivity (PsppireDataSheet *sheet, gpointer selection, gpointer p)
243 SswRange *range = selection;
245 PsppireDataStore *data_store = NULL;
246 g_object_get (sheet, "data-model", &data_store, NULL);
249 gint width = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (data_store));
250 gint length = psppire_data_store_get_case_count (data_store);
253 gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
254 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, whole_row_selected);
256 gboolean whole_column_selected =
257 (range->start_y == 0 && range->end_y == length - 1);
258 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item,
259 whole_column_selected);
260 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item,
261 whole_column_selected);
262 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item,
263 whole_column_selected);
267 psppire_data_sheet_delete_variables (PsppireDataSheet *sheet)
269 SswRange *range = SSW_SHEET(sheet)->selection;
271 PsppireDataStore *data_store = NULL;
272 g_object_get (sheet, "data-model", &data_store, NULL);
274 psppire_dict_delete_variables (data_store->dict, range->start_x,
275 (range->end_x - range->start_x + 1));
277 ssw_sheet_scroll_to (SSW_SHEET (sheet), range->start_x, -1);
279 gtk_widget_queue_draw (GTK_WIDGET (sheet));
285 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
287 GtkWidget *menu = gtk_menu_new ();
290 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
291 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
293 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
295 item = gtk_separator_menu_item_new ();
296 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
298 sheet->data_clear_variables_menu_item =
299 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
300 g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
301 G_CALLBACK (psppire_data_sheet_delete_variables),
303 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
304 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
306 item = gtk_separator_menu_item_new ();
307 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
310 sheet->data_sort_ascending_menu_item =
311 gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
312 g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
313 G_CALLBACK (sort_ascending), sheet);
314 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
315 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
317 sheet->data_sort_descending_menu_item =
318 gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
319 g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
320 G_CALLBACK (sort_descending), sheet);
321 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
322 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
324 gtk_widget_show_all (menu);
331 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
333 static GObjectClass * parent_class = NULL;
334 static gboolean dispose_has_run = FALSE;
337 psppire_data_sheet_dispose (GObject *obj)
339 // PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
344 dispose_has_run = TRUE;
346 /* Chain up to the parent class */
347 G_OBJECT_CLASS (parent_class)->dispose (obj);
351 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
353 GObjectClass *object_class = G_OBJECT_CLASS (class);
354 object_class->dispose = psppire_data_sheet_dispose;
356 parent_class = g_type_class_peek_parent (class);
360 psppire_data_sheet_new (void)
363 g_object_new (PSPPIRE_TYPE_DATA_SHEET,
364 "forward-conversion", psppire_data_store_value_to_string,
365 "reverse-conversion", psppire_data_store_string_to_value,
367 "horizontal-draggable", TRUE,
370 return GTK_WIDGET (obj);
375 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
377 guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
379 if (!psppire_data_store_filtered (store, row))
382 /* Draw a diagonal line through the widget */
383 guint width = gtk_widget_get_allocated_width (widget);
384 guint height = gtk_widget_get_allocated_height (widget);
386 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
387 gtk_render_line (sc, cr, 0, 0, width, height);
393 button_post_create (GtkWidget *button, guint i, gpointer user_data)
395 PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
397 g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
398 g_signal_connect_after (button, "draw", G_CALLBACK (indicate_filtered_case), data_store);
403 resize_display_width (PsppireDict *dict, gint pos, gint size, gpointer user_data)
408 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (user_data);
409 PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (sheet));
410 PangoLayout *layout = pango_layout_new (context);
413 pango_layout_set_text (layout, "M", 1);
414 pango_layout_get_extents (layout, NULL, &rect);
416 gdouble width_of_M = rect.width / (gdouble) PANGO_SCALE;
418 g_object_unref (G_OBJECT (layout));
419 g_object_unref (G_OBJECT (context));
421 gint Ms = round ((size / width_of_M) - 0.25);
422 struct variable *var = psppire_dict_get_variable (dict, pos);
423 g_return_val_if_fail (var, TRUE);
424 var_set_display_width (var, Ms);
429 set_dictionary (PsppireDataSheet *sheet)
431 GtkTreeModel *data_model = NULL;
432 g_object_get (sheet, "data-model", &data_model, NULL);
434 PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
435 g_object_set (sheet, "hmodel", store->dict, NULL);
437 g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
440 SswAxisModel *vmodel = NULL;
441 g_object_get (sheet, "vmodel", &vmodel, NULL);
442 g_assert (SSW_IS_AXIS_MODEL (vmodel));
444 g_object_set (vmodel,
445 "post-button-create-func", button_post_create,
446 "post-button-create-func-data", store,
451 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
453 PsppireDataStore *data_store = NULL;
454 g_object_get (sheet, "data-model", &data_store, NULL);
456 if (data_store == NULL)
459 PsppireDict *dict = data_store->dict;
460 struct variable *var = psppire_dict_get_variable (dict, from);
465 /* The index refers to the final position, so if the source
466 is less than the destination, then we must subtract 1, to
467 account for the position vacated by the source */
470 dict_reorder_var (dict->dict, var, new_pos);
474 psppire_data_sheet_init (PsppireDataSheet *sheet)
476 sheet->data_sheet_cases_column_popup =
477 create_data_column_header_popup_menu (sheet);
479 sheet->data_sheet_cases_row_popup =
480 create_data_row_header_popup_menu (sheet);
482 g_signal_connect (sheet, "selection-changed",
483 G_CALLBACK (set_menu_items_sensitivity), sheet);
485 g_signal_connect (sheet, "column-header-pressed",
486 G_CALLBACK (show_cases_column_popup), sheet);
488 g_signal_connect (sheet, "row-header-pressed",
489 G_CALLBACK (show_cases_row_popup), sheet);
491 g_signal_connect (sheet, "value-changed",
492 G_CALLBACK (change_data_value), NULL);
494 g_signal_connect (sheet, "notify::data-model",
495 G_CALLBACK (set_dictionary), NULL);
497 g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);