1 /*
2  * gog-object.c :
3  *
4  * Copyright (C) 2003-2007 Jody Goldberg (jody@gnome.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <goffice/goffice-config.h>
23 #include <goffice/goffice.h>
24 #include <goffice/goffice-debug.h>
25 
26 #include <gsf/gsf-impl-utils.h>
27 #include <glib/gi18n-lib.h>
28 #include <string.h>
29 #include <stdlib.h>
30 
31 /**
32  * GogObjectNamingConv:
33  * @GOG_OBJECT_NAME_BY_ROLE: named built from role.
34  * @GOG_OBJECT_NAME_BY_TYPE: named built from type.
35  * @GOG_OBJECT_NAME_MANUALLY: custom name.
36  **/
37 
38 /**
39  * GogManualSizeMode:
40  * @GOG_MANUAL_SIZE_AUTO: auto size, can't be changed.
41  * @GOG_MANUAL_SIZE_WIDTH: the width can be changed.
42  * @GOG_MANUAL_SIZE_HEIGHT: the height can be changed.
43  * @GOG_MANUAL_SIZE_FULL: both height and width can be changed.
44  **/
45 
46 /**
47  * GogObjectClass:
48  * @base: base class.
49  * @roles: roles for the class.
50  * @view_type: view type.
51  * @update: updates the object.
52  * @parent_changed: called when parent changed.
53  * @type_name: gets the type public name.
54  * @populate_editor: populates the editor.
55  * @document_changed: the document changed.
56  * @get_manual_size_mode: resize mode.
57  * @changed: implements the "changed" signal.
58  * @name_changed: implements the "name-changed" signal.
59  * @possible_additions_changed: implements the "possible-additions-changed" signal.
60  * @child_added: implements the "child-added" signal.
61  * @child_removed: implements the "child-removed" signal.
62  * @child_name_changed: implements the "child-name-changed" signal.
63  * @children_reordered: implements the "children-reordered" signal.
64  * @update_editor: implements the "update-editor" signal.
65  **/
66 
67 /**
68  * GogObjectPosition:
69  * @GOG_POSITION_AUTO: automatic.
70  * @GOG_POSITION_N: north, might be combined with east or west.
71  * @GOG_POSITION_S: south, might be combined with east or west.
72  * @GOG_POSITION_E: east.
73  * @GOG_POSITION_W: west.
74  * @GOG_POSITION_COMPASS: mask of the four previous positions.
75  * @GOG_POSITION_ALIGN_FILL: fills.
76  * @GOG_POSITION_ALIGN_START: start.
77  * @GOG_POSITION_ALIGN_END: end.
78  * @GOG_POSITION_ALIGN_CENTER: centered.
79  * @GOG_POSITION_ALIGNMENT: mask for start or end.
80  * @GOG_POSITION_SPECIAL: special.
81  * @GOG_POSITION_MANUAL: manual.
82  * @GOG_POSITION_MANUAL_X_ABS: whether the x position is absolute or relative.
83  * @GOG_POSITION_MANUAL_Y_ABS: whether the y position is absolute or relative.
84  * @GOG_POSITION_MANUAL_X_END: x position relative to start or end.
85  * @GOG_POSITION_MANUAL_Y_END: y position relative to start or end.
86  * @GOG_POSITION_ANCHOR_NW: anchored north-west.
87  * @GOG_POSITION_ANCHOR_N: anchored north.
88  * @GOG_POSITION_ANCHOR_NE: anchored north-east.
89  * @GOG_POSITION_ANCHOR_E: anchored east.
90  * @GOG_POSITION_ANCHOR_SE: anchored south-east.
91  * @GOG_POSITION_ANCHOR_S: anchored south.
92  * @GOG_POSITION_ANCHOR_SW: anchored south-west.
93  * @GOG_POSITION_ANCHOR_W: anchored west.
94  * @GOG_POSITION_ANCHOR_CENTER: anchored at center.
95  * @GOG_POSITION_ANCHOR: mask for anchors.
96  * @GOG_POSITION_ANY_MANUAL: mask for all manual positions
97  * @GOG_POSITION_PADDING: padding.
98  * @GOG_POSITION_MANUAL_W: relative width.
99  * @GOG_POSITION_MANUAL_W_ABS: absolute width.
100  * @GOG_POSITION_MANUAL_H: relative height.
101  * @GOG_POSITION_MANUAL_H_ABS: absolute height.
102  * @GOG_POSITION_ANY_MANUAL_SIZE: mask for manual sizes.
103  * @GOG_POSITION_HEXPAND: expands in the horizontal direction.
104  * @GOG_POSITION_VEXPAND: expands in the vertical direction.
105  * @GOG_POSITION_EXPAND: expands in either direction.
106  **/
107 
108 /**
109  * GogObjectRole:
110  * @id: id for persistence.
111  * @is_a_typename: type name.
112  * @allowable_positions: allowed positions inside parent.
113  * @default_position: default position.
114  * @naming_conv: naming convention.
115  * @can_add: return %TRUE if a new child can be added.
116  * @can_remove: return %TRUE if the child can be removed.
117  * @allocate: optional allocator, g_object_new() is used if %NULL.
118  * @post_add: called after adding the child.
119  * @pre_remove: called before removing the child.
120  * @post_remove: called after removing the child.
121  *
122  * Describes allowable children for a #GogObject.
123  **/
124 
125 static GogObjectRole*
gog_object_role_ref(GogObjectRole * role)126 gog_object_role_ref (GogObjectRole* role)
127 {
128 	return role;
129 }
130 
131 static void
gog_object_role_unref(G_GNUC_UNUSED GogObjectRole * role)132 gog_object_role_unref (G_GNUC_UNUSED GogObjectRole *role)
133 {
134 }
135 
136 GType
gog_object_role_get_type(void)137 gog_object_role_get_type (void)
138 {
139 	static GType t = 0;
140 
141 	if (t == 0)
142 		t = g_boxed_type_register_static ("GogObjectRole",
143 			 (GBoxedCopyFunc) gog_object_role_ref,
144 			 (GBoxedFreeFunc) gog_object_role_unref);
145 	return t;
146 }
147 
148 /**
149  * SECTION: gog-object
150  * @short_description: The base class for graph objects.
151  * @See_also: #GogGraph
152  *
153  * Abstract base class that objects in the graph hierarchy are based on.
154  * This class handles manipulation of the object hierarchy, and positioning of
155  * objects in the graph.
156  *
157  * Every object has a name that is unique in the graph. It can have a parent
158  * and a list of children in specific roles (see #GogObjectRole).
159  * There can generally be several children in each role.
160  *
161  * If built with GTK+ support, each object also knows how to populate a widget
162  * that allows one to manipulate the attributes of that object. This can be used
163  * by #GOEditor to present a widget that allows manipulation of the whole graph.
164  */
165 
166 typedef struct {
167 	char const *label;
168 	char const *value;
169 	unsigned const flags;
170 } GogPositionFlagDesc;
171 
172 static GogPositionFlagDesc const position_compass[] = {
173 	{N_("Top"), 		"top",		GOG_POSITION_N},
174 	{N_("Top right"), 	"top-right",	GOG_POSITION_N|GOG_POSITION_E},
175 	{N_("Right"), 		"right",	GOG_POSITION_E},
176 	{N_("Bottom right"), 	"bottom-right",	GOG_POSITION_E|GOG_POSITION_S},
177 	{N_("Bottom"),		"bottom",	GOG_POSITION_S},
178 	{N_("Bottom left"),	"bottom-left",	GOG_POSITION_S|GOG_POSITION_W},
179 	{N_("Left"),		"left",		GOG_POSITION_W},
180 	{N_("Top left"),	"top-left",	GOG_POSITION_W|GOG_POSITION_N}
181 };
182 
183 static GogPositionFlagDesc const position_alignment[] = {
184 	{N_("Fill"), 	"fill",		GOG_POSITION_ALIGN_FILL},
185 	{N_("Start"), 	"start",	GOG_POSITION_ALIGN_START},
186 	{N_("End"), 	"end",		GOG_POSITION_ALIGN_END},
187 	{N_("Center"), 	"center",	GOG_POSITION_ALIGN_CENTER}
188 };
189 
190 static GogPositionFlagDesc const position_anchor[] = {
191 	{N_("Top left"), 	"top-left",	GOG_POSITION_ANCHOR_NW},
192 	{N_("Top"), 		"top",		GOG_POSITION_ANCHOR_N},
193 	{N_("Top right"), 	"top-right",	GOG_POSITION_ANCHOR_NE},
194 	{N_("Left"), 		"left",		GOG_POSITION_ANCHOR_W},
195 	{N_("Center"), 		"center",	GOG_POSITION_ANCHOR_CENTER},
196 	{N_("Right"), 		"right",	GOG_POSITION_ANCHOR_E},
197 	{N_("Bottom left"), 	"bottom-left",	GOG_POSITION_ANCHOR_SW},
198 	{N_("Bottom"), 		"bottom",	GOG_POSITION_ANCHOR_S},
199 	{N_("Bottom right"),	"bottom-right",	GOG_POSITION_ANCHOR_SE}
200 };
201 
202 static GogPositionFlagDesc const manual_size[] = {
203 	{N_("None"),			"none",		  GOG_POSITION_AUTO},
204 	{N_("Width"), 			"width",	  GOG_POSITION_MANUAL_W},
205 	{N_("Absolute width"), 	"abs-width",  GOG_POSITION_MANUAL_W_ABS},
206 	{N_("Height"), 			"height",	  GOG_POSITION_MANUAL_H},
207 	{N_("Absolute height"), "abs-height", GOG_POSITION_MANUAL_H_ABS},
208 	{N_("Size"), 			"size",		  GOG_POSITION_MANUAL_W | GOG_POSITION_MANUAL_H},
209 	{N_("Absolute size"),	"abs-size",	  GOG_POSITION_MANUAL_W_ABS | GOG_POSITION_MANUAL_H_ABS}
210 };
211 
212 enum {
213 	OBJECT_PROP_0,
214 	OBJECT_PROP_ID,
215 	OBJECT_PROP_POSITION,
216 	OBJECT_PROP_POSITION_COMPASS,
217 	OBJECT_PROP_POSITION_ALIGNMENT,
218 	OBJECT_PROP_POSITION_IS_MANUAL,
219 	OBJECT_PROP_POSITION_ANCHOR,
220 	OBJECT_PROP_INVISIBLE,
221 	OBJECT_PROP_MANUAL_SIZE_MODE
222 };
223 
224 enum {
225 	CHILD_ADDED,
226 	CHILD_REMOVED,
227 	CHILD_NAME_CHANGED,
228 	CHILDREN_REORDERED,
229 	NAME_CHANGED,
230 	CHANGED,
231 	UPDATE_EDITOR,
232 	LAST_SIGNAL
233 };
234 static gulong gog_object_signals [LAST_SIGNAL] = { 0, };
235 
236 static GObjectClass *parent_klass;
237 
238 static void gog_object_set_id (GogObject *obj, unsigned id);
239 
240 static void
gog_object_parent_finalized(GogObject * obj)241 gog_object_parent_finalized (GogObject *obj)
242 {
243 	obj->parent = NULL;
244 	g_object_unref (obj);
245 }
246 
247 static void
gog_object_finalize(GObject * gobj)248 gog_object_finalize (GObject *gobj)
249 {
250 	GogObject *obj = GOG_OBJECT (gobj);
251 
252 	g_free (obj->user_name); obj->user_name = NULL;
253 	g_free (obj->auto_name); obj->auto_name = NULL;
254 
255 	g_slist_foreach (obj->children, (GFunc) gog_object_parent_finalized, NULL);
256 	g_slist_free (obj->children);
257 	obj->children = NULL;
258 
259 	(parent_klass->finalize) (gobj);
260 }
261 
262 static void
gog_object_parent_changed(GogObject * child,gboolean was_set)263 gog_object_parent_changed (GogObject *child, gboolean was_set)
264 {
265 	GSList *ptr = child->children;
266 	for (; ptr != NULL ; ptr = ptr->next) {
267 		GogObjectClass *klass = GOG_OBJECT_GET_CLASS (ptr->data);
268 		(*klass->parent_changed) (ptr->data, was_set);
269 	}
270 
271 	if (GOG_IS_DATASET (child))
272 		gog_dataset_parent_changed (GOG_DATASET (child), was_set);
273 }
274 
275 static void
gog_object_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)276 gog_object_set_property (GObject *obj, guint param_id,
277 			 GValue const *value, GParamSpec *pspec)
278 {
279 	GogObject *gobj = GOG_OBJECT (obj);
280 	char const *str;
281 	char **str_doubles;
282 	unsigned id;
283 
284 	switch (param_id) {
285 	case OBJECT_PROP_ID:
286 		id = g_value_get_uint (value);
287 		gog_object_set_id (gobj, id);
288 		break;
289 	case OBJECT_PROP_POSITION:
290 		str = g_value_get_string (value);
291 		str_doubles = g_strsplit (str, " ", 4);
292 		if (g_strv_length (str_doubles) != 4) {
293 			g_strfreev (str_doubles);
294 			break;
295 		}
296 		gobj->manual_position.x = g_ascii_strtod (str_doubles[0], NULL);
297 		gobj->manual_position.y = g_ascii_strtod (str_doubles[1], NULL);
298 		gobj->manual_position.w = g_ascii_strtod (str_doubles[2], NULL);
299 		gobj->manual_position.h = g_ascii_strtod (str_doubles[3], NULL);
300 		g_strfreev (str_doubles);
301 		break;
302 	case OBJECT_PROP_POSITION_COMPASS:
303 		str = g_value_get_string (value);
304 		if (str == NULL)
305 			break;
306 		for (id = 0; id < G_N_ELEMENTS (position_compass); id++)
307 			if (strcmp (str, position_compass[id].value) == 0)
308 				break;
309 		if (id < G_N_ELEMENTS (position_compass))
310 			gog_object_set_position_flags (gobj,
311 						       position_compass[id].flags,
312 						       GOG_POSITION_COMPASS);
313 		break;
314 	case OBJECT_PROP_POSITION_ALIGNMENT:
315 		str = g_value_get_string (value);
316 		if (str == NULL)
317 			break;
318 		for (id = 0; id < G_N_ELEMENTS (position_alignment); id++)
319 			if (strcmp (str, position_alignment[id].value) == 0)
320 				break;
321 		if (id < G_N_ELEMENTS (position_alignment))
322 			gog_object_set_position_flags (gobj,
323 						       position_alignment[id].flags,
324 						       GOG_POSITION_ALIGNMENT);
325 		break;
326 	case OBJECT_PROP_POSITION_IS_MANUAL:
327 		gog_object_set_position_flags (gobj,
328 			g_value_get_boolean (value) ? GOG_POSITION_MANUAL : 0,
329 			GOG_POSITION_MANUAL);
330 		break;
331 	case OBJECT_PROP_POSITION_ANCHOR:
332 		str = g_value_get_string (value);
333 		if (str == NULL)
334 			break;
335 		for (id = 0; id < G_N_ELEMENTS (position_anchor); id++)
336 			if (strcmp (str, position_anchor[id].value) == 0)
337 				break;
338 		if (id < G_N_ELEMENTS (position_anchor))
339 			gog_object_set_position_flags (gobj,
340 						       position_anchor[id].flags,
341 						       GOG_POSITION_ANCHOR);
342 		break;
343 	case OBJECT_PROP_INVISIBLE :
344 		gog_object_set_invisible (gobj, g_value_get_boolean (value));
345 		break;
346 	case OBJECT_PROP_MANUAL_SIZE_MODE:
347 		str = g_value_get_string (value);
348 		if (str == NULL)
349 			break;
350 		for (id = 0; id < G_N_ELEMENTS (manual_size); id++)
351 			if (strcmp (str, manual_size[id].value) == 0)
352 				break;
353 		if (id < G_N_ELEMENTS (manual_size))
354 			gog_object_set_position_flags (gobj,
355 						       manual_size[id].flags,
356 						       GOG_POSITION_ANY_MANUAL_SIZE);
357 		break;
358 
359 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
360 		 return; /* NOTE : RETURN */
361 	}
362 }
363 
364 static void
gog_object_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)365 gog_object_get_property (GObject *obj, guint param_id,
366 		       GValue *value, GParamSpec *pspec)
367 {
368 	GogObject *gobj = GOG_OBJECT (obj);
369 	GogObjectPosition flags;
370 	GString *string;
371 	char buffer[G_ASCII_DTOSTR_BUF_SIZE];
372 	unsigned i;
373 
374 	switch (param_id) {
375 	case OBJECT_PROP_ID:
376 		g_value_set_uint (value, GOG_OBJECT (obj)->id);
377 		break;
378 	case OBJECT_PROP_POSITION:
379 		string = g_string_new ("");
380 		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), gobj->manual_position.x));
381 		g_string_append_c (string, ' ');
382 		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), gobj->manual_position.y));
383 		g_string_append_c (string, ' ');
384 		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), gobj->manual_position.w));
385 		g_string_append_c (string, ' ');
386 		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), gobj->manual_position.h));
387 		g_value_set_string (value, string->str);
388 		g_string_free (string, TRUE);
389 		break;
390 	case OBJECT_PROP_POSITION_COMPASS:
391 		flags = gog_object_get_position_flags (GOG_OBJECT (obj), GOG_POSITION_COMPASS);
392 		for (i = 0; i < G_N_ELEMENTS (position_compass); i++)
393 			if (position_compass[i].flags == flags) {
394 				g_value_set_string (value, position_compass[i].value);
395 				break;
396 			}
397 		break;
398 	case OBJECT_PROP_POSITION_ALIGNMENT:
399 		flags = gog_object_get_position_flags (GOG_OBJECT (obj), GOG_POSITION_ALIGNMENT);
400 		for (i = 0; i < G_N_ELEMENTS (position_alignment); i++)
401 			if (position_alignment[i].flags == flags) {
402 				g_value_set_string (value, position_alignment[i].value);
403 				break;
404 			}
405 		break;
406 	case OBJECT_PROP_POSITION_IS_MANUAL:
407 		g_value_set_boolean (value, (gobj->position & GOG_POSITION_MANUAL) != 0);
408 		break;
409 	case OBJECT_PROP_POSITION_ANCHOR:
410 		flags = gog_object_get_position_flags (GOG_OBJECT (obj), GOG_POSITION_ANCHOR);
411 		for (i = 0; i < G_N_ELEMENTS (position_anchor); i++)
412 			if (position_anchor[i].flags == flags) {
413 				g_value_set_string (value, position_anchor[i].value);
414 				break;
415 			}
416 		break;
417 	case OBJECT_PROP_INVISIBLE :
418 		g_value_set_boolean (value, gobj->invisible != 0);
419 		break;
420 	case OBJECT_PROP_MANUAL_SIZE_MODE:
421 		flags = gog_object_get_position_flags (GOG_OBJECT (obj), GOG_POSITION_ANY_MANUAL_SIZE);
422 		for (i = 0; i < G_N_ELEMENTS (manual_size); i++)
423 			if (manual_size[i].flags == flags) {
424 				g_value_set_string (value, manual_size[i].value);
425 				break;
426 			}
427 		if (i == G_N_ELEMENTS (manual_size))
428 			g_value_set_string (value, "none");
429 		break;
430 
431 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
432 		 break;
433 	}
434 }
435 
436 #ifdef GOFFICE_WITH_GTK
437 typedef struct {
438 	GtkWidget	*x_spin, *y_spin, *w_spin, *h_spin;
439 	GtkWidget	*position_select_combo;
440 	GtkWidget	*position_notebook;
441 	GogObject	*gobj;
442 	GtkBuilder	*gui;
443 	gulong		 update_editor_handler;
444 	gulong		 h_sig, w_sig;
445 } ObjectPrefState;
446 
447 static void
object_pref_state_free(ObjectPrefState * state)448 object_pref_state_free (ObjectPrefState *state)
449 {
450 	g_signal_handler_disconnect (state->gobj, state->update_editor_handler);
451 	g_object_unref (state->gobj);
452 	g_object_unref (state->gui);
453 	g_free (state);
454 }
455 
456 static void
cb_compass_changed(GtkComboBox * combo,ObjectPrefState * state)457 cb_compass_changed (GtkComboBox *combo, ObjectPrefState *state)
458 {
459 	GogObjectPosition position = position_compass[gtk_combo_box_get_active (combo)].flags;
460 
461 	gog_object_set_position_flags (state->gobj, position, GOG_POSITION_COMPASS);
462 }
463 
464 static void
cb_alignment_changed(GtkComboBox * combo,ObjectPrefState * state)465 cb_alignment_changed (GtkComboBox *combo, ObjectPrefState *state)
466 {
467 	GogObjectPosition position = position_alignment[gtk_combo_box_get_active (combo)].flags;
468 
469 	gog_object_set_position_flags (state->gobj, position, GOG_POSITION_ALIGNMENT);
470 }
471 
472 static void
cb_position_changed(GtkWidget * spin,ObjectPrefState * state)473 cb_position_changed (GtkWidget *spin, ObjectPrefState *state)
474 {
475 	GogViewAllocation pos;
476 	double value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin)) / 100.0;
477 
478     gog_object_get_manual_position (state->gobj, &pos);
479 	if (spin == state->x_spin)
480 		pos.x = value;
481 	else if (spin == state->y_spin)
482 		pos.y = value;
483 	else if (spin == state->w_spin)
484 		pos.w = value;
485 	else if (spin == state->h_spin)
486 		pos.h = value;
487 	gog_object_set_manual_position (state->gobj, &pos);
488 }
489 
490 static void
cb_size_changed(GtkWidget * spin,ObjectPrefState * state)491 cb_size_changed (GtkWidget *spin, ObjectPrefState *state)
492 {
493 	GogViewAllocation pos;
494 	double value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin)) / 100.0;
495 
496        	gog_object_get_manual_position (state->gobj, &pos);
497 	if (spin == state->w_spin)
498 		pos.w = value;
499 	else if (spin == state->h_spin)
500 		pos.h = value;
501 	gog_object_set_manual_position (state->gobj, &pos);
502 }
503 
504 static void
update_select_state(ObjectPrefState * state)505 update_select_state (ObjectPrefState *state)
506 {
507 	if (state->position_select_combo) {
508 		int index = gog_object_get_position_flags (state->gobj, GOG_POSITION_MANUAL) == 0 ? 0 : 1;
509 
510 		gtk_combo_box_set_active (GTK_COMBO_BOX (state->position_select_combo), index);
511 		if (index == 0 && GOG_IS_CHART (state->gobj))
512 			index = 2;
513 		gtk_notebook_set_current_page (GTK_NOTEBOOK (state->position_notebook), index);
514 	}
515 }
516 
517 static void
cb_manual_position_changed(GtkComboBox * combo,ObjectPrefState * state)518 cb_manual_position_changed (GtkComboBox *combo, ObjectPrefState *state)
519 {
520 	int index = gtk_combo_box_get_active (combo);
521 
522 	gog_object_set_position_flags (state->gobj,
523 		index != 0 ? GOG_POSITION_MANUAL : 0,
524 		GOG_POSITION_MANUAL);
525 	if (index == 0 && GOG_IS_CHART (state->gobj))
526 		index = 2;
527 	gtk_notebook_set_current_page (GTK_NOTEBOOK (state->position_notebook), index);
528 }
529 
530 static void
cb_anchor_changed(GtkComboBox * combo,ObjectPrefState * state)531 cb_anchor_changed (GtkComboBox *combo, ObjectPrefState *state)
532 {
533 	GogObjectPosition position = position_anchor[gtk_combo_box_get_active (combo)].flags;
534 
535 	gog_object_set_position_flags (state->gobj, position, GOG_POSITION_ANCHOR);
536 }
537 
538 static void
cb_update_editor(GogObject * gobj,ObjectPrefState * state)539 cb_update_editor (GogObject *gobj, ObjectPrefState *state)
540 {
541 	GogObjectPosition manual_size = gog_object_get_position_flags (gobj, GOG_POSITION_ANY_MANUAL_SIZE);
542 	if (state->x_spin != NULL)
543 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->x_spin), gobj->manual_position.x * 100.0);
544 	if (state->y_spin != NULL)
545 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->y_spin), gobj->manual_position.y * 100.0);
546 	if (state->w_spin != NULL) {
547 		gboolean visible = (manual_size & GOG_POSITION_MANUAL_W) != 0;
548 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->w_spin), gobj->manual_position.w * 100.0);
549 		gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "width_label"), visible);
550 		gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "width_spin"), visible);
551 		gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "width-pc-lbl"), visible);
552 	}
553 	if (state->h_spin != NULL) {
554 		gboolean visible = (manual_size & GOG_POSITION_MANUAL_H) != 0;
555 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->h_spin), gobj->manual_position.h * 100.0);
556 		gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "height_label"), visible);
557 		gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "height_spin"), visible);
558 		gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "height-pc-lbl"), visible);
559 	}
560 
561 	update_select_state (state);
562 }
563 
564 static void
cb_chart_position_changed(GtkWidget * spin,ObjectPrefState * state)565 cb_chart_position_changed (GtkWidget *spin, ObjectPrefState *state)
566 {
567 	g_object_set (G_OBJECT (state->gobj), gtk_buildable_get_name (GTK_BUILDABLE (spin)),
568 		      (int) gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin)), NULL);
569 }
570 
571 static void
cb_manual_size_changed(GtkComboBox * combo,ObjectPrefState * state)572 cb_manual_size_changed (GtkComboBox *combo, ObjectPrefState *state)
573 {
574 	int index = gtk_combo_box_get_active (combo);
575 	GogObjectPosition pos = GOG_POSITION_AUTO;
576 	gboolean visible;
577 	if (index > 0)
578 		switch (gog_object_get_manual_size_mode (state->gobj)) {
579 		case GOG_MANUAL_SIZE_AUTO:
580 			break;
581 		case GOG_MANUAL_SIZE_WIDTH:
582 			pos = GOG_POSITION_MANUAL_W;
583 			break;
584 		case GOG_MANUAL_SIZE_HEIGHT:
585 			pos = GOG_POSITION_MANUAL_H;
586 			break;
587 		case GOG_MANUAL_SIZE_FULL:
588 			pos = GOG_POSITION_MANUAL_W | GOG_POSITION_MANUAL_H;
589 			break;
590 		}
591 	gog_object_set_position_flags (state->gobj, pos, GOG_POSITION_ANY_MANUAL_SIZE);
592 	visible = (pos & GOG_POSITION_MANUAL_W) != 0;
593 	gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "width_label"), visible);
594 	gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "width_spin"), visible);
595 	gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "width-pc-lbl"), visible);
596 	visible = (pos & GOG_POSITION_MANUAL_H) != 0;
597 	gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "height_label"), visible);
598 	gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "height_spin"), visible);
599 	gtk_widget_set_visible (go_gtk_builder_get_widget (state->gui, "height-pc-lbl"), visible);
600 }
601 
602 static void
gog_object_populate_editor(GogObject * gobj,GOEditor * editor,G_GNUC_UNUSED GogDataAllocator * dalloc,GOCmdContext * cc)603 gog_object_populate_editor (GogObject *gobj,
604 			    GOEditor *editor,
605 			    G_GNUC_UNUSED GogDataAllocator *dalloc,
606 			    GOCmdContext *cc)
607 {
608 	GtkWidget *w;
609 	GtkSizeGroup *widget_size_group, *label_size_group;
610 	GtkBuilder *gui;
611 	GogObjectPosition allowable_positions, flags;
612 	ObjectPrefState *state;
613 	unsigned i;
614 
615 	if (gobj->role == NULL)
616 		return;
617 
618        	allowable_positions = gobj->role->allowable_positions;
619 	if (!(allowable_positions & (GOG_POSITION_MANUAL | GOG_POSITION_COMPASS)))
620 		return;
621 
622 	gui = go_gtk_builder_load_internal ("res:go:graph/gog-object-prefs.ui", GETTEXT_PACKAGE, cc);
623 	if (gui == NULL)
624 		return;
625 
626 	state = g_new (ObjectPrefState, 1);
627 	state->gobj = gobj;
628 	state->gui = gui;
629 	state->position_select_combo = NULL;
630 	state->x_spin = NULL;
631 	state->y_spin = NULL;
632 	state->w_spin = NULL;
633 	state->h_spin = NULL;
634 	state->position_notebook = go_gtk_builder_get_widget (gui, "position_notebook");
635 
636 	g_object_ref (gobj);
637 
638 	widget_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
639 	label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
640 
641 	if (allowable_positions & GOG_POSITION_COMPASS) {
642 		w = GTK_WIDGET (go_gtk_builder_combo_box_init_text (gui, "position_combo"));
643 		gtk_size_group_add_widget (widget_size_group, w);
644 		flags = gog_object_get_position_flags (gobj, GOG_POSITION_COMPASS);
645 		for (i = 0; i < G_N_ELEMENTS (position_compass); i++) {
646 			go_gtk_combo_box_append_text (GTK_COMBO_BOX (w), _(position_compass[i].label));
647 			if (position_compass[i].flags == flags)
648 				gtk_combo_box_set_active (GTK_COMBO_BOX (w), i);
649 		}
650 		g_signal_connect (G_OBJECT (w), "changed", G_CALLBACK (cb_compass_changed), state);
651 		w = go_gtk_builder_get_widget (gui, "position_label");
652 		gtk_size_group_add_widget (label_size_group, w);
653 	} else {
654 		w = go_gtk_builder_get_widget (gui, "compass_position");
655 		gtk_widget_hide (w);
656 	}
657 
658 	if (allowable_positions & GOG_POSITION_COMPASS) {
659 		w = GTK_WIDGET (go_gtk_builder_combo_box_init_text (gui, "alignment_combo"));
660 		gtk_size_group_add_widget (widget_size_group, w);
661 		flags = gog_object_get_position_flags (gobj, GOG_POSITION_ALIGNMENT);
662 		for (i = 0; i < G_N_ELEMENTS (position_alignment); i++) {
663 			go_gtk_combo_box_append_text (GTK_COMBO_BOX (w), _(position_alignment[i].label));
664 			if (position_alignment[i].flags == flags)
665 				gtk_combo_box_set_active (GTK_COMBO_BOX (w), i);
666 		}
667 		g_signal_connect (G_OBJECT (w), "changed", G_CALLBACK (cb_alignment_changed), state);
668 		w = go_gtk_builder_get_widget (gui, "alignment_label");
669 		gtk_size_group_add_widget (label_size_group, w);
670 	} else {
671 		w = go_gtk_builder_get_widget (gui, "compass_alignment");
672 		gtk_widget_hide (w);
673 	}
674 
675 	if (!(allowable_positions & GOG_POSITION_COMPASS))
676 		gtk_notebook_set_current_page (GTK_NOTEBOOK (state->position_notebook), 1);
677 
678 	g_object_unref (widget_size_group);
679 	g_object_unref (label_size_group);
680 
681 	widget_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
682 	label_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
683 
684 	if (allowable_positions & GOG_POSITION_MANUAL) {
685 		w = go_gtk_builder_get_widget (gui, "x_label");
686 		gtk_size_group_add_widget (label_size_group, w);
687 		w = go_gtk_builder_get_widget (gui, "x_spin");
688 		gtk_size_group_add_widget (widget_size_group, w);
689 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), gobj->manual_position.x * 100.0);
690 		g_signal_connect (G_OBJECT (w), "value-changed", G_CALLBACK (cb_position_changed), state);
691 		state->x_spin = w;
692 
693 		w = go_gtk_builder_get_widget (gui, "y_label");
694 		gtk_size_group_add_widget (label_size_group, w);
695 		w = go_gtk_builder_get_widget (gui, "y_spin");
696 		gtk_size_group_add_widget (widget_size_group, w);
697 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), gobj->manual_position.y * 100.0);
698 		g_signal_connect (G_OBJECT (w), "value-changed", G_CALLBACK (cb_position_changed), state);
699 		state->y_spin = w;
700 
701 		w = go_gtk_builder_get_widget (gui, "anchor_label");
702 		gtk_size_group_add_widget (label_size_group, w);
703 		w =  GTK_WIDGET (go_gtk_builder_combo_box_init_text (gui, "anchor_combo"));
704 		flags = gog_object_get_position_flags (gobj, GOG_POSITION_ANCHOR);
705 		for (i = 0; i < G_N_ELEMENTS (position_anchor); i++) {
706 			go_gtk_combo_box_append_text (GTK_COMBO_BOX (w), _(position_anchor[i].label));
707 			if (i == 0 || position_anchor[i].flags == flags)
708 				gtk_combo_box_set_active (GTK_COMBO_BOX (w), i);
709 		}
710 		g_signal_connect (G_OBJECT (w), "changed", G_CALLBACK (cb_anchor_changed), state);
711 		gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (w), 3);
712 
713 	}
714 
715 	if (gog_object_get_manual_size_mode (gobj) == GOG_MANUAL_SIZE_AUTO)  {
716 		w = go_gtk_builder_get_widget (gui, "manual-sizes");
717 		gtk_widget_destroy (w);
718 		w = go_gtk_builder_get_widget (gui, "size-select-box");
719 		gtk_widget_destroy (w);
720 	} else {
721 		gboolean manual_size = (flags = gog_object_get_position_flags (gobj, GOG_POSITION_ANY_MANUAL_SIZE)) != 0;
722 		w = go_gtk_builder_get_widget (gui, "object-size-combo");
723 		gtk_combo_box_set_active (GTK_COMBO_BOX (w),
724 		                          manual_size? 1: 0);
725 		g_signal_connect (G_OBJECT (w),
726 				  "changed", G_CALLBACK (cb_manual_size_changed), state);
727 		w = go_gtk_builder_get_widget (gui, "width_label");
728 		gtk_size_group_add_widget (label_size_group, w);
729 		w = go_gtk_builder_get_widget (gui, "width_spin");
730 		gtk_size_group_add_widget (widget_size_group, w);
731 		g_signal_connect (G_OBJECT (w), "value-changed",
732 		                  G_CALLBACK (cb_size_changed), state);
733 		state->w_spin = w;
734 
735 		w = go_gtk_builder_get_widget (gui, "height_label");
736 		gtk_size_group_add_widget (label_size_group, w);
737 		w = go_gtk_builder_get_widget (gui, "height_spin");
738 		gtk_size_group_add_widget (widget_size_group, w);
739 		g_signal_connect (G_OBJECT (w), "value-changed",
740 		                  G_CALLBACK (cb_size_changed), state);
741 		state->h_spin = w;
742 		cb_update_editor (gobj, state);
743 	}
744 
745 	if (GOG_IS_CHART (gobj)) {
746 		/* setting special notebook page */
747 		int col, row, cols, rows;
748 		g_object_get (G_OBJECT (gobj), "xpos", &col, "ypos", &row, "columns", &cols, "rows", &rows, NULL);
749 		w = go_gtk_builder_get_widget (gui, "xpos");
750 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), col);
751 		g_signal_connect (G_OBJECT (w), "value-changed",
752 				  G_CALLBACK (cb_chart_position_changed), state);
753 		w = go_gtk_builder_get_widget (gui, "columns");
754 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), cols);
755 		g_signal_connect (G_OBJECT (w), "value-changed",
756 				  G_CALLBACK (cb_chart_position_changed), state);
757 		w = go_gtk_builder_get_widget (gui, "ypos");
758 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), row);
759 		g_signal_connect (G_OBJECT (w), "value-changed",
760 				  G_CALLBACK (cb_chart_position_changed), state);
761 		w = go_gtk_builder_get_widget (gui, "rows");
762 		gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), rows);
763 		g_signal_connect (G_OBJECT (w), "value-changed",
764 				  G_CALLBACK (cb_chart_position_changed), state);
765 	}
766 
767 	g_object_unref (widget_size_group);
768 	g_object_unref (label_size_group);
769 
770 	if ((allowable_positions & GOG_POSITION_MANUAL) &&
771 	    ((allowable_positions & (GOG_POSITION_COMPASS | GOG_POSITION_ALIGNMENT)) ||
772 	     (allowable_positions & GOG_POSITION_SPECIAL))) {
773 		state->position_select_combo = go_gtk_builder_get_widget (gui, "position_select_combo");
774 
775 		update_select_state (state);
776 
777 		g_signal_connect (G_OBJECT (state->position_select_combo),
778 				  "changed", G_CALLBACK (cb_manual_position_changed), state);
779 	} else {
780 		w = go_gtk_builder_get_widget (gui, "position_select_box");
781 		gtk_widget_hide (w);
782 	}
783 
784 	state->update_editor_handler = g_signal_connect (G_OBJECT (gobj),
785 							 "update-editor",
786 							 G_CALLBACK (cb_update_editor), state);
787 
788 	w = go_gtk_builder_get_widget (gui, "gog_object_prefs");
789 	g_signal_connect_swapped (G_OBJECT (w), "destroy", G_CALLBACK (object_pref_state_free), state);
790 	go_editor_add_page (editor, w, _("Position"));
791 }
792 #endif
793 
794 static void
gog_object_base_init(GogObjectClass * klass)795 gog_object_base_init (GogObjectClass *klass)
796 {
797 	klass->roles_allocated = FALSE;
798 	/* klass->roles might be non-NULL; in that case, it points to
799 	   the roles hash of the superclass. */
800 }
801 
802 static void
gog_object_base_finalize(GogObjectClass * klass)803 gog_object_base_finalize (GogObjectClass *klass)
804 {
805 	if (klass->roles_allocated)
806 		g_hash_table_destroy (klass->roles);
807 }
808 
809 static void
gog_object_class_init(GObjectClass * klass)810 gog_object_class_init (GObjectClass *klass)
811 {
812 	GogObjectClass *gog_klass = (GogObjectClass *)klass;
813 	parent_klass = g_type_class_peek_parent (klass);
814 
815 	klass->finalize = gog_object_finalize;
816 	klass->set_property	= gog_object_set_property;
817 	klass->get_property	= gog_object_get_property;
818 
819 	gog_klass->parent_changed  = gog_object_parent_changed;
820 #ifdef GOFFICE_WITH_GTK
821 	gog_klass->populate_editor = gog_object_populate_editor;
822 #endif
823 
824 	gog_klass->use_parent_as_proxy = FALSE;
825 
826 	g_object_class_install_property (klass, OBJECT_PROP_ID,
827 		g_param_spec_uint ("id",
828 			_("Object ID"),
829 			_("Object numerical ID"),
830 			0, G_MAXINT, 0,
831 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
832 	g_object_class_install_property (klass, OBJECT_PROP_POSITION,
833 		g_param_spec_string ("position",
834 			_("Position"),
835 			_("Position and size of object, in percentage of parent size"),
836 			"0 0 1 1",
837 			GSF_PARAM_STATIC | G_PARAM_READWRITE|GO_PARAM_PERSISTENT));
838 	g_object_class_install_property (klass, OBJECT_PROP_POSITION_COMPASS,
839 		g_param_spec_string ("compass",
840 			_("Compass"),
841 			_("Compass auto position flags"),
842 			"top",
843 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT | GOG_PARAM_POSITION));
844 	g_object_class_install_property (klass, OBJECT_PROP_POSITION_ALIGNMENT,
845 		g_param_spec_string ("alignment",
846 			_("Alignment"),
847 			_("Alignment flag"),
848 			"fill",
849 		       	GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT | GOG_PARAM_POSITION));
850 	g_object_class_install_property (klass, OBJECT_PROP_POSITION_IS_MANUAL,
851 		g_param_spec_boolean ("is-position-manual",
852 			_("Is position manual"),
853 			_("Is position manual"),
854 			FALSE,
855 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
856 	g_object_class_install_property (klass, OBJECT_PROP_POSITION_ANCHOR,
857 		g_param_spec_string ("anchor",
858 			_("Anchor"),
859 			_("Anchor for manual position"),
860 			"top-left",
861 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT | GOG_PARAM_POSITION));
862 	g_object_class_install_property (klass, OBJECT_PROP_INVISIBLE,
863 		g_param_spec_boolean ("invisible",
864 			_("Should the object be hidden"),
865 			_("Should the object be hidden"),
866 			FALSE,
867 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
868 	g_object_class_install_property (klass, OBJECT_PROP_MANUAL_SIZE_MODE,
869 		g_param_spec_string ("manual-size",
870 			_("Manual size"),
871 			_("Whether the height or width are manually set"),
872 			"none",
873 			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
874 
875 	/**
876 	 * GogObject::child-added:
877 	 * @object: the object on which the signal is emitted
878 	 * @child: The new #GogObject whose parent is @object
879 	 *
880 	 * The ::child-added signal is emitted AFTER the child has been added
881 	 * and AFTER the parent-changed signal has been called for it.
882 	 **/
883 	gog_object_signals [CHILD_ADDED] = g_signal_new ("child-added",
884 		G_TYPE_FROM_CLASS (klass),
885 		G_SIGNAL_RUN_LAST,
886 		G_STRUCT_OFFSET (GogObjectClass, child_added),
887 		NULL, NULL,
888 		g_cclosure_marshal_VOID__OBJECT,
889 		G_TYPE_NONE,	1, G_TYPE_OBJECT);
890 	/**
891 	 * GogObject::child-removed:
892 	 * @object: the object on which the signal is emitted
893 	 * @child: The new #GogObject whose parent is @object
894 	 *
895 	 * The ::child-removed signal is emitted BEFORE the child has been
896 	 * added and BEFORE the parent-changed signal has been called for it.
897 	 **/
898 	gog_object_signals [CHILD_REMOVED] = g_signal_new ("child-removed",
899 		G_TYPE_FROM_CLASS (klass),
900 		G_SIGNAL_RUN_LAST,
901 		G_STRUCT_OFFSET (GogObjectClass, child_removed),
902 		NULL, NULL,
903 		g_cclosure_marshal_VOID__OBJECT,
904 		G_TYPE_NONE,	1, G_TYPE_OBJECT);
905 	gog_object_signals [CHILD_NAME_CHANGED] = g_signal_new ("child-name-changed",
906 		G_TYPE_FROM_CLASS (gog_klass),
907 		G_SIGNAL_RUN_LAST,
908 		G_STRUCT_OFFSET (GogObjectClass, child_name_changed),
909 		NULL, NULL,
910 		g_cclosure_marshal_VOID__OBJECT,
911 		G_TYPE_NONE,	1, G_TYPE_OBJECT);
912 	gog_object_signals [CHILDREN_REORDERED] = g_signal_new ("children-reordered",
913 		G_TYPE_FROM_CLASS (klass),
914 		G_SIGNAL_RUN_LAST,
915 		G_STRUCT_OFFSET (GogObjectClass, children_reordered),
916 		NULL, NULL,
917 		g_cclosure_marshal_VOID__VOID,
918 		G_TYPE_NONE, 0);
919 	gog_object_signals [NAME_CHANGED] = g_signal_new ("name-changed",
920 		G_TYPE_FROM_CLASS (klass),
921 		G_SIGNAL_RUN_LAST,
922 		G_STRUCT_OFFSET (GogObjectClass, name_changed),
923 		NULL, NULL,
924 		g_cclosure_marshal_VOID__VOID,
925 		G_TYPE_NONE, 0);
926 	gog_object_signals [CHANGED] = g_signal_new ("changed",
927 		G_TYPE_FROM_CLASS (klass),
928 		G_SIGNAL_RUN_LAST,
929 		G_STRUCT_OFFSET (GogObjectClass, changed),
930 		NULL, NULL,
931 		g_cclosure_marshal_VOID__BOOLEAN,
932 		G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
933 	gog_object_signals [UPDATE_EDITOR] = g_signal_new ("update-editor",
934 		G_TYPE_FROM_CLASS (klass),
935 		G_SIGNAL_RUN_LAST,
936 		G_STRUCT_OFFSET (GogObjectClass, update_editor),
937 		NULL, NULL,
938 		g_cclosure_marshal_VOID__VOID,
939 		G_TYPE_NONE, 0);
940 }
941 
942 static void
gog_object_init(GogObject * obj)943 gog_object_init (GogObject *obj)
944 {
945 	obj->children = NULL;
946 	obj->user_name = NULL;
947 	obj->auto_name = NULL;
948 	obj->id = 0;
949 	obj->needs_update = FALSE;
950 	obj->being_updated = FALSE;
951 	obj->explicitly_typed_role = FALSE;
952 	obj->invisible = FALSE;
953 	obj->manual_position.x =
954 	obj->manual_position.y = 0.0;
955 	obj->manual_position.w =
956 	obj->manual_position.h = 1.0;
957 }
958 
959 GSF_CLASS_FULL (GogObject, gog_object,
960 		gog_object_base_init, gog_object_base_finalize,
961 		gog_object_class_init, NULL, gog_object_init,
962 		G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, {})
963 
964 static gboolean
gog_object_is_same_type(GogObject * obj_a,GogObject * obj_b)965 gog_object_is_same_type (GogObject *obj_a, GogObject *obj_b)
966 {
967 	g_return_val_if_fail (obj_a->role != NULL, FALSE);
968 	g_return_val_if_fail (obj_b->role != NULL, FALSE);
969 
970 	if (obj_a->role->naming_conv != obj_b->role->naming_conv)
971 		return FALSE;
972 
973 	if (obj_a->role->naming_conv == GOG_OBJECT_NAME_BY_ROLE)
974 		return (obj_a->role == obj_b->role);
975 
976 	return (G_OBJECT_TYPE (obj_a) == G_OBJECT_TYPE (obj_b));
977 }
978 
979 static void
gog_object_generate_name(GogObject * obj)980 gog_object_generate_name (GogObject *obj)
981 {
982 	GogObjectClass *klass;
983 
984 	char const *type_name;
985 
986 	g_return_if_fail (GOG_IS_OBJECT (obj));
987 
988 	klass = GOG_OBJECT_GET_CLASS (obj);
989 	g_return_if_fail (obj->role != NULL);
990 
991 	switch (obj->role->naming_conv) {
992 	default :
993 	case GOG_OBJECT_NAME_MANUALLY :
994 		g_warning ("Role %s should not be autogenerating names",
995 			   obj->role->id);
996 
997 	case GOG_OBJECT_NAME_BY_ROLE :
998 		g_return_if_fail (obj->role != NULL);
999 		type_name = _(obj->role->id);
1000 		break;
1001 
1002 	case GOG_OBJECT_NAME_BY_TYPE :
1003 		g_return_if_fail (klass->type_name != NULL);
1004 		type_name = _((*klass->type_name) (obj));
1005 		break;
1006 	}
1007 
1008 	if (type_name == NULL)
1009 		type_name =  "BROKEN";
1010 
1011 	g_free (obj->auto_name);
1012 	obj->auto_name =  g_strdup_printf ("%s%d", type_name, obj->id);
1013 }
1014 
1015 unsigned
gog_object_get_id(GogObject const * obj)1016 gog_object_get_id (GogObject const *obj)
1017 {
1018 	g_return_val_if_fail (GOG_IS_OBJECT (obj), 0);
1019 	g_return_val_if_fail (obj != NULL, 0);
1020 
1021 	return obj->id;
1022 }
1023 
1024 static void
gog_object_generate_id(GogObject * obj)1025 gog_object_generate_id (GogObject *obj)
1026 {
1027 	GSList *ptr;
1028 	unsigned id_max = 0;
1029 	GogObject *child;
1030 
1031 	obj->id = 0;
1032 
1033 	if (obj->parent == NULL)
1034 		return;
1035 
1036 	for (ptr = obj->parent->children; ptr != NULL ; ptr = ptr->next) {
1037 		child = GOG_OBJECT (ptr->data);
1038 		if (gog_object_is_same_type (obj, child))
1039 		    id_max = MAX (child->id, id_max);
1040 	}
1041 	obj->id = id_max + 1;
1042 
1043 	gog_object_generate_name (obj);
1044 }
1045 
1046 static void
gog_object_set_id(GogObject * obj,unsigned id)1047 gog_object_set_id (GogObject *obj, unsigned id)
1048 {
1049 	gboolean found = FALSE;
1050 	GSList *ptr;
1051 	GogObject *child;
1052 
1053 	g_return_if_fail (GOG_IS_OBJECT (obj));
1054 
1055 	if (id == 0) {
1056 		gog_object_generate_id (obj);
1057 		return;
1058 	}
1059 
1060 	g_return_if_fail (GOG_OBJECT (obj)->parent != NULL);
1061 
1062 	for (ptr = obj->parent->children; ptr != NULL && !found; ptr = ptr->next) {
1063 		child = GOG_OBJECT (ptr->data);
1064 		found = child->id == id &&
1065 			gog_object_is_same_type (obj, child) &&
1066 			ptr->data != obj;
1067 		}
1068 
1069 	if (found) {
1070 		g_warning ("id %u already exists", id);
1071 		gog_object_generate_id (obj);
1072 		return;
1073 	}
1074 
1075 	if (id == obj->id)
1076 		return;
1077 
1078 	obj->id = id;
1079 	gog_object_generate_name (obj);
1080 }
1081 
1082 static void
dataset_dup(GogDataset const * src,GogDataset * dst)1083 dataset_dup (GogDataset const *src, GogDataset *dst)
1084 {
1085 	gint	     n, last;
1086 	gog_dataset_dims (src, &n, &last);
1087 	for ( ; n <= last ; n++)
1088 		gog_dataset_set_dim (dst, n,
1089 			go_data_dup (gog_dataset_get_dim (src, n)),
1090 			NULL);
1091 }
1092 
1093 /**
1094  * gog_object_dup:
1095  * @src: #GogObject
1096  * @new_parent: #GogObject the parent tree for the object (can be %NULL)
1097  * @datadup: (scope call): a function to duplicate the data (a default one is used if %NULL)
1098  *
1099  * Create a deep copy of @obj using @new_parent as its parent.
1100  *
1101  * Returns: (transfer full): the duplicated object
1102  **/
1103 GogObject *
gog_object_dup(GogObject const * src,GogObject * new_parent,GogDataDuplicator datadup)1104 gog_object_dup (GogObject const *src, GogObject *new_parent, GogDataDuplicator datadup)
1105 {
1106 	guint	     n;
1107 	GParamSpec **props;
1108 	GogObject   *dst = NULL;
1109 	GSList      *ptr;
1110 	GValue	     val = { 0 };
1111 
1112 	if (src == NULL)
1113 		return NULL;
1114 
1115 	g_return_val_if_fail (GOG_OBJECT (src) != NULL, NULL);
1116 
1117 	if (src->role == NULL || src->explicitly_typed_role)
1118 		dst = g_object_new (G_OBJECT_TYPE (src), NULL);
1119 	if (new_parent)
1120 		dst = gog_object_add_by_role (new_parent, src->role, dst);
1121 
1122 	g_return_val_if_fail (GOG_OBJECT (dst) != NULL, NULL);
1123 
1124 	dst->position = src->position;
1125 	/* properties */
1126 	props = g_object_class_list_properties (G_OBJECT_GET_CLASS (src), &n);
1127 	while (n-- > 0)
1128 		if (props[n]->flags & GO_PARAM_PERSISTENT) {
1129 			g_value_init (&val, props[n]->value_type);
1130 			g_object_get_property (G_OBJECT (src), props[n]->name, &val);
1131 			g_object_set_property (G_OBJECT (dst), props[n]->name, &val);
1132 			g_value_unset (&val);
1133 		}
1134 	g_free (props);
1135 
1136 	if (GOG_IS_DATASET (src)) {	/* convenience to save data */
1137 		if (datadup)
1138 			datadup (GOG_DATASET (src), GOG_DATASET (dst));
1139 		else
1140 			dataset_dup (GOG_DATASET (src), GOG_DATASET (dst));
1141 	}
1142 	if (GOG_IS_GRAPH (src))
1143 		GOG_GRAPH (dst)->doc = GOG_GRAPH (src)->doc;
1144 	else if (GOG_IS_CHART (src))
1145 		GOG_CHART (dst)->axis_set = GOG_CHART (src)->axis_set;
1146 
1147 	for (ptr = src->children; ptr != NULL ; ptr = ptr->next)
1148 		/* children added directly to new parent, no need to use the
1149 		 * function result */
1150 		gog_object_dup (ptr->data, dst, datadup);
1151 
1152 	return dst;
1153 }
1154 
1155 /**
1156  * gog_object_get_parent:
1157  * @obj: a #GogObject
1158  *
1159  * Returns: (transfer none): @obj's parent, potentially %NULL if it has not been added to a
1160  * 	heirarchy yet.  does not change ref-count in any way.
1161  **/
1162 GogObject *
gog_object_get_parent(GogObject const * obj)1163 gog_object_get_parent (GogObject const *obj)
1164 {
1165 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1166 	return obj->parent;
1167 }
1168 
1169 /**
1170  * gog_object_get_parent_typed:
1171  * @obj: a #GogObject
1172  * @t: a #GType
1173  *
1174  * Returns: (transfer none): @obj's parent of type @type, potentially %NULL if it has not been
1175  * added to a hierarchy yet or none of the parents are of type @type.
1176  **/
1177 GogObject *
gog_object_get_parent_typed(GogObject const * obj,GType t)1178 gog_object_get_parent_typed (GogObject const *obj, GType t)
1179 {
1180 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1181 
1182 	for (; obj != NULL ; obj = obj->parent)
1183 		if (G_TYPE_CHECK_INSTANCE_TYPE (obj, t))
1184 			return GOG_OBJECT (obj); /* const cast */
1185 	return NULL;
1186 }
1187 
1188 /**
1189  * gog_object_get_graph:
1190  * @obj: const * #GogObject
1191  *
1192  * Returns: (transfer none): the parent graph.
1193  **/
1194 GogGraph *
gog_object_get_graph(GogObject const * obj)1195 gog_object_get_graph (GogObject const *obj)
1196 {
1197 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1198 
1199 	for (; obj != NULL ; obj = obj->parent)
1200 		if (GOG_IS_GRAPH (obj))
1201 			return GOG_GRAPH (obj);
1202 	return NULL;
1203 }
1204 
1205 /**
1206  * gog_object_get_theme:
1207  * @obj: const * #GogObject
1208  *
1209  * Returns: (transfer none): the parent graph theme.
1210  **/
1211 GogTheme *
gog_object_get_theme(GogObject const * obj)1212 gog_object_get_theme (GogObject const *obj)
1213 {
1214 	GogGraph *graph = gog_object_get_graph (obj);
1215 
1216 	return (graph != NULL) ? gog_graph_get_theme (graph) : NULL;
1217 }
1218 
1219 /**
1220  * gog_object_get_name:
1221  * @obj: a #GogObject
1222  *
1223  * No need to free the result
1224  *
1225  * Returns: a name.
1226  **/
1227 char const *
gog_object_get_name(GogObject const * obj)1228 gog_object_get_name (GogObject const *obj)
1229 {
1230 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1231 	return (obj->user_name != NULL && *obj->user_name != '\0') ? obj->user_name : obj->auto_name;
1232 }
1233 
1234 /**
1235  * gog_object_set_name:
1236  * @obj: #GogObject
1237  * @name: (transfer full): The new name for @obj
1238  * @err: #GError
1239  *
1240  * Assign the new name and signals that it has changed.
1241  * NOTE : it _absorbs_ @name rather than copying it, and generates a new name
1242  * if @name == %NULL
1243  **/
1244 void
gog_object_set_name(GogObject * obj,char * name,GError ** err)1245 gog_object_set_name (GogObject *obj, char *name, GError **err)
1246 {
1247 	GogObject *tmp;
1248 
1249 	g_return_if_fail (GOG_IS_OBJECT (obj));
1250 
1251 	if (obj->user_name == name)
1252 		return;
1253 	g_free (obj->user_name);
1254 	obj->user_name = name;
1255 
1256 	g_signal_emit (G_OBJECT (obj),
1257 		gog_object_signals [NAME_CHANGED], 0);
1258 
1259 	for (tmp = obj; tmp != NULL ; tmp = tmp->parent)
1260 		g_signal_emit (G_OBJECT (tmp),
1261 			gog_object_signals [CHILD_NAME_CHANGED], 0, obj);
1262 }
1263 
1264 /**
1265  * gog_object_get_children:
1266  * @obj: a #GogObject
1267  * @filter: an optional #GogObjectRole to use as a filter
1268  *
1269  * Returns: (element-type GogObject) (transfer container): list of @obj's
1270  * Children.  Caller must free the list, but not the children.
1271  **/
1272 GSList *
gog_object_get_children(GogObject const * obj,GogObjectRole const * filter)1273 gog_object_get_children (GogObject const *obj, GogObjectRole const *filter)
1274 {
1275 	GSList *ptr, *res = NULL;
1276 
1277 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1278 
1279 	if (filter == NULL)
1280 		return g_slist_copy (obj->children);
1281 
1282 	for (ptr = obj->children ; ptr != NULL ; ptr = ptr->next)
1283 		if (GOG_OBJECT (ptr->data)->role == filter)
1284 			res = g_slist_prepend (res, ptr->data);
1285 	return g_slist_reverse (res);
1286 }
1287 
1288 /**
1289  * gog_object_get_child_by_role:
1290  * @obj: a #GogObject
1291  * @role: a #GogObjectRole to use as a filter
1292  *
1293  * A convenience routine to find a unique child with @role.
1294  *
1295  * Returns: (transfer none): %NULL and spews an error if there is more than one.
1296  **/
1297 GogObject *
gog_object_get_child_by_role(GogObject const * obj,GogObjectRole const * role)1298 gog_object_get_child_by_role (GogObject const *obj, GogObjectRole const *role)
1299 {
1300 	GogObject *res = NULL;
1301 	GSList *children;
1302 
1303 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1304 
1305 	children = gog_object_get_children (obj, role);
1306 	if (children != NULL && children->next == NULL)
1307 		res = children->data;
1308 	g_slist_free (children);
1309 	return res;
1310 }
1311 
1312 /**
1313  * gog_object_get_child_by_name:
1314  * @obj: a #GogObject
1315  * @name: a #char to use as a role name filter
1316  *
1317  * A convenience routine to find a unique child with role == @name
1318  *
1319  * Returns: (transfer none): %NULL and spews an error if there is more than one.
1320  **/
1321 GogObject *
gog_object_get_child_by_name(GogObject const * obj,char const * name)1322 gog_object_get_child_by_name (GogObject const *obj, char const *name)
1323 {
1324 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1325 	return gog_object_get_child_by_role (obj,
1326 		gog_object_find_role_by_name (obj, name));
1327 }
1328 
1329 /**
1330  * gog_object_is_deletable:
1331  * @obj: a #GogObject
1332  *
1333  * Returns: %TRUE if @obj can be deleted.
1334  **/
1335 gboolean
gog_object_is_deletable(GogObject const * obj)1336 gog_object_is_deletable (GogObject const *obj)
1337 {
1338 	g_return_val_if_fail (GOG_IS_OBJECT (obj), FALSE);
1339 
1340 	if (GOG_IS_GRAPH (obj))
1341 		return FALSE;
1342 
1343 	return obj->role == NULL || obj->role->can_remove == NULL ||
1344 		(obj->role->can_remove) (obj);
1345 }
1346 
1347 struct possible_add_closure {
1348 	GSList *res;
1349 	GogObject const *parent;
1350 };
1351 
1352 static void
cb_collect_possible_additions(char const * name,GogObjectRole const * role,struct possible_add_closure * data)1353 cb_collect_possible_additions (char const *name, GogObjectRole const *role,
1354 			       struct possible_add_closure *data)
1355 {
1356 	if (role->can_add == NULL || (role->can_add) (data->parent))
1357 		data->res = g_slist_prepend (data->res, (gpointer)role);
1358 }
1359 
1360 static int
gog_object_position_cmp(GogObjectPosition pos)1361 gog_object_position_cmp (GogObjectPosition pos)
1362 {
1363 	if (pos & GOG_POSITION_COMPASS)
1364 		return 0;
1365 	if (GOG_POSITION_IS_SPECIAL (pos) ||
1366 	    GOG_POSITION_IS_PADDING (pos))
1367 		return 2;
1368 	return 1; /* GOG_POSITION_MANUAL */
1369 }
1370 
1371 static int
gog_role_cmp(GogObjectRole const * a,GogObjectRole const * b)1372 gog_role_cmp (GogObjectRole const *a, GogObjectRole const *b)
1373 {
1374 	int index_a = gog_object_position_cmp (a->allowable_positions);
1375 	int index_b = gog_object_position_cmp (b->allowable_positions);
1376 
1377 	if (b->priority != a->priority)
1378 		return b->priority - a->priority;
1379 
1380 	/* intentionally reverse to put SPECIAL at the top */
1381 	if (index_a < index_b)
1382 		return 1;
1383 	else if (index_a > index_b)
1384 		return -1;
1385 	return 0;
1386 }
1387 
1388 static int
gog_role_cmp_full(GogObjectRole const * a,GogObjectRole const * b)1389 gog_role_cmp_full (GogObjectRole const *a, GogObjectRole const *b)
1390 {
1391 	int res = gog_role_cmp (a, b);
1392 	if (res != 0)
1393 		return res;
1394 	return g_utf8_collate (a->id, b->id);
1395 }
1396 
1397 /**
1398  * gog_object_possible_additions:
1399  * @parent: a #GogObject
1400  *
1401  * Returns: (element-type GogObjectRole) (transfer container): a list
1402  * of GogObjectRoles that could be added. The resulting list needs to be freed
1403  **/
1404 GSList *
gog_object_possible_additions(GogObject const * parent)1405 gog_object_possible_additions (GogObject const *parent)
1406 {
1407 	GogObjectClass *klass;
1408 
1409 	g_return_val_if_fail (GOG_IS_OBJECT (parent), NULL);
1410 
1411 	klass = GOG_OBJECT_GET_CLASS (parent);
1412 
1413 	if (klass->roles != NULL) {
1414 		struct possible_add_closure data;
1415 		data.res = NULL;
1416 		data.parent = parent;
1417 
1418 		g_hash_table_foreach (klass->roles,
1419 			(GHFunc) cb_collect_possible_additions, &data);
1420 
1421 		return g_slist_sort (data.res, (GCompareFunc) gog_role_cmp_full);
1422 	}
1423 
1424 	return NULL;
1425 }
1426 
1427 /**
1428  * gog_object_can_reorder:
1429  * @obj: #GogObject
1430  * @inc_ok: optionally %NULL pointer for result.
1431  * @dec_ok: optionally %NULL pointer for result.
1432  *
1433  * If @obj can move forward or backward in its parents child list
1434  **/
1435 void
gog_object_can_reorder(GogObject const * obj,gboolean * inc_ok,gboolean * dec_ok)1436 gog_object_can_reorder (GogObject const *obj, gboolean *inc_ok, gboolean *dec_ok)
1437 {
1438 	GogObject const *parent;
1439 	GSList *ptr;
1440 
1441 	g_return_if_fail (GOG_IS_OBJECT (obj));
1442 
1443 	if (inc_ok != NULL)
1444 		*inc_ok = FALSE;
1445 	if (dec_ok != NULL)
1446 		*dec_ok = FALSE;
1447 
1448 	if (obj->parent == NULL || gog_object_get_graph (obj) == NULL)
1449 		return;
1450 	parent = obj->parent;
1451 	ptr = parent->children;
1452 
1453 	g_return_if_fail (ptr != NULL);
1454 
1455 	/* find a pointer to the previous sibling */
1456 	if (ptr->data != obj) {
1457 		while (ptr->next != NULL && ptr->next->data != obj)
1458 			ptr = ptr->next;
1459 
1460 		g_return_if_fail (ptr->next != NULL);
1461 
1462 		if (inc_ok != NULL &&
1463 		    !gog_role_cmp (((GogObject *)ptr->data)->role, obj->role))
1464 			*inc_ok = TRUE;
1465 
1466 		ptr = ptr->next;
1467 	}
1468 
1469 	/* ptr now points at @obj */
1470 	if (dec_ok != NULL && ptr->next != NULL &&
1471 	    !gog_role_cmp (obj->role, ((GogObject *)ptr->next->data)->role))
1472 		*dec_ok = TRUE;
1473 }
1474 
1475 /**
1476  * gog_object_reorder:
1477  * @obj: #GogObject
1478  * @inc:
1479  * @goto_max:
1480  *
1481  * Returns: (transfer none): the object just before @obj in the new ordering.
1482  **/
1483 GogObject *
gog_object_reorder(GogObject const * obj,gboolean inc,gboolean goto_max)1484 gog_object_reorder (GogObject const *obj, gboolean inc, gboolean goto_max)
1485 {
1486 	GogObject *parent, *obj_follows;
1487 	GSList **ptr, *tmp;
1488 
1489 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1490 
1491 	if (obj->parent == NULL || gog_object_get_graph (obj) == NULL)
1492 		return NULL;
1493 	parent = obj->parent;
1494 
1495 	if (inc)
1496 		parent->children = g_slist_reverse (parent->children);
1497 
1498 	for (ptr = &parent->children; *ptr != NULL && (*ptr)->data != obj ;)
1499 		ptr = &(*ptr)->next;
1500 
1501 	g_return_val_if_fail (*ptr != NULL, NULL);
1502 	g_return_val_if_fail ((*ptr)->next != NULL, NULL);
1503 
1504 	tmp = *ptr;
1505 	*ptr = tmp->next;
1506 	ptr = &(*ptr)->next;
1507 
1508 	while (goto_max && *ptr != NULL &&
1509 	       !gog_role_cmp (obj->role, ((GogObject *)((*ptr)->data))->role))
1510 		ptr = &(*ptr)->next;
1511 
1512 	tmp->next = *ptr;
1513 	*ptr = tmp;
1514 
1515 	if (inc)
1516 		parent->children = g_slist_reverse (parent->children);
1517 
1518 	if (parent->children->data != obj) {
1519 		for (tmp = parent->children ; tmp->next->data != obj ; )
1520 			tmp = tmp->next;
1521 		obj_follows = tmp->data;
1522 	} else
1523 		obj_follows = NULL;
1524 
1525 	/* Pass the sibling that precedes obj, or %NULL if is the head */
1526 	g_signal_emit (G_OBJECT (parent),
1527 		gog_object_signals [CHILDREN_REORDERED], 0);
1528 	gog_object_emit_changed (parent, FALSE);
1529 
1530 	return obj_follows;
1531 }
1532 
1533 /**
1534  * gog_object_get_editor:
1535  * @obj: a #GogObject
1536  * @dalloc: a #GogDataAllocator
1537  * @cc: a #GOCmdContext
1538  *
1539  * Builds an object property editor, by calling GogObject::populate_editor
1540  * virtual functions.
1541  *
1542  * Returns: (transfer full): a #GtkNotebook widget
1543  **/
1544 gpointer
gog_object_get_editor(GogObject * obj,GogDataAllocator * dalloc,GOCmdContext * cc)1545 gog_object_get_editor (GogObject *obj, GogDataAllocator *dalloc,
1546 		       GOCmdContext *cc)
1547 {
1548 #ifdef GOFFICE_WITH_GTK
1549 	GtkWidget *notebook;
1550 	GOEditor *editor;
1551 	GogObjectClass *klass;
1552 
1553 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1554 
1555 	klass = GOG_OBJECT_GET_CLASS (obj);
1556 
1557 	editor = go_editor_new ();
1558 	go_editor_set_use_scrolled_window (editor, TRUE);
1559 	if (klass->populate_editor) {
1560 		/* If there are pending updates do them before creating the editor
1561 		 * to avoid expensive widget changes later */
1562 		gog_graph_force_update (gog_object_get_graph (obj));
1563 		(*klass->populate_editor) (obj, editor, dalloc, cc);
1564 	}
1565 
1566 	notebook = go_editor_get_notebook (editor);
1567 
1568 	go_editor_free (editor);
1569 
1570 	return notebook;
1571 #else
1572 	return NULL;
1573 #endif
1574 }
1575 
1576 /**
1577  * gog_object_new_view:
1578  * @obj: a #GogObject
1579  * @parent: parent view
1580  *
1581  * Creates a new #GogView associated to @obj, and sets its parent to @parent.
1582  *
1583  * Returns: (transfer full): a new #GogView
1584  **/
1585 GogView *
gog_object_new_view(GogObject const * obj,GogView * parent)1586 gog_object_new_view (GogObject const *obj, GogView *parent)
1587 {
1588 	GogObjectClass *klass;
1589 
1590 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
1591 
1592 	klass = GOG_OBJECT_GET_CLASS (obj);
1593 
1594 	if (klass->view_type != 0)
1595 		/* set model before parent */
1596 		return g_object_new (klass->view_type,
1597 			"model", obj,
1598 			"parent", parent,
1599 			NULL);
1600 
1601 	return NULL;
1602 }
1603 
1604 void
gog_object_update(GogObject * obj)1605 gog_object_update (GogObject *obj)
1606 {
1607 	GogObjectClass *klass;
1608 	GSList *ptr;
1609 
1610 	g_return_if_fail (GOG_IS_OBJECT (obj));
1611 
1612 	klass = GOG_OBJECT_GET_CLASS (obj);
1613 
1614 	ptr = obj->children; /* depth first */
1615 	for (; ptr != NULL ; ptr = ptr->next)
1616 		gog_object_update (ptr->data);
1617 
1618 	if (obj->needs_update) {
1619 		obj->needs_update = FALSE;
1620 		obj->being_updated = TRUE;
1621 		gog_debug (0, g_warning ("updating %s (%p)", G_OBJECT_TYPE_NAME (obj), obj););
1622 		if (klass->update != NULL)
1623 			(*klass->update) (obj);
1624 		obj->being_updated = FALSE;
1625 	}
1626 }
1627 
1628 gboolean
gog_object_request_update(GogObject * obj)1629 gog_object_request_update (GogObject *obj)
1630 {
1631 	GogGraph *graph;
1632 	g_return_val_if_fail (GOG_OBJECT (obj), FALSE);
1633 	g_return_val_if_fail (!obj->being_updated, FALSE);
1634 
1635 	if (obj->needs_update)
1636 		return FALSE;
1637 
1638 	graph = gog_object_get_graph (obj);
1639 	if (graph == NULL) /* we are not linked into a graph yet */
1640 		return FALSE;
1641 
1642 	gog_graph_request_update (graph);
1643 	obj->needs_update = TRUE;
1644 
1645 	return TRUE;
1646 }
1647 
1648 void
gog_object_emit_changed(GogObject * obj,gboolean resize)1649 gog_object_emit_changed (GogObject *obj, gboolean resize)
1650 {
1651 	GogObjectClass *gog_klass;
1652 
1653 	g_return_if_fail (GOG_OBJECT (obj));
1654 
1655 	gog_klass = GOG_OBJECT_GET_CLASS (obj);
1656 
1657 	if (gog_klass->use_parent_as_proxy) {
1658 		obj = obj->parent;
1659 		if (obj != NULL) {
1660 			g_return_if_fail (GOG_IS_OBJECT (obj));
1661 			gog_object_emit_changed (obj, resize);
1662 		}
1663 		return;
1664 	}
1665 	g_signal_emit (G_OBJECT (obj),
1666 		gog_object_signals [CHANGED], 0, resize);
1667 }
1668 
1669 /**
1670  * gog_object_request_editor_update:
1671  * @obj: #GogObject
1672  *
1673  * Emits a update-editor signal. This signal should be used by object editors
1674  * in order to refresh their states.
1675  **/
1676 void
gog_object_request_editor_update(GogObject * obj)1677 gog_object_request_editor_update (GogObject *obj)
1678 {
1679 	g_signal_emit (G_OBJECT (obj),
1680 		gog_object_signals [UPDATE_EDITOR], 0);
1681 }
1682 
1683 /******************************************************************************/
1684 
1685 /**
1686  * gog_object_clear_parent:
1687  * @obj: #GogObject
1688  *
1689  * Does _not_ unref the child, which in effect adds a ref by freeing up the ref
1690  * previously associated with the parent.
1691  *
1692  * Returns: %TRUE on success.
1693  **/
1694 gboolean
gog_object_clear_parent(GogObject * obj)1695 gog_object_clear_parent (GogObject *obj)
1696 {
1697 	GogObjectClass *klass;
1698 	GogObject *parent;
1699 
1700 	g_return_val_if_fail (GOG_OBJECT (obj), FALSE);
1701 	g_return_val_if_fail (obj->parent != NULL, FALSE);
1702 	g_return_val_if_fail (gog_object_is_deletable (obj), FALSE);
1703 
1704 	klass = GOG_OBJECT_GET_CLASS (obj);
1705 	parent = obj->parent;
1706 	(*klass->parent_changed) (obj, FALSE);
1707 
1708 	if (obj->role != NULL && obj->role->pre_remove != NULL)
1709 		(obj->role->pre_remove) (parent, obj);
1710 
1711 	parent->children = g_slist_remove (parent->children, obj);
1712 	obj->parent = NULL;
1713 
1714 	if (obj->role != NULL && obj->role->post_remove != NULL)
1715 		(obj->role->post_remove) (parent, obj);
1716 
1717 	obj->role = NULL;
1718 
1719 	g_signal_emit (G_OBJECT (parent),
1720 		gog_object_signals [CHILD_REMOVED], 0, obj);
1721 
1722 	return TRUE;
1723 }
1724 
1725 /**
1726  * gog_object_set_parent:
1727  * @child: (transfer full): #GogObject.
1728  * @parent: #GogObject.
1729  * @id: optionally %NULL.
1730  * @role: a static string that can be sent to @parent::add
1731  *
1732  * Absorbs a ref to @child
1733  *
1734  * Returns: %TRUE on success
1735  **/
1736 gboolean
gog_object_set_parent(GogObject * child,GogObject * parent,GogObjectRole const * role,unsigned int id)1737 gog_object_set_parent (GogObject *child, GogObject *parent,
1738 		       GogObjectRole const *role, unsigned int id)
1739 {
1740 	GogObjectClass *klass;
1741 	GSList **step;
1742 
1743 	g_return_val_if_fail (GOG_OBJECT (child), FALSE);
1744 	g_return_val_if_fail (child->parent == NULL, FALSE);
1745 	g_return_val_if_fail (role != NULL, FALSE);
1746 
1747 	klass = GOG_OBJECT_GET_CLASS (child);
1748 	child->parent	= parent;
1749 	child->role	= role;
1750 	child->position = role->default_position;
1751 
1752 	/* Insert sorted based on hokey little ordering */
1753 	step = &parent->children;
1754 	while (*step != NULL &&
1755 	       gog_role_cmp_full (GOG_OBJECT ((*step)->data)->role, role) >= 0)
1756 		step = &((*step)->next);
1757 	*step = g_slist_prepend (*step, child);
1758 
1759 	if (id != 0)
1760 		gog_object_set_id (child, id);
1761 	else
1762 		gog_object_generate_id (child);
1763 
1764 	if (role->post_add != NULL)
1765 		(role->post_add) (parent, child);
1766 	(*klass->parent_changed) (child, TRUE);
1767 
1768 	g_signal_emit (G_OBJECT (parent),
1769 		gog_object_signals [CHILD_ADDED], 0, child);
1770 
1771 	return TRUE;
1772 }
1773 
1774 /**
1775  * gog_object_add_by_role:
1776  * @parent: #GogObject
1777  * @role: #GogObjectRole
1778  * @child: (transfer full) (allow-none): #GogObject
1779  *
1780  * Absorb a ref to @child if it is non-NULL.
1781  * Returns: (transfer none): @child or a newly created object with @role.
1782  **/
1783 GogObject *
gog_object_add_by_role(GogObject * parent,GogObjectRole const * role,GogObject * child)1784 gog_object_add_by_role (GogObject *parent, GogObjectRole const *role, GogObject *child)
1785 {
1786 	GType is_a;
1787 	gboolean explicitly_typed_role;
1788 
1789 	g_return_val_if_fail (role != NULL, NULL);
1790 	g_return_val_if_fail (GOG_OBJECT (parent) != NULL, NULL);
1791 
1792 	is_a = g_type_from_name (role->is_a_typename);
1793 
1794 	g_return_val_if_fail (is_a != 0, NULL);
1795 
1796 	/*
1797 	 * It's unclear why we need this flag; just set is to indicate a non-default
1798 	 * type.  We used to set for any pre-allocated child.
1799 	 */
1800 	explicitly_typed_role = (child && G_OBJECT_TYPE (child) != is_a);
1801 
1802 	if (child == NULL) {
1803 		child = (role->allocate)
1804 			? (role->allocate) (parent)
1805 			: (G_TYPE_IS_ABSTRACT (is_a)? NULL: g_object_new (is_a, NULL));
1806 
1807 		/* g_object_new or the allocator have already generated an
1808 		 * error message, just exit */
1809 		if (child == NULL)
1810 			return NULL;
1811 	}
1812 
1813 	g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (child, is_a), NULL);
1814 
1815 	child->explicitly_typed_role = explicitly_typed_role;
1816 	if (gog_object_set_parent (child, parent, role, 0))
1817 		return child;
1818 	g_object_unref (child);
1819 	return NULL;
1820 }
1821 
1822 /**
1823  * gog_object_add_by_name:
1824  * @parent: #GogObject
1825  * @role:
1826  * @child: (transfer full) (allow-none): optionally null #GogObject
1827  *
1828  * Returns: (transfer none):  a newly created child of @parent in @role.  If @child is provided,
1829  * it is assumed to be an unaffiliated object that will be assigned in @role.
1830  * On failure return NULL.
1831  **/
1832 GogObject *
gog_object_add_by_name(GogObject * parent,char const * role,GogObject * child)1833 gog_object_add_by_name (GogObject *parent,
1834 			char const *role, GogObject *child)
1835 {
1836 	g_return_val_if_fail (GOG_IS_OBJECT (parent), NULL);
1837 	return gog_object_add_by_role (parent,
1838 		gog_object_find_role_by_name (parent, role), child);
1839 }
1840 
1841 /**
1842  * gog_object_set_invisible:
1843  * @obj: #GogObject
1844  * @invisible:
1845  **/
1846 void
gog_object_set_invisible(GogObject * obj,gboolean invisible)1847 gog_object_set_invisible (GogObject *obj, gboolean invisible)
1848 {
1849 	unsigned int new_val = invisible ? 1 : 0;
1850 	if ((!obj->invisible) != !(new_val)) {
1851 		obj->invisible = new_val;
1852 		gog_object_emit_changed (obj, TRUE);
1853 	}
1854 }
1855 
1856 /**
1857  * gog_object_get_position_flags:
1858  * @obj: #GogObject
1859  * @mask: #GogObjectPosition
1860  *
1861  * Returns: @obj's position flags, masked by @mask.
1862  **/
1863 GogObjectPosition
gog_object_get_position_flags(GogObject const * obj,GogObjectPosition mask)1864 gog_object_get_position_flags (GogObject const *obj, GogObjectPosition mask)
1865 {
1866 	g_return_val_if_fail (GOG_IS_OBJECT (obj), GOG_POSITION_SPECIAL & mask);
1867 	return obj->position & mask;
1868 }
1869 
1870 /**
1871  * gog_object_set_position_flags:
1872  * @obj: #GogObject
1873  * @flags: #GogObjectPosition
1874  * @mask: #GogObjectPosition
1875  *
1876  * Attempts to set the position flags of @obj to @flags.
1877  *
1878  * Returns: TRUE the new flags are permitted.
1879  **/
1880 gboolean
gog_object_set_position_flags(GogObject * obj,GogObjectPosition flags,GogObjectPosition mask)1881 gog_object_set_position_flags (GogObject *obj, GogObjectPosition flags, GogObjectPosition mask)
1882 {
1883 	g_return_val_if_fail (GOG_IS_OBJECT (obj), FALSE);
1884 
1885 	if (obj->role == NULL)
1886 		return FALSE;
1887 
1888 	if ((obj->position & mask) == flags)
1889 		return TRUE;
1890 
1891 	if ((flags & obj->role->allowable_positions) !=
1892 	    (flags & (GOG_POSITION_COMPASS | GOG_POSITION_ANY_MANUAL |
1893 	              GOG_POSITION_ANY_MANUAL_SIZE))) {
1894 		g_warning ("[GogObject::set_position_flags] Invalid flags (%s) flags=0x%x  allowable=0x%x",
1895 			   gog_object_get_name (obj),
1896 			   flags, obj->role->allowable_positions);
1897 		return FALSE;
1898 	}
1899 	obj->position = (obj->position & ~mask) | (flags & mask);
1900 	if (GOG_IS_CHART (obj))
1901 		gog_graph_validate_chart_layout (GOG_GRAPH (obj->parent));
1902 	gog_object_emit_changed (obj, TRUE);
1903 	return TRUE;
1904 }
1905 
1906 /**
1907  * gog_object_get_manual_position:
1908  * @obj: #GogObject
1909  * @pos: #GogViewAllocation
1910  *
1911  * FIXME
1912  **/
1913 void
gog_object_get_manual_position(GogObject * gobj,GogViewAllocation * pos)1914 gog_object_get_manual_position (GogObject *gobj, GogViewAllocation *pos)
1915 {
1916 	g_return_if_fail (GOG_OBJECT (gobj) != NULL);
1917 
1918 	if (pos != NULL)
1919 		*pos = gobj->manual_position;
1920 }
1921 
1922 /**
1923  * gog_object_set_manual_position:
1924  * @obj: #GogObject
1925  * @pos: #GogViewAllocation
1926  *
1927  * set manual position of given object, in points.
1928  **/
1929 void
gog_object_set_manual_position(GogObject * gobj,GogViewAllocation const * pos)1930 gog_object_set_manual_position (GogObject *gobj, GogViewAllocation const *pos)
1931 {
1932 	g_return_if_fail (GOG_OBJECT (gobj) != NULL);
1933 
1934 	if (gobj->manual_position.x == pos->x &&
1935 	    gobj->manual_position.y == pos->y &&
1936 	    gobj->manual_position.w == pos->w &&
1937 	    gobj->manual_position.h == pos->h)
1938 		return;
1939 
1940 	gobj->manual_position = *pos;
1941 	gog_object_emit_changed (gobj, TRUE);
1942 }
1943 
1944 /**
1945  * gog_object_get_manual_allocation:
1946  * @gobj: #GogObject
1947  * @parent_allocation: #GogViewAllocation
1948  * @requisition: #GogViewRequisition
1949  *
1950  * Returns: manual allocation of a GogObject given its parent allocation and
1951  * its size request.
1952  **/
1953 GogViewAllocation
gog_object_get_manual_allocation(GogObject * gobj,GogViewAllocation const * parent_allocation,GogViewRequisition const * requisition)1954 gog_object_get_manual_allocation (GogObject *gobj,
1955 				  GogViewAllocation const *parent_allocation,
1956 				  GogViewRequisition const *requisition)
1957 {
1958 	GogViewAllocation pos;
1959 	unsigned anchor, size, expand;
1960 	GogManualSizeMode size_mode = gog_object_get_manual_size_mode (gobj);
1961 
1962 	pos.x = parent_allocation->x + gobj->manual_position.x * parent_allocation->w;
1963 	pos.y = parent_allocation->y + gobj->manual_position.y * parent_allocation->h;
1964 
1965 	size = gog_object_get_position_flags (gobj, GOG_POSITION_ANY_MANUAL_SIZE);
1966 	anchor = gog_object_get_position_flags (gobj, GOG_POSITION_ANCHOR);
1967 	expand = gobj->role->allowable_positions & GOG_POSITION_EXPAND;
1968 
1969 	if ((size_mode & GOG_MANUAL_SIZE_WIDTH) &&
1970 	         (size & (GOG_POSITION_MANUAL_W | GOG_POSITION_MANUAL_W_ABS)))
1971 		pos.w = gobj->manual_position.w * parent_allocation->w;
1972 	else  if (expand & GOG_POSITION_HEXPAND) {
1973 		/* use available width */
1974 		switch (anchor) {
1975 			case GOG_POSITION_ANCHOR_N:
1976 			case GOG_POSITION_ANCHOR_CENTER:
1977 			case GOG_POSITION_ANCHOR_S:
1978 				pos.w = MIN (gobj->manual_position.x, 1 - gobj->manual_position.x)
1979 						 * 2. * parent_allocation->w;
1980 				break;
1981 			case GOG_POSITION_ANCHOR_SE:
1982 			case GOG_POSITION_ANCHOR_E:
1983 			case GOG_POSITION_ANCHOR_NE:
1984 				pos.w = gobj->manual_position.x * parent_allocation->w;
1985 				break;
1986 			default:
1987 				pos.w = (1 - gobj->manual_position.x) * parent_allocation->w;
1988 				break;
1989 		}
1990 		if (pos.w < requisition->w)
1991 			pos.w = requisition->w;
1992 	} else
1993 		pos.w = requisition->w;
1994 
1995 	switch (anchor) {
1996 		case GOG_POSITION_ANCHOR_N:
1997 		case GOG_POSITION_ANCHOR_CENTER:
1998 		case GOG_POSITION_ANCHOR_S:
1999 			pos.x -= pos.w / 2.0;
2000 			break;
2001 		case GOG_POSITION_ANCHOR_SE:
2002 		case GOG_POSITION_ANCHOR_E:
2003 		case GOG_POSITION_ANCHOR_NE:
2004 			pos.x -= pos.w;
2005 			break;
2006 		default:
2007 			break;
2008 	}
2009 	if ((size_mode & GOG_MANUAL_SIZE_HEIGHT) &&
2010 	         (size & (GOG_POSITION_MANUAL_H | GOG_POSITION_MANUAL_H_ABS)))
2011 		pos.h = gobj->manual_position.h * parent_allocation->h;
2012 	else  if (expand & GOG_POSITION_VEXPAND) {
2013 		/* use available width */
2014 		switch (anchor) {
2015 			case GOG_POSITION_ANCHOR_E:
2016 			case GOG_POSITION_ANCHOR_CENTER:
2017 			case GOG_POSITION_ANCHOR_W:
2018 				pos.h = MIN (gobj->manual_position.y, 1 - gobj->manual_position.y)
2019 						 * 2. * parent_allocation->h;
2020 				break;
2021 			case GOG_POSITION_ANCHOR_SE:
2022 			case GOG_POSITION_ANCHOR_S:
2023 			case GOG_POSITION_ANCHOR_SW:
2024 				pos.h = gobj->manual_position.y * parent_allocation->h;
2025 				break;
2026 			default:
2027 				pos.h = (1 - gobj->manual_position.y) * parent_allocation->h;
2028 				break;
2029 		}
2030 		if (pos.h < requisition->h)
2031 			pos.h = requisition->h;
2032 	} else
2033 		pos.h = requisition->h;
2034 	switch (anchor) {
2035 		case GOG_POSITION_ANCHOR_E:
2036 		case GOG_POSITION_ANCHOR_CENTER:
2037 		case GOG_POSITION_ANCHOR_W:
2038 			pos.y -= pos.h / 2.0;
2039 			break;
2040 		case GOG_POSITION_ANCHOR_SE:
2041 		case GOG_POSITION_ANCHOR_S:
2042 		case GOG_POSITION_ANCHOR_SW:
2043 			pos.y -= pos.h;
2044 			break;
2045 		default:
2046 			break;
2047 	}
2048 
2049 	return pos;
2050 }
2051 
2052 /*
2053  * gog_object_is_default_position_flags:
2054  * @obj: a #GogObject
2055  * @name: name of the position property
2056  *
2057  * returns: true if the current position flags corresponding to the property @name
2058  * are the defaults stored in @obj role.
2059  **/
2060 gboolean
gog_object_is_default_position_flags(GogObject const * obj,char const * name)2061 gog_object_is_default_position_flags (GogObject const *obj, char const *name)
2062 {
2063 	int mask;
2064 
2065 	g_return_val_if_fail (name != NULL, FALSE);
2066 
2067 	if (obj->role == NULL)
2068 		return FALSE;
2069 
2070 	if (strcmp (name, "compass") == 0)
2071 		mask = GOG_POSITION_COMPASS;
2072 	else if (strcmp (name, "alignment") == 0)
2073 		mask = GOG_POSITION_ALIGNMENT;
2074 	else if (strcmp (name, "anchor") == 0)
2075 		mask = GOG_POSITION_ANCHOR;
2076 	else
2077 		return FALSE;
2078 
2079 	return (obj->position & mask) == (obj->role->default_position & mask);
2080 }
2081 
2082 GogObjectRole const *
gog_object_find_role_by_name(GogObject const * obj,char const * role)2083 gog_object_find_role_by_name (GogObject const *obj, char const *role)
2084 {
2085 	GogObjectClass *klass;
2086 
2087 	g_return_val_if_fail (GOG_IS_OBJECT (obj), NULL);
2088 
2089 	klass = GOG_OBJECT_GET_CLASS (obj);
2090 
2091 	return g_hash_table_lookup (klass->roles, role);
2092 }
2093 
2094 static void
cb_copy_hash_table(gpointer key,gpointer value,GHashTable * hash_table)2095 cb_copy_hash_table (gpointer key, gpointer value, GHashTable *hash_table)
2096 {
2097 	g_hash_table_insert (hash_table, key, value);
2098 }
2099 
2100 static void
gog_object_allocate_roles(GogObjectClass * klass)2101 gog_object_allocate_roles (GogObjectClass *klass)
2102 {
2103 	GHashTable *roles = g_hash_table_new (g_str_hash, g_str_equal);
2104 
2105 	if (klass->roles != NULL)
2106 		g_hash_table_foreach (klass->roles,
2107 			(GHFunc) cb_copy_hash_table, roles);
2108 	klass->roles = roles;
2109 	klass->roles_allocated = TRUE;
2110 }
2111 
2112 /**
2113  * gog_object_register_roles:
2114  * @klass: #GogObjectClass
2115  * @roles: #GogObjectRole
2116  * @n_roles: number of roles
2117  *
2118  **/
2119 
2120 void
gog_object_register_roles(GogObjectClass * klass,GogObjectRole const * roles,unsigned int n_roles)2121 gog_object_register_roles (GogObjectClass *klass,
2122 			   GogObjectRole const *roles, unsigned int n_roles)
2123 {
2124 	unsigned i;
2125 
2126 	if (!klass->roles_allocated)
2127 		gog_object_allocate_roles (klass);
2128 
2129 	for (i = 0 ; i < n_roles ; i++) {
2130 		g_return_if_fail (g_hash_table_lookup (klass->roles,
2131 			(gpointer )roles[i].id) == NULL);
2132 		g_hash_table_replace (klass->roles,
2133 			(gpointer )roles[i].id, (gpointer) (roles + i));
2134 	}
2135 }
2136 
2137 void
gog_object_document_changed(GogObject * obj,GODoc * doc)2138 gog_object_document_changed (GogObject *obj, GODoc *doc)
2139 {
2140 	GSList *ptr;
2141 	g_return_if_fail (GOG_IS_OBJECT (obj) && GO_IS_DOC (doc));
2142 	if (GOG_OBJECT_GET_CLASS (obj)->document_changed != NULL)
2143 		GOG_OBJECT_GET_CLASS (obj)->document_changed (obj, doc);
2144 	for (ptr = obj->children; ptr != NULL; ptr = ptr->next)
2145 		gog_object_document_changed (GOG_OBJECT (ptr->data), doc);
2146 }
2147 
2148 GogManualSizeMode
gog_object_get_manual_size_mode(GogObject * obj)2149 gog_object_get_manual_size_mode (GogObject *obj)
2150 {
2151 	if (GOG_OBJECT_GET_CLASS (obj)->get_manual_size_mode != NULL)
2152 		return GOG_OBJECT_GET_CLASS (obj)->get_manual_size_mode (obj);
2153 	return GOG_MANUAL_SIZE_AUTO;
2154 }
2155