1.12.42
[gnumeric.git] / src / dialogs / dialog-cell-format.c
blob65afd67a05d9625861fcb9d2f98e908faa443e2f
1 /*
2 * dialog-cell-format.c: Implements a dialog to format cells.
4 * Authors:
5 * Jody Goldberg <jody@gnome.org>
6 * Almer S. Tigelaar <almer@gnome.org>
7 * Andreas J. Guelzow <aguelzow@pyrshep.ca>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <https://www.gnu.org/licenses/>.
21 **/
23 #include <gnumeric-config.h>
24 #include <glib/gi18n-lib.h>
25 #include <gnumeric.h>
26 #include <dialogs/dialogs.h>
27 #include <dialogs/help.h>
29 #include <sheet.h>
30 #include <sheet-view.h>
31 #include <sheet-merge.h>
32 #include <sheet-style.h>
33 #include <style-color.h>
34 #include <gui-util.h>
35 #include <selection.h>
36 #include <ranges.h>
37 #include <cell.h>
38 #include <expr.h>
39 #include <value.h>
40 #include <gnm-format.h>
41 #include <pattern.h>
42 #include <position.h>
43 #include <mstyle.h>
44 #include <application.h>
45 #include <validation.h>
46 #include <input-msg.h>
47 #include <workbook.h>
48 #include <wbc-gtk.h>
49 #include <commands.h>
50 #include <mathfunc.h>
51 #include <preview-grid.h>
52 #include <widgets/gnm-dashed-canvas-line.h>
53 #include <widgets/gnm-format-sel.h>
54 #include <style-conditions.h>
56 #include <goffice/goffice.h>
57 #include <goffice/canvas/goc-canvas.h>
58 #include <goffice/canvas/goc-item.h>
59 #include <goffice/canvas/goc-rectangle.h>
61 #include <string.h>
63 #define CELL_FORMAT_KEY "cell-format-dialog"
65 #if 0
66 static struct {
67 char const *Cname;
68 GnmUnderline ut;
69 } const underline_types[] = {
70 /* xgettext: This refers to a "none underline" */
71 { NC_("underline", "None"), UNDERLINE_NONE },
72 { NC_("underline", "Single"), UNDERLINE_SINGLE },
73 { NC_("underline", "Double"), UNDERLINE_DOUBLE },
74 /* xgettext: This refers to a "single low underline" */
75 { NC_("underline", "Single Low"), UNDERLINE_SINGLE_LOW },
76 /* xgettext: This refers to a "double low underline" */
77 { NC_("underline", "Double Low"), UNDERLINE_DOUBLE_LOW }
79 #endif
81 /* The order corresponds to border_preset_buttons */
82 typedef enum
84 BORDER_PRESET_NONE,
85 BORDER_PRESET_OUTLINE,
86 BORDER_PRESET_INSIDE,
88 BORDER_PRESET_MAX
89 } BorderPresets;
91 struct _FormatState;
93 typedef struct {
94 struct _FormatState *state;
95 int cur_index;
96 GtkToggleButton *current_pattern;
97 GtkToggleButton *default_button;
98 void (*draw_preview) (struct _FormatState *);
99 } PatternPicker;
101 typedef struct {
102 struct _FormatState *state;
104 GtkWidget *combo;
105 GCallback preview_update;
106 } ColorPicker;
108 typedef struct {
109 struct _FormatState *state;
110 GtkToggleButton *button;
111 GnmStyleBorderType pattern_index;
112 gboolean is_selected; /* Is it selected */
113 GnmStyleBorderLocation index;
114 guint rgba;
115 gboolean is_auto_color;
116 gboolean is_set; /* Has the element been changed */
117 } BorderPicker;
119 typedef struct {
120 GtkLabel *name;
121 GnmExprEntry *entry;
122 } ExprEntry;
124 typedef struct _FormatState {
125 GtkBuilder *gui;
126 WBCGtk *wbcg;
127 GtkDialog *dialog;
128 GtkNotebook *notebook;
129 GtkWidget *apply_button;
130 GtkWidget *ok_button;
132 Sheet *sheet;
133 SheetView *sv;
134 GnmValue *value;
135 unsigned int conflicts;
136 GnmStyle *style, *result;
137 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX];
139 int selection_mask;
140 gboolean enable_edit;
142 GtkWidget * format_sel;
144 struct {
145 GtkCheckButton *wrap;
146 GtkSpinButton *indent_button;
147 GtkWidget *indent_label;
148 int indent;
149 GORotationSel *rotation;
150 } align;
151 struct {
152 GOFontSel *selector;
153 GtkWidget *underline_picker;
154 GnmUnderline underline;
155 } font;
156 struct {
157 GocCanvas *canvas;
158 GtkButton *preset[BORDER_PRESET_MAX];
159 GocItem *back;
160 GocItem *lines[20];
162 BorderPicker edge[GNM_STYLE_BORDER_EDGE_MAX];
163 ColorPicker color;
164 guint rgba;
165 gboolean is_auto_color;
166 PatternPicker pattern;
167 } border;
168 struct {
169 GocCanvas *canvas;
170 GnmPreviewGrid *grid;
171 GnmStyle *style;
173 ColorPicker back_color;
174 ColorPicker pattern_color;
175 PatternPicker pattern;
176 } back;
177 struct {
178 GtkCheckButton *hidden, *locked, *sheet_protected;
180 gboolean sheet_protected_changed;
181 gboolean sheet_protected_value;
182 } protection;
183 struct {
184 GtkGrid *criteria_grid;
185 GtkComboBox *constraint_type;
186 GtkLabel *operator_label;
187 GtkComboBox *op;
188 ExprEntry expr0, expr1;
189 GtkToggleButton *allow_blank;
190 GtkToggleButton *use_dropdown;
192 struct {
193 GtkLabel *action_label;
194 GtkLabel *title_label;
195 GtkLabel *msg_label;
196 GtkComboBox *action;
197 GtkEntry *title;
198 GtkTextView *msg;
199 GtkImage *image;
200 } error;
201 gboolean changed;
202 int valid;
203 } validation;
204 struct {
205 GtkToggleButton *flag;
207 GtkLabel *title_label;
208 GtkLabel *msg_label;
209 GtkEntry *title;
210 GtkTextView *msg;
211 } input_msg;
212 struct {
213 gboolean is_selector;
214 GtkWindow *w;
215 gpointer closure;
216 } style_selector;
217 } FormatState;
219 enum {
220 CONDITIONS_RANGE,
221 CONDITIONS_COND,
222 CONDITIONS_NUM_COLUMNS
227 /*****************************************************************************/
228 /* Some utility routines shared by all pages */
231 * A utility routine to help mark the attributes as being changed
232 * VERY stupid for now.
234 static void
235 fmt_dialog_changed (FormatState *state)
237 GOFormatSel *gfs;
238 GOFormat const *fmt;
239 gboolean ok;
241 if (!state->enable_edit)
242 return;
244 gfs = GO_FORMAT_SEL (state->format_sel);
245 fmt = go_format_sel_get_fmt (gfs);
246 ok = !go_format_is_invalid (fmt);
248 gtk_widget_set_sensitive (state->apply_button, ok);
249 gtk_widget_set_sensitive (state->ok_button, ok);
252 /* Default to the 'Format' page but remember which page we were on between
253 * invocations */
254 static FormatDialogPosition_t fmt_dialog_page = FD_NUMBER;
256 #if 0
257 /* The last currency selected */
258 static int fmt_dialog_currency = 0;
259 #endif
262 * Callback routine to help remember which format tab was selected
263 * between dialog invocations.
265 static void
266 cb_page_select (G_GNUC_UNUSED GtkNotebook *notebook,
267 G_GNUC_UNUSED GtkWidget *page,
268 gint page_num,
269 G_GNUC_UNUSED gpointer user_data)
271 fmt_dialog_page = page_num;
274 static void
275 cb_notebook_destroy (GtkWidget *nb, gpointer page_sig_ptr)
277 g_signal_handler_disconnect (nb, GPOINTER_TO_UINT (page_sig_ptr));
281 * Callback routine to give radio button like behaviour to the
282 * set of toggle buttons used for line & background patterns.
284 static void
285 cb_toggle_changed (GtkToggleButton *button, PatternPicker *picker)
287 if (gtk_toggle_button_get_active (button) &&
288 picker->current_pattern != button) {
289 gtk_toggle_button_set_active (picker->current_pattern, FALSE);
290 picker->current_pattern = button;
291 picker->cur_index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "index"));
292 if (picker->draw_preview)
293 picker->draw_preview (picker->state);
298 * Setup routine to associate images with toggle buttons
299 * and to adjust the relief so it looks nice.
301 static void
302 setup_pattern_button (GdkScreen *screen,
303 GtkBuilder *gui,
304 char const *const name,
305 PatternPicker *picker,
306 gboolean do_image,
307 gboolean from_icon,
308 int const index,
309 int const select_index,
310 unsigned size)
312 GtkWidget *tmp = go_gtk_builder_get_widget (gui, name);
313 if (tmp != NULL) {
314 GtkButton *button = GTK_BUTTON (tmp);
315 if (do_image) {
316 char *res = g_strconcat ("/org/gnumeric/gnumeric/images/", name, ".png", NULL);
317 GtkWidget *image;
318 if (from_icon)
319 image = gtk_image_new_from_icon_name (name, GTK_ICON_SIZE_DIALOG);
320 else {
321 /* gtk_image_new_from_resource() is unable to load pixdata with gdk-pixbuf >= 2.36.1
322 * because it uses the gdk_pixbuf_loader API and the pixdata module has been removed
323 * because of a security issue. See #776004. */
324 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_resource (res, NULL);
325 image = gtk_image_new_from_pixbuf (pixbuf);
326 g_object_unref (pixbuf);
328 g_free (res);
329 gtk_widget_show (image);
330 gtk_container_add (GTK_CONTAINER (tmp), image);
333 if (picker->current_pattern == NULL) {
334 picker->default_button = GTK_TOGGLE_BUTTON (button);
335 picker->current_pattern = picker->default_button;
336 picker->cur_index = index;
339 gtk_button_set_relief (button, GTK_RELIEF_NONE);
340 g_signal_connect (G_OBJECT (button),
341 "toggled",
342 G_CALLBACK (cb_toggle_changed), picker);
343 g_object_set_data (G_OBJECT (button), "index",
344 GINT_TO_POINTER (index));
346 /* Set the state AFTER the signal to get things redrawn correctly */
347 if (index == select_index) {
348 picker->cur_index = index;
349 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
350 TRUE);
352 } else
353 g_warning ("CellFormat: Unexpected missing widget");
356 static void
357 setup_color_pickers (FormatState *state,
358 ColorPicker *picker,
359 char const *color_group,
360 char const *placeholder,
361 char const *label,
362 char const *default_caption,
363 char const *caption,
364 GCallback preview_update,
365 GnmStyleElement e,
366 gboolean allow_alpha)
368 GtkWidget *combo, *w, *frame;
369 GOColorGroup *cg;
370 GnmColor *mcolor = NULL;
371 GnmColor *def_sc = NULL;
373 switch (e) {
374 case MSTYLE_COLOR_PATTERN:
375 if (0 == (state->conflicts & (1 << MSTYLE_COLOR_PATTERN)))
376 mcolor = gnm_style_get_pattern_color (state->style);
378 /* fallthrough */
380 case MSTYLE_BORDER_TOP: /* MSTYLE_BORDER_TOP is abused as representing all borders. */
381 def_sc = sheet_style_get_auto_pattern_color (state->sheet);
382 break;
383 case MSTYLE_FONT_COLOR:
384 if (0 == (state->conflicts & (1 << MSTYLE_FONT_COLOR)))
385 mcolor = gnm_style_get_font_color (state->style);
386 def_sc = style_color_auto_font ();
387 break;
388 case MSTYLE_COLOR_BACK:
389 if (0 == (state->conflicts & (1 << MSTYLE_COLOR_BACK)))
390 mcolor = gnm_style_get_back_color (state->style);
391 def_sc = style_color_auto_back ();
392 break;
393 default:
394 g_warning ("Unhandled style element!");
396 cg = go_color_group_fetch (color_group, NULL);
397 combo = go_combo_color_new (NULL, default_caption,
398 def_sc ? def_sc->go_color : GO_COLOR_BLACK, cg);
399 g_object_unref (cg);
400 go_combo_box_set_title (GO_COMBO_BOX (combo), caption);
402 /* Connect to the sample canvas and redraw it */
403 g_signal_connect (G_OBJECT (combo),
404 "color_changed",
405 G_CALLBACK (preview_update), state);
407 if (mcolor && !mcolor->is_auto)
408 go_combo_color_set_color (GO_COMBO_COLOR (combo),
409 mcolor->go_color);
410 else
411 go_combo_color_set_color_to_default (GO_COMBO_COLOR (combo));
413 if (allow_alpha)
414 go_combo_color_set_allow_alpha (GO_COMBO_COLOR (combo), TRUE);
415 frame = gtk_frame_new (NULL);
416 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
417 gtk_container_add (GTK_CONTAINER (frame), combo);
418 gtk_widget_show_all (frame);
420 w = go_gtk_builder_get_widget (state->gui, placeholder);
421 go_gtk_widget_replace (w, frame);
423 w = go_gtk_builder_get_widget (state->gui, label);
424 gtk_label_set_mnemonic_widget (GTK_LABEL (w), combo);
426 style_color_unref (def_sc);
428 if (picker != NULL) {
429 picker->combo = combo;
430 picker->preview_update = preview_update;
434 /*****************************************************************************/
436 static void
437 cb_number_format_changed (G_GNUC_UNUSED GtkWidget *widget,
438 const char *fmt,
439 FormatState *state)
441 gboolean changed = FALSE;
442 g_return_if_fail (state != NULL);
444 if (!state->enable_edit)
445 return;
447 if (fmt) {
448 GOFormat *format = go_format_new_from_XL (fmt);
449 gnm_style_set_format (state->result, format);
450 go_format_unref (format);
451 changed = TRUE;
454 if (changed)
455 fmt_dialog_changed (state);
458 static void
459 fmt_dialog_init_format_page (FormatState *state)
461 GOFormatSel *gfs;
462 GODateConventions const *date_conv = sheet_date_conv (state->sheet);
464 state->format_sel = gnm_format_sel_new ();
465 gfs = GO_FORMAT_SEL (state->format_sel);
467 gtk_notebook_prepend_page (GTK_NOTEBOOK (state->notebook),
468 state->format_sel,
469 gtk_label_new (_("Number")));
470 gtk_widget_show (GTK_WIDGET (gfs));
472 if (0 == (state->conflicts & (1 << MSTYLE_FORMAT))) {
473 GOFormat const *fmt = gnm_style_get_format (state->style);
474 go_format_sel_set_style_format (gfs, fmt);
476 go_format_sel_set_dateconv (gfs, date_conv);
477 go_format_sel_editable_enters (gfs, GTK_WINDOW (state->dialog));
479 g_signal_connect (G_OBJECT (state->format_sel), "format_changed",
480 G_CALLBACK (cb_number_format_changed), state);
483 /*****************************************************************************/
485 static void
486 cb_indent_changed (GtkEditable *editable, FormatState *state)
488 if (state->enable_edit) {
489 GtkSpinButton *sb = GTK_SPIN_BUTTON (editable);
490 int val = gtk_spin_button_get_value_as_int (sb);
492 if (state->align.indent != val) {
493 state->align.indent = val;
494 gnm_style_set_indent (state->result, val);
495 fmt_dialog_changed (state);
500 static void
501 cb_align_h_toggle (GtkToggleButton *button, FormatState *state)
503 if (!gtk_toggle_button_get_active (button))
504 return;
506 if (state->enable_edit) {
507 GnmHAlign const new_h =
508 GPOINTER_TO_INT (g_object_get_data (
509 G_OBJECT (button), "align"));
510 gboolean const supports_indent =
511 (new_h == GNM_HALIGN_LEFT || new_h == GNM_HALIGN_RIGHT);
512 gnm_style_set_align_h (state->result, new_h);
513 gtk_widget_set_sensitive (GTK_WIDGET (state->align.indent_button),
514 supports_indent);
515 gtk_widget_set_sensitive (GTK_WIDGET (state->align.indent_label),
516 supports_indent);
517 /* TODO: Should we 0 the indent ? */
518 fmt_dialog_changed (state);
522 static void
523 cb_align_v_toggle (GtkToggleButton *button, FormatState *state)
525 if (!gtk_toggle_button_get_active (button))
526 return;
528 if (state->enable_edit) {
529 gnm_style_set_align_v (
530 state->result,
531 GPOINTER_TO_INT (g_object_get_data (
532 G_OBJECT (button), "align")));
533 fmt_dialog_changed (state);
537 static void
538 cb_align_wrap_toggle (GtkToggleButton *button, FormatState *state)
540 if (state->enable_edit) {
541 gnm_style_set_wrap_text (state->result,
542 gtk_toggle_button_get_active (button));
543 fmt_dialog_changed (state);
547 static void
548 fmt_dialog_init_align_radio (char const *const name,
549 int const val, int const target,
550 FormatState *state,
551 GCallback handler)
553 GtkWidget *tmp = go_gtk_builder_get_widget (state->gui, name);
554 if (tmp != NULL) {
555 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tmp),
556 val == target);
557 g_object_set_data (G_OBJECT (tmp), "align",
558 GINT_TO_POINTER (val));
559 g_signal_connect (G_OBJECT (tmp),
560 "toggled",
561 handler, state);
565 static void
566 cb_rotation_changed (G_GNUC_UNUSED GORotationSel *grs, int angle, FormatState *state)
568 if (angle < 0)
569 angle += 360;
570 gnm_style_set_rotation (state->result, angle);
571 fmt_dialog_changed (state);
574 static void
575 fmt_dialog_init_align_page (FormatState *state)
577 static struct {
578 char const *const name;
579 GnmHAlign align;
580 } const h_buttons[] = {
581 { "halign_left", GNM_HALIGN_LEFT },
582 { "halign_center", GNM_HALIGN_CENTER },
583 { "halign_right", GNM_HALIGN_RIGHT },
584 { "halign_general", GNM_HALIGN_GENERAL },
585 { "halign_justify", GNM_HALIGN_JUSTIFY },
586 { "halign_fill", GNM_HALIGN_FILL },
587 { "halign_center_across_selection", GNM_HALIGN_CENTER_ACROSS_SELECTION },
588 { "halign_distributed", GNM_HALIGN_DISTRIBUTED },
589 { NULL, 0}
591 static struct {
592 char const *const name;
593 GnmVAlign align;
594 } const v_buttons[] = {
595 { "valign_top", GNM_VALIGN_TOP },
596 { "valign_center", GNM_VALIGN_CENTER },
597 { "valign_bottom", GNM_VALIGN_BOTTOM },
598 { "valign_justify", GNM_VALIGN_JUSTIFY },
599 { "valign_distributed", GNM_VALIGN_DISTRIBUTED },
600 { NULL, 0}
603 GtkWidget *w;
604 gboolean wrap = FALSE;
605 GnmHAlign h = GNM_HALIGN_GENERAL;
606 GnmVAlign v = GNM_VALIGN_CENTER;
607 char const *name;
608 int i, r;
610 if (0 == (state->conflicts & (1 << MSTYLE_ALIGN_H)))
611 h = gnm_style_get_align_h (state->style);
612 if (0 == (state->conflicts & (1 << MSTYLE_ALIGN_V)))
613 v = gnm_style_get_align_v (state->style);
615 /* Setup the horizontal buttons */
616 for (i = 0; (name = h_buttons[i].name) != NULL; ++i)
617 fmt_dialog_init_align_radio (name, h_buttons[i].align,
618 h, state,
619 G_CALLBACK (cb_align_h_toggle));
621 /* Setup the vertical buttons */
622 for (i = 0; (name = v_buttons[i].name) != NULL; ++i)
623 fmt_dialog_init_align_radio (name, v_buttons[i].align,
624 v, state,
625 G_CALLBACK (cb_align_v_toggle));
627 /* Setup the wrap button, and assign the current value */
628 if (0 == (state->conflicts & (1 << MSTYLE_WRAP_TEXT)))
629 wrap = gnm_style_get_wrap_text (state->style);
631 w = go_gtk_builder_get_widget (state->gui, "align_wrap");
632 state->align.wrap = GTK_CHECK_BUTTON (w);
633 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), wrap);
634 g_signal_connect (G_OBJECT (w),
635 "toggled",
636 G_CALLBACK (cb_align_wrap_toggle), state);
638 if (0 != (state->conflicts & (1 << MSTYLE_INDENT)) ||
639 (h != GNM_HALIGN_LEFT && h != GNM_HALIGN_RIGHT))
640 state->align.indent = 0;
641 else
642 state->align.indent = gnm_style_get_indent (state->style);
644 state->align.indent_label =
645 go_gtk_builder_get_widget (state->gui, "halign_indent_label");
646 w = go_gtk_builder_get_widget (state->gui, "halign_indent");
647 state->align.indent_button = GTK_SPIN_BUTTON (w);
648 gtk_spin_button_set_value (state->align.indent_button, state->align.indent);
649 gtk_widget_set_sensitive (GTK_WIDGET (state->align.indent_button),
650 (h == GNM_HALIGN_LEFT || h == GNM_HALIGN_RIGHT));
651 gtk_widget_set_sensitive (GTK_WIDGET (state->align.indent_label),
652 (h == GNM_HALIGN_LEFT || h == GNM_HALIGN_RIGHT));
654 /* Catch changes to the spin box */
655 g_signal_connect (G_OBJECT (w),
656 "value-changed",
657 G_CALLBACK (cb_indent_changed), state);
659 /* Catch <return> in the spin box */
660 gnm_editable_enters (
661 GTK_WINDOW (state->dialog),
662 GTK_WIDGET (w));
664 /* setup the rotation canvas */
665 if (0 == (state->conflicts & (1 << MSTYLE_ROTATION))) {
666 r = gnm_style_get_rotation (state->style);
667 if (r > 180)
668 r -= 360;
669 } else
670 r = 0;
671 state->align.rotation = (GORotationSel *) go_rotation_sel_new ();
672 go_rotation_sel_set_rotation (state->align.rotation, r);
673 g_signal_connect (G_OBJECT (state->align.rotation), "rotation-changed",
674 G_CALLBACK (cb_rotation_changed), state);
675 go_gtk_widget_replace (go_gtk_builder_get_widget (state->gui, "rotation_placeholder"),
676 GTK_WIDGET (state->align.rotation));
679 /*****************************************************************************/
681 static void
682 cb_font_changed (G_GNUC_UNUSED GtkWidget *widget,
683 PangoAttrList *attrs, FormatState *state)
685 PangoAttrIterator *aiter;
686 const PangoAttribute *attr;
687 GnmStyle *res = state->result;
688 GOFontScript script = GO_FONT_SCRIPT_STANDARD;
689 gboolean has_script_attr = FALSE;
690 GnmColor *c;
692 gboolean changed = FALSE;
693 g_return_if_fail (state != NULL);
695 if (!state->enable_edit)
696 return;
698 aiter = pango_attr_list_get_iterator (attrs);
700 attr = pango_attr_iterator_get (aiter, PANGO_ATTR_FAMILY);
701 if (attr) {
702 const char *s = ((PangoAttrString*)attr)->value;
703 if (!gnm_style_is_element_set (res, MSTYLE_FONT_NAME) ||
704 !g_str_equal (s, gnm_style_get_font_name (res))) {
705 changed = TRUE;
706 gnm_style_set_font_name (res, s);
710 attr = pango_attr_iterator_get (aiter, PANGO_ATTR_SIZE);
711 if (attr) {
712 int i = ((PangoAttrInt*)attr)->value;
713 double d = i / (double)PANGO_SCALE;
714 if (!gnm_style_is_element_set (res, MSTYLE_FONT_SIZE) ||
715 d != gnm_style_get_font_size (res)) {
716 changed = TRUE;
717 gnm_style_set_font_size (res, d);
721 attr = pango_attr_iterator_get (aiter, PANGO_ATTR_WEIGHT);
722 if (attr) {
723 int i = ((PangoAttrInt*)attr)->value;
724 gboolean b = (i >= PANGO_WEIGHT_BOLD);
725 if (!gnm_style_is_element_set (res, MSTYLE_FONT_BOLD) ||
726 b != gnm_style_get_font_bold (res)) {
727 changed = TRUE;
728 gnm_style_set_font_bold (res, b);
732 attr = pango_attr_iterator_get (aiter, PANGO_ATTR_STYLE);
733 if (attr) {
734 int i = ((PangoAttrInt*)attr)->value;
735 gboolean b = (i != PANGO_STYLE_NORMAL);
736 if (!gnm_style_is_element_set (res, MSTYLE_FONT_ITALIC) ||
737 b != gnm_style_get_font_italic (res)) {
738 changed = TRUE;
739 gnm_style_set_font_italic (res, b);
743 attr = pango_attr_iterator_get (aiter, PANGO_ATTR_UNDERLINE);
744 if (attr) {
745 /* Underline is special: we go beyond what pango has */
746 GnmUnderline u = state->font.underline;
747 if (!gnm_style_is_element_set (res, MSTYLE_FONT_UNDERLINE) ||
748 u != gnm_style_get_font_uline (res)) {
749 changed = TRUE;
750 gnm_style_set_font_uline (res, u);
754 attr = pango_attr_iterator_get (aiter, PANGO_ATTR_STRIKETHROUGH);
755 if (attr) {
756 int i = ((PangoAttrInt*)attr)->value;
757 gboolean b = (i != 0);
758 if (!gnm_style_is_element_set (res, MSTYLE_FONT_STRIKETHROUGH) ||
759 b != gnm_style_get_font_strike (res)) {
760 changed = TRUE;
761 gnm_style_set_font_strike (res, b);
765 attr = pango_attr_iterator_get (aiter, go_pango_attr_subscript_get_attr_type ());
766 if (attr) {
767 has_script_attr = TRUE;
768 if (((GOPangoAttrSubscript*)attr)->val)
769 script = GO_FONT_SCRIPT_SUB;
771 attr = pango_attr_iterator_get (aiter, go_pango_attr_superscript_get_attr_type ());
772 if (attr) {
773 has_script_attr = TRUE;
774 if (((GOPangoAttrSuperscript*)attr)->val)
775 script = GO_FONT_SCRIPT_SUPER;
777 if (has_script_attr &&
778 (!gnm_style_is_element_set (res, MSTYLE_FONT_SCRIPT) ||
779 script != gnm_style_get_font_script (res))) {
780 changed = TRUE;
781 gnm_style_set_font_script (res, script);
784 attr = pango_attr_iterator_get (aiter, PANGO_ATTR_FOREGROUND);
785 c = attr
786 ? gnm_color_new_pango (&((PangoAttrColor*)attr)->color)
787 : style_color_auto_font ();
788 if (!gnm_style_is_element_set (res, MSTYLE_FONT_COLOR) ||
789 !style_color_equal (c, gnm_style_get_font_color (res))) {
790 changed = TRUE;
791 gnm_style_set_font_color (res, c);
792 } else
793 style_color_unref (c);
795 pango_attr_iterator_destroy (aiter);
797 if (changed)
798 fmt_dialog_changed (state);
801 static void
802 change_font_attr (FormatState *state, PangoAttribute *attr)
804 GOFontSel *gfs = state->font.selector;
805 PangoAttrList *attrs = pango_attr_list_copy
806 (go_font_sel_get_sample_attributes (gfs));
807 attr->start_index = 0;
808 attr->end_index = -1;
809 pango_attr_list_change (attrs, attr);
810 go_font_sel_set_sample_attributes (gfs, attrs);
811 cb_font_changed (NULL, attrs, state);
812 pango_attr_list_unref (attrs);
815 static void
816 set_font_underline (FormatState *state, GnmUnderline uline)
818 PangoUnderline pu = gnm_translate_underline_to_pango (uline);
819 GOOptionMenu *om = GO_OPTION_MENU (state->font.underline_picker);
820 GtkMenuShell *ms = GTK_MENU_SHELL (go_option_menu_get_menu (om));
821 GList *children, *l;
823 if (uline != state->font.underline) {
824 state->font.underline = uline;
825 change_font_attr (state, pango_attr_underline_new (pu));
828 children = gtk_container_get_children (GTK_CONTAINER (ms));
829 for (l = children; l; l = l->next) {
830 GtkMenuItem *item = GTK_MENU_ITEM (l->data);
831 GnmUnderline u = GPOINTER_TO_INT
832 (g_object_get_data (G_OBJECT (item), "value"));
833 if (u == uline)
834 go_option_menu_select_item (om, item);
836 g_list_free (children);
839 static void
840 cb_underline_changed (GOOptionMenu *om, FormatState *state)
842 GtkWidget *selected = go_option_menu_get_history (om);
843 GnmUnderline u;
845 if (!selected)
846 return;
848 u = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (selected), "value"));
849 set_font_underline (state, u);
853 /* Manually insert the font selector, and setup signals */
854 static void
855 fmt_dialog_init_font_page (FormatState *state)
857 GOColorGroup *cg;
858 GtkWidget *font_widget;
859 gboolean strikethrough = FALSE;
860 GOFontScript script = GO_FONT_SCRIPT_STANDARD;
861 GODateConventions const *date_conv = sheet_date_conv (state->sheet);
862 GnmColor *mcolor = NULL;
863 GnmColor *def_sc;
864 GtkWidget *up;
866 up = state->font.underline_picker = go_option_menu_build
867 (C_("underline", "None"), UNDERLINE_NONE,
868 C_("underline", "Single"), UNDERLINE_SINGLE,
869 C_("underline", "Double"), UNDERLINE_DOUBLE,
870 C_("underline", "Single Low"), UNDERLINE_SINGLE_LOW,
871 C_("underline", "Double Low"), UNDERLINE_DOUBLE_LOW,
872 NULL);
873 g_signal_connect (up,
874 "changed", G_CALLBACK (cb_underline_changed), state);
875 def_sc = style_color_auto_font ();
876 cg = go_color_group_fetch ("fore_color_group", NULL);
877 font_widget = g_object_new (GO_TYPE_FONT_SEL,
878 "show-style", TRUE,
879 "show-color", TRUE,
880 "color-unset-text", _("Automatic"),
881 "color-group", cg,
882 "color-default", def_sc->go_color,
883 "show-underline", TRUE,
884 "underline-picker", up,
885 "show-script", TRUE,
886 "show-strikethrough", TRUE,
887 "vexpand", TRUE,
888 "hexpand", TRUE,
889 NULL);
890 g_object_unref (cg);
891 style_color_unref (def_sc);
892 state->font.selector = GO_FONT_SEL (font_widget);
893 g_object_unref (up);
895 gtk_widget_show (font_widget);
896 gtk_container_add (GTK_CONTAINER (go_gtk_builder_get_widget (state->gui, "font_sel_placeholder")),
897 font_widget);
899 go_font_sel_editable_enters (state->font.selector,
900 GTK_WINDOW (state->dialog));
902 if (state->value) {
903 char *s = format_value (NULL, state->value, -1, date_conv);
904 go_font_sel_set_sample_text (state->font.selector, s);
905 g_free (s);
908 if (0 == (state->conflicts & (1 << MSTYLE_FONT_NAME))) {
909 const char *family = gnm_style_get_font_name (state->style);
910 go_font_sel_set_family (state->font.selector, family);
913 if (0 == (state->conflicts & (1 << MSTYLE_FONT_BOLD)) &&
914 0 == (state->conflicts & (1 << MSTYLE_FONT_ITALIC))) {
915 gboolean is_bold = gnm_style_get_font_bold (state->style);
916 gboolean is_italic = gnm_style_get_font_italic (state->style);
918 go_font_sel_set_style
919 (state->font.selector,
920 is_bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
921 is_italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
924 if (0 == (state->conflicts & (1 << MSTYLE_FONT_SIZE))) {
925 double pts = gnm_style_get_font_size (state->style);
926 go_font_sel_set_size (state->font.selector,
927 pts * PANGO_SCALE);
930 state->font.underline = UNDERLINE_NONE;
931 if (0 == (state->conflicts & (1 << MSTYLE_FONT_UNDERLINE))) {
932 GnmUnderline ut = gnm_style_get_font_uline (state->style);
933 set_font_underline (state, ut);
936 if (0 == (state->conflicts & (1 << MSTYLE_FONT_COLOR)))
937 mcolor = gnm_style_get_font_color (state->style);
938 go_font_sel_set_color (state->font.selector,
939 mcolor ? mcolor->go_color : GO_COLOR_BLACK,
940 !mcolor || mcolor->is_auto);
942 if (0 == (state->conflicts & (1 << MSTYLE_FONT_STRIKETHROUGH)))
943 strikethrough = gnm_style_get_font_strike (state->style);
944 go_font_sel_set_strikethrough (state->font.selector, strikethrough);
946 if (0 == (state->conflicts & (1 << MSTYLE_FONT_SCRIPT)))
947 script = gnm_style_get_font_script (state->style);
948 go_font_sel_set_script (state->font.selector, script);
950 if (0 == (state->conflicts & (1 << MSTYLE_FONT_COLOR)))
951 change_font_attr
952 (state,
953 go_color_to_pango (gnm_style_get_font_color (state->style)->go_color,
954 TRUE));
956 g_signal_connect (G_OBJECT (state->font.selector),
957 "font_changed",
958 G_CALLBACK (cb_font_changed), state);
961 /*****************************************************************************/
963 static void
964 back_style_changed (FormatState *state)
966 g_return_if_fail (state->back.style != NULL);
968 fmt_dialog_changed (state);
970 if (state->enable_edit) {
971 gnm_style_merge_element (state->result, state->back.style, MSTYLE_PATTERN);
972 gnm_style_merge_element (state->result, state->back.style, MSTYLE_COLOR_BACK);
973 gnm_style_merge_element (state->result, state->back.style, MSTYLE_COLOR_PATTERN);
974 goc_item_set (GOC_ITEM (state->back.grid),
975 "default-style", state->back.style,
976 NULL);
980 static void
981 cb_back_preview_color (G_GNUC_UNUSED GOComboColor *combo,
982 GOColor c,
983 G_GNUC_UNUSED gboolean is_custom,
984 G_GNUC_UNUSED gboolean by_user,
985 gboolean is_default,
986 FormatState *state)
988 GnmColor *sc;
990 g_return_if_fail (c);
992 if (is_default) {
993 sc = style_color_auto_back ();
994 gnm_style_set_pattern (state->back.style, 0);
995 } else {
996 sc = gnm_color_new_go (c);
997 gnm_style_set_pattern (state->back.style, state->back.pattern.cur_index);
1000 gnm_style_set_back_color (state->back.style, sc);
1001 back_style_changed (state);
1004 static void
1005 cb_pattern_preview_color (G_GNUC_UNUSED GOComboColor *combo,
1006 GOColor c,
1007 G_GNUC_UNUSED gboolean is_custom,
1008 G_GNUC_UNUSED gboolean by_user,
1009 gboolean is_default, FormatState *state)
1011 GnmColor *col = is_default
1012 ? sheet_style_get_auto_pattern_color (state->sheet)
1013 : gnm_color_new_go (c);
1015 gnm_style_set_pattern_color (state->back.style, col);
1017 back_style_changed (state);
1020 static void
1021 draw_pattern_selected (FormatState *state)
1023 gnm_style_set_pattern (state->back.style, state->back.pattern.cur_index);
1024 back_style_changed (state);
1027 static void
1028 fmt_dialog_init_background_page (FormatState *state)
1030 GtkWidget *widget;
1031 int w = 120;
1032 int h = 60;
1034 widget = g_object_new (GOC_TYPE_CANVAS, NULL);
1035 state->back.canvas = GOC_CANVAS (widget);
1036 gtk_widget_set_size_request (widget, w, h);
1038 widget = go_gtk_builder_get_widget (state->gui, "back_sample_frame");
1039 gtk_container_add (GTK_CONTAINER (widget),
1040 GTK_WIDGET (state->back.canvas));
1041 gtk_widget_show_all (widget);
1043 state->back.grid = GNM_PREVIEW_GRID (goc_item_new (
1044 goc_canvas_get_root (state->back.canvas),
1045 gnm_preview_grid_get_type (),
1046 "render-gridlines", FALSE,
1047 "default-col-width", w,
1048 "default-row-height", h,
1049 "default-style", state->back.style,
1050 NULL));
1053 /*****************************************************************************/
1056 * This is self-evident.
1057 * I stared at it for 15 minutes before realizing that it's self-evident,
1058 * but it is. - jon_kare
1060 * @points: x, y coordinates for the endpoints of the line segment.
1061 * @states: A bitmap of states the coordinates are valid for.
1062 * @location: Location.
1064 #define L 10. /* Left */
1065 #define R 140. /* Right */
1066 #define T 10. /* Top */
1067 #define B 90. /* Bottom */
1068 #define H 50. /* Horizontal Middle */
1069 #define V 75. /* Vertical Middle */
1071 static struct
1073 double const points[4];
1074 int const states;
1075 GnmStyleBorderLocation const location;
1076 } const line_info[] = {
1078 state 1 = single cell;
1079 state 2 = multi vert, single horiz (A1:A2);
1080 state 3 = single vert, multi horiz (A1:B1);
1081 state 4 = multi vertical & multi horizontal
1084 /* 1, 2, 3, 4 */
1085 { { L, T, R, T }, 0xf, GNM_STYLE_BORDER_TOP },
1086 { { L, B, R, B }, 0xf, GNM_STYLE_BORDER_BOTTOM },
1087 { { L, T, L, B }, 0xf, GNM_STYLE_BORDER_LEFT },
1088 { { R, T, R, B }, 0xf, GNM_STYLE_BORDER_RIGHT },
1090 /* Only for state 2 & 4 */
1091 { { L, H, R, H }, 0xa, GNM_STYLE_BORDER_HORIZ },
1093 /* Only for state 3 & 4 */
1094 { { V, T, V, B }, 0xc, GNM_STYLE_BORDER_VERT },
1096 /* Only for state 1 & 4 */
1097 { { L, T, R, B }, 0x9, GNM_STYLE_BORDER_REV_DIAG },
1098 { { L, B, R, T }, 0x9, GNM_STYLE_BORDER_DIAG},
1100 /* Only for state 2 */
1101 { { L, T, R, H }, 0x2, GNM_STYLE_BORDER_REV_DIAG },
1102 { { L, H, R, B }, 0x2, GNM_STYLE_BORDER_REV_DIAG },
1103 { { L, H, R, T }, 0x2, GNM_STYLE_BORDER_DIAG },
1104 { { L, B, R, H }, 0x2, GNM_STYLE_BORDER_DIAG },
1106 /* Only for state 3 */
1107 { { L, T, V, B }, 0x4, GNM_STYLE_BORDER_REV_DIAG },
1108 { { V, T, R, B }, 0x4, GNM_STYLE_BORDER_REV_DIAG },
1109 { { L, B, V, T }, 0x4, GNM_STYLE_BORDER_DIAG },
1110 { { V, B, R, T }, 0x4, GNM_STYLE_BORDER_DIAG },
1112 /* Only for state 4 */
1113 { { L, H, V, B }, 0x8, GNM_STYLE_BORDER_REV_DIAG },
1114 { { V, T, R, H }, 0x8, GNM_STYLE_BORDER_REV_DIAG },
1115 { { L, H, V, T }, 0x8, GNM_STYLE_BORDER_DIAG },
1116 { { V, B, R, H }, 0x8, GNM_STYLE_BORDER_DIAG },
1118 { { 0., 0., 0., 0. }, 0, 0 }
1121 static GnmBorder *
1122 border_get_mstyle (FormatState const *state, GnmStyleBorderLocation const loc)
1124 BorderPicker const *edge = & state->border.edge[loc];
1125 GnmColor *color;
1126 /* Don't set borders that have not been changed */
1127 if (!edge->is_set)
1128 return NULL;
1130 if (!edge->is_selected)
1131 return gnm_style_border_ref (gnm_style_border_none ());
1133 if (edge->is_auto_color) {
1134 color = sheet_style_get_auto_pattern_color (state->sheet);
1135 } else {
1136 guint8 const r = (guint8) (edge->rgba >> 24);
1137 guint8 const g = (guint8) (edge->rgba >> 16);
1138 guint8 const b = (guint8) (edge->rgba >> 8);
1139 guint8 const a = (guint8) (edge->rgba >> 0);
1140 color = gnm_color_new_rgba8 (r, g, b, a);
1142 return gnm_style_border_fetch
1143 (state->border.edge[loc].pattern_index, color,
1144 gnm_style_border_get_orientation (loc));
1147 /* See if either the color or pattern for any segment has changed and
1148 * apply the change to all of the lines that make up the segment.
1150 static gboolean
1151 border_format_has_changed (FormatState *state, BorderPicker *edge)
1153 int i;
1154 gboolean changed = FALSE;
1156 edge->is_set = TRUE;
1157 if (edge->is_auto_color) {
1158 if (!state->border.is_auto_color) {
1159 edge->is_auto_color = state->border.is_auto_color;
1160 changed = TRUE;
1162 } else if (edge->rgba != state->border.rgba)
1163 changed = TRUE;
1165 if (edge->rgba != state->border.rgba) {
1166 edge->rgba = state->border.rgba;
1168 for (i = 0; line_info[i].states != 0 ; ++i ) {
1169 if (line_info[i].location == edge->index &&
1170 state->border.lines[i] != NULL)
1171 go_styled_object_get_style (
1172 GO_STYLED_OBJECT (state->border.lines[i]))->line.color = edge->rgba;
1175 if ((int)edge->pattern_index != state->border.pattern.cur_index) {
1176 edge->pattern_index = state->border.pattern.cur_index;
1177 for (i = 0; line_info[i].states != 0 ; ++i ) {
1178 if (line_info[i].location == edge->index &&
1179 state->border.lines[i] != NULL) {
1180 gnm_dashed_canvas_line_set_dash_index (
1181 GNM_DASHED_CANVAS_LINE (state->border.lines[i]),
1182 edge->pattern_index);
1185 changed = TRUE;
1188 return changed;
1192 * Map canvas x.y coords to a border type
1193 * Handle all of the various permutations of lines
1195 static gboolean
1196 border_event (GtkWidget *widget, GdkEventButton *event, FormatState *state)
1198 double x = event->x;
1199 double y = event->y;
1200 BorderPicker *edge;
1201 GnmStyleBorderLocation which;
1203 if (event->button != 1)
1204 return FALSE;
1206 /* If we receive a double or triple translate them into single clicks */
1207 if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) {
1208 GdkEventType type = event->type;
1209 event->type = GDK_BUTTON_PRESS;
1210 border_event (widget, event, state);
1211 if (event->type == GDK_3BUTTON_PRESS)
1212 border_event (widget, event, state);
1213 event->type = type;
1216 /* The edges are always there */
1217 if (x <= L+5.) which = GNM_STYLE_BORDER_LEFT;
1218 else if (y <= T+5.) which = GNM_STYLE_BORDER_TOP;
1219 else if (y >= B-5.) which = GNM_STYLE_BORDER_BOTTOM;
1220 else if (x >= R-5.) which = GNM_STYLE_BORDER_RIGHT;
1221 else switch (state->selection_mask) {
1222 case 1:
1223 if ((x < V) == (y < H))
1224 which = GNM_STYLE_BORDER_REV_DIAG;
1225 else
1226 which = GNM_STYLE_BORDER_DIAG;
1227 break;
1228 case 2:
1229 if (H-5. < y && y < H+5.)
1230 which = GNM_STYLE_BORDER_HORIZ;
1231 else {
1232 /* Map everything back to the top */
1233 if (y > H) y -= H-10.;
1235 if ((x < V) == (y < H/2.))
1236 which = GNM_STYLE_BORDER_REV_DIAG;
1237 else
1238 which = GNM_STYLE_BORDER_DIAG;
1240 break;
1241 case 4:
1242 if (V-5. < x && x < V+5.)
1243 which = GNM_STYLE_BORDER_VERT;
1244 else {
1245 /* Map everything back to the left */
1246 if (x > V) x -= V-10.;
1248 if ((x < V/2.) == (y < H))
1249 which = GNM_STYLE_BORDER_REV_DIAG;
1250 else
1251 which = GNM_STYLE_BORDER_DIAG;
1253 break;
1254 case 8:
1255 if (V-5. < x && x < V+5.)
1256 which = GNM_STYLE_BORDER_VERT;
1257 else if (H-5. < y && y < H+5.)
1258 which = GNM_STYLE_BORDER_HORIZ;
1259 else {
1260 /* Map everything back to the 1st quadrant */
1261 if (x > V) x -= V-10.;
1262 if (y > H) y -= H-10.;
1264 if ((x < V/2.) == (y < H/2.))
1265 which = GNM_STYLE_BORDER_REV_DIAG;
1266 else
1267 which = GNM_STYLE_BORDER_DIAG;
1269 break;
1271 default:
1272 which = GNM_STYLE_BORDER_LEFT;
1273 g_assert_not_reached ();
1276 edge = &state->border.edge[which];
1277 if (!border_format_has_changed (state, edge) || !edge->is_selected)
1278 gtk_toggle_button_set_active (edge->button,
1279 !edge->is_selected);
1280 else
1281 fmt_dialog_changed (state);
1283 return TRUE;
1286 static void
1287 draw_border_preview (FormatState *state)
1289 static double const corners[12][6] = {
1290 { L-5., T, L, T, L, T-5. },
1291 { R+5., T, R, T, R, T-5 },
1292 { L-5., B, L, B, L, B+5. },
1293 { R+5., B, R, B, R, B+5. },
1295 { V-5., T-1., V, T-1., V, T-5. },
1296 { V+5., T-1., V, T-1., V, T-5. },
1298 { V-5., B+1., V, B+1., V, B+5. },
1299 { V+5., B+1., V, B+1., V, B+5. },
1301 { L-1., H-5., L-1., H, L-5., H },
1302 { L-1., H+5., L-1., H, L-5., H },
1304 { R+1., H-5., R+1., H, R+5., H },
1305 { R+1., H+5., R+1., H, R+5., H }
1307 int i, j, k;
1309 /* The first time through lets initialize */
1310 if (state->border.canvas == NULL) {
1311 GocGroup *group;
1312 GocPoints *points;
1313 GOStyle *style;
1315 state->border.canvas = GOC_CANVAS (g_object_new (GOC_TYPE_CANVAS, NULL));
1316 gtk_widget_show (GTK_WIDGET (state->border.canvas));
1317 gtk_widget_set_size_request (GTK_WIDGET (state->border.canvas),
1318 150, 100);
1319 go_gtk_widget_replace (go_gtk_builder_get_widget (state->gui, "border_sample_placeholder"),
1320 GTK_WIDGET (state->border.canvas));
1321 group = GOC_GROUP (goc_canvas_get_root (state->border.canvas));
1323 g_signal_connect (G_OBJECT (state->border.canvas),
1324 "button-press-event",
1325 G_CALLBACK (border_event), state);
1327 state->border.back = goc_item_new (group,
1328 GOC_TYPE_RECTANGLE,
1329 "x", L-10., "y", T-10.,
1330 "width", R-L+20., "height", B-T+20.,
1331 NULL);
1332 style = go_styled_object_get_style (GO_STYLED_OBJECT (state->border.back));
1333 style->line.dash_type = GO_LINE_NONE;
1335 /* Draw the corners */
1336 points = goc_points_new (3);
1338 for (i = 0; i < 12 ; ++i) {
1339 if (i >= 8) {
1340 if (!(state->selection_mask & 0xa))
1341 continue;
1342 } else if (i >= 4) {
1343 if (!(state->selection_mask & 0xc))
1344 continue;
1347 for (j = 3, k = 5 ; --j >= 0 ;) {
1348 points->points[j].y = corners[i][k--] + .5;
1349 points->points[j].x = corners[i][k--] + .5;
1353 style = go_styled_object_get_style (GO_STYLED_OBJECT (
1354 goc_item_new (group,
1355 goc_polyline_get_type (),
1356 "points", points,
1357 NULL)));
1358 style->line.color = GO_COLOR_FROM_RGBA (0xa1, 0xa1, 0xa1, 0xff); /* gray63 */
1359 style->line.width = 0.;
1361 goc_points_unref (points);
1363 for (i = 0; line_info[i].states != 0 ; ++i ) {
1364 if (line_info[i].states & state->selection_mask) {
1365 BorderPicker const *p =
1366 & state->border.edge[line_info[i].location];
1367 state->border.lines[i] =
1368 goc_item_new (group,
1369 gnm_dashed_canvas_line_get_type (),
1370 "x0", line_info[i].points[0],
1371 "y0", line_info[i].points[1],
1372 "x1", line_info[i].points[2],
1373 "y1", line_info[i].points[3],
1374 NULL);
1375 style = go_styled_object_get_style (GO_STYLED_OBJECT (state->border.lines[i]));
1376 style->line.color = p->rgba;
1377 gnm_dashed_canvas_line_set_dash_index (
1378 GNM_DASHED_CANVAS_LINE (state->border.lines[i]),
1379 p->pattern_index);
1380 } else
1381 state->border.lines[i] = NULL;
1385 for (i = 0; i < GNM_STYLE_BORDER_EDGE_MAX; ++i) {
1386 BorderPicker const *border = &state->border.edge[i];
1387 int j;
1389 for (j = 0; line_info[j].states != 0 ; ++j) {
1390 if ((int)line_info[j].location == i &&
1391 state->border.lines[j] != NULL)
1392 goc_item_set_visible (state->border.lines[j],
1393 border->is_selected);
1397 fmt_dialog_changed (state);
1400 static void
1401 cb_border_preset_clicked (GtkButton *btn, FormatState *state)
1403 gboolean target_state;
1404 GnmStyleBorderLocation i, last;
1406 if (state->border.preset[BORDER_PRESET_NONE] == btn) {
1407 i = GNM_STYLE_BORDER_TOP;
1408 last = GNM_STYLE_BORDER_VERT;
1409 target_state = FALSE;
1410 } else if (state->border.preset[BORDER_PRESET_OUTLINE] == btn) {
1411 i = GNM_STYLE_BORDER_TOP;
1412 last = GNM_STYLE_BORDER_RIGHT;
1413 target_state = TRUE;
1414 } else if (state->border.preset[BORDER_PRESET_INSIDE] == btn) {
1415 i = GNM_STYLE_BORDER_HORIZ;
1416 last = GNM_STYLE_BORDER_VERT;
1417 target_state = TRUE;
1418 } else {
1419 g_warning ("Unknown border preset button");
1420 return;
1423 /* If we are turning things on, TOGGLE the states to
1424 * capture the current pattern and color */
1425 for (; i <= last; ++i) {
1426 gtk_toggle_button_set_active (
1427 state->border.edge[i].button,
1428 FALSE);
1430 if (target_state)
1431 gtk_toggle_button_set_active (
1432 state->border.edge[i].button,
1433 TRUE);
1434 else if (gtk_toggle_button_get_active (
1435 state->border.edge[i].button))
1436 /* Turn off damn it !
1437 * we really want things off not just to pick up
1438 * the new colours.
1440 gtk_toggle_button_set_active (
1441 state->border.edge[i].button,
1442 FALSE);
1447 * Callback routine to update the border preview when a button is clicked
1449 static void
1450 cb_border_toggle (GtkToggleButton *button, BorderPicker *picker)
1452 picker->is_selected = gtk_toggle_button_get_active (button);
1454 /* If the format has changed and we were just toggled off,
1455 * turn ourselves back on.
1457 if (border_format_has_changed (picker->state, picker) &&
1458 !picker->is_selected)
1459 gtk_toggle_button_set_active (button, TRUE);
1460 else
1461 /* Update the preview lines and enable/disable them */
1462 draw_border_preview (picker->state);
1465 static void
1466 cb_border_color (G_GNUC_UNUSED GOComboColor *combo,
1467 GOColor c,
1468 G_GNUC_UNUSED gboolean is_custom,
1469 G_GNUC_UNUSED gboolean by_user,
1470 gboolean is_default, FormatState *state)
1472 state->border.rgba = c;
1473 state->border.is_auto_color = is_default;
1476 #undef L
1477 #undef R
1478 #undef T
1479 #undef B
1480 #undef H
1481 #undef V
1483 typedef struct
1485 GnmStyleBorderLocation t;
1486 GnmBorder const *res;
1487 } check_border_closure_t;
1490 * Initialize the fields of a BorderPicker, connect signals and
1491 * hide if needed.
1493 static void
1494 init_border_button (FormatState *state, GnmStyleBorderLocation const i,
1495 GtkWidget *button,
1496 GnmBorder const * const border)
1498 if (border == NULL) {
1499 state->border.edge[i].rgba = 0;
1500 state->border.edge[i].is_auto_color = TRUE;
1501 state->border.edge[i].pattern_index = GNM_STYLE_BORDER_INCONSISTENT;
1502 state->border.edge[i].is_selected = TRUE;
1503 } else {
1504 GnmColor const *c = border->color;
1505 state->border.edge[i].rgba = c->go_color;
1506 state->border.edge[i].is_auto_color = c->is_auto;
1507 state->border.edge[i].pattern_index = border->line_type;
1508 state->border.edge[i].is_selected = (border->line_type != GNM_STYLE_BORDER_NONE);
1511 state->border.edge[i].state = state;
1512 state->border.edge[i].index = i;
1513 state->border.edge[i].button = GTK_TOGGLE_BUTTON (button);
1514 state->border.edge[i].is_set = FALSE;
1516 g_return_if_fail (button != NULL);
1518 gtk_toggle_button_set_active (state->border.edge[i].button,
1519 state->border.edge[i].is_selected);
1521 g_signal_connect (G_OBJECT (button),
1522 "toggled",
1523 G_CALLBACK (cb_border_toggle), &state->border.edge[i]);
1525 if ((i == GNM_STYLE_BORDER_HORIZ && !(state->selection_mask & 0xa)) ||
1526 (i == GNM_STYLE_BORDER_VERT && !(state->selection_mask & 0xc)))
1527 gtk_widget_hide (button);
1530 /*****************************************************************************/
1532 static void
1533 cb_protection_locked_toggle (GtkToggleButton *button, FormatState *state)
1535 if (state->enable_edit) {
1536 gnm_style_set_contents_locked (state->result,
1537 gtk_toggle_button_get_active (button));
1538 fmt_dialog_changed (state);
1542 static void
1543 cb_protection_hidden_toggle (GtkToggleButton *button, FormatState *state)
1545 if (state->enable_edit) {
1546 gnm_style_set_contents_hidden (state->result,
1547 gtk_toggle_button_get_active (button));
1548 fmt_dialog_changed (state);
1552 static void
1553 cb_protection_sheet_protected_toggle (GtkToggleButton *button, FormatState *state)
1555 if (state->enable_edit) {
1556 state->protection.sheet_protected_value =
1557 gtk_toggle_button_get_active (button);
1558 state->protection.sheet_protected_changed = TRUE;
1559 fmt_dialog_changed (state);
1563 static void
1564 fmt_dialog_init_protection_page (FormatState *state)
1566 GtkWidget *w;
1567 gboolean flag = FALSE;
1569 flag = (state->conflicts & (1 << MSTYLE_CONTENTS_LOCKED))
1570 ? FALSE : gnm_style_get_contents_locked (state->style);
1571 w = go_gtk_builder_get_widget (state->gui, "protection_locked");
1572 state->protection.locked = GTK_CHECK_BUTTON (w);
1573 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), flag);
1574 g_signal_connect (G_OBJECT (w),
1575 "toggled",
1576 G_CALLBACK (cb_protection_locked_toggle), state);
1578 flag = (state->conflicts & (1 << MSTYLE_CONTENTS_HIDDEN))
1579 ? FALSE : gnm_style_get_contents_hidden (state->style);
1580 w = go_gtk_builder_get_widget (state->gui, "protection_hidden");
1581 state->protection.hidden = GTK_CHECK_BUTTON (w);
1582 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), flag);
1583 g_signal_connect (G_OBJECT (w),
1584 "toggled",
1585 G_CALLBACK (cb_protection_hidden_toggle), state);
1587 state->protection.sheet_protected_changed = FALSE;
1588 flag = state->sheet->is_protected;
1589 w = go_gtk_builder_get_widget (state->gui, "protection_sheet_protected");
1590 state->protection.sheet_protected = GTK_CHECK_BUTTON (w);
1591 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), flag);
1592 g_signal_connect (G_OBJECT (w),
1593 "toggled",
1594 G_CALLBACK (cb_protection_sheet_protected_toggle), state);
1597 /*****************************************************************************/
1599 static GnmExprTop const *
1600 validation_entry_to_expr (Sheet *sheet, GnmExprEntry *gee)
1602 GnmParsePos pp;
1603 parse_pos_init_sheet (&pp, sheet);
1604 return gnm_expr_entry_parse (gee, &pp, NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
1607 static void
1608 validation_rebuild_validation (FormatState *state)
1610 ValidationType type;
1612 if (!state->enable_edit)
1613 return;
1615 state->validation.changed = FALSE;
1616 type = gtk_combo_box_get_active (
1617 state->validation.constraint_type);
1619 if (type != GNM_VALIDATION_TYPE_ANY) {
1620 ValidationStyle style = gtk_combo_box_get_active (state->validation.error.action);
1621 ValidationOp op = gtk_combo_box_get_active (state->validation.op);
1622 char *title = gtk_editable_get_chars (GTK_EDITABLE (state->validation.error.title), 0, -1);
1623 char *msg = gnm_textview_get_text (state->validation.error.msg);
1624 GnmExprTop const *texpr0 =
1625 validation_entry_to_expr (state->sheet,
1626 state->validation.expr0.entry);
1627 GnmExprTop const *texpr1 = NULL;
1629 if (texpr0 != NULL) {
1630 if (type == GNM_VALIDATION_TYPE_CUSTOM || type == GNM_VALIDATION_TYPE_IN_LIST) {
1631 state->validation.valid = 1;
1632 op = GNM_VALIDATION_OP_NONE;
1633 } else if (op == GNM_VALIDATION_OP_BETWEEN || op == GNM_VALIDATION_OP_NOT_BETWEEN) {
1634 texpr1 = validation_entry_to_expr (state->sheet,
1635 state->validation.expr1.entry);
1636 if (texpr1 != NULL)
1637 state->validation.valid = 2;
1638 else {
1639 state->validation.valid = -2;
1640 gnm_expr_top_unref (texpr0);
1642 } else
1643 state->validation.valid = 1;
1644 } else
1645 state->validation.valid = -1;
1647 if (state->validation.valid > 0) {
1648 gboolean allow_blank = gtk_toggle_button_get_active (state->validation.allow_blank);
1649 gboolean use_dropdown = gtk_toggle_button_get_active (state->validation.use_dropdown);
1650 gnm_style_set_validation
1651 (state->result,
1652 gnm_validation_new
1653 (style, type, op,
1654 state->sheet,
1655 title, msg,
1656 texpr0,
1657 texpr1,
1658 allow_blank,
1659 use_dropdown));
1662 g_free (msg);
1663 g_free (title);
1664 } else
1665 gnm_style_set_validation (state->result, NULL);
1666 fmt_dialog_changed (state);
1669 static struct {
1670 const char *text;
1671 const char *icon_name;
1672 } validation_error_actions[] = {
1674 N_("None (silently accept invalid input)"),
1675 NULL
1678 N_("Stop (never allow invalid input)"),
1679 "dialog-error"
1682 N_("Warning (accept/discard invalid input)"),
1683 "dialog-warning"
1686 N_("Information (allow invalid input)"),
1687 "dialog-information"
1691 static void
1692 cb_validation_error_action_changed (G_GNUC_UNUSED GtkMenuShell *ignored,
1693 FormatState *state)
1695 int index = gtk_combo_box_get_active (state->validation.error.action);
1696 gboolean const flag = (index > 0) &&
1697 (gtk_combo_box_get_active (state->validation.constraint_type) > 0);
1699 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.error.title_label), flag);
1700 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.error.msg_label), flag);
1701 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.error.title), flag);
1702 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.error.msg), flag);
1704 if (flag) {
1705 char const *icon_name = validation_error_actions[index].icon_name;
1706 gtk_image_set_from_icon_name (state->validation.error.image,
1707 icon_name, GTK_ICON_SIZE_DIALOG);
1708 gtk_widget_show (GTK_WIDGET (state->validation.error.image));
1709 } else
1710 gtk_widget_hide (GTK_WIDGET (state->validation.error.image));
1712 validation_rebuild_validation (state);
1715 static void
1716 cb_validation_sensitivity (G_GNUC_UNUSED GtkMenuShell *ignored,
1717 FormatState *state)
1719 gboolean has_operators = FALSE;
1720 char const *msg0 = "";
1721 char const *msg1 = "";
1722 ValidationType const type = gtk_combo_box_get_active (
1723 state->validation.constraint_type);
1725 switch (type) {
1726 case GNM_VALIDATION_TYPE_IN_LIST: msg0 = _("Source"); break;
1727 case GNM_VALIDATION_TYPE_CUSTOM: msg0 = _("Criteria"); break;
1729 case GNM_VALIDATION_TYPE_AS_INT:
1730 case GNM_VALIDATION_TYPE_AS_NUMBER:
1731 case GNM_VALIDATION_TYPE_AS_DATE:
1732 case GNM_VALIDATION_TYPE_AS_TIME:
1733 case GNM_VALIDATION_TYPE_TEXT_LENGTH: {
1734 ValidationOp const op = gtk_combo_box_get_active (
1735 state->validation.op);
1736 has_operators = TRUE;
1737 switch (op) {
1738 case GNM_VALIDATION_OP_NONE:
1739 break;
1740 case GNM_VALIDATION_OP_BETWEEN:
1741 case GNM_VALIDATION_OP_NOT_BETWEEN:
1742 msg0 = _("Min:");
1743 msg1 = _("Max:");
1744 break;
1745 case GNM_VALIDATION_OP_EQUAL:
1746 case GNM_VALIDATION_OP_NOT_EQUAL:
1747 msg0 = _("Value:");
1748 break;
1749 case GNM_VALIDATION_OP_GT:
1750 case GNM_VALIDATION_OP_GTE:
1751 msg0 =_("Min:");
1752 break;
1753 case GNM_VALIDATION_OP_LT:
1754 case GNM_VALIDATION_OP_LTE:
1755 msg0 = _("Max:");
1756 break;
1757 default:
1758 g_warning ("Unknown operator index %d", (int)op);
1760 break;
1762 default:
1763 break;
1766 gtk_label_set_text (state->validation.expr0.name, msg0);
1767 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.expr0.name), *msg0 != '\0');
1768 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.expr0.entry), *msg0 != '\0');
1770 gtk_label_set_text (state->validation.expr1.name, msg1);
1771 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.expr1.name), *msg1 != '\0');
1772 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.expr1.entry), *msg1 != '\0');
1774 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.op),
1775 has_operators);
1776 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.operator_label),
1777 has_operators);
1779 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.error.action_label),
1780 type != GNM_VALIDATION_TYPE_ANY);
1781 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.error.action),
1782 type != GNM_VALIDATION_TYPE_ANY);
1783 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.allow_blank),
1784 type != GNM_VALIDATION_TYPE_ANY);
1785 gtk_widget_set_sensitive (GTK_WIDGET (state->validation.use_dropdown),
1786 type == GNM_VALIDATION_TYPE_IN_LIST);
1788 validation_rebuild_validation (state);
1791 static void
1792 cb_validation_changed (G_GNUC_UNUSED GtkEntry *ignored,
1793 FormatState *state)
1795 if (state->enable_edit)
1796 state->validation.changed = TRUE;
1799 static void
1800 fmt_dialog_init_validation_expr_entry (FormatState *state, ExprEntry *entry,
1801 char const *name, int i)
1803 entry->name = GTK_LABEL (go_gtk_builder_get_widget (state->gui, name));
1804 entry->entry = gnm_expr_entry_new (state->wbcg, TRUE);
1805 gtk_grid_attach (state->validation.criteria_grid,
1806 GTK_WIDGET (entry->entry), 1, 3+i, 3, 1);
1807 gtk_widget_show (GTK_WIDGET (entry->entry));
1808 gnm_editable_enters (
1809 GTK_WINDOW (state->dialog),
1810 GTK_WIDGET (entry->entry));
1811 gnm_expr_entry_set_flags (entry->entry, GNM_EE_FORCE_ABS_REF | GNM_EE_SHEET_OPTIONAL, GNM_EE_MASK);
1812 g_signal_connect (G_OBJECT (entry->entry),
1813 "changed",
1814 G_CALLBACK (cb_validation_changed), state);
1817 static void
1818 cb_validation_rebuild (G_GNUC_UNUSED void *ignored,
1819 FormatState *state)
1821 validation_rebuild_validation (state);
1824 static void
1825 build_validation_error_combo (FormatState *state, GtkComboBox *box)
1827 GtkListStore *store;
1828 GtkCellRenderer *renderer;
1829 unsigned ui;
1831 store = gtk_list_store_new (2, GDK_TYPE_PIXBUF, G_TYPE_STRING);
1832 gtk_combo_box_set_model (box, GTK_TREE_MODEL (store));
1834 for (ui = 0; ui < G_N_ELEMENTS (validation_error_actions); ui++) {
1835 const char *icon_name = validation_error_actions[ui].icon_name;
1836 GdkPixbuf *pixbuf = icon_name
1837 ? go_gtk_widget_render_icon_pixbuf (GTK_WIDGET (wbcg_toplevel (state->wbcg)),
1838 icon_name, GTK_ICON_SIZE_MENU)
1839 : NULL;
1840 GtkTreeIter iter;
1842 gtk_list_store_append (store, &iter);
1843 gtk_list_store_set (store, &iter,
1844 0, pixbuf,
1845 1, _(validation_error_actions[ui].text),
1846 -1);
1847 if (pixbuf)
1848 g_object_unref (pixbuf);
1851 renderer = gtk_cell_renderer_pixbuf_new ();
1852 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (box),
1853 renderer,
1854 FALSE);
1855 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (box), renderer,
1856 "pixbuf", 0,
1857 NULL);
1859 renderer = gtk_cell_renderer_text_new ();
1860 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (box),
1861 renderer,
1862 TRUE);
1863 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (box), renderer,
1864 "text", 1,
1865 NULL);
1867 g_object_unref (store);
1870 static void
1871 fmt_dialog_init_validation_page (FormatState *state)
1873 GnmValidation const *v = NULL;
1874 g_return_if_fail (state != NULL);
1876 /* Setup widgets */
1877 state->validation.changed = FALSE;
1878 state->validation.valid = 1;
1879 state->validation.criteria_grid = GTK_GRID (go_gtk_builder_get_widget (state->gui, "validation-grid"));
1880 state->validation.constraint_type = GTK_COMBO_BOX (go_gtk_builder_get_widget (state->gui, "validation_constraint_type"));
1881 gtk_combo_box_set_active (state->validation.constraint_type, 0);
1882 state->validation.operator_label = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "validation_operator_label"));
1883 state->validation.op = GTK_COMBO_BOX (go_gtk_builder_get_widget (state->gui, "validation_operator"));
1884 gtk_combo_box_set_active (state->validation.op, 0);
1885 state->validation.allow_blank = GTK_TOGGLE_BUTTON(go_gtk_builder_get_widget (state->gui, "validation_ignore_blank"));
1886 state->validation.use_dropdown = GTK_TOGGLE_BUTTON(go_gtk_builder_get_widget (state->gui, "validation_in_dropdown"));
1887 state->validation.error.action_label = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "validation_error_action_label"));
1888 state->validation.error.title_label = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "validation_error_title_label"));
1889 state->validation.error.msg_label = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "validation_error_msg_label"));
1890 state->validation.error.action = GTK_COMBO_BOX (go_gtk_builder_get_widget (state->gui, "validation_error_action"));
1891 build_validation_error_combo (state, state->validation.error.action);
1892 gtk_combo_box_set_active (state->validation.error.action, 0);
1893 state->validation.error.title = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "validation_error_title"));
1894 state->validation.error.msg = GTK_TEXT_VIEW (go_gtk_builder_get_widget (state->gui, "validation_error_msg"));
1895 state->validation.error.image = GTK_IMAGE (go_gtk_builder_get_widget (state->gui, "validation_error_image"));
1897 gnm_editable_enters (
1898 GTK_WINDOW (state->dialog),
1899 GTK_WIDGET (state->validation.error.title));
1901 g_signal_connect (state->validation.constraint_type,
1902 "changed",
1903 G_CALLBACK (cb_validation_sensitivity), state);
1904 g_signal_connect (state->validation.op,
1905 "changed",
1906 G_CALLBACK (cb_validation_sensitivity), state);
1907 g_signal_connect (state->validation.error.action,
1908 "changed",
1909 G_CALLBACK (cb_validation_error_action_changed), state);
1911 fmt_dialog_init_validation_expr_entry (state, &state->validation.expr0, "validation_expr0_name", 0);
1912 fmt_dialog_init_validation_expr_entry (state, &state->validation.expr1, "validation_expr1_name", 1);
1914 g_signal_connect (G_OBJECT (state->validation.allow_blank),
1915 "toggled",
1916 G_CALLBACK (cb_validation_rebuild), state);
1917 g_signal_connect (G_OBJECT (state->validation.use_dropdown),
1918 "toggled",
1919 G_CALLBACK (cb_validation_rebuild), state);
1920 g_signal_connect (G_OBJECT (state->validation.error.title),
1921 "changed",
1922 G_CALLBACK (cb_validation_rebuild), state);
1923 g_signal_connect (G_OBJECT (gtk_text_view_get_buffer (state->validation.error.msg)),
1924 "changed",
1925 G_CALLBACK (cb_validation_rebuild), state);
1927 /* Initialize */
1928 if (0 == (state->conflicts & (1 << MSTYLE_VALIDATION)))
1929 v = gnm_style_get_validation (state->style);
1930 if (v != NULL) {
1931 GnmParsePos pp;
1933 gtk_combo_box_set_active (state->validation.error.action, v->style);
1934 gtk_combo_box_set_active (state->validation.constraint_type, v->type);
1935 gtk_combo_box_set_active (state->validation.op, v->op);
1937 gtk_entry_set_text (GTK_ENTRY (state->validation.error.title),
1938 (v->title != NULL) ? v->title->str : "");
1939 if (v->msg != NULL)
1940 gnm_textview_set_text (GTK_TEXT_VIEW (state->validation.error.msg),
1941 v->msg->str);
1942 gtk_toggle_button_set_active (state->validation.allow_blank, v->allow_blank);
1943 gtk_toggle_button_set_active (state->validation.use_dropdown, v->use_dropdown);
1945 parse_pos_init (&pp, state->sheet->workbook, state->sheet,
1946 state->sv->edit_pos.col, state->sv->edit_pos.row);
1947 gnm_expr_entry_load_from_expr (state->validation.expr0.entry,
1948 v->deps[0].texpr, &pp);
1949 gnm_expr_entry_load_from_expr (state->validation.expr1.entry,
1950 v->deps[1].texpr, &pp);
1953 cb_validation_sensitivity (NULL, state);
1954 cb_validation_error_action_changed (NULL, state);
1957 /*****************************************************************************/
1959 static void
1960 input_msg_rebuild_input_msg (FormatState *state)
1962 GnmInputMsg *im;
1963 char *msg = gnm_textview_get_text (state->input_msg.msg);
1964 char const *title = gtk_entry_get_text (state->input_msg.title);
1966 im = gnm_input_msg_new (msg, title);
1967 g_free (msg);
1968 gnm_style_set_input_msg (state->result, im);
1969 fmt_dialog_changed (state);
1972 static void
1973 cb_input_msg_rebuild (G_GNUC_UNUSED void *ignored,
1974 FormatState *state)
1976 input_msg_rebuild_input_msg (state);
1979 static void
1980 cb_input_msg_flag_toggled (GtkToggleButton *button, FormatState *state)
1982 gboolean flag = gtk_toggle_button_get_active (button);
1984 gtk_widget_set_sensitive (GTK_WIDGET (state->input_msg.title_label), flag);
1985 gtk_widget_set_sensitive (GTK_WIDGET (state->input_msg.msg_label), flag);
1986 gtk_widget_set_sensitive (GTK_WIDGET (state->input_msg.title), flag);
1987 gtk_widget_set_sensitive (GTK_WIDGET (state->input_msg.msg), flag);
1989 if (state->enable_edit) {
1990 if (flag)
1991 input_msg_rebuild_input_msg (state);
1992 else
1993 gnm_style_set_input_msg (state->result, NULL);
1994 fmt_dialog_changed (state);
1998 static void
1999 fmt_dialog_init_input_msg_page (FormatState *state)
2001 GnmInputMsg const *im = NULL;
2003 g_return_if_fail (state != NULL);
2005 /* Setup widgets */
2006 state->input_msg.flag = GTK_TOGGLE_BUTTON (go_gtk_builder_get_widget (state->gui, "input_msg_flag"));
2007 state->input_msg.title_label = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "input_msg_title_label"));
2008 state->input_msg.msg_label = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "input_msg_msg_label"));
2009 state->input_msg.title = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "input_msg_title"));
2010 state->input_msg.msg = GTK_TEXT_VIEW (go_gtk_builder_get_widget (state->gui, "input_msg_msg"));
2012 /* Initialize */
2013 if (0 == (state->conflicts & (1 << MSTYLE_INPUT_MSG)))
2014 im = gnm_style_get_input_msg (state->style);
2015 if (im) {
2016 gtk_entry_set_text (state->input_msg.title,
2017 gnm_input_msg_get_title (im));
2018 gnm_textview_set_text (state->input_msg.msg,
2019 gnm_input_msg_get_msg (im));
2021 gtk_toggle_button_set_active (state->input_msg.flag, im != NULL);
2023 gnm_editable_enters (
2024 GTK_WINDOW (state->dialog),
2025 GTK_WIDGET (state->input_msg.title));
2027 g_signal_connect (G_OBJECT (state->input_msg.flag),
2028 "toggled",
2029 G_CALLBACK (cb_input_msg_flag_toggled), state);
2030 g_signal_connect (G_OBJECT (state->input_msg.title),
2031 "changed",
2032 G_CALLBACK (cb_input_msg_rebuild), state);
2033 g_signal_connect (G_OBJECT (gtk_text_view_get_buffer (state->input_msg.msg)),
2034 "changed",
2035 G_CALLBACK (cb_input_msg_rebuild), state);
2037 /* Initialize */
2038 cb_input_msg_flag_toggled (state->input_msg.flag, state);
2041 /*****************************************************************************/
2043 /* button handlers */
2044 static void
2045 cb_fmt_dialog_dialog_buttons (GtkWidget *btn, FormatState *state)
2047 #if 0
2048 static GnmStyleBorderLocation const bmap_ltr[] = {
2049 GNM_STYLE_BORDER_TOP, GNM_STYLE_BORDER_BOTTOM,
2050 GNM_STYLE_BORDER_LEFT, GNM_STYLE_BORDER_RIGHT,
2051 GNM_STYLE_BORDER_REV_DIAG, GNM_STYLE_BORDER_DIAG,
2052 GNM_STYLE_BORDER_HORIZ, GNM_STYLE_BORDER_VERT
2054 static GnmStyleBorderLocation const bmap_rtl[] = {
2055 GNM_STYLE_BORDER_TOP, GNM_STYLE_BORDER_BOTTOM,
2056 /* reverse */
2057 GNM_STYLE_BORDER_RIGHT, GNM_STYLE_BORDER_LEFT,
2058 /* reverse */
2059 GNM_STYLE_BORDER_DIAG, GNM_STYLE_BORDER_REV_DIAG,
2060 GNM_STYLE_BORDER_HORIZ, GNM_STYLE_BORDER_VERT
2062 GnmStyleBorderLocation const *bmap = bmap_ltr;
2064 if (NULL != state->sheet && state->sheet->text_is_rtl)
2065 bmap = bmap_rtl;
2066 #endif
2068 if (btn == state->apply_button || btn == state->ok_button) {
2069 int i;
2071 /* We need to make sure the right sheet is active */
2072 /* since we are acting on the current selection */
2073 /* validation may have switched sheets. */
2075 wb_control_sheet_focus (GNM_WBC (state->wbcg),
2076 state->sheet);
2078 if (state->validation.changed)
2079 validation_rebuild_validation (state);
2081 if (state->validation.valid < 0) {
2082 if (go_gtk_query_yes_no (
2083 GTK_WINDOW (state->dialog),
2084 FALSE,
2085 _ ("The validation criteria are unusable. Disable validation?")))
2087 gtk_combo_box_set_active (state->validation.constraint_type, 0);
2088 cb_validation_sensitivity (NULL, state);
2089 } else {
2090 gtk_notebook_set_current_page (state->notebook, FD_VALIDATION);
2092 if (state->validation.valid == -1)
2093 gnm_expr_entry_grab_focus (state->validation.expr0.entry, FALSE);
2094 else
2095 gnm_expr_entry_grab_focus (state->validation.expr1.entry, FALSE);
2096 return;
2100 if (state->protection.sheet_protected_changed) {
2101 state->sheet->is_protected = state->protection.sheet_protected_value;
2102 state->protection.sheet_protected_changed = FALSE;
2106 if (state->style_selector.is_selector) {
2107 GnmStyle *style = gnm_style_dup (state->style);
2108 for (i = GNM_STYLE_BORDER_TOP; i <= GNM_STYLE_BORDER_DIAG; i++) {
2109 GnmBorder *b = border_get_mstyle (state, i);
2110 if (b)
2111 gnm_style_set_border
2112 (state->result,
2113 MSTYLE_BORDER_TOP +
2114 (int)(i - GNM_STYLE_BORDER_TOP),
2117 gnm_style_merge (style, state->result);
2118 dialog_cell_format_style_added
2119 (state->style_selector.closure,
2120 style);
2121 gnm_style_unref (state->result);
2122 } else {
2123 GnmBorder *borders[GNM_STYLE_BORDER_EDGE_MAX];
2124 for (i = GNM_STYLE_BORDER_TOP; i < GNM_STYLE_BORDER_EDGE_MAX; i++)
2125 borders[i] = border_get_mstyle (state, i);
2126 cmd_selection_format (GNM_WBC (state->wbcg),
2127 state->result, borders, NULL);
2129 /* state->result got absorbed. */
2130 /* Get a fresh style to accumulate results in */
2131 state->result = gnm_style_new ();
2132 sheet_update (state->sheet);
2135 gtk_widget_set_sensitive (state->apply_button, FALSE);
2138 if (btn != state->apply_button)
2139 gtk_widget_destroy (GTK_WIDGET (state->dialog));
2142 /* Handler for destroy */
2143 static void
2144 cb_fmt_dialog_dialog_destroy (FormatState *state)
2146 gnm_style_unref (state->back.style);
2147 gnm_style_unref (state->style);
2148 gnm_style_unref (state->result);
2149 g_object_unref (state->gui);
2150 g_free (state);
2153 /* Handler for expr-entry's focus.
2155 * NOTE: This will only become useful once the
2156 * cell format dialog is made non-modal
2158 static void
2159 cb_fmt_dialog_set_focus (G_GNUC_UNUSED GtkWidget *window,
2160 G_GNUC_UNUSED GtkWidget *focus_widget,
2161 FormatState *state)
2163 if (state->validation.changed)
2164 validation_rebuild_validation (state);
2167 static void
2168 cb_dialog_destroy (GtkDialog *dialog)
2170 g_object_set_data (G_OBJECT (dialog), "state", NULL);
2174 /* Set initial focus */
2175 static void
2176 set_initial_focus (FormatState *s)
2178 GtkWidget *focus_widget = NULL, *pagew;
2179 gchar const *name;
2181 pagew = gtk_notebook_get_nth_page (s->notebook, fmt_dialog_page);
2182 name = gtk_widget_get_name (pagew);
2184 if (strcmp (name, "number_box") == 0) {
2185 go_format_sel_set_focus (GO_FORMAT_SEL (s->format_sel));
2186 return;
2187 } else if (strcmp (name, "alignment_box") == 0)
2188 focus_widget = go_gtk_builder_get_widget (s->gui, "halign_left");
2189 else if (strcmp (name, "font_box") == 0)
2190 focus_widget = GTK_WIDGET (s->font.selector);
2191 else if (strcmp (name, "border_box") == 0)
2192 focus_widget = go_gtk_builder_get_widget (s->gui, "gnumeric-format-border-outline");
2193 else if (strcmp (name, "background_box") == 0)
2194 focus_widget = go_gtk_builder_get_widget (s->gui, "back_color_auto");
2195 else if (strcmp (name, "protection_box") == 0)
2196 focus_widget = GTK_WIDGET (s->protection.locked);
2197 else
2198 focus_widget = NULL;
2200 if (focus_widget &&
2201 gtk_widget_get_can_focus (focus_widget) &&
2202 gtk_widget_is_sensitive (focus_widget))
2203 gtk_widget_grab_focus (focus_widget);
2206 static void
2207 fmt_dialog_impl (FormatState *state, FormatDialogPosition_t pageno, gint pages)
2209 static struct {
2210 char const *const name;
2211 GnmStyleBorderType const pattern;
2212 } const line_pattern_buttons[] = {
2213 { "line_pattern_none", GNM_STYLE_BORDER_NONE },
2214 { "line_pattern_medium_dash_dot_dot", GNM_STYLE_BORDER_MEDIUM_DASH_DOT_DOT },
2216 { "line_pattern_hair", GNM_STYLE_BORDER_HAIR },
2217 { "line_pattern_slant", GNM_STYLE_BORDER_SLANTED_DASH_DOT },
2219 { "line_pattern_dotted", GNM_STYLE_BORDER_DOTTED },
2220 { "line_pattern_medium_dash_dot", GNM_STYLE_BORDER_MEDIUM_DASH_DOT },
2222 { "line_pattern_dash_dot_dot", GNM_STYLE_BORDER_DASH_DOT_DOT },
2223 { "line_pattern_medium_dash", GNM_STYLE_BORDER_MEDIUM_DASH },
2225 { "line_pattern_dash_dot", GNM_STYLE_BORDER_DASH_DOT },
2226 { "line_pattern_medium", GNM_STYLE_BORDER_MEDIUM },
2228 { "line_pattern_dashed", GNM_STYLE_BORDER_DASHED },
2229 { "line_pattern_thick", GNM_STYLE_BORDER_THICK },
2231 { "line_pattern_thin", GNM_STYLE_BORDER_THIN },
2232 { "line_pattern_double", GNM_STYLE_BORDER_DOUBLE },
2234 { NULL, 0}
2236 static char const *const pattern_buttons[] = {
2237 "gnumeric-pattern-solid", "gnumeric-pattern-75grey", "gnumeric-pattern-50grey",
2238 "gnumeric-pattern-25grey", "gnumeric-pattern-125grey", "gnumeric-pattern-625grey",
2240 "gnumeric-pattern-horiz",
2241 "gnumeric-pattern-vert",
2242 "gnumeric-pattern-diag",
2243 "gnumeric-pattern-rev-diag",
2244 "gnumeric-pattern-diag-cross",
2245 "gnumeric-pattern-thick-diag-cross",
2247 "gnumeric-pattern-thin-horiz",
2248 "gnumeric-pattern-thin-vert",
2249 "gnumeric-pattern-thin-rev-diag",
2250 "gnumeric-pattern-thin-diag",
2251 "gnumeric-pattern-thin-horiz-cross",
2252 "gnumeric-pattern-thin-diag-cross",
2254 "gnumeric-pattern-small-circle",
2255 "gnumeric-pattern-semi-circle",
2256 "gnumeric-pattern-thatch",
2257 "gnumeric-pattern-large-circles",
2258 "gnumeric-pattern-bricks",
2259 "gnumeric-pattern-foreground-solid",
2261 NULL
2264 /* The order corresponds to the BorderLocation enum */
2265 static char const *const border_buttons[] = {
2266 "gnumeric-format-border-top", "gnumeric-format-border-bottom",
2267 "gnumeric-format-border-left", "gnumeric-format-border-right",
2268 "gnumeric-format-border-rev-diag", "gnumeric-format-border-diag",
2269 "gnumeric-format-border-inside-horiz", "gnumeric-format-border-inside-vert",
2270 NULL
2273 /* The order corresponds to BorderPresets */
2274 static char const *const border_preset_buttons[] = {
2275 "gnumeric-format-border-no", "gnumeric-format-border-outline", "gnumeric-format-border-inside",
2276 NULL
2279 int page_signal;
2280 int i, selected;
2281 char const *name;
2282 gboolean has_back;
2283 GOColor default_border_color;
2284 int default_border_style = GNM_STYLE_BORDER_THIN;
2285 GtkStyleContext *ctxt;
2286 GdkRGBA bc;
2288 GtkWidget *tmp, *dialog = go_gtk_builder_get_widget (state->gui, "CellFormat");
2289 g_return_if_fail (dialog != NULL);
2291 gtk_window_set_title (GTK_WINDOW (dialog), _("Format Cells"));
2293 /* Initialize */
2294 state->dialog = GTK_DIALOG (dialog);
2295 state->notebook = GTK_NOTEBOOK (go_gtk_builder_get_widget (state->gui, "notebook"));
2297 state->enable_edit = FALSE; /* Enable below */
2299 state->border.canvas = NULL;
2300 state->border.pattern.cur_index = 0;
2302 state->back.canvas = NULL;
2303 state->back.grid = NULL;
2304 state->back.style = gnm_style_new_default ();
2305 state->back.pattern.cur_index = 0;
2307 fmt_dialog_init_format_page (state);
2308 fmt_dialog_init_align_page (state);
2309 fmt_dialog_init_font_page (state);
2310 fmt_dialog_init_background_page (state);
2311 fmt_dialog_init_protection_page (state);
2312 fmt_dialog_init_validation_page (state);
2313 fmt_dialog_init_input_msg_page (state);
2315 ctxt = gtk_widget_get_style_context (GTK_WIDGET (state->dialog));
2316 gtk_style_context_get_border_color (ctxt, GTK_STATE_FLAG_NORMAL, &bc);
2317 default_border_color = GO_COLOR_FROM_GDK_RGBA (bc);
2319 if (pageno == FD_CURRENT)
2320 pageno = fmt_dialog_page;
2321 gtk_notebook_set_current_page (state->notebook, pageno);
2323 page_signal = g_signal_connect (G_OBJECT (state->notebook),
2324 "switch_page",
2325 G_CALLBACK (cb_page_select), NULL);
2326 g_signal_connect (G_OBJECT (state->notebook),
2327 "destroy",
2328 G_CALLBACK (cb_notebook_destroy), GINT_TO_POINTER (page_signal));
2330 /* Setup border line pattern buttons & select the 1st button */
2331 for (i = MSTYLE_BORDER_TOP; i < MSTYLE_BORDER_DIAGONAL; i++) {
2332 GnmBorder const *border = gnm_style_get_border (state->style, i);
2333 if (!gnm_style_border_is_blank (border)) {
2334 default_border_color = border->color->go_color;
2335 default_border_style = border->line_type;
2336 break;
2340 state->border.pattern.draw_preview = NULL;
2341 state->border.pattern.current_pattern = NULL;
2342 state->border.pattern.state = state;
2343 state->border.rgba = default_border_color;
2344 for (i = 0; (name = line_pattern_buttons[i].name) != NULL; ++i)
2345 setup_pattern_button (gtk_widget_get_screen (GTK_WIDGET (state->dialog)),
2346 state->gui, name, &state->border.pattern,
2347 i != 0, /* No image for None */
2348 FALSE,
2349 line_pattern_buttons[i].pattern,
2350 default_border_style, 54);
2352 setup_color_pickers (state, &state->border.color, "border_color_group",
2353 "border_color_placeholder", "border_color_label",
2354 _("Automatic"), _("Border"),
2355 G_CALLBACK (cb_border_color), MSTYLE_BORDER_TOP, FALSE);
2356 setup_color_pickers (state, &state->back.back_color, "back_color_group",
2357 "background_color_placeholder", "back_color_label",
2358 _("Clear Background"), _("Background"),
2359 G_CALLBACK (cb_back_preview_color), MSTYLE_COLOR_BACK, FALSE);
2360 setup_color_pickers (state, &state->back.pattern_color, "pattern_color_group",
2361 "pattern_color_placeholder", "pattern_color_label",
2362 _("Automatic"), _("Pattern"),
2363 G_CALLBACK (cb_pattern_preview_color), MSTYLE_COLOR_PATTERN, FALSE);
2365 /* Setup the border images */
2366 for (i = 0; (name = border_buttons[i]) != NULL; ++i) {
2367 GtkWidget *tmp = go_gtk_builder_get_widget (state->gui, name);
2368 if (tmp != NULL) {
2369 init_border_button (state, i, tmp,
2370 state->borders[i]);
2371 gnm_style_border_unref (state->borders[i]);
2372 state->borders[i] = NULL;
2376 /* Get the current background
2377 * A pattern of 0 is has no background.
2378 * A pattern of 1 is a solid background
2379 * All others have 2 colours and a stipple
2381 has_back = FALSE;
2382 selected = 1;
2383 if (0 == (state->conflicts & (1 << MSTYLE_PATTERN))) {
2384 selected = gnm_style_get_pattern (state->style);
2385 has_back = (selected != 0);
2388 /* Setup pattern buttons & select the current pattern (or the 1st
2389 * if none is selected)
2390 * NOTE: This must be done AFTER the colour has been setup to
2391 * avoid having it erased by initialization.
2393 state->back.pattern.draw_preview = &draw_pattern_selected;
2394 state->back.pattern.current_pattern = NULL;
2395 state->back.pattern.state = state;
2396 for (i = 0; (name = pattern_buttons[i]) != NULL; ++i)
2397 setup_pattern_button (gtk_widget_get_screen (GTK_WIDGET (state->dialog)),
2398 state->gui, name,
2399 &state->back.pattern, TRUE, TRUE,
2400 i+1, /* Pattern #s start at 1 */
2401 selected, 16);
2403 /* If the pattern is 0 indicating no background colour
2404 * Set background to No colour. This will set states correctly.
2406 if (!has_back)
2407 go_combo_color_set_color_to_default (GO_COMBO_COLOR (state->back.back_color.combo));
2409 /* Setup the images in the border presets */
2410 for (i = 0; (name = border_preset_buttons[i]) != NULL; ++i) {
2411 GtkWidget *tmp = go_gtk_builder_get_widget (state->gui, name);
2412 if (tmp != NULL) {
2413 state->border.preset[i] = GTK_BUTTON (tmp);
2414 g_signal_connect (G_OBJECT (tmp),
2415 "clicked",
2416 G_CALLBACK (cb_border_preset_clicked), state);
2417 if (i == BORDER_PRESET_INSIDE && state->selection_mask != 0x8)
2418 gtk_widget_hide (tmp);
2422 draw_border_preview (state);
2424 gnm_init_help_button (
2425 go_gtk_builder_get_widget (state->gui, "helpbutton"),
2426 GNUMERIC_HELP_LINK_CELL_FORMAT);
2428 state->ok_button = go_gtk_builder_get_widget (state->gui, "okbutton");
2429 gtk_widget_set_sensitive (state->ok_button, FALSE);
2430 g_signal_connect (G_OBJECT (state->ok_button),
2431 "clicked",
2432 G_CALLBACK (cb_fmt_dialog_dialog_buttons), state);
2433 state->apply_button = go_gtk_builder_get_widget (state->gui, "applybutton");
2434 gtk_widget_set_sensitive (state->apply_button, FALSE);
2435 g_signal_connect (G_OBJECT (state->apply_button),
2436 "clicked",
2437 G_CALLBACK (cb_fmt_dialog_dialog_buttons), state);
2438 tmp = go_gtk_builder_get_widget (state->gui, "cancelbutton");
2439 g_signal_connect (G_OBJECT (tmp),
2440 "clicked",
2441 G_CALLBACK (cb_fmt_dialog_dialog_buttons), state);
2443 set_initial_focus (state);
2444 gtk_notebook_set_scrollable (state->notebook, TRUE);
2446 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (dialog), state->wbcg,
2447 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);
2449 /* Ok, edit events from now on are real */
2450 state->enable_edit = TRUE;
2452 g_signal_connect (G_OBJECT (dialog),
2453 "set-focus",
2454 G_CALLBACK (cb_fmt_dialog_set_focus), state);
2455 /* We could now make it modeless, and arguably should do so. We must
2456 * then track the selection: styles should be applied to the current
2457 * selection.
2458 * There are some UI issues to discuss before we do this, though. Most
2459 * important:
2460 * - will users be confused?
2461 * And on a different level:
2462 * - should the preselected style in the dialog change when another
2463 * cell is selected? May be, but then we can't first make a style,
2464 * then move around and apply it to different cells.
2467 g_object_set_data_full (G_OBJECT (state->dialog),
2468 "state", state, (GDestroyNotify)cb_fmt_dialog_dialog_destroy);
2469 g_signal_connect (G_OBJECT (dialog), "destroy",
2470 G_CALLBACK (cb_dialog_destroy), NULL);
2472 if (pages > 0)
2473 for (i = 0; i <= FD_LAST; i++) {
2474 GtkWidget *widget = gtk_notebook_get_nth_page
2475 (state->notebook, i);
2476 if (widget != NULL && !((1<<i) & pages))
2477 gtk_widget_hide (widget);
2481 gnm_restore_window_geometry (GTK_WINDOW (state->dialog),
2482 CELL_FORMAT_KEY);
2485 static GnmValue *
2486 cb_check_cell_format (GnmCellIter const *iter, gpointer user)
2488 FormatState *state = user;
2489 GnmValue const *value = iter->cell->value;
2490 GOFormat const *common = gnm_style_get_format (state->style);
2491 GOFormat const *fmt = value ? VALUE_FMT (value) : NULL;
2493 if (!fmt ||
2494 go_format_is_markup (fmt) ||
2495 go_format_eq (common, fmt))
2496 return NULL;
2498 if (go_format_is_general (common)) {
2499 gnm_style_set_format (state->style, fmt);
2500 return NULL;
2501 } else {
2502 state->conflicts |= MSTYLE_FORMAT;
2503 return VALUE_TERMINATE;
2507 static gboolean
2508 fmt_dialog_selection_type (SheetView *sv,
2509 GnmRange const *range,
2510 gpointer user_data)
2512 FormatState *state = user_data;
2513 GSList *merged = gnm_sheet_merge_get_overlap (sv->sheet, range);
2514 GnmRange r = *range;
2515 gboolean allow_multi =
2516 merged == NULL ||
2517 merged->next != NULL ||
2518 !range_equal ((GnmRange *)merged->data, range);
2519 g_slist_free (merged);
2521 /* allow_multi == FALSE && !is_singleton (range) means that we are in
2522 * an merge cell, use only the top left */
2523 if (r.start.col != r.end.col)
2525 if (allow_multi)
2526 state->selection_mask |= 2;
2527 else
2528 r.end.col = r.start.col;
2530 if (range->start.row != range->end.row)
2532 if (allow_multi)
2533 state->selection_mask |= 1;
2534 else
2535 r.end.row = r.start.row;
2538 state->conflicts = sheet_style_find_conflicts (state->sheet, &r,
2539 &(state->style), state->borders);
2541 if ((state->conflicts & MSTYLE_FORMAT) == 0 &&
2542 go_format_is_general (gnm_style_get_format (state->style))) {
2543 sheet_foreach_cell_in_range (state->sheet,
2544 CELL_ITER_IGNORE_BLANK,
2546 cb_check_cell_format,
2547 state);
2550 return TRUE;
2553 static FormatState *
2554 dialog_cell_format_init (WBCGtk *wbcg)
2556 GtkBuilder *gui;
2557 GnmCell *edit_cell;
2558 FormatState *state;
2560 gui = gnm_gtk_builder_load ("res:ui/cell-format.ui", NULL, GO_CMD_CONTEXT (wbcg));
2561 if (gui == NULL)
2562 return NULL;
2564 /* Initialize */
2565 state = g_new (FormatState, 1);
2566 state->wbcg = wbcg;
2567 state->gui = gui;
2568 state->sv = wb_control_cur_sheet_view (GNM_WBC (wbcg));
2569 state->sheet = sv_sheet (state->sv);
2571 edit_cell = sheet_cell_get (state->sheet,
2572 state->sv->edit_pos.col,
2573 state->sv->edit_pos.row);
2575 state->value = (edit_cell != NULL) ? edit_cell->value : NULL;
2576 state->style = NULL;
2577 state->result = gnm_style_new ();
2578 state->selection_mask = 0;
2580 (void) sv_selection_foreach (state->sv,
2581 fmt_dialog_selection_type, state);
2582 state->selection_mask = 1 << state->selection_mask;
2584 return state;
2587 void
2588 dialog_cell_format (WBCGtk *wbcg, FormatDialogPosition_t pageno, gint pages)
2590 FormatState *state;
2592 g_return_if_fail (wbcg != NULL);
2594 state = dialog_cell_format_init (wbcg);
2596 if (state == NULL)
2597 return;
2599 state->style_selector.is_selector = FALSE;
2600 state->style_selector.w = NULL;
2601 state->style_selector.closure = NULL;
2603 if (pages == 0) {
2604 int i;
2605 for (i = FD_NUMBER; i <= FD_PROTECTION; i++)
2606 pages |= (1 << i);
2609 fmt_dialog_impl (state, pageno, pages);
2611 wbc_gtk_attach_guru (state->wbcg, GTK_WIDGET (state->dialog));
2612 go_gtk_nonmodal_dialog (wbcg_toplevel (state->wbcg),
2613 GTK_WINDOW (state->dialog));
2614 gtk_widget_show (GTK_WIDGET (state->dialog));
2618 * TODO
2620 * Borders
2621 * - Add the 'text' elements in the preview
2623 * Wishlist
2624 * - Some undo capabilities in the dialog.
2625 * - How to distinguish between auto & custom colors on extraction from styles.
2629 * dialog_cell_format_select_style:
2631 * Returns: (transfer floating): a #GtkDialog.
2633 GtkDialog *
2634 dialog_cell_format_select_style (WBCGtk *wbcg, gint pages,
2635 GtkWindow *w,
2636 GnmStyle *style, gpointer closure)
2638 FormatState *state;
2640 g_return_val_if_fail (wbcg != NULL, NULL);
2641 state = dialog_cell_format_init (wbcg);
2642 g_return_val_if_fail (state != NULL, NULL);
2644 state->style_selector.is_selector = TRUE;
2645 state->style_selector.w = w;
2646 state->style_selector.closure = closure;
2647 state->selection_mask = 1;
2649 if (style) {
2650 gnm_style_unref (state->style);
2651 state->style = style;
2652 state->conflicts = 0;
2655 fmt_dialog_impl (state, FD_BACKGROUND, pages);
2657 gtk_widget_hide (state->apply_button);
2659 go_gtk_nonmodal_dialog (w, GTK_WINDOW (state->dialog));
2660 gtk_widget_show (GTK_WIDGET (state->dialog));
2662 return state->dialog;