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[] =
57 {FMT_DATETIME, 17, 0},
58 {FMT_DATETIME, 20, 0},
64 static const struct fmt_spec dollar_format[] =
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 const struct fmt_spec *format);
96 static int find_format (const 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);
130 psppire_var_type_dialog_set_format (obj, g_value_get_boxed (value));
133 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
139 psppire_var_type_dialog_get_property (GObject *object,
144 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
149 g_value_set_boxed (value, &obj->fmt_l);
152 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
158 psppire_var_type_dialog_set_format (PsppireVarTypeDialog *dialog,
159 const struct fmt_spec *format)
161 dialog->base_format = *format;
162 psppire_var_type_dialog_set_state (dialog);
165 static const struct fmt_spec *
166 psppire_var_type_dialog_get_format (const PsppireVarTypeDialog *dialog)
168 return &dialog->fmt_l;
172 psppire_var_type_dialog_init (PsppireVarTypeDialog *obj)
174 /* We do all of our work on widgets in the constructor function, because that
175 runs after the construction properties have been set. Otherwise
176 PsppireDialog's "orientation" property hasn't been set and therefore we
177 have no box to populate. */
178 obj->base_format = F_8_0;
183 psppire_var_type_dialog_class_init (PsppireVarTypeDialogClass *class)
185 GObjectClass *gobject_class;
186 gobject_class = G_OBJECT_CLASS (class);
188 gobject_class->constructor = psppire_var_type_dialog_constructor;
189 gobject_class->set_property = psppire_var_type_dialog_set_property;
190 gobject_class->get_property = psppire_var_type_dialog_get_property;
192 g_object_class_install_property (
193 gobject_class, PROP_FORMAT,
194 g_param_spec_boxed ("format",
196 "The format being edited.",
198 G_PARAM_READABLE | G_PARAM_WRITABLE));
201 PsppireVarTypeDialog *
202 psppire_var_type_dialog_new (const struct fmt_spec *format)
204 return PSPPIRE_VAR_TYPE_DIALOG (
205 g_object_new (PSPPIRE_TYPE_VAR_TYPE_DIALOG,
211 psppire_var_type_dialog_run (GtkWindow *parent_window,
212 struct fmt_spec *format)
214 PsppireVarTypeDialog *dialog;
216 dialog = psppire_var_type_dialog_new (format);
217 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
218 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
219 gtk_widget_show (GTK_WIDGET (dialog));
221 gint result = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
222 if (result == GTK_RESPONSE_OK)
223 *format = *psppire_var_type_dialog_get_format (dialog);
225 gtk_widget_destroy (GTK_WIDGET (dialog));
231 /* callback for when any of the radio buttons are toggled */
233 on_toggle (GtkToggleButton *togglebutton, gpointer dialog_)
235 PsppireVarTypeDialog *dialog = dialog_;
237 if (gtk_toggle_button_get_active (togglebutton) == TRUE)
238 refresh_active_button (dialog);
242 refresh_active_button (PsppireVarTypeDialog *dialog)
246 for (i = 0; i < num_BUTTONS; i++)
248 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (dialog->radioButton[i]);
250 if (gtk_toggle_button_get_active (toggle))
252 if (dialog->active_button != i)
254 dialog->active_button = i;
255 on_active_button_change (toggle, dialog);
261 g_return_if_reached ();
265 update_adj_ranges (PsppireVarTypeDialog *dialog)
267 enum fmt_type type = dialog->fmt_l.type;
268 const enum fmt_use use = FMT_FOR_OUTPUT;
269 int min_w = fmt_min_width (type, use);
270 int max_w = fmt_max_width (type, use);
271 int max_d = fmt_max_decimals (type, max_w, use);
273 g_object_set (dialog->adj_width,
274 "lower", (double) min_w,
275 "upper", (double) max_w,
278 g_object_set (dialog->adj_decimals,
280 "upper", (double) max_d,
284 /* callback for when any of the radio buttons are toggled */
286 on_active_button_change (GtkToggleButton *togglebutton,
287 PsppireVarTypeDialog *dialog)
292 W_DATE_FORMATS = 1 << 2,
293 W_DOLLAR_FORMATS = 1 << 3,
294 W_CC_FORMATS = 1 << 4,
297 enum widgets widgets;
300 switch (dialog->active_button)
305 case BUTTON_SCIENTIFIC:
306 widgets = W_WIDTH | W_DECIMALS;
314 widgets = W_DATE_FORMATS;
318 widgets = W_DOLLAR_FORMATS;
322 widgets = W_CC_FORMATS | W_WIDTH | W_DECIMALS;
326 /* No button active */
330 gtk_widget_set_visible (dialog->width_decimals, (widgets & W_WIDTH) != 0);
331 gtk_widget_set_visible (dialog->entry_width, (widgets & W_WIDTH) != 0);
332 gtk_widget_set_visible (dialog->entry_decimals, (widgets & W_DECIMALS) != 0);
333 gtk_widget_set_visible (dialog->label_decimals, (widgets & W_DECIMALS) != 0);
334 gtk_widget_set_visible (dialog->date_format_list,
335 (widgets & W_DATE_FORMATS) != 0);
336 gtk_widget_set_visible (dialog->custom_currency_hbox,
337 (widgets & W_CC_FORMATS) != 0);
338 gtk_widget_set_visible (dialog->dollar_window,
339 (widgets & W_DOLLAR_FORMATS) != 0);
341 dialog->fmt_l = dialog->base_format;
343 switch (dialog->active_button)
346 dialog->fmt_l.type = FMT_F;
349 dialog->fmt_l.type = FMT_COMMA;
352 dialog->fmt_l.type = FMT_DOT;
354 case BUTTON_SCIENTIFIC:
355 dialog->fmt_l.type = FMT_E;
358 dialog->fmt_l.type = FMT_A;
361 indx = find_format (&dialog->fmt_l, date_format,
362 sizeof date_format / sizeof *date_format);
363 select_treeview_at_index (dialog->date_format_treeview, indx);
364 dialog->fmt_l = date_format[indx];
367 indx = find_format (&dialog->fmt_l, dollar_format,
368 sizeof dollar_format / sizeof *dollar_format);
369 select_treeview_at_index (dialog->dollar_treeview, indx);
370 dialog->fmt_l = dollar_format[indx];
373 indx = find_format_type (dialog->fmt_l.type, cc_format,
374 sizeof cc_format / sizeof *cc_format);
375 select_treeview_at_index (dialog->custom_treeview, indx);
376 dialog->fmt_l.type = cc_format[indx];
380 fmt_fix_output (&dialog->fmt_l);
381 update_adj_ranges (dialog);
382 update_width_decimals (dialog);
386 add_to_group (GtkWidget *w, gpointer data)
388 GtkSizeGroup *sg = data;
390 gtk_size_group_add_widget (sg, w);
393 /* Set the local width and decimals entry boxes to reflec the local format */
395 update_width_decimals (const PsppireVarTypeDialog *dialog)
397 gtk_adjustment_set_value (dialog->adj_width, dialog->fmt_l.w);
398 gtk_adjustment_set_value (dialog->adj_decimals, dialog->fmt_l.d);
402 on_width_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
404 int w = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_width)));
405 fmt_change_width (&dialog->fmt_l, w, FMT_FOR_OUTPUT);
406 update_width_decimals (dialog);
410 on_decimals_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
412 int d = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals)));
413 fmt_change_decimals (&dialog->fmt_l, d, FMT_FOR_OUTPUT);
414 update_width_decimals (dialog);
417 /* Callback for when the custom treeview row is changed.
418 It sets dialog box to reflect the selected format */
420 preview_custom (GtkWidget *w, gpointer data)
424 PsppireVarTypeDialog *dialog = data;
426 if (dialog->active_button != BUTTON_CUSTOM)
429 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals));
430 dialog->fmt_l.d = atoi (text);
432 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_width));
433 dialog->fmt_l.w = atoi (text);
436 if (! fmt_check_output (&dialog->fmt_l))
438 gtk_label_set_text (GTK_LABEL (dialog->label_psample), "---");
439 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), "---");
447 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
448 gtk_label_set_text (GTK_LABEL (dialog->label_psample), sample_text);
449 g_free (sample_text);
452 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
453 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), sample_text);
454 g_free (sample_text);
460 get_index_from_treeview (GtkTreeView *treeview)
462 GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
468 if (selection == NULL)
471 gtk_tree_selection_get_selected (selection, &model, &iter);
472 path = gtk_tree_model_get_path (model, &iter);
473 if (!path || gtk_tree_path_get_depth (path) < 1)
476 index = gtk_tree_path_get_indices (path)[0];
477 gtk_tree_path_free (path);
482 /* Callback for when a date treeview row is changed.
483 It sets the fmt_l_spec to reflect the selected format */
485 set_date_format_from_treeview (GtkTreeView *treeview,
486 PsppireVarTypeDialog *dialog)
488 gint idx = get_index_from_treeview (treeview);
492 dialog->fmt_l = date_format[idx];
495 /* Callback for when a dollar treeview row is changed.
496 It sets the fmt_l_spec to reflect the selected format */
498 set_dollar_format_from_treeview (GtkTreeView *treeview,
499 PsppireVarTypeDialog *dialog)
501 gint idx = get_index_from_treeview (treeview);
505 dialog->fmt_l = dollar_format[idx];
508 /* Callback for when a treeview row is changed.
509 It sets the type of the fmt_l to reflect the selected type */
511 set_custom_format_from_treeview (GtkTreeView *treeview,
512 PsppireVarTypeDialog *dialog)
514 gint idx = get_index_from_treeview (treeview);
518 dialog->fmt_l.type = cc_format[idx];
519 update_adj_ranges (dialog);
520 fmt_fix_output (&dialog->fmt_l);
521 update_width_decimals (dialog);
524 /* Create the structure */
526 psppire_var_type_dialog_constructor (GType type,
528 GObjectConstructParam *properties)
530 PsppireVarTypeDialog *dialog;
531 GtkContainer *content_area;
536 obj = G_OBJECT_CLASS (psppire_var_type_dialog_parent_class)->constructor (
537 type, n_properties, properties);
538 dialog = PSPPIRE_VAR_TYPE_DIALOG (obj);
540 g_object_set (dialog, "help-page", "Input-and-Output-Formats",
541 "title", _("Variable Type and Format"), NULL);
543 xml = builder_new ("var-type-dialog.ui");
545 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
546 gtk_container_add (GTK_CONTAINER (content_area),
547 get_widget_assert (xml, "var-type-dialog"));
549 dialog->active_button = -1;
551 g_signal_connect (dialog, "delete-event",
552 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
554 dialog->radioButton[BUTTON_NUMERIC] =
555 get_widget_assert (xml,"radiobutton1");
556 dialog->radioButton[BUTTON_COMMA] =
557 get_widget_assert (xml,"radiobutton2");
558 dialog->radioButton[BUTTON_DOT] =
559 get_widget_assert (xml,"radiobutton3");
560 dialog->radioButton[BUTTON_SCIENTIFIC] =
561 get_widget_assert (xml,"radiobutton4");
562 dialog->radioButton[BUTTON_DATE] =
563 get_widget_assert (xml,"radiobutton5");
564 dialog->radioButton[BUTTON_DOLLAR] =
565 get_widget_assert (xml,"radiobutton6");
566 dialog->radioButton[BUTTON_CUSTOM] =
567 get_widget_assert (xml,"radiobutton7");
568 dialog->radioButton[BUTTON_STRING] =
569 get_widget_assert (xml,"radiobutton8");
572 dialog->date_format_list = get_widget_assert (xml, "scrolledwindow4");
573 dialog->width_decimals = get_widget_assert (xml, "width_decimals");
574 dialog->label_decimals = get_widget_assert (xml, "decimals_label");
575 dialog->entry_decimals = get_widget_assert (xml, "decimals_entry");
576 dialog->adj_decimals = gtk_spin_button_get_adjustment (
577 GTK_SPIN_BUTTON (dialog->entry_decimals));
579 dialog->label_psample = get_widget_assert (xml, "psample_label");
580 dialog->label_nsample = get_widget_assert (xml, "nsample_label");
583 dialog->entry_width = get_widget_assert (xml,"width_entry");
584 dialog->adj_width = gtk_spin_button_get_adjustment (
585 GTK_SPIN_BUTTON (dialog->entry_width));
586 dialog->custom_currency_hbox = get_widget_assert (xml,
587 "custom_currency_hbox");
589 dialog->dollar_window = get_widget_assert (xml, "dollar_window");
590 dialog->dollar_treeview =
591 GTK_TREE_VIEW (get_widget_assert (xml, "dollar_treeview"));
593 dialog->custom_treeview =
594 GTK_TREE_VIEW (get_widget_assert (xml, "custom_treeview"));
600 GtkListStore *list_store ;
602 GtkTreeViewColumn *column;
603 GtkCellRenderer *renderer ;
605 /* The "middle_box" is a vbox with serveral children.
606 However only one child is ever shown at a time.
607 We need to make sure that they all have the same width, to avoid
608 upleasant resizing effects */
609 GtkSizeGroup *sizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
611 gtk_container_foreach (GTK_CONTAINER (get_widget_assert (xml, "middle_box")),
612 add_to_group, sizeGroup);
615 for (i = 0 ; i < num_BUTTONS; ++i)
616 g_signal_connect (dialog->radioButton[i], "toggled",
617 G_CALLBACK (on_toggle), dialog);
619 /* Populate the date format tree view */
620 dialog->date_format_treeview = GTK_TREE_VIEW (get_widget_assert (xml,
621 "date_format_list_view"));
623 renderer = gtk_cell_renderer_text_new ();
625 column = gtk_tree_view_column_new_with_attributes ("Title",
631 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->date_format_treeview),
635 list_store = gtk_list_store_new (1, G_TYPE_STRING);
637 for (i = 0 ; i < sizeof (date_format) / sizeof (date_format[0]) ; ++i)
639 const struct fmt_spec *f = &date_format[i];
640 gtk_list_store_append (list_store, &iter);
641 gtk_list_store_set (list_store, &iter,
642 0, fmt_date_template (f->type, f->w),
646 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->date_format_treeview),
647 GTK_TREE_MODEL (list_store));
649 g_object_unref (list_store);
651 g_signal_connect (dialog->date_format_treeview, "cursor-changed",
652 G_CALLBACK (set_date_format_from_treeview), dialog);
655 /* populate the dollar treeview */
657 renderer = gtk_cell_renderer_text_new ();
659 column = gtk_tree_view_column_new_with_attributes ("Title",
665 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview),
669 list_store = gtk_list_store_new (1, G_TYPE_STRING);
671 for (i = 0 ; i < sizeof (dollar_format)/sizeof (dollar_format[0]) ; ++i)
673 char *template = settings_dollar_template (&dollar_format[i]);
674 gtk_list_store_append (list_store, &iter);
675 gtk_list_store_set (list_store, &iter,
681 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dollar_treeview),
682 GTK_TREE_MODEL (list_store));
684 g_object_unref (list_store);
686 g_signal_connect (dialog->dollar_treeview,
688 G_CALLBACK (set_dollar_format_from_treeview), dialog);
690 g_signal_connect_swapped (dialog->dollar_treeview,
692 G_CALLBACK (update_width_decimals), dialog);
695 /* populate the custom treeview */
697 renderer = gtk_cell_renderer_text_new ();
699 column = gtk_tree_view_column_new_with_attributes ("Title",
705 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview),
709 list_store = gtk_list_store_new (1, G_TYPE_STRING);
711 for (i = 0 ; i < 5 ; ++i)
713 enum fmt_type cc_fmts[5] = {FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE};
714 gtk_list_store_append (list_store, &iter);
715 gtk_list_store_set (list_store, &iter,
716 0, fmt_name (cc_fmts[i]),
720 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->custom_treeview),
721 GTK_TREE_MODEL (list_store));
723 g_object_unref (list_store);
726 g_signal_connect (dialog->custom_treeview,
728 G_CALLBACK (set_custom_format_from_treeview), dialog);
731 g_signal_connect (dialog->custom_treeview,
733 G_CALLBACK (preview_custom), dialog);
736 g_signal_connect (dialog->entry_width, "changed",
737 G_CALLBACK (on_width_changed), dialog);
738 g_signal_connect (dialog->entry_decimals, "changed",
739 G_CALLBACK (on_decimals_changed), dialog);
741 g_signal_connect (dialog->entry_width,
743 G_CALLBACK (preview_custom), dialog);
746 g_signal_connect (dialog->entry_decimals,
748 G_CALLBACK (preview_custom), dialog);
752 g_object_unref (xml);
754 psppire_var_type_dialog_set_state (dialog);
760 /* Set a particular button to be active */
762 var_type_dialog_set_active_button (PsppireVarTypeDialog *dialog, gint b)
764 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radioButton[b]),
771 select_treeview_at_index (GtkTreeView *treeview, int index)
775 path = gtk_tree_path_new_from_indices (index, -1);
776 gtk_tree_view_set_cursor (treeview, path, 0, 0);
777 gtk_tree_path_free (path);
781 find_format (const struct fmt_spec *target,
782 const struct fmt_spec formats[], int n_formats)
786 for (i = 0; i < n_formats; i++)
787 if (fmt_equal (target, &formats[i]))
794 find_format_type (int target, const int types[], int n_types)
798 for (i = 0; i < n_types; i++)
799 if (target == types[i])
805 /* Set up the state of the dialog box to match the variable VAR */
807 psppire_var_type_dialog_set_state (PsppireVarTypeDialog *dialog)
811 g_return_if_fail (dialog != NULL);
813 /* Populate the radio button states */
814 switch (dialog->base_format.type)
818 button = BUTTON_NUMERIC;
821 button = BUTTON_STRING;
824 button = BUTTON_COMMA;
830 button = BUTTON_DOLLAR;
847 button = BUTTON_DATE;
854 button = BUTTON_CUSTOM;
858 var_type_dialog_set_active_button (dialog, button);
859 refresh_active_button (dialog);
860 on_active_button_change (GTK_TOGGLE_BUTTON (dialog->radioButton[button]),