1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2005, 2006, 2010, 2011, 2012, 2015 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},
58 static const struct fmt_spec dollar_format[] =
74 static const int cc_format[] =
83 static GObject *psppire_var_type_dialog_constructor (GType type, guint,
84 GObjectConstructParam *);
85 static void psppire_var_type_dialog_set_state (PsppireVarTypeDialog *);
87 static void psppire_var_type_dialog_set_format (PsppireVarTypeDialog *dialog,
88 const struct fmt_spec *format);
90 static int find_format (const struct fmt_spec *target,
91 const struct fmt_spec formats[], int n_formats);
92 static int find_format_type (int target, const int types[], int n_types);
94 static void select_treeview_at_index (GtkTreeView *, int index);
96 static void update_width_decimals (const PsppireVarTypeDialog *);
97 static void refresh_active_button (PsppireVarTypeDialog *);
98 static void on_active_button_change (GtkToggleButton *,
99 PsppireVarTypeDialog *);
100 static void on_width_changed (GtkEntry *, PsppireVarTypeDialog *);
101 static void on_decimals_changed (GtkEntry *, PsppireVarTypeDialog *);
103 G_DEFINE_TYPE (PsppireVarTypeDialog,
104 psppire_var_type_dialog,
105 PSPPIRE_TYPE_DIALOG);
114 psppire_var_type_dialog_set_property (GObject *object,
119 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
124 psppire_var_type_dialog_set_format (obj, g_value_get_boxed (value));
127 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
133 psppire_var_type_dialog_get_property (GObject *object,
138 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
143 g_value_set_boxed (value, &obj->fmt_l);
146 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
152 psppire_var_type_dialog_set_format (PsppireVarTypeDialog *dialog,
153 const struct fmt_spec *format)
155 dialog->base_format = *format;
156 psppire_var_type_dialog_set_state (dialog);
159 static const struct fmt_spec *
160 psppire_var_type_dialog_get_format (const PsppireVarTypeDialog *dialog)
162 return &dialog->fmt_l;
166 psppire_var_type_dialog_init (PsppireVarTypeDialog *obj)
168 /* We do all of our work on widgets in the constructor function, because that
169 runs after the construction properties have been set. Otherwise
170 PsppireDialog's "orientation" property hasn't been set and therefore we
171 have no box to populate. */
172 obj->base_format = F_8_0;
177 psppire_var_type_dialog_class_init (PsppireVarTypeDialogClass *class)
179 GObjectClass *gobject_class;
180 gobject_class = G_OBJECT_CLASS (class);
182 gobject_class->constructor = psppire_var_type_dialog_constructor;
183 gobject_class->set_property = psppire_var_type_dialog_set_property;
184 gobject_class->get_property = psppire_var_type_dialog_get_property;
186 g_object_class_install_property (
187 gobject_class, PROP_FORMAT,
188 g_param_spec_boxed ("format",
190 "The format being edited.",
192 G_PARAM_READABLE | G_PARAM_WRITABLE));
195 PsppireVarTypeDialog *
196 psppire_var_type_dialog_new (const struct fmt_spec *format)
198 return PSPPIRE_VAR_TYPE_DIALOG (
199 g_object_new (PSPPIRE_TYPE_VAR_TYPE_DIALOG,
205 psppire_var_type_dialog_run (GtkWindow *parent_window,
206 struct fmt_spec *format)
208 PsppireVarTypeDialog *dialog;
210 dialog = psppire_var_type_dialog_new (format);
211 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
212 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
213 gtk_widget_show (GTK_WIDGET (dialog));
215 gint result = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
216 if (result == GTK_RESPONSE_OK)
217 *format = *psppire_var_type_dialog_get_format (dialog);
219 gtk_widget_destroy (GTK_WIDGET (dialog));
225 /* callback for when any of the radio buttons are toggled */
227 on_toggle (GtkToggleButton *togglebutton, gpointer dialog_)
229 PsppireVarTypeDialog *dialog = dialog_;
231 if ( gtk_toggle_button_get_active (togglebutton) == TRUE)
232 refresh_active_button (dialog);
236 refresh_active_button (PsppireVarTypeDialog *dialog)
240 for (i = 0; i < num_BUTTONS; i++)
242 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (dialog->radioButton[i]);
244 if (gtk_toggle_button_get_active (toggle))
246 if (dialog->active_button != i)
248 dialog->active_button = i;
249 on_active_button_change (toggle, dialog);
255 g_return_if_reached ();
259 update_adj_ranges (PsppireVarTypeDialog *dialog)
261 enum fmt_type type = dialog->fmt_l.type;
262 const enum fmt_use use = FMT_FOR_OUTPUT;
263 int min_w = fmt_min_width (type, use);
264 int max_w = fmt_max_width (type, use);
265 int max_d = fmt_max_decimals (type, max_w, use);
267 g_object_set (dialog->adj_width,
268 "lower", (double) min_w,
269 "upper", (double) max_w,
272 g_object_set (dialog->adj_decimals,
274 "upper", (double) max_d,
278 /* callback for when any of the radio buttons are toggled */
280 on_active_button_change (GtkToggleButton *togglebutton,
281 PsppireVarTypeDialog *dialog)
286 W_DATE_FORMATS = 1 << 2,
287 W_DOLLAR_FORMATS = 1 << 3,
288 W_CC_FORMATS = 1 << 4,
291 enum widgets widgets;
294 switch (dialog->active_button)
299 case BUTTON_SCIENTIFIC:
300 widgets = W_WIDTH | W_DECIMALS;
308 widgets = W_DATE_FORMATS;
312 widgets = W_DOLLAR_FORMATS;
316 widgets = W_CC_FORMATS | W_WIDTH | W_DECIMALS;
320 /* No button active */
324 gtk_widget_set_visible (dialog->width_decimals, (widgets & W_WIDTH) != 0);
325 gtk_widget_set_visible (dialog->entry_width, (widgets & W_WIDTH) != 0);
326 gtk_widget_set_visible (dialog->entry_decimals, (widgets & W_DECIMALS) != 0);
327 gtk_widget_set_visible (dialog->label_decimals, (widgets & W_DECIMALS) != 0);
328 gtk_widget_set_visible (dialog->date_format_list,
329 (widgets & W_DATE_FORMATS) != 0);
330 gtk_widget_set_visible (dialog->custom_currency_hbox,
331 (widgets & W_CC_FORMATS) != 0);
332 gtk_widget_set_visible (dialog->dollar_window,
333 (widgets & W_DOLLAR_FORMATS) != 0);
335 dialog->fmt_l = dialog->base_format;
337 switch (dialog->active_button)
340 dialog->fmt_l.type = FMT_F;
343 dialog->fmt_l.type = FMT_COMMA;
346 dialog->fmt_l.type = FMT_DOT;
348 case BUTTON_SCIENTIFIC:
349 dialog->fmt_l.type = FMT_E;
352 dialog->fmt_l.type = FMT_A;
355 indx = find_format (&dialog->fmt_l, date_format,
356 sizeof date_format / sizeof *date_format);
357 select_treeview_at_index (dialog->date_format_treeview, indx);
358 dialog->fmt_l = date_format[indx];
361 indx = find_format (&dialog->fmt_l, dollar_format,
362 sizeof dollar_format / sizeof *dollar_format);
363 select_treeview_at_index (dialog->dollar_treeview, indx);
364 dialog->fmt_l = dollar_format[indx];
367 indx = find_format_type (dialog->fmt_l.type, cc_format,
368 sizeof cc_format / sizeof *cc_format);
369 select_treeview_at_index (dialog->custom_treeview, indx);
370 dialog->fmt_l.type = cc_format[indx];
374 fmt_fix_output (&dialog->fmt_l);
375 update_adj_ranges (dialog);
376 update_width_decimals (dialog);
380 add_to_group (GtkWidget *w, gpointer data)
382 GtkSizeGroup *sg = data;
384 gtk_size_group_add_widget (sg, w);
387 /* Set the local width and decimals entry boxes to reflec the local format */
389 update_width_decimals (const PsppireVarTypeDialog *dialog)
391 gtk_adjustment_set_value (dialog->adj_width, dialog->fmt_l.w);
392 gtk_adjustment_set_value (dialog->adj_decimals, dialog->fmt_l.d);
396 on_width_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
398 int w = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_width)));
399 fmt_change_width (&dialog->fmt_l, w, FMT_FOR_OUTPUT);
400 update_width_decimals (dialog);
404 on_decimals_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
406 int d = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals)));
407 fmt_change_decimals (&dialog->fmt_l, d, FMT_FOR_OUTPUT);
408 update_width_decimals (dialog);
411 /* Callback for when the custom treeview row is changed.
412 It sets dialog box to reflect the selected format */
414 preview_custom (GtkWidget *w, gpointer data)
418 PsppireVarTypeDialog *dialog = data;
420 if ( dialog->active_button != BUTTON_CUSTOM )
423 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals));
424 dialog->fmt_l.d = atoi (text);
426 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_width));
427 dialog->fmt_l.w = atoi (text);
430 if ( ! fmt_check_output (&dialog->fmt_l))
432 gtk_label_set_text (GTK_LABEL (dialog->label_psample), "---");
433 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), "---");
441 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
442 gtk_label_set_text (GTK_LABEL (dialog->label_psample), sample_text);
443 g_free (sample_text);
446 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
447 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), sample_text);
448 g_free (sample_text);
454 get_index_from_treeview (GtkTreeView *treeview)
456 GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
462 if (selection == NULL)
465 gtk_tree_selection_get_selected (selection, &model, &iter);
466 path = gtk_tree_model_get_path (model, &iter);
467 if (!path || gtk_tree_path_get_depth (path) < 1)
470 index = gtk_tree_path_get_indices (path)[0];
471 gtk_tree_path_free (path);
476 /* Callback for when a date treeview row is changed.
477 It sets the fmt_l_spec to reflect the selected format */
479 set_date_format_from_treeview (GtkTreeView *treeview,
480 PsppireVarTypeDialog *dialog)
482 gint idx = get_index_from_treeview (treeview);
486 dialog->fmt_l = date_format[idx];
489 /* Callback for when a dollar treeview row is changed.
490 It sets the fmt_l_spec to reflect the selected format */
492 set_dollar_format_from_treeview (GtkTreeView *treeview,
493 PsppireVarTypeDialog *dialog)
495 gint idx = get_index_from_treeview (treeview);
499 dialog->fmt_l = dollar_format[idx];
502 /* Callback for when a treeview row is changed.
503 It sets the type of the fmt_l to reflect the selected type */
505 set_custom_format_from_treeview (GtkTreeView *treeview,
506 PsppireVarTypeDialog *dialog)
508 gint idx = get_index_from_treeview (treeview);
512 dialog->fmt_l.type = cc_format[idx];
513 update_adj_ranges (dialog);
514 fmt_fix_output (&dialog->fmt_l);
515 update_width_decimals (dialog);
518 /* Create the structure */
520 psppire_var_type_dialog_constructor (GType type,
522 GObjectConstructParam *properties)
524 PsppireVarTypeDialog *dialog;
525 GtkContainer *content_area;
530 obj = G_OBJECT_CLASS (psppire_var_type_dialog_parent_class)->constructor (
531 type, n_properties, properties);
532 dialog = PSPPIRE_VAR_TYPE_DIALOG (obj);
534 xml = builder_new ("var-type-dialog.ui");
536 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
537 gtk_container_add (GTK_CONTAINER (content_area),
538 get_widget_assert (xml, "var-type-dialog"));
540 dialog->active_button = -1;
542 g_signal_connect (dialog, "delete-event",
543 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
545 dialog->radioButton[BUTTON_NUMERIC] =
546 get_widget_assert (xml,"radiobutton1");
547 dialog->radioButton[BUTTON_COMMA] =
548 get_widget_assert (xml,"radiobutton2");
549 dialog->radioButton[BUTTON_DOT] =
550 get_widget_assert (xml,"radiobutton3");
551 dialog->radioButton[BUTTON_SCIENTIFIC] =
552 get_widget_assert (xml,"radiobutton4");
553 dialog->radioButton[BUTTON_DATE] =
554 get_widget_assert (xml,"radiobutton5");
555 dialog->radioButton[BUTTON_DOLLAR] =
556 get_widget_assert (xml,"radiobutton6");
557 dialog->radioButton[BUTTON_CUSTOM] =
558 get_widget_assert (xml,"radiobutton7");
559 dialog->radioButton[BUTTON_STRING] =
560 get_widget_assert (xml,"radiobutton8");
563 dialog->date_format_list = get_widget_assert (xml, "scrolledwindow4");
564 dialog->width_decimals = get_widget_assert (xml, "width_decimals");
565 dialog->label_decimals = get_widget_assert (xml, "decimals_label");
566 dialog->entry_decimals = get_widget_assert (xml, "decimals_entry");
567 dialog->adj_decimals = gtk_spin_button_get_adjustment (
568 GTK_SPIN_BUTTON (dialog->entry_decimals));
570 dialog->label_psample = get_widget_assert (xml, "psample_label");
571 dialog->label_nsample = get_widget_assert (xml, "nsample_label");
574 dialog->entry_width = get_widget_assert (xml,"width_entry");
575 dialog->adj_width = gtk_spin_button_get_adjustment (
576 GTK_SPIN_BUTTON (dialog->entry_width));
577 dialog->custom_currency_hbox = get_widget_assert (xml,
578 "custom_currency_hbox");
580 dialog->dollar_window = get_widget_assert (xml, "dollar_window");
581 dialog->dollar_treeview =
582 GTK_TREE_VIEW (get_widget_assert (xml, "dollar_treeview"));
584 dialog->custom_treeview =
585 GTK_TREE_VIEW (get_widget_assert (xml, "custom_treeview"));
591 GtkListStore *list_store ;
593 GtkTreeViewColumn *column;
594 GtkCellRenderer *renderer ;
596 /* The "middle_box" is a vbox with serveral children.
597 However only one child is ever shown at a time.
598 We need to make sure that they all have the same width, to avoid
599 upleasant resizing effects */
600 GtkSizeGroup *sizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
602 gtk_container_foreach (GTK_CONTAINER (get_widget_assert (xml, "middle_box")),
603 add_to_group, sizeGroup);
606 for (i = 0 ; i < num_BUTTONS; ++i )
607 g_signal_connect (dialog->radioButton[i], "toggled",
608 G_CALLBACK (on_toggle), dialog);
610 /* Populate the date format tree view */
611 dialog->date_format_treeview = GTK_TREE_VIEW (get_widget_assert (xml,
612 "date_format_list_view"));
614 renderer = gtk_cell_renderer_text_new ();
616 column = gtk_tree_view_column_new_with_attributes ("Title",
622 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->date_format_treeview),
626 list_store = gtk_list_store_new (1, G_TYPE_STRING);
628 for ( i = 0 ; i < sizeof (date_format) / sizeof (date_format[0]) ; ++i )
630 const struct fmt_spec *f = &date_format[i];
631 gtk_list_store_append (list_store, &iter);
632 gtk_list_store_set (list_store, &iter,
633 0, fmt_date_template (f->type, f->w),
637 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->date_format_treeview),
638 GTK_TREE_MODEL (list_store));
640 g_object_unref (list_store);
642 g_signal_connect (dialog->date_format_treeview, "cursor-changed",
643 G_CALLBACK (set_date_format_from_treeview), dialog);
646 /* populate the dollar treeview */
648 renderer = gtk_cell_renderer_text_new ();
650 column = gtk_tree_view_column_new_with_attributes ("Title",
656 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview),
660 list_store = gtk_list_store_new (1, G_TYPE_STRING);
662 for ( i = 0 ; i < sizeof (dollar_format)/sizeof (dollar_format[0]) ; ++i )
664 char *template = settings_dollar_template (&dollar_format[i]);
665 gtk_list_store_append (list_store, &iter);
666 gtk_list_store_set (list_store, &iter,
672 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dollar_treeview),
673 GTK_TREE_MODEL (list_store));
675 g_object_unref (list_store);
677 g_signal_connect (dialog->dollar_treeview,
679 G_CALLBACK (set_dollar_format_from_treeview), dialog);
681 g_signal_connect_swapped (dialog->dollar_treeview,
683 G_CALLBACK (update_width_decimals), dialog);
686 /* populate the custom treeview */
688 renderer = gtk_cell_renderer_text_new ();
690 column = gtk_tree_view_column_new_with_attributes ("Title",
696 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview),
700 list_store = gtk_list_store_new (1, G_TYPE_STRING);
702 for ( i = 0 ; i < 5 ; ++i )
704 enum fmt_type cc_fmts[5] = {FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE};
705 gtk_list_store_append (list_store, &iter);
706 gtk_list_store_set (list_store, &iter,
707 0, fmt_name (cc_fmts[i]),
711 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->custom_treeview),
712 GTK_TREE_MODEL (list_store));
714 g_object_unref (list_store);
717 g_signal_connect (dialog->custom_treeview,
719 G_CALLBACK (set_custom_format_from_treeview), dialog);
722 g_signal_connect (dialog->custom_treeview,
724 G_CALLBACK (preview_custom), dialog);
727 g_signal_connect (dialog->entry_width, "changed",
728 G_CALLBACK (on_width_changed), dialog);
729 g_signal_connect (dialog->entry_decimals, "changed",
730 G_CALLBACK (on_decimals_changed), dialog);
732 g_signal_connect (dialog->entry_width,
734 G_CALLBACK (preview_custom), dialog);
737 g_signal_connect (dialog->entry_decimals,
739 G_CALLBACK (preview_custom), dialog);
743 g_object_unref (xml);
745 psppire_var_type_dialog_set_state (dialog);
751 /* Set a particular button to be active */
753 var_type_dialog_set_active_button (PsppireVarTypeDialog *dialog, gint b)
755 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radioButton[b]),
762 select_treeview_at_index (GtkTreeView *treeview, int index)
766 path = gtk_tree_path_new_from_indices (index, -1);
767 gtk_tree_view_set_cursor (treeview, path, 0, 0);
768 gtk_tree_path_free (path);
772 find_format (const struct fmt_spec *target,
773 const struct fmt_spec formats[], int n_formats)
777 for (i = 0; i < n_formats; i++)
778 if (fmt_equal (target, &formats[i]))
785 find_format_type (int target, const int types[], int n_types)
789 for (i = 0; i < n_types; i++)
790 if (target == types[i])
796 /* Set up the state of the dialog box to match the variable VAR */
798 psppire_var_type_dialog_set_state (PsppireVarTypeDialog *dialog)
802 g_return_if_fail (dialog != NULL);
804 /* Populate the radio button states */
805 switch (dialog->base_format.type)
809 button = BUTTON_NUMERIC;
812 button = BUTTON_STRING;
815 button = BUTTON_COMMA;
821 button = BUTTON_DOLLAR;
836 button = BUTTON_DATE;
843 button = BUTTON_CUSTOM;
847 var_type_dialog_set_active_button (dialog, button);
848 refresh_active_button (dialog);
849 on_active_button_change (GTK_TOGGLE_BUTTON (dialog->radioButton[button]),