1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2017, 2019 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);
376 psppire_data_sheet_class_init (PsppireDataSheetClass *class)
378 GObjectClass *object_class = G_OBJECT_CLASS (class);
379 object_class->dispose = psppire_data_sheet_dispose;
380 object_class->finalize = psppire_data_sheet_finalize;
382 parent_class = g_type_class_peek_parent (class);
386 psppire_data_sheet_new (void)
389 g_object_new (PSPPIRE_TYPE_DATA_SHEET,
390 "forward-conversion", psppire_data_store_value_to_string,
391 "reverse-conversion", psppire_data_store_string_to_value,
393 "horizontal-draggable", TRUE,
396 return GTK_WIDGET (obj);
401 indicate_filtered_case (GtkWidget *widget, cairo_t *cr, PsppireDataStore *store)
403 guint row = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "row"));
405 if (!psppire_data_store_filtered (store, row))
408 /* Draw a diagonal line through the widget */
409 guint width = gtk_widget_get_allocated_width (widget);
410 guint height = gtk_widget_get_allocated_height (widget);
412 GtkStyleContext *sc = gtk_widget_get_style_context (widget);
413 gtk_render_line (sc, cr, 0, 0, width, height);
419 button_post_create (GtkWidget *button, guint i, gpointer user_data)
421 PsppireDataStore *data_store = PSPPIRE_DATA_STORE (user_data);
423 g_object_set_data (G_OBJECT (button), "row", GUINT_TO_POINTER (i));
424 g_signal_connect_after (button, "draw", G_CALLBACK (indicate_filtered_case), data_store);
429 resize_display_width (PsppireDict *dict, gint pos, gint size, gpointer user_data)
434 PsppireDataSheet *sheet = PSPPIRE_DATA_SHEET (user_data);
435 PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (sheet));
436 PangoLayout *layout = pango_layout_new (context);
439 pango_layout_set_text (layout, "M", 1);
440 pango_layout_get_extents (layout, NULL, &rect);
442 gdouble width_of_M = rect.width / (gdouble) PANGO_SCALE;
444 g_object_unref (G_OBJECT (layout));
445 g_object_unref (G_OBJECT (context));
447 gint Ms = round ((size / width_of_M) - 0.25);
448 struct variable *var = psppire_dict_get_variable (dict, pos);
449 g_return_val_if_fail (var, TRUE);
450 var_set_display_width (var, Ms);
455 set_dictionary (PsppireDataSheet *sheet)
457 GtkTreeModel *data_model = NULL;
458 g_object_get (sheet, "data-model", &data_model, NULL);
460 PsppireDataStore *store = PSPPIRE_DATA_STORE (data_model);
461 g_object_set (sheet, "hmodel", store->dict, NULL);
463 g_signal_connect (store->dict, "resize-item", G_CALLBACK (resize_display_width),
466 SswAxisModel *vmodel = NULL;
467 g_object_get (sheet, "vmodel", &vmodel, NULL);
468 g_assert (SSW_IS_AXIS_MODEL (vmodel));
470 g_object_set (vmodel,
471 "post-button-create-func", button_post_create,
472 "post-button-create-func-data", store,
477 move_variable (PsppireDataSheet *sheet, gint from, gint to, gpointer ud)
479 PsppireDataStore *data_store = NULL;
480 g_object_get (sheet, "data-model", &data_store, NULL);
482 if (data_store == NULL)
485 PsppireDict *dict = data_store->dict;
486 struct variable *var = psppire_dict_get_variable (dict, from);
491 /* The index refers to the final position, so if the source
492 is less than the destination, then we must subtract 1, to
493 account for the position vacated by the source */
496 dict_reorder_var (dict->dict, var, new_pos);
500 psppire_data_sheet_init (PsppireDataSheet *sheet)
502 sheet->data_sheet_cases_column_popup =
503 create_data_column_header_popup_menu (sheet);
505 sheet->data_sheet_cases_row_popup =
506 create_data_row_header_popup_menu (sheet);
508 g_signal_connect (sheet, "selection-changed",
509 G_CALLBACK (set_menu_items_sensitivity), sheet);
511 g_signal_connect (sheet, "column-header-pressed",
512 G_CALLBACK (show_cases_column_popup), sheet);
514 g_signal_connect (sheet, "row-header-pressed",
515 G_CALLBACK (show_cases_row_popup), sheet);
517 g_signal_connect (sheet, "value-changed",
518 G_CALLBACK (change_data_value), NULL);
520 g_signal_connect (sheet, "notify::data-model",
521 G_CALLBACK (set_dictionary), NULL);
523 g_signal_connect (sheet, "column-moved", G_CALLBACK (move_variable), NULL);