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"
36 #define _(msgid) gettext (msgid)
37 #define N_(msgid) msgid
39 static const struct fmt_spec date_format[] =
41 { .type = FMT_DATE, .w = 11, .d = 0 },
42 { .type = FMT_DATE, .w = 9, .d = 0 },
43 { .type = FMT_ADATE, .w = 10, .d = 0 },
44 { .type = FMT_ADATE, .w = 8, .d = 0 },
45 { .type = FMT_EDATE, .w = 10, .d = 0 },
46 { .type = FMT_EDATE, .w = 8, .d = 0 },
47 { .type = FMT_SDATE, .w = 10, .d = 0 },
48 { .type = FMT_SDATE, .w = 8, .d = 0 },
49 { .type = FMT_JDATE, .w = 5, .d = 0 },
50 { .type = FMT_JDATE, .w = 7, .d = 0 },
51 { .type = FMT_QYR, .w = 8, .d = 0 },
52 { .type = FMT_QYR, .w = 6, .d = 0 },
53 { .type = FMT_MOYR, .w = 8, .d = 0 },
54 { .type = FMT_MOYR, .w = 6, .d = 0 },
55 { .type = FMT_WKYR, .w = 10, .d = 0 },
56 { .type = FMT_WKYR, .w = 8, .d = 0 },
57 { .type = FMT_DATETIME, .w = 17, .d = 0 },
58 { .type = FMT_DATETIME, .w = 20, .d = 0 },
59 { .type = FMT_YMDHMS, .w = 16, .d = 0 },
60 { .type = FMT_YMDHMS, .w = 20, .d = 0 }
64 static const struct fmt_spec dollar_format[] =
66 { .type = FMT_DOLLAR, .w = 2, .d = 0 },
67 { .type = FMT_DOLLAR, .w = 3, .d = 0 },
68 { .type = FMT_DOLLAR, .w = 4, .d = 0 },
69 { .type = FMT_DOLLAR, .w = 7, .d = 2 },
70 { .type = FMT_DOLLAR, .w = 6, .d = 0 },
71 { .type = FMT_DOLLAR, .w = 9, .d = 2 },
72 { .type = FMT_DOLLAR, .w = 8, .d = 0 },
73 { .type = FMT_DOLLAR, .w = 11, .d = 2 },
74 { .type = FMT_DOLLAR, .w = 12, .d = 0 },
75 { .type = FMT_DOLLAR, .w = 15, .d = 2 },
76 { .type = FMT_DOLLAR, .w = 16, .d = 0 },
77 { .type = FMT_DOLLAR, .w = 19, .d = 2 }
80 static const int cc_format[] =
89 static GObject *psppire_var_type_dialog_constructor (GType type, guint,
90 GObjectConstructParam *);
91 static void psppire_var_type_dialog_set_state (PsppireVarTypeDialog *);
93 static void psppire_var_type_dialog_set_format (PsppireVarTypeDialog *dialog,
94 struct fmt_spec format);
96 static int find_format (struct fmt_spec target,
97 const struct fmt_spec formats[], int n_formats);
98 static int find_format_type (int target, const int types[], int n_types);
100 static void select_treeview_at_index (GtkTreeView *, int index);
102 static void update_width_decimals (const PsppireVarTypeDialog *);
103 static void refresh_active_button (PsppireVarTypeDialog *);
104 static void on_active_button_change (GtkToggleButton *,
105 PsppireVarTypeDialog *);
106 static void on_width_changed (GtkEntry *, PsppireVarTypeDialog *);
107 static void on_decimals_changed (GtkEntry *, PsppireVarTypeDialog *);
109 G_DEFINE_TYPE (PsppireVarTypeDialog,
110 psppire_var_type_dialog,
111 PSPPIRE_TYPE_DIALOG);
120 psppire_var_type_dialog_set_property (GObject *object,
125 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
131 const struct fmt_spec *f = g_value_get_boxed (value);
132 psppire_var_type_dialog_set_format (obj, *f);
136 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
142 psppire_var_type_dialog_get_property (GObject *object,
147 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
152 g_value_set_boxed (value, &obj->fmt_l);
155 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
161 psppire_var_type_dialog_set_format (PsppireVarTypeDialog *dialog,
162 struct fmt_spec format)
164 dialog->base_format = format;
165 psppire_var_type_dialog_set_state (dialog);
168 static struct fmt_spec
169 psppire_var_type_dialog_get_format (const PsppireVarTypeDialog *dialog)
171 return dialog->fmt_l;
175 psppire_var_type_dialog_init (PsppireVarTypeDialog *obj)
177 /* We do all of our work on widgets in the constructor function, because that
178 runs after the construction properties have been set. Otherwise
179 PsppireDialog's "orientation" property hasn't been set and therefore we
180 have no box to populate. */
181 obj->base_format = F_8_0;
186 psppire_var_type_dialog_class_init (PsppireVarTypeDialogClass *class)
188 GObjectClass *gobject_class;
189 gobject_class = G_OBJECT_CLASS (class);
191 gobject_class->constructor = psppire_var_type_dialog_constructor;
192 gobject_class->set_property = psppire_var_type_dialog_set_property;
193 gobject_class->get_property = psppire_var_type_dialog_get_property;
195 g_object_class_install_property (
196 gobject_class, PROP_FORMAT,
197 g_param_spec_boxed ("format",
199 "The format being edited.",
201 G_PARAM_READABLE | G_PARAM_WRITABLE));
204 PsppireVarTypeDialog *
205 psppire_var_type_dialog_new (const struct fmt_spec *format)
207 return PSPPIRE_VAR_TYPE_DIALOG (
208 g_object_new (PSPPIRE_TYPE_VAR_TYPE_DIALOG,
214 psppire_var_type_dialog_run (GtkWindow *parent_window,
215 struct fmt_spec *format)
217 PsppireVarTypeDialog *dialog;
219 dialog = psppire_var_type_dialog_new (format);
220 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
221 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
222 gtk_widget_show (GTK_WIDGET (dialog));
224 gint result = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
225 if (result == GTK_RESPONSE_OK)
226 *format = psppire_var_type_dialog_get_format (dialog);
228 gtk_widget_destroy (GTK_WIDGET (dialog));
234 /* callback for when any of the radio buttons are toggled */
236 on_toggle (GtkToggleButton *togglebutton, gpointer dialog_)
238 PsppireVarTypeDialog *dialog = dialog_;
240 if (gtk_toggle_button_get_active (togglebutton) == TRUE)
241 refresh_active_button (dialog);
245 refresh_active_button (PsppireVarTypeDialog *dialog)
249 for (i = 0; i < num_BUTTONS; i++)
251 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (dialog->radioButton[i]);
253 if (gtk_toggle_button_get_active (toggle))
255 if (dialog->active_button != i)
257 dialog->active_button = i;
258 on_active_button_change (toggle, dialog);
264 g_return_if_reached ();
268 update_adj_ranges (PsppireVarTypeDialog *dialog)
270 enum fmt_type type = dialog->fmt_l.type;
271 const enum fmt_use use = FMT_FOR_OUTPUT;
272 int min_w = fmt_min_width (type, use);
273 int max_w = fmt_max_width (type, use);
274 int max_d = fmt_max_decimals (type, max_w, use);
276 g_object_set (dialog->adj_width,
277 "lower", (double) min_w,
278 "upper", (double) max_w,
281 g_object_set (dialog->adj_decimals,
283 "upper", (double) max_d,
287 /* callback for when any of the radio buttons are toggled */
289 on_active_button_change (GtkToggleButton *togglebutton,
290 PsppireVarTypeDialog *dialog)
295 W_DATE_FORMATS = 1 << 2,
296 W_DOLLAR_FORMATS = 1 << 3,
297 W_CC_FORMATS = 1 << 4,
300 enum widgets widgets;
303 switch (dialog->active_button)
308 case BUTTON_SCIENTIFIC:
309 widgets = W_WIDTH | W_DECIMALS;
317 widgets = W_DATE_FORMATS;
321 widgets = W_DOLLAR_FORMATS;
325 widgets = W_CC_FORMATS | W_WIDTH | W_DECIMALS;
329 /* No button active */
333 gtk_widget_set_visible (dialog->width_decimals, (widgets & W_WIDTH) != 0);
334 gtk_widget_set_visible (dialog->entry_width, (widgets & W_WIDTH) != 0);
335 gtk_widget_set_visible (dialog->entry_decimals, (widgets & W_DECIMALS) != 0);
336 gtk_widget_set_visible (dialog->label_decimals, (widgets & W_DECIMALS) != 0);
337 gtk_widget_set_visible (dialog->date_format_list,
338 (widgets & W_DATE_FORMATS) != 0);
339 gtk_widget_set_visible (dialog->custom_currency_hbox,
340 (widgets & W_CC_FORMATS) != 0);
341 gtk_widget_set_visible (dialog->dollar_window,
342 (widgets & W_DOLLAR_FORMATS) != 0);
344 dialog->fmt_l = dialog->base_format;
346 switch (dialog->active_button)
349 dialog->fmt_l.type = FMT_F;
352 dialog->fmt_l.type = FMT_COMMA;
355 dialog->fmt_l.type = FMT_DOT;
357 case BUTTON_SCIENTIFIC:
358 dialog->fmt_l.type = FMT_E;
361 dialog->fmt_l.type = FMT_A;
364 indx = find_format (dialog->fmt_l, date_format,
365 sizeof date_format / sizeof *date_format);
366 select_treeview_at_index (dialog->date_format_treeview, indx);
367 dialog->fmt_l = date_format[indx];
370 indx = find_format (dialog->fmt_l, dollar_format,
371 sizeof dollar_format / sizeof *dollar_format);
372 select_treeview_at_index (dialog->dollar_treeview, indx);
373 dialog->fmt_l = dollar_format[indx];
376 indx = find_format_type (dialog->fmt_l.type, cc_format,
377 sizeof cc_format / sizeof *cc_format);
378 select_treeview_at_index (dialog->custom_treeview, indx);
379 dialog->fmt_l.type = cc_format[indx];
383 fmt_fix_output (&dialog->fmt_l);
384 update_adj_ranges (dialog);
385 update_width_decimals (dialog);
389 add_to_group (GtkWidget *w, gpointer data)
391 GtkSizeGroup *sg = data;
393 gtk_size_group_add_widget (sg, w);
396 /* Set the local width and decimals entry boxes to reflec the local format */
398 update_width_decimals (const PsppireVarTypeDialog *dialog)
400 gtk_adjustment_set_value (dialog->adj_width, dialog->fmt_l.w);
401 gtk_adjustment_set_value (dialog->adj_decimals, dialog->fmt_l.d);
405 on_width_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
407 int w = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_width)));
408 fmt_change_width (&dialog->fmt_l, w, FMT_FOR_OUTPUT);
409 update_width_decimals (dialog);
413 on_decimals_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
415 int d = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals)));
416 fmt_change_decimals (&dialog->fmt_l, d, FMT_FOR_OUTPUT);
417 update_width_decimals (dialog);
420 /* Callback for when the custom treeview row is changed.
421 It sets dialog box to reflect the selected format */
423 preview_custom (GtkWidget *w, gpointer data)
427 PsppireVarTypeDialog *dialog = data;
429 if (dialog->active_button != BUTTON_CUSTOM)
432 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals));
433 dialog->fmt_l.d = atoi (text);
435 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_width));
436 dialog->fmt_l.w = atoi (text);
438 if (! fmt_check_output (dialog->fmt_l))
440 gtk_label_set_text (GTK_LABEL (dialog->label_psample), "---");
441 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), "---");
449 sample_text = g_strchug (data_out (&v, NULL, dialog->fmt_l,
450 settings_get_fmt_settings ()));
451 gtk_label_set_text (GTK_LABEL (dialog->label_psample), sample_text);
452 g_free (sample_text);
455 sample_text = g_strchug (data_out (&v, NULL, dialog->fmt_l,
456 settings_get_fmt_settings ()));
457 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), sample_text);
458 g_free (sample_text);
463 get_index_from_treeview (GtkTreeView *treeview)
465 GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
471 if (selection == NULL)
474 gtk_tree_selection_get_selected (selection, &model, &iter);
475 path = gtk_tree_model_get_path (model, &iter);
476 if (!path || gtk_tree_path_get_depth (path) < 1)
479 index = gtk_tree_path_get_indices (path)[0];
480 gtk_tree_path_free (path);
485 /* Callback for when a date treeview row is changed.
486 It sets the fmt_l_spec to reflect the selected format */
488 set_date_format_from_treeview (GtkTreeView *treeview,
489 PsppireVarTypeDialog *dialog)
491 gint idx = get_index_from_treeview (treeview);
495 dialog->fmt_l = date_format[idx];
498 /* Callback for when a dollar treeview row is changed.
499 It sets the fmt_l_spec to reflect the selected format */
501 set_dollar_format_from_treeview (GtkTreeView *treeview,
502 PsppireVarTypeDialog *dialog)
504 gint idx = get_index_from_treeview (treeview);
508 dialog->fmt_l = dollar_format[idx];
511 /* Callback for when a treeview row is changed.
512 It sets the type of the fmt_l to reflect the selected type */
514 set_custom_format_from_treeview (GtkTreeView *treeview,
515 PsppireVarTypeDialog *dialog)
517 gint idx = get_index_from_treeview (treeview);
521 dialog->fmt_l.type = cc_format[idx];
522 update_adj_ranges (dialog);
523 fmt_fix_output (&dialog->fmt_l);
524 update_width_decimals (dialog);
527 /* Create the structure */
529 psppire_var_type_dialog_constructor (GType type,
531 GObjectConstructParam *properties)
533 PsppireVarTypeDialog *dialog;
534 GtkContainer *content_area;
539 obj = G_OBJECT_CLASS (psppire_var_type_dialog_parent_class)->constructor (
540 type, n_properties, properties);
541 dialog = PSPPIRE_VAR_TYPE_DIALOG (obj);
543 g_object_set (dialog, "help-page", "Input-and-Output-Formats",
544 "title", _("Variable Type and Format"), NULL);
546 xml = builder_new ("var-type-dialog.ui");
548 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
549 gtk_container_add (GTK_CONTAINER (content_area),
550 get_widget_assert (xml, "var-type-dialog"));
552 dialog->active_button = -1;
554 g_signal_connect (dialog, "delete-event",
555 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
557 dialog->radioButton[BUTTON_NUMERIC] =
558 get_widget_assert (xml,"radiobutton1");
559 dialog->radioButton[BUTTON_COMMA] =
560 get_widget_assert (xml,"radiobutton2");
561 dialog->radioButton[BUTTON_DOT] =
562 get_widget_assert (xml,"radiobutton3");
563 dialog->radioButton[BUTTON_SCIENTIFIC] =
564 get_widget_assert (xml,"radiobutton4");
565 dialog->radioButton[BUTTON_DATE] =
566 get_widget_assert (xml,"radiobutton5");
567 dialog->radioButton[BUTTON_DOLLAR] =
568 get_widget_assert (xml,"radiobutton6");
569 dialog->radioButton[BUTTON_CUSTOM] =
570 get_widget_assert (xml,"radiobutton7");
571 dialog->radioButton[BUTTON_STRING] =
572 get_widget_assert (xml,"radiobutton8");
575 dialog->date_format_list = get_widget_assert (xml, "scrolledwindow4");
576 dialog->width_decimals = get_widget_assert (xml, "width_decimals");
577 dialog->label_decimals = get_widget_assert (xml, "decimals_label");
578 dialog->entry_decimals = get_widget_assert (xml, "decimals_entry");
579 dialog->adj_decimals = gtk_spin_button_get_adjustment (
580 GTK_SPIN_BUTTON (dialog->entry_decimals));
582 dialog->label_psample = get_widget_assert (xml, "psample_label");
583 dialog->label_nsample = get_widget_assert (xml, "nsample_label");
586 dialog->entry_width = get_widget_assert (xml,"width_entry");
587 dialog->adj_width = gtk_spin_button_get_adjustment (
588 GTK_SPIN_BUTTON (dialog->entry_width));
589 dialog->custom_currency_hbox = get_widget_assert (xml,
590 "custom_currency_hbox");
592 dialog->dollar_window = get_widget_assert (xml, "dollar_window");
593 dialog->dollar_treeview =
594 GTK_TREE_VIEW (get_widget_assert (xml, "dollar_treeview"));
596 dialog->custom_treeview =
597 GTK_TREE_VIEW (get_widget_assert (xml, "custom_treeview"));
603 GtkListStore *list_store ;
605 GtkTreeViewColumn *column;
606 GtkCellRenderer *renderer ;
608 /* The "middle_box" is a vbox with serveral children.
609 However only one child is ever shown at a time.
610 We need to make sure that they all have the same width, to avoid
611 upleasant resizing effects */
612 GtkSizeGroup *sizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
614 gtk_container_foreach (GTK_CONTAINER (get_widget_assert (xml, "middle_box")),
615 add_to_group, sizeGroup);
618 for (i = 0 ; i < num_BUTTONS; ++i)
619 g_signal_connect (dialog->radioButton[i], "toggled",
620 G_CALLBACK (on_toggle), dialog);
622 /* Populate the date format tree view */
623 dialog->date_format_treeview = GTK_TREE_VIEW (get_widget_assert (xml,
624 "date_format_list_view"));
626 renderer = gtk_cell_renderer_text_new ();
628 column = gtk_tree_view_column_new_with_attributes ("Title",
634 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->date_format_treeview),
638 list_store = gtk_list_store_new (1, G_TYPE_STRING);
640 for (i = 0 ; i < sizeof (date_format) / sizeof (date_format[0]) ; ++i)
642 struct fmt_spec f = date_format[i];
643 gtk_list_store_append (list_store, &iter);
644 gtk_list_store_set (list_store, &iter,
645 0, fmt_date_template (f.type, f.w),
649 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->date_format_treeview),
650 GTK_TREE_MODEL (list_store));
652 g_object_unref (list_store);
654 g_signal_connect (dialog->date_format_treeview, "cursor-changed",
655 G_CALLBACK (set_date_format_from_treeview), dialog);
658 /* populate the dollar treeview */
660 renderer = gtk_cell_renderer_text_new ();
662 column = gtk_tree_view_column_new_with_attributes ("Title",
668 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview),
672 list_store = gtk_list_store_new (1, G_TYPE_STRING);
674 for (i = 0 ; i < sizeof (dollar_format)/sizeof (dollar_format[0]) ; ++i)
676 char *template = settings_dollar_template (dollar_format[i]);
677 gtk_list_store_append (list_store, &iter);
678 gtk_list_store_set (list_store, &iter,
684 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dollar_treeview),
685 GTK_TREE_MODEL (list_store));
687 g_object_unref (list_store);
689 g_signal_connect (dialog->dollar_treeview,
691 G_CALLBACK (set_dollar_format_from_treeview), dialog);
693 g_signal_connect_swapped (dialog->dollar_treeview,
695 G_CALLBACK (update_width_decimals), dialog);
698 /* populate the custom treeview */
700 renderer = gtk_cell_renderer_text_new ();
702 column = gtk_tree_view_column_new_with_attributes ("Title",
708 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview),
712 list_store = gtk_list_store_new (1, G_TYPE_STRING);
714 for (i = 0 ; i < 5 ; ++i)
716 enum fmt_type cc_fmts[5] = {FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE};
717 gtk_list_store_append (list_store, &iter);
718 gtk_list_store_set (list_store, &iter,
719 0, fmt_name (cc_fmts[i]),
723 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->custom_treeview),
724 GTK_TREE_MODEL (list_store));
726 g_object_unref (list_store);
729 g_signal_connect (dialog->custom_treeview,
731 G_CALLBACK (set_custom_format_from_treeview), dialog);
734 g_signal_connect (dialog->custom_treeview,
736 G_CALLBACK (preview_custom), dialog);
739 g_signal_connect (dialog->entry_width, "changed",
740 G_CALLBACK (on_width_changed), dialog);
741 g_signal_connect (dialog->entry_decimals, "changed",
742 G_CALLBACK (on_decimals_changed), dialog);
744 g_signal_connect (dialog->entry_width,
746 G_CALLBACK (preview_custom), dialog);
749 g_signal_connect (dialog->entry_decimals,
751 G_CALLBACK (preview_custom), dialog);
755 g_object_unref (xml);
757 psppire_var_type_dialog_set_state (dialog);
763 /* Set a particular button to be active */
765 var_type_dialog_set_active_button (PsppireVarTypeDialog *dialog, gint b)
767 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radioButton[b]),
774 select_treeview_at_index (GtkTreeView *treeview, int index)
778 path = gtk_tree_path_new_from_indices (index, -1);
779 gtk_tree_view_set_cursor (treeview, path, 0, 0);
780 gtk_tree_path_free (path);
784 find_format (struct fmt_spec target,
785 const struct fmt_spec formats[], int n_formats)
789 for (i = 0; i < n_formats; i++)
790 if (fmt_equal (target, formats[i]))
797 find_format_type (int target, const int types[], int n_types)
801 for (i = 0; i < n_types; i++)
802 if (target == types[i])
808 /* Set up the state of the dialog box to match the variable VAR */
810 psppire_var_type_dialog_set_state (PsppireVarTypeDialog *dialog)
814 g_return_if_fail (dialog != NULL);
816 /* Populate the radio button states */
817 switch (dialog->base_format.type)
821 button = BUTTON_NUMERIC;
824 button = BUTTON_STRING;
827 button = BUTTON_COMMA;
833 button = BUTTON_DOLLAR;
850 button = BUTTON_DATE;
857 button = BUTTON_CUSTOM;
861 var_type_dialog_set_active_button (dialog, button);
862 refresh_active_button (dialog);
863 on_active_button_change (GTK_TOGGLE_BUTTON (dialog->radioButton[button]),