Move plugin init code
[gnumeric.git] / src / sheet-object.c
blobe28ba7cb363e6098aa45f5ca38891c3324e7d4b8
1 /*
2 * sheet-object.c: Implements the sheet object manipulation for Gnumeric
4 * Author:
5 * Miguel de Icaza (miguel@kernel.org)
6 * Michael Meeks (mmeeks@gnu.org)
7 * Jody Goldberg (jody@gnome.org)
8 */
9 #include <gnumeric-config.h>
10 #include <glib/gi18n-lib.h>
11 #include <gnumeric.h>
12 #include <sheet-object.h>
14 #include <sheet.h>
15 #include <dependent.h>
16 #include <sheet-view.h>
17 #include <sheet-control.h>
18 #include <sheet-control-gui.h>
19 #include <sheet-private.h>
20 #include <dialogs/dialogs.h>
21 #include <sheet-object-impl.h>
22 #include <expr.h>
23 #include <ranges.h>
24 #include <commands.h>
25 #include <gui-util.h>
27 #include <gnm-pane-impl.h>
28 #include <gnm-so-line.h>
29 #include <gnm-so-filled.h>
30 #include <sheet-control-gui-priv.h>
31 #include <sheet-object-cell-comment.h>
32 #include <sheet-object-widget.h>
33 #include <sheet-object-graph.h>
34 #include <sheet-object-image.h>
35 #include <sheet-filter-combo.h>
36 #include <wbc-gtk-impl.h>
37 #include <graph.h>
38 #include <print.h>
39 #include <goffice/goffice.h>
40 #include <application.h>
41 #include <gutils.h>
43 #include <libxml/globals.h>
44 #include <gsf/gsf-impl-utils.h>
46 #include <string.h>
48 /* GType code for SheetObjectAnchor */
49 static SheetObjectAnchor *
50 sheet_object_anchor_copy (SheetObjectAnchor * soa)
52 SheetObjectAnchor *res = g_new (SheetObjectAnchor, 1);
53 *res = *soa;
54 return res;
57 GType
58 sheet_object_anchor_get_type (void)
60 static GType t = 0;
62 if (t == 0) {
63 t = g_boxed_type_register_static ("SheetObjectAnchor",
64 (GBoxedCopyFunc)sheet_object_anchor_copy,
65 (GBoxedFreeFunc)g_free);
67 return t;
70 GType
71 gnm_sheet_object_anchor_mode_get_type (void)
73 static GType etype = 0;
74 if (etype == 0) {
75 static GEnumValue const values[] = {
76 { GNM_SO_ANCHOR_TWO_CELLS, "GNM_SO_ANCHOR_TWO_CELLS", "two-cells" },
77 { GNM_SO_ANCHOR_ONE_CELL, "GNM_SO_ANCHOR_ONE_CELL", "one-cell" },
78 { GNM_SO_ANCHOR_ABSOLUTE, "GNM_SO_ANCHOR_ABSOLUTE", "absolute" },
79 { 0, NULL, NULL }
81 etype = g_enum_register_static ("GnmSOAnchorMode", values);
83 return etype;
87 /* Returns the class for a SheetObject */
88 #define SO_CLASS(so) GNM_SO_CLASS(G_OBJECT_GET_CLASS(so))
90 enum {
91 SO_PROP_0 = 0,
92 SO_PROP_NAME
95 enum {
96 BOUNDS_CHANGED,
97 UNREALIZED,
98 LAST_SIGNAL
100 static guint signals [LAST_SIGNAL] = { 0 };
101 static GObjectClass *parent_klass;
102 static GQuark sov_so_quark;
103 static GQuark sov_container_quark;
105 void
106 sheet_object_set_print_flag (SheetObject *so, gboolean *print)
108 if (*print)
109 so->flags |= SHEET_OBJECT_PRINT;
110 else
111 so->flags &= ~SHEET_OBJECT_PRINT;
115 static void
116 cb_so_size_position (SheetObject *so, SheetControl *sc)
118 WBCGtk *wbcg;
120 g_return_if_fail (GNM_IS_SCG (sc));
122 wbcg = scg_wbcg ((SheetControlGUI *)sc);
124 if (wbcg->edit_line.guru != NULL) {
125 GtkWidget *w = wbcg->edit_line.guru;
126 wbc_gtk_detach_guru (wbcg);
127 gtk_widget_destroy (w);
130 dialog_so_size (wbcg, G_OBJECT (so));
133 static void
134 cb_so_snap_to_grid (SheetObject *so, SheetControl *sc)
136 SheetObjectAnchor *snapped =
137 sheet_object_anchor_dup (sheet_object_get_anchor (so));
138 GnmSOAnchorMode mode = snapped->mode;
139 snapped->mode = GNM_SO_ANCHOR_TWO_CELLS;
140 snapped->offset[0] = snapped->offset[1] = 0.;
141 snapped->offset[2] = snapped->offset[3] = 1.;
142 if (mode != GNM_SO_ANCHOR_TWO_CELLS) {
143 double pts[4];
144 sheet_object_anchor_to_pts (snapped, so->sheet, pts);
145 snapped->mode = mode;
146 sheet_object_pts_to_anchor (snapped, so->sheet, pts);
148 cmd_objects_move (sc_wbc (sc),
149 g_slist_prepend (NULL, so),
150 g_slist_prepend (NULL, snapped),
151 FALSE, _("Snap object to grid"));
153 static void
154 cb_so_pull_to_front (SheetObject *so, SheetControl *sc)
156 cmd_object_raise (sc_wbc (sc), so, cmd_object_pull_to_front);
158 static void
159 cb_so_pull_forward (SheetObject *so, SheetControl *sc)
161 cmd_object_raise (sc_wbc (sc), so, cmd_object_pull_forward);
163 static void
164 cb_so_push_backward (SheetObject *so, SheetControl *sc)
166 cmd_object_raise (sc_wbc (sc), so, cmd_object_push_backward);
168 static void
169 cb_so_push_to_back (SheetObject *so, SheetControl *sc)
171 cmd_object_raise (sc_wbc (sc), so, cmd_object_push_to_back);
173 static void
174 cb_so_delete (SheetObject *so, SheetControl *sc)
176 cmd_objects_delete (sc_wbc (sc),
177 go_slist_create (so, NULL), NULL);
179 static void
180 cb_so_print (SheetObject *so, SheetControl *sc)
182 GPtrArray *a = g_ptr_array_new ();
183 g_ptr_array_add (a, so);
184 gnm_print_so (sc_wbc (sc), a, NULL);
185 g_ptr_array_unref (a);
187 void
188 sheet_object_get_editor (SheetObject *so, SheetControl *sc)
190 WBCGtk *wbcg;
192 g_return_if_fail (GNM_IS_SO (so));
193 g_return_if_fail (SO_CLASS (so));
194 g_return_if_fail (GNM_IS_SCG (sc));
196 wbcg = scg_wbcg ((SheetControlGUI *)sc);
198 if (wbcg->edit_line.guru != NULL) {
199 GtkWidget *w = wbcg->edit_line.guru;
200 wbc_gtk_detach_guru (wbcg);
201 gtk_widget_destroy (w);
204 if (SO_CLASS(so)->user_config)
205 SO_CLASS(so)->user_config (so, sc);
207 static void
208 cb_so_cut (SheetObject *so, SheetControl *sc)
210 gnm_app_clipboard_cut_copy_obj (sc_wbc (sc), TRUE, sc_view (sc),
211 go_slist_create (so, NULL));
213 static void
214 cb_so_copy (SheetObject *so, SheetControl *sc)
216 gnm_app_clipboard_cut_copy_obj (sc_wbc (sc), FALSE, sc_view (sc),
217 go_slist_create (so, NULL));
220 gboolean
221 sheet_object_can_print (SheetObject const *so)
223 g_return_val_if_fail (GNM_IS_SO (so), FALSE);
224 return (so->flags & SHEET_OBJECT_IS_VISIBLE) &&
225 (so->flags & SHEET_OBJECT_PRINT) &&
226 SO_CLASS (so)->draw_cairo != NULL;
229 gboolean
230 sheet_object_can_resize (SheetObject const *so)
232 g_return_val_if_fail (GNM_IS_SO (so), FALSE);
233 return so->flags & SHEET_OBJECT_CAN_RESIZE;
236 gboolean
237 sheet_object_can_edit (SheetObject const *so)
239 g_return_val_if_fail (GNM_IS_SO (so), FALSE);
240 return so->flags & SHEET_OBJECT_CAN_EDIT;
243 static gboolean
244 sheet_object_can_prop (SheetObject const *so)
246 g_return_val_if_fail (GNM_IS_SO (so), FALSE);
247 return (sheet_object_can_edit (so) && (SO_CLASS(so)->user_config != NULL));
250 static void
251 sheet_object_populate_menu_real (SheetObject *so, GPtrArray *actions)
253 unsigned i;
254 if (so->sheet->sheet_type == GNM_SHEET_OBJECT) {
255 static SheetObjectAction const so_actions [] = {
256 { "gtk-properties", NULL, NULL, 0, sheet_object_get_editor, sheet_object_can_prop},
257 { NULL, NULL, NULL, 0, NULL, NULL },
258 { "edit-copy", N_("_Copy"), NULL, 0, cb_so_copy, NULL },
260 for (i = 0 ; i < G_N_ELEMENTS (so_actions); i++)
261 g_ptr_array_add (actions, (gpointer) (so_actions + i));
262 } else {
263 static SheetObjectAction const so_actions [] = {
264 { GTK_STOCK_PROPERTIES, NULL, NULL, 0, sheet_object_get_editor, sheet_object_can_prop},
265 { NULL, NULL, NULL, 0, NULL, NULL },
266 #warning "Two highly dubious icon names here"
267 { GTK_STOCK_LEAVE_FULLSCREEN, N_("Size _& Position"), NULL, 0, cb_so_size_position, NULL },
268 { GTK_STOCK_FULLSCREEN, N_("_Snap to Grid"), NULL, 0, cb_so_snap_to_grid, NULL },
269 { NULL, N_("_Order"), NULL, 1, NULL, NULL },
270 { NULL, N_("Pul_l to Front"), NULL, 0, cb_so_pull_to_front, NULL },
271 { NULL, N_("Pull _Forward"), NULL, 0, cb_so_pull_forward, NULL },
272 { NULL, N_("Push _Backward"), NULL, 0, cb_so_push_backward, NULL },
273 { NULL, N_("Pus_h to Back"), NULL, 0, cb_so_push_to_back, NULL },
274 { NULL, NULL, NULL, -1, NULL, NULL },
275 { NULL, NULL, NULL, 0, NULL, NULL },
276 { "edit-cut", N_("Cu_t"), NULL, 0, cb_so_cut, NULL },
277 { "edit-copy", N_("_Copy"), NULL, 0, cb_so_copy, NULL },
278 { "edit-delete", N_("_Delete"), NULL, 0, cb_so_delete, NULL },
279 { NULL, NULL, NULL, 0, NULL, NULL },
280 { "document-print", N_("Print"), NULL, 0, cb_so_print, sheet_object_can_print},
282 for (i = 0; i < G_N_ELEMENTS (so_actions); i++)
283 g_ptr_array_add (actions, (gpointer) (so_actions + i));
288 * sheet_object_populate_menu:
289 * @so: #SheetObject
290 * @actions: (inout) (transfer full) (element-type SheetObjectAction): #GPtrArray
292 * Get a list of the actions that can be performed on @so
294 void
295 sheet_object_populate_menu (SheetObject *so, GPtrArray *actions)
297 g_return_if_fail (NULL != so);
299 GNM_SO_CLASS (G_OBJECT_GET_CLASS(so))->populate_menu (so, actions);
303 * sheet_objects_max_extent:
304 * @sheet:
306 * Utility routine to calculate the maximum extent of objects in this sheet.
308 static void
309 sheet_objects_max_extent (Sheet *sheet)
311 GnmCellPos max_pos = { 0, 0 };
312 GSList *ptr;
314 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next ) {
315 SheetObject *so = GNM_SO (ptr->data);
317 if (max_pos.col < so->anchor.cell_bound.end.col)
318 max_pos.col = so->anchor.cell_bound.end.col;
319 if (max_pos.row < so->anchor.cell_bound.end.row)
320 max_pos.row = so->anchor.cell_bound.end.row;
323 if (sheet->max_object_extent.col != max_pos.col ||
324 sheet->max_object_extent.row != max_pos.row) {
325 sheet->max_object_extent = max_pos;
326 sheet_scrollbar_config (sheet);
330 void
331 sheet_object_set_name (SheetObject *so, const char *name)
333 if (name == so->name)
334 return;
336 g_free (so->name);
337 so->name = g_strdup (name);
339 g_object_notify (G_OBJECT (so), "name");
342 static void
343 sheet_object_get_property (GObject *obj, guint param_id,
344 GValue *value, GParamSpec *pspec)
346 SheetObject *so = GNM_SO (obj);
348 switch (param_id) {
349 case SO_PROP_NAME:
350 g_value_set_string (value, so->name);
351 break;
352 default:
353 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
354 break;
358 static void
359 sheet_object_set_property (GObject *obj, guint param_id,
360 GValue const *value, GParamSpec *pspec)
362 SheetObject *so = GNM_SO (obj);
364 switch (param_id) {
365 case SO_PROP_NAME:
366 sheet_object_set_name (so, g_value_get_string (value));
367 break;
368 default:
369 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
370 return;
375 static void
376 sheet_object_finalize (GObject *object)
378 SheetObject *so = GNM_SO (object);
379 if (so->sheet != NULL)
380 sheet_object_clear_sheet (so);
381 g_free (so->name);
382 parent_klass->finalize (object);
385 static void
386 sheet_object_init (GObject *object)
388 int i;
389 SheetObject *so = GNM_SO (object);
391 so->sheet = NULL;
392 so->flags = SHEET_OBJECT_IS_VISIBLE | SHEET_OBJECT_PRINT |
393 SHEET_OBJECT_CAN_RESIZE | SHEET_OBJECT_CAN_EDIT |
394 SHEET_OBJECT_MOVE_WITH_CELLS | SHEET_OBJECT_SIZE_WITH_CELLS;
396 /* Store the logical position as A1 */
397 so->anchor.cell_bound.start.col = so->anchor.cell_bound.start.row = 0;
398 so->anchor.cell_bound.end.col = so->anchor.cell_bound.end.row = 1;
399 so->anchor.base.direction = GOD_ANCHOR_DIR_UNKNOWN;
401 for (i = 4; i-- > 0 ;)
402 so->anchor.offset [i] = 0.;
405 static void
406 so_default_size (G_GNUC_UNUSED SheetObject const *so, double *width, double *height)
408 /* Provide some defaults (derived classes may want to override) */
409 *width = 72.;
410 *height = 72.;
413 static void
414 sheet_object_class_init (GObjectClass *klass)
416 SheetObjectClass *sheet_object_class = GNM_SO_CLASS (klass);
418 parent_klass = g_type_class_peek_parent (klass);
420 klass->finalize = sheet_object_finalize;
421 klass->get_property = sheet_object_get_property;
422 klass->set_property = sheet_object_set_property;
424 sheet_object_class->populate_menu = sheet_object_populate_menu_real;
425 sheet_object_class->user_config = NULL;
426 sheet_object_class->rubber_band_directly = FALSE;
427 sheet_object_class->interactive = FALSE;
428 sheet_object_class->default_size = so_default_size;
429 sheet_object_class->xml_export_name = NULL;
430 sheet_object_class->foreach_dep = NULL;
432 g_object_class_install_property
433 (klass, SO_PROP_NAME,
434 g_param_spec_string ("name", NULL, NULL, NULL,
435 GSF_PARAM_STATIC | G_PARAM_READWRITE));
437 signals [BOUNDS_CHANGED] = g_signal_new ("bounds-changed",
438 GNM_SO_TYPE,
439 G_SIGNAL_RUN_LAST,
440 G_STRUCT_OFFSET (SheetObjectClass, bounds_changed),
441 NULL, NULL,
442 g_cclosure_marshal_VOID__VOID,
443 G_TYPE_NONE, 0);
444 signals [UNREALIZED] = g_signal_new ("unrealized",
445 GNM_SO_TYPE,
446 G_SIGNAL_RUN_LAST,
447 G_STRUCT_OFFSET (SheetObjectClass, unrealized),
448 NULL, NULL,
449 g_cclosure_marshal_VOID__VOID,
450 G_TYPE_NONE, 0);
453 GSF_CLASS (SheetObject, sheet_object,
454 sheet_object_class_init, sheet_object_init,
455 G_TYPE_OBJECT)
458 * sheet_object_get_view:
459 * @so: #SheetObject
460 * @container: #SheetObjectViewContainer
462 * Returns: (transfer none) (nullable): the found #SheetObjectView or %NULL.
464 SheetObjectView *
465 sheet_object_get_view (SheetObject const *so, SheetObjectViewContainer *container)
467 GList *l;
469 g_return_val_if_fail (GNM_IS_SO (so), NULL);
471 for (l = so->realized_list; l; l = l->next) {
472 SheetObjectView *view = GNM_SO_VIEW (l->data);
473 if (container == g_object_get_qdata (G_OBJECT (view), sov_container_quark))
474 return view;
477 return NULL;
481 * sheet_object_update_bounds:
482 * @so: The sheet object
483 * @p: (nullable): A position marking the top left of the region
484 * needing relocation (default == A1)
486 * update the bounds of an object that intersects the region whose top left
487 * is @p. This is used when an objects position is anchored to cols/rows
488 * and they change position.
490 void
491 sheet_object_update_bounds (SheetObject *so, GnmCellPos const *pos)
493 gboolean is_hidden = TRUE;
494 int i, end;
496 g_return_if_fail (GNM_IS_SO (so));
498 if (pos != NULL &&
499 so->anchor.cell_bound.end.col < pos->col &&
500 so->anchor.cell_bound.end.row < pos->row)
501 return;
503 if (so->anchor.mode != GNM_SO_ANCHOR_TWO_CELLS) {
504 double x[4];
505 sheet_object_anchor_to_pts (&so->anchor, so->sheet, x);
506 sheet_object_pts_to_anchor (&so->anchor, so->sheet, x);
509 switch (so->anchor.mode) {
510 default:
511 case GNM_SO_ANCHOR_TWO_CELLS:
512 /* Are all cols hidden ? */
513 end = so->anchor.cell_bound.end.col;
514 i = so->anchor.cell_bound.start.col;
515 while (i <= end && is_hidden)
516 is_hidden &= sheet_col_is_hidden (so->sheet, i++);
518 /* Are all rows hidden ? */
519 if (!is_hidden) {
520 is_hidden = TRUE;
521 end = so->anchor.cell_bound.end.row;
522 i = so->anchor.cell_bound.start.row;
523 while (i <= end && is_hidden)
524 is_hidden &= sheet_row_is_hidden (so->sheet, i++);
526 break;
527 case GNM_SO_ANCHOR_ONE_CELL:
528 /* Should we really hide if the row or column is hidden? */
529 is_hidden = sheet_col_is_hidden (so->sheet, so->anchor.cell_bound.start.col) ||
530 sheet_row_is_hidden (so->sheet, so->anchor.cell_bound.start.row);
531 break;
532 case GNM_SO_ANCHOR_ABSOLUTE:
533 is_hidden = FALSE;
534 break;
536 if (is_hidden)
537 so->flags &= ~SHEET_OBJECT_IS_VISIBLE;
538 else
539 so->flags |= SHEET_OBJECT_IS_VISIBLE;
541 g_signal_emit (so, signals [BOUNDS_CHANGED], 0);
545 * sheet_object_get_sheet:
546 * @so: #SheetObject
548 * A small utility to help keep the implementation of SheetObjects modular.
549 * Returns: (transfer none): the #Sheet owning the object.
551 Sheet *
552 sheet_object_get_sheet (SheetObject const *so)
554 g_return_val_if_fail (GNM_IS_SO (so), NULL);
556 return so->sheet;
559 static int
560 cb_create_views (SheetObject *so)
562 g_object_set_data (G_OBJECT (so), "create_view_handler", NULL);
563 SHEET_FOREACH_CONTROL (so->sheet, view, control,
564 sc_object_create_view (control, so););
565 return FALSE;
569 * sheet_object_set_sheet:
570 * @so:
571 * @sheet:
573 * Adds a reference to the object.
575 * Returns: %TRUE if there was a problem
577 gboolean
578 sheet_object_set_sheet (SheetObject *so, Sheet *sheet)
580 g_return_val_if_fail (GNM_IS_SO (so), TRUE);
581 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
583 if (sheet == so->sheet)
584 return FALSE;
586 g_return_val_if_fail (so->sheet == NULL, TRUE);
587 g_return_val_if_fail (g_slist_find (sheet->sheet_objects, so) == NULL, TRUE);
589 so->sheet = sheet;
590 if (SO_CLASS (so)->assign_to_sheet &&
591 SO_CLASS (so)->assign_to_sheet (so, sheet)) {
592 so->sheet = NULL;
593 return TRUE;
596 g_object_ref (so);
597 sheet->sheet_objects = g_slist_prepend (sheet->sheet_objects, so);
598 /* Update object bounds for absolute and one cell anchored objects */
599 if (so->anchor.mode != GNM_SO_ANCHOR_TWO_CELLS) {
600 double x[4];
601 sheet_object_anchor_to_pts (&so->anchor, sheet, x);
602 sheet_object_pts_to_anchor (&so->anchor, sheet, x);
604 /* FIXME : add a flag to sheet to have sheet_update do this */
605 sheet_objects_max_extent (sheet);
607 if (NULL == g_object_get_data (G_OBJECT (so), "create_view_handler")) {
608 guint id = g_idle_add ((GSourceFunc) cb_create_views, so);
609 g_object_set_data (G_OBJECT (so), "create_view_handler", GUINT_TO_POINTER (id));
612 return FALSE;
616 * sheet_object_clear_sheet:
617 * @so: #SheetObject
619 * Removes @so from its container, unrealizes all views, disconects the
620 * associated data and unrefs the object
622 void
623 sheet_object_clear_sheet (SheetObject *so)
625 GSList *ptr;
626 gpointer view_handler;
628 g_return_if_fail (GNM_IS_SO (so));
630 if (so->sheet == NULL) /* already removed */
631 return;
633 g_return_if_fail (IS_SHEET (so->sheet));
635 ptr = g_slist_find (so->sheet->sheet_objects, so);
636 g_return_if_fail (ptr != NULL);
638 /* clear any pending attempts to create views */
639 view_handler = g_object_get_data (G_OBJECT (so), "create_view_handler");
640 if (NULL != view_handler) {
641 g_source_remove (GPOINTER_TO_UINT (view_handler));
642 g_object_set_data (G_OBJECT (so), "create_view_handler", NULL);
645 while (so->realized_list != NULL) {
646 g_object_set_qdata (G_OBJECT (so->realized_list->data), sov_so_quark, NULL);
647 g_object_unref (so->realized_list->data);
648 so->realized_list = g_list_remove (so->realized_list, so->realized_list->data);
651 g_signal_emit (so, signals [UNREALIZED], 0);
653 if (SO_CLASS (so)->remove_from_sheet &&
654 SO_CLASS (so)->remove_from_sheet (so))
655 return;
657 so->sheet->sheet_objects = g_slist_remove_link (so->sheet->sheet_objects, ptr);
658 g_slist_free (ptr);
660 if (so->anchor.cell_bound.end.col == so->sheet->max_object_extent.col &&
661 so->anchor.cell_bound.end.row == so->sheet->max_object_extent.row)
662 sheet_objects_max_extent (so->sheet);
664 so->sheet = NULL;
665 g_object_unref (so);
668 static void
669 cb_sheet_object_invalidate_sheet (GnmDependent *dep, G_GNUC_UNUSED SheetObject *so, gpointer user)
671 Sheet *sheet = user;
672 GnmExprRelocateInfo rinfo;
673 GnmExprTop const *texpr;
674 gboolean save_invalidated = sheet->being_invalidated;
675 gboolean dep_sheet_invalidated = (dep->sheet == sheet);
677 if (!dep->texpr)
678 return;
680 sheet->being_invalidated = TRUE;
681 rinfo.reloc_type = GNM_EXPR_RELOCATE_INVALIDATE_SHEET;
682 texpr = gnm_expr_top_relocate (dep->texpr, &rinfo, FALSE);
683 if (!texpr && dep_sheet_invalidated) {
684 texpr = dep->texpr;
685 gnm_expr_top_ref (texpr);
688 sheet->being_invalidated = save_invalidated;
690 if (texpr) {
691 gboolean was_linked = dependent_is_linked (dep);
692 dependent_set_expr (dep, texpr);
693 gnm_expr_top_unref (texpr);
694 if (dep_sheet_invalidated)
695 dep->sheet = NULL;
696 else if (was_linked)
697 dependent_link (dep);
701 void
702 sheet_object_invalidate_sheet (SheetObject *so, Sheet const *sheet)
704 sheet_object_foreach_dep (so, cb_sheet_object_invalidate_sheet,
705 (gpointer)sheet);
709 * sheet_object_foreach_dep:
710 * @so: #SheetObject
711 * @func: (scope call): #SheetObjectForeachDepFunc
712 * @user: user data
714 * Loops over each dependent contained in a sheet object and call the handler.
716 void
717 sheet_object_foreach_dep (SheetObject *so,
718 SheetObjectForeachDepFunc func,
719 gpointer user)
721 if (SO_CLASS (so)->foreach_dep)
722 SO_CLASS (so)->foreach_dep (so, func, user);
726 * sheet_object_new_view:
727 * @so:
728 * @container:
730 * Asks @so to create a view for @container.
731 * Returns: (transfer none): the new #SheetObjectView.
733 SheetObjectView *
734 sheet_object_new_view (SheetObject *so, SheetObjectViewContainer *container)
736 SheetObjectView *view;
738 g_return_val_if_fail (GNM_IS_SO (so), NULL);
739 g_return_val_if_fail (NULL != container, NULL);
741 view = sheet_object_get_view (so, container);
742 if (view != NULL)
743 return NULL;
745 view = SO_CLASS (so)->new_view (so, container);
747 if (NULL == view)
748 return NULL;
750 g_return_val_if_fail (GNM_IS_SO_VIEW (view), NULL);
752 /* Store some useful information */
753 g_object_set_qdata (G_OBJECT (view), sov_so_quark, so);
754 g_object_set_qdata (G_OBJECT (view), sov_container_quark, container);
755 so->realized_list = g_list_prepend (so->realized_list, view);
756 sheet_object_update_bounds (so, NULL);
758 return view;
762 * sheet_object_draw_cairo:
764 * Draw a sheet object using cairo.
767 * We are assuming that the cairo context is set up so that the top
768 * left of the bounds is (0,0). Note that this
769 * is the real top left cell, not necessarily the cell with to which we are
770 * anchored.
773 void
774 sheet_object_draw_cairo (SheetObject const *so, cairo_t *cr, gboolean rtl)
776 if (SO_CLASS (so)->draw_cairo) {
777 SheetObjectAnchor const *anchor;
778 double x = 0., y = 0., width, height, cell_width, cell_height;
779 anchor = sheet_object_get_anchor (so);
780 if (anchor->mode == GNM_SO_ANCHOR_ABSOLUTE) {
781 x = anchor->offset[0];
782 y = anchor->offset[1];
783 if (sheet_object_can_resize (so)) {
784 width = anchor->offset[2];
785 height = anchor->offset[3];
786 } else
787 sheet_object_default_size ((SheetObject *) so, &width, &height);
788 if (rtl)
789 x = -x - width;
790 } else {
791 cell_width = sheet_col_get_distance_pts (so->sheet,
792 anchor->cell_bound.start.col,
793 anchor->cell_bound.start.col + 1);
794 cell_height = sheet_row_get_distance_pts (so->sheet,
795 anchor->cell_bound.start.row,
796 anchor->cell_bound.start.row + 1);
797 x = cell_width * anchor->offset[0];
799 y = cell_height * anchor->offset[1];
800 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
801 cell_width = sheet_col_get_distance_pts (so->sheet,
802 anchor->cell_bound.end.col,
803 anchor->cell_bound.end.col + 1);
804 cell_height = sheet_row_get_distance_pts (so->sheet,
805 anchor->cell_bound.end.row,
806 anchor->cell_bound.end.row + 1);
808 if (rtl)
809 x = cell_width * (1 - anchor->offset[2]);
811 if (sheet_object_can_resize (so)) {
812 width = sheet_col_get_distance_pts (so->sheet,
813 anchor->cell_bound.start.col,
814 anchor->cell_bound.end.col + 1);
815 height = sheet_row_get_distance_pts (so->sheet,
816 anchor->cell_bound.start.row,
817 anchor->cell_bound.end.row + 1);
818 width -= x;
819 height -= y;
820 width -= cell_width * (1. - ((rtl)? anchor->offset[0]: anchor->offset[2]));
821 height -= cell_height * (1 - anchor->offset[3]);
822 } else
823 sheet_object_default_size ((SheetObject *) so, &width, &height);
824 } else {
825 if (sheet_object_can_resize (so)) {
826 width = anchor->offset[2];
827 height = anchor->offset[3];
828 } else
829 sheet_object_default_size ((SheetObject *) so, &width, &height);
830 if (rtl)
831 x = cell_width * (1 - anchor->offset[0]) - width;
835 /* we don't need to save/restore cairo, the caller must do it */
836 cairo_translate (cr, x, y);
837 SO_CLASS (so)->draw_cairo (so, cr, width, height);
841 void
842 sheet_object_draw_cairo_sized (SheetObject const *so, cairo_t *cr, double width, double height)
844 SO_CLASS (so)->draw_cairo (so, cr, width, height);
848 * sheet_object_get_range:
849 * @so: the #SheetObject to query
851 * Returns: (transfer none): the #GnmRange used for @so.
853 GnmRange const *
854 sheet_object_get_range (SheetObject const *so)
856 g_return_val_if_fail (GNM_IS_SO (so), NULL);
858 return &so->anchor.cell_bound;
862 * sheet_object_get_anchor:
863 * @so: #SheetObject
865 * Returns: (transfer none): the #SheetObjectAnchor for @so.
867 SheetObjectAnchor const *
868 sheet_object_get_anchor (SheetObject const *so)
870 g_return_val_if_fail (GNM_IS_SO (so), NULL);
872 return &so->anchor;
875 void
876 sheet_object_set_anchor (SheetObject *so, SheetObjectAnchor const *anchor)
878 g_return_if_fail (GNM_IS_SO (so));
880 so->anchor = *anchor;
881 if (so->sheet != NULL) {
882 sheet_objects_max_extent (so->sheet);
883 sheet_object_update_bounds (so, NULL);
887 SheetObjectAnchor *
888 sheet_object_anchor_dup (SheetObjectAnchor const *src)
890 SheetObjectAnchor *res = g_memdup (src, sizeof (SheetObjectAnchor));
891 return res;
894 static double
895 cell_offset_calc_pt (Sheet const *sheet, int i, gboolean is_col, double offset)
897 ColRowInfo const *cri = sheet_colrow_get_info (sheet, i, is_col);
898 return offset * cri->size_pts;
902 * sheet_object_default_size:
903 * @so: The sheet object
904 * @w: (out): a ptr into which to store the default_width.
905 * @h: (out): a ptr into which to store the default_height.
907 * Measurements are in pts.
909 void
910 sheet_object_default_size (SheetObject *so, double *w, double *h)
912 g_return_if_fail (GNM_IS_SO (so));
913 g_return_if_fail (w != NULL);
914 g_return_if_fail (h != NULL);
916 SO_CLASS (so)->default_size (so, w, h);
920 * sheet_object_position_pts_get:
921 * @so: The sheet object
922 * @coords: (out) (array fixed-size=4): coordinates
924 * Calculate the position of the object @so in pts from the logical position in
925 * the object.
927 void
928 sheet_object_position_pts_get (SheetObject const *so, double *coords)
930 g_return_if_fail (GNM_IS_SO (so));
931 sheet_object_anchor_to_pts (&so->anchor, so->sheet, coords);
934 void
935 sheet_object_anchor_to_pts (SheetObjectAnchor const *anchor,
936 Sheet const *sheet, double *res_pts)
938 GnmRange const *r;
940 g_return_if_fail (res_pts != NULL);
942 r = &anchor->cell_bound;
944 if (anchor->mode != GNM_SO_ANCHOR_ABSOLUTE) {
945 res_pts [0] = sheet_col_get_distance_pts (sheet, 0,
946 r->start.col);
947 res_pts [1] = sheet_row_get_distance_pts (sheet, 0,
948 r->start.row);
949 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
950 res_pts [2] = res_pts [0] + sheet_col_get_distance_pts (sheet,
951 r->start.col, r->end.col);
952 res_pts [3] = res_pts [1] + sheet_row_get_distance_pts (sheet,
953 r->start.row, r->end.row);
955 res_pts [0] += cell_offset_calc_pt (sheet, r->start.col,
956 TRUE, anchor->offset [0]);
957 res_pts [1] += cell_offset_calc_pt (sheet, r->start.row,
958 FALSE, anchor->offset [1]);
959 res_pts [2] += cell_offset_calc_pt (sheet, r->end.col,
960 TRUE, anchor->offset [2]);
961 res_pts [3] += cell_offset_calc_pt (sheet, r->end.row,
962 FALSE, anchor->offset [3]);
963 } else {
964 res_pts [0] += cell_offset_calc_pt (sheet, r->start.col,
965 TRUE, anchor->offset [0]);
966 res_pts [1] += cell_offset_calc_pt (sheet, r->start.row,
967 FALSE, anchor->offset [1]);
968 res_pts[2] = res_pts [0] + anchor->offset [2];
969 res_pts[3] = res_pts [1] + anchor->offset [3];
971 } else {
972 res_pts [0] = anchor->offset [0];
973 res_pts [1] = anchor->offset [1];
974 res_pts[2] = res_pts [0] + anchor->offset [2];
975 res_pts[3] = res_pts [1] + anchor->offset [3];
979 void
980 sheet_object_pts_to_anchor (SheetObjectAnchor *anchor,
981 Sheet const *sheet, double const *res_pts)
983 int col, row;
984 double x, y, tmp = 0;
985 ColRowInfo const *ci;
986 /* find end column */
987 col = x = 0;
988 do {
989 ci = sheet_col_get_info (sheet, col);
990 if (ci->visible) {
991 tmp = ci->size_pts;
992 if (res_pts[0] <= x + tmp)
993 break;
994 x += tmp;
996 } while (++col < gnm_sheet_get_last_col (sheet));
997 if (col == gnm_sheet_get_last_col (sheet)) {
998 /* not sure this will occur */
999 col--;
1000 x -= tmp;
1002 anchor->cell_bound.start.col = col;
1003 anchor->offset[0] = (anchor->mode == GNM_SO_ANCHOR_ABSOLUTE)?
1004 res_pts[0]: (res_pts[0] - x) / tmp;
1005 /* find start row */
1006 row = y = 0;
1007 do {
1008 ci = sheet_row_get_info (sheet, row);
1009 if (ci->visible) {
1010 tmp = ci->size_pts;
1011 if (res_pts[1] <= y + tmp)
1012 break;
1013 y += tmp;
1015 } while (++row < gnm_sheet_get_last_row (sheet));
1016 if (row == gnm_sheet_get_last_row (sheet)) {
1017 /* not sure this will occur */
1018 row--;
1019 y -= tmp;
1021 anchor->cell_bound.start.row = row;
1022 anchor->offset[1] = (anchor->mode == GNM_SO_ANCHOR_ABSOLUTE)?
1023 res_pts[1]: (res_pts[1] - y) / tmp;
1025 /* find end column */
1026 do {
1027 ci = sheet_col_get_info (sheet, col);
1028 if (ci->visible) {
1029 tmp = ci->size_pts;
1030 if (res_pts[2] <= x + tmp)
1031 break;
1032 x += tmp;
1034 } while (++col < gnm_sheet_get_last_col (sheet));
1035 if (col == gnm_sheet_get_last_col (sheet)) {
1036 /* not sure this will occur */
1037 col--;
1038 x -= tmp;
1040 anchor->cell_bound.end.col = col;
1041 anchor->offset[2] = (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS)?
1042 (res_pts[2] - x) / tmp: res_pts[2] - res_pts[0];
1043 /* find end row */
1044 do {
1045 ci = sheet_row_get_info (sheet, row);
1046 if (ci->visible) {
1047 tmp = ci->size_pts;
1048 if (res_pts[3] <= y + tmp)
1049 break;
1050 y += tmp;
1052 } while (++row < gnm_sheet_get_last_row (sheet));
1053 if (row == gnm_sheet_get_last_row (sheet)) {
1054 /* not sure this will occur */
1055 row--;
1056 y -= tmp;
1058 anchor->cell_bound.end.row = row;
1059 anchor->offset[3] = (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS)?
1060 (res_pts[3] - y) / tmp: res_pts[3] - res_pts[1];
1063 void
1064 sheet_object_anchor_to_offset_pts (SheetObjectAnchor const *anchor,
1065 Sheet const *sheet, double *res_pts)
1067 GnmRange const *r;
1069 g_return_if_fail (res_pts != NULL);
1071 r = &anchor->cell_bound;
1073 if (anchor->mode != GNM_SO_ANCHOR_ABSOLUTE) {
1074 res_pts [0] = cell_offset_calc_pt (sheet, r->start.col,
1075 TRUE, anchor->offset [0]);
1076 res_pts [1] = cell_offset_calc_pt (sheet, r->start.row,
1077 FALSE, anchor->offset [1]);
1078 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
1079 res_pts [2] = cell_offset_calc_pt (sheet, r->end.col,
1080 TRUE, anchor->offset [2]);
1081 res_pts [3] = cell_offset_calc_pt (sheet, r->end.row,
1082 FALSE, anchor->offset [3]);
1087 static void
1088 clear_sheet (SheetObject *so, GOUndo **pundo)
1090 if (pundo) {
1091 GOUndo *u = go_undo_binary_new
1092 (g_object_ref (so),
1093 so->sheet,
1094 (GOUndoBinaryFunc)sheet_object_set_sheet,
1095 (GFreeFunc) g_object_unref,
1096 NULL);
1097 *pundo = go_undo_combine (*pundo, u);
1100 sheet_object_clear_sheet (so);
1105 * sheet_objects_relocate:
1106 * @rinfo: details on what should be moved.
1107 * @update: Should we do the bound_update now, or leave it for later.
1108 * if FALSE honour the move_with_cells flag.
1109 * @pundo: (optional) (out): add dropped objects to ::objects
1111 * Uses the relocation info and the anchors to decide whether or not, and how
1112 * to relocate objects when the grid moves (eg ins/del col/row).
1114 void
1115 sheet_objects_relocate (GnmExprRelocateInfo const *rinfo, gboolean update,
1116 GOUndo **pundo)
1118 GSList *ptr, *next;
1119 GnmRange dest;
1120 gboolean change_sheets;
1122 g_return_if_fail (rinfo != NULL);
1123 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
1124 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
1126 dest = rinfo->origin;
1127 range_translate (&dest, rinfo->target_sheet,
1128 rinfo->col_offset, rinfo->row_offset);
1129 change_sheets = (rinfo->origin_sheet != rinfo->target_sheet);
1131 /* Clear the destination range on the target sheet */
1132 if (change_sheets) {
1133 GSList *copy = g_slist_copy (rinfo->target_sheet->sheet_objects);
1134 for (ptr = copy; ptr != NULL ; ptr = ptr->next ) {
1135 SheetObject *so = GNM_SO (ptr->data);
1136 GnmRange const *r = &so->anchor.cell_bound;
1137 if (range_contains (&dest, r->start.col, r->start.row)) {
1138 clear_sheet (so, pundo);
1141 g_slist_free (copy);
1144 ptr = rinfo->origin_sheet->sheet_objects;
1145 for (; ptr != NULL ; ptr = next ) {
1146 SheetObject *so = GNM_SO (ptr->data);
1147 GnmRange r = so->anchor.cell_bound;
1149 next = ptr->next;
1150 if ((so->anchor.mode == GNM_SO_ANCHOR_ABSOLUTE) ||
1151 (update && 0 == (so->flags & SHEET_OBJECT_MOVE_WITH_CELLS)))
1152 continue;
1153 if (range_contains (&rinfo->origin, r.start.col, r.start.row)) {
1154 /* FIXME : just moving the range is insufficent for all anchor types */
1155 /* Toss any objects that would be clipped. */
1156 if (range_translate (&r, rinfo->origin_sheet,
1157 rinfo->col_offset, rinfo->row_offset)) {
1158 clear_sheet (so, pundo);
1159 continue;
1161 so->anchor.cell_bound = r;
1163 if (change_sheets) {
1164 g_object_ref (so);
1165 sheet_object_clear_sheet (so);
1166 sheet_object_set_sheet (so, rinfo->target_sheet);
1167 g_object_unref (so);
1168 } else if (update)
1169 sheet_object_update_bounds (so, NULL);
1170 } else if (!change_sheets &&
1171 range_contains (&dest, r.start.col, r.start.row)) {
1172 clear_sheet (so, pundo);
1173 continue;
1177 sheet_objects_max_extent (rinfo->origin_sheet);
1178 if (change_sheets)
1179 sheet_objects_max_extent (rinfo->target_sheet);
1183 * sheet_objects_get:
1184 * @sheet: the sheet.
1185 * @r: (nullable): #GnmRange to look in
1186 * @t: The type of object to lookup, %G_TYPE_NONE for any.
1188 * Returns: (element-type SheetObject) (transfer container): a list
1189 * containing all objects of exactly the specified type (inheritence does
1190 * not count) that are completely contained in @r.
1192 GSList *
1193 sheet_objects_get (Sheet const *sheet, GnmRange const *r, GType t)
1195 GSList *res = NULL;
1196 GSList *ptr;
1198 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1200 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next ) {
1201 GObject *obj = G_OBJECT (ptr->data);
1203 if (t == G_TYPE_NONE || t == G_OBJECT_TYPE (obj)) {
1204 SheetObject *so = GNM_SO (obj);
1205 if (r == NULL || range_contained (&so->anchor.cell_bound, r))
1206 res = g_slist_prepend (res, so);
1209 return g_slist_reverse (res);
1213 * sheet_objects_clear:
1214 * @sheet: the sheet.
1215 * @r: (nullable): #GnmRange to look in
1216 * @t #GType
1217 * @pundo: (out) (nullable):
1219 * Removes the objects in the region.
1221 void
1222 sheet_objects_clear (Sheet const *sheet, GnmRange const *r, GType t,
1223 GOUndo **pundo)
1225 GSList *ptr, *next;
1227 g_return_if_fail (IS_SHEET (sheet));
1229 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = next ) {
1230 GObject *obj = G_OBJECT (ptr->data);
1231 next = ptr->next;
1232 if ((t == G_TYPE_NONE && G_OBJECT_TYPE (obj) != GNM_FILTER_COMBO_TYPE)
1233 || t == G_OBJECT_TYPE (obj)) {
1234 SheetObject *so = GNM_SO (obj);
1235 if (r == NULL || range_contained (&so->anchor.cell_bound, r))
1236 clear_sheet (so, pundo);
1242 * sheet_object_dup:
1243 * @so: a #SheetObject to duplicate
1245 * Returns: (transfer full) (nullable): A copy of @so that is not attached
1246 * to a sheet.
1248 SheetObject *
1249 sheet_object_dup (SheetObject const *so)
1251 SheetObject *new_so = NULL;
1253 if (!SO_CLASS (so)->copy)
1254 return NULL;
1256 new_so = g_object_new (G_OBJECT_TYPE (so), NULL);
1258 g_return_val_if_fail (new_so != NULL, NULL);
1260 SO_CLASS (so)->copy (new_so, so);
1261 new_so->flags = so->flags;
1262 new_so->anchor = so->anchor;
1264 return new_so;
1267 static void
1268 cb_sheet_objects_dup (GnmDependent *dep, SheetObject *so, gpointer user)
1270 Sheet *src = user;
1271 Sheet *dst = sheet_object_get_sheet (so);
1272 GnmExprTop const *texpr;
1274 if (!dep->texpr)
1275 return;
1277 texpr = gnm_expr_top_relocate_sheet (dep->texpr, src, dst);
1278 if (texpr != dep->texpr) {
1279 gboolean was_linked= dependent_is_linked (dep);
1280 dependent_set_expr (dep, texpr);
1281 if (was_linked)
1282 dependent_link (dep);
1284 gnm_expr_top_unref (texpr);
1289 * sheet_objects_dup:
1290 * @src: The source sheet to read the objects from
1291 * @dst: The destination sheet to attach the objects to
1292 * @range: (nullable): #GnmRange to look in
1294 * Clones the objects of the src sheet and attaches them into the dst sheet
1296 void
1297 sheet_objects_dup (Sheet const *src, Sheet *dst, GnmRange *range)
1299 GSList *list;
1301 g_return_if_fail (IS_SHEET (dst));
1302 g_return_if_fail (dst->sheet_objects == NULL);
1304 for (list = src->sheet_objects; list != NULL; list = list->next) {
1305 SheetObject *so = list->data;
1306 if (range == NULL || range_overlap (range, &so->anchor.cell_bound)) {
1307 SheetObject *new_so = sheet_object_dup (so);
1308 if (new_so != NULL) {
1309 sheet_object_set_sheet (new_so, dst);
1310 sheet_object_foreach_dep (new_so, cb_sheet_objects_dup,
1311 (gpointer)src);
1312 g_object_unref (new_so);
1317 dst->sheet_objects = g_slist_reverse (dst->sheet_objects);
1322 * sheet_object_direction_set:
1323 * @so: The sheet object that we are calculating the direction for
1324 * @coords: (in) (array fixed-size=4): array of coordinates in L,T,R,B order
1326 * Sets the object direction from the given the new coordinates
1327 * The original coordinates are assumed to be normalized (so that top
1328 * is above bottom and right is at the right of left)
1330 void
1331 sheet_object_direction_set (SheetObject *so, gdouble const *coords)
1333 if (so->anchor.base.direction == GOD_ANCHOR_DIR_UNKNOWN)
1334 return;
1336 so->anchor.base.direction = GOD_ANCHOR_DIR_NONE_MASK;
1338 if (coords [1] < coords [3])
1339 so->anchor.base.direction |= GOD_ANCHOR_DIR_DOWN;
1340 if (coords [0] < coords [2])
1341 so->anchor.base.direction |= GOD_ANCHOR_DIR_RIGHT;
1345 * sheet_object_rubber_band_directly:
1346 * @so:
1348 * Returns: %TRUE if we should draw the object as we are laying it out on
1349 * an sheet. If %FALSE we draw a rectangle where the object is going to go
1351 gboolean
1352 sheet_object_rubber_band_directly (G_GNUC_UNUSED SheetObject const *so)
1354 return FALSE;
1358 * sheet_object_anchor_init:
1359 * @anchor: #SheetObjectAnchor
1360 * @r: (nullable): #GnmRange to look in
1361 * @offsets: (in) (array fixed-size=4) (nullable):
1362 * @direction: #GODrawingAnchorDir
1363 * @mode: #GnmSOAnchorMode
1365 * A utility routine to initialize an anchor. Useful in case we change fields
1366 * in the future and want to ensure that everything is initialized.
1368 void
1369 sheet_object_anchor_init (SheetObjectAnchor *anchor,
1370 GnmRange const *r, const double *offsets,
1371 GODrawingAnchorDir direction,
1372 GnmSOAnchorMode mode)
1374 int i;
1376 if (r == NULL) {
1377 static GnmRange const defaultVal = { { 0, 0 }, { 1, 1 } };
1378 r = &defaultVal;
1380 anchor->cell_bound = *r;
1382 if (offsets == NULL) {
1383 static double const defaultVal [4] = { 0., 0., 0., 0. };
1384 offsets = defaultVal;
1386 for (i = 4; i-- > 0 ; )
1387 anchor->offset[i] = offsets [i];
1389 anchor->base.direction = direction;
1390 anchor->mode = mode;
1391 /* TODO : add sanity checking to handle offsets past edges of col/row */
1394 /*****************************************************************************/
1397 * sheet_object_get_stacking:
1398 * @so: #SheetObject
1400 * Returns: @so's position in the stack of sheet objects.
1402 gint
1403 sheet_object_get_stacking (SheetObject *so)
1405 g_return_val_if_fail (so != NULL, 0);
1406 g_return_val_if_fail (so->sheet != NULL, 0);
1408 return g_slist_index (so->sheet->sheet_objects, so);
1411 gint
1412 sheet_object_adjust_stacking (SheetObject *so, gint offset)
1414 GList *l;
1415 GSList **ptr, *node = NULL;
1416 int i, target, cur = 0;
1418 g_return_val_if_fail (so != NULL, 0);
1419 g_return_val_if_fail (so->sheet != NULL, 0);
1421 for (ptr = &so->sheet->sheet_objects ; *ptr ; ptr = &(*ptr)->next, cur++)
1422 if ((*ptr)->data == so) {
1423 node = *ptr;
1424 *ptr = (*ptr)->next;
1425 break;
1428 g_return_val_if_fail (node != NULL, 0);
1430 /* Start at the begining when moving things towards the front */
1431 if (offset > 0) {
1432 ptr = &so->sheet->sheet_objects;
1433 i = 0;
1434 } else
1435 i = cur;
1437 for (target = cur - offset; *ptr && i < target ; ptr = &(*ptr)->next)
1438 i++;
1440 node->next = *ptr;
1441 *ptr = node;
1443 /* TODO : Move this to the container */
1444 for (l = so->realized_list; l; l = l->next) {
1445 GocItem *item = GOC_ITEM (l->data);
1446 if (offset > 0)
1447 goc_item_raise (item, offset);
1448 else
1449 goc_item_lower (item, - offset);
1451 return cur - i;
1454 void
1455 sheet_object_set_anchor_mode (SheetObject *so, GnmSOAnchorMode const *mode)
1457 /* FIXME: adjust offsets according to the old and new modes */
1458 double pts[4];
1460 if (so->anchor.mode == *mode)
1461 return;
1462 sheet_object_anchor_to_pts (&so->anchor, so->sheet, pts);
1463 so->anchor.mode = *mode;
1464 sheet_object_pts_to_anchor (&so->anchor, so->sheet, pts);
1467 /*****************************************************************************/
1469 static GObjectClass *view_parent_class;
1471 void
1472 sheet_object_view_set_bounds (SheetObjectView *sov,
1473 double const *coords, gboolean visible)
1475 SheetObjectViewClass *klass;
1477 g_return_if_fail (GNM_IS_SO_VIEW (sov));
1478 klass = GNM_SO_VIEW_GET_CLASS (sov);
1479 if (NULL != klass->set_bounds)
1480 (klass->set_bounds) (sov, coords, visible);
1484 * sheet_object_view_get_so:
1485 * @sov: #SheetObjectView
1487 * Returns: (transfer none): the #SheetObject owning @view.
1489 SheetObject *
1490 sheet_object_view_get_so (SheetObjectView *view)
1492 return g_object_get_qdata (G_OBJECT (view), sov_so_quark);
1495 static gboolean
1496 sheet_object_view_enter_notify (GocItem *item, double x, double y)
1498 SheetObject *so;
1500 if (GNM_IS_PANE (item->canvas) && scg_wbcg (GNM_SIMPLE_CANVAS (item->canvas)->scg)->new_object) {
1501 GnmItemGrid *grid = GNM_PANE (item->canvas)->grid;
1502 return GOC_ITEM_GET_CLASS (grid)->enter_notify (GOC_ITEM (grid), x, y);
1505 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1506 gnm_widget_set_cursor_type (GTK_WIDGET (item->canvas),
1507 (so->flags & SHEET_OBJECT_CAN_PRESS) ? GDK_HAND2 : GDK_ARROW);
1508 return FALSE;
1511 static void
1512 cb_so_menu_activate (GObject *menu, GocItem *view)
1514 SheetObjectAction const *a = g_object_get_data (menu, "action");
1516 if (a->func) {
1517 SheetObject *so = sheet_object_view_get_so (GNM_SO_VIEW (view));
1518 gpointer data = g_object_get_data (G_OBJECT (view->canvas), "sheet-control");
1520 if (data == NULL)
1521 data = GNM_SIMPLE_CANVAS (view->canvas)->scg;
1523 (a->func) (so, GNM_SHEET_CONTROL (data));
1527 static void
1528 cb_ptr_array_free (GPtrArray *actions)
1530 g_ptr_array_free (actions, TRUE);
1534 * sheet_object_build_menu:
1535 * @view: #SheetObjectView
1536 * @actions: (element-type SheetObjectAction): array of actions.
1537 * @i: index of first action to add in the array.
1539 * Builds the contextual menu for @view.
1540 * Returns: (transfer full): the newly constructed #GtkMenu.
1542 GtkWidget *
1543 sheet_object_build_menu (SheetObjectView *view,
1544 GPtrArray const *actions, unsigned *i)
1546 SheetObjectAction const *a;
1547 GtkWidget *item, *menu = gtk_menu_new ();
1549 while (*i < actions->len) {
1550 a = g_ptr_array_index (actions, *i);
1551 (*i)++;
1552 if (a->submenu < 0)
1553 break;
1554 if (a->icon != NULL) {
1555 if (a->label != NULL) {
1556 item = gtk_image_menu_item_new_with_mnemonic (_(a->label));
1557 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
1558 gtk_image_new_from_icon_name (a->icon, GTK_ICON_SIZE_MENU));
1559 } else
1560 item = gtk_image_menu_item_new_from_stock (a->icon, NULL);
1561 } else if (a->label != NULL)
1562 item = gtk_menu_item_new_with_mnemonic (_(a->label));
1563 else
1564 item = gtk_separator_menu_item_new ();
1565 if (a->submenu > 0)
1566 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
1567 sheet_object_build_menu (view, actions, i));
1568 else if (a->label != NULL || a->icon != NULL) { /* not a separator or menu */
1569 g_object_set_data (G_OBJECT (item), "action", (gpointer)a);
1570 g_signal_connect_object (G_OBJECT (item), "activate",
1571 G_CALLBACK (cb_so_menu_activate), view, 0);
1572 gtk_widget_set_sensitive (item, a->enable_func == NULL
1573 || a->enable_func (sheet_object_view_get_so (view)));
1575 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1577 return menu;
1580 static gboolean
1581 sheet_object_view_button_pressed (GocItem *item, int button, double x, double y)
1583 GnmPane *pane;
1584 SheetObject *so;
1585 if (GNM_IS_PANE (item->canvas)) {
1586 if (scg_wbcg (GNM_SIMPLE_CANVAS (item->canvas)->scg)->new_object) {
1587 GnmItemGrid *grid = GNM_PANE (item->canvas)->grid;
1588 return GOC_ITEM_GET_CLASS (grid)->button_pressed (GOC_ITEM (grid), button, x, y);
1591 if (button > 3)
1592 return FALSE;
1594 pane = GNM_PANE (item->canvas);
1595 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1597 x *= goc_canvas_get_pixels_per_unit (item->canvas);
1598 y *= goc_canvas_get_pixels_per_unit (item->canvas);
1599 /* cb_sheet_object_widget_canvas_event calls even if selected */
1600 if (NULL == g_hash_table_lookup (pane->drag.ctrl_pts, so)) {
1601 SheetObjectClass *soc =
1602 G_TYPE_INSTANCE_GET_CLASS (so, GNM_SO_TYPE, SheetObjectClass);
1603 GdkEventButton *event = (GdkEventButton *) goc_canvas_get_cur_event (item->canvas);
1605 if (soc->interactive && button != 3)
1606 return FALSE;
1608 if (!(event->state & GDK_SHIFT_MASK))
1609 scg_object_unselect (pane->simple.scg, NULL);
1610 scg_object_select (pane->simple.scg, so);
1611 if (NULL == g_hash_table_lookup (pane->drag.ctrl_pts, so))
1612 return FALSE; /* protected ? */
1615 if (button < 3)
1616 gnm_pane_object_start_resize (pane, button, x, y, so, 8, FALSE);
1617 else
1618 gnm_pane_display_object_menu (pane, so, goc_canvas_get_cur_event (item->canvas));
1619 } else {
1620 if (button == 3) {
1621 GPtrArray *actions = g_ptr_array_new ();
1622 GtkWidget *menu;
1623 unsigned i = 0;
1625 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1626 sheet_object_populate_menu (so, actions);
1628 if (actions->len == 0) {
1629 g_ptr_array_free (actions, TRUE);
1630 return FALSE;
1633 menu = sheet_object_build_menu
1634 (sheet_object_get_view (so, (SheetObjectViewContainer *) item->canvas),
1635 actions, &i);
1636 g_object_set_data_full (G_OBJECT (menu), "actions", actions,
1637 (GDestroyNotify) cb_ptr_array_free);
1638 gtk_widget_show_all (menu);
1639 gnumeric_popup_menu (GTK_MENU (menu),
1640 goc_canvas_get_cur_event (item->canvas));
1643 return TRUE;
1646 static gboolean
1647 sheet_object_view_button2_pressed (GocItem *item, int button, double x, double y)
1649 if (button == 1 && !GNM_IS_PANE (item->canvas)) {
1650 SheetControl *sc = GNM_SHEET_CONTROL (g_object_get_data (G_OBJECT (item->canvas), "sheet-control"));
1651 SheetObject *so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1653 if (sc && sheet_object_can_edit (so))
1654 sheet_object_get_editor (so, sc);
1656 return TRUE;
1659 static void
1660 sheet_object_view_finalize (GObject *obj)
1662 SheetObject *so = (SheetObject *) g_object_get_qdata (obj, sov_so_quark);
1663 if (so)
1664 so->realized_list = g_list_remove (so->realized_list, obj);
1665 view_parent_class->finalize (obj);
1668 static void
1669 sheet_object_view_class_init (GocItemClass *item_klass)
1671 GObjectClass *obj_klass = (GObjectClass *) item_klass;
1672 view_parent_class = g_type_class_peek_parent (item_klass);
1674 obj_klass->finalize = sheet_object_view_finalize;
1676 item_klass->enter_notify = sheet_object_view_enter_notify;
1677 item_klass->button_pressed = sheet_object_view_button_pressed;
1678 item_klass->button2_pressed = sheet_object_view_button2_pressed;
1681 GSF_CLASS (SheetObjectView, sheet_object_view,
1682 sheet_object_view_class_init, NULL,
1683 GOC_TYPE_GROUP)
1685 /*****************************************************************************/
1687 GType
1688 sheet_object_imageable_get_type (void)
1690 static GType type = 0;
1692 if (!type) {
1693 static GTypeInfo const type_info = {
1694 sizeof (SheetObjectImageableIface), /* class_size */
1695 NULL, /* base_init */
1696 NULL, /* base_finalize */
1697 NULL, NULL, NULL, 0, 0, NULL, NULL
1700 type = g_type_register_static (G_TYPE_INTERFACE,
1701 "SheetObjectImageable", &type_info, 0);
1704 return type;
1707 #define GNM_SO_IMAGEABLE_CLASS(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GNM_SO_IMAGEABLE_TYPE, SheetObjectImageableIface))
1709 GtkTargetList *
1710 sheet_object_get_target_list (SheetObject const *so)
1712 if (!GNM_IS_SO_IMAGEABLE (so))
1713 return NULL;
1715 return GNM_SO_IMAGEABLE_CLASS (so)->get_target_list (so);
1718 void
1719 sheet_object_write_image (SheetObject const *so, char const *format, double resolution,
1720 GsfOutput *output, GError **err)
1722 g_return_if_fail (GNM_IS_SO_IMAGEABLE (so));
1724 GNM_SO_IMAGEABLE_CLASS (so)->write_image (so, format, resolution,
1725 output, err);
1729 /*****************************************************************************/
1731 GType
1732 sheet_object_exportable_get_type (void)
1734 static GType type = 0;
1736 if (!type) {
1737 static GTypeInfo const type_info = {
1738 sizeof (SheetObjectExportableIface), /* class_size */
1739 NULL, /* base_init */
1740 NULL, /* base_finalize */
1741 NULL, NULL, NULL, 0, 0, NULL, NULL
1744 type = g_type_register_static (G_TYPE_INTERFACE,
1745 "SheetObjectExportable", &type_info, 0);
1748 return type;
1751 #define GNM_SO_EXPORTABLE_CLASS(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GNM_SO_EXPORTABLE_TYPE, SheetObjectExportableIface))
1753 GtkTargetList *
1754 sheet_object_exportable_get_target_list (SheetObject const *so)
1756 if (!GNM_IS_SO_EXPORTABLE (so))
1757 return NULL;
1759 return GNM_SO_EXPORTABLE_CLASS (so)->get_target_list (so);
1762 void
1763 sheet_object_write_object (SheetObject const *so, char const *format,
1764 GsfOutput *output, GError **err,
1765 GnmConventions const *convs)
1767 GnmLocale *locale;
1769 g_return_if_fail (GNM_IS_SO_EXPORTABLE (so));
1771 locale = gnm_push_C_locale ();
1772 GNM_SO_EXPORTABLE_CLASS (so)->
1773 write_object (so, format, output, err, convs);
1774 gnm_pop_C_locale (locale);
1778 * sheet_object_move_undo:
1779 * @objects: (element-type SheetObject):
1780 * @objects_created:
1782 * Returns: (transfer full): the newly allocated #GOUndo.
1784 GOUndo *
1785 sheet_object_move_undo (GSList *objects, gboolean objects_created)
1787 GOUndo *undo = NULL;
1788 GSList *objs = objects;
1790 g_return_val_if_fail (NULL != objects, NULL);
1792 for (; objs; objs = objs->next) {
1793 SheetObject *obj = objs->data;
1794 SheetObjectAnchor *tmp;
1796 if (objects_created) {
1797 undo = go_undo_combine
1798 (undo,
1799 go_undo_unary_new
1800 (g_object_ref (obj),
1801 (GOUndoUnaryFunc) sheet_object_clear_sheet,
1802 (GFreeFunc) g_object_unref));
1805 tmp = g_new (SheetObjectAnchor, 1);
1806 *tmp = *sheet_object_get_anchor (obj);
1807 undo = go_undo_combine
1808 (undo, go_undo_binary_new
1809 (g_object_ref (obj), tmp,
1810 (GOUndoBinaryFunc) sheet_object_set_anchor,
1811 (GFreeFunc) g_object_unref,
1812 (GFreeFunc) g_free));
1814 return undo;
1818 * sheet_object_move_do:
1819 * @objects: (element-type SheetObject):
1820 * @anchors: (element-type SheetObjectAnchor):
1821 * @objects_created:
1823 * Returns: (transfer full): the newly allocated #GOUndo.
1825 GOUndo *
1826 sheet_object_move_do (GSList *objects, GSList *anchors,
1827 gboolean objects_created)
1829 GOUndo *undo = NULL;
1830 GSList *objs = objects, *anchs = anchors;
1832 g_return_val_if_fail (NULL != objects, NULL);
1833 g_return_val_if_fail (NULL != anchors, NULL);
1834 g_return_val_if_fail (g_slist_length (objects)
1835 == g_slist_length (anchors), NULL);
1837 for (; objs && anchs; objs = objs->next, anchs = anchs->next) {
1838 SheetObject *obj = objs->data;
1839 SheetObjectAnchor *anch = anchs->data;
1840 SheetObjectAnchor *tmp;
1842 if (objects_created) {
1843 undo = go_undo_combine
1844 (undo,
1845 go_undo_binary_new
1846 (g_object_ref (obj),
1847 sheet_object_get_sheet (obj),
1848 (GOUndoBinaryFunc) sheet_object_set_sheet,
1849 (GFreeFunc) g_object_unref,
1850 NULL));
1852 tmp = g_new (SheetObjectAnchor, 1);
1853 *tmp = *anch;
1854 undo = go_undo_combine
1855 (go_undo_binary_new
1856 (g_object_ref (obj), tmp,
1857 (GOUndoBinaryFunc) sheet_object_set_anchor,
1858 (GFreeFunc) g_object_unref,
1859 (GFreeFunc) g_free), undo);
1861 return undo;
1865 /*****************************************************************************/
1868 * sheet_objects_init: (skip)
1870 void
1871 sheet_objects_init (void)
1873 GNM_SO_LINE_TYPE;
1874 GNM_SO_FILLED_TYPE;
1875 GNM_SO_GRAPH_TYPE;
1876 GNM_SO_IMAGE_TYPE;
1877 GNM_GO_DATA_SCALAR_TYPE;
1878 GNM_GO_DATA_VECTOR_TYPE;
1879 GNM_GO_DATA_MATRIX_TYPE;
1880 GNM_CELL_COMMENT_TYPE;
1882 sheet_object_widget_register ();
1883 sov_so_quark = g_quark_from_static_string ("SheetObject");
1884 sov_container_quark = g_quark_from_static_string ("SheetObjectViewContainer");