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 if (psppire_dialog_run (PSPPIRE_DIALOG (dialog)) == GTK_RESPONSE_OK)
216 *format = *psppire_var_type_dialog_get_format (dialog);
218 gtk_widget_destroy (GTK_WIDGET (dialog));
222 /* callback for when any of the radio buttons are toggled */
224 on_toggle (GtkToggleButton *togglebutton, gpointer dialog_)
226 PsppireVarTypeDialog *dialog = dialog_;
228 if ( gtk_toggle_button_get_active (togglebutton) == TRUE)
229 refresh_active_button (dialog);
233 refresh_active_button (PsppireVarTypeDialog *dialog)
237 for (i = 0; i < num_BUTTONS; i++)
239 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (dialog->radioButton[i]);
241 if (gtk_toggle_button_get_active (toggle))
243 if (dialog->active_button != i)
245 dialog->active_button = i;
246 on_active_button_change (toggle, dialog);
252 g_return_if_reached ();
256 update_adj_ranges (PsppireVarTypeDialog *dialog)
258 enum fmt_type type = dialog->fmt_l.type;
259 const enum fmt_use use = FMT_FOR_OUTPUT;
260 int min_w = fmt_min_width (type, use);
261 int max_w = fmt_max_width (type, use);
262 int max_d = fmt_max_decimals (type, max_w, use);
264 g_object_set (dialog->adj_width,
265 "lower", (double) min_w,
266 "upper", (double) max_w,
269 g_object_set (dialog->adj_decimals,
271 "upper", (double) max_d,
275 /* callback for when any of the radio buttons are toggled */
277 on_active_button_change (GtkToggleButton *togglebutton,
278 PsppireVarTypeDialog *dialog)
283 W_DATE_FORMATS = 1 << 2,
284 W_DOLLAR_FORMATS = 1 << 3,
285 W_CC_FORMATS = 1 << 4,
288 enum widgets widgets;
291 switch (dialog->active_button)
296 case BUTTON_SCIENTIFIC:
297 widgets = W_WIDTH | W_DECIMALS;
305 widgets = W_DATE_FORMATS;
309 widgets = W_DOLLAR_FORMATS;
313 widgets = W_CC_FORMATS | W_WIDTH | W_DECIMALS;
317 /* No button active */
321 gtk_widget_set_visible (dialog->width_decimals, (widgets & W_WIDTH) != 0);
322 gtk_widget_set_visible (dialog->entry_width, (widgets & W_WIDTH) != 0);
323 gtk_widget_set_visible (dialog->entry_decimals, (widgets & W_DECIMALS) != 0);
324 gtk_widget_set_visible (dialog->label_decimals, (widgets & W_DECIMALS) != 0);
325 gtk_widget_set_visible (dialog->date_format_list,
326 (widgets & W_DATE_FORMATS) != 0);
327 gtk_widget_set_visible (dialog->custom_currency_hbox,
328 (widgets & W_CC_FORMATS) != 0);
329 gtk_widget_set_visible (dialog->dollar_window,
330 (widgets & W_DOLLAR_FORMATS) != 0);
332 dialog->fmt_l = dialog->base_format;
334 switch (dialog->active_button)
337 dialog->fmt_l.type = FMT_F;
340 dialog->fmt_l.type = FMT_COMMA;
343 dialog->fmt_l.type = FMT_DOT;
345 case BUTTON_SCIENTIFIC:
346 dialog->fmt_l.type = FMT_E;
349 dialog->fmt_l.type = FMT_A;
352 indx = find_format (&dialog->fmt_l, date_format,
353 sizeof date_format / sizeof *date_format);
354 select_treeview_at_index (dialog->date_format_treeview, indx);
355 dialog->fmt_l = date_format[indx];
358 indx = find_format (&dialog->fmt_l, dollar_format,
359 sizeof dollar_format / sizeof *dollar_format);
360 select_treeview_at_index (dialog->dollar_treeview, indx);
361 dialog->fmt_l = dollar_format[indx];
364 indx = find_format_type (dialog->fmt_l.type, cc_format,
365 sizeof cc_format / sizeof *cc_format);
366 select_treeview_at_index (dialog->custom_treeview, indx);
367 dialog->fmt_l.type = cc_format[indx];
371 fmt_fix_output (&dialog->fmt_l);
372 update_adj_ranges (dialog);
373 update_width_decimals (dialog);
377 add_to_group (GtkWidget *w, gpointer data)
379 GtkSizeGroup *sg = data;
381 gtk_size_group_add_widget (sg, w);
384 /* Set the local width and decimals entry boxes to reflec the local format */
386 update_width_decimals (const PsppireVarTypeDialog *dialog)
388 gtk_adjustment_set_value (dialog->adj_width, dialog->fmt_l.w);
389 gtk_adjustment_set_value (dialog->adj_decimals, dialog->fmt_l.d);
393 on_width_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
395 int w = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_width)));
396 fmt_change_width (&dialog->fmt_l, w, FMT_FOR_OUTPUT);
397 update_width_decimals (dialog);
401 on_decimals_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
403 int d = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals)));
404 fmt_change_decimals (&dialog->fmt_l, d, FMT_FOR_OUTPUT);
405 update_width_decimals (dialog);
408 /* Callback for when the custom treeview row is changed.
409 It sets dialog box to reflect the selected format */
411 preview_custom (GtkWidget *w, gpointer data)
415 PsppireVarTypeDialog *dialog = data;
417 if ( dialog->active_button != BUTTON_CUSTOM )
420 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals));
421 dialog->fmt_l.d = atoi (text);
423 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_width));
424 dialog->fmt_l.w = atoi (text);
427 if ( ! fmt_check_output (&dialog->fmt_l))
429 gtk_label_set_text (GTK_LABEL (dialog->label_psample), "---");
430 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), "---");
438 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
439 gtk_label_set_text (GTK_LABEL (dialog->label_psample), sample_text);
440 g_free (sample_text);
443 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
444 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), sample_text);
445 g_free (sample_text);
451 get_index_from_treeview (GtkTreeView *treeview)
453 GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
459 if (selection == NULL)
462 gtk_tree_selection_get_selected (selection, &model, &iter);
463 path = gtk_tree_model_get_path (model, &iter);
464 if (!path || gtk_tree_path_get_depth (path) < 1)
467 index = gtk_tree_path_get_indices (path)[0];
468 gtk_tree_path_free (path);
473 /* Callback for when a date treeview row is changed.
474 It sets the fmt_l_spec to reflect the selected format */
476 set_date_format_from_treeview (GtkTreeView *treeview,
477 PsppireVarTypeDialog *dialog)
479 gint idx = get_index_from_treeview (treeview);
483 dialog->fmt_l = date_format[idx];
486 /* Callback for when a dollar treeview row is changed.
487 It sets the fmt_l_spec to reflect the selected format */
489 set_dollar_format_from_treeview (GtkTreeView *treeview,
490 PsppireVarTypeDialog *dialog)
492 gint idx = get_index_from_treeview (treeview);
496 dialog->fmt_l = dollar_format[idx];
499 /* Callback for when a treeview row is changed.
500 It sets the type of the fmt_l to reflect the selected type */
502 set_custom_format_from_treeview (GtkTreeView *treeview,
503 PsppireVarTypeDialog *dialog)
505 gint idx = get_index_from_treeview (treeview);
509 dialog->fmt_l.type = cc_format[idx];
510 update_adj_ranges (dialog);
511 fmt_fix_output (&dialog->fmt_l);
512 update_width_decimals (dialog);
515 /* Create the structure */
517 psppire_var_type_dialog_constructor (GType type,
519 GObjectConstructParam *properties)
521 PsppireVarTypeDialog *dialog;
522 GtkContainer *content_area;
527 obj = G_OBJECT_CLASS (psppire_var_type_dialog_parent_class)->constructor (
528 type, n_properties, properties);
529 dialog = PSPPIRE_VAR_TYPE_DIALOG (obj);
531 xml = builder_new ("var-type-dialog.ui");
533 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
534 gtk_container_add (GTK_CONTAINER (content_area),
535 get_widget_assert (xml, "var-type-dialog"));
537 dialog->active_button = -1;
539 g_signal_connect (dialog, "delete-event",
540 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
542 dialog->radioButton[BUTTON_NUMERIC] =
543 get_widget_assert (xml,"radiobutton1");
544 dialog->radioButton[BUTTON_COMMA] =
545 get_widget_assert (xml,"radiobutton2");
546 dialog->radioButton[BUTTON_DOT] =
547 get_widget_assert (xml,"radiobutton3");
548 dialog->radioButton[BUTTON_SCIENTIFIC] =
549 get_widget_assert (xml,"radiobutton4");
550 dialog->radioButton[BUTTON_DATE] =
551 get_widget_assert (xml,"radiobutton5");
552 dialog->radioButton[BUTTON_DOLLAR] =
553 get_widget_assert (xml,"radiobutton6");
554 dialog->radioButton[BUTTON_CUSTOM] =
555 get_widget_assert (xml,"radiobutton7");
556 dialog->radioButton[BUTTON_STRING] =
557 get_widget_assert (xml,"radiobutton8");
560 dialog->date_format_list = get_widget_assert (xml, "scrolledwindow4");
561 dialog->width_decimals = get_widget_assert (xml, "width_decimals");
562 dialog->label_decimals = get_widget_assert (xml, "decimals_label");
563 dialog->entry_decimals = get_widget_assert (xml, "decimals_entry");
564 dialog->adj_decimals = gtk_spin_button_get_adjustment (
565 GTK_SPIN_BUTTON (dialog->entry_decimals));
567 dialog->label_psample = get_widget_assert (xml, "psample_label");
568 dialog->label_nsample = get_widget_assert (xml, "nsample_label");
571 dialog->entry_width = get_widget_assert (xml,"width_entry");
572 dialog->adj_width = gtk_spin_button_get_adjustment (
573 GTK_SPIN_BUTTON (dialog->entry_width));
574 dialog->custom_currency_hbox = get_widget_assert (xml,
575 "custom_currency_hbox");
577 dialog->dollar_window = get_widget_assert (xml, "dollar_window");
578 dialog->dollar_treeview =
579 GTK_TREE_VIEW (get_widget_assert (xml, "dollar_treeview"));
581 dialog->custom_treeview =
582 GTK_TREE_VIEW (get_widget_assert (xml, "custom_treeview"));
588 GtkListStore *list_store ;
590 GtkTreeViewColumn *column;
591 GtkCellRenderer *renderer ;
593 /* The "middle_box" is a vbox with serveral children.
594 However only one child is ever shown at a time.
595 We need to make sure that they all have the same width, to avoid
596 upleasant resizing effects */
597 GtkSizeGroup *sizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
599 gtk_container_foreach (GTK_CONTAINER (get_widget_assert (xml, "middle_box")),
600 add_to_group, sizeGroup);
603 for (i = 0 ; i < num_BUTTONS; ++i )
604 g_signal_connect (dialog->radioButton[i], "toggled",
605 G_CALLBACK (on_toggle), dialog);
607 /* Populate the date format tree view */
608 dialog->date_format_treeview = GTK_TREE_VIEW (get_widget_assert (xml,
609 "date_format_list_view"));
611 renderer = gtk_cell_renderer_text_new ();
613 column = gtk_tree_view_column_new_with_attributes ("Title",
619 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->date_format_treeview),
623 list_store = gtk_list_store_new (1, G_TYPE_STRING);
625 for ( i = 0 ; i < sizeof (date_format) / sizeof (date_format[0]) ; ++i )
627 const struct fmt_spec *f = &date_format[i];
628 gtk_list_store_append (list_store, &iter);
629 gtk_list_store_set (list_store, &iter,
630 0, fmt_date_template (f->type, f->w),
634 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->date_format_treeview),
635 GTK_TREE_MODEL (list_store));
637 g_object_unref (list_store);
639 g_signal_connect (dialog->date_format_treeview, "cursor-changed",
640 G_CALLBACK (set_date_format_from_treeview), dialog);
643 /* populate the dollar treeview */
645 renderer = gtk_cell_renderer_text_new ();
647 column = gtk_tree_view_column_new_with_attributes ("Title",
653 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview),
657 list_store = gtk_list_store_new (1, G_TYPE_STRING);
659 for ( i = 0 ; i < sizeof (dollar_format)/sizeof (dollar_format[0]) ; ++i )
661 char *template = settings_dollar_template (&dollar_format[i]);
662 gtk_list_store_append (list_store, &iter);
663 gtk_list_store_set (list_store, &iter,
669 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dollar_treeview),
670 GTK_TREE_MODEL (list_store));
672 g_object_unref (list_store);
674 g_signal_connect (dialog->dollar_treeview,
676 G_CALLBACK (set_dollar_format_from_treeview), dialog);
678 g_signal_connect_swapped (dialog->dollar_treeview,
680 G_CALLBACK (update_width_decimals), dialog);
683 /* populate the custom treeview */
685 renderer = gtk_cell_renderer_text_new ();
687 column = gtk_tree_view_column_new_with_attributes ("Title",
693 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview),
697 list_store = gtk_list_store_new (1, G_TYPE_STRING);
699 for ( i = 0 ; i < 5 ; ++i )
701 enum fmt_type cc_fmts[5] = {FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE};
702 gtk_list_store_append (list_store, &iter);
703 gtk_list_store_set (list_store, &iter,
704 0, fmt_name (cc_fmts[i]),
708 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->custom_treeview),
709 GTK_TREE_MODEL (list_store));
711 g_object_unref (list_store);
714 g_signal_connect (dialog->custom_treeview,
716 G_CALLBACK (set_custom_format_from_treeview), dialog);
719 g_signal_connect (dialog->custom_treeview,
721 G_CALLBACK (preview_custom), dialog);
724 g_signal_connect (dialog->entry_width, "changed",
725 G_CALLBACK (on_width_changed), dialog);
726 g_signal_connect (dialog->entry_decimals, "changed",
727 G_CALLBACK (on_decimals_changed), dialog);
729 g_signal_connect (dialog->entry_width,
731 G_CALLBACK (preview_custom), dialog);
734 g_signal_connect (dialog->entry_decimals,
736 G_CALLBACK (preview_custom), dialog);
740 g_object_unref (xml);
742 psppire_var_type_dialog_set_state (dialog);
748 /* Set a particular button to be active */
750 var_type_dialog_set_active_button (PsppireVarTypeDialog *dialog, gint b)
752 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radioButton[b]),
759 select_treeview_at_index (GtkTreeView *treeview, int index)
763 path = gtk_tree_path_new_from_indices (index, -1);
764 gtk_tree_view_set_cursor (treeview, path, 0, 0);
765 gtk_tree_path_free (path);
769 find_format (const struct fmt_spec *target,
770 const struct fmt_spec formats[], int n_formats)
774 for (i = 0; i < n_formats; i++)
775 if (fmt_equal (target, &formats[i]))
782 find_format_type (int target, const int types[], int n_types)
786 for (i = 0; i < n_types; i++)
787 if (target == types[i])
793 /* Set up the state of the dialog box to match the variable VAR */
795 psppire_var_type_dialog_set_state (PsppireVarTypeDialog *dialog)
799 g_return_if_fail (dialog != NULL);
801 /* Populate the radio button states */
802 switch (dialog->base_format.type)
806 button = BUTTON_NUMERIC;
809 button = BUTTON_STRING;
812 button = BUTTON_COMMA;
818 button = BUTTON_DOLLAR;
833 button = BUTTON_DATE;
840 button = BUTTON_CUSTOM;
844 var_type_dialog_set_active_button (dialog, button);
845 refresh_active_button (dialog);
846 on_active_button_change (GTK_TOGGLE_BUTTON (dialog->radioButton[button]),