1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2017 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);
107 gboolean myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in,
113 show_cases_row_popup (PsppireDataSheet *sheet, int row,
114 guint button, guint state, gpointer p)
116 GListModel *vmodel = NULL;
117 g_object_get (sheet, "vmodel", &vmodel, NULL);
121 guint n_items = g_list_model_get_n_items (vmodel);
129 g_object_set_data (G_OBJECT (sheet->data_sheet_cases_row_popup), "item",
130 GINT_TO_POINTER (row));
132 gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_row_popup), NULL);
137 insert_new_case (PsppireDataSheet *sheet)
139 PsppireDataStore *data_store = NULL;
140 g_object_get (sheet, "data-model", &data_store, NULL);
142 gint posn = GPOINTER_TO_INT (g_object_get_data
143 (G_OBJECT (sheet->data_sheet_cases_row_popup), "item"));
145 psppire_data_store_insert_new_case (data_store, posn);
147 gtk_widget_queue_draw (GTK_WIDGET (sheet));
151 delete_cases (PsppireDataSheet *sheet)
153 SswRange *range = SSW_SHEET(sheet)->selection;
155 PsppireDataStore *data_store = NULL;
156 g_object_get (sheet, "data-model", &data_store, NULL);
158 psppire_data_store_delete_cases (data_store, range->start_y,
159 range->end_y - range->start_y + 1);
161 gtk_widget_queue_draw (GTK_WIDGET (sheet));
165 create_data_row_header_popup_menu (PsppireDataSheet *sheet)
167 GtkWidget *menu = gtk_menu_new ();
170 gtk_menu_item_new_with_mnemonic (_("_Insert Case"));
172 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), sheet);
173 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
175 item = gtk_separator_menu_item_new ();
176 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
178 sheet->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
179 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, FALSE);
180 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_cases_menu_item);
181 g_signal_connect_swapped (sheet->data_clear_cases_menu_item, "activate",
182 G_CALLBACK (delete_cases), sheet);
184 gtk_widget_show_all (menu);
190 show_cases_column_popup (PsppireDataSheet *sheet, int column, guint button, guint state,
193 GListModel *hmodel = NULL;
194 g_object_get (sheet, "hmodel", &hmodel, NULL);
198 guint n_items = g_list_model_get_n_items (hmodel);
200 if (column >= n_items)
206 g_object_set_data (G_OBJECT (sheet->data_sheet_cases_column_popup), "item",
207 GINT_TO_POINTER (column));
209 gtk_menu_popup_at_pointer (GTK_MENU (sheet->data_sheet_cases_column_popup), NULL);
213 insert_new_variable (PsppireDataSheet *sheet)
215 PsppireDataStore *data_store = NULL;
216 g_object_get (sheet, "data-model", &data_store, NULL);
218 gint posn = GPOINTER_TO_INT (g_object_get_data
219 (G_OBJECT (sheet->data_sheet_cases_column_popup),
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_case_index (v));
228 gtk_widget_queue_draw (GTK_WIDGET (sheet));
232 set_menu_items_sensitivity (PsppireDataSheet *sheet, gpointer selection, gpointer p)
234 SswRange *range = selection;
236 PsppireDataStore *data_store = NULL;
237 g_object_get (sheet, "data-model", &data_store, NULL);
240 gint width = gtk_tree_model_get_n_columns (GTK_TREE_MODEL (data_store));
241 gint length = psppire_data_store_get_case_count (data_store);
244 gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
245 gtk_widget_set_sensitive (sheet->data_clear_cases_menu_item, whole_row_selected);
247 gboolean whole_column_selected =
248 (range->start_y == 0 && range->end_y == length - 1);
249 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item,
250 whole_column_selected);
251 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item,
252 whole_column_selected);
253 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item,
254 whole_column_selected);
258 delete_variables (PsppireDataSheet *sheet)
260 SswRange *range = SSW_SHEET(sheet)->selection;
262 PsppireDataStore *data_store = NULL;
263 g_object_get (sheet, "data-model", &data_store, NULL);
265 psppire_dict_delete_variables (data_store->dict, range->start_x,
266 (range->end_x - range->start_x + 1));
268 gtk_widget_queue_draw (GTK_WIDGET (sheet));
274 create_data_column_header_popup_menu (PsppireDataSheet *sheet)
276 GtkWidget *menu = gtk_menu_new ();
279 gtk_menu_item_new_with_mnemonic (_("_Insert Variable"));
280 g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable),
282 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
284 item = gtk_separator_menu_item_new ();
285 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
287 sheet->data_clear_variables_menu_item =
288 gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
289 g_signal_connect_swapped (sheet->data_clear_variables_menu_item, "activate",
290 G_CALLBACK (delete_variables),
292 gtk_widget_set_sensitive (sheet->data_clear_variables_menu_item, FALSE);
293 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_clear_variables_menu_item);
295 item = gtk_separator_menu_item_new ();
296 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
299 sheet->data_sort_ascending_menu_item =
300 gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
301 g_signal_connect_swapped (sheet->data_sort_ascending_menu_item, "activate",
302 G_CALLBACK (sort_ascending), sheet);
303 gtk_widget_set_sensitive (sheet->data_sort_ascending_menu_item, FALSE);
304 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_ascending_menu_item);
306 sheet->data_sort_descending_menu_item =
307 gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
308 g_signal_connect_swapped (sheet->data_sort_descending_menu_item, "activate",
309 G_CALLBACK (sort_descending), sheet);
310 gtk_widget_set_sensitive (sheet->data_sort_descending_menu_item, FALSE);
311 gtk_menu_shell_append (GTK_MENU_SHELL (menu), sheet->data_sort_descending_menu_item);
313 gtk_widget_show_all (menu);
320 G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, SSW_TYPE_SHEET)
322 static GObjectClass * parent_class = NULL;
323 static gboolean dispose_has_run = FALSE;
326 psppire_data_sheet_dispose (GObject *obj)
328 // PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
333 dispose_has_run = TRUE;
335 /* Chain up to the parent class */
336 G_OBJECT_CLASS (parent_class)->dispose (obj);
340 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
342 GObjectClass *object_class = G_OBJECT_CLASS (class);
343 object_class->dispose = psppire_data_sheet_dispose;
345 parent_class = g_type_class_peek_parent (class);
349 psppire_data_sheet_new (void)
352 g_object_new (PSPPIRE_TYPE_DATA_SHEET,
353 "forward-conversion", psppire_data_store_value_to_string,
354 "reverse-conversion", myreversefunc,
356 "horizontal-draggable", TRUE,
359 return GTK_WIDGET (obj);
364 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
366 guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
368 if (!psppire_data_store_filtered (store, row))
371 /* Draw a diagonal line through the widget */
372 guint width = gtk_widget_get_allocated_width (widget);
373 guint height = gtk_widget_get_allocated_height (widget);
375 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
376 gtk_render_line (sc, cr, 0, 0, width, height);
382 button_post_create (GtkWidget *button, guint i, gpointer user_data)
384 PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
386 g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
387 g_signal_connect_after (button, "draw", G_CALLBACK (indicate_filtered_case), data_store);
392 resize_display_width (PsppireDict *dict, gint pos, gint size, gpointer user_data)
397 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (user_data);
398 PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (sheet));
399 PangoLayout *layout = pango_layout_new (context);
402 pango_layout_set_text (layout, "M", 1);
403 pango_layout_get_extents (layout, NULL, &rect);
405 gdouble width_of_M = rect.width / (gdouble) PANGO_SCALE;
407 g_object_unref (G_OBJECT (layout));
408 g_object_unref (G_OBJECT (context));
410 gint Ms = round ((size / width_of_M) - 0.25);
411 struct variable *var = psppire_dict_get_variable (dict, pos);
412 g_return_val_if_fail (var, TRUE);
413 var_set_display_width (var, Ms);
418 set_dictionary (PsppireDataSheet *sheet)
420 GtkTreeModel *data_model = NULL;
421 g_object_get (sheet, "data-model", &data_model, NULL);
423 PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
424 g_object_set (sheet, "hmodel", store->dict, NULL);
426 g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
429 SswAxisModel *vmodel = NULL;
430 g_object_get (sheet, "vmodel", &vmodel, NULL);
431 g_assert (SSW_IS_AXIS_MODEL (vmodel));
433 g_object_set (vmodel,
434 "post-button-create-func", button_post_create,
435 "post-button-create-func-data", store,
440 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
442 PsppireDataStore *data_store = NULL;
443 g_object_get (sheet, "data-model", &data_store, NULL);
445 if (data_store == NULL)
448 PsppireDict *dict = data_store->dict;
449 struct variable *var = psppire_dict_get_variable (dict, from);
454 /* The index refers to the final position, so if the source
455 is less than the destination, then we must subtract 1, to
456 account for the position vacated by the source */
459 dict_reorder_var (dict->dict, var, new_pos);
463 psppire_data_sheet_init (PsppireDataSheet *sheet)
465 sheet->data_sheet_cases_column_popup =
466 create_data_column_header_popup_menu (sheet);
468 sheet->data_sheet_cases_row_popup =
469 create_data_row_header_popup_menu (sheet);
471 g_signal_connect (sheet, "selection-changed",
472 G_CALLBACK (set_menu_items_sensitivity), sheet);
474 g_signal_connect (sheet, "column-header-pressed",
475 G_CALLBACK (show_cases_column_popup), sheet);
477 g_signal_connect (sheet, "row-header-pressed",
478 G_CALLBACK (show_cases_row_popup), sheet);
480 g_signal_connect (sheet, "value-changed",
481 G_CALLBACK (change_data_value), NULL);
483 g_signal_connect (sheet, "notify::data-model",
484 G_CALLBACK (set_dictionary), NULL);
486 g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);