1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2005, 2006, 2010, 2011, 2012 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 int find_format (const struct fmt_spec *target,
88 const struct fmt_spec formats[], int n_formats);
89 static int find_format_type (int target, const int types[], int n_types);
91 static void select_treeview_at_index (GtkTreeView *, int index);
93 static void update_width_decimals (const PsppireVarTypeDialog *);
94 static void refresh_active_button (PsppireVarTypeDialog *);
95 static void on_active_button_change (GtkToggleButton *,
96 PsppireVarTypeDialog *);
97 static void on_width_changed (GtkEntry *, PsppireVarTypeDialog *);
98 static void on_decimals_changed (GtkEntry *, PsppireVarTypeDialog *);
100 G_DEFINE_TYPE (PsppireVarTypeDialog,
101 psppire_var_type_dialog,
102 PSPPIRE_TYPE_DIALOG);
111 psppire_var_type_dialog_set_property (GObject *object,
116 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
121 psppire_var_type_dialog_set_format (obj, g_value_get_boxed (value));
124 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
130 psppire_var_type_dialog_get_property (GObject *object,
135 PsppireVarTypeDialog *obj = PSPPIRE_VAR_TYPE_DIALOG (object);
140 g_value_set_boxed (value, &obj->fmt_l);
143 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
149 psppire_var_type_dialog_set_format (PsppireVarTypeDialog *dialog,
150 const struct fmt_spec *format)
152 dialog->base_format = *format;
153 psppire_var_type_dialog_set_state (dialog);
156 const struct fmt_spec *
157 psppire_var_type_dialog_get_format (const PsppireVarTypeDialog *dialog)
159 return &dialog->fmt_l;
163 psppire_var_type_dialog_init (PsppireVarTypeDialog *obj)
165 /* We do all of our work on widgets in the constructor function, because that
166 runs after the construction properties have been set. Otherwise
167 PsppireDialog's "orientation" property hasn't been set and therefore we
168 have no box to populate. */
169 obj->base_format = F_8_0;
174 psppire_var_type_dialog_class_init (PsppireVarTypeDialogClass *class)
176 GObjectClass *gobject_class;
177 gobject_class = G_OBJECT_CLASS (class);
179 gobject_class->constructor = psppire_var_type_dialog_constructor;
180 gobject_class->set_property = psppire_var_type_dialog_set_property;
181 gobject_class->get_property = psppire_var_type_dialog_get_property;
183 g_object_class_install_property (
184 gobject_class, PROP_FORMAT,
185 g_param_spec_boxed ("format",
187 "The format being edited.",
189 G_PARAM_READABLE | G_PARAM_WRITABLE));
192 PsppireVarTypeDialog *
193 psppire_var_type_dialog_new (const struct fmt_spec *format)
195 return PSPPIRE_VAR_TYPE_DIALOG (
196 g_object_new (PSPPIRE_TYPE_VAR_TYPE_DIALOG,
197 "orientation", PSPPIRE_HORIZONTAL,
203 psppire_var_type_dialog_run (GtkWindow *parent_window,
204 struct fmt_spec *format)
206 PsppireVarTypeDialog *dialog;
208 dialog = psppire_var_type_dialog_new (format);
209 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
210 gtk_widget_show (GTK_WIDGET (dialog));
212 if (psppire_dialog_run (PSPPIRE_DIALOG (dialog)) == GTK_RESPONSE_OK)
213 *format = *psppire_var_type_dialog_get_format (dialog);
217 /* callback for when any of the radio buttons are toggled */
219 on_toggle (GtkToggleButton *togglebutton, gpointer dialog_)
221 PsppireVarTypeDialog *dialog = dialog_;
223 if ( gtk_toggle_button_get_active (togglebutton) == TRUE)
224 refresh_active_button (dialog);
228 refresh_active_button (PsppireVarTypeDialog *dialog)
232 for (i = 0; i < num_BUTTONS; i++)
234 GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (dialog->radioButton[i]);
236 if (gtk_toggle_button_get_active (toggle))
238 if (dialog->active_button != i)
240 dialog->active_button = i;
241 on_active_button_change (toggle, dialog);
247 g_return_if_reached ();
251 update_adj_ranges (PsppireVarTypeDialog *dialog)
253 enum fmt_type type = dialog->fmt_l.type;
254 const enum fmt_use use = FMT_FOR_OUTPUT;
255 int min_w = fmt_min_width (type, use);
256 int max_w = fmt_max_width (type, use);
257 int max_d = fmt_max_decimals (type, max_w, use);
259 g_object_set (dialog->adj_width,
260 "lower", (double) min_w,
261 "upper", (double) max_w,
264 g_object_set (dialog->adj_decimals,
266 "upper", (double) max_d,
270 /* callback for when any of the radio buttons are toggled */
272 on_active_button_change (GtkToggleButton *togglebutton,
273 PsppireVarTypeDialog *dialog)
278 W_DATE_FORMATS = 1 << 2,
279 W_DOLLAR_FORMATS = 1 << 3,
280 W_CC_FORMATS = 1 << 4,
283 enum widgets widgets;
286 switch (dialog->active_button)
291 case BUTTON_SCIENTIFIC:
292 widgets = W_WIDTH | W_DECIMALS;
300 widgets = W_DATE_FORMATS;
304 widgets = W_DOLLAR_FORMATS;
308 widgets = W_CC_FORMATS | W_WIDTH | W_DECIMALS;
312 /* No button active */
316 gtk_widget_set_visible (dialog->width_decimals, (widgets & W_WIDTH) != 0);
317 gtk_widget_set_visible (dialog->entry_width, (widgets & W_WIDTH) != 0);
318 gtk_widget_set_visible (dialog->entry_decimals, (widgets & W_DECIMALS) != 0);
319 gtk_widget_set_visible (dialog->label_decimals, (widgets & W_DECIMALS) != 0);
320 gtk_widget_set_visible (dialog->date_format_list,
321 (widgets & W_DATE_FORMATS) != 0);
322 gtk_widget_set_visible (dialog->custom_currency_hbox,
323 (widgets & W_CC_FORMATS) != 0);
324 gtk_widget_set_visible (dialog->dollar_window,
325 (widgets & W_DOLLAR_FORMATS) != 0);
327 dialog->fmt_l = dialog->base_format;
329 switch (dialog->active_button)
332 dialog->fmt_l.type = FMT_F;
335 dialog->fmt_l.type = FMT_COMMA;
338 dialog->fmt_l.type = FMT_DOT;
340 case BUTTON_SCIENTIFIC:
341 dialog->fmt_l.type = FMT_E;
344 dialog->fmt_l.type = FMT_A;
347 indx = find_format (&dialog->fmt_l, date_format,
348 sizeof date_format / sizeof *date_format);
349 select_treeview_at_index (dialog->date_format_treeview, indx);
350 dialog->fmt_l = date_format[indx];
353 indx = find_format (&dialog->fmt_l, dollar_format,
354 sizeof dollar_format / sizeof *dollar_format);
355 select_treeview_at_index (dialog->dollar_treeview, indx);
356 dialog->fmt_l = dollar_format[indx];
359 indx = find_format_type (dialog->fmt_l.type, cc_format,
360 sizeof cc_format / sizeof *cc_format);
361 select_treeview_at_index (dialog->custom_treeview, indx);
362 dialog->fmt_l.type = cc_format[indx];
366 fmt_fix_output (&dialog->fmt_l);
367 update_adj_ranges (dialog);
368 update_width_decimals (dialog);
372 add_to_group (GtkWidget *w, gpointer data)
374 GtkSizeGroup *sg = data;
376 gtk_size_group_add_widget (sg, w);
379 /* Set the local width and decimals entry boxes to reflec the local format */
381 update_width_decimals (const PsppireVarTypeDialog *dialog)
383 gtk_adjustment_set_value (dialog->adj_width, dialog->fmt_l.w);
384 gtk_adjustment_set_value (dialog->adj_decimals, dialog->fmt_l.d);
388 on_width_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
390 int w = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_width)));
391 fmt_change_width (&dialog->fmt_l, w, FMT_FOR_OUTPUT);
392 update_width_decimals (dialog);
396 on_decimals_changed (GtkEntry *entry, PsppireVarTypeDialog *dialog)
398 int d = atoi (gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals)));
399 fmt_change_decimals (&dialog->fmt_l, d, FMT_FOR_OUTPUT);
400 update_width_decimals (dialog);
403 /* Callback for when the custom treeview row is changed.
404 It sets dialog box to reflect the selected format */
406 preview_custom (GtkWidget *w, gpointer data)
410 PsppireVarTypeDialog *dialog = data;
412 if ( dialog->active_button != BUTTON_CUSTOM )
415 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_decimals));
416 dialog->fmt_l.d = atoi (text);
418 text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_width));
419 dialog->fmt_l.w = atoi (text);
422 if ( ! fmt_check_output (&dialog->fmt_l))
424 gtk_label_set_text (GTK_LABEL (dialog->label_psample), "---");
425 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), "---");
433 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
434 gtk_label_set_text (GTK_LABEL (dialog->label_psample), sample_text);
435 g_free (sample_text);
438 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l));
439 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), sample_text);
440 g_free (sample_text);
446 get_index_from_treeview (GtkTreeView *treeview)
448 GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
454 gtk_tree_selection_get_selected (selection, &model, &iter);
455 path = gtk_tree_model_get_path (model, &iter);
456 if (!path || gtk_tree_path_get_depth (path) < 1)
459 index = gtk_tree_path_get_indices (path)[0];
460 gtk_tree_path_free (path);
465 /* Callback for when a date treeview row is changed.
466 It sets the fmt_l_spec to reflect the selected format */
468 set_date_format_from_treeview (GtkTreeView *treeview,
469 PsppireVarTypeDialog *dialog)
471 dialog->fmt_l = date_format[get_index_from_treeview (treeview)];
474 /* Callback for when a dollar treeview row is changed.
475 It sets the fmt_l_spec to reflect the selected format */
477 set_dollar_format_from_treeview (GtkTreeView *treeview,
478 PsppireVarTypeDialog *dialog)
480 dialog->fmt_l = dollar_format[get_index_from_treeview (treeview)];
483 /* Callback for when a treeview row is changed.
484 It sets the type of the fmt_l to reflect the selected type */
486 set_custom_format_from_treeview (GtkTreeView *treeview,
487 PsppireVarTypeDialog *dialog)
489 dialog->fmt_l.type = cc_format[get_index_from_treeview (treeview)];
490 update_adj_ranges (dialog);
491 fmt_fix_output (&dialog->fmt_l);
492 update_width_decimals (dialog);
495 /* Create the structure */
497 psppire_var_type_dialog_constructor (GType type,
499 GObjectConstructParam *properties)
501 PsppireVarTypeDialog *dialog;
502 GtkContainer *content_area;
507 obj = G_OBJECT_CLASS (psppire_var_type_dialog_parent_class)->constructor (
508 type, n_properties, properties);
509 dialog = PSPPIRE_VAR_TYPE_DIALOG (obj);
511 xml = builder_new ("var-type-dialog.ui");
513 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog)->box);
514 gtk_container_add (GTK_CONTAINER (content_area),
515 get_widget_assert (xml, "var-type-dialog"));
517 dialog->active_button = -1;
519 g_signal_connect (dialog, "delete-event",
520 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
522 dialog->radioButton[BUTTON_NUMERIC] =
523 get_widget_assert (xml,"radiobutton1");
524 dialog->radioButton[BUTTON_COMMA] =
525 get_widget_assert (xml,"radiobutton2");
526 dialog->radioButton[BUTTON_DOT] =
527 get_widget_assert (xml,"radiobutton3");
528 dialog->radioButton[BUTTON_SCIENTIFIC] =
529 get_widget_assert (xml,"radiobutton4");
530 dialog->radioButton[BUTTON_DATE] =
531 get_widget_assert (xml,"radiobutton5");
532 dialog->radioButton[BUTTON_DOLLAR] =
533 get_widget_assert (xml,"radiobutton6");
534 dialog->radioButton[BUTTON_CUSTOM] =
535 get_widget_assert (xml,"radiobutton7");
536 dialog->radioButton[BUTTON_STRING] =
537 get_widget_assert (xml,"radiobutton8");
540 dialog->date_format_list = get_widget_assert (xml, "scrolledwindow4");
541 dialog->width_decimals = get_widget_assert (xml, "width_decimals");
542 dialog->label_decimals = get_widget_assert (xml, "decimals_label");
543 dialog->entry_decimals = get_widget_assert (xml, "decimals_entry");
544 dialog->adj_decimals = gtk_spin_button_get_adjustment (
545 GTK_SPIN_BUTTON (dialog->entry_decimals));
547 dialog->label_psample = get_widget_assert (xml, "psample_label");
548 dialog->label_nsample = get_widget_assert (xml, "nsample_label");
551 dialog->entry_width = get_widget_assert (xml,"width_entry");
552 dialog->adj_width = gtk_spin_button_get_adjustment (
553 GTK_SPIN_BUTTON (dialog->entry_width));
554 dialog->custom_currency_hbox = get_widget_assert (xml,
555 "custom_currency_hbox");
557 dialog->dollar_window = get_widget_assert (xml, "dollar_window");
558 dialog->dollar_treeview =
559 GTK_TREE_VIEW (get_widget_assert (xml, "dollar_treeview"));
561 dialog->custom_treeview =
562 GTK_TREE_VIEW (get_widget_assert (xml, "custom_treeview"));
568 GtkListStore *list_store ;
570 GtkTreeViewColumn *column;
571 GtkCellRenderer *renderer ;
573 /* The "middle_box" is a vbox with serveral children.
574 However only one child is ever shown at a time.
575 We need to make sure that they all have the same width, to avoid
576 upleasant resizing effects */
577 GtkSizeGroup *sizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
579 gtk_container_foreach (GTK_CONTAINER (get_widget_assert (xml, "middle_box")),
580 add_to_group, sizeGroup);
583 for (i = 0 ; i < num_BUTTONS; ++i )
584 g_signal_connect (dialog->radioButton[i], "toggled",
585 G_CALLBACK (on_toggle), dialog);
587 /* Populate the date format tree view */
588 dialog->date_format_treeview = GTK_TREE_VIEW (get_widget_assert (xml,
589 "date_format_list_view"));
591 renderer = gtk_cell_renderer_text_new ();
593 column = gtk_tree_view_column_new_with_attributes ("Title",
599 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->date_format_treeview),
603 list_store = gtk_list_store_new (1, G_TYPE_STRING);
605 for ( i = 0 ; i < sizeof (date_format) / sizeof (date_format[0]) ; ++i )
607 const struct fmt_spec *f = &date_format[i];
608 gtk_list_store_append (list_store, &iter);
609 gtk_list_store_set (list_store, &iter,
610 0, fmt_date_template (f->type, f->w),
614 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->date_format_treeview),
615 GTK_TREE_MODEL (list_store));
617 g_object_unref (list_store);
619 g_signal_connect (dialog->date_format_treeview, "cursor-changed",
620 G_CALLBACK (set_date_format_from_treeview), dialog);
623 /* populate the dollar treeview */
625 renderer = gtk_cell_renderer_text_new ();
627 column = gtk_tree_view_column_new_with_attributes ("Title",
633 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview),
637 list_store = gtk_list_store_new (1, G_TYPE_STRING);
639 for ( i = 0 ; i < sizeof (dollar_format)/sizeof (dollar_format[0]) ; ++i )
641 char *template = settings_dollar_template (&dollar_format[i]);
642 gtk_list_store_append (list_store, &iter);
643 gtk_list_store_set (list_store, &iter,
649 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dollar_treeview),
650 GTK_TREE_MODEL (list_store));
652 g_object_unref (list_store);
654 g_signal_connect (dialog->dollar_treeview,
656 G_CALLBACK (set_dollar_format_from_treeview), dialog);
658 g_signal_connect_swapped (dialog->dollar_treeview,
660 G_CALLBACK (update_width_decimals), dialog);
663 /* populate the custom treeview */
665 renderer = gtk_cell_renderer_text_new ();
667 column = gtk_tree_view_column_new_with_attributes ("Title",
673 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview),
677 list_store = gtk_list_store_new (1, G_TYPE_STRING);
679 for ( i = 0 ; i < 5 ; ++i )
681 enum fmt_type cc_fmts[5] = {FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE};
682 gtk_list_store_append (list_store, &iter);
683 gtk_list_store_set (list_store, &iter,
684 0, fmt_name (cc_fmts[i]),
688 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->custom_treeview),
689 GTK_TREE_MODEL (list_store));
691 g_object_unref (list_store);
694 g_signal_connect (dialog->custom_treeview,
696 G_CALLBACK (set_custom_format_from_treeview), dialog);
699 g_signal_connect (dialog->custom_treeview,
701 G_CALLBACK (preview_custom), dialog);
704 g_signal_connect (dialog->entry_width, "changed",
705 G_CALLBACK (on_width_changed), dialog);
706 g_signal_connect (dialog->entry_decimals, "changed",
707 G_CALLBACK (on_decimals_changed), dialog);
709 g_signal_connect (dialog->entry_width,
711 G_CALLBACK (preview_custom), dialog);
714 g_signal_connect (dialog->entry_decimals,
716 G_CALLBACK (preview_custom), dialog);
720 g_object_unref (xml);
722 psppire_var_type_dialog_set_state (dialog);
728 /* Set a particular button to be active */
730 var_type_dialog_set_active_button (PsppireVarTypeDialog *dialog, gint b)
732 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radioButton[b]),
739 select_treeview_at_index (GtkTreeView *treeview, int index)
743 path = gtk_tree_path_new_from_indices (index, -1);
744 gtk_tree_view_set_cursor (treeview, path, 0, 0);
745 gtk_tree_path_free (path);
749 find_format (const struct fmt_spec *target,
750 const struct fmt_spec formats[], int n_formats)
754 for (i = 0; i < n_formats; i++)
755 if (fmt_equal (target, &formats[i]))
762 find_format_type (int target, const int types[], int n_types)
766 for (i = 0; i < n_types; i++)
767 if (target == types[i])
773 /* Set up the state of the dialog box to match the variable VAR */
775 psppire_var_type_dialog_set_state (PsppireVarTypeDialog *dialog)
779 g_return_if_fail (dialog != NULL);
781 /* Populate the radio button states */
782 switch (dialog->base_format.type)
786 button = BUTTON_NUMERIC;
789 button = BUTTON_STRING;
792 button = BUTTON_COMMA;
798 button = BUTTON_DOLLAR;
813 button = BUTTON_DATE;
820 button = BUTTON_CUSTOM;
824 var_type_dialog_set_active_button (dialog, button);
825 refresh_active_button (dialog);
826 on_active_button_change (GTK_TOGGLE_BUTTON (dialog->radioButton[button]),