1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2005, 2006, 2010, 2011, 2012, 2015, 2020 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/>. */
18 /* This module describes the behaviour of the Variable Type dialog box used
19 for inputing the variable type in the var sheet */
27 #include "data/data-out.h"
28 #include "data/settings.h"
29 #include "data/variable.h"
30 #include "libpspp/message.h"
31 #include "ui/gui/builder-wrapper.h"
32 #include "ui/gui/psppire-format.h"
33 #include "ui/gui/var-type-dialog.h"
35 static const struct fmt_spec date_format[] =
53 {FMT_DATETIME, 17, 0},
54 {FMT_DATETIME, 20, 0},
60 static const struct fmt_spec dollar_format[] =
76 static const int cc_format[] =
85 static GObject *psppire_var_type_dialog_constructor (GType type, guint,
86 GObjectConstructParam *);
87 static void psppire_var_type_dialog_set_state (PsppireVarTypeDialog *);
89 static void psppire_var_type_dialog_set_format (PsppireVarTypeDialog *dialog,
90 const struct fmt_spec *format);
92 static int find_format (const struct fmt_spec *target,
93 const struct fmt_spec formats[], int n_formats);
94 static int find_format_type (int target, const int types[], int n_types);
96 static void select_treeview_at_index (GtkTreeView *, int index);
98 static void update_width_decimals (const PsppireVarTypeDialog *);
99 static void refresh_active_button (PsppireVarTypeDialog *);
100 static void on_active_button_change (GtkToggleButton *,
101 PsppireVarTypeDialog *);
102 static void on_width_changed (GtkEntry *, PsppireVarTypeDialog *);
103 static void on_decimals_changed (GtkEntry *, PsppireVarTypeDialog *);
105 G_DEFINE_TYPE (PsppireVarTypeDialog,
106 psppire_var_type_dialog,
107 PSPPIRE_TYPE_DIALOG);
116 psppire_var_type_dialog_set_property (GObject *object,
121 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
126 psppire_var_type_dialog_set_format (obj, g_value_get_boxed (value));
129 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
135 psppire_var_type_dialog_get_property (GObject *object,
140 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
145 g_value_set_boxed (value, &obj->fmt_l);
148 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
154 psppire_var_type_dialog_set_format (PsppireVarTypeDialog *dialog,
155 const struct fmt_spec *format)
157 dialog->base_format = *format;
158 psppire_var_type_dialog_set_state (dialog);
161 static const struct fmt_spec *
162 psppire_var_type_dialog_get_format (const PsppireVarTypeDialog *dialog)
164 return &dialog->fmt_l;
168 psppire_var_type_dialog_init (PsppireVarTypeDialog *obj)
170 /* We do all of our work on widgets in the constructor function, because that
171 runs after the construction properties have been set. Otherwise
172 PsppireDialog's "orientation" property hasn't been set and therefore we
173 have no box to populate. */
174 obj->base_format = F_8_0;
179 psppire_var_type_dialog_class_init (PsppireVarTypeDialogClass *class)
181 GObjectClass *gobject_class;
182 gobject_class = G_OBJECT_CLASS (class);
184 gobject_class->constructor = psppire_var_type_dialog_constructor;
185 gobject_class->set_property = psppire_var_type_dialog_set_property;
186 gobject_class->get_property = psppire_var_type_dialog_get_property;
188 g_object_class_install_property (
189 gobject_class, PROP_FORMAT,
190 g_param_spec_boxed ("format",
192 "The format being edited.",
194 G_PARAM_READABLE | G_PARAM_WRITABLE));
197 PsppireVarTypeDialog *
198 psppire_var_type_dialog_new (const struct fmt_spec *format)
200 return PSPPIRE_VAR_TYPE_DIALOG (
201 g_object_new (PSPPIRE_TYPE_VAR_TYPE_DIALOG,
207 psppire_var_type_dialog_run (GtkWindow *parent_window,
208 struct fmt_spec *format)
210 PsppireVarTypeDialog *dialog;
212 dialog = psppire_var_type_dialog_new (format);
213 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
214 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
215 gtk_widget_show (GTK_WIDGET (dialog));
217 gint result = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
218 if (result == GTK_RESPONSE_OK)
219 *format = *psppire_var_type_dialog_get_format (dialog);
221 gtk_widget_destroy (GTK_WIDGET (dialog));
227 /* callback for when any of the radio buttons are toggled */
229 on_toggle (GtkToggleButton *togglebutton, gpointer dialog_)
231 PsppireVarTypeDialog *dialog = dialog_;
233 if (gtk_toggle_button_get_active (togglebutton) == TRUE)
234 refresh_active_button (dialog);
238 refresh_active_button (PsppireVarTypeDialog *dialog)
242 for (i = 0; i < num_BUTTONS; i++)
244 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (dialog->radioButton[i]);
246 if (gtk_toggle_button_get_active (toggle))
248 if (dialog->active_button != i)
250 dialog->active_button = i;
251 on_active_button_change (toggle, dialog);
257 g_return_if_reached ();
261 update_adj_ranges (PsppireVarTypeDialog *dialog)
263 enum fmt_type type = dialog->fmt_l.type;
264 const enum fmt_use use = FMT_FOR_OUTPUT;
265 int min_w = fmt_min_width (type, use);
266 int max_w = fmt_max_width (type, use);
267 int max_d = fmt_max_decimals (type, max_w, use);
269 g_object_set (dialog->adj_width,
270 "lower", (double) min_w,
271 "upper", (double) max_w,
274 g_object_set (dialog->adj_decimals,
276 "upper", (double) max_d,
280 /* callback for when any of the radio buttons are toggled */
282 on_active_button_change (GtkToggleButton *togglebutton,
283 PsppireVarTypeDialog *dialog)
288 W_DATE_FORMATS = 1 << 2,
289 W_DOLLAR_FORMATS = 1 << 3,
290 W_CC_FORMATS = 1 << 4,
293 enum widgets widgets;
296 switch (dialog->active_button)
301 case BUTTON_SCIENTIFIC:
302 widgets = W_WIDTH | W_DECIMALS;
310 widgets = W_DATE_FORMATS;
314 widgets = W_DOLLAR_FORMATS;
318 widgets = W_CC_FORMATS | W_WIDTH | W_DECIMALS;
322 /* No button active */
326 gtk_widget_set_visible (dialog->width_decimals, (widgets & W_WIDTH) != 0);
327 gtk_widget_set_visible (dialog->entry_width, (widgets & W_WIDTH) != 0);
328 gtk_widget_set_visible (dialog->entry_decimals, (widgets & W_DECIMALS) != 0);
329 gtk_widget_set_visible (dialog->label_decimals, (widgets & W_DECIMALS) != 0);
330 gtk_widget_set_visible (dialog->date_format_list,
331 (widgets & W_DATE_FORMATS) != 0);
332 gtk_widget_set_visible (dialog->custom_currency_hbox,
333 (widgets & W_CC_FORMATS) != 0);
334 gtk_widget_set_visible (dialog->dollar_window,
335 (widgets & W_DOLLAR_FORMATS) != 0);
337 dialog->fmt_l = dialog->base_format;
339 switch (dialog->active_button)
342 dialog->fmt_l.type = FMT_F;
345 dialog->fmt_l.type = FMT_COMMA;
348 dialog->fmt_l.type = FMT_DOT;
350 case BUTTON_SCIENTIFIC:
351 dialog->fmt_l.type = FMT_E;
354 dialog->fmt_l.type = FMT_A;
357 indx = find_format (&dialog->fmt_l, date_format,
358 sizeof date_format / sizeof *date_format);
359 select_treeview_at_index (dialog->date_format_treeview, indx);
360 dialog->fmt_l = date_format[indx];
363 indx = find_format (&dialog->fmt_l, dollar_format,
364 sizeof dollar_format / sizeof *dollar_format);
365 select_treeview_at_index (dialog->dollar_treeview, indx);
366 dialog->fmt_l = dollar_format[indx];
369 indx = find_format_type (dialog->fmt_l.type, cc_format,
370 sizeof cc_format / sizeof *cc_format);
371 select_treeview_at_index (dialog->custom_treeview, indx);
372 dialog->fmt_l.type = cc_format[indx];
376 fmt_fix_output (&dialog->fmt_l);
377 update_adj_ranges (dialog);
378 update_width_decimals (dialog);
382 add_to_group (GtkWidget *w, gpointer data)
384 GtkSizeGroup *sg = data;
386 gtk_size_group_add_widget (sg, w);
389 /* Set the local width and decimals entry boxes to reflec the local format */
391 update_width_decimals (const PsppireVarTypeDialog *dialog)
393 gtk_adjustment_set_value (dialog->adj_width, dialog->fmt_l.w);
394 gtk_adjustment_set_value (dialog->adj_decimals, dialog->fmt_l.d);
398 on_width_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
400 int w = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_width)));
401 fmt_change_width (&dialog->fmt_l, w, FMT_FOR_OUTPUT);
402 update_width_decimals (dialog);
406 on_decimals_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
408 int d = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals)));
409 fmt_change_decimals (&dialog->fmt_l, d, FMT_FOR_OUTPUT);
410 update_width_decimals (dialog);
413 /* Callback for when the custom treeview row is changed.
414 It sets dialog box to reflect the selected format */
416 preview_custom (GtkWidget *w, gpointer data)
420 PsppireVarTypeDialog *dialog = data;
422 if (dialog->active_button != BUTTON_CUSTOM)
425 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals));
426 dialog->fmt_l.d = atoi (text);
428 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_width));
429 dialog->fmt_l.w = atoi (text);
432 if (! fmt_check_output (&dialog->fmt_l))
434 gtk_label_set_text (GTK_LABEL (dialog->label_psample), "---");
435 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), "---");
443 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
444 gtk_label_set_text (GTK_LABEL (dialog->label_psample), sample_text);
445 g_free (sample_text);
448 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
449 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), sample_text);
450 g_free (sample_text);
456 get_index_from_treeview (GtkTreeView *treeview)
458 GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
464 if (selection == NULL)
467 gtk_tree_selection_get_selected (selection, &model, &iter);
468 path = gtk_tree_model_get_path (model, &iter);
469 if (!path || gtk_tree_path_get_depth (path) < 1)
472 index = gtk_tree_path_get_indices (path)[0];
473 gtk_tree_path_free (path);
478 /* Callback for when a date treeview row is changed.
479 It sets the fmt_l_spec to reflect the selected format */
481 set_date_format_from_treeview (GtkTreeView *treeview,
482 PsppireVarTypeDialog *dialog)
484 gint idx = get_index_from_treeview (treeview);
488 dialog->fmt_l = date_format[idx];
491 /* Callback for when a dollar treeview row is changed.
492 It sets the fmt_l_spec to reflect the selected format */
494 set_dollar_format_from_treeview (GtkTreeView *treeview,
495 PsppireVarTypeDialog *dialog)
497 gint idx = get_index_from_treeview (treeview);
501 dialog->fmt_l = dollar_format[idx];
504 /* Callback for when a treeview row is changed.
505 It sets the type of the fmt_l to reflect the selected type */
507 set_custom_format_from_treeview (GtkTreeView *treeview,
508 PsppireVarTypeDialog *dialog)
510 gint idx = get_index_from_treeview (treeview);
514 dialog->fmt_l.type = cc_format[idx];
515 update_adj_ranges (dialog);
516 fmt_fix_output (&dialog->fmt_l);
517 update_width_decimals (dialog);
520 /* Create the structure */
522 psppire_var_type_dialog_constructor (GType type,
524 GObjectConstructParam *properties)
526 PsppireVarTypeDialog *dialog;
527 GtkContainer *content_area;
532 obj = G_OBJECT_CLASS (psppire_var_type_dialog_parent_class)->constructor (
533 type, n_properties, properties);
534 dialog = PSPPIRE_VAR_TYPE_DIALOG (obj);
536 g_object_set (dialog, "help_page", "Input-and-Output-Formats", NULL);
538 xml = builder_new ("var-type-dialog.ui");
540 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
541 gtk_container_add (GTK_CONTAINER (content_area),
542 get_widget_assert (xml, "var-type-dialog"));
544 dialog->active_button = -1;
546 g_signal_connect (dialog, "delete-event",
547 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
549 dialog->radioButton[BUTTON_NUMERIC] =
550 get_widget_assert (xml,"radiobutton1");
551 dialog->radioButton[BUTTON_COMMA] =
552 get_widget_assert (xml,"radiobutton2");
553 dialog->radioButton[BUTTON_DOT] =
554 get_widget_assert (xml,"radiobutton3");
555 dialog->radioButton[BUTTON_SCIENTIFIC] =
556 get_widget_assert (xml,"radiobutton4");
557 dialog->radioButton[BUTTON_DATE] =
558 get_widget_assert (xml,"radiobutton5");
559 dialog->radioButton[BUTTON_DOLLAR] =
560 get_widget_assert (xml,"radiobutton6");
561 dialog->radioButton[BUTTON_CUSTOM] =
562 get_widget_assert (xml,"radiobutton7");
563 dialog->radioButton[BUTTON_STRING] =
564 get_widget_assert (xml,"radiobutton8");
567 dialog->date_format_list = get_widget_assert (xml, "scrolledwindow4");
568 dialog->width_decimals = get_widget_assert (xml, "width_decimals");
569 dialog->label_decimals = get_widget_assert (xml, "decimals_label");
570 dialog->entry_decimals = get_widget_assert (xml, "decimals_entry");
571 dialog->adj_decimals = gtk_spin_button_get_adjustment (
572 GTK_SPIN_BUTTON (dialog->entry_decimals));
574 dialog->label_psample = get_widget_assert (xml, "psample_label");
575 dialog->label_nsample = get_widget_assert (xml, "nsample_label");
578 dialog->entry_width = get_widget_assert (xml,"width_entry");
579 dialog->adj_width = gtk_spin_button_get_adjustment (
580 GTK_SPIN_BUTTON (dialog->entry_width));
581 dialog->custom_currency_hbox = get_widget_assert (xml,
582 "custom_currency_hbox");
584 dialog->dollar_window = get_widget_assert (xml, "dollar_window");
585 dialog->dollar_treeview =
586 GTK_TREE_VIEW (get_widget_assert (xml, "dollar_treeview"));
588 dialog->custom_treeview =
589 GTK_TREE_VIEW (get_widget_assert (xml, "custom_treeview"));
595 GtkListStore *list_store ;
597 GtkTreeViewColumn *column;
598 GtkCellRenderer *renderer ;
600 /* The "middle_box" is a vbox with serveral children.
601 However only one child is ever shown at a time.
602 We need to make sure that they all have the same width, to avoid
603 upleasant resizing effects */
604 GtkSizeGroup *sizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
606 gtk_container_foreach (GTK_CONTAINER (get_widget_assert (xml, "middle_box")),
607 add_to_group, sizeGroup);
610 for (i = 0 ; i < num_BUTTONS; ++i)
611 g_signal_connect (dialog->radioButton[i], "toggled",
612 G_CALLBACK (on_toggle), dialog);
614 /* Populate the date format tree view */
615 dialog->date_format_treeview = GTK_TREE_VIEW (get_widget_assert (xml,
616 "date_format_list_view"));
618 renderer = gtk_cell_renderer_text_new ();
620 column = gtk_tree_view_column_new_with_attributes ("Title",
626 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->date_format_treeview),
630 list_store = gtk_list_store_new (1, G_TYPE_STRING);
632 for (i = 0 ; i < sizeof (date_format) / sizeof (date_format[0]) ; ++i)
634 const struct fmt_spec *f = &date_format[i];
635 gtk_list_store_append (list_store, &iter);
636 gtk_list_store_set (list_store, &iter,
637 0, fmt_date_template (f->type, f->w),
641 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->date_format_treeview),
642 GTK_TREE_MODEL (list_store));
644 g_object_unref (list_store);
646 g_signal_connect (dialog->date_format_treeview, "cursor-changed",
647 G_CALLBACK (set_date_format_from_treeview), dialog);
650 /* populate the dollar treeview */
652 renderer = gtk_cell_renderer_text_new ();
654 column = gtk_tree_view_column_new_with_attributes ("Title",
660 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview),
664 list_store = gtk_list_store_new (1, G_TYPE_STRING);
666 for (i = 0 ; i < sizeof (dollar_format)/sizeof (dollar_format[0]) ; ++i)
668 char *template = settings_dollar_template (&dollar_format[i]);
669 gtk_list_store_append (list_store, &iter);
670 gtk_list_store_set (list_store, &iter,
676 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dollar_treeview),
677 GTK_TREE_MODEL (list_store));
679 g_object_unref (list_store);
681 g_signal_connect (dialog->dollar_treeview,
683 G_CALLBACK (set_dollar_format_from_treeview), dialog);
685 g_signal_connect_swapped (dialog->dollar_treeview,
687 G_CALLBACK (update_width_decimals), dialog);
690 /* populate the custom treeview */
692 renderer = gtk_cell_renderer_text_new ();
694 column = gtk_tree_view_column_new_with_attributes ("Title",
700 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview),
704 list_store = gtk_list_store_new (1, G_TYPE_STRING);
706 for (i = 0 ; i < 5 ; ++i)
708 enum fmt_type cc_fmts[5] = {FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE};
709 gtk_list_store_append (list_store, &iter);
710 gtk_list_store_set (list_store, &iter,
711 0, fmt_name (cc_fmts[i]),
715 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->custom_treeview),
716 GTK_TREE_MODEL (list_store));
718 g_object_unref (list_store);
721 g_signal_connect (dialog->custom_treeview,
723 G_CALLBACK (set_custom_format_from_treeview), dialog);
726 g_signal_connect (dialog->custom_treeview,
728 G_CALLBACK (preview_custom), dialog);
731 g_signal_connect (dialog->entry_width, "changed",
732 G_CALLBACK (on_width_changed), dialog);
733 g_signal_connect (dialog->entry_decimals, "changed",
734 G_CALLBACK (on_decimals_changed), dialog);
736 g_signal_connect (dialog->entry_width,
738 G_CALLBACK (preview_custom), dialog);
741 g_signal_connect (dialog->entry_decimals,
743 G_CALLBACK (preview_custom), dialog);
747 g_object_unref (xml);
749 psppire_var_type_dialog_set_state (dialog);
755 /* Set a particular button to be active */
757 var_type_dialog_set_active_button (PsppireVarTypeDialog *dialog, gint b)
759 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radioButton[b]),
766 select_treeview_at_index (GtkTreeView *treeview, int index)
770 path = gtk_tree_path_new_from_indices (index, -1);
771 gtk_tree_view_set_cursor (treeview, path, 0, 0);
772 gtk_tree_path_free (path);
776 find_format (const struct fmt_spec *target,
777 const struct fmt_spec formats[], int n_formats)
781 for (i = 0; i < n_formats; i++)
782 if (fmt_equal (target, &formats[i]))
789 find_format_type (int target, const int types[], int n_types)
793 for (i = 0; i < n_types; i++)
794 if (target == types[i])
800 /* Set up the state of the dialog box to match the variable VAR */
802 psppire_var_type_dialog_set_state (PsppireVarTypeDialog *dialog)
806 g_return_if_fail (dialog != NULL);
808 /* Populate the radio button states */
809 switch (dialog->base_format.type)
813 button = BUTTON_NUMERIC;
816 button = BUTTON_STRING;
819 button = BUTTON_COMMA;
825 button = BUTTON_DOLLAR;
842 button = BUTTON_DATE;
849 button = BUTTON_CUSTOM;
853 var_type_dialog_set_active_button (dialog, button);
854 refresh_active_button (dialog);
855 on_active_button_change (GTK_TOGGLE_BUTTON (dialog->radioButton[button]),