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[] =
41 { .type = FMT_DATE, .w = 11, .d = 0 },
42 { .type = FMT_DATE, .w = 9, .d = 0 },
43 { .type = FMT_ADATE, .w = 10, .d = 0 },
44 { .type = FMT_ADATE, .w = 8, .d = 0 },
45 { .type = FMT_EDATE, .w = 10, .d = 0 },
46 { .type = FMT_EDATE, .w = 8, .d = 0 },
47 { .type = FMT_SDATE, .w = 10, .d = 0 },
48 { .type = FMT_SDATE, .w = 8, .d = 0 },
49 { .type = FMT_JDATE, .w = 5, .d = 0 },
50 { .type = FMT_JDATE, .w = 7, .d = 0 },
51 { .type = FMT_QYR, .w = 8, .d = 0 },
52 { .type = FMT_QYR, .w = 6, .d = 0 },
53 { .type = FMT_MOYR, .w = 8, .d = 0 },
54 { .type = FMT_MOYR, .w = 6, .d = 0 },
55 { .type = FMT_WKYR, .w = 10, .d = 0 },
56 { .type = FMT_WKYR, .w = 8, .d = 0 },
57 { .type = FMT_DATETIME, .w = 17, .d = 0 },
58 { .type = FMT_DATETIME, .w = 20, .d = 0 },
59 { .type = FMT_YMDHMS, .w = 16, .d = 0 },
60 { .type = FMT_YMDHMS, .w = 20, .d = 0 }
64 static const struct fmt_spec dollar_format[] =
66 { .type = FMT_DOLLAR, .w = 2, .d = 0 },
67 { .type = FMT_DOLLAR, .w = 3, .d = 0 },
68 { .type = FMT_DOLLAR, .w = 4, .d = 0 },
69 { .type = FMT_DOLLAR, .w = 7, .d = 2 },
70 { .type = FMT_DOLLAR, .w = 6, .d = 0 },
71 { .type = FMT_DOLLAR, .w = 9, .d = 2 },
72 { .type = FMT_DOLLAR, .w = 8, .d = 0 },
73 { .type = FMT_DOLLAR, .w = 11, .d = 2 },
74 { .type = FMT_DOLLAR, .w = 12, .d = 0 },
75 { .type = FMT_DOLLAR, .w = 15, .d = 2 },
76 { .type = FMT_DOLLAR, .w = 16, .d = 0 },
77 { .type = FMT_DOLLAR, .w = 19, .d = 2 }
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 settings_get_fmt_settings ()));
449 gtk_label_set_text (GTK_LABEL (dialog->label_psample), sample_text);
450 g_free (sample_text);
453 sample_text = g_strchug (data_out (&v, NULL, &dialog->fmt_l,
454 settings_get_fmt_settings ()));
455 gtk_label_set_text (GTK_LABEL (dialog->label_nsample), sample_text);
456 g_free (sample_text);
462 get_index_from_treeview (GtkTreeView *treeview)
464 GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
470 if (selection == NULL)
473 gtk_tree_selection_get_selected (selection, &model, &iter);
474 path = gtk_tree_model_get_path (model, &iter);
475 if (!path || gtk_tree_path_get_depth (path) < 1)
478 index = gtk_tree_path_get_indices (path)[0];
479 gtk_tree_path_free (path);
484 /* Callback for when a date treeview row is changed.
485 It sets the fmt_l_spec to reflect the selected format */
487 set_date_format_from_treeview (GtkTreeView *treeview,
488 PsppireVarTypeDialog *dialog)
490 gint idx = get_index_from_treeview (treeview);
494 dialog->fmt_l = date_format[idx];
497 /* Callback for when a dollar treeview row is changed.
498 It sets the fmt_l_spec to reflect the selected format */
500 set_dollar_format_from_treeview (GtkTreeView *treeview,
501 PsppireVarTypeDialog *dialog)
503 gint idx = get_index_from_treeview (treeview);
507 dialog->fmt_l = dollar_format[idx];
510 /* Callback for when a treeview row is changed.
511 It sets the type of the fmt_l to reflect the selected type */
513 set_custom_format_from_treeview (GtkTreeView *treeview,
514 PsppireVarTypeDialog *dialog)
516 gint idx = get_index_from_treeview (treeview);
520 dialog->fmt_l.type = cc_format[idx];
521 update_adj_ranges (dialog);
522 fmt_fix_output (&dialog->fmt_l);
523 update_width_decimals (dialog);
526 /* Create the structure */
528 psppire_var_type_dialog_constructor (GType type,
530 GObjectConstructParam *properties)
532 PsppireVarTypeDialog *dialog;
533 GtkContainer *content_area;
538 obj = G_OBJECT_CLASS (psppire_var_type_dialog_parent_class)->constructor (
539 type, n_properties, properties);
540 dialog = PSPPIRE_VAR_TYPE_DIALOG (obj);
542 g_object_set (dialog, "help-page", "Input-and-Output-Formats",
543 "title", _("Variable Type and Format"), NULL);
545 xml = builder_new ("var-type-dialog.ui");
547 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
548 gtk_container_add (GTK_CONTAINER (content_area),
549 get_widget_assert (xml, "var-type-dialog"));
551 dialog->active_button = -1;
553 g_signal_connect (dialog, "delete-event",
554 G_CALLBACK (gtk_widget_hide_on_delete), NULL);
556 dialog->radioButton[BUTTON_NUMERIC] =
557 get_widget_assert (xml,"radiobutton1");
558 dialog->radioButton[BUTTON_COMMA] =
559 get_widget_assert (xml,"radiobutton2");
560 dialog->radioButton[BUTTON_DOT] =
561 get_widget_assert (xml,"radiobutton3");
562 dialog->radioButton[BUTTON_SCIENTIFIC] =
563 get_widget_assert (xml,"radiobutton4");
564 dialog->radioButton[BUTTON_DATE] =
565 get_widget_assert (xml,"radiobutton5");
566 dialog->radioButton[BUTTON_DOLLAR] =
567 get_widget_assert (xml,"radiobutton6");
568 dialog->radioButton[BUTTON_CUSTOM] =
569 get_widget_assert (xml,"radiobutton7");
570 dialog->radioButton[BUTTON_STRING] =
571 get_widget_assert (xml,"radiobutton8");
574 dialog->date_format_list = get_widget_assert (xml, "scrolledwindow4");
575 dialog->width_decimals = get_widget_assert (xml, "width_decimals");
576 dialog->label_decimals = get_widget_assert (xml, "decimals_label");
577 dialog->entry_decimals = get_widget_assert (xml, "decimals_entry");
578 dialog->adj_decimals = gtk_spin_button_get_adjustment (
579 GTK_SPIN_BUTTON (dialog->entry_decimals));
581 dialog->label_psample = get_widget_assert (xml, "psample_label");
582 dialog->label_nsample = get_widget_assert (xml, "nsample_label");
585 dialog->entry_width = get_widget_assert (xml,"width_entry");
586 dialog->adj_width = gtk_spin_button_get_adjustment (
587 GTK_SPIN_BUTTON (dialog->entry_width));
588 dialog->custom_currency_hbox = get_widget_assert (xml,
589 "custom_currency_hbox");
591 dialog->dollar_window = get_widget_assert (xml, "dollar_window");
592 dialog->dollar_treeview =
593 GTK_TREE_VIEW (get_widget_assert (xml, "dollar_treeview"));
595 dialog->custom_treeview =
596 GTK_TREE_VIEW (get_widget_assert (xml, "custom_treeview"));
602 GtkListStore *list_store ;
604 GtkTreeViewColumn *column;
605 GtkCellRenderer *renderer ;
607 /* The "middle_box" is a vbox with serveral children.
608 However only one child is ever shown at a time.
609 We need to make sure that they all have the same width, to avoid
610 upleasant resizing effects */
611 GtkSizeGroup *sizeGroup = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
613 gtk_container_foreach (GTK_CONTAINER (get_widget_assert (xml, "middle_box")),
614 add_to_group, sizeGroup);
617 for (i = 0 ; i < num_BUTTONS; ++i)
618 g_signal_connect (dialog->radioButton[i], "toggled",
619 G_CALLBACK (on_toggle), dialog);
621 /* Populate the date format tree view */
622 dialog->date_format_treeview = GTK_TREE_VIEW (get_widget_assert (xml,
623 "date_format_list_view"));
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->date_format_treeview),
637 list_store = gtk_list_store_new (1, G_TYPE_STRING);
639 for (i = 0 ; i < sizeof (date_format) / sizeof (date_format[0]) ; ++i)
641 const struct fmt_spec *f = &date_format[i];
642 gtk_list_store_append (list_store, &iter);
643 gtk_list_store_set (list_store, &iter,
644 0, fmt_date_template (f->type, f->w),
648 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->date_format_treeview),
649 GTK_TREE_MODEL (list_store));
651 g_object_unref (list_store);
653 g_signal_connect (dialog->date_format_treeview, "cursor-changed",
654 G_CALLBACK (set_date_format_from_treeview), dialog);
657 /* populate the dollar treeview */
659 renderer = gtk_cell_renderer_text_new ();
661 column = gtk_tree_view_column_new_with_attributes ("Title",
667 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->dollar_treeview),
671 list_store = gtk_list_store_new (1, G_TYPE_STRING);
673 for (i = 0 ; i < sizeof (dollar_format)/sizeof (dollar_format[0]) ; ++i)
675 char *template = settings_dollar_template (&dollar_format[i]);
676 gtk_list_store_append (list_store, &iter);
677 gtk_list_store_set (list_store, &iter,
683 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->dollar_treeview),
684 GTK_TREE_MODEL (list_store));
686 g_object_unref (list_store);
688 g_signal_connect (dialog->dollar_treeview,
690 G_CALLBACK (set_dollar_format_from_treeview), dialog);
692 g_signal_connect_swapped (dialog->dollar_treeview,
694 G_CALLBACK (update_width_decimals), dialog);
697 /* populate the custom treeview */
699 renderer = gtk_cell_renderer_text_new ();
701 column = gtk_tree_view_column_new_with_attributes ("Title",
707 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->custom_treeview),
711 list_store = gtk_list_store_new (1, G_TYPE_STRING);
713 for (i = 0 ; i < 5 ; ++i)
715 enum fmt_type cc_fmts[5] = {FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE};
716 gtk_list_store_append (list_store, &iter);
717 gtk_list_store_set (list_store, &iter,
718 0, fmt_name (cc_fmts[i]),
722 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->custom_treeview),
723 GTK_TREE_MODEL (list_store));
725 g_object_unref (list_store);
728 g_signal_connect (dialog->custom_treeview,
730 G_CALLBACK (set_custom_format_from_treeview), dialog);
733 g_signal_connect (dialog->custom_treeview,
735 G_CALLBACK (preview_custom), dialog);
738 g_signal_connect (dialog->entry_width, "changed",
739 G_CALLBACK (on_width_changed), dialog);
740 g_signal_connect (dialog->entry_decimals, "changed",
741 G_CALLBACK (on_decimals_changed), dialog);
743 g_signal_connect (dialog->entry_width,
745 G_CALLBACK (preview_custom), dialog);
748 g_signal_connect (dialog->entry_decimals,
750 G_CALLBACK (preview_custom), dialog);
754 g_object_unref (xml);
756 psppire_var_type_dialog_set_state (dialog);
762 /* Set a particular button to be active */
764 var_type_dialog_set_active_button (PsppireVarTypeDialog *dialog, gint b)
766 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->radioButton[b]),
773 select_treeview_at_index (GtkTreeView *treeview, int index)
777 path = gtk_tree_path_new_from_indices (index, -1);
778 gtk_tree_view_set_cursor (treeview, path, 0, 0);
779 gtk_tree_path_free (path);
783 find_format (const struct fmt_spec *target,
784 const struct fmt_spec formats[], int n_formats)
788 for (i = 0; i < n_formats; i++)
789 if (fmt_equal (target, &formats[i]))
796 find_format_type (int target, const int types[], int n_types)
800 for (i = 0; i < n_types; i++)
801 if (target == types[i])
807 /* Set up the state of the dialog box to match the variable VAR */
809 psppire_var_type_dialog_set_state (PsppireVarTypeDialog *dialog)
813 g_return_if_fail (dialog != NULL);
815 /* Populate the radio button states */
816 switch (dialog->base_format.type)
820 button = BUTTON_NUMERIC;
823 button = BUTTON_STRING;
826 button = BUTTON_COMMA;
832 button = BUTTON_DOLLAR;
849 button = BUTTON_DATE;
856 button = BUTTON_CUSTOM;
860 var_type_dialog_set_active_button (dialog, button);
861 refresh_active_button (dialog);
862 on_active_button_change (GTK_TOGGLE_BUTTON (dialog->radioButton[button]),