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 ();
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_dispose (GObject *obj)
354 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (obj);
359 dispose_has_run = TRUE;
361 g_object_unref (sheet->data_sheet_cases_column_popup);
362 g_object_unref (sheet->data_sheet_cases_row_popup);
364 /* Chain up to the parent class */
365 G_OBJECT_CLASS (parent_class)->dispose (obj);
369 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
371 GObjectClass *object_class = G_OBJECT_CLASS (class);
372 object_class->dispose = psppire_data_sheet_dispose;
374 parent_class = g_type_class_peek_parent (class);
378 psppire_data_sheet_new (void)
381 g_object_new (PSPPIRE_TYPE_DATA_SHEET,
382 "forward-conversion", psppire_data_store_value_to_string,
383 "reverse-conversion", psppire_data_store_string_to_value,
385 "horizontal-draggable", TRUE,
388 return GTK_WIDGET (obj);
393 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
395 guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
397 if (!psppire_data_store_filtered (store, row))
400 /* Draw a diagonal line through the widget */
401 guint width = gtk_widget_get_allocated_width (widget);
402 guint height = gtk_widget_get_allocated_height (widget);
404 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
405 gtk_render_line (sc, cr, 0, 0, width, height);
411 button_post_create (GtkWidget *button, guint i, gpointer user_data)
413 PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
415 g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
416 g_signal_connect_after (button, "draw", G_CALLBACK (indicate_filtered_case), data_store);
421 resize_display_width (PsppireDict *dict, gint pos, gint size, gpointer user_data)
426 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (user_data);
427 PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (sheet));
428 PangoLayout *layout = pango_layout_new (context);
431 pango_layout_set_text (layout, "M", 1);
432 pango_layout_get_extents (layout, NULL, &rect);
434 gdouble width_of_M = rect.width / (gdouble) PANGO_SCALE;
436 g_object_unref (G_OBJECT (layout));
437 g_object_unref (G_OBJECT (context));
439 gint Ms = round ((size / width_of_M) - 0.25);
440 struct variable *var = psppire_dict_get_variable (dict, pos);
441 g_return_val_if_fail (var, TRUE);
442 var_set_display_width (var, Ms);
447 set_dictionary (PsppireDataSheet *sheet)
449 GtkTreeModel *data_model = NULL;
450 g_object_get (sheet, "data-model", &data_model, NULL);
452 PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
453 g_object_set (sheet, "hmodel", store->dict, NULL);
455 g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
458 SswAxisModel *vmodel = NULL;
459 g_object_get (sheet, "vmodel", &vmodel, NULL);
460 g_assert (SSW_IS_AXIS_MODEL (vmodel));
462 g_object_set (vmodel,
463 "post-button-create-func", button_post_create,
464 "post-button-create-func-data", store,
469 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
471 PsppireDataStore *data_store = NULL;
472 g_object_get (sheet, "data-model", &data_store, NULL);
474 if (data_store == NULL)
477 PsppireDict *dict = data_store->dict;
478 struct variable *var = psppire_dict_get_variable (dict, from);
483 /* The index refers to the final position, so if the source
484 is less than the destination, then we must subtract 1, to
485 account for the position vacated by the source */
488 dict_reorder_var (dict->dict, var, new_pos);
492 psppire_data_sheet_init (PsppireDataSheet *sheet)
494 sheet->data_sheet_cases_column_popup =
495 create_data_column_header_popup_menu (sheet);
497 sheet->data_sheet_cases_row_popup =
498 create_data_row_header_popup_menu (sheet);
500 g_signal_connect (sheet, "selection-changed",
501 G_CALLBACK (set_menu_items_sensitivity), sheet);
503 g_signal_connect (sheet, "column-header-pressed",
504 G_CALLBACK (show_cases_column_popup), sheet);
506 g_signal_connect (sheet, "row-header-pressed",
507 G_CALLBACK (show_cases_row_popup), sheet);
509 g_signal_connect (sheet, "value-changed",
510 G_CALLBACK (change_data_value), NULL);
512 g_signal_connect (sheet, "notify::data-model",
513 G_CALLBACK (set_dictionary), NULL);
515 g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);