1 
2 /*
3  * sheet-object-widget.c: SheetObject wrappers for simple gtk widgets.
4  *
5  * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
24 #include <gnumeric.h>
25 #include <application.h>
26 #include <sheet-object-widget-impl.h>
27 #include <widgets/gnm-radiobutton.h>
28 #include <gnm-pane.h>
29 #include <gnumeric-simple-canvas.h>
30 #include <gui-util.h>
31 #include <gutils.h>
32 #include <dependent.h>
33 #include <sheet-control-gui.h>
34 #include <sheet-object-impl.h>
35 #include <expr.h>
36 #include <parse-util.h>
37 #include <value.h>
38 #include <ranges.h>
39 #include <selection.h>
40 #include <wbc-gtk.h>
41 #include <workbook.h>
42 #include <sheet.h>
43 #include <cell.h>
44 #include <mathfunc.h>
45 #include <widgets/gnm-expr-entry.h>
46 #include <dialogs/dialogs.h>
47 #include <dialogs/help.h>
48 #include <xml-sax.h>
49 #include <commands.h>
50 #include <gnm-format.h>
51 #include <number-match.h>
52 
53 #include <goffice/goffice.h>
54 
55 #include <gsf/gsf-impl-utils.h>
56 #include <libxml/globals.h>
57 #include <gdk/gdkkeysyms.h>
58 #include <math.h>
59 #include <string.h>
60 
61 #define CXML2C(s) ((char const *)(s))
62 #define CC2XML(s) ((xmlChar const *)(s))
63 
64 static inline gboolean
attr_eq(const xmlChar * a,const char * s)65 attr_eq (const xmlChar *a, const char *s)
66 {
67 	return !strcmp (CXML2C (a), s);
68 }
69 
70 /****************************************************************************/
71 
72 static void
cb_so_get_ref(GnmDependent * dep,G_GNUC_UNUSED SheetObject * so,gpointer user)73 cb_so_get_ref (GnmDependent *dep, G_GNUC_UNUSED SheetObject *so, gpointer user)
74 {
75 	GnmDependent **pdep = user;
76 	*pdep = dep;
77 }
78 
79 static GnmCellRef *
so_get_ref(SheetObject const * so,GnmCellRef * res,gboolean force_sheet)80 so_get_ref (SheetObject const *so, GnmCellRef *res, gboolean force_sheet)
81 {
82 	GnmValue *target;
83 	GnmDependent *dep = NULL;
84 
85 	g_return_val_if_fail (so != NULL, NULL);
86 
87 	/* Let's hope there's just one.  */
88 	sheet_object_foreach_dep ((SheetObject*)so, cb_so_get_ref, &dep);
89 	g_return_val_if_fail (dep, NULL);
90 
91 	if (dep->texpr == NULL)
92 		return NULL;
93 
94 	target = gnm_expr_top_get_range (dep->texpr);
95 	if (target == NULL)
96 		return NULL;
97 
98 	*res = target->v_range.cell.a;
99 	value_release (target);
100 
101 	if (force_sheet && res->sheet == NULL)
102 		res->sheet = sheet_object_get_sheet (so);
103 	return res;
104 }
105 
106 static void
cb_so_clear_sheet(GnmDependent * dep,G_GNUC_UNUSED SheetObject * so,G_GNUC_UNUSED gpointer user)107 cb_so_clear_sheet (GnmDependent *dep, G_GNUC_UNUSED SheetObject *so, G_GNUC_UNUSED gpointer user)
108 {
109 	if (dependent_is_linked (dep))
110 		dependent_unlink (dep);
111 	dep->sheet = NULL;
112 }
113 
114 static gboolean
so_clear_sheet(SheetObject * so)115 so_clear_sheet (SheetObject *so)
116 {
117 	/* Note: This implements sheet_object_clear_sheet.  */
118 	sheet_object_foreach_dep (so, cb_so_clear_sheet, NULL);
119 	return FALSE;
120 }
121 
122 static GocWidget *
get_goc_widget(SheetObjectView * view)123 get_goc_widget (SheetObjectView *view)
124 {
125 	GocItem *item = sheet_object_view_get_item (view);
126 	return item ? GOC_WIDGET (item) : NULL;
127 }
128 
129 static void
so_widget_view_set_bounds(SheetObjectView * sov,double const * coords,gboolean visible)130 so_widget_view_set_bounds (SheetObjectView *sov, double const *coords, gboolean visible)
131 {
132 	GocItem *view = GOC_ITEM (sov);
133 	double scale = goc_canvas_get_pixels_per_unit (view->canvas);
134 	double left = MIN (coords [0], coords [2]) / scale;
135 	double top = MIN (coords [1], coords [3]) / scale;
136 	double width = (fabs (coords [2] - coords [0]) + 1.) / scale;
137 	double height = (fabs (coords [3] - coords [1]) + 1.) / scale;
138 
139 	/* We only need the next check for frames, but it doesn't hurt otherwise. */
140 	if (width < 8.)
141 		width = 8.;
142 
143 	if (visible) {
144 		/* NOTE : far point is EXCLUDED so we add 1 */
145 		goc_widget_set_bounds (get_goc_widget (sov),
146 				       left, top, width, height);
147 		goc_item_show (view);
148 	} else
149 		goc_item_hide (view);
150 }
151 
152 static GdkWindow *
so_widget_view_get_window(GocItem * item)153 so_widget_view_get_window (GocItem *item)
154 {
155 	GocItem *item0 = sheet_object_view_get_item (GNM_SO_VIEW (item));
156 	return goc_item_get_window (item0);
157 }
158 
159 static void
so_widget_view_class_init(SheetObjectViewClass * sov_klass)160 so_widget_view_class_init (SheetObjectViewClass *sov_klass)
161 {
162 	GocItemClass *item_klass = (GocItemClass *) sov_klass;
163 	sov_klass->set_bounds	= so_widget_view_set_bounds;
164 	item_klass->get_window	= so_widget_view_get_window;
165 }
166 
167 static GSF_CLASS (SOWidgetView, so_widget_view,
168 	so_widget_view_class_init, NULL,
169 	GNM_SO_VIEW_TYPE)
170 
171 /****************************************************************************/
172 
173 #define SHEET_OBJECT_CONFIG_KEY "sheet-object-config-dialog"
174 
175 #define GNM_SOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GNM_SOW_TYPE, SheetObjectWidgetClass))
176 #define SOW_CLASS(so) (GNM_SOW_CLASS (G_OBJECT_GET_CLASS(so)))
177 
178 #define SOW_MAKE_TYPE(n1, n2, fn_config, fn_set_sheet, fn_clear_sheet, fn_foreach_dep, \
179 		      fn_copy, fn_write_sax, fn_prep_sax_parser,	\
180 		      fn_get_property, fn_set_property,                 \
181 		      fn_draw_cairo, class_init_code)					\
182 									\
183 static void								\
184 sheet_widget_ ## n1 ## _class_init (GObjectClass *object_class)		\
185 {									\
186 	SheetObjectWidgetClass *sow_class = GNM_SOW_CLASS (object_class); \
187 	SheetObjectClass *so_class = GNM_SO_CLASS (object_class);	\
188 	object_class->finalize		= &sheet_widget_ ## n1 ## _finalize; \
189 	object_class->set_property	= fn_set_property;		\
190 	object_class->get_property	= fn_get_property;		\
191 	so_class->user_config		= fn_config;			\
192         so_class->interactive           = TRUE;				\
193 	so_class->assign_to_sheet	= fn_set_sheet;			\
194 	so_class->remove_from_sheet	= fn_clear_sheet;		\
195 	so_class->foreach_dep		= fn_foreach_dep;		\
196 	so_class->copy			= fn_copy;			\
197 	so_class->write_xml_sax		= fn_write_sax;			\
198 	so_class->prep_sax_parser	= fn_prep_sax_parser;		\
199 	so_class->draw_cairo            = fn_draw_cairo;                            \
200 	sow_class->create_widget	= &sheet_widget_ ## n1 ## _create_widget; \
201         { class_init_code; }						\
202 }									\
203 									\
204 GSF_CLASS (SheetWidget ## n2, sheet_widget_ ## n1,			\
205 	   &sheet_widget_ ## n1 ## _class_init,				\
206 	   &sheet_widget_ ## n1 ## _init,				\
207 	   GNM_SOW_TYPE)
208 
209 typedef struct {
210 	SheetObject so;
211 } SheetObjectWidget;
212 
213 typedef struct {
214 	SheetObjectClass parent_class;
215 	GtkWidget *(*create_widget)(SheetObjectWidget *);
216 } SheetObjectWidgetClass;
217 
218 static GObjectClass *sheet_object_widget_class = NULL;
219 
220 static GtkWidget *
sow_create_widget(SheetObjectWidget * sow)221 sow_create_widget (SheetObjectWidget *sow)
222 {
223 	GtkWidget *w = SOW_CLASS(sow)->create_widget (sow);
224 	GtkStyleContext *context = gtk_widget_get_style_context (w);
225 	gtk_style_context_add_class (context, "sheet-object");
226 	return w;
227 }
228 
229 static void
sheet_widget_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)230 sheet_widget_draw_cairo (SheetObject const *so, cairo_t *cr,
231 			 double width, double height)
232 {
233 	/* This is the default for so widgets without their own method */
234 	/* See bugs #705638 and #705640 */
235 	if (NULL != gdk_screen_get_default ()) {
236 		GtkWidget *win = gtk_offscreen_window_new ();
237 		GtkWidget *w = sow_create_widget (GNM_SOW (so));
238 
239 		gtk_container_add (GTK_CONTAINER (win), w);
240 		gtk_widget_set_size_request (w, width, height);
241 		gtk_widget_show_all (win);
242 		gtk_container_propagate_draw (GTK_CONTAINER (win), w, cr);
243 		gtk_widget_destroy (win);
244 	} else
245 		g_warning (_("Because of GTK bug #705640, a sheet object widget is not being printed."));
246 }
247 
248 static void
sax_write_dep(GsfXMLOut * output,GnmDependent const * dep,char const * id,GnmConventions const * convs)249 sax_write_dep (GsfXMLOut *output, GnmDependent const *dep, char const *id,
250 	       GnmConventions const *convs)
251 {
252 	if (dep->texpr != NULL) {
253 		GnmParsePos pos;
254 		char *val;
255 
256 		parse_pos_init_dep (&pos, dep);
257 		val = gnm_expr_top_as_string (dep->texpr, &pos, convs);
258 		gsf_xml_out_add_cstr (output, id, val);
259 		g_free (val);
260 	}
261 }
262 
263 static gboolean
sax_read_dep(xmlChar const * const * attrs,char const * name,GnmDependent * dep,GsfXMLIn * xin,GnmConventions const * convs)264 sax_read_dep (xmlChar const * const *attrs, char const *name,
265 	      GnmDependent *dep, GsfXMLIn *xin, GnmConventions const *convs)
266 {
267 	g_return_val_if_fail (attrs != NULL, FALSE);
268 	g_return_val_if_fail (attrs[0] != NULL, FALSE);
269 	g_return_val_if_fail (attrs[1] != NULL, FALSE);
270 
271 	if (!attr_eq (attrs[0], name))
272 		return FALSE;
273 
274 	dep->sheet = NULL;
275 	if (attrs[1] != NULL && *attrs[1] != '\0') {
276 		GnmParsePos pp;
277 
278 		parse_pos_init_sheet (&pp, gnm_xml_in_cur_sheet (xin));
279 		dep->texpr = gnm_expr_parse_str (CXML2C (attrs[1]), &pp,
280 						 GNM_EXPR_PARSE_DEFAULT,
281 						 convs, NULL);
282 	} else
283 		dep->texpr = NULL;
284 
285 	return TRUE;
286 }
287 
288 static SheetObjectView *
sheet_object_widget_new_view(SheetObject * so,SheetObjectViewContainer * container)289 sheet_object_widget_new_view (SheetObject *so, SheetObjectViewContainer *container)
290 {
291 	GtkWidget *view_widget = sow_create_widget (GNM_SOW (so));
292 	GocItem *view_item = goc_item_new (
293 		gnm_pane_object_group (GNM_PANE (container)),
294 		so_widget_view_get_type (),
295 		NULL);
296 	goc_item_new (GOC_GROUP (view_item),
297 		      GOC_TYPE_WIDGET,
298 		      "widget", view_widget,
299 		      NULL);
300 	/* g_warning ("%p is widget for so %p", (void *)view_widget, (void *)so);*/
301 	gtk_widget_show_all (view_widget);
302 	goc_item_hide (view_item);
303 	gnm_pane_widget_register (so, view_widget, view_item);
304 	return gnm_pane_object_register (so, view_item, TRUE);
305 }
306 
307 static void
sheet_object_widget_class_init(GObjectClass * object_class)308 sheet_object_widget_class_init (GObjectClass *object_class)
309 {
310 	SheetObjectClass *so_class = GNM_SO_CLASS (object_class);
311 	SheetObjectWidgetClass *sow_class = GNM_SOW_CLASS (object_class);
312 
313 	sheet_object_widget_class = G_OBJECT_CLASS (object_class);
314 
315 	/* SheetObject class method overrides */
316 	so_class->new_view		= sheet_object_widget_new_view;
317 	so_class->rubber_band_directly	= TRUE;
318 	so_class->draw_cairo		= sheet_widget_draw_cairo;
319 
320 	sow_class->create_widget = NULL;
321 }
322 
323 static void
sheet_object_widget_init(SheetObjectWidget * sow)324 sheet_object_widget_init (SheetObjectWidget *sow)
325 {
326 	SheetObject *so = GNM_SO (sow);
327 	so->flags |= SHEET_OBJECT_CAN_PRESS;
328 }
329 
GSF_CLASS(SheetObjectWidget,sheet_object_widget,sheet_object_widget_class_init,sheet_object_widget_init,GNM_SO_TYPE)330 GSF_CLASS (SheetObjectWidget, sheet_object_widget,
331 	   sheet_object_widget_class_init,
332 	   sheet_object_widget_init,
333 	   GNM_SO_TYPE)
334 
335 static WorkbookControl *
336 widget_wbc (GtkWidget *widget)
337 {
338 	return scg_wbc (GNM_SIMPLE_CANVAS (gtk_widget_get_ancestor (widget, GNM_SIMPLE_CANVAS_TYPE))->scg);
339 }
340 
341 
342 /****************************************************************************/
343 #define GNM_SOW_FRAME(obj)     (G_TYPE_CHECK_INSTANCE_CAST((obj), GNM_SOW_FRAME_TYPE, SheetWidgetFrame))
344 typedef struct {
345 	SheetObjectWidget	sow;
346 	char *label;
347 } SheetWidgetFrame;
348 typedef SheetObjectWidgetClass SheetWidgetFrameClass;
349 
350 enum {
351 	SOF_PROP_0 = 0,
352 	SOF_PROP_TEXT
353 };
354 
355 static void
sheet_widget_frame_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)356 sheet_widget_frame_get_property (GObject *obj, guint param_id,
357 				  GValue *value, GParamSpec *pspec)
358 {
359 	SheetWidgetFrame *swf = GNM_SOW_FRAME (obj);
360 
361 	switch (param_id) {
362 	case SOF_PROP_TEXT:
363 		g_value_set_string (value, swf->label);
364 		break;
365 	default:
366 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
367 		break;
368 	}
369 }
370 
371 static void
sheet_widget_frame_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)372 sheet_widget_frame_set_property (GObject *obj, guint param_id,
373 				 GValue const *value, GParamSpec *pspec)
374 {
375 	SheetWidgetFrame *swf = GNM_SOW_FRAME (obj);
376 
377 	switch (param_id) {
378 	case SOF_PROP_TEXT:
379 		sheet_widget_frame_set_label (GNM_SO (swf),
380 					       g_value_get_string (value));
381 		break;
382 	default:
383 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
384 		return;
385 	}
386 }
387 
388 
389 static void
sheet_widget_frame_init_full(SheetWidgetFrame * swf,char const * text)390 sheet_widget_frame_init_full (SheetWidgetFrame *swf, char const *text)
391 {
392 	swf->label = g_strdup (text);
393 }
394 
395 static void
sheet_widget_frame_init(SheetWidgetFrame * swf)396 sheet_widget_frame_init (SheetWidgetFrame *swf)
397 {
398 	sheet_widget_frame_init_full (swf, _("Frame"));
399 }
400 
401 static void
sheet_widget_frame_finalize(GObject * obj)402 sheet_widget_frame_finalize (GObject *obj)
403 {
404 	SheetWidgetFrame *swf = GNM_SOW_FRAME (obj);
405 
406 	g_free (swf->label);
407 	swf->label = NULL;
408 
409 	sheet_object_widget_class->finalize (obj);
410 }
411 
412 static GtkWidget *
sheet_widget_frame_create_widget(SheetObjectWidget * sow)413 sheet_widget_frame_create_widget (SheetObjectWidget *sow)
414 {
415 	GtkWidget *widget = gtk_event_box_new (),
416 		  *frame = gtk_frame_new (GNM_SOW_FRAME (sow)->label);
417 	gtk_container_add (GTK_CONTAINER (widget), frame);
418 	gtk_event_box_set_visible_window (GTK_EVENT_BOX (widget), FALSE);
419 	return widget;
420 }
421 
422 static void
sheet_widget_frame_copy(SheetObject * dst,SheetObject const * src)423 sheet_widget_frame_copy (SheetObject *dst, SheetObject const *src)
424 {
425 	sheet_widget_frame_init_full (GNM_SOW_FRAME (dst),
426 		GNM_SOW_FRAME (src)->label);
427 }
428 
429 static void
sheet_widget_frame_write_xml_sax(SheetObject const * so,GsfXMLOut * output,G_GNUC_UNUSED GnmConventions const * convs)430 sheet_widget_frame_write_xml_sax (SheetObject const *so, GsfXMLOut *output,
431 				  G_GNUC_UNUSED GnmConventions const *convs)
432 {
433 	SheetWidgetFrame const *swf = GNM_SOW_FRAME (so);
434 	gsf_xml_out_add_cstr (output, "Label", swf->label);
435 }
436 
437 static void
sheet_widget_frame_prep_sax_parser(SheetObject * so,G_GNUC_UNUSED GsfXMLIn * xin,xmlChar const ** attrs,G_GNUC_UNUSED GnmConventions const * convs)438 sheet_widget_frame_prep_sax_parser (SheetObject *so, G_GNUC_UNUSED GsfXMLIn *xin,
439 				    xmlChar const **attrs,
440 				    G_GNUC_UNUSED GnmConventions const *convs)
441 {
442 	SheetWidgetFrame *swf = GNM_SOW_FRAME (so);
443 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2)
444 		if (attr_eq (attrs[0], "Label")) {
445 			g_free (swf->label);
446 			swf->label = g_strdup (CXML2C (attrs[1]));
447 		}
448 }
449 
450 typedef struct {
451 	GtkWidget          *dialog;
452 	GtkWidget          *label;
453 
454 	char               *old_label;
455 	GtkWidget          *old_focus;
456 
457 	WBCGtk *wbcg;
458 	SheetWidgetFrame   *swf;
459 	Sheet		   *sheet;
460 } FrameConfigState;
461 
462 static void
cb_frame_config_destroy(FrameConfigState * state)463 cb_frame_config_destroy (FrameConfigState *state)
464 {
465 	g_return_if_fail (state != NULL);
466 
467 	g_free (state->old_label);
468 	state->old_label = NULL;
469 	state->dialog = NULL;
470 	g_free (state);
471 }
472 
473 static void
cb_frame_config_ok_clicked(G_GNUC_UNUSED GtkWidget * button,FrameConfigState * state)474 cb_frame_config_ok_clicked (G_GNUC_UNUSED GtkWidget *button, FrameConfigState *state)
475 {
476 	gchar const *text = gtk_entry_get_text(GTK_ENTRY(state->label));
477 
478 	cmd_so_set_frame_label (GNM_WBC (state->wbcg),
479 				GNM_SO (state->swf),
480 				g_strdup (state->old_label), g_strdup (text));
481 	gtk_widget_destroy (state->dialog);
482 }
483 
484 void
sheet_widget_frame_set_label(SheetObject * so,char const * str)485 sheet_widget_frame_set_label (SheetObject *so, char const* str)
486 {
487 	SheetWidgetFrame *swf = GNM_SOW_FRAME (so);
488 	GList *ptr;
489 
490 	str = str ? str : "";
491 
492 	if (go_str_compare (str, swf->label) == 0)
493 		return;
494 
495 	g_free (swf->label);
496 	swf->label = g_strdup (str);
497 
498 	for (ptr = swf->sow.so.realized_list; ptr != NULL; ptr = ptr->next) {
499 		SheetObjectView *view = ptr->data;
500 		GocWidget *item = get_goc_widget (view);
501 		GList *children = gtk_container_get_children (GTK_CONTAINER (item->widget));
502 		gtk_frame_set_label (GTK_FRAME (children->data), str);
503 		g_list_free (children);
504 	}
505 }
506 
507 static void
cb_frame_config_cancel_clicked(G_GNUC_UNUSED GtkWidget * button,FrameConfigState * state)508 cb_frame_config_cancel_clicked (G_GNUC_UNUSED GtkWidget *button, FrameConfigState *state)
509 {
510 	sheet_widget_frame_set_label (GNM_SO (state->swf), state->old_label);
511 
512 	gtk_widget_destroy (state->dialog);
513 }
514 
515 static void
cb_frame_label_changed(GtkWidget * entry,FrameConfigState * state)516 cb_frame_label_changed (GtkWidget *entry, FrameConfigState *state)
517 {
518 	gchar const *text;
519 
520 	text = gtk_entry_get_text(GTK_ENTRY(entry));
521 	sheet_widget_frame_set_label (GNM_SO (state->swf), text);
522 }
523 
524 static void
sheet_widget_frame_user_config(SheetObject * so,SheetControl * sc)525 sheet_widget_frame_user_config (SheetObject *so, SheetControl *sc)
526 {
527 	SheetWidgetFrame *swf = GNM_SOW_FRAME (so);
528 	WBCGtk   *wbcg = scg_wbcg (GNM_SCG (sc));
529 	FrameConfigState *state;
530 	GtkBuilder *gui;
531 
532 	g_return_if_fail (swf != NULL);
533 
534 	/* Only pop up one copy per workbook */
535 	if (gnm_dialog_raise_if_exists (wbcg, SHEET_OBJECT_CONFIG_KEY))
536 		return;
537 
538 	gui = gnm_gtk_builder_load ("res:ui/so-frame.ui", NULL, GO_CMD_CONTEXT (wbcg));
539 	if (!gui)
540 		return;
541 	state = g_new (FrameConfigState, 1);
542 	state->swf = swf;
543 	state->wbcg = wbcg;
544 	state->sheet = sc_sheet	(sc);
545 	state->old_focus = NULL;
546 	state->old_label = g_strdup(swf->label);
547 	state->dialog = go_gtk_builder_get_widget (gui, "so_frame");
548 
549 	state->label = go_gtk_builder_get_widget (gui, "entry");
550 	gtk_entry_set_text (GTK_ENTRY(state->label), swf->label);
551 	gtk_editable_select_region (GTK_EDITABLE(state->label), 0, -1);
552 	gnm_editable_enters (GTK_WINDOW (state->dialog),
553 				  GTK_WIDGET (state->label));
554 
555 	g_signal_connect (G_OBJECT(state->label),
556 			  "changed",
557 			  G_CALLBACK (cb_frame_label_changed), state);
558 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui,
559 							  "ok_button")),
560 			  "clicked",
561 			  G_CALLBACK (cb_frame_config_ok_clicked), state);
562 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui,
563 							  "cancel_button")),
564 			  "clicked",
565 			  G_CALLBACK (cb_frame_config_cancel_clicked), state);
566 
567 	gnm_init_help_button (
568 		go_gtk_builder_get_widget (gui, "help_button"),
569 		GNUMERIC_HELP_LINK_SO_FRAME);
570 
571 
572 	gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
573 			       SHEET_OBJECT_CONFIG_KEY);
574 
575 	wbc_gtk_attach_guru (state->wbcg, state->dialog);
576 	g_object_set_data_full (G_OBJECT (state->dialog),
577 		"state", state, (GDestroyNotify) cb_frame_config_destroy);
578 	g_object_unref (gui);
579 
580 	gtk_widget_show (state->dialog);
581 }
582 
583 static PangoFontDescription *
get_font(void)584 get_font (void)
585 {
586 	// Note: Under gnumeric, we get a proper font using GtkStyleContext.
587 	// Under ssconvert, we try GSettings.
588 	// The 'sans 10' is just insurance
589 
590 	PangoFontDescription *desc;
591 	PangoFontMask mask;
592 	int size = 0;
593 
594 	if (gdk_screen_get_default ()) {
595 		// Without a default screen, the following will crash
596 		// with newer gtk+.
597 		GtkStyleContext *style = gtk_style_context_new ();
598 		GtkWidgetPath *path = gtk_widget_path_new ();
599 
600 		gtk_style_context_set_path (style, path);
601 		gtk_widget_path_unref (path);
602 
603 		gtk_style_context_get (style, GTK_STATE_FLAG_NORMAL,
604 				       GTK_STYLE_PROPERTY_FONT, &desc, NULL);
605 		g_object_unref (style);
606 	} else
607 		desc = pango_font_description_new ();
608 
609 	mask = pango_font_description_get_set_fields (desc);
610 	if ((mask & PANGO_FONT_MASK_SIZE) != 0)
611 		size = pango_font_description_get_size (desc);
612 
613 	if (gnm_debug_flag ("so-font")) {
614 		char *s = pango_font_description_to_string (desc);
615 		g_printerr ("from GtkStyleContext font=\"%s\", family set = %i,"
616 			    " size set = %i, size = %i\n",
617 			    s, ((mask & PANGO_FONT_MASK_FAMILY) != 0),
618 			    ((mask & PANGO_FONT_MASK_SIZE) != 0), size);
619 		g_free (s);
620 	}
621 
622 	if ((mask & PANGO_FONT_MASK_FAMILY) == 0 || size == 0) {
623 		/* Trying gsettings */
624 		GSettings *set = g_settings_new ("org.gnome.desktop.interface");
625 		char *font_name = g_settings_get_string (set, "font-name");
626 		if (font_name != NULL) {
627 			pango_font_description_free (desc);
628 			desc = pango_font_description_from_string (font_name);
629 			g_free (font_name);
630 			mask = pango_font_description_get_set_fields (desc);
631 			if ((mask & PANGO_FONT_MASK_SIZE) != 0)
632 				size = pango_font_description_get_size (desc);
633 			else
634 				size = 0;
635 			if (gnm_debug_flag ("so-font")) {
636 				char *s = pango_font_description_to_string (desc);
637 				g_printerr ("from GSettings: font=\"%s\", family set = %i,"
638 					    " size set = %i, size = %i\n",
639 					    s, ((mask & PANGO_FONT_MASK_FAMILY) != 0),
640 					    ((mask & PANGO_FONT_MASK_SIZE) != 0), size);
641 				g_free (s);
642 			}
643 		}
644 	}
645 
646 	if ((mask & PANGO_FONT_MASK_FAMILY) == 0 || size == 0) {
647 		pango_font_description_free (desc);
648 		desc = pango_font_description_from_string ("sans 10");
649 		if (gnm_debug_flag ("so-font"))
650 			g_printerr ("Using \"sans 10\" instead.\n");
651 	}
652 
653 	return desc;
654 }
655 
656 static void
draw_cairo_text(cairo_t * cr,char const * text,int * pwidth,int * pheight,gboolean centered_v,gboolean centered_h,gboolean single,gint highlight_n,gboolean scale)657 draw_cairo_text (cairo_t *cr, char const *text, int *pwidth, int *pheight,
658 		 gboolean centered_v, gboolean centered_h, gboolean single, gint highlight_n, gboolean scale)
659 {
660 	PangoLayout *layout = pango_cairo_create_layout (cr);
661 	double const scale_h = 72. / gnm_app_display_dpi_get (TRUE);
662 	double const scale_v = 72. / gnm_app_display_dpi_get (FALSE);
663 	PangoFontDescription *desc = get_font ();
664 	int width, height;
665 
666 	pango_context_set_font_description
667 		(pango_layout_get_context (layout), desc);
668 	pango_layout_set_spacing (layout, 3 * PANGO_SCALE);
669 	pango_layout_set_single_paragraph_mode (layout, single);
670 	pango_layout_set_text (layout, text, -1);
671 	pango_layout_get_pixel_size (layout, &width, &height);
672 
673 	cairo_scale (cr, scale_h, scale_v);
674 
675 	if (scale && pwidth != NULL && pheight != NULL) {
676 		double sc_x = ((double) *pwidth)/(width * scale_h);
677 		double sc_y = ((double) *pheight)/(height * scale_v);
678 		double sc = MIN(sc_x, sc_y);
679 
680 		if (sc < 1.)
681 			cairo_scale (cr, sc, sc);
682 	}
683 
684 	if (centered_v)
685 		cairo_rel_move_to (cr, 0., 0.5 - ((double)height)/2.);
686 	if (centered_h)
687 		cairo_rel_move_to (cr, 0.5 - ((double)width)/2., 0.);
688 	if (highlight_n > 0 && pheight != NULL && pwidth != NULL) {
689 		PangoLayoutIter *pliter;
690 		gboolean got_line = TRUE;
691 		int i;
692 		pliter = pango_layout_get_iter (layout);
693 		for (i = 1; i < highlight_n; i++)
694 			got_line = pango_layout_iter_next_line (pliter);
695 
696 		if (got_line) {
697 			int y0, y1;
698 			double dy0 = 0, dy1 = 0;
699 			pango_layout_iter_get_line_yrange (pliter, &y0, &y1);
700 			dy0 = y0 / (double)PANGO_SCALE;
701 			dy1 = y1 / (double)PANGO_SCALE;
702 
703 			if (dy1 > (*pheight - 4)/scale_v)
704 				cairo_translate (cr, 0, (*pheight - 4)/scale_v - dy1);
705 
706 			cairo_new_path (cr);
707 			cairo_rectangle (cr, -4/scale_h, dy0,
708 					 *pwidth/scale_h, dy1 - dy0);
709 			cairo_set_source_rgb(cr, 0.8, 0.8, 0.8);
710 			cairo_fill (cr);
711 		}
712 		pango_layout_iter_free (pliter);
713 		cairo_set_source_rgb(cr, 0, 0, 0);
714 	}
715 	pango_cairo_show_layout (cr, layout);
716 	pango_font_description_free (desc);
717 	g_object_unref (layout);
718 
719 	if (pwidth)
720 		*pwidth = width * scale_h;
721 	if (pheight)
722 		*pheight = height * scale_v;
723 }
724 
725 static void
sheet_widget_frame_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)726 sheet_widget_frame_draw_cairo (SheetObject const *so, cairo_t *cr,
727 			       double width, double height)
728 {
729 	SheetWidgetFrame *swf = GNM_SOW_FRAME (so);
730 
731 	int theight = 0, twidth = 0;
732 	cairo_save (cr);
733 	cairo_move_to (cr, 10, 0);
734 
735 	cairo_save (cr);
736 	cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
737 	draw_cairo_text (cr, swf->label, &twidth, &theight, FALSE, FALSE, TRUE, 0, FALSE);
738 	cairo_restore (cr);
739 
740 	cairo_set_line_width (cr, 1);
741 	cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
742 	cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
743 	cairo_new_path (cr);
744 	cairo_move_to (cr, 6, theight/2);
745 	cairo_line_to (cr, 0, theight/2);
746 	cairo_line_to (cr, 0, height);
747 	cairo_line_to (cr, width, height);
748 	cairo_line_to (cr, width, theight/2);
749 	cairo_line_to (cr, 14 + twidth, theight/2);
750 	cairo_stroke (cr);
751 
752 	cairo_set_source_rgb(cr, 0.8, 0.8, 0.8);
753 	cairo_new_path (cr);
754 	cairo_move_to (cr, 6, theight/2 + 1);
755 	cairo_line_to (cr, 1, theight/2 + 1);
756 	cairo_line_to (cr, 1, height - 1);
757 	cairo_line_to (cr, width - 1, height - 1);
758 	cairo_line_to (cr, width - 1, theight/2 + 1);
759 	cairo_line_to (cr, 14 + twidth, theight/2 + 1);
760 	cairo_stroke (cr);
761 
762 	cairo_new_path (cr);
763 	cairo_restore (cr);
764 }
765 
766 SOW_MAKE_TYPE (frame, Frame,
767 	       sheet_widget_frame_user_config,
768 	       NULL,
769 	       NULL,
770 	       NULL,
771 	       sheet_widget_frame_copy,
772 	       sheet_widget_frame_write_xml_sax,
773 	       sheet_widget_frame_prep_sax_parser,
774 	       sheet_widget_frame_get_property,
775 	       sheet_widget_frame_set_property,
776 	       sheet_widget_frame_draw_cairo,
777 	       {
778 		       g_object_class_install_property
779 			       (object_class, SOF_PROP_TEXT,
780 				g_param_spec_string ("text", NULL, NULL, NULL,
781 						     GSF_PARAM_STATIC | G_PARAM_READWRITE));
782 	       })
783 
784 /****************************************************************************/
785 #define GNM_SOW_BUTTON(obj)     (G_TYPE_CHECK_INSTANCE_CAST((obj), GNM_SOW_BUTTON_TYPE, SheetWidgetButton))
786 #define DEP_TO_BUTTON(d_ptr)		(SheetWidgetButton *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetButton, dep))
787 typedef struct {
788 	SheetObjectWidget	sow;
789 
790 	GnmDependent	 dep;
791 	char *label;
792 	PangoAttrList *markup;
793 	gboolean	 value;
794 } SheetWidgetButton;
795 typedef SheetObjectWidgetClass SheetWidgetButtonClass;
796 
797 enum {
798 	SOB_PROP_0 = 0,
799 	SOB_PROP_TEXT,
800 	SOB_PROP_MARKUP
801 };
802 
803 static void
sheet_widget_button_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)804 sheet_widget_button_get_property (GObject *obj, guint param_id,
805 				  GValue *value, GParamSpec *pspec)
806 {
807 	SheetWidgetButton *swb = GNM_SOW_BUTTON (obj);
808 
809 	switch (param_id) {
810 	case SOB_PROP_TEXT:
811 		g_value_set_string (value, swb->label);
812 		break;
813 	case SOB_PROP_MARKUP:
814 		g_value_set_boxed (value, NULL); /* swb->markup */
815 		break;
816 	default:
817 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
818 		break;
819 	}
820 }
821 
822 static void
sheet_widget_button_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)823 sheet_widget_button_set_property (GObject *obj, guint param_id,
824 				    GValue const *value, GParamSpec *pspec)
825 {
826 	SheetWidgetButton *swb = GNM_SOW_BUTTON (obj);
827 
828 	switch (param_id) {
829 	case SOB_PROP_TEXT:
830 		sheet_widget_button_set_label (GNM_SO (swb),
831 					       g_value_get_string (value));
832 		break;
833 	case SOB_PROP_MARKUP:
834 #if 0
835 		sheet_widget_button_set_markup (GNM_SO (swb),
836 						g_value_peek_pointer (value));
837 #endif
838 		break;
839 	default:
840 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
841 		return;
842 	}
843 }
844 
845 static void
button_eval(GnmDependent * dep)846 button_eval (GnmDependent *dep)
847 {
848 	GnmValue *v;
849 	GnmEvalPos pos;
850 	gboolean err, result;
851 
852 	v = gnm_expr_top_eval (dep->texpr, eval_pos_init_dep (&pos, dep),
853 			       GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
854 	result = value_get_as_bool (v, &err);
855 	value_release (v);
856 	if (!err) {
857 		SheetWidgetButton *swb = DEP_TO_BUTTON(dep);
858 
859 		swb->value = result;
860 	}
861 }
862 
863 static void
button_debug_name(GnmDependent const * dep,GString * target)864 button_debug_name (GnmDependent const *dep, GString *target)
865 {
866 	g_string_append_printf (target, "Button%p", (void *)dep);
867 }
868 
869 static DEPENDENT_MAKE_TYPE (button, .eval = button_eval, .debug_name = button_debug_name )
870 
871 static void
sheet_widget_button_init_full(SheetWidgetButton * swb,GnmCellRef const * ref,char const * text,PangoAttrList * markup)872 sheet_widget_button_init_full (SheetWidgetButton *swb,
873 			       GnmCellRef const *ref,
874 			       char const *text,
875 			       PangoAttrList *markup)
876 {
877 	SheetObject *so = GNM_SO (swb);
878 
879 	so->flags &= ~SHEET_OBJECT_PRINT;
880 	swb->label = g_strdup (text);
881 	swb->markup = markup;
882 	swb->value = FALSE;
883 	swb->dep.sheet = NULL;
884 	swb->dep.flags = button_get_dep_type ();
885 	swb->dep.texpr = (ref != NULL)
886 		? gnm_expr_top_new (gnm_expr_new_cellref (ref))
887 		: NULL;
888 	if (markup) pango_attr_list_ref (markup);
889 }
890 
891 static void
sheet_widget_button_init(SheetWidgetButton * swb)892 sheet_widget_button_init (SheetWidgetButton *swb)
893 {
894 	sheet_widget_button_init_full (swb, NULL, _("Button"), NULL);
895 }
896 
897 static void
sheet_widget_button_finalize(GObject * obj)898 sheet_widget_button_finalize (GObject *obj)
899 {
900 	SheetWidgetButton *swb = GNM_SOW_BUTTON (obj);
901 
902 	g_free (swb->label);
903 	swb->label = NULL;
904 
905 	if (swb->markup) {
906 		pango_attr_list_unref (swb->markup);
907 		swb->markup = NULL;
908 	}
909 
910 	dependent_set_expr (&swb->dep, NULL);
911 
912 	sheet_object_widget_class->finalize (obj);
913 }
914 
915 static void
cb_button_pressed(GtkToggleButton * button,SheetWidgetButton * swb)916 cb_button_pressed (GtkToggleButton *button, SheetWidgetButton *swb)
917 {
918 	GnmCellRef ref;
919 
920 	swb->value = TRUE;
921 
922 	if (so_get_ref (GNM_SO (swb), &ref, TRUE) != NULL) {
923 		cmd_so_set_value (widget_wbc (GTK_WIDGET (button)),
924 				  _("Pressed Button"),
925 				  &ref, value_new_bool (TRUE),
926 				  sheet_object_get_sheet (GNM_SO (swb)));
927 	}
928 }
929 
930 static void
cb_button_released(GtkToggleButton * button,SheetWidgetButton * swb)931 cb_button_released (GtkToggleButton *button, SheetWidgetButton *swb)
932 {
933 	GnmCellRef ref;
934 
935 	swb->value = FALSE;
936 
937 	if (so_get_ref (GNM_SO (swb), &ref, TRUE) != NULL) {
938 		cmd_so_set_value (widget_wbc (GTK_WIDGET (button)),
939 				  _("Released Button"),
940 				  &ref, value_new_bool (FALSE),
941 				  sheet_object_get_sheet (GNM_SO (swb)));
942 	}
943 }
944 
945 static GtkWidget *
sheet_widget_button_create_widget(SheetObjectWidget * sow)946 sheet_widget_button_create_widget (SheetObjectWidget *sow)
947 {
948 	SheetWidgetButton *swb = GNM_SOW_BUTTON (sow);
949 	GtkWidget *w = gtk_button_new_with_label (swb->label);
950 	gtk_widget_set_can_focus (w, FALSE);
951 	gtk_label_set_attributes (GTK_LABEL (gtk_bin_get_child (GTK_BIN (w))),
952 				  swb->markup);
953 	g_signal_connect (G_OBJECT (w),
954 			  "pressed",
955 			  G_CALLBACK (cb_button_pressed), swb);
956 	g_signal_connect (G_OBJECT (w),
957 			  "released",
958 			  G_CALLBACK (cb_button_released), swb);
959 	return w;
960 }
961 
962 static void
sheet_widget_button_copy(SheetObject * dst,SheetObject const * src)963 sheet_widget_button_copy (SheetObject *dst, SheetObject const *src)
964 {
965 	SheetWidgetButton const *src_swb = GNM_SOW_BUTTON (src);
966 	SheetWidgetButton       *dst_swb = GNM_SOW_BUTTON (dst);
967 	GnmCellRef ref;
968 	sheet_widget_button_init_full (dst_swb,
969 				       so_get_ref (src, &ref, FALSE),
970 				       src_swb->label,
971 				       src_swb->markup);
972 	dst_swb->value = src_swb->value;
973 }
974 
975 typedef struct {
976 	GtkWidget *dialog;
977 	GnmExprEntry *expression;
978 	GtkWidget *label;
979 
980 	char *old_label;
981 	GtkWidget *old_focus;
982 
983 	WBCGtk  *wbcg;
984 	SheetWidgetButton *swb;
985 	Sheet		    *sheet;
986 } ButtonConfigState;
987 
988 static void
cb_button_set_focus(G_GNUC_UNUSED GtkWidget * window,GtkWidget * focus_widget,ButtonConfigState * state)989 cb_button_set_focus (G_GNUC_UNUSED GtkWidget *window, GtkWidget *focus_widget,
990 		     ButtonConfigState *state)
991 {
992 	/* Note:  half of the set-focus action is handle by the default
993 	 *        callback installed by wbc_gtk_attach_guru */
994 
995 	/* Force an update of the content in case it needs tweaking (eg make it
996 	 * absolute) */
997 	if (state->old_focus != NULL &&
998 	    GNM_EXPR_ENTRY_IS (gtk_widget_get_parent (state->old_focus))) {
999 		GnmParsePos  pp;
1000 		GnmExprTop const *texpr = gnm_expr_entry_parse
1001 			(GNM_EXPR_ENTRY (gtk_widget_get_parent (state->old_focus)),
1002 			 parse_pos_init_sheet (&pp, state->sheet),
1003 			 NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
1004 		if (texpr != NULL)
1005 			gnm_expr_top_unref (texpr);
1006 	}
1007 	state->old_focus = focus_widget;
1008 }
1009 
1010 static void
cb_button_config_destroy(ButtonConfigState * state)1011 cb_button_config_destroy (ButtonConfigState *state)
1012 {
1013 	g_return_if_fail (state != NULL);
1014 
1015 	g_free (state->old_label);
1016 	state->old_label = NULL;
1017 	state->dialog = NULL;
1018 	g_free (state);
1019 }
1020 
1021 static void
cb_button_config_ok_clicked(G_GNUC_UNUSED GtkWidget * button,ButtonConfigState * state)1022 cb_button_config_ok_clicked (G_GNUC_UNUSED GtkWidget *button, ButtonConfigState *state)
1023 {
1024 	SheetObject *so = GNM_SO (state->swb);
1025 	GnmParsePos  pp;
1026 	GnmExprTop const *texpr = gnm_expr_entry_parse (state->expression,
1027 		parse_pos_init_sheet (&pp, so->sheet),
1028 		NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
1029 	gchar const *text = gtk_entry_get_text(GTK_ENTRY(state->label));
1030 
1031 	cmd_so_set_button (GNM_WBC (state->wbcg), so,
1032 			     texpr, g_strdup (state->old_label), g_strdup (text));
1033 
1034 	gtk_widget_destroy (state->dialog);
1035 }
1036 
1037 static void
cb_button_config_cancel_clicked(G_GNUC_UNUSED GtkWidget * button,ButtonConfigState * state)1038 cb_button_config_cancel_clicked (G_GNUC_UNUSED GtkWidget *button, ButtonConfigState *state)
1039 {
1040 	sheet_widget_button_set_label	(GNM_SO (state->swb),
1041 					 state->old_label);
1042 	gtk_widget_destroy (state->dialog);
1043 }
1044 
1045 static void
cb_button_label_changed(GtkEntry * entry,ButtonConfigState * state)1046 cb_button_label_changed (GtkEntry *entry, ButtonConfigState *state)
1047 {
1048 	sheet_widget_button_set_label	(GNM_SO (state->swb),
1049 					 gtk_entry_get_text (entry));
1050 }
1051 
1052 static void
sheet_widget_button_user_config(SheetObject * so,SheetControl * sc)1053 sheet_widget_button_user_config (SheetObject *so, SheetControl *sc)
1054 {
1055 	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1056 	WBCGtk  *wbcg = scg_wbcg (GNM_SCG (sc));
1057 	ButtonConfigState *state;
1058 	GtkWidget *grid;
1059 	GtkBuilder *gui;
1060 
1061 	g_return_if_fail (swb != NULL);
1062 
1063 	/* Only pop up one copy per workbook */
1064 	if (gnm_dialog_raise_if_exists (wbcg, SHEET_OBJECT_CONFIG_KEY))
1065 		return;
1066 
1067 	gui = gnm_gtk_builder_load ("res:ui/so-button.ui", NULL, GO_CMD_CONTEXT (wbcg));
1068 	if (!gui)
1069 		return;
1070 	state = g_new (ButtonConfigState, 1);
1071 	state->swb = swb;
1072 	state->wbcg = wbcg;
1073 	state->sheet = sc_sheet	(sc);
1074 	state->old_focus = NULL;
1075 	state->old_label = g_strdup (swb->label);
1076 	state->dialog = go_gtk_builder_get_widget (gui, "SO-Button");
1077 
1078 	grid = go_gtk_builder_get_widget (gui, "main-grid");
1079 
1080 	state->expression = gnm_expr_entry_new (wbcg, TRUE);
1081 	gnm_expr_entry_set_flags (state->expression,
1082 		GNM_EE_FORCE_ABS_REF | GNM_EE_SHEET_OPTIONAL | GNM_EE_SINGLE_RANGE,
1083 		GNM_EE_MASK);
1084 	gnm_expr_entry_load_from_dep (state->expression, &swb->dep);
1085 	go_atk_setup_label (go_gtk_builder_get_widget (gui, "label_linkto"),
1086 			     GTK_WIDGET (state->expression));
1087 	gtk_grid_attach (GTK_GRID (grid),
1088 	                 GTK_WIDGET (state->expression), 1, 0, 1, 1);
1089 	gtk_widget_show (GTK_WIDGET (state->expression));
1090 
1091 	state->label = go_gtk_builder_get_widget (gui, "label_entry");
1092 	gtk_entry_set_text (GTK_ENTRY (state->label), swb->label);
1093 	gtk_editable_select_region (GTK_EDITABLE(state->label), 0, -1);
1094 	gnm_editable_enters (GTK_WINDOW (state->dialog),
1095 				  GTK_WIDGET (state->expression));
1096 	gnm_editable_enters (GTK_WINDOW (state->dialog),
1097 				  GTK_WIDGET (state->label));
1098 
1099 	g_signal_connect (G_OBJECT (state->label),
1100 		"changed",
1101 		G_CALLBACK (cb_button_label_changed), state);
1102 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "ok_button")),
1103 		"clicked",
1104 		G_CALLBACK (cb_button_config_ok_clicked), state);
1105 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "cancel_button")),
1106 		"clicked",
1107 		G_CALLBACK (cb_button_config_cancel_clicked), state);
1108 
1109 	gnm_init_help_button (
1110 		go_gtk_builder_get_widget (gui, "help_button"),
1111 		GNUMERIC_HELP_LINK_SO_BUTTON);
1112 
1113 	gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
1114 			       SHEET_OBJECT_CONFIG_KEY);
1115 
1116 	wbc_gtk_attach_guru (state->wbcg, state->dialog);
1117 	g_object_set_data_full (G_OBJECT (state->dialog),
1118 		"state", state, (GDestroyNotify) cb_button_config_destroy);
1119 
1120 	/* Note:  half of the set-focus action is handle by the default */
1121 	/*        callback installed by wbc_gtk_attach_guru */
1122 	g_signal_connect (G_OBJECT (state->dialog), "set-focus",
1123 		G_CALLBACK (cb_button_set_focus), state);
1124 	g_object_unref (gui);
1125 
1126 	gtk_widget_show (state->dialog);
1127 }
1128 
1129 static gboolean
sheet_widget_button_set_sheet(SheetObject * so,Sheet * sheet)1130 sheet_widget_button_set_sheet (SheetObject *so, Sheet *sheet)
1131 {
1132 	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1133 
1134 	dependent_set_sheet (&swb->dep, sheet);
1135 
1136 	return FALSE;
1137 }
1138 
1139 static void
sheet_widget_button_foreach_dep(SheetObject * so,SheetObjectForeachDepFunc func,gpointer user)1140 sheet_widget_button_foreach_dep (SheetObject *so,
1141 				   SheetObjectForeachDepFunc func,
1142 				   gpointer user)
1143 {
1144 	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1145 	func (&swb->dep, so, user);
1146 }
1147 
1148 static void
sheet_widget_button_write_xml_sax(SheetObject const * so,GsfXMLOut * output,GnmConventions const * convs)1149 sheet_widget_button_write_xml_sax (SheetObject const *so, GsfXMLOut *output,
1150 				   GnmConventions const *convs)
1151 {
1152 	/* FIXME: markup */
1153 	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1154 	gsf_xml_out_add_cstr (output, "Label", swb->label);
1155 	gsf_xml_out_add_int (output, "Value", swb->value);
1156 	sax_write_dep (output, &swb->dep, "Input", convs);
1157 }
1158 
1159 static void
sheet_widget_button_prep_sax_parser(SheetObject * so,GsfXMLIn * xin,xmlChar const ** attrs,GnmConventions const * convs)1160 sheet_widget_button_prep_sax_parser (SheetObject *so, GsfXMLIn *xin,
1161 				     xmlChar const **attrs,
1162 				     GnmConventions const *convs)
1163 {
1164 	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1165 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2)
1166 		if (attr_eq (attrs[0], "Label"))
1167 			g_object_set (G_OBJECT (swb), "text", attrs[1], NULL);
1168 		else if (gnm_xml_attr_int (attrs, "Value", &swb->value))
1169 			;
1170 		else if (sax_read_dep (attrs, "Input", &swb->dep, xin, convs))
1171 			;
1172 }
1173 
1174 void
sheet_widget_button_set_link(SheetObject * so,GnmExprTop const * texpr)1175 sheet_widget_button_set_link (SheetObject *so, GnmExprTop const *texpr)
1176 {
1177  	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1178  	dependent_set_expr (&swb->dep, texpr);
1179  	if (texpr && swb->dep.sheet)
1180  		dependent_link (&swb->dep);
1181 }
1182 
1183 GnmExprTop const *
sheet_widget_button_get_link(SheetObject * so)1184 sheet_widget_button_get_link	 (SheetObject *so)
1185 {
1186  	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1187  	GnmExprTop const *texpr = swb->dep.texpr;
1188 
1189  	if (texpr)
1190  		gnm_expr_top_ref (texpr);
1191 
1192  	return texpr;
1193 }
1194 
1195 
1196 void
sheet_widget_button_set_label(SheetObject * so,char const * str)1197 sheet_widget_button_set_label (SheetObject *so, char const *str)
1198 {
1199 	GList *ptr;
1200 	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1201 	char *new_label;
1202 
1203 	if (go_str_compare (str, swb->label) == 0)
1204 		return;
1205 
1206 	new_label = g_strdup (str);
1207 	g_free (swb->label);
1208 	swb->label = new_label;
1209 
1210 	for (ptr = swb->sow.so.realized_list; ptr != NULL; ptr = ptr->next) {
1211 		SheetObjectView *view = ptr->data;
1212 		GocWidget *item = get_goc_widget (view);
1213 		gtk_button_set_label (GTK_BUTTON (item->widget), swb->label);
1214 	}
1215 }
1216 
1217 void
sheet_widget_button_set_markup(SheetObject * so,PangoAttrList * markup)1218 sheet_widget_button_set_markup (SheetObject *so, PangoAttrList *markup)
1219 {
1220 	GList *ptr;
1221 	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1222 
1223 	if (markup == swb->markup)
1224 		return;
1225 
1226 	if (swb->markup) pango_attr_list_unref (swb->markup);
1227 	swb->markup = markup;
1228 	if (markup) pango_attr_list_ref (markup);
1229 
1230 	for (ptr = swb->sow.so.realized_list; ptr != NULL; ptr = ptr->next) {
1231 		SheetObjectView *view = ptr->data;
1232 		GocWidget *item = get_goc_widget (view);
1233 		GtkLabel *lab =
1234 			GTK_LABEL (gtk_bin_get_child (GTK_BIN (item->widget)));
1235 		gtk_label_set_attributes (lab, swb->markup);
1236 	}
1237 }
1238 
1239 static void
sheet_widget_button_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)1240 sheet_widget_button_draw_cairo (SheetObject const *so, cairo_t *cr,
1241 				double width, double height)
1242 {
1243 	SheetWidgetButton *swb = GNM_SOW_BUTTON (so);
1244 	int twidth, theight;
1245 	double half_line;
1246 	double radius = 10;
1247 
1248 	if (height < 3 * radius)
1249 		radius = height / 3.;
1250 	if (width < 3 * radius)
1251 		radius = width / 3.;
1252 	if (radius < 1)
1253 		radius = 1;
1254 	half_line = radius * 0.15;
1255 
1256 	cairo_save (cr);
1257 	cairo_set_line_width (cr, 2 * half_line);
1258 	cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
1259 
1260 	cairo_new_path (cr);
1261 	cairo_arc (cr, radius + half_line, radius + half_line, radius, M_PI, - M_PI/2);
1262 	cairo_arc (cr, width - (radius + half_line), radius + half_line,
1263 		   radius, - M_PI/2, 0);
1264 	cairo_arc (cr, width - (radius + half_line), height - (radius + half_line),
1265 		   radius, 0, M_PI/2);
1266 	cairo_arc (cr, (radius + half_line), height - (radius + half_line),
1267 		   radius, M_PI/2, M_PI);
1268 	cairo_close_path (cr);
1269 	cairo_stroke (cr);
1270 
1271 	cairo_set_source_rgb(cr, 0, 0, 0);
1272 
1273 	cairo_move_to (cr, width/2., height/2.);
1274 
1275 	twidth = 0.8 * width;
1276 	theight = 0.8 * height;
1277 	draw_cairo_text (cr, swb->label, &twidth, &theight, TRUE, TRUE, TRUE, 0, TRUE);
1278 
1279 	cairo_new_path (cr);
1280 	cairo_restore (cr);
1281 }
1282 
1283 SOW_MAKE_TYPE (button, Button,
1284 	       sheet_widget_button_user_config,
1285 	       sheet_widget_button_set_sheet,
1286 	       so_clear_sheet,
1287 	       sheet_widget_button_foreach_dep,
1288 	       sheet_widget_button_copy,
1289 	       sheet_widget_button_write_xml_sax,
1290 	       sheet_widget_button_prep_sax_parser,
1291 	       sheet_widget_button_get_property,
1292 	       sheet_widget_button_set_property,
1293 	       sheet_widget_button_draw_cairo,
1294 	       {
1295 		       g_object_class_install_property
1296 			       (object_class, SOB_PROP_TEXT,
1297 				g_param_spec_string ("text", NULL, NULL, NULL,
1298 						     GSF_PARAM_STATIC | G_PARAM_READWRITE));
1299 		       g_object_class_install_property
1300 			       (object_class, SOB_PROP_MARKUP,
1301 				g_param_spec_boxed ("markup", NULL, NULL, PANGO_TYPE_ATTR_LIST,
1302 						    GSF_PARAM_STATIC | G_PARAM_READWRITE));
1303 	       })
1304 
1305 /****************************************************************************/
1306 
1307 #define DEP_TO_ADJUSTMENT(d_ptr)	(SheetWidgetAdjustment *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetAdjustment, dep))
1308 #define GNM_SOW_ADJUSTMENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GNM_SOW_ADJUSTMENT_TYPE, SheetWidgetAdjustmentClass))
1309 #define SWA_CLASS(so)		     (GNM_SOW_ADJUSTMENT_CLASS (G_OBJECT_GET_CLASS(so)))
1310 
1311 typedef struct {
1312 	SheetObjectWidget	sow;
1313 
1314 	gboolean  being_updated;
1315 	GnmDependent dep;
1316 	GtkAdjustment *adjustment;
1317 
1318 	gboolean horizontal;
1319 } SheetWidgetAdjustment;
1320 
1321 typedef struct {
1322 	SheetObjectWidgetClass parent_class;
1323 	GType type;
1324 	gboolean has_orientation;
1325 } SheetWidgetAdjustmentClass;
1326 
1327 enum {
1328 	SWA_PROP_0 = 0,
1329 	SWA_PROP_HORIZONTAL
1330 };
1331 
1332 #ifndef g_signal_handlers_disconnect_by_data
1333 #define g_signal_handlers_disconnect_by_data(instance, data) \
1334   g_signal_handlers_disconnect_matched ((instance), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, (data))
1335 #endif
1336 static void
cb_range_destroyed(GtkWidget * w,SheetWidgetAdjustment * swa)1337 cb_range_destroyed (GtkWidget *w, SheetWidgetAdjustment *swa)
1338 {
1339 	GObject *accessible = G_OBJECT (gtk_widget_get_accessible (w));
1340 	if (accessible)
1341 		g_signal_handlers_disconnect_by_data (swa->adjustment, accessible);
1342 }
1343 
1344 static void
sheet_widget_adjustment_set_value(SheetWidgetAdjustment * swa,double new_val)1345 sheet_widget_adjustment_set_value (SheetWidgetAdjustment *swa, double new_val)
1346 {
1347 	if (swa->being_updated)
1348 		return;
1349 	swa->being_updated = TRUE;
1350 	gtk_adjustment_set_value (swa->adjustment, new_val);
1351 	swa->being_updated = FALSE;
1352 }
1353 
1354 /**
1355  * sheet_widget_adjustment_get_adjustment:
1356  * @so: #SheetObject
1357  *
1358  * Returns: (transfer none): the associated #GtkAdjustment.
1359  **/
1360 GtkAdjustment *
sheet_widget_adjustment_get_adjustment(SheetObject * so)1361 sheet_widget_adjustment_get_adjustment (SheetObject *so)
1362 {
1363 	g_return_val_if_fail (GNM_IS_SOW_ADJUSTMENT (so), NULL);
1364 	return (GNM_SOW_ADJUSTMENT (so)->adjustment);
1365 }
1366 
1367 gboolean
sheet_widget_adjustment_get_horizontal(SheetObject * so)1368 sheet_widget_adjustment_get_horizontal (SheetObject *so)
1369 {
1370 	g_return_val_if_fail (GNM_IS_SOW_ADJUSTMENT (so), TRUE);
1371 	return (GNM_SOW_ADJUSTMENT (so)->horizontal);
1372 }
1373 
1374 void
sheet_widget_adjustment_set_link(SheetObject * so,GnmExprTop const * texpr)1375 sheet_widget_adjustment_set_link (SheetObject *so, GnmExprTop const *texpr)
1376 {
1377 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
1378 	dependent_set_expr (&swa->dep, texpr);
1379 	if (texpr && swa->dep.sheet)
1380 		dependent_link (&swa->dep);
1381 }
1382 
1383 GnmExprTop const *
sheet_widget_adjustment_get_link(SheetObject * so)1384 sheet_widget_adjustment_get_link (SheetObject *so)
1385 {
1386 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
1387 	GnmExprTop const *texpr = swa->dep.texpr;
1388 
1389 	if (texpr)
1390 		gnm_expr_top_ref (texpr);
1391 
1392 	return texpr;
1393 }
1394 
1395 
1396 static void
adjustment_eval(GnmDependent * dep)1397 adjustment_eval (GnmDependent *dep)
1398 {
1399 	GnmValue *v;
1400 	GnmEvalPos pos;
1401 
1402 	v = gnm_expr_top_eval (dep->texpr, eval_pos_init_dep (&pos, dep),
1403 			       GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
1404 	sheet_widget_adjustment_set_value (DEP_TO_ADJUSTMENT(dep),
1405 		value_get_as_float (v));
1406 	value_release (v);
1407 }
1408 
1409 static void
adjustment_debug_name(GnmDependent const * dep,GString * target)1410 adjustment_debug_name (GnmDependent const *dep, GString *target)
1411 {
1412 	g_string_append_printf (target, "Adjustment%p", (void *)dep);
1413 }
1414 
1415 static DEPENDENT_MAKE_TYPE (adjustment, .eval = adjustment_eval, .debug_name = adjustment_debug_name )
1416 
1417 static void
cb_adjustment_widget_value_changed(GtkWidget * widget,SheetWidgetAdjustment * swa)1418 cb_adjustment_widget_value_changed (GtkWidget *widget,
1419 				    SheetWidgetAdjustment *swa)
1420 {
1421 	GnmCellRef ref;
1422 
1423 	if (swa->being_updated)
1424 		return;
1425 
1426 	if (so_get_ref (GNM_SO (swa), &ref, TRUE) != NULL) {
1427 		GnmCell *cell = sheet_cell_fetch (ref.sheet, ref.col, ref.row);
1428 		/* TODO : add more control for precision, XL is stupid */
1429 		int new_val = gnm_fake_round (gtk_adjustment_get_value (swa->adjustment));
1430 		if (cell->value != NULL &&
1431 		    VALUE_IS_FLOAT (cell->value) &&
1432 		    value_get_as_float (cell->value) == new_val)
1433 			return;
1434 
1435 		swa->being_updated = TRUE;
1436 		cmd_so_set_value (widget_wbc (widget),
1437 				  /* FIXME: This text sucks:  */
1438 				  _("Change widget"),
1439 				  &ref, value_new_int (new_val),
1440 				  sheet_object_get_sheet (GNM_SO (swa)));
1441 		swa->being_updated = FALSE;
1442 	}
1443 }
1444 
1445 void
sheet_widget_adjustment_set_horizontal(SheetObject * so,gboolean horizontal)1446 sheet_widget_adjustment_set_horizontal (SheetObject *so,
1447 					gboolean horizontal)
1448 {
1449 	SheetWidgetAdjustment *swa = (SheetWidgetAdjustment *)so;
1450 	GList *ptr;
1451 	GtkOrientation o;
1452 
1453 	if (!SWA_CLASS (swa)->has_orientation)
1454 		return;
1455 	horizontal = !!horizontal;
1456 	if (horizontal == swa->horizontal)
1457 		return;
1458 	swa->horizontal = horizontal;
1459 	o = horizontal ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
1460 
1461 	/* Change direction for all realized widgets.  */
1462 	for (ptr = swa->sow.so.realized_list; ptr != NULL; ptr = ptr->next) {
1463 		SheetObjectView *view = ptr->data;
1464 		GocWidget *item = get_goc_widget (view);
1465 		gtk_orientable_set_orientation (GTK_ORIENTABLE (item->widget), o);
1466 	}
1467 }
1468 
1469 
1470 static void
sheet_widget_adjustment_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)1471 sheet_widget_adjustment_get_property (GObject *obj, guint param_id,
1472 				      GValue *value, GParamSpec *pspec)
1473 {
1474 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (obj);
1475 
1476 	switch (param_id) {
1477 	case SWA_PROP_HORIZONTAL:
1478 		g_value_set_boolean (value, swa->horizontal);
1479 		break;
1480 	default:
1481 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
1482 		break;
1483 	}
1484 }
1485 
1486 static void
sheet_widget_adjustment_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)1487 sheet_widget_adjustment_set_property (GObject *obj, guint param_id,
1488 				      GValue const *value, GParamSpec *pspec)
1489 {
1490 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (obj);
1491 
1492 	switch (param_id) {
1493 	case SWA_PROP_HORIZONTAL:
1494 		sheet_widget_adjustment_set_horizontal (GNM_SO (swa), g_value_get_boolean (value));
1495 		break;
1496 	default:
1497 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
1498 		return;
1499 	}
1500 }
1501 
1502 static void
sheet_widget_adjustment_init_full(SheetWidgetAdjustment * swa,GnmCellRef const * ref,gboolean horizontal)1503 sheet_widget_adjustment_init_full (SheetWidgetAdjustment *swa,
1504 				   GnmCellRef const *ref,
1505 				   gboolean horizontal)
1506 {
1507 	SheetObject *so;
1508 	g_return_if_fail (swa != NULL);
1509 
1510 	so = GNM_SO (swa);
1511 	so->flags &= ~SHEET_OBJECT_PRINT;
1512 
1513 	swa->adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0., 0., 100., 1., 10., 0.));
1514 	g_object_ref_sink (swa->adjustment);
1515 
1516 	swa->horizontal = horizontal;
1517 	swa->being_updated = FALSE;
1518 	swa->dep.sheet = NULL;
1519 	swa->dep.flags = adjustment_get_dep_type ();
1520 	swa->dep.texpr = (ref != NULL)
1521 		? gnm_expr_top_new (gnm_expr_new_cellref (ref))
1522 		: NULL;
1523 }
1524 
1525 static void
sheet_widget_adjustment_init(SheetWidgetAdjustment * swa)1526 sheet_widget_adjustment_init (SheetWidgetAdjustment *swa)
1527 {
1528 	sheet_widget_adjustment_init_full (swa, NULL, FALSE);
1529 }
1530 
1531 static void
sheet_widget_adjustment_finalize(GObject * obj)1532 sheet_widget_adjustment_finalize (GObject *obj)
1533 {
1534 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (obj);
1535 
1536 	g_return_if_fail (swa != NULL);
1537 
1538 	dependent_set_expr (&swa->dep, NULL);
1539 	if (swa->adjustment != NULL) {
1540 		g_object_unref (swa->adjustment);
1541 		swa->adjustment = NULL;
1542 	}
1543 
1544 	sheet_object_widget_class->finalize (obj);
1545 }
1546 
1547 static void
sheet_widget_adjustment_copy(SheetObject * dst,SheetObject const * src)1548 sheet_widget_adjustment_copy (SheetObject *dst, SheetObject const *src)
1549 {
1550 	SheetWidgetAdjustment const *src_swa = GNM_SOW_ADJUSTMENT (src);
1551 	SheetWidgetAdjustment       *dst_swa = GNM_SOW_ADJUSTMENT (dst);
1552 	GtkAdjustment *dst_adjust, *src_adjust;
1553 	GnmCellRef ref;
1554 
1555 	sheet_widget_adjustment_init_full (dst_swa,
1556 					   so_get_ref (src, &ref, FALSE),
1557 					   src_swa->horizontal);
1558 	dst_adjust = dst_swa->adjustment;
1559 	src_adjust = src_swa->adjustment;
1560 
1561 	gtk_adjustment_configure
1562 		(dst_adjust,
1563 		 gtk_adjustment_get_value (src_adjust),
1564 		 gtk_adjustment_get_lower (src_adjust),
1565 		 gtk_adjustment_get_upper (src_adjust),
1566 		 gtk_adjustment_get_step_increment (src_adjust),
1567 		 gtk_adjustment_get_page_increment (src_adjust),
1568 		 gtk_adjustment_get_page_size (src_adjust));
1569 }
1570 
1571 typedef struct {
1572 	GtkWidget          *dialog;
1573 	GnmExprEntry       *expression;
1574 	GtkWidget          *min;
1575 	GtkWidget          *max;
1576 	GtkWidget          *inc;
1577 	GtkWidget          *page;
1578 	GtkWidget          *direction_h;
1579 	GtkWidget          *direction_v;
1580 
1581 	char               *undo_label;
1582 	GtkWidget          *old_focus;
1583 
1584 	WBCGtk *wbcg;
1585 	SheetWidgetAdjustment *swa;
1586 	Sheet		   *sheet;
1587 } AdjustmentConfigState;
1588 
1589 static void
cb_adjustment_set_focus(G_GNUC_UNUSED GtkWidget * window,GtkWidget * focus_widget,AdjustmentConfigState * state)1590 cb_adjustment_set_focus (G_GNUC_UNUSED GtkWidget *window, GtkWidget *focus_widget,
1591 			 AdjustmentConfigState *state)
1592 {
1593 	GtkWidget *ofp;
1594 
1595 	/* Note:  half of the set-focus action is handle by the default
1596 	 *        callback installed by wbc_gtk_attach_guru. */
1597 
1598 	ofp = state->old_focus
1599 		? gtk_widget_get_parent (state->old_focus)
1600 		: NULL;
1601 	/* Force an update of the content in case it needs tweaking (eg make it
1602 	 * absolute) */
1603 	if (ofp && GNM_EXPR_ENTRY_IS (ofp)) {
1604 		GnmParsePos  pp;
1605 		GnmExprTop const *texpr = gnm_expr_entry_parse (
1606 			GNM_EXPR_ENTRY (ofp),
1607 			parse_pos_init_sheet (&pp, state->sheet),
1608 			NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
1609 		if (texpr != NULL)
1610 			gnm_expr_top_unref (texpr);
1611 	}
1612 	state->old_focus = focus_widget;
1613 }
1614 
1615 static void
cb_adjustment_config_destroy(AdjustmentConfigState * state)1616 cb_adjustment_config_destroy (AdjustmentConfigState *state)
1617 {
1618 	g_return_if_fail (state != NULL);
1619 
1620 	g_free (state->undo_label);
1621 
1622 	state->dialog = NULL;
1623 	g_free (state);
1624 }
1625 
1626 static void
cb_adjustment_config_ok_clicked(G_GNUC_UNUSED GtkWidget * button,AdjustmentConfigState * state)1627 cb_adjustment_config_ok_clicked (G_GNUC_UNUSED GtkWidget *button, AdjustmentConfigState *state)
1628 {
1629 	SheetObject *so = GNM_SO (state->swa);
1630 	GnmParsePos pp;
1631 	GnmExprTop const *texpr = gnm_expr_entry_parse (state->expression,
1632 		parse_pos_init_sheet (&pp, so->sheet),
1633 		NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
1634 	gboolean horizontal;
1635 
1636 	horizontal = state->direction_h
1637 		? gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (state->direction_h))
1638 		: state->swa->horizontal;
1639 
1640 	cmd_so_set_adjustment (GNM_WBC (state->wbcg), so,
1641 			       texpr,
1642 			       horizontal,
1643 			       gtk_spin_button_get_value_as_int (
1644 				       GTK_SPIN_BUTTON (state->min)),
1645 			       gtk_spin_button_get_value_as_int (
1646 				       GTK_SPIN_BUTTON (state->max)),
1647 			       gtk_spin_button_get_value_as_int (
1648 				       GTK_SPIN_BUTTON (state->inc)),
1649 			       gtk_spin_button_get_value_as_int (
1650 				       GTK_SPIN_BUTTON (state->page)),
1651 			       state->undo_label);
1652 
1653 	gtk_widget_destroy (state->dialog);
1654 }
1655 
1656 static void
cb_adjustment_config_cancel_clicked(G_GNUC_UNUSED GtkWidget * button,AdjustmentConfigState * state)1657 cb_adjustment_config_cancel_clicked (G_GNUC_UNUSED GtkWidget *button, AdjustmentConfigState *state)
1658 {
1659 	gtk_widget_destroy (state->dialog);
1660 }
1661 
1662 static void
sheet_widget_adjustment_user_config_impl(SheetObject * so,SheetControl * sc,char const * undo_label,char const * dialog_label)1663 sheet_widget_adjustment_user_config_impl (SheetObject *so, SheetControl *sc, char const *undo_label, char const *dialog_label)
1664 {
1665 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
1666 	SheetWidgetAdjustmentClass *swa_class = SWA_CLASS (swa);
1667 	WBCGtk *wbcg = scg_wbcg (GNM_SCG (sc));
1668 	AdjustmentConfigState *state;
1669 	GtkWidget *grid;
1670 	GtkBuilder *gui;
1671 	gboolean has_directions = swa_class->has_orientation;
1672 
1673 	/* Only pop up one copy per workbook */
1674 	if (gnm_dialog_raise_if_exists (wbcg, SHEET_OBJECT_CONFIG_KEY))
1675 		return;
1676 
1677 	gui = gnm_gtk_builder_load ("res:ui/so-scrollbar.ui", NULL, GO_CMD_CONTEXT (wbcg));
1678 	if (!gui)
1679 		return;
1680 	state = g_new (AdjustmentConfigState, 1);
1681 	state->swa = swa;
1682 	state->wbcg = wbcg;
1683 	state->sheet = sc_sheet	(sc);
1684 	state->old_focus = NULL;
1685 	state->undo_label = (undo_label == NULL) ? NULL : g_strdup (undo_label);
1686 	state->dialog = go_gtk_builder_get_widget (gui, "SO-Scrollbar");
1687 
1688 	if (dialog_label != NULL)
1689 		gtk_window_set_title (GTK_WINDOW (state->dialog), dialog_label);
1690 
1691 	grid = go_gtk_builder_get_widget (gui, "main-grid");
1692 
1693 	state->expression = gnm_expr_entry_new (wbcg, TRUE);
1694 	gnm_expr_entry_set_flags (state->expression,
1695 		GNM_EE_FORCE_ABS_REF | GNM_EE_SHEET_OPTIONAL | GNM_EE_SINGLE_RANGE,
1696 		GNM_EE_MASK);
1697 	gnm_expr_entry_load_from_dep (state->expression, &swa->dep);
1698 	go_atk_setup_label (go_gtk_builder_get_widget (gui, "label_linkto"),
1699 			     GTK_WIDGET (state->expression));
1700 	gtk_grid_attach (GTK_GRID (grid),
1701 	                 GTK_WIDGET (state->expression), 1, 0, 2, 1);
1702 	gtk_widget_show (GTK_WIDGET (state->expression));
1703 
1704 	if (has_directions) {
1705 		state->direction_h = go_gtk_builder_get_widget (gui, "direction_h");
1706 		state->direction_v = go_gtk_builder_get_widget (gui, "direction_v");
1707 		gtk_toggle_button_set_active
1708 			(GTK_TOGGLE_BUTTON (swa->horizontal
1709 					    ? state->direction_h
1710 					    : state->direction_v),
1711 			 TRUE);
1712 	} else {
1713 		state->direction_h = NULL;
1714 		state->direction_v = NULL;
1715 		gtk_widget_destroy (go_gtk_builder_get_widget (gui, "direction_label"));
1716 		gtk_widget_destroy (go_gtk_builder_get_widget (gui, "direction_h"));
1717 		gtk_widget_destroy (go_gtk_builder_get_widget (gui, "direction_v"));
1718 	}
1719 
1720 	/* TODO : This is silly, no need to be similar to XL here. */
1721 	state->min = go_gtk_builder_get_widget (gui, "spin_min");
1722 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->min),
1723 				   gtk_adjustment_get_lower (swa->adjustment));
1724 	state->max = go_gtk_builder_get_widget (gui, "spin_max");
1725 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->max),
1726 				   gtk_adjustment_get_upper (swa->adjustment));
1727 	state->inc = go_gtk_builder_get_widget (gui, "spin_increment");
1728 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->inc),
1729 				   gtk_adjustment_get_step_increment (swa->adjustment));
1730 	state->page = go_gtk_builder_get_widget (gui, "spin_page");
1731 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->page),
1732 				   gtk_adjustment_get_page_increment (swa->adjustment));
1733 
1734 	gnm_editable_enters (GTK_WINDOW (state->dialog),
1735 				  GTK_WIDGET (state->expression));
1736 	gnm_editable_enters (GTK_WINDOW (state->dialog),
1737 				  GTK_WIDGET (state->min));
1738 	gnm_editable_enters (GTK_WINDOW (state->dialog),
1739 				  GTK_WIDGET (state->max));
1740 	gnm_editable_enters (GTK_WINDOW (state->dialog),
1741 				  GTK_WIDGET (state->inc));
1742 	gnm_editable_enters (GTK_WINDOW (state->dialog),
1743 				  GTK_WIDGET (state->page));
1744 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "ok_button")),
1745 		"clicked",
1746 		G_CALLBACK (cb_adjustment_config_ok_clicked), state);
1747 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "cancel_button")),
1748 		"clicked",
1749 		G_CALLBACK (cb_adjustment_config_cancel_clicked), state);
1750 
1751 	gnm_init_help_button (
1752 		go_gtk_builder_get_widget (gui, "help_button"),
1753 		GNUMERIC_HELP_LINK_SO_ADJUSTMENT);
1754 
1755 	gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
1756 			       SHEET_OBJECT_CONFIG_KEY);
1757 
1758 	wbc_gtk_attach_guru (state->wbcg, state->dialog);
1759 	g_object_set_data_full (G_OBJECT (state->dialog),
1760 		"state", state, (GDestroyNotify) cb_adjustment_config_destroy);
1761 
1762 	/* Note:  half of the set-focus action is handle by the default */
1763 	/*        callback installed by wbc_gtk_attach_guru           */
1764 	g_signal_connect (G_OBJECT (state->dialog), "set-focus",
1765 		G_CALLBACK (cb_adjustment_set_focus), state);
1766 	g_object_unref (gui);
1767 
1768 	gtk_widget_show (state->dialog);
1769 }
1770 
1771 static void
sheet_widget_adjustment_user_config(SheetObject * so,SheetControl * sc)1772 sheet_widget_adjustment_user_config (SheetObject *so, SheetControl *sc)
1773 {
1774 	sheet_widget_adjustment_user_config_impl (so, sc, N_("Configure Adjustment"),
1775 						  N_("Adjustment Properties"));
1776 }
1777 
1778 static gboolean
sheet_widget_adjustment_set_sheet(SheetObject * so,Sheet * sheet)1779 sheet_widget_adjustment_set_sheet (SheetObject *so, Sheet *sheet)
1780 {
1781 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
1782 
1783 	dependent_set_sheet (&swa->dep, sheet);
1784 
1785 	return FALSE;
1786 }
1787 
1788 static void
sheet_widget_adjustment_foreach_dep(SheetObject * so,SheetObjectForeachDepFunc func,gpointer user)1789 sheet_widget_adjustment_foreach_dep (SheetObject *so,
1790 				     SheetObjectForeachDepFunc func,
1791 				     gpointer user)
1792 {
1793 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
1794 	func (&swa->dep, so, user);
1795 }
1796 
1797 static void
sheet_widget_adjustment_write_xml_sax(SheetObject const * so,GsfXMLOut * output,GnmConventions const * convs)1798 sheet_widget_adjustment_write_xml_sax (SheetObject const *so, GsfXMLOut *output,
1799 				       GnmConventions const *convs)
1800 {
1801 	SheetWidgetAdjustment const *swa = GNM_SOW_ADJUSTMENT (so);
1802 	SheetWidgetAdjustmentClass *swa_class = SWA_CLASS (so);
1803 
1804 	go_xml_out_add_double (output, "Min", gtk_adjustment_get_lower (swa->adjustment));
1805 	go_xml_out_add_double (output, "Max", gtk_adjustment_get_upper (swa->adjustment));
1806 	go_xml_out_add_double (output, "Inc", gtk_adjustment_get_step_increment (swa->adjustment));
1807 	go_xml_out_add_double (output, "Page", gtk_adjustment_get_page_increment (swa->adjustment));
1808 	go_xml_out_add_double (output, "Value", gtk_adjustment_get_value (swa->adjustment));
1809 
1810 	if (swa_class->has_orientation)
1811 		gsf_xml_out_add_bool (output, "Horizontal", swa->horizontal);
1812 
1813 	sax_write_dep (output, &swa->dep, "Input", convs);
1814 }
1815 
1816 static void
sheet_widget_adjustment_prep_sax_parser(SheetObject * so,GsfXMLIn * xin,xmlChar const ** attrs,GnmConventions const * convs)1817 sheet_widget_adjustment_prep_sax_parser (SheetObject *so, GsfXMLIn *xin,
1818 					 xmlChar const **attrs,
1819 					 GnmConventions const *convs)
1820 {
1821 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
1822 	SheetWidgetAdjustmentClass *swa_class = SWA_CLASS (so);
1823 	swa->horizontal = FALSE;
1824 
1825 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
1826 		double tmp;
1827 		gboolean b;
1828 
1829 		if (gnm_xml_attr_double (attrs, "Min", &tmp))
1830 			gtk_adjustment_set_lower (swa->adjustment, tmp);
1831 		else if (gnm_xml_attr_double (attrs, "Max", &tmp))
1832 			gtk_adjustment_set_upper (swa->adjustment, tmp);  /* allow scrolling to max */
1833 		else if (gnm_xml_attr_double (attrs, "Inc", &tmp))
1834 			gtk_adjustment_set_step_increment (swa->adjustment, tmp);
1835 		else if (gnm_xml_attr_double (attrs, "Page", &tmp))
1836 			gtk_adjustment_set_page_increment (swa->adjustment, tmp);
1837 		else if (gnm_xml_attr_double (attrs, "Value", &tmp))
1838 			gtk_adjustment_set_value (swa->adjustment, tmp);
1839 		else if (sax_read_dep (attrs, "Input", &swa->dep, xin, convs))
1840 			;
1841 		else if (swa_class->has_orientation &&
1842 			 gnm_xml_attr_bool (attrs, "Horizontal", &b))
1843 			swa->horizontal = b;
1844 	}
1845 
1846 	swa->dep.flags = adjustment_get_dep_type ();
1847 }
1848 
1849 void
sheet_widget_adjustment_set_details(SheetObject * so,GnmExprTop const * tlink,int value,int min,int max,int inc,int page)1850 sheet_widget_adjustment_set_details (SheetObject *so, GnmExprTop const *tlink,
1851 				     int value, int min, int max,
1852 				     int inc, int page)
1853 {
1854 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
1855 	double page_size;
1856 
1857 	g_return_if_fail (swa != NULL);
1858 
1859 	dependent_set_expr (&swa->dep, tlink);
1860 	if (tlink && swa->dep.sheet)
1861 		dependent_link (&swa->dep);
1862 
1863 	page_size = gtk_adjustment_get_page_size (swa->adjustment); /* ??? */
1864 	gtk_adjustment_configure (swa->adjustment,
1865 				  value, min, max, inc, page, page_size);
1866 }
1867 
1868 static GtkWidget *
sheet_widget_adjustment_create_widget(G_GNUC_UNUSED SheetObjectWidget * sow)1869 sheet_widget_adjustment_create_widget (G_GNUC_UNUSED SheetObjectWidget *sow)
1870 {
1871 	g_assert_not_reached ();
1872 	return NULL;
1873 }
1874 
1875 SOW_MAKE_TYPE (adjustment, Adjustment,
1876 	       sheet_widget_adjustment_user_config,
1877 	       sheet_widget_adjustment_set_sheet,
1878 	       so_clear_sheet,
1879 	       sheet_widget_adjustment_foreach_dep,
1880 	       sheet_widget_adjustment_copy,
1881 	       sheet_widget_adjustment_write_xml_sax,
1882 	       sheet_widget_adjustment_prep_sax_parser,
1883 	       sheet_widget_adjustment_get_property,
1884 	       sheet_widget_adjustment_set_property,
1885 	       sheet_widget_draw_cairo,
1886 	       {
1887 		       ((SheetWidgetAdjustmentClass *) object_class)->has_orientation = TRUE;
1888 		       g_object_class_install_property
1889 			       (object_class, SWA_PROP_HORIZONTAL,
1890 				g_param_spec_boolean ("horizontal", NULL, NULL,
1891 						      FALSE,
1892 						      GSF_PARAM_STATIC | G_PARAM_READWRITE));
1893 	       })
1894 
1895 /****************************************************************************/
1896 
1897 #define GNM_SOW_SCROLLBAR(obj)	(G_TYPE_CHECK_INSTANCE_CAST((obj), GNM_SOW_SCROLLBAR_TYPE, SheetWidgetScrollbar))
1898 #define DEP_TO_SCROLLBAR(d_ptr)		(SheetWidgetScrollbar *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetScrollbar, dep))
1899 
1900 typedef SheetWidgetAdjustment  SheetWidgetScrollbar;
1901 typedef SheetWidgetAdjustmentClass SheetWidgetScrollbarClass;
1902 
1903 static GtkWidget *
sheet_widget_scrollbar_create_widget(SheetObjectWidget * sow)1904 sheet_widget_scrollbar_create_widget (SheetObjectWidget *sow)
1905 {
1906 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (sow);
1907 	GtkWidget *bar;
1908 
1909 	swa->being_updated = TRUE;
1910 	bar = gtk_scrollbar_new (swa->horizontal? GTK_ORIENTATION_HORIZONTAL: GTK_ORIENTATION_VERTICAL, swa->adjustment);
1911 	gtk_widget_set_can_focus (bar, FALSE);
1912 	g_signal_connect (G_OBJECT (bar),
1913 		"value_changed",
1914 		G_CALLBACK (cb_adjustment_widget_value_changed), swa);
1915 	g_signal_connect (G_OBJECT (bar), "destroy",
1916 	                  G_CALLBACK (cb_range_destroyed), swa);
1917 	swa->being_updated = FALSE;
1918 
1919 	return bar;
1920 }
1921 
1922 static void
sheet_widget_scrollbar_user_config(SheetObject * so,SheetControl * sc)1923 sheet_widget_scrollbar_user_config (SheetObject *so, SheetControl *sc)
1924 {
1925 	sheet_widget_adjustment_user_config_impl (so, sc, N_("Configure Scrollbar"),
1926 						  N_("Scrollbar Properties"));
1927 }
1928 
1929 static void sheet_widget_slider_horizontal_draw_cairo
1930 (SheetObject const *so, cairo_t *cr, double width, double height);
1931 
1932 static void
sheet_widget_scrollbar_horizontal_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)1933 sheet_widget_scrollbar_horizontal_draw_cairo (SheetObject const *so, cairo_t *cr,
1934 					      double width, double height)
1935 {
1936 	cairo_save (cr);
1937 	cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
1938 
1939 	cairo_new_path (cr);
1940 	cairo_move_to (cr, 0., height/2);
1941 	cairo_rel_line_to (cr, 15., 7.5);
1942 	cairo_rel_line_to (cr, 0, -15);
1943 	cairo_close_path (cr);
1944 	cairo_fill (cr);
1945 
1946 	cairo_new_path (cr);
1947 	cairo_move_to (cr, width, height/2);
1948 	cairo_rel_line_to (cr, -15., 7.5);
1949 	cairo_rel_line_to (cr, 0, -15);
1950 	cairo_close_path (cr);
1951 	cairo_fill (cr);
1952 
1953 	cairo_new_path (cr);
1954 	cairo_translate (cr, 15., 0.);
1955 	sheet_widget_slider_horizontal_draw_cairo (so, cr, width - 30, height);
1956 	cairo_restore (cr);
1957 }
1958 
1959 static void
sheet_widget_scrollbar_vertical_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)1960 sheet_widget_scrollbar_vertical_draw_cairo (SheetObject const *so, cairo_t *cr,
1961 					    double width, double height)
1962 {
1963 	cairo_save (cr);
1964 	cairo_rotate (cr, M_PI/2);
1965 	cairo_translate (cr, 0., -width);
1966 	sheet_widget_scrollbar_horizontal_draw_cairo (so, cr, height, width);
1967 	cairo_restore (cr);
1968 }
1969 
1970 static void
sheet_widget_scrollbar_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)1971 sheet_widget_scrollbar_draw_cairo (SheetObject const *so, cairo_t *cr,
1972 				   double width, double height)
1973 {
1974 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
1975 	if (swa->horizontal)
1976 		sheet_widget_scrollbar_horizontal_draw_cairo
1977 			(so, cr, width, height);
1978 	else
1979 		sheet_widget_scrollbar_vertical_draw_cairo
1980 			(so, cr, width, height);
1981 }
1982 
1983 static void
sheet_widget_scrollbar_class_init(SheetObjectWidgetClass * sow_class)1984 sheet_widget_scrollbar_class_init (SheetObjectWidgetClass *sow_class)
1985 {
1986 	SheetWidgetAdjustmentClass *swa_class = (SheetWidgetAdjustmentClass *)sow_class;
1987 	SheetObjectClass *so_class = GNM_SO_CLASS (sow_class);
1988 
1989         sow_class->create_widget = &sheet_widget_scrollbar_create_widget;
1990 	so_class->user_config = &sheet_widget_scrollbar_user_config;
1991 	so_class->draw_cairo = &sheet_widget_scrollbar_draw_cairo;
1992 	swa_class->type = GTK_TYPE_SCROLLBAR;
1993 }
1994 
1995 GSF_CLASS (SheetWidgetScrollbar, sheet_widget_scrollbar,
1996 	   &sheet_widget_scrollbar_class_init, NULL,
1997 	   GNM_SOW_ADJUSTMENT_TYPE)
1998 
1999 /****************************************************************************/
2000 
2001 #define GNM_SOW_SPIN_BUTTON(obj)	(G_TYPE_CHECK_INSTANCE_CAST((obj), GNM_SOW_SPIN_BUTTON_TYPE, SheetWidgetSpinbutton))
2002 #define DEP_TO_SPINBUTTON(d_ptr)		(SheetWidgetSpinbutton *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetSpinbutton, dep))
2003 
2004 typedef SheetWidgetAdjustment		SheetWidgetSpinbutton;
2005 typedef SheetWidgetAdjustmentClass	SheetWidgetSpinbuttonClass;
2006 
2007 static GtkWidget *
sheet_widget_spinbutton_create_widget(SheetObjectWidget * sow)2008 sheet_widget_spinbutton_create_widget (SheetObjectWidget *sow)
2009 {
2010 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (sow);
2011 	GtkWidget *spinbutton;
2012 
2013 	swa->being_updated = TRUE;
2014 	spinbutton = gtk_spin_button_new
2015 		(swa->adjustment,
2016 		 gtk_adjustment_get_step_increment (swa->adjustment),
2017 		 0);
2018 	gtk_widget_set_can_focus (spinbutton, FALSE);
2019 	g_signal_connect (G_OBJECT (spinbutton),
2020 		"value_changed",
2021 		G_CALLBACK (cb_adjustment_widget_value_changed), swa);
2022 	g_signal_connect (G_OBJECT (spinbutton), "destroy",
2023 	                  G_CALLBACK (cb_range_destroyed), swa);
2024 	swa->being_updated = FALSE;
2025 	return spinbutton;
2026 }
2027 
2028 static void
sheet_widget_spinbutton_user_config(SheetObject * so,SheetControl * sc)2029 sheet_widget_spinbutton_user_config (SheetObject *so, SheetControl *sc)
2030 {
2031            sheet_widget_adjustment_user_config_impl (so, sc, N_("Configure Spinbutton"),
2032 						     N_("Spinbutton Properties"));
2033 }
2034 
2035 static void
sheet_widget_spinbutton_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)2036 sheet_widget_spinbutton_draw_cairo (SheetObject const *so, cairo_t *cr,
2037 				    double width, double height)
2038 {
2039 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
2040 	GtkAdjustment *adjustment = swa->adjustment;
2041 	double value = gtk_adjustment_get_value (adjustment);
2042 	int ivalue = (int) value;
2043 	double halfheight = height/2;
2044 	char *str;
2045 
2046 	cairo_save (cr);
2047 	cairo_set_line_width (cr, 0.5);
2048 	cairo_set_source_rgb(cr, 0, 0, 0);
2049 
2050 	cairo_new_path (cr);
2051 	cairo_move_to (cr, 0, 0);
2052 	cairo_line_to (cr, width, 0);
2053 	cairo_line_to (cr, width, height);
2054 	cairo_line_to (cr, 0, height);
2055 	cairo_close_path (cr);
2056 	cairo_stroke (cr);
2057 
2058 	cairo_new_path (cr);
2059 	cairo_move_to (cr, width - 10, 0);
2060 	cairo_rel_line_to (cr, 0, height);
2061 	cairo_stroke (cr);
2062 
2063 	cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
2064 
2065 	cairo_new_path (cr);
2066 	cairo_move_to (cr, width - 5, 3);
2067 	cairo_rel_line_to (cr, 3, 3);
2068 	cairo_rel_line_to (cr, -6, 0);
2069 	cairo_close_path (cr);
2070 	cairo_fill (cr);
2071 
2072 	cairo_new_path (cr);
2073 	cairo_move_to (cr, width - 5, height - 3);
2074 	cairo_rel_line_to (cr, 3, -3);
2075 	cairo_rel_line_to (cr, -6, 0);
2076 	cairo_close_path (cr);
2077 	cairo_fill (cr);
2078 
2079 	str = g_strdup_printf ("%i", ivalue);
2080 	cairo_set_source_rgb(cr, 0, 0, 0);
2081 	cairo_move_to (cr, 4., halfheight);
2082 	draw_cairo_text (cr, str, NULL, NULL, TRUE, FALSE, TRUE, 0, FALSE);
2083 	g_free (str);
2084 
2085 	cairo_new_path (cr);
2086 	cairo_restore (cr);
2087 }
2088 
2089 static void
sheet_widget_spinbutton_class_init(SheetObjectWidgetClass * sow_class)2090 sheet_widget_spinbutton_class_init (SheetObjectWidgetClass *sow_class)
2091 {
2092 	SheetWidgetAdjustmentClass *swa_class = (SheetWidgetAdjustmentClass *)sow_class;
2093 	SheetObjectClass *so_class = GNM_SO_CLASS (sow_class);
2094 
2095         sow_class->create_widget = &sheet_widget_spinbutton_create_widget;
2096 	so_class->user_config = &sheet_widget_spinbutton_user_config;
2097 	so_class->draw_cairo = &sheet_widget_spinbutton_draw_cairo;
2098 
2099 	swa_class->type = GTK_TYPE_SPIN_BUTTON;
2100 	swa_class->has_orientation = FALSE;
2101 }
2102 
2103 GSF_CLASS (SheetWidgetSpinbutton, sheet_widget_spinbutton,
2104 	   &sheet_widget_spinbutton_class_init, NULL,
2105 	   GNM_SOW_ADJUSTMENT_TYPE)
2106 
2107 /****************************************************************************/
2108 
2109 #define GNM_SOW_SLIDER(obj)	(G_TYPE_CHECK_INSTANCE_CAST((obj), GNM_SOW_SLIDER_TYPE, SheetWidgetSlider))
2110 #define DEP_TO_SLIDER(d_ptr)		(SheetWidgetSlider *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetSlider, dep))
2111 
2112 typedef SheetWidgetAdjustment		SheetWidgetSlider;
2113 typedef SheetWidgetAdjustmentClass	SheetWidgetSliderClass;
2114 
2115 static GtkWidget *
sheet_widget_slider_create_widget(SheetObjectWidget * sow)2116 sheet_widget_slider_create_widget (SheetObjectWidget *sow)
2117 {
2118 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (sow);
2119 	GtkWidget *slider;
2120 
2121 	swa->being_updated = TRUE;
2122 	slider = gtk_scale_new (swa->horizontal? GTK_ORIENTATION_HORIZONTAL: GTK_ORIENTATION_VERTICAL, swa->adjustment);
2123 	gtk_scale_set_draw_value (GTK_SCALE (slider), FALSE);
2124 	gtk_widget_set_can_focus (slider, FALSE);
2125 	g_signal_connect (G_OBJECT (slider),
2126 		"value_changed",
2127 		G_CALLBACK (cb_adjustment_widget_value_changed), swa);
2128 	g_signal_connect (G_OBJECT (slider), "destroy",
2129 	                  G_CALLBACK (cb_range_destroyed), swa);
2130 	swa->being_updated = FALSE;
2131 
2132 	return slider;
2133 }
2134 
2135 static void
sheet_widget_slider_user_config(SheetObject * so,SheetControl * sc)2136 sheet_widget_slider_user_config (SheetObject *so, SheetControl *sc)
2137 {
2138            sheet_widget_adjustment_user_config_impl (so, sc, N_("Configure Slider"),
2139 			   N_("Slider Properties"));
2140 }
2141 
2142 static void
sheet_widget_slider_horizontal_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)2143 sheet_widget_slider_horizontal_draw_cairo (SheetObject const *so, cairo_t *cr,
2144 					   double width, double height)
2145 {
2146 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
2147 	GtkAdjustment *adjustment = swa->adjustment;
2148 	double value = gtk_adjustment_get_value (adjustment);
2149 	double upper = gtk_adjustment_get_upper (adjustment);
2150 	double lower = gtk_adjustment_get_lower (adjustment);
2151 	double fraction = (upper == lower) ? 0.0 : (value - lower)/(upper- lower);
2152 
2153 	cairo_save (cr);
2154 	cairo_set_line_width (cr, 5);
2155 	cairo_set_source_rgb(cr, 0.8, 0.8, 0.8);
2156 	cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
2157 
2158 	cairo_new_path (cr);
2159 	cairo_move_to (cr, 4, height/2);
2160 	cairo_rel_line_to (cr, width - 8., 0);
2161 	cairo_stroke (cr);
2162 
2163 	cairo_set_line_width (cr, 15);
2164 	cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
2165 	cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
2166 
2167 	cairo_new_path (cr);
2168 	cairo_move_to (cr, fraction * (width - 8. - 1. - 5. - 5. + 2.5 + 2.5)
2169 		       - 10. + 10. + 4. + 5. - 2.5, height/2);
2170 	cairo_rel_line_to (cr, 1, 0);
2171 	cairo_stroke (cr);
2172 
2173 	cairo_new_path (cr);
2174 	cairo_restore (cr);
2175 }
2176 
2177 static void
sheet_widget_slider_vertical_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)2178 sheet_widget_slider_vertical_draw_cairo (SheetObject const *so, cairo_t *cr,
2179 					 double width, double height)
2180 {
2181 	cairo_save (cr);
2182 	cairo_rotate (cr, M_PI/2);
2183 	cairo_translate (cr, 0., -width);
2184 	sheet_widget_slider_horizontal_draw_cairo (so, cr, height, width);
2185 	cairo_restore (cr);
2186 }
2187 
2188 static void
sheet_widget_slider_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)2189 sheet_widget_slider_draw_cairo (SheetObject const *so, cairo_t *cr,
2190 				double width, double height)
2191 {
2192 	SheetWidgetAdjustment *swa = GNM_SOW_ADJUSTMENT (so);
2193 	if (swa->horizontal)
2194 		sheet_widget_slider_horizontal_draw_cairo (so, cr, width, height);
2195 	else
2196 		sheet_widget_slider_vertical_draw_cairo (so, cr, width, height);
2197 }
2198 
2199 static void
sheet_widget_slider_class_init(SheetObjectWidgetClass * sow_class)2200 sheet_widget_slider_class_init (SheetObjectWidgetClass *sow_class)
2201 {
2202 	SheetWidgetAdjustmentClass *swa_class = (SheetWidgetAdjustmentClass *)sow_class;
2203 	SheetObjectClass *so_class = GNM_SO_CLASS (sow_class);
2204 
2205         sow_class->create_widget = &sheet_widget_slider_create_widget;
2206 	so_class->user_config = &sheet_widget_slider_user_config;
2207 	so_class->draw_cairo = &sheet_widget_slider_draw_cairo;
2208 
2209 	swa_class->type = GTK_TYPE_SCALE;
2210 }
2211 
2212 GSF_CLASS (SheetWidgetSlider, sheet_widget_slider,
2213 	   &sheet_widget_slider_class_init, NULL,
2214 	   GNM_SOW_ADJUSTMENT_TYPE)
2215 
2216 /****************************************************************************/
2217 
2218 #define GNM_SOW_CHECKBOX(obj)	(G_TYPE_CHECK_INSTANCE_CAST((obj), GNM_SOW_CHECKBOX_TYPE, SheetWidgetCheckbox))
2219 #define DEP_TO_CHECKBOX(d_ptr)		(SheetWidgetCheckbox *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetCheckbox, dep))
2220 
2221 typedef struct {
2222 	SheetObjectWidget	sow;
2223 
2224 	GnmDependent	 dep;
2225 	char		*label;
2226 	gboolean	 value;
2227 	gboolean	 being_updated;
2228 } SheetWidgetCheckbox;
2229 typedef SheetObjectWidgetClass SheetWidgetCheckboxClass;
2230 
2231 enum {
2232 	SOC_PROP_0 = 0,
2233 	SOC_PROP_ACTIVE,
2234 	SOC_PROP_TEXT,
2235 	SOC_PROP_MARKUP
2236 };
2237 
2238 static void
sheet_widget_checkbox_set_active(SheetWidgetCheckbox * swc)2239 sheet_widget_checkbox_set_active (SheetWidgetCheckbox *swc)
2240 {
2241 	GList *ptr;
2242 
2243 	swc->being_updated = TRUE;
2244 
2245 	for (ptr = swc->sow.so.realized_list; ptr != NULL ; ptr = ptr->next) {
2246 		SheetObjectView *view = ptr->data;
2247 		GocWidget *item = get_goc_widget (view);
2248 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item->widget),
2249 					      swc->value);
2250 	}
2251 
2252 	g_object_notify (G_OBJECT (swc), "active");
2253 
2254 	swc->being_updated = FALSE;
2255 }
2256 
2257 static void
sheet_widget_checkbox_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)2258 sheet_widget_checkbox_get_property (GObject *obj, guint param_id,
2259 				    GValue *value, GParamSpec *pspec)
2260 {
2261 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (obj);
2262 
2263 	switch (param_id) {
2264 	case SOC_PROP_ACTIVE:
2265 		g_value_set_boolean (value, swc->value);
2266 		break;
2267 	case SOC_PROP_TEXT:
2268 		g_value_set_string (value, swc->label);
2269 		break;
2270 	case SOC_PROP_MARKUP:
2271 		g_value_set_boxed (value, NULL); /* swc->markup */
2272 		break;
2273 	default:
2274 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
2275 		break;
2276 	}
2277 }
2278 
2279 static void
sheet_widget_checkbox_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)2280 sheet_widget_checkbox_set_property (GObject *obj, guint param_id,
2281 				    GValue const *value, GParamSpec *pspec)
2282 {
2283 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (obj);
2284 
2285 	switch (param_id) {
2286 	case SOC_PROP_ACTIVE:
2287 		swc->value = g_value_get_boolean (value);
2288 		sheet_widget_checkbox_set_active (swc);
2289 		break;
2290 	case SOC_PROP_TEXT:
2291 		sheet_widget_checkbox_set_label (GNM_SO (swc),
2292 						 g_value_get_string (value));
2293 		break;
2294 	case SOC_PROP_MARKUP:
2295 #if 0
2296 		sheet_widget_checkbox_set_markup (GNM_SO (swc),
2297 						g_value_peek_pointer (value));
2298 #endif
2299 		break;
2300 	default:
2301 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
2302 		return;
2303 	}
2304 }
2305 
2306 static void
checkbox_eval(GnmDependent * dep)2307 checkbox_eval (GnmDependent *dep)
2308 {
2309 	GnmValue *v;
2310 	GnmEvalPos pos;
2311 	gboolean err, result;
2312 
2313 	v = gnm_expr_top_eval (dep->texpr, eval_pos_init_dep (&pos, dep),
2314 			       GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
2315 	result = value_get_as_bool (v, &err);
2316 	value_release (v);
2317 	if (!err) {
2318 		SheetWidgetCheckbox *swc = DEP_TO_CHECKBOX(dep);
2319 
2320 		swc->value = result;
2321 		sheet_widget_checkbox_set_active (swc);
2322 	}
2323 }
2324 
2325 static void
checkbox_debug_name(GnmDependent const * dep,GString * target)2326 checkbox_debug_name (GnmDependent const *dep, GString *target)
2327 {
2328 	g_string_append_printf (target, "Checkbox%p", (void *)dep);
2329 }
2330 
2331 static DEPENDENT_MAKE_TYPE (checkbox, .eval = checkbox_eval, .debug_name = checkbox_debug_name)
2332 
2333 static void
sheet_widget_checkbox_init_full(SheetWidgetCheckbox * swc,GnmCellRef const * ref,char const * label)2334 sheet_widget_checkbox_init_full (SheetWidgetCheckbox *swc,
2335 				 GnmCellRef const *ref, char const *label)
2336 {
2337 	static int counter = 0;
2338 
2339 	g_return_if_fail (swc != NULL);
2340 
2341 	swc->label = label ? g_strdup (label) : g_strdup_printf (_("CheckBox %d"), ++counter);
2342 	swc->being_updated = FALSE;
2343 	swc->value = FALSE;
2344 	swc->dep.sheet = NULL;
2345 	swc->dep.flags = checkbox_get_dep_type ();
2346 	swc->dep.texpr = (ref != NULL)
2347 		? gnm_expr_top_new (gnm_expr_new_cellref (ref))
2348 		: NULL;
2349 }
2350 
2351 static void
sheet_widget_checkbox_init(SheetWidgetCheckbox * swc)2352 sheet_widget_checkbox_init (SheetWidgetCheckbox *swc)
2353 {
2354 	sheet_widget_checkbox_init_full (swc, NULL, NULL);
2355 }
2356 
2357 static void
sheet_widget_checkbox_finalize(GObject * obj)2358 sheet_widget_checkbox_finalize (GObject *obj)
2359 {
2360 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (obj);
2361 
2362 	g_return_if_fail (swc != NULL);
2363 
2364 	g_free (swc->label);
2365 	swc->label = NULL;
2366 
2367 	dependent_set_expr (&swc->dep, NULL);
2368 
2369 	sheet_object_widget_class->finalize (obj);
2370 }
2371 
2372 static void
cb_checkbox_toggled(GtkToggleButton * button,SheetWidgetCheckbox * swc)2373 cb_checkbox_toggled (GtkToggleButton *button, SheetWidgetCheckbox *swc)
2374 {
2375 	GnmCellRef ref;
2376 
2377 	if (swc->being_updated)
2378 		return;
2379 	swc->value = gtk_toggle_button_get_active (button);
2380 	sheet_widget_checkbox_set_active (swc);
2381 
2382 	if (so_get_ref (GNM_SO (swc), &ref, TRUE) != NULL) {
2383 		gboolean new_val = gtk_toggle_button_get_active (button);
2384 		cmd_so_set_value (widget_wbc (GTK_WIDGET (button)),
2385 				  /* FIXME: This text sucks:  */
2386 				  _("Clicking checkbox"),
2387 				  &ref, value_new_bool (new_val),
2388 				  sheet_object_get_sheet (GNM_SO (swc)));
2389 	}
2390 }
2391 
2392 static GtkWidget *
sheet_widget_checkbox_create_widget(SheetObjectWidget * sow)2393 sheet_widget_checkbox_create_widget (SheetObjectWidget *sow)
2394 {
2395 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (sow);
2396 	GtkWidget *button;
2397 
2398 	g_return_val_if_fail (swc != NULL, NULL);
2399 
2400 	button = gtk_check_button_new_with_label (swc->label);
2401 	gtk_widget_set_can_focus (button, FALSE);
2402 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), swc->value);
2403 	g_signal_connect (G_OBJECT (button),
2404 			  "toggled",
2405 			  G_CALLBACK (cb_checkbox_toggled), swc);
2406 
2407 	return button;
2408 }
2409 
2410 static void
sheet_widget_checkbox_copy(SheetObject * dst,SheetObject const * src)2411 sheet_widget_checkbox_copy (SheetObject *dst, SheetObject const *src)
2412 {
2413 	SheetWidgetCheckbox const *src_swc = GNM_SOW_CHECKBOX (src);
2414 	SheetWidgetCheckbox       *dst_swc = GNM_SOW_CHECKBOX (dst);
2415 	GnmCellRef ref;
2416 	sheet_widget_checkbox_init_full (dst_swc,
2417 					 so_get_ref (src, &ref, FALSE),
2418 					 src_swc->label);
2419 	dst_swc->value = src_swc->value;
2420 }
2421 
2422 typedef struct {
2423 	GtkWidget *dialog;
2424 	GnmExprEntry *expression;
2425 	GtkWidget *label;
2426 
2427 	char *old_label;
2428 	GtkWidget *old_focus;
2429 
2430 	WBCGtk  *wbcg;
2431 	SheetWidgetCheckbox *swc;
2432 	Sheet		    *sheet;
2433 } CheckboxConfigState;
2434 
2435 static void
cb_checkbox_set_focus(G_GNUC_UNUSED GtkWidget * window,GtkWidget * focus_widget,CheckboxConfigState * state)2436 cb_checkbox_set_focus (G_GNUC_UNUSED GtkWidget *window, GtkWidget *focus_widget,
2437 		       CheckboxConfigState *state)
2438 {
2439 	GtkWidget *ofp;
2440 
2441 	/* Note:  half of the set-focus action is handle by the default
2442 	 *        callback installed by wbc_gtk_attach_guru. */
2443 
2444 	ofp = state->old_focus
2445 		? gtk_widget_get_parent (state->old_focus)
2446 		: NULL;
2447 
2448 	/* Force an update of the content in case it needs tweaking (eg make it
2449 	 * absolute) */
2450 	if (ofp && GNM_EXPR_ENTRY_IS (ofp)) {
2451 		GnmParsePos  pp;
2452 		GnmExprTop const *texpr = gnm_expr_entry_parse (
2453 			GNM_EXPR_ENTRY (ofp),
2454 			parse_pos_init_sheet (&pp, state->sheet),
2455 			NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
2456 		if (texpr != NULL)
2457 			gnm_expr_top_unref (texpr);
2458 	}
2459 	state->old_focus = focus_widget;
2460 }
2461 
2462 static void
cb_checkbox_config_destroy(CheckboxConfigState * state)2463 cb_checkbox_config_destroy (CheckboxConfigState *state)
2464 {
2465 	g_return_if_fail (state != NULL);
2466 
2467 	g_free (state->old_label);
2468 	state->old_label = NULL;
2469 	state->dialog = NULL;
2470 	g_free (state);
2471 }
2472 
2473 static void
cb_checkbox_config_ok_clicked(G_GNUC_UNUSED GtkWidget * button,CheckboxConfigState * state)2474 cb_checkbox_config_ok_clicked (G_GNUC_UNUSED GtkWidget *button, CheckboxConfigState *state)
2475 {
2476 	SheetObject *so = GNM_SO (state->swc);
2477 	GnmParsePos  pp;
2478 	GnmExprTop const *texpr = gnm_expr_entry_parse (state->expression,
2479 		parse_pos_init_sheet (&pp, so->sheet),
2480 		NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
2481 	gchar const *text = gtk_entry_get_text(GTK_ENTRY(state->label));
2482 
2483 	cmd_so_set_checkbox (GNM_WBC (state->wbcg), so,
2484 			     texpr, g_strdup (state->old_label), g_strdup (text));
2485 
2486 	gtk_widget_destroy (state->dialog);
2487 }
2488 
2489 static void
cb_checkbox_config_cancel_clicked(G_GNUC_UNUSED GtkWidget * button,CheckboxConfigState * state)2490 cb_checkbox_config_cancel_clicked (G_GNUC_UNUSED GtkWidget *button, CheckboxConfigState *state)
2491 {
2492 	sheet_widget_checkbox_set_label	(GNM_SO (state->swc),
2493 					 state->old_label);
2494 	gtk_widget_destroy (state->dialog);
2495 }
2496 
2497 static void
cb_checkbox_label_changed(GtkEntry * entry,CheckboxConfigState * state)2498 cb_checkbox_label_changed (GtkEntry *entry, CheckboxConfigState *state)
2499 {
2500 	sheet_widget_checkbox_set_label	(GNM_SO (state->swc),
2501 					 gtk_entry_get_text (entry));
2502 }
2503 
2504 static void
sheet_widget_checkbox_user_config(SheetObject * so,SheetControl * sc)2505 sheet_widget_checkbox_user_config (SheetObject *so, SheetControl *sc)
2506 {
2507 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (so);
2508 	WBCGtk  *wbcg = scg_wbcg (GNM_SCG (sc));
2509 	CheckboxConfigState *state;
2510 	GtkWidget *grid;
2511 	GtkBuilder *gui;
2512 
2513 	g_return_if_fail (swc != NULL);
2514 
2515 	/* Only pop up one copy per workbook */
2516 	if (gnm_dialog_raise_if_exists (wbcg, SHEET_OBJECT_CONFIG_KEY))
2517 		return;
2518 
2519 	gui = gnm_gtk_builder_load ("res:ui/so-checkbox.ui", NULL, GO_CMD_CONTEXT (wbcg));
2520 	if (!gui)
2521 		return;
2522 	state = g_new (CheckboxConfigState, 1);
2523 	state->swc = swc;
2524 	state->wbcg = wbcg;
2525 	state->sheet = sc_sheet	(sc);
2526 	state->old_focus = NULL;
2527 	state->old_label = g_strdup (swc->label);
2528 	state->dialog = go_gtk_builder_get_widget (gui, "SO-Checkbox");
2529 
2530 	grid = go_gtk_builder_get_widget (gui, "main-grid");
2531 
2532 	state->expression = gnm_expr_entry_new (wbcg, TRUE);
2533 	gnm_expr_entry_set_flags (state->expression,
2534 		GNM_EE_FORCE_ABS_REF | GNM_EE_SHEET_OPTIONAL | GNM_EE_SINGLE_RANGE,
2535 		GNM_EE_MASK);
2536 	gnm_expr_entry_load_from_dep (state->expression, &swc->dep);
2537 	go_atk_setup_label (go_gtk_builder_get_widget (gui, "label_linkto"),
2538 			     GTK_WIDGET (state->expression));
2539 	gtk_grid_attach (GTK_GRID (grid),
2540 	                 GTK_WIDGET (state->expression), 1, 0, 1, 1);
2541 	gtk_widget_show (GTK_WIDGET (state->expression));
2542 
2543 	state->label = go_gtk_builder_get_widget (gui, "label_entry");
2544 	gtk_entry_set_text (GTK_ENTRY (state->label), swc->label);
2545 	gtk_editable_select_region (GTK_EDITABLE(state->label), 0, -1);
2546 	gnm_editable_enters (GTK_WINDOW (state->dialog),
2547 				  GTK_WIDGET (state->expression));
2548 	gnm_editable_enters (GTK_WINDOW (state->dialog),
2549 				  GTK_WIDGET (state->label));
2550 
2551 	g_signal_connect (G_OBJECT (state->label),
2552 		"changed",
2553 		G_CALLBACK (cb_checkbox_label_changed), state);
2554 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "ok_button")),
2555 		"clicked",
2556 		G_CALLBACK (cb_checkbox_config_ok_clicked), state);
2557 	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "cancel_button")),
2558 		"clicked",
2559 		G_CALLBACK (cb_checkbox_config_cancel_clicked), state);
2560 
2561 	gnm_init_help_button (
2562 		go_gtk_builder_get_widget (gui, "help_button"),
2563 		GNUMERIC_HELP_LINK_SO_CHECKBOX);
2564 
2565 	gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
2566 			       SHEET_OBJECT_CONFIG_KEY);
2567 
2568 	wbc_gtk_attach_guru (state->wbcg, state->dialog);
2569 	g_object_set_data_full (G_OBJECT (state->dialog),
2570 		"state", state, (GDestroyNotify) cb_checkbox_config_destroy);
2571 
2572 	/* Note:  half of the set-focus action is handle by the default */
2573 	/*        callback installed by wbc_gtk_attach_guru */
2574 	g_signal_connect (G_OBJECT (state->dialog), "set-focus",
2575 		G_CALLBACK (cb_checkbox_set_focus), state);
2576 	g_object_unref (gui);
2577 
2578 	gtk_widget_show (state->dialog);
2579 }
2580 
2581 static gboolean
sheet_widget_checkbox_set_sheet(SheetObject * so,Sheet * sheet)2582 sheet_widget_checkbox_set_sheet (SheetObject *so, Sheet *sheet)
2583 {
2584 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (so);
2585 
2586 	dependent_set_sheet (&swc->dep, sheet);
2587 	sheet_widget_checkbox_set_active (swc);
2588 
2589 	return FALSE;
2590 }
2591 
2592 static void
sheet_widget_checkbox_foreach_dep(SheetObject * so,SheetObjectForeachDepFunc func,gpointer user)2593 sheet_widget_checkbox_foreach_dep (SheetObject *so,
2594 				   SheetObjectForeachDepFunc func,
2595 				   gpointer user)
2596 {
2597 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (so);
2598 	func (&swc->dep, so, user);
2599 }
2600 
2601 static void
sheet_widget_checkbox_write_xml_sax(SheetObject const * so,GsfXMLOut * output,GnmConventions const * convs)2602 sheet_widget_checkbox_write_xml_sax (SheetObject const *so, GsfXMLOut *output,
2603 				     GnmConventions const *convs)
2604 {
2605 	SheetWidgetCheckbox const *swc = GNM_SOW_CHECKBOX (so);
2606 	gsf_xml_out_add_cstr (output, "Label", swc->label);
2607 	gsf_xml_out_add_int (output, "Value", swc->value);
2608 	sax_write_dep (output, &swc->dep, "Input", convs);
2609 }
2610 
2611 static void
sheet_widget_checkbox_prep_sax_parser(SheetObject * so,GsfXMLIn * xin,xmlChar const ** attrs,GnmConventions const * convs)2612 sheet_widget_checkbox_prep_sax_parser (SheetObject *so, GsfXMLIn *xin,
2613 				       xmlChar const **attrs,
2614 				       GnmConventions const *convs)
2615 {
2616 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (so);
2617 
2618 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2)
2619 		if (attr_eq (attrs[0], "Label")) {
2620 			g_free (swc->label);
2621 			swc->label = g_strdup (CXML2C (attrs[1]));
2622 		} else if (gnm_xml_attr_int (attrs, "Value", &swc->value))
2623 			; /* ??? */
2624 		else if (sax_read_dep (attrs, "Input", &swc->dep, xin, convs))
2625 			; /* ??? */
2626 }
2627 
2628 void
sheet_widget_checkbox_set_link(SheetObject * so,GnmExprTop const * texpr)2629 sheet_widget_checkbox_set_link (SheetObject *so, GnmExprTop const *texpr)
2630 {
2631 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (so);
2632 	dependent_set_expr (&swc->dep, texpr);
2633 	if (texpr && swc->dep.sheet)
2634 		dependent_link (&swc->dep);
2635 }
2636 
2637 GnmExprTop const *
sheet_widget_checkbox_get_link(SheetObject * so)2638 sheet_widget_checkbox_get_link	 (SheetObject *so)
2639 {
2640 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (so);
2641 	GnmExprTop const *texpr = swc->dep.texpr;
2642 
2643 	if (texpr)
2644 		gnm_expr_top_ref (texpr);
2645 
2646 	return texpr;
2647 }
2648 
2649 
2650 void
sheet_widget_checkbox_set_label(SheetObject * so,char const * str)2651 sheet_widget_checkbox_set_label	(SheetObject *so, char const *str)
2652 {
2653 	GList *list;
2654 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (so);
2655 	char *new_label;
2656 
2657 	if (go_str_compare (str, swc->label) == 0)
2658 		return;
2659 
2660 	new_label = g_strdup (str);
2661 	g_free (swc->label);
2662 	swc->label = new_label;
2663 
2664 	for (list = swc->sow.so.realized_list; list; list = list->next) {
2665 		SheetObjectView *view = list->data;
2666 		GocWidget *item = get_goc_widget (view);
2667 		gtk_button_set_label (GTK_BUTTON (item->widget), swc->label);
2668 	}
2669 }
2670 
2671 static void
sheet_widget_checkbox_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)2672 sheet_widget_checkbox_draw_cairo (SheetObject const *so, cairo_t *cr,
2673 				  double width, double height)
2674 {
2675 	SheetWidgetCheckbox const *swc = GNM_SOW_CHECKBOX (so);
2676 	double halfheight = height/2;
2677 	double dx = 8., dxh, pm;
2678 	int pw, ph;
2679 
2680 	pm = MIN (height - 2, width - 12);
2681 	if (dx > pm)
2682 		dx = MAX (pm, 3);
2683 	dxh = dx/2;
2684 
2685 	cairo_save (cr);
2686 	cairo_set_line_width (cr, 0.5);
2687 	cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
2688 
2689 	cairo_new_path (cr);
2690 	cairo_move_to (cr, dxh, halfheight - dxh);
2691 	cairo_rel_line_to (cr, 0, dx);
2692 	cairo_rel_line_to (cr, dx, 0);
2693 	cairo_rel_line_to (cr, 0., -dx);
2694 	cairo_rel_line_to (cr, -dx, 0.);
2695 	cairo_close_path (cr);
2696 	cairo_fill_preserve (cr);
2697 	cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
2698 	cairo_stroke (cr);
2699 
2700 	if (swc->value) {
2701 		cairo_new_path (cr);
2702 		cairo_move_to (cr, dxh, halfheight - dxh);
2703 		cairo_rel_line_to (cr, dx, dx);
2704 		cairo_rel_line_to (cr, -dx, 0.);
2705 		cairo_rel_line_to (cr, dx, -dx);
2706 		cairo_rel_line_to (cr, -dx, 0.);
2707 		cairo_close_path (cr);
2708 		cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
2709 		cairo_stroke (cr);
2710 	}
2711 
2712 	cairo_move_to (cr, 2 * dx, halfheight);
2713 
2714 	pw = width - 2 * dx;
2715 	ph = height;
2716 
2717 	draw_cairo_text (cr, swc->label, &pw, &ph, TRUE, FALSE, TRUE, 0, TRUE);
2718 
2719 	cairo_new_path (cr);
2720 	cairo_restore (cr);
2721 }
2722 
2723 
2724 SOW_MAKE_TYPE (checkbox, Checkbox,
2725 	       sheet_widget_checkbox_user_config,
2726 	       sheet_widget_checkbox_set_sheet,
2727 	       so_clear_sheet,
2728 	       sheet_widget_checkbox_foreach_dep,
2729 	       sheet_widget_checkbox_copy,
2730 	       sheet_widget_checkbox_write_xml_sax,
2731 	       sheet_widget_checkbox_prep_sax_parser,
2732 	       sheet_widget_checkbox_get_property,
2733 	       sheet_widget_checkbox_set_property,
2734 	       sheet_widget_checkbox_draw_cairo,
2735 	       {
2736 		       g_object_class_install_property
2737 			       (object_class, SOC_PROP_ACTIVE,
2738 				g_param_spec_boolean ("active", NULL, NULL,
2739 						      FALSE,
2740 						      GSF_PARAM_STATIC | G_PARAM_READWRITE));
2741 		       g_object_class_install_property
2742 			       (object_class, SOC_PROP_TEXT,
2743 				g_param_spec_string ("text", NULL, NULL, NULL,
2744 						     GSF_PARAM_STATIC | G_PARAM_READWRITE));
2745 		       g_object_class_install_property
2746 			       (object_class, SOC_PROP_MARKUP,
2747 				g_param_spec_boxed ("markup", NULL, NULL, PANGO_TYPE_ATTR_LIST,
2748 						    GSF_PARAM_STATIC | G_PARAM_READWRITE));
2749 	       })
2750 
2751 /****************************************************************************/
2752 typedef SheetWidgetCheckbox		SheetWidgetToggleButton;
2753 typedef SheetWidgetCheckboxClass	SheetWidgetToggleButtonClass;
2754 static GtkWidget *
sheet_widget_toggle_button_create_widget(SheetObjectWidget * sow)2755 sheet_widget_toggle_button_create_widget (SheetObjectWidget *sow)
2756 {
2757 	SheetWidgetCheckbox *swc = GNM_SOW_CHECKBOX (sow);
2758 	GtkWidget *button = gtk_toggle_button_new_with_label (swc->label);
2759 	gtk_widget_set_can_focus (button, FALSE);
2760 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), swc->value);
2761 	g_signal_connect (G_OBJECT (button),
2762 		"toggled",
2763 		G_CALLBACK (cb_checkbox_toggled), swc);
2764 	return button;
2765 }
2766 static void
sheet_widget_toggle_button_class_init(SheetObjectWidgetClass * sow_class)2767 sheet_widget_toggle_button_class_init (SheetObjectWidgetClass *sow_class)
2768 {
2769         sow_class->create_widget = &sheet_widget_toggle_button_create_widget;
2770 }
2771 
2772 GSF_CLASS (SheetWidgetToggleButton, sheet_widget_toggle_button,
2773 	   &sheet_widget_toggle_button_class_init, NULL,
2774 	   GNM_SOW_CHECKBOX_TYPE)
2775 
2776 /****************************************************************************/
2777 
2778 #define GNM_SOW_RADIO_BUTTON(obj)	(G_TYPE_CHECK_INSTANCE_CAST((obj), GNM_SOW_RADIO_BUTTON_TYPE, SheetWidgetRadioButton))
2779 #define DEP_TO_RADIO_BUTTON(d_ptr)	(SheetWidgetRadioButton *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetRadioButton, dep))
2780 
2781 typedef struct {
2782 	SheetObjectWidget sow;
2783 
2784 	gboolean	 being_updated;
2785 	char		*label;
2786 	GnmValue        *value;
2787 	gboolean	 active;
2788 	GnmDependent	 dep;
2789 } SheetWidgetRadioButton;
2790 typedef SheetObjectWidgetClass SheetWidgetRadioButtonClass;
2791 
2792 enum {
2793 	SOR_PROP_0 = 0,
2794 	SOR_PROP_ACTIVE,
2795 	SOR_PROP_TEXT,
2796 	SOR_PROP_MARKUP,
2797 	SOR_PROP_VALUE
2798 };
2799 
2800 static void
sheet_widget_radio_button_set_active(SheetWidgetRadioButton * swrb,gboolean active)2801 sheet_widget_radio_button_set_active (SheetWidgetRadioButton *swrb,
2802 				      gboolean active)
2803 {
2804 	GList *ptr;
2805 
2806 	if (swrb->active == active)
2807 		return;
2808 	swrb->active = active;
2809 
2810 	swrb->being_updated = TRUE;
2811 
2812 	for (ptr = swrb->sow.so.realized_list; ptr != NULL ; ptr = ptr->next) {
2813 		SheetObjectView *view = ptr->data;
2814 		GocWidget *item = get_goc_widget (view);
2815 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (item->widget),
2816 					      active);
2817 	}
2818 
2819 	g_object_notify (G_OBJECT (swrb), "active");
2820 
2821 	swrb->being_updated = FALSE;
2822 }
2823 
2824 
2825 static void
sheet_widget_radio_button_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)2826 sheet_widget_radio_button_get_property (GObject *obj, guint param_id,
2827 					GValue *value, GParamSpec *pspec)
2828 {
2829 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (obj);
2830 
2831 	switch (param_id) {
2832 	case SOR_PROP_ACTIVE:
2833 		g_value_set_boolean (value, swrb->active);
2834 		break;
2835 	case SOR_PROP_TEXT:
2836 		g_value_set_string (value, swrb->label);
2837 		break;
2838 	case SOR_PROP_MARKUP:
2839 		g_value_set_boxed (value, NULL); /* swrb->markup */
2840 		break;
2841 	case SOR_PROP_VALUE:
2842 		g_value_set_boxed (value, swrb->value);
2843 		break;
2844 	default:
2845 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
2846 		break;
2847 	}
2848 }
2849 
2850 static void
sheet_widget_radio_button_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)2851 sheet_widget_radio_button_set_property (GObject *obj, guint param_id,
2852 					GValue const *value, GParamSpec *pspec)
2853 {
2854 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (obj);
2855 
2856 	switch (param_id) {
2857 	case SOR_PROP_ACTIVE:
2858 		sheet_widget_radio_button_set_active (swrb,
2859 						      g_value_get_boolean (value));
2860 		break;
2861 	case SOR_PROP_TEXT:
2862 		sheet_widget_radio_button_set_label (GNM_SO (swrb),
2863 						     g_value_get_string (value));
2864 		break;
2865 	case SOR_PROP_MARKUP:
2866 #if 0
2867 		sheet_widget_radio_button_set_markup (GNM_SO (swrb),
2868 						      g_value_peek_pointer (value));
2869 #endif
2870 		break;
2871 	case SOR_PROP_VALUE:
2872 		sheet_widget_radio_button_set_value (GNM_SO (swrb),
2873 						     g_value_get_boxed (value));
2874 		break;
2875 	default:
2876 		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
2877 		return;
2878 	}
2879 }
2880 
2881 GnmValue const *
sheet_widget_radio_button_get_value(SheetObject * so)2882 sheet_widget_radio_button_get_value (SheetObject *so)
2883 {
2884 	SheetWidgetRadioButton *swrb;
2885 
2886 	g_return_val_if_fail (GNM_IS_SOW_RADIO_BUTTON (so), NULL);
2887 
2888 	swrb = GNM_SOW_RADIO_BUTTON (so);
2889 	return swrb->value;
2890 }
2891 
2892 void
sheet_widget_radio_button_set_value(SheetObject * so,GnmValue const * val)2893 sheet_widget_radio_button_set_value (SheetObject *so, GnmValue const *val)
2894 {
2895 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (so);
2896 
2897 	value_release (swrb->value);
2898 	swrb->value = value_dup (val);
2899 }
2900 
2901 static void
radio_button_eval(GnmDependent * dep)2902 radio_button_eval (GnmDependent *dep)
2903 {
2904 	GnmValue *v;
2905 	GnmEvalPos pos;
2906 	SheetWidgetRadioButton *swrb = DEP_TO_RADIO_BUTTON (dep);
2907 
2908 	v = gnm_expr_top_eval (dep->texpr, eval_pos_init_dep (&pos, dep),
2909 			       GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
2910 	if (v && swrb->value) {
2911 		gboolean active = value_equal (swrb->value, v);
2912 		sheet_widget_radio_button_set_active (swrb, active);
2913 	}
2914 	value_release (v);
2915 }
2916 
2917 static void
radio_button_debug_name(GnmDependent const * dep,GString * target)2918 radio_button_debug_name (GnmDependent const *dep, GString *target)
2919 {
2920 	g_string_append_printf (target, "RadioButton%p", (void *)dep);
2921 }
2922 
2923 static DEPENDENT_MAKE_TYPE (radio_button, .eval = radio_button_eval, .debug_name = radio_button_debug_name)
2924 
2925 static void
sheet_widget_radio_button_init_full(SheetWidgetRadioButton * swrb,GnmCellRef const * ref,char const * label,GnmValue const * value,gboolean active)2926 sheet_widget_radio_button_init_full (SheetWidgetRadioButton *swrb,
2927 				     GnmCellRef const *ref,
2928 				     char const *label,
2929 				     GnmValue const *value,
2930 				     gboolean active)
2931 {
2932 	g_return_if_fail (swrb != NULL);
2933 
2934 	swrb->being_updated = FALSE;
2935 	swrb->label = g_strdup (label ? label : _("RadioButton"));
2936 	swrb->value = value ? value_dup (value) : value_new_empty ();
2937 	swrb->active = active;
2938 
2939 	swrb->dep.sheet = NULL;
2940 	swrb->dep.flags = radio_button_get_dep_type ();
2941 	swrb->dep.texpr = (ref != NULL)
2942 		? gnm_expr_top_new (gnm_expr_new_cellref (ref))
2943 		: NULL;
2944 }
2945 
2946 static void
sheet_widget_radio_button_init(SheetWidgetRadioButton * swrb)2947 sheet_widget_radio_button_init (SheetWidgetRadioButton *swrb)
2948 {
2949 	sheet_widget_radio_button_init_full (swrb, NULL, NULL, NULL, TRUE);
2950 }
2951 
2952 static void
sheet_widget_radio_button_finalize(GObject * obj)2953 sheet_widget_radio_button_finalize (GObject *obj)
2954 {
2955 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (obj);
2956 
2957 	g_return_if_fail (swrb != NULL);
2958 
2959 	g_free (swrb->label);
2960 	swrb->label = NULL;
2961 	value_release (swrb->value);
2962 	swrb->value = NULL;
2963 
2964 	dependent_set_expr (&swrb->dep, NULL);
2965 
2966 	sheet_object_widget_class->finalize (obj);
2967 }
2968 
2969 static void
sheet_widget_radio_button_toggled(GtkToggleButton * button,SheetWidgetRadioButton * swrb)2970 sheet_widget_radio_button_toggled (GtkToggleButton *button,
2971 				   SheetWidgetRadioButton *swrb)
2972 {
2973 	GnmCellRef ref;
2974 
2975 	if (swrb->being_updated)
2976 		return;
2977 	swrb->active = gtk_toggle_button_get_active (button);
2978 
2979 	if (so_get_ref (GNM_SO (swrb), &ref, TRUE) != NULL) {
2980 		cmd_so_set_value (widget_wbc (GTK_WIDGET (button)),
2981 				  /* FIXME: This text sucks:  */
2982 				  _("Clicking radiobutton"),
2983 				  &ref, value_dup (swrb->value),
2984 				  sheet_object_get_sheet (GNM_SO (swrb)));
2985 	}
2986 }
2987 
2988 static GtkWidget *
sheet_widget_radio_button_create_widget(SheetObjectWidget * sow)2989 sheet_widget_radio_button_create_widget (SheetObjectWidget *sow)
2990 {
2991 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (sow);
2992 	GtkWidget *w = g_object_new (GNM_TYPE_RADIO_BUTTON,
2993 				     "label", swrb->label,
2994 				     NULL) ;
2995 
2996 	gtk_widget_set_can_focus (w, FALSE);
2997 
2998 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), swrb->active);
2999 
3000 	g_signal_connect (G_OBJECT (w),
3001 			  "toggled",
3002 			  G_CALLBACK (sheet_widget_radio_button_toggled), sow);
3003 	return w;
3004 }
3005 
3006 static void
sheet_widget_radio_button_copy(SheetObject * dst,SheetObject const * src)3007 sheet_widget_radio_button_copy (SheetObject *dst, SheetObject const *src)
3008 {
3009 	SheetWidgetRadioButton const *src_swrb = GNM_SOW_RADIO_BUTTON (src);
3010 	SheetWidgetRadioButton       *dst_swrb = GNM_SOW_RADIO_BUTTON (dst);
3011 	GnmCellRef ref;
3012 
3013 	sheet_widget_radio_button_init_full (dst_swrb,
3014 					     so_get_ref (src, &ref, FALSE),
3015 					     src_swrb->label,
3016 					     src_swrb->value,
3017 					     src_swrb->active);
3018 }
3019 
3020 static gboolean
sheet_widget_radio_button_set_sheet(SheetObject * so,Sheet * sheet)3021 sheet_widget_radio_button_set_sheet (SheetObject *so, Sheet *sheet)
3022 {
3023 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (so);
3024 
3025 	dependent_set_sheet (&swrb->dep, sheet);
3026 
3027 	return FALSE;
3028 }
3029 
3030 static void
sheet_widget_radio_button_foreach_dep(SheetObject * so,SheetObjectForeachDepFunc func,gpointer user)3031 sheet_widget_radio_button_foreach_dep (SheetObject *so,
3032 				       SheetObjectForeachDepFunc func,
3033 				       gpointer user)
3034 {
3035 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (so);
3036 	func (&swrb->dep, so, user);
3037 }
3038 
3039 static void
sheet_widget_radio_button_write_xml_sax(SheetObject const * so,GsfXMLOut * output,GnmConventions const * convs)3040 sheet_widget_radio_button_write_xml_sax (SheetObject const *so,
3041 					 GsfXMLOut *output,
3042 					 GnmConventions const *convs)
3043 {
3044 	SheetWidgetRadioButton const *swrb = GNM_SOW_RADIO_BUTTON (so);
3045 	GString *valstr = g_string_new (NULL);
3046 
3047 	value_get_as_gstring (swrb->value, valstr, convs);
3048 
3049 	gsf_xml_out_add_cstr (output, "Label", swrb->label);
3050 	gsf_xml_out_add_cstr (output, "Value", valstr->str);
3051 	gsf_xml_out_add_int (output, "ValueType", swrb->value->v_any.type);
3052 	gsf_xml_out_add_int (output, "Active", swrb->active);
3053 	sax_write_dep (output, &swrb->dep, "Input", convs);
3054 
3055 	g_string_free (valstr, TRUE);
3056 }
3057 
3058 static void
sheet_widget_radio_button_prep_sax_parser(SheetObject * so,GsfXMLIn * xin,xmlChar const ** attrs,GnmConventions const * convs)3059 sheet_widget_radio_button_prep_sax_parser (SheetObject *so, GsfXMLIn *xin,
3060 					   xmlChar const **attrs,
3061 					   GnmConventions const *convs)
3062 {
3063 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (so);
3064 	const char *valstr = NULL;
3065 	int value_type = 0;
3066 
3067 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
3068 		if (attr_eq (attrs[0], "Label")) {
3069 			g_free (swrb->label);
3070 			swrb->label = g_strdup (CXML2C (attrs[1]));
3071 		} else if (attr_eq (attrs[0], "Value")) {
3072 			valstr = CXML2C (attrs[1]);
3073 		} else if (gnm_xml_attr_bool (attrs, "Active", &swrb->active) ||
3074 			   gnm_xml_attr_int (attrs, "ValueType", &value_type) ||
3075 			   sax_read_dep (attrs, "Input", &swrb->dep, xin, convs))
3076 			; /* Nothing */
3077 	}
3078 
3079 	value_release (swrb->value);
3080 	swrb->value = NULL;
3081 	if (valstr) {
3082 		swrb->value = value_type
3083 			? value_new_from_string (value_type, valstr, NULL, FALSE)
3084 			: format_match (valstr, NULL, NULL);
3085 	}
3086 	if (!swrb->value)
3087 		swrb->value = value_new_empty ();
3088 }
3089 
3090 void
sheet_widget_radio_button_set_link(SheetObject * so,GnmExprTop const * texpr)3091 sheet_widget_radio_button_set_link (SheetObject *so, GnmExprTop const *texpr)
3092 {
3093 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (so);
3094 	dependent_set_expr (&swrb->dep, texpr);
3095 	if (texpr && swrb->dep.sheet)
3096 		dependent_link (&swrb->dep);
3097 }
3098 
3099 GnmExprTop const *
sheet_widget_radio_button_get_link(SheetObject * so)3100 sheet_widget_radio_button_get_link (SheetObject *so)
3101 {
3102 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (so);
3103 	GnmExprTop const *texpr = swrb->dep.texpr;
3104 
3105 	if (texpr)
3106 		gnm_expr_top_ref (texpr);
3107 
3108 	return texpr;
3109 }
3110 
3111 void
sheet_widget_radio_button_set_label(SheetObject * so,char const * str)3112 sheet_widget_radio_button_set_label (SheetObject *so, char const *str)
3113 {
3114 	GList *list;
3115 	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (so);
3116 	char *new_label;
3117 
3118 	if (go_str_compare (str, swrb->label) == 0)
3119 		return;
3120 
3121 	new_label = g_strdup (str);
3122 	g_free (swrb->label);
3123 	swrb->label = new_label;
3124 
3125 	for (list = swrb->sow.so.realized_list; list; list = list->next) {
3126 		SheetObjectView *view = list->data;
3127 		GocWidget *item = get_goc_widget (view);
3128 		gtk_button_set_label (GTK_BUTTON (item->widget), swrb->label);
3129 	}
3130 }
3131 
3132 
3133 typedef struct {
3134  	GtkWidget *dialog;
3135  	GnmExprEntry *expression;
3136  	GtkWidget *label, *value;
3137 
3138  	char *old_label;
3139 	GnmValue *old_value;
3140  	GtkWidget *old_focus;
3141 
3142  	WBCGtk  *wbcg;
3143  	SheetWidgetRadioButton *swrb;
3144  	Sheet		    *sheet;
3145 } RadioButtonConfigState;
3146 
3147 static void
cb_radio_button_set_focus(G_GNUC_UNUSED GtkWidget * window,GtkWidget * focus_widget,RadioButtonConfigState * state)3148 cb_radio_button_set_focus (G_GNUC_UNUSED GtkWidget *window, GtkWidget *focus_widget,
3149  			   RadioButtonConfigState *state)
3150 {
3151 	GtkWidget *ofp;
3152 
3153  	/* Note:  half of the set-focus action is handle by the default
3154  	 *        callback installed by wbc_gtk_attach_guru */
3155 
3156 	ofp = state->old_focus
3157 		? gtk_widget_get_parent (state->old_focus)
3158 		: NULL;
3159 
3160  	/* Force an update of the content in case it needs tweaking (eg make it
3161  	 * absolute) */
3162  	if (ofp && GNM_EXPR_ENTRY_IS (ofp)) {
3163  		GnmParsePos  pp;
3164  		GnmExprTop const *texpr = gnm_expr_entry_parse (
3165  			GNM_EXPR_ENTRY (ofp),
3166  			parse_pos_init_sheet (&pp, state->sheet),
3167  			NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
3168  		if (texpr != NULL)
3169  			gnm_expr_top_unref (texpr);
3170   	}
3171  	state->old_focus = focus_widget;
3172 }
3173 
3174 static void
cb_radio_button_config_destroy(RadioButtonConfigState * state)3175 cb_radio_button_config_destroy (RadioButtonConfigState *state)
3176 {
3177  	g_return_if_fail (state != NULL);
3178 
3179  	g_free (state->old_label);
3180  	state->old_label = NULL;
3181 
3182  	value_release (state->old_value);
3183  	state->old_value = NULL;
3184 
3185  	state->dialog = NULL;
3186 
3187  	g_free (state);
3188 }
3189 
3190 static GnmValue *
so_parse_value(SheetObject * so,const char * s)3191 so_parse_value (SheetObject *so, const char *s)
3192 {
3193 	Sheet *sheet = so->sheet;
3194 	return format_match (s, NULL, sheet_date_conv (sheet));
3195 }
3196 
3197 static void
cb_radio_button_config_ok_clicked(G_GNUC_UNUSED GtkWidget * button,RadioButtonConfigState * state)3198 cb_radio_button_config_ok_clicked (G_GNUC_UNUSED GtkWidget *button, RadioButtonConfigState *state)
3199 {
3200 	SheetObject *so = GNM_SO (state->swrb);
3201 	GnmParsePos  pp;
3202  	GnmExprTop const *texpr = gnm_expr_entry_parse
3203 		(state->expression,
3204 		 parse_pos_init_sheet (&pp, so->sheet),
3205 		 NULL, FALSE, GNM_EXPR_PARSE_DEFAULT);
3206  	gchar const *text = gtk_entry_get_text (GTK_ENTRY (state->label));
3207  	gchar const *val = gtk_entry_get_text (GTK_ENTRY (state->value));
3208 	GnmValue *new_val = so_parse_value (so, val);
3209 
3210  	cmd_so_set_radio_button (GNM_WBC (state->wbcg), so,
3211  				 texpr,
3212 				 g_strdup (state->old_label), g_strdup (text),
3213 				 value_dup (state->old_value), new_val);
3214 
3215  	gtk_widget_destroy (state->dialog);
3216 }
3217 
3218 static void
cb_radio_button_config_cancel_clicked(G_GNUC_UNUSED GtkWidget * button,RadioButtonConfigState * state)3219 cb_radio_button_config_cancel_clicked (G_GNUC_UNUSED GtkWidget *button, RadioButtonConfigState *state)
3220 {
3221  	sheet_widget_radio_button_set_label (GNM_SO (state->swrb),
3222  					     state->old_label);
3223  	sheet_widget_radio_button_set_value (GNM_SO (state->swrb),
3224  					     state->old_value);
3225  	gtk_widget_destroy (state->dialog);
3226 }
3227 
3228 static void
cb_radio_button_label_changed(GtkEntry * entry,RadioButtonConfigState * state)3229 cb_radio_button_label_changed (GtkEntry *entry, RadioButtonConfigState *state)
3230 {
3231  	sheet_widget_radio_button_set_label (GNM_SO (state->swrb),
3232  					     gtk_entry_get_text (entry));
3233 }
3234 
3235 static void
cb_radio_button_value_changed(GtkEntry * entry,RadioButtonConfigState * state)3236 cb_radio_button_value_changed (GtkEntry *entry, RadioButtonConfigState *state)
3237 {
3238 	const char *text = gtk_entry_get_text (entry);
3239 	SheetObject *so = GNM_SO (state->swrb);
3240 	GnmValue *val = so_parse_value (so, text);
3241 
3242  	sheet_widget_radio_button_set_value (so, val);
3243 	value_release (val);
3244 }
3245 
3246 static void
sheet_widget_radio_button_user_config(SheetObject * so,SheetControl * sc)3247 sheet_widget_radio_button_user_config (SheetObject *so, SheetControl *sc)
3248 {
3249  	SheetWidgetRadioButton *swrb = GNM_SOW_RADIO_BUTTON (so);
3250  	WBCGtk  *wbcg = scg_wbcg (GNM_SCG (sc));
3251  	RadioButtonConfigState *state;
3252  	GtkWidget *grid;
3253 	GString *valstr;
3254 	GtkBuilder *gui;
3255 
3256  	g_return_if_fail (swrb != NULL);
3257 
3258 	/* Only pop up one copy per workbook */
3259  	if (gnm_dialog_raise_if_exists (wbcg, SHEET_OBJECT_CONFIG_KEY))
3260  		return;
3261 
3262 	gui = gnm_gtk_builder_load ("res:ui/so-radiobutton.ui", NULL, GO_CMD_CONTEXT (wbcg));
3263 	if (!gui)
3264 		return;
3265  	state = g_new (RadioButtonConfigState, 1);
3266  	state->swrb = swrb;
3267  	state->wbcg = wbcg;
3268  	state->sheet = sc_sheet	(sc);
3269  	state->old_focus = NULL;
3270 	state->old_label = g_strdup (swrb->label);
3271  	state->old_value = value_dup (swrb->value);
3272  	state->dialog = go_gtk_builder_get_widget (gui, "SO-Radiobutton");
3273 
3274  	grid = go_gtk_builder_get_widget (gui, "main-grid");
3275 
3276  	state->expression = gnm_expr_entry_new (wbcg, TRUE);
3277  	gnm_expr_entry_set_flags (state->expression,
3278 				  GNM_EE_FORCE_ABS_REF | GNM_EE_SHEET_OPTIONAL | GNM_EE_SINGLE_RANGE,
3279 				  GNM_EE_MASK);
3280  	gnm_expr_entry_load_from_dep (state->expression, &swrb->dep);
3281  	go_atk_setup_label (go_gtk_builder_get_widget (gui, "label_linkto"),
3282 			    GTK_WIDGET (state->expression));
3283  	gtk_grid_attach (GTK_GRID (grid),
3284 	                  GTK_WIDGET (state->expression), 1, 0, 1, 1);
3285  	gtk_widget_show (GTK_WIDGET (state->expression));
3286 
3287  	state->label = go_gtk_builder_get_widget (gui, "label_entry");
3288  	gtk_entry_set_text (GTK_ENTRY (state->label), swrb->label);
3289  	gtk_editable_select_region (GTK_EDITABLE(state->label), 0, -1);
3290  	state->value = go_gtk_builder_get_widget (gui, "value_entry");
3291 
3292 	valstr = g_string_new (NULL);
3293 	value_get_as_gstring (swrb->value, valstr, so->sheet->convs);
3294  	gtk_entry_set_text (GTK_ENTRY (state->value), valstr->str);
3295 	g_string_free (valstr, TRUE);
3296 
3297   	gnm_editable_enters (GTK_WINDOW (state->dialog),
3298  				  GTK_WIDGET (state->expression));
3299  	gnm_editable_enters (GTK_WINDOW (state->dialog),
3300  				  GTK_WIDGET (state->label));
3301  	gnm_editable_enters (GTK_WINDOW (state->dialog),
3302  				  GTK_WIDGET (state->value));
3303 
3304  	g_signal_connect (G_OBJECT (state->label),
3305 			  "changed",
3306 			  G_CALLBACK (cb_radio_button_label_changed), state);
3307  	g_signal_connect (G_OBJECT (state->value),
3308 			  "changed",
3309 			  G_CALLBACK (cb_radio_button_value_changed), state);
3310  	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "ok_button")),
3311 			  "clicked",
3312 			  G_CALLBACK (cb_radio_button_config_ok_clicked), state);
3313  	g_signal_connect (G_OBJECT (go_gtk_builder_get_widget (gui, "cancel_button")),
3314 			  "clicked",
3315 			  G_CALLBACK (cb_radio_button_config_cancel_clicked), state);
3316 
3317  	gnm_init_help_button (
3318  		go_gtk_builder_get_widget (gui, "help_button"),
3319  		GNUMERIC_HELP_LINK_SO_RADIO_BUTTON);
3320 
3321  	gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
3322  			       SHEET_OBJECT_CONFIG_KEY);
3323 
3324  	wbc_gtk_attach_guru (state->wbcg, state->dialog);
3325  	g_object_set_data_full (G_OBJECT (state->dialog),
3326 				"state", state, (GDestroyNotify) cb_radio_button_config_destroy);
3327 	g_object_unref (gui);
3328 
3329 	/* Note:  half of the set-focus action is handle by the default */
3330  	/*        callback installed by wbc_gtk_attach_guru */
3331  	g_signal_connect (G_OBJECT (state->dialog), "set-focus",
3332 			  G_CALLBACK (cb_radio_button_set_focus), state);
3333 
3334  	gtk_widget_show (state->dialog);
3335 }
3336 
3337 static void
sheet_widget_radio_button_draw_cairo(SheetObject const * so,cairo_t * cr,G_GNUC_UNUSED double width,double height)3338 sheet_widget_radio_button_draw_cairo (SheetObject const *so, cairo_t *cr,
3339 				      G_GNUC_UNUSED double width, double height)
3340 {
3341 	SheetWidgetRadioButton const *swr = GNM_SOW_RADIO_BUTTON (so);
3342 	double halfheight = height/2;
3343 	double dx = 8., dxh, pm;
3344 	int pw, ph;
3345 
3346 	pm = MIN (height - 2, width - 12);
3347 	if (dx > pm)
3348 		dx = MAX (pm, 3);
3349 	dxh = dx/2;
3350 
3351 	cairo_save (cr);
3352 	cairo_set_line_width (cr, 0.5);
3353 	cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
3354 
3355 	cairo_new_path (cr);
3356 	cairo_move_to (cr, dxh + dx, halfheight);
3357 	cairo_arc (cr, dx, halfheight, dxh, 0., 2*M_PI);
3358 	cairo_close_path (cr);
3359 	cairo_fill_preserve (cr);
3360 	cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
3361 	cairo_stroke (cr);
3362 
3363 	if (swr->active) {
3364 		cairo_new_path (cr);
3365 		cairo_move_to (cr, dx + dxh/2 + 0.5, halfheight);
3366 		cairo_arc (cr, dx, halfheight, dxh/2 + 0.5, 0., 2*M_PI);
3367 		cairo_close_path (cr);
3368 		cairo_fill (cr);
3369 	}
3370 
3371 	cairo_move_to (cr, 2 * dx, halfheight);
3372 
3373 	pw = width - 2 * dx;
3374 	ph = height;
3375 
3376 	draw_cairo_text (cr, swr->label, &pw, &ph, TRUE, FALSE, TRUE, 0, TRUE);
3377 
3378 	cairo_new_path (cr);
3379 	cairo_restore (cr);
3380 }
3381 
3382 SOW_MAKE_TYPE (radio_button, RadioButton,
3383  	       sheet_widget_radio_button_user_config,
3384   	       sheet_widget_radio_button_set_sheet,
3385   	       so_clear_sheet,
3386   	       sheet_widget_radio_button_foreach_dep,
3387  	       sheet_widget_radio_button_copy,
3388  	       sheet_widget_radio_button_write_xml_sax,
3389  	       sheet_widget_radio_button_prep_sax_parser,
3390   	       sheet_widget_radio_button_get_property,
3391   	       sheet_widget_radio_button_set_property,
3392 	       sheet_widget_radio_button_draw_cairo,
3393 	       {
3394 		       g_object_class_install_property
3395 			       (object_class, SOR_PROP_ACTIVE,
3396 				g_param_spec_boolean ("active", NULL, NULL,
3397 						      FALSE,
3398 						      GSF_PARAM_STATIC | G_PARAM_READWRITE));
3399 		       g_object_class_install_property
3400 			       (object_class, SOR_PROP_TEXT,
3401 				g_param_spec_string ("text", NULL, NULL, NULL,
3402 						     GSF_PARAM_STATIC | G_PARAM_READWRITE));
3403 		       g_object_class_install_property
3404 			       (object_class, SOR_PROP_MARKUP,
3405 				g_param_spec_boxed ("markup", NULL, NULL, PANGO_TYPE_ATTR_LIST,
3406 						    GSF_PARAM_STATIC | G_PARAM_READWRITE));
3407 		       g_object_class_install_property
3408 			       (object_class, SOR_PROP_VALUE,
3409 				g_param_spec_boxed ("value", NULL, NULL,
3410 						    gnm_value_get_type (),
3411 						    GSF_PARAM_STATIC | G_PARAM_READWRITE));
3412 	       })
3413 
3414 /****************************************************************************/
3415 
3416 #define GNM_SOW_LIST_BASE_TYPE     (sheet_widget_list_base_get_type ())
3417 #define GNM_SOW_LIST_BASE(obj)     (G_TYPE_CHECK_INSTANCE_CAST((obj), GNM_SOW_LIST_BASE_TYPE, SheetWidgetListBase))
3418 #define DEP_TO_LIST_BASE_CONTENT(d_ptr)	(SheetWidgetListBase *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetListBase, content_dep))
3419 #define DEP_TO_LIST_BASE_OUTPUT(d_ptr)	(SheetWidgetListBase *)(((char *)d_ptr) - G_STRUCT_OFFSET(SheetWidgetListBase, output_dep))
3420 
3421 typedef struct {
3422 	SheetObjectWidget	sow;
3423 
3424 	GnmDependent	content_dep;	/* content of the list */
3425 	GnmDependent	output_dep;	/* selected element */
3426 
3427 	GtkTreeModel	*model;
3428 	int		 selection;
3429 	gboolean        result_as_index;
3430 } SheetWidgetListBase;
3431 typedef struct {
3432 	SheetObjectWidgetClass base;
3433 
3434 	void (*model_changed)     (SheetWidgetListBase *list);
3435 	void (*selection_changed) (SheetWidgetListBase *list);
3436 } SheetWidgetListBaseClass;
3437 
3438 enum {
3439 	LIST_BASE_MODEL_CHANGED,
3440 	LIST_BASE_SELECTION_CHANGED,
3441 	LIST_BASE_LAST_SIGNAL
3442 };
3443 
3444 static guint list_base_signals [LIST_BASE_LAST_SIGNAL] = { 0 };
3445 static GType sheet_widget_list_base_get_type (void);
3446 
3447 static void
sheet_widget_list_base_set_selection(SheetWidgetListBase * swl,int selection,WorkbookControl * wbc)3448 sheet_widget_list_base_set_selection (SheetWidgetListBase *swl, int selection,
3449 				      WorkbookControl *wbc)
3450 {
3451 	GnmCellRef ref;
3452 
3453 	if (selection >= 0 && swl->model != NULL) {
3454 		int n = gtk_tree_model_iter_n_children (swl->model, NULL);
3455 		if (selection > n)
3456 			selection = n;
3457 	} else
3458 		selection = 0;
3459 
3460 	if (swl->selection != selection) {
3461 		swl->selection = selection;
3462 		if (NULL!= wbc &&
3463 		    so_get_ref (GNM_SO (swl), &ref, TRUE) != NULL) {
3464 			GnmValue *v;
3465 			if (swl->result_as_index)
3466 				v = value_new_int (swl->selection);
3467 			else if (selection != 0) {
3468 				GtkTreeIter iter;
3469 				char *content;
3470 				gtk_tree_model_iter_nth_child
3471 					(swl->model, &iter, NULL, selection - 1);
3472 				gtk_tree_model_get (swl->model, &iter,
3473 						    0, &content, -1);
3474 				v = value_new_string_nocopy (content);
3475 			} else
3476 				v = value_new_string ("");
3477 			cmd_so_set_value (wbc, _("Clicking in list"), &ref, v,
3478 					  sheet_object_get_sheet (GNM_SO (swl)));
3479 		}
3480 		g_signal_emit (G_OBJECT (swl),
3481 			list_base_signals [LIST_BASE_SELECTION_CHANGED], 0);
3482 	}
3483 }
3484 
3485 static void
sheet_widget_list_base_set_selection_value(SheetWidgetListBase * swl,GnmValue * v)3486 sheet_widget_list_base_set_selection_value (SheetWidgetListBase *swl, GnmValue *v)
3487 {
3488 	GtkTreeIter iter;
3489 	int selection = 0, i = 1;
3490 
3491 	if (swl->model != NULL && gtk_tree_model_get_iter_first (swl->model, &iter)) {
3492 		char *str = value_get_as_string (v);
3493 		do {
3494 			char *content;
3495 			gboolean match;
3496 			gtk_tree_model_get (swl->model, &iter,
3497 					    0, &content, -1);
3498 			match = 0 == g_ascii_strcasecmp (str, content);
3499 			g_free (content);
3500 			if (match) {
3501 				selection = i;
3502 				break;
3503 			}
3504 			i++;
3505 		} while (gtk_tree_model_iter_next (swl->model, &iter));
3506 		g_free (str);
3507 	}
3508 
3509 	if (swl->selection != selection) {
3510 		swl->selection = selection;
3511 		g_signal_emit (G_OBJECT (swl),
3512 			list_base_signals [LIST_BASE_SELECTION_CHANGED], 0);
3513 	}
3514 }
3515 
3516 static void
list_output_eval(GnmDependent * dep)3517 list_output_eval (GnmDependent *dep)
3518 {
3519 	GnmEvalPos pos;
3520 	GnmValue *v = gnm_expr_top_eval (dep->texpr,
3521 		eval_pos_init_dep (&pos, dep),
3522 		GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
3523 	SheetWidgetListBase *swl = DEP_TO_LIST_BASE_OUTPUT (dep);
3524 
3525 	if (swl->result_as_index)
3526 		sheet_widget_list_base_set_selection
3527 			(swl, floor (value_get_as_float (v)), NULL);
3528 	else
3529 		sheet_widget_list_base_set_selection_value (swl, v);
3530 	value_release (v);
3531 }
3532 
3533 static void
list_output_debug_name(GnmDependent const * dep,GString * target)3534 list_output_debug_name (GnmDependent const *dep, GString *target)
3535 {
3536 	g_string_append_printf (target, "ListOutput%p", (void *)dep);
3537 }
3538 
3539 static DEPENDENT_MAKE_TYPE (list_output, .eval = list_output_eval, .debug_name = list_output_debug_name)
3540 
3541 /*-----------*/
3542 static GnmValue *
cb_collect(GnmValueIter const * iter,GtkListStore * model)3543 cb_collect (GnmValueIter const *iter, GtkListStore *model)
3544 {
3545 	GtkTreeIter list_iter;
3546 
3547 	gtk_list_store_append (model, &list_iter);
3548 	if (NULL != iter->v) {
3549 		GOFormat const *fmt = (NULL != iter->cell_iter)
3550 			? gnm_cell_get_format (iter->cell_iter->cell) : NULL;
3551 		char *label = format_value (fmt, iter->v, -1, NULL);
3552 		gtk_list_store_set (model, &list_iter, 0, label, -1);
3553 		g_free (label);
3554 	} else
3555 		gtk_list_store_set (model, &list_iter, 0, "", -1);
3556 
3557 	return NULL;
3558 }
3559 static void
list_content_eval(GnmDependent * dep)3560 list_content_eval (GnmDependent *dep)
3561 {
3562 	SheetWidgetListBase *swl = DEP_TO_LIST_BASE_CONTENT (dep);
3563 	GnmEvalPos ep;
3564 	GnmValue *v = NULL;
3565 	GtkListStore *model;
3566 
3567 	if (dep->texpr != NULL) {
3568 		v = gnm_expr_top_eval (dep->texpr,
3569 				       eval_pos_init_dep (&ep, dep),
3570 				       GNM_EXPR_EVAL_PERMIT_NON_SCALAR |
3571 				       GNM_EXPR_EVAL_PERMIT_EMPTY);
3572 	}
3573 	model = gtk_list_store_new (1, G_TYPE_STRING);
3574 	if (v) {
3575 		value_area_foreach (v, &ep, CELL_ITER_ALL,
3576 				    (GnmValueIterFunc) cb_collect, model);
3577 		value_release (v);
3578 	}
3579 
3580 	if (NULL != swl->model)
3581 		g_object_unref (swl->model);
3582 	swl->model = GTK_TREE_MODEL (model);
3583 	g_signal_emit (G_OBJECT (swl), list_base_signals [LIST_BASE_MODEL_CHANGED], 0);
3584 }
3585 
3586 static void
list_content_debug_name(GnmDependent const * dep,GString * target)3587 list_content_debug_name (GnmDependent const *dep, GString *target)
3588 {
3589 	g_string_append_printf (target, "ListContent%p", (void *)dep);
3590 }
3591 
3592 static DEPENDENT_MAKE_TYPE (list_content, .eval = list_content_eval, .debug_name = list_content_debug_name)
3593 
3594 /*-----------*/
3595 
3596 static void
sheet_widget_list_base_init(SheetObjectWidget * sow)3597 sheet_widget_list_base_init (SheetObjectWidget *sow)
3598 {
3599 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (sow);
3600 	SheetObject *so = GNM_SO (sow);
3601 
3602 	so->flags &= ~SHEET_OBJECT_PRINT;
3603 
3604 	swl->content_dep.sheet = NULL;
3605 	swl->content_dep.flags = list_content_get_dep_type ();
3606 	swl->content_dep.texpr = NULL;
3607 
3608 	swl->output_dep.sheet = NULL;
3609 	swl->output_dep.flags = list_output_get_dep_type ();
3610 	swl->output_dep.texpr = NULL;
3611 
3612 	swl->model = NULL;
3613 	swl->selection = 0;
3614 	swl->result_as_index = TRUE;
3615 }
3616 
3617 static void
sheet_widget_list_base_finalize(GObject * obj)3618 sheet_widget_list_base_finalize (GObject *obj)
3619 {
3620 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (obj);
3621 	dependent_set_expr (&swl->content_dep, NULL);
3622 	dependent_set_expr (&swl->output_dep, NULL);
3623 	if (swl->model != NULL) {
3624 		g_object_unref (swl->model);
3625 		swl->model = NULL;
3626 	}
3627 	sheet_object_widget_class->finalize (obj);
3628 }
3629 
3630 static void
sheet_widget_list_base_user_config(SheetObject * so,SheetControl * sc)3631 sheet_widget_list_base_user_config (SheetObject *so, SheetControl *sc)
3632 {
3633 	dialog_so_list (scg_wbcg (GNM_SCG (sc)), G_OBJECT (so));
3634 }
3635 static gboolean
sheet_widget_list_base_set_sheet(SheetObject * so,Sheet * sheet)3636 sheet_widget_list_base_set_sheet (SheetObject *so, Sheet *sheet)
3637 {
3638 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3639 
3640 	g_return_val_if_fail (swl != NULL, TRUE);
3641 	g_return_val_if_fail (swl->content_dep.sheet == NULL, TRUE);
3642 	g_return_val_if_fail (swl->output_dep.sheet == NULL, TRUE);
3643 
3644 	dependent_set_sheet (&swl->content_dep, sheet);
3645 	dependent_set_sheet (&swl->output_dep, sheet);
3646 
3647 	list_content_eval (&swl->content_dep); /* populate the list */
3648 
3649 	return FALSE;
3650 }
3651 
3652 static void
sheet_widget_list_base_foreach_dep(SheetObject * so,SheetObjectForeachDepFunc func,gpointer user)3653 sheet_widget_list_base_foreach_dep (SheetObject *so,
3654 				    SheetObjectForeachDepFunc func,
3655 				    gpointer user)
3656 {
3657 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3658 	func (&swl->content_dep, so, user);
3659 	func (&swl->output_dep, so, user);
3660 }
3661 
3662 static void
sheet_widget_list_base_write_xml_sax(SheetObject const * so,GsfXMLOut * output,GnmConventions const * convs)3663 sheet_widget_list_base_write_xml_sax (SheetObject const *so, GsfXMLOut *output,
3664 				      GnmConventions const *convs)
3665 {
3666 	SheetWidgetListBase const *swl = GNM_SOW_LIST_BASE (so);
3667 	sax_write_dep (output, &swl->content_dep, "Content", convs);
3668 	sax_write_dep (output, &swl->output_dep, "Output", convs);
3669 	gsf_xml_out_add_int (output, "OutputAsIndex", swl->result_as_index ? 1 : 0);
3670 }
3671 
3672 static void
sheet_widget_list_base_prep_sax_parser(SheetObject * so,GsfXMLIn * xin,xmlChar const ** attrs,GnmConventions const * convs)3673 sheet_widget_list_base_prep_sax_parser (SheetObject *so, GsfXMLIn *xin,
3674 					xmlChar const **attrs,
3675 					GnmConventions const *convs)
3676 {
3677 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3678 
3679 	for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2)
3680 		if (sax_read_dep (attrs, "Content", &swl->content_dep, xin, convs))
3681 			;
3682 		else if (sax_read_dep (attrs, "Output", &swl->output_dep, xin, convs))
3683 			;
3684 		else if (gnm_xml_attr_bool (attrs, "OutputAsIndex", &swl->result_as_index))
3685 			;
3686 }
3687 
3688 static GtkWidget *
sheet_widget_list_base_create_widget(G_GNUC_UNUSED SheetObjectWidget * sow)3689 sheet_widget_list_base_create_widget (G_GNUC_UNUSED SheetObjectWidget *sow)
3690 {
3691 	g_warning("ERROR: sheet_widget_list_base_create_widget SHOULD NEVER BE CALLED (but it has been)!\n");
3692 	return gtk_frame_new ("invisiwidget(WARNING: I AM A BUG!)");
3693 }
3694 
3695 SOW_MAKE_TYPE (list_base, ListBase,
3696 	       sheet_widget_list_base_user_config,
3697 	       sheet_widget_list_base_set_sheet,
3698 	       so_clear_sheet,
3699 	       sheet_widget_list_base_foreach_dep,
3700 	       NULL,
3701 	       sheet_widget_list_base_write_xml_sax,
3702 	       sheet_widget_list_base_prep_sax_parser,
3703 	       NULL,
3704 	       NULL,
3705 	       sheet_widget_draw_cairo,
3706 	       {
3707 	       list_base_signals[LIST_BASE_MODEL_CHANGED] = g_signal_new ("model-changed",
3708 			GNM_SOW_LIST_BASE_TYPE,
3709 			G_SIGNAL_RUN_LAST,
3710 			G_STRUCT_OFFSET (SheetWidgetListBaseClass, model_changed),
3711 			NULL, NULL,
3712 			g_cclosure_marshal_VOID__VOID,
3713 			G_TYPE_NONE, 0);
3714 	       list_base_signals[LIST_BASE_SELECTION_CHANGED] = g_signal_new ("selection-changed",
3715 			GNM_SOW_LIST_BASE_TYPE,
3716 			G_SIGNAL_RUN_LAST,
3717 			G_STRUCT_OFFSET (SheetWidgetListBaseClass, selection_changed),
3718 			NULL, NULL,
3719 			g_cclosure_marshal_VOID__VOID,
3720 			G_TYPE_NONE, 0);
3721 	       })
3722 
3723 void
sheet_widget_list_base_set_links(SheetObject * so,GnmExprTop const * output,GnmExprTop const * content)3724 sheet_widget_list_base_set_links (SheetObject *so,
3725 				  GnmExprTop const *output,
3726 				  GnmExprTop const *content)
3727 {
3728 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3729 	dependent_set_expr (&swl->output_dep, output);
3730 	if (output && swl->output_dep.sheet)
3731 		dependent_link (&swl->output_dep);
3732 	dependent_set_expr (&swl->content_dep, content);
3733 	if (content && swl->content_dep.sheet) {
3734 		dependent_link (&swl->content_dep);
3735 		list_content_eval (&swl->content_dep); /* populate the list */
3736 	}
3737 }
3738 
3739 GnmExprTop const *
sheet_widget_list_base_get_result_link(SheetObject const * so)3740 sheet_widget_list_base_get_result_link  (SheetObject const *so)
3741 {
3742 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3743 	GnmExprTop const *texpr = swl->output_dep.texpr;
3744 
3745  	if (texpr)
3746 		gnm_expr_top_ref (texpr);
3747 
3748  	return texpr;
3749 }
3750 
3751 GnmExprTop const *
sheet_widget_list_base_get_content_link(SheetObject const * so)3752 sheet_widget_list_base_get_content_link (SheetObject const *so)
3753 {
3754 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3755 	GnmExprTop const *texpr = swl->content_dep.texpr;
3756 
3757  	if (texpr)
3758 		gnm_expr_top_ref (texpr);
3759 
3760  	return texpr;
3761 }
3762 
3763 gboolean
sheet_widget_list_base_result_type_is_index(SheetObject const * so)3764 sheet_widget_list_base_result_type_is_index (SheetObject const *so)
3765 {
3766 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3767 
3768 	return swl->result_as_index;
3769 }
3770 
3771 void
sheet_widget_list_base_set_result_type(SheetObject * so,gboolean as_index)3772 sheet_widget_list_base_set_result_type (SheetObject *so, gboolean as_index)
3773 {
3774 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3775 
3776 	if (swl->result_as_index == as_index)
3777 		return;
3778 
3779 	swl->result_as_index = as_index;
3780 
3781 }
3782 
3783 /**
3784  * sheet_widget_list_base_get_adjustment:
3785  * @so: #SheetObject
3786  *
3787  * Note: allocates a new adjustment.
3788  * Returns: (transfer full): the newly created #GtkAdjustment.
3789  **/
3790 GtkAdjustment *
sheet_widget_list_base_get_adjustment(SheetObject * so)3791 sheet_widget_list_base_get_adjustment (SheetObject *so)
3792 {
3793 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3794 	GtkAdjustment *adj;
3795 
3796 	g_return_val_if_fail (swl, NULL);
3797 
3798 	adj = (GtkAdjustment*)gtk_adjustment_new
3799 		(swl->selection,
3800 		 1,
3801 		 1 + gtk_tree_model_iter_n_children (swl->model, NULL),
3802 		 1,
3803 		 5,
3804 		 5);
3805 	g_object_ref_sink (adj);
3806 
3807 	return adj;
3808 }
3809 
3810 /****************************************************************************/
3811 
3812 #define GNM_SOW_LIST(o)	(G_TYPE_CHECK_INSTANCE_CAST((o), GNM_SOW_LIST_TYPE, SheetWidgetList))
3813 
3814 typedef SheetWidgetListBase		SheetWidgetList;
3815 typedef SheetWidgetListBaseClass	SheetWidgetListClass;
3816 
3817 static void
cb_list_selection_changed(SheetWidgetListBase * swl,GtkTreeSelection * selection)3818 cb_list_selection_changed (SheetWidgetListBase *swl,
3819 			   GtkTreeSelection *selection)
3820 {
3821 	if (swl->selection > 0) {
3822 		GtkTreePath *path = gtk_tree_path_new_from_indices (swl->selection-1, -1);
3823 		gtk_tree_selection_select_path (selection, path);
3824 		gtk_tree_path_free (path);
3825 	} else
3826 		gtk_tree_selection_unselect_all (selection);
3827 }
3828 
3829 static void
cb_list_model_changed(SheetWidgetListBase * swl,GtkTreeView * list)3830 cb_list_model_changed (SheetWidgetListBase *swl, GtkTreeView *list)
3831 {
3832 	int old_selection = swl->selection;
3833 	swl->selection = -1;
3834 	gtk_tree_view_set_model (GTK_TREE_VIEW (list), swl->model);
3835 	sheet_widget_list_base_set_selection (swl, old_selection, NULL);
3836 }
3837 static void
cb_selection_changed(GtkTreeSelection * selection,SheetWidgetListBase * swl)3838 cb_selection_changed (GtkTreeSelection *selection,
3839 		      SheetWidgetListBase *swl)
3840 {
3841 	GtkWidget    *view = (GtkWidget *)gtk_tree_selection_get_tree_view (selection);
3842 	GnmSimpleCanvas *scanvas = GNM_SIMPLE_CANVAS (gtk_widget_get_ancestor (view, GNM_SIMPLE_CANVAS_TYPE));
3843 	GtkTreeModel *model;
3844 	GtkTreeIter   iter;
3845 	int	      pos = 0;
3846 	if (swl->selection != -1) {
3847 		if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3848 			GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
3849 			if (NULL != path) {
3850 				pos = *gtk_tree_path_get_indices (path) + 1;
3851 				gtk_tree_path_free (path);
3852 			}
3853 		}
3854 		sheet_widget_list_base_set_selection
3855 			(swl, pos, scg_wbc (scanvas->scg));
3856 	}
3857 }
3858 
3859 static GtkWidget *
sheet_widget_list_create_widget(SheetObjectWidget * sow)3860 sheet_widget_list_create_widget (SheetObjectWidget *sow)
3861 {
3862 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (sow);
3863 	GtkTreeSelection *selection;
3864 	GtkTreeIter iter;
3865 	GtkWidget *list = gtk_tree_view_new_with_model (swl->model);
3866 	GtkWidget *sw = gtk_scrolled_window_new (
3867 		gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (list)),
3868 		gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (list)));
3869 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
3870 		GTK_POLICY_AUTOMATIC,
3871 		GTK_POLICY_ALWAYS);
3872 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE);
3873 	gtk_tree_view_append_column (GTK_TREE_VIEW (list),
3874 		gtk_tree_view_column_new_with_attributes ("ID",
3875 			gtk_cell_renderer_text_new (), "text", 0,
3876 			NULL));
3877 
3878 	gtk_container_add (GTK_CONTAINER (sw), list);
3879 
3880 	g_signal_connect_object (G_OBJECT (swl), "model-changed",
3881 		G_CALLBACK (cb_list_model_changed), list, 0);
3882 
3883 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
3884 	if ((swl->model != NULL) && (swl->selection > 0) &&
3885 	    gtk_tree_model_iter_nth_child (swl->model, &iter, NULL, swl->selection - 1))
3886 		gtk_tree_selection_select_iter (selection, &iter);
3887 	g_signal_connect_object (G_OBJECT (swl), "selection-changed",
3888 		G_CALLBACK (cb_list_selection_changed), selection, 0);
3889 	g_signal_connect (selection, "changed",
3890 		G_CALLBACK (cb_selection_changed), swl);
3891 	return sw;
3892 }
3893 
3894 static void
sheet_widget_list_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)3895 sheet_widget_list_draw_cairo (SheetObject const *so, cairo_t *cr,
3896 			      double width, double height)
3897 {
3898 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
3899 
3900 	cairo_save (cr);
3901 	cairo_set_line_width (cr, 0.5);
3902 	cairo_set_source_rgb(cr, 0, 0, 0);
3903 
3904 	cairo_new_path (cr);
3905 	cairo_move_to (cr, 0, 0);
3906 	cairo_line_to (cr, width, 0);
3907 	cairo_line_to (cr, width, height);
3908 	cairo_line_to (cr, 0, height);
3909 	cairo_close_path (cr);
3910 	cairo_stroke (cr);
3911 
3912 	cairo_new_path (cr);
3913 	cairo_move_to (cr, width - 10, 0);
3914 	cairo_rel_line_to (cr, 0, height);
3915 	cairo_stroke (cr);
3916 
3917 	cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
3918 
3919 	cairo_new_path (cr);
3920 	cairo_move_to (cr, width - 5 -3, height - 12);
3921 	cairo_rel_line_to (cr, 6, 0);
3922 	cairo_rel_line_to (cr, -3, 8);
3923 	cairo_close_path (cr);
3924 	cairo_fill (cr);
3925 
3926 	cairo_new_path (cr);
3927 	cairo_move_to (cr, width - 5 -3, 12);
3928 	cairo_rel_line_to (cr, 6, 0);
3929 	cairo_rel_line_to (cr, -3, -8);
3930 	cairo_close_path (cr);
3931 	cairo_fill (cr);
3932 
3933 	if (swl->model != NULL) {
3934 		GtkTreeIter iter;
3935 		GString*str = g_string_new (NULL);
3936 		int twidth = width, theight = height;
3937 
3938 
3939 		cairo_new_path (cr);
3940 		cairo_rectangle (cr, 2, 1, width - 2 - 12, height - 2);
3941 		cairo_clip (cr);
3942 		if (gtk_tree_model_get_iter_first (swl->model, &iter))
3943 			do {
3944 				char *astr = NULL, *newline;
3945 				gtk_tree_model_get (swl->model, &iter, 0, &astr, -1);
3946 				while (NULL != (newline = strchr (astr, '\n')))
3947 					*newline = ' ';
3948 				g_string_append (str, astr);
3949 				g_string_append_c (str, '\n');
3950 				g_free (astr);
3951 			} while (gtk_tree_model_iter_next (swl->model, &iter));
3952 
3953 		cairo_translate (cr, 4., 2.);
3954 
3955 		draw_cairo_text (cr, str->str, &twidth, &theight, FALSE, FALSE, FALSE,
3956 				 swl->selection, FALSE);
3957 
3958 		g_string_free (str, TRUE);
3959 	}
3960 
3961 	cairo_new_path (cr);
3962 	cairo_restore (cr);
3963 }
3964 
3965 static void
sheet_widget_list_class_init(SheetObjectWidgetClass * sow_class)3966 sheet_widget_list_class_init (SheetObjectWidgetClass *sow_class)
3967 {
3968 	SheetObjectClass *so_class = GNM_SO_CLASS (sow_class);
3969 
3970 	so_class->draw_cairo = &sheet_widget_list_draw_cairo;
3971         sow_class->create_widget = &sheet_widget_list_create_widget;
3972 }
3973 
3974 GSF_CLASS (SheetWidgetList, sheet_widget_list,
3975 	   &sheet_widget_list_class_init, NULL,
3976 	   GNM_SOW_LIST_BASE_TYPE)
3977 
3978 /****************************************************************************/
3979 
3980 #define GNM_SOW_COMBO(o)	(G_TYPE_CHECK_INSTANCE_CAST((o), GNM_SOW_COMBO_TYPE, SheetWidgetCombo))
3981 
3982 typedef SheetWidgetListBase		SheetWidgetCombo;
3983 typedef SheetWidgetListBaseClass	SheetWidgetComboClass;
3984 
3985 static void
cb_combo_selection_changed(SheetWidgetListBase * swl,GtkComboBox * combo)3986 cb_combo_selection_changed (SheetWidgetListBase *swl,
3987 			    GtkComboBox *combo)
3988 {
3989 	int pos = swl->selection - 1;
3990 	if (pos < 0) {
3991 		gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (combo))), "");
3992 		pos = -1;
3993 	}
3994 	gtk_combo_box_set_active (combo, pos);
3995 }
3996 
3997 static void
cb_combo_model_changed(SheetWidgetListBase * swl,GtkComboBox * combo)3998 cb_combo_model_changed (SheetWidgetListBase *swl, GtkComboBox *combo)
3999 {
4000 	gtk_combo_box_set_model (GTK_COMBO_BOX (combo), swl->model);
4001 
4002 	/* we cannot set this until we have a model,
4003 	 * but after that we cannot reset it */
4004 	if (gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo)) < 0)
4005 		gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo), 0);
4006 
4007 	/* force entry to reload */
4008 	cb_combo_selection_changed (swl, combo);
4009 }
4010 
4011 static void
cb_combo_changed(GtkComboBox * combo,SheetWidgetListBase * swl)4012 cb_combo_changed (GtkComboBox *combo, SheetWidgetListBase *swl)
4013 {
4014 	int pos = gtk_combo_box_get_active (combo) + 1;
4015 	sheet_widget_list_base_set_selection (swl, pos,
4016 		widget_wbc (GTK_WIDGET (combo)));
4017 }
4018 
4019 static GtkWidget *
sheet_widget_combo_create_widget(SheetObjectWidget * sow)4020 sheet_widget_combo_create_widget (SheetObjectWidget *sow)
4021 {
4022 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (sow);
4023 	GtkWidget *widget = gtk_event_box_new (), *combo;
4024 
4025 	combo = gtk_combo_box_new_with_entry ();
4026 	gtk_widget_set_can_focus (gtk_bin_get_child (GTK_BIN (combo)),
4027 				  FALSE);
4028 	if (swl->model != NULL)
4029 		g_object_set (G_OBJECT (combo),
4030                       "model",		swl->model,
4031                       "entry-text-column",	0,
4032                       "active",	swl->selection - 1,
4033                       NULL);
4034 
4035 	g_signal_connect_object (G_OBJECT (swl), "model-changed",
4036 		G_CALLBACK (cb_combo_model_changed), combo, 0);
4037 	g_signal_connect_object (G_OBJECT (swl), "selection-changed",
4038 		G_CALLBACK (cb_combo_selection_changed), combo, 0);
4039 	g_signal_connect (G_OBJECT (combo), "changed",
4040 		G_CALLBACK (cb_combo_changed), swl);
4041 
4042 	gtk_container_add (GTK_CONTAINER (widget), combo);
4043 	gtk_event_box_set_visible_window (GTK_EVENT_BOX (widget), FALSE);
4044 	return widget;
4045 }
4046 
4047 static void
sheet_widget_combo_draw_cairo(SheetObject const * so,cairo_t * cr,double width,double height)4048 sheet_widget_combo_draw_cairo (SheetObject const *so, cairo_t *cr,
4049 			       double width, double height)
4050 {
4051 	SheetWidgetListBase *swl = GNM_SOW_LIST_BASE (so);
4052 	double halfheight = height/2;
4053 
4054 	cairo_save (cr);
4055 	cairo_set_line_width (cr, 0.5);
4056 	cairo_set_source_rgb(cr, 0, 0, 0);
4057 
4058 	cairo_new_path (cr);
4059 	cairo_move_to (cr, 0, 0);
4060 	cairo_line_to (cr, width, 0);
4061 	cairo_line_to (cr, width, height);
4062 	cairo_line_to (cr, 0, height);
4063 	cairo_close_path (cr);
4064 	cairo_stroke (cr);
4065 
4066 	cairo_new_path (cr);
4067 	cairo_move_to (cr, width - 10, 0);
4068 	cairo_rel_line_to (cr, 0, height);
4069 	cairo_stroke (cr);
4070 
4071 	cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
4072 
4073 	cairo_new_path (cr);
4074 	cairo_move_to (cr, width - 5 -3, halfheight - 4);
4075 	cairo_rel_line_to (cr, 6, 0);
4076 	cairo_rel_line_to (cr, -3, 8);
4077 	cairo_close_path (cr);
4078 	cairo_fill (cr);
4079 
4080 	cairo_set_source_rgb(cr, 0, 0, 0);
4081 	cairo_move_to (cr, 4., halfheight);
4082 
4083 	if (swl->model != NULL) {
4084 		GtkTreeIter iter;
4085 		if (gtk_tree_model_iter_nth_child (swl->model, &iter, NULL,
4086 						   swl->selection - 1)) {
4087 			char *str = NULL;
4088 			gtk_tree_model_get (swl->model, &iter, 0, &str, -1);
4089 			draw_cairo_text (cr, str, NULL, NULL, TRUE, FALSE, TRUE, 0, FALSE);
4090 			g_free (str);
4091 		}
4092 	}
4093 
4094 	cairo_new_path (cr);
4095 	cairo_restore (cr);
4096 }
4097 
4098 static void
sheet_widget_combo_class_init(SheetObjectWidgetClass * sow_class)4099 sheet_widget_combo_class_init (SheetObjectWidgetClass *sow_class)
4100 {
4101 	SheetObjectClass *so_class = GNM_SO_CLASS (sow_class);
4102 
4103 	so_class->draw_cairo = &sheet_widget_combo_draw_cairo;
4104         sow_class->create_widget = &sheet_widget_combo_create_widget;
4105 }
4106 
4107 GSF_CLASS (SheetWidgetCombo, sheet_widget_combo,
4108 	   &sheet_widget_combo_class_init, NULL,
4109 	   GNM_SOW_LIST_BASE_TYPE)
4110 
4111 
4112 
4113 
4114 
4115 /**************************************************************************/
4116 
4117 /**
4118  * sheet_object_widget_register:
4119  *
4120  * Initialize the classes for the sheet-object-widgets. We need to initialize
4121  * them before we try loading a sheet that might contain sheet-object-widgets
4122  **/
4123 void
sheet_object_widget_register(void)4124 sheet_object_widget_register (void)
4125 {
4126 	GNM_SOW_FRAME_TYPE;
4127 	GNM_SOW_BUTTON_TYPE;
4128 	GNM_SOW_SCROLLBAR_TYPE;
4129 	GNM_SOW_CHECKBOX_TYPE;
4130 	GNM_SOW_RADIO_BUTTON_TYPE;
4131 	GNM_SOW_LIST_TYPE;
4132 	GNM_SOW_COMBO_TYPE;
4133 	GNM_SOW_SPIN_BUTTON_TYPE;
4134 	GNM_SOW_SLIDER_TYPE;
4135 }
4136