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},
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 xml = builder_new ("var-type-dialog.ui");
538 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
539 gtk_container_add (GTK_CONTAINER (content_area),
540 get_widget_assert (xml, "var-type-dialog"));
542 dialog->active_button = -1;
544 g_signal_connect (dialog, "delete-event",
545 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
547 dialog->radioButton[BUTTON_NUMERIC] =
548 get_widget_assert (xml,"radiobutton1");
549 dialog->radioButton[BUTTON_COMMA] =
550 get_widget_assert (xml,"radiobutton2");
551 dialog->radioButton[BUTTON_DOT] =
552 get_widget_assert (xml,"radiobutton3");
553 dialog->radioButton[BUTTON_SCIENTIFIC] =
554 get_widget_assert (xml,"radiobutton4");
555 dialog->radioButton[BUTTON_DATE] =
556 get_widget_assert (xml,"radiobutton5");
557 dialog->radioButton[BUTTON_DOLLAR] =
558 get_widget_assert (xml,"radiobutton6");
559 dialog->radioButton[BUTTON_CUSTOM] =
560 get_widget_assert (xml,"radiobutton7");
561 dialog->radioButton[BUTTON_STRING] =
562 get_widget_assert (xml,"radiobutton8");
565 dialog->date_format_list = get_widget_assert (xml, "scrolledwindow4");
566 dialog->width_decimals = get_widget_assert (xml, "width_decimals");
567 dialog->label_decimals = get_widget_assert (xml, "decimals_label");
568 dialog->entry_decimals = get_widget_assert (xml, "decimals_entry");
569 dialog->adj_decimals = gtk_spin_button_get_adjustment (
570 GTK_SPIN_BUTTON (dialog->entry_decimals));
572 dialog->label_psample = get_widget_assert (xml, "psample_label");
573 dialog->label_nsample = get_widget_assert (xml, "nsample_label");
576 dialog->entry_width = get_widget_assert (xml,"width_entry");
577 dialog->adj_width = gtk_spin_button_get_adjustment (
578 GTK_SPIN_BUTTON (dialog->entry_width));
579 dialog->custom_currency_hbox = get_widget_assert (xml,
580 "custom_currency_hbox");
582 dialog->dollar_window = get_widget_assert (xml, "dollar_window");
583 dialog->dollar_treeview =
584 GTK_TREE_VIEW (get_widget_assert (xml, "dollar_treeview"));
586 dialog->custom_treeview =
587 GTK_TREE_VIEW (get_widget_assert (xml, "custom_treeview"));
593 GtkListStore *list_store ;
595 GtkTreeViewColumn *column;
596 GtkCellRenderer *renderer ;
598 /* The "middle_box" is a vbox with serveral children.
599 However only one child is ever shown at a time.
600 We need to make sure that they all have the same width, to avoid
601 upleasant resizing effects */
602 GtkSizeGroup *sizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
604 gtk_container_foreach (GTK_CONTAINER (get_widget_assert (xml, "middle_box")),
605 add_to_group, sizeGroup);
608 for (i = 0 ; i < num_BUTTONS; ++i )
609 g_signal_connect (dialog->radioButton[i], "toggled",
610 G_CALLBACK (on_toggle), dialog);
612 /* Populate the date format tree view */
613 dialog->date_format_treeview = GTK_TREE_VIEW (get_widget_assert (xml,
614 "date_format_list_view"));
616 renderer = gtk_cell_renderer_text_new ();
618 column = gtk_tree_view_column_new_with_attributes ("Title",
624 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->date_format_treeview),
628 list_store = gtk_list_store_new (1, G_TYPE_STRING);
630 for ( i = 0 ; i < sizeof (date_format) / sizeof (date_format[0]) ; ++i )
632 const struct fmt_spec *f = &date_format[i];
633 gtk_list_store_append (list_store, &iter);
634 gtk_list_store_set (list_store, &iter,
635 0, fmt_date_template (f->type, f->w),
639 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->date_format_treeview),
640 GTK_TREE_MODEL (list_store));
642 g_object_unref (list_store);
644 g_signal_connect (dialog->date_format_treeview, "cursor-changed",
645 G_CALLBACK (set_date_format_from_treeview), dialog);
648 /* populate the dollar treeview */
650 renderer = gtk_cell_renderer_text_new ();
652 column = gtk_tree_view_column_new_with_attributes ("Title",
658 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview),
662 list_store = gtk_list_store_new (1, G_TYPE_STRING);
664 for ( i = 0 ; i < sizeof (dollar_format)/sizeof (dollar_format[0]) ; ++i )
666 char *template = settings_dollar_template (&dollar_format[i]);
667 gtk_list_store_append (list_store, &iter);
668 gtk_list_store_set (list_store, &iter,
674 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dollar_treeview),
675 GTK_TREE_MODEL (list_store));
677 g_object_unref (list_store);
679 g_signal_connect (dialog->dollar_treeview,
681 G_CALLBACK (set_dollar_format_from_treeview), dialog);
683 g_signal_connect_swapped (dialog->dollar_treeview,
685 G_CALLBACK (update_width_decimals), dialog);
688 /* populate the custom treeview */
690 renderer = gtk_cell_renderer_text_new ();
692 column = gtk_tree_view_column_new_with_attributes ("Title",
698 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview),
702 list_store = gtk_list_store_new (1, G_TYPE_STRING);
704 for ( i = 0 ; i < 5 ; ++i )
706 enum fmt_type cc_fmts[5] = {FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE};
707 gtk_list_store_append (list_store, &iter);
708 gtk_list_store_set (list_store, &iter,
709 0, fmt_name (cc_fmts[i]),
713 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->custom_treeview),
714 GTK_TREE_MODEL (list_store));
716 g_object_unref (list_store);
719 g_signal_connect (dialog->custom_treeview,
721 G_CALLBACK (set_custom_format_from_treeview), dialog);
724 g_signal_connect (dialog->custom_treeview,
726 G_CALLBACK (preview_custom), dialog);
729 g_signal_connect (dialog->entry_width, "changed",
730 G_CALLBACK (on_width_changed), dialog);
731 g_signal_connect (dialog->entry_decimals, "changed",
732 G_CALLBACK (on_decimals_changed), dialog);
734 g_signal_connect (dialog->entry_width,
736 G_CALLBACK (preview_custom), dialog);
739 g_signal_connect (dialog->entry_decimals,
741 G_CALLBACK (preview_custom), dialog);
745 g_object_unref (xml);
747 psppire_var_type_dialog_set_state (dialog);
753 /* Set a particular button to be active */
755 var_type_dialog_set_active_button (PsppireVarTypeDialog *dialog, gint b)
757 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radioButton[b]),
764 select_treeview_at_index (GtkTreeView *treeview, int index)
768 path = gtk_tree_path_new_from_indices (index, -1);
769 gtk_tree_view_set_cursor (treeview, path, 0, 0);
770 gtk_tree_path_free (path);
774 find_format (const struct fmt_spec *target,
775 const struct fmt_spec formats[], int n_formats)
779 for (i = 0; i < n_formats; i++)
780 if (fmt_equal (target, &formats[i]))
787 find_format_type (int target, const int types[], int n_types)
791 for (i = 0; i < n_types; i++)
792 if (target == types[i])
798 /* Set up the state of the dialog box to match the variable VAR */
800 psppire_var_type_dialog_set_state (PsppireVarTypeDialog *dialog)
804 g_return_if_fail (dialog != NULL);
806 /* Populate the radio button states */
807 switch (dialog->base_format.type)
811 button = BUTTON_NUMERIC;
814 button = BUTTON_STRING;
817 button = BUTTON_COMMA;
823 button = BUTTON_DOLLAR;
840 button = BUTTON_DATE;
847 button = BUTTON_CUSTOM;
851 var_type_dialog_set_active_button (dialog, button);
852 refresh_active_button (dialog);
853 on_active_button_change (GTK_TOGGLE_BUTTON (dialog->radioButton[button]),