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 if (range->start_x > range->end_x)
278 gint temp = range->start_x;
279 range->start_x = range->end_x;
283 psppire_dict_delete_variables (data_store->dict, range->start_x,
284 (range->end_x - range->start_x + 1));
286 ssw_sheet_scroll_to (SSW_SHEET (sheet), range->start_x, -1);
288 gtk_widget_queue_draw (GTK_WIDGET (sheet));
294 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
296 GtkWidget *menu = gtk_menu_new ();
299 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
300 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
302 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
304 item = gtk_separator_menu_item_new ();
305 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
307 sheet->data_clear_variables_menu_item =
308 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
309 g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
310 G_CALLBACK (psppire_data_sheet_delete_variables),
312 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
313 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
315 item = gtk_separator_menu_item_new ();
316 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
319 sheet->data_sort_ascending_menu_item =
320 gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
321 g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
322 G_CALLBACK (sort_ascending), sheet);
323 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
324 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
326 sheet->data_sort_descending_menu_item =
327 gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
328 g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
329 G_CALLBACK (sort_descending), sheet);
330 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
331 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
333 gtk_widget_show_all (menu);
340 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
342 static GObjectClass * parent_class = NULL;
343 static gboolean dispose_has_run = FALSE;
346 psppire_data_sheet_dispose (GObject *obj)
348 // PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
353 dispose_has_run = TRUE;
355 /* Chain up to the parent class */
356 G_OBJECT_CLASS (parent_class)->dispose (obj);
360 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
362 GObjectClass *object_class = G_OBJECT_CLASS (class);
363 object_class->dispose = psppire_data_sheet_dispose;
365 parent_class = g_type_class_peek_parent (class);
369 psppire_data_sheet_new (void)
372 g_object_new (PSPPIRE_TYPE_DATA_SHEET,
373 "forward-conversion", psppire_data_store_value_to_string,
374 "reverse-conversion", psppire_data_store_string_to_value,
376 "horizontal-draggable", TRUE,
379 return GTK_WIDGET (obj);
384 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
386 guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
388 if (!psppire_data_store_filtered (store, row))
391 /* Draw a diagonal line through the widget */
392 guint width = gtk_widget_get_allocated_width (widget);
393 guint height = gtk_widget_get_allocated_height (widget);
395 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
396 gtk_render_line (sc, cr, 0, 0, width, height);
402 button_post_create (GtkWidget *button, guint i, gpointer user_data)
404 PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
406 g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
407 g_signal_connect_after (button, "draw", G_CALLBACK (indicate_filtered_case), data_store);
412 resize_display_width (PsppireDict *dict, gint pos, gint size, gpointer user_data)
417 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (user_data);
418 PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (sheet));
419 PangoLayout *layout = pango_layout_new (context);
422 pango_layout_set_text (layout, "M", 1);
423 pango_layout_get_extents (layout, NULL, &rect);
425 gdouble width_of_M = rect.width / (gdouble) PANGO_SCALE;
427 g_object_unref (G_OBJECT (layout));
428 g_object_unref (G_OBJECT (context));
430 gint Ms = round ((size / width_of_M) - 0.25);
431 struct variable *var = psppire_dict_get_variable (dict, pos);
432 g_return_val_if_fail (var, TRUE);
433 var_set_display_width (var, Ms);
438 set_dictionary (PsppireDataSheet *sheet)
440 GtkTreeModel *data_model = NULL;
441 g_object_get (sheet, "data-model", &data_model, NULL);
443 PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
444 g_object_set (sheet, "hmodel", store->dict, NULL);
446 g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
449 SswAxisModel *vmodel = NULL;
450 g_object_get (sheet, "vmodel", &vmodel, NULL);
451 g_assert (SSW_IS_AXIS_MODEL (vmodel));
453 g_object_set (vmodel,
454 "post-button-create-func", button_post_create,
455 "post-button-create-func-data", store,
460 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
462 PsppireDataStore *data_store = NULL;
463 g_object_get (sheet, "data-model", &data_store, NULL);
465 if (data_store == NULL)
468 PsppireDict *dict = data_store->dict;
469 struct variable *var = psppire_dict_get_variable (dict, from);
474 /* The index refers to the final position, so if the source
475 is less than the destination, then we must subtract 1, to
476 account for the position vacated by the source */
479 dict_reorder_var (dict->dict, var, new_pos);
483 psppire_data_sheet_init (PsppireDataSheet *sheet)
485 sheet->data_sheet_cases_column_popup =
486 create_data_column_header_popup_menu (sheet);
488 sheet->data_sheet_cases_row_popup =
489 create_data_row_header_popup_menu (sheet);
491 g_signal_connect (sheet, "selection-changed",
492 G_CALLBACK (set_menu_items_sensitivity), sheet);
494 g_signal_connect (sheet, "column-header-pressed",
495 G_CALLBACK (show_cases_column_popup), sheet);
497 g_signal_connect (sheet, "row-header-pressed",
498 G_CALLBACK (show_cases_row_popup), sheet);
500 g_signal_connect (sheet, "value-changed",
501 G_CALLBACK (change_data_value), NULL);
503 g_signal_connect (sheet, "notify::data-model",
504 G_CALLBACK (set_dictionary), NULL);
506 g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);