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