Whitespace.
[gnumeric.git] / src / sheet-object.c
blob117b0486abcf6f216869ad1e671c7b6a8a811c31
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.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 high 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): 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: An optional 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);
847 GnmRange const *
848 sheet_object_get_range (SheetObject const *so)
850 g_return_val_if_fail (GNM_IS_SO (so), NULL);
852 return &so->anchor.cell_bound;
856 * sheet_object_get_anchor:
857 * @so: #SheetObject
859 * Returns: (transfer none): the #SheetObjectAnchor for @so.
861 SheetObjectAnchor const *
862 sheet_object_get_anchor (SheetObject const *so)
864 g_return_val_if_fail (GNM_IS_SO (so), NULL);
866 return &so->anchor;
869 void
870 sheet_object_set_anchor (SheetObject *so, SheetObjectAnchor const *anchor)
872 g_return_if_fail (GNM_IS_SO (so));
874 so->anchor = *anchor;
875 if (so->sheet != NULL) {
876 sheet_objects_max_extent (so->sheet);
877 sheet_object_update_bounds (so, NULL);
881 SheetObjectAnchor *
882 sheet_object_anchor_dup (SheetObjectAnchor const *src)
884 SheetObjectAnchor *res = g_memdup (src, sizeof (SheetObjectAnchor));
885 return res;
888 static double
889 cell_offset_calc_pt (Sheet const *sheet, int i, gboolean is_col, double offset)
891 ColRowInfo const *cri = sheet_colrow_get_info (sheet, i, is_col);
892 return offset * cri->size_pts;
896 * sheet_object_default_size:
897 * @so: The sheet object
898 * @w: a ptr into which to store the default_width.
899 * @h: a ptr into which to store the default_height.
901 * Measurements are in pts.
903 void
904 sheet_object_default_size (SheetObject *so, double *w, double *h)
906 g_return_if_fail (GNM_IS_SO (so));
907 g_return_if_fail (w != NULL);
908 g_return_if_fail (h != NULL);
910 SO_CLASS (so)->default_size (so, w, h);
914 * sheet_object_position_pts_get:
915 * @so: The sheet object
916 * @coords: array of 4 doubles
918 * Calculate the position of the object @so in pts from the logical position in
919 * the object.
921 void
922 sheet_object_position_pts_get (SheetObject const *so, double *coords)
924 g_return_if_fail (GNM_IS_SO (so));
925 sheet_object_anchor_to_pts (&so->anchor, so->sheet, coords);
928 void
929 sheet_object_anchor_to_pts (SheetObjectAnchor const *anchor,
930 Sheet const *sheet, double *res_pts)
932 GnmRange const *r;
934 g_return_if_fail (res_pts != NULL);
936 r = &anchor->cell_bound;
938 if (anchor->mode != GNM_SO_ANCHOR_ABSOLUTE) {
939 res_pts [0] = sheet_col_get_distance_pts (sheet, 0,
940 r->start.col);
941 res_pts [1] = sheet_row_get_distance_pts (sheet, 0,
942 r->start.row);
943 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
944 res_pts [2] = res_pts [0] + sheet_col_get_distance_pts (sheet,
945 r->start.col, r->end.col);
946 res_pts [3] = res_pts [1] + sheet_row_get_distance_pts (sheet,
947 r->start.row, r->end.row);
949 res_pts [0] += cell_offset_calc_pt (sheet, r->start.col,
950 TRUE, anchor->offset [0]);
951 res_pts [1] += cell_offset_calc_pt (sheet, r->start.row,
952 FALSE, anchor->offset [1]);
953 res_pts [2] += cell_offset_calc_pt (sheet, r->end.col,
954 TRUE, anchor->offset [2]);
955 res_pts [3] += cell_offset_calc_pt (sheet, r->end.row,
956 FALSE, anchor->offset [3]);
957 } else {
958 res_pts [0] += cell_offset_calc_pt (sheet, r->start.col,
959 TRUE, anchor->offset [0]);
960 res_pts [1] += cell_offset_calc_pt (sheet, r->start.row,
961 FALSE, anchor->offset [1]);
962 res_pts[2] = res_pts [0] + anchor->offset [2];
963 res_pts[3] = res_pts [1] + anchor->offset [3];
965 } else {
966 res_pts [0] = anchor->offset [0];
967 res_pts [1] = anchor->offset [1];
968 res_pts[2] = res_pts [0] + anchor->offset [2];
969 res_pts[3] = res_pts [1] + anchor->offset [3];
973 void
974 sheet_object_pts_to_anchor (SheetObjectAnchor *anchor,
975 Sheet const *sheet, double const *res_pts)
977 int col, row;
978 double x, y, tmp = 0;
979 ColRowInfo const *ci;
980 /* find end column */
981 col = x = 0;
982 do {
983 ci = sheet_col_get_info (sheet, col);
984 if (ci->visible) {
985 tmp = ci->size_pts;
986 if (res_pts[0] <= x + tmp)
987 break;
988 x += tmp;
990 } while (++col < gnm_sheet_get_last_col (sheet));
991 if (col == gnm_sheet_get_last_col (sheet)) {
992 /* not sure this will occur */
993 col--;
994 x -= tmp;
996 anchor->cell_bound.start.col = col;
997 anchor->offset[0] = (anchor->mode == GNM_SO_ANCHOR_ABSOLUTE)?
998 res_pts[0]: (res_pts[0] - x) / tmp;
999 /* find start row */
1000 row = y = 0;
1001 do {
1002 ci = sheet_row_get_info (sheet, row);
1003 if (ci->visible) {
1004 tmp = ci->size_pts;
1005 if (res_pts[1] <= y + tmp)
1006 break;
1007 y += tmp;
1009 } while (++row < gnm_sheet_get_last_row (sheet));
1010 if (row == gnm_sheet_get_last_row (sheet)) {
1011 /* not sure this will occur */
1012 row--;
1013 y -= tmp;
1015 anchor->cell_bound.start.row = row;
1016 anchor->offset[1] = (anchor->mode == GNM_SO_ANCHOR_ABSOLUTE)?
1017 res_pts[1]: (res_pts[1] - y) / tmp;
1019 /* find end column */
1020 do {
1021 ci = sheet_col_get_info (sheet, col);
1022 if (ci->visible) {
1023 tmp = ci->size_pts;
1024 if (res_pts[2] <= x + tmp)
1025 break;
1026 x += tmp;
1028 } while (++col < gnm_sheet_get_last_col (sheet));
1029 if (col == gnm_sheet_get_last_col (sheet)) {
1030 /* not sure this will occur */
1031 col--;
1032 x -= tmp;
1034 anchor->cell_bound.end.col = col;
1035 anchor->offset[2] = (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS)?
1036 (res_pts[2] - x) / tmp: res_pts[2] - res_pts[0];
1037 /* find end row */
1038 do {
1039 ci = sheet_row_get_info (sheet, row);
1040 if (ci->visible) {
1041 tmp = ci->size_pts;
1042 if (res_pts[3] <= y + tmp)
1043 break;
1044 y += tmp;
1046 } while (++row < gnm_sheet_get_last_row (sheet));
1047 if (row == gnm_sheet_get_last_row (sheet)) {
1048 /* not sure this will occur */
1049 row--;
1050 y -= tmp;
1052 anchor->cell_bound.end.row = row;
1053 anchor->offset[3] = (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS)?
1054 (res_pts[3] - y) / tmp: res_pts[3] - res_pts[1];
1057 void
1058 sheet_object_anchor_to_offset_pts (SheetObjectAnchor const *anchor,
1059 Sheet const *sheet, double *res_pts)
1061 GnmRange const *r;
1063 g_return_if_fail (res_pts != NULL);
1065 r = &anchor->cell_bound;
1067 if (anchor->mode != GNM_SO_ANCHOR_ABSOLUTE) {
1068 res_pts [0] = cell_offset_calc_pt (sheet, r->start.col,
1069 TRUE, anchor->offset [0]);
1070 res_pts [1] = cell_offset_calc_pt (sheet, r->start.row,
1071 FALSE, anchor->offset [1]);
1072 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
1073 res_pts [2] = cell_offset_calc_pt (sheet, r->end.col,
1074 TRUE, anchor->offset [2]);
1075 res_pts [3] = cell_offset_calc_pt (sheet, r->end.row,
1076 FALSE, anchor->offset [3]);
1081 static void
1082 clear_sheet (SheetObject *so, GOUndo **pundo)
1084 if (pundo) {
1085 GOUndo *u = go_undo_binary_new
1086 (g_object_ref (so),
1087 so->sheet,
1088 (GOUndoBinaryFunc)sheet_object_set_sheet,
1089 (GFreeFunc) g_object_unref,
1090 NULL);
1091 *pundo = go_undo_combine (*pundo, u);
1094 sheet_object_clear_sheet (so);
1099 * sheet_objects_relocate:
1100 * @rinfo: details on what should be moved.
1101 * @update: Should we do the bound_update now, or leave it for later.
1102 * if FALSE honour the move_with_cells flag.
1103 * @pundo: if non-NULL add dropped objects to ::objects
1105 * Uses the relocation info and the anchors to decide whether or not, and how
1106 * to relocate objects when the grid moves (eg ins/del col/row).
1108 void
1109 sheet_objects_relocate (GnmExprRelocateInfo const *rinfo, gboolean update,
1110 GOUndo **pundo)
1112 GSList *ptr, *next;
1113 GnmRange dest;
1114 gboolean change_sheets;
1116 g_return_if_fail (rinfo != NULL);
1117 g_return_if_fail (IS_SHEET (rinfo->origin_sheet));
1118 g_return_if_fail (IS_SHEET (rinfo->target_sheet));
1120 dest = rinfo->origin;
1121 range_translate (&dest, rinfo->target_sheet,
1122 rinfo->col_offset, rinfo->row_offset);
1123 change_sheets = (rinfo->origin_sheet != rinfo->target_sheet);
1125 /* Clear the destination range on the target sheet */
1126 if (change_sheets) {
1127 GSList *copy = g_slist_copy (rinfo->target_sheet->sheet_objects);
1128 for (ptr = copy; ptr != NULL ; ptr = ptr->next ) {
1129 SheetObject *so = GNM_SO (ptr->data);
1130 GnmRange const *r = &so->anchor.cell_bound;
1131 if (range_contains (&dest, r->start.col, r->start.row)) {
1132 clear_sheet (so, pundo);
1135 g_slist_free (copy);
1138 ptr = rinfo->origin_sheet->sheet_objects;
1139 for (; ptr != NULL ; ptr = next ) {
1140 SheetObject *so = GNM_SO (ptr->data);
1141 GnmRange r = so->anchor.cell_bound;
1143 next = ptr->next;
1144 if ((so->anchor.mode == GNM_SO_ANCHOR_ABSOLUTE) ||
1145 (update && 0 == (so->flags & SHEET_OBJECT_MOVE_WITH_CELLS)))
1146 continue;
1147 if (range_contains (&rinfo->origin, r.start.col, r.start.row)) {
1148 /* FIXME : just moving the range is insufficent for all anchor types */
1149 /* Toss any objects that would be clipped. */
1150 if (range_translate (&r, rinfo->origin_sheet,
1151 rinfo->col_offset, rinfo->row_offset)) {
1152 clear_sheet (so, pundo);
1153 continue;
1155 so->anchor.cell_bound = r;
1157 if (change_sheets) {
1158 g_object_ref (so);
1159 sheet_object_clear_sheet (so);
1160 sheet_object_set_sheet (so, rinfo->target_sheet);
1161 g_object_unref (so);
1162 } else if (update)
1163 sheet_object_update_bounds (so, NULL);
1164 } else if (!change_sheets &&
1165 range_contains (&dest, r.start.col, r.start.row)) {
1166 clear_sheet (so, pundo);
1167 continue;
1171 sheet_objects_max_extent (rinfo->origin_sheet);
1172 if (change_sheets)
1173 sheet_objects_max_extent (rinfo->target_sheet);
1177 * sheet_objects_get:
1178 * @sheet: the sheet.
1179 * @r: an optional range to look in
1180 * @t: The type of object to lookup
1182 * Returns: (element-type SheetObject) (transfer container): a list of which
1183 * the caller must free (just the list not the content).
1184 * Containing all objects of exactly the specified type (inheritence does not count)
1185 * that are completely contained by @r.
1187 GSList *
1188 sheet_objects_get (Sheet const *sheet, GnmRange const *r, GType t)
1190 GSList *res = NULL;
1191 GSList *ptr;
1193 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1195 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next ) {
1196 GObject *obj = G_OBJECT (ptr->data);
1198 if (t == G_TYPE_NONE || t == G_OBJECT_TYPE (obj)) {
1199 SheetObject *so = GNM_SO (obj);
1200 if (r == NULL || range_contained (&so->anchor.cell_bound, r))
1201 res = g_slist_prepend (res, so);
1204 return g_slist_reverse (res);
1208 * sheet_objects_clear:
1209 * @sheet: the sheet.
1210 * @r: an optional range to look in
1212 * removes the objects in the region.
1214 void
1215 sheet_objects_clear (Sheet const *sheet, GnmRange const *r, GType t,
1216 GOUndo **pundo)
1218 GSList *ptr, *next;
1220 g_return_if_fail (IS_SHEET (sheet));
1222 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = next ) {
1223 GObject *obj = G_OBJECT (ptr->data);
1224 next = ptr->next;
1225 if ((t == G_TYPE_NONE && G_OBJECT_TYPE (obj) != GNM_FILTER_COMBO_TYPE)
1226 || t == G_OBJECT_TYPE (obj)) {
1227 SheetObject *so = GNM_SO (obj);
1228 if (r == NULL || range_contained (&so->anchor.cell_bound, r))
1229 clear_sheet (so, pundo);
1235 * sheet_object_dup:
1236 * @so: a #SheetObject to duplicate
1238 * Returns: (transfer full): A copy of @so that is not attached to a sheet.
1239 * Caller is responsible for the reference.
1241 SheetObject *
1242 sheet_object_dup (SheetObject const *so)
1244 SheetObject *new_so = NULL;
1246 if (!SO_CLASS (so)->copy)
1247 return NULL;
1249 new_so = g_object_new (G_OBJECT_TYPE (so), NULL);
1251 g_return_val_if_fail (new_so != NULL, NULL);
1253 SO_CLASS (so)->copy (new_so, so);
1254 new_so->flags = so->flags;
1255 new_so->anchor = so->anchor;
1257 return new_so;
1260 static void
1261 cb_sheet_objects_dup (GnmDependent *dep, SheetObject *so, gpointer user)
1263 Sheet *src = user;
1264 Sheet *dst = sheet_object_get_sheet (so);
1265 GnmExprTop const *texpr;
1267 if (!dep->texpr)
1268 return;
1270 texpr = gnm_expr_top_relocate_sheet (dep->texpr, src, dst);
1271 if (texpr != dep->texpr) {
1272 gboolean was_linked= dependent_is_linked (dep);
1273 dependent_set_expr (dep, texpr);
1274 if (was_linked)
1275 dependent_link (dep);
1277 gnm_expr_top_unref (texpr);
1282 * sheet_objects_dup:
1283 * @src: The source sheet to read the objects from
1284 * @dst: The destination sheet to attach the objects to
1285 * @range: Optionally NULL region of interest
1287 * Clones the objects of the src sheet and attaches them into the dst sheet
1289 void
1290 sheet_objects_dup (Sheet const *src, Sheet *dst, GnmRange *range)
1292 GSList *list;
1294 g_return_if_fail (IS_SHEET (dst));
1295 g_return_if_fail (dst->sheet_objects == NULL);
1297 for (list = src->sheet_objects; list != NULL; list = list->next) {
1298 SheetObject *so = list->data;
1299 if (range == NULL || range_overlap (range, &so->anchor.cell_bound)) {
1300 SheetObject *new_so = sheet_object_dup (so);
1301 if (new_so != NULL) {
1302 sheet_object_set_sheet (new_so, dst);
1303 sheet_object_foreach_dep (new_so, cb_sheet_objects_dup,
1304 (gpointer)src);
1305 g_object_unref (new_so);
1310 dst->sheet_objects = g_slist_reverse (dst->sheet_objects);
1315 * sheet_object_direction_set:
1316 * @so: The sheet object that we are calculating the direction for
1317 * @coords: array of coordinates in L,T,R,B order
1319 * Sets the object direction from the given the new coordinates
1320 * The original coordinates are assumed to be normalized (so that top
1321 * is above bottom and right is at the right of left)
1323 void
1324 sheet_object_direction_set (SheetObject *so, gdouble const *coords)
1326 if (so->anchor.base.direction == GOD_ANCHOR_DIR_UNKNOWN)
1327 return;
1329 so->anchor.base.direction = GOD_ANCHOR_DIR_NONE_MASK;
1331 if (coords [1] < coords [3])
1332 so->anchor.base.direction |= GOD_ANCHOR_DIR_DOWN;
1333 if (coords [0] < coords [2])
1334 so->anchor.base.direction |= GOD_ANCHOR_DIR_RIGHT;
1338 * sheet_object_rubber_band_directly:
1339 * @so:
1341 * Returns TRUE if we should draw the object as we are laying it out on
1342 * an sheet. If FLASE we draw a rectangle where the object is going to go
1344 * Return Value:
1346 gboolean
1347 sheet_object_rubber_band_directly (G_GNUC_UNUSED SheetObject const *so)
1349 return FALSE;
1353 * sheet_object_anchor_init:
1354 * @anchor: #SheetObjectAnchor
1355 * @cell_bound: #GnmRange
1356 * @offsets: double[4]
1357 * @direction: #GODrawingAnchorDir
1358 * @mode: #GnmSOAnchorMode
1360 * A utility routine to initialize an anchor. Useful in case we change fields
1361 * in the future and want to ensure that everything is initialized.
1363 void
1364 sheet_object_anchor_init (SheetObjectAnchor *anchor,
1365 GnmRange const *r, const double *offsets,
1366 GODrawingAnchorDir direction,
1367 GnmSOAnchorMode mode)
1369 int i;
1371 if (r == NULL) {
1372 static GnmRange const defaultVal = { { 0, 0 }, { 1, 1 } };
1373 r = &defaultVal;
1375 anchor->cell_bound = *r;
1377 if (offsets == NULL) {
1378 static double const defaultVal [4] = { 0., 0., 0., 0. };
1379 offsets = defaultVal;
1381 for (i = 4; i-- > 0 ; )
1382 anchor->offset[i] = offsets [i];
1384 anchor->base.direction = direction;
1385 anchor->mode = mode;
1386 /* TODO : add sanity checking to handle offsets past edges of col/row */
1389 /*****************************************************************************/
1392 * sheet_object_get_stacking:
1393 * @so: #SheetObject
1395 * Returns @so's position in the stack of sheet objects.
1397 gint
1398 sheet_object_get_stacking (SheetObject *so)
1400 g_return_val_if_fail (so != NULL, 0);
1401 g_return_val_if_fail (so->sheet != NULL, 0);
1403 return g_slist_index (so->sheet->sheet_objects, so);
1406 gint
1407 sheet_object_adjust_stacking (SheetObject *so, gint offset)
1409 GList *l;
1410 GSList **ptr, *node = NULL;
1411 int i, target, cur = 0;
1413 g_return_val_if_fail (so != NULL, 0);
1414 g_return_val_if_fail (so->sheet != NULL, 0);
1416 for (ptr = &so->sheet->sheet_objects ; *ptr ; ptr = &(*ptr)->next, cur++)
1417 if ((*ptr)->data == so) {
1418 node = *ptr;
1419 *ptr = (*ptr)->next;
1420 break;
1423 g_return_val_if_fail (node != NULL, 0);
1425 /* Start at the begining when moving things towards the front */
1426 if (offset > 0) {
1427 ptr = &so->sheet->sheet_objects;
1428 i = 0;
1429 } else
1430 i = cur;
1432 for (target = cur - offset; *ptr && i < target ; ptr = &(*ptr)->next)
1433 i++;
1435 node->next = *ptr;
1436 *ptr = node;
1438 /* TODO : Move this to the container */
1439 for (l = so->realized_list; l; l = l->next) {
1440 GocItem *item = GOC_ITEM (l->data);
1441 if (offset > 0)
1442 goc_item_raise (item, offset);
1443 else
1444 goc_item_lower (item, - offset);
1446 return cur - i;
1449 void
1450 sheet_object_set_anchor_mode (SheetObject *so, GnmSOAnchorMode const *mode)
1452 /* FIXME: adjust offsets according to the old and new modes */
1453 double pts[4];
1455 if (so->anchor.mode == *mode)
1456 return;
1457 sheet_object_anchor_to_pts (&so->anchor, so->sheet, pts);
1458 so->anchor.mode = *mode;
1459 sheet_object_pts_to_anchor (&so->anchor, so->sheet, pts);
1462 /*****************************************************************************/
1464 static GObjectClass *view_parent_class;
1466 void
1467 sheet_object_view_set_bounds (SheetObjectView *sov,
1468 double const *coords, gboolean visible)
1470 SheetObjectViewClass *klass;
1472 g_return_if_fail (GNM_IS_SO_VIEW (sov));
1473 klass = GNM_SO_VIEW_GET_CLASS (sov);
1474 if (NULL != klass->set_bounds)
1475 (klass->set_bounds) (sov, coords, visible);
1479 * sheet_object_view_get_so:
1480 * @sov: #SheetObjectView
1482 * Returns: (transfer none): the #SheetObject owning @view.
1484 SheetObject *
1485 sheet_object_view_get_so (SheetObjectView *view)
1487 return g_object_get_qdata (G_OBJECT (view), sov_so_quark);
1490 static gboolean
1491 sheet_object_view_enter_notify (GocItem *item, double x, double y)
1493 SheetObject *so;
1495 if (GNM_IS_PANE (item->canvas) && scg_wbcg (GNM_SIMPLE_CANVAS (item->canvas)->scg)->new_object) {
1496 GnmItemGrid *grid = GNM_PANE (item->canvas)->grid;
1497 return GOC_ITEM_GET_CLASS (grid)->enter_notify (GOC_ITEM (grid), x, y);
1500 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1501 gnm_widget_set_cursor_type (GTK_WIDGET (item->canvas),
1502 (so->flags & SHEET_OBJECT_CAN_PRESS) ? GDK_HAND2 : GDK_ARROW);
1503 return FALSE;
1506 static void
1507 cb_so_menu_activate (GObject *menu, GocItem *view)
1509 SheetObjectAction const *a = g_object_get_data (menu, "action");
1511 if (a->func) {
1512 SheetObject *so = sheet_object_view_get_so (GNM_SO_VIEW (view));
1513 gpointer data = g_object_get_data (G_OBJECT (view->canvas), "sheet-control");
1515 if (data == NULL)
1516 data = GNM_SIMPLE_CANVAS (view->canvas)->scg;
1518 (a->func) (so, GNM_SC (data));
1522 static void
1523 cb_ptr_array_free (GPtrArray *actions)
1525 g_ptr_array_free (actions, TRUE);
1529 * sheet_object_build_menu:
1530 * @view: #SheetObjectView
1531 * @actions: (element-type SheetObjectAction): array of actions.
1532 * @i: index of first action to add in the array.
1534 * Builds the contextual menu for @view.
1535 * Returns: (transfer full): the newly constructed #GtkMenu.
1537 GtkWidget *
1538 sheet_object_build_menu (SheetObjectView *view,
1539 GPtrArray const *actions, unsigned *i)
1541 SheetObjectAction const *a;
1542 GtkWidget *item, *menu = gtk_menu_new ();
1544 while (*i < actions->len) {
1545 a = g_ptr_array_index (actions, *i);
1546 (*i)++;
1547 if (a->submenu < 0)
1548 break;
1549 if (a->icon != NULL) {
1550 if (a->label != NULL) {
1551 item = gtk_image_menu_item_new_with_mnemonic (_(a->label));
1552 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
1553 gtk_image_new_from_icon_name (a->icon, GTK_ICON_SIZE_MENU));
1554 } else
1555 item = gtk_image_menu_item_new_from_stock (a->icon, NULL);
1556 } else if (a->label != NULL)
1557 item = gtk_menu_item_new_with_mnemonic (_(a->label));
1558 else
1559 item = gtk_separator_menu_item_new ();
1560 if (a->submenu > 0)
1561 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
1562 sheet_object_build_menu (view, actions, i));
1563 else if (a->label != NULL || a->icon != NULL) { /* not a separator or menu */
1564 g_object_set_data (G_OBJECT (item), "action", (gpointer)a);
1565 g_signal_connect_object (G_OBJECT (item), "activate",
1566 G_CALLBACK (cb_so_menu_activate), view, 0);
1567 gtk_widget_set_sensitive (item, a->enable_func == NULL
1568 || a->enable_func (sheet_object_view_get_so (view)));
1570 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1572 return menu;
1575 static gboolean
1576 sheet_object_view_button_pressed (GocItem *item, int button, double x, double y)
1578 GnmPane *pane;
1579 SheetObject *so;
1580 if (GNM_IS_PANE (item->canvas)) {
1581 if (scg_wbcg (GNM_SIMPLE_CANVAS (item->canvas)->scg)->new_object) {
1582 GnmItemGrid *grid = GNM_PANE (item->canvas)->grid;
1583 return GOC_ITEM_GET_CLASS (grid)->button_pressed (GOC_ITEM (grid), button, x, y);
1586 if (button > 3)
1587 return FALSE;
1589 pane = GNM_PANE (item->canvas);
1590 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1592 x *= goc_canvas_get_pixels_per_unit (item->canvas);
1593 y *= goc_canvas_get_pixels_per_unit (item->canvas);
1594 /* cb_sheet_object_widget_canvas_event calls even if selected */
1595 if (NULL == g_hash_table_lookup (pane->drag.ctrl_pts, so)) {
1596 SheetObjectClass *soc =
1597 G_TYPE_INSTANCE_GET_CLASS (so, GNM_SO_TYPE, SheetObjectClass);
1598 GdkEventButton *event = (GdkEventButton *) goc_canvas_get_cur_event (item->canvas);
1600 if (soc->interactive && button != 3)
1601 return FALSE;
1603 if (!(event->state & GDK_SHIFT_MASK))
1604 scg_object_unselect (pane->simple.scg, NULL);
1605 scg_object_select (pane->simple.scg, so);
1606 if (NULL == g_hash_table_lookup (pane->drag.ctrl_pts, so))
1607 return FALSE; /* protected ? */
1610 if (button < 3)
1611 gnm_pane_object_start_resize (pane, button, x, y, so, 8, FALSE);
1612 else
1613 gnm_pane_display_object_menu (pane, so, goc_canvas_get_cur_event (item->canvas));
1614 } else {
1615 if (button == 3) {
1616 GPtrArray *actions = g_ptr_array_new ();
1617 GtkWidget *menu;
1618 unsigned i = 0;
1620 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1621 sheet_object_populate_menu (so, actions);
1623 if (actions->len == 0) {
1624 g_ptr_array_free (actions, TRUE);
1625 return FALSE;
1628 menu = sheet_object_build_menu
1629 (sheet_object_get_view (so, (SheetObjectViewContainer *) item->canvas),
1630 actions, &i);
1631 g_object_set_data_full (G_OBJECT (menu), "actions", actions,
1632 (GDestroyNotify) cb_ptr_array_free);
1633 gtk_widget_show_all (menu);
1634 gnumeric_popup_menu (GTK_MENU (menu),
1635 goc_canvas_get_cur_event (item->canvas));
1638 return TRUE;
1641 static gboolean
1642 sheet_object_view_button2_pressed (GocItem *item, int button, double x, double y)
1644 if (button == 1 && !GNM_IS_PANE (item->canvas)) {
1645 SheetControl *sc = GNM_SC (g_object_get_data (G_OBJECT (item->canvas), "sheet-control"));
1646 SheetObject *so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1648 if (sc && sheet_object_can_edit (so))
1649 sheet_object_get_editor (so, sc);
1651 return TRUE;
1654 static void
1655 sheet_object_view_finalize (GObject *obj)
1657 SheetObject *so = (SheetObject *) g_object_get_qdata (obj, sov_so_quark);
1658 if (so)
1659 so->realized_list = g_list_remove (so->realized_list, obj);
1660 view_parent_class->finalize (obj);
1663 static void
1664 sheet_object_view_class_init (GocItemClass *item_klass)
1666 GObjectClass *obj_klass = (GObjectClass *) item_klass;
1667 view_parent_class = g_type_class_peek_parent (item_klass);
1669 obj_klass->finalize = sheet_object_view_finalize;
1671 item_klass->enter_notify = sheet_object_view_enter_notify;
1672 item_klass->button_pressed = sheet_object_view_button_pressed;
1673 item_klass->button2_pressed = sheet_object_view_button2_pressed;
1676 GSF_CLASS (SheetObjectView, sheet_object_view,
1677 sheet_object_view_class_init, NULL,
1678 GOC_TYPE_GROUP)
1680 /*****************************************************************************/
1682 GType
1683 sheet_object_imageable_get_type (void)
1685 static GType type = 0;
1687 if (!type) {
1688 static GTypeInfo const type_info = {
1689 sizeof (SheetObjectImageableIface), /* class_size */
1690 NULL, /* base_init */
1691 NULL, /* base_finalize */
1692 NULL, NULL, NULL, 0, 0, NULL, NULL
1695 type = g_type_register_static (G_TYPE_INTERFACE,
1696 "SheetObjectImageable", &type_info, 0);
1699 return type;
1702 #define GNM_SO_IMAGEABLE_CLASS(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GNM_SO_IMAGEABLE_TYPE, SheetObjectImageableIface))
1704 GtkTargetList *
1705 sheet_object_get_target_list (SheetObject const *so)
1707 if (!GNM_IS_SO_IMAGEABLE (so))
1708 return NULL;
1710 return GNM_SO_IMAGEABLE_CLASS (so)->get_target_list (so);
1713 void
1714 sheet_object_write_image (SheetObject const *so, char const *format, double resolution,
1715 GsfOutput *output, GError **err)
1717 g_return_if_fail (GNM_IS_SO_IMAGEABLE (so));
1719 GNM_SO_IMAGEABLE_CLASS (so)->write_image (so, format, resolution,
1720 output, err);
1724 /*****************************************************************************/
1726 GType
1727 sheet_object_exportable_get_type (void)
1729 static GType type = 0;
1731 if (!type) {
1732 static GTypeInfo const type_info = {
1733 sizeof (SheetObjectExportableIface), /* class_size */
1734 NULL, /* base_init */
1735 NULL, /* base_finalize */
1736 NULL, NULL, NULL, 0, 0, NULL, NULL
1739 type = g_type_register_static (G_TYPE_INTERFACE,
1740 "SheetObjectExportable", &type_info, 0);
1743 return type;
1746 #define GNM_SO_EXPORTABLE_CLASS(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GNM_SO_EXPORTABLE_TYPE, SheetObjectExportableIface))
1748 GtkTargetList *
1749 sheet_object_exportable_get_target_list (SheetObject const *so)
1751 if (!GNM_IS_SO_EXPORTABLE (so))
1752 return NULL;
1754 return GNM_SO_EXPORTABLE_CLASS (so)->get_target_list (so);
1757 void
1758 sheet_object_write_object (SheetObject const *so, char const *format,
1759 GsfOutput *output, GError **err,
1760 GnmConventions const *convs)
1762 GnmLocale *locale;
1764 g_return_if_fail (GNM_IS_SO_EXPORTABLE (so));
1766 locale = gnm_push_C_locale ();
1767 GNM_SO_EXPORTABLE_CLASS (so)->
1768 write_object (so, format, output, err, convs);
1769 gnm_pop_C_locale (locale);
1773 * sheet_object_move_undo:
1774 * @objects: (element-type SheetObject):
1775 * @objects_created:
1777 * Returns: (transfer full): the newly allocated #GOUndo.
1779 GOUndo *
1780 sheet_object_move_undo (GSList *objects, gboolean objects_created)
1782 GOUndo *undo = NULL;
1783 GSList *objs = objects;
1785 g_return_val_if_fail (NULL != objects, NULL);
1787 for (; objs; objs = objs->next) {
1788 SheetObject *obj = objs->data;
1789 SheetObjectAnchor *tmp;
1791 if (objects_created) {
1792 undo = go_undo_combine
1793 (undo,
1794 go_undo_unary_new
1795 (g_object_ref (obj),
1796 (GOUndoUnaryFunc) sheet_object_clear_sheet,
1797 (GFreeFunc) g_object_unref));
1800 tmp = g_new (SheetObjectAnchor, 1);
1801 *tmp = *sheet_object_get_anchor (obj);
1802 undo = go_undo_combine
1803 (undo, go_undo_binary_new
1804 (g_object_ref (obj), tmp,
1805 (GOUndoBinaryFunc) sheet_object_set_anchor,
1806 (GFreeFunc) g_object_unref,
1807 (GFreeFunc) g_free));
1809 return undo;
1813 * sheet_object_move_do:
1814 * @objects: (element-type SheetObject):
1815 * @anchors: (element-type SheetObjectAnchor):
1816 * @objects_created:
1818 * Returns: (transfer full): the newly allocated #GOUndo.
1820 GOUndo *
1821 sheet_object_move_do (GSList *objects, GSList *anchors,
1822 gboolean objects_created)
1824 GOUndo *undo = NULL;
1825 GSList *objs = objects, *anchs = anchors;
1827 g_return_val_if_fail (NULL != objects, NULL);
1828 g_return_val_if_fail (NULL != anchors, NULL);
1829 g_return_val_if_fail (g_slist_length (objects)
1830 == g_slist_length (anchors), NULL);
1832 for (; objs && anchs; objs = objs->next, anchs = anchs->next) {
1833 SheetObject *obj = objs->data;
1834 SheetObjectAnchor *anch = anchs->data;
1835 SheetObjectAnchor *tmp;
1837 if (objects_created) {
1838 undo = go_undo_combine
1839 (undo,
1840 go_undo_binary_new
1841 (g_object_ref (obj),
1842 sheet_object_get_sheet (obj),
1843 (GOUndoBinaryFunc) sheet_object_set_sheet,
1844 (GFreeFunc) g_object_unref,
1845 NULL));
1847 tmp = g_new (SheetObjectAnchor, 1);
1848 *tmp = *anch;
1849 undo = go_undo_combine
1850 (go_undo_binary_new
1851 (g_object_ref (obj), tmp,
1852 (GOUndoBinaryFunc) sheet_object_set_anchor,
1853 (GFreeFunc) g_object_unref,
1854 (GFreeFunc) g_free), undo);
1856 return undo;
1860 /*****************************************************************************/
1863 * sheet_objects_init: (skip)
1865 void
1866 sheet_objects_init (void)
1868 GNM_SO_LINE_TYPE;
1869 GNM_SO_FILLED_TYPE;
1870 GNM_SO_GRAPH_TYPE;
1871 GNM_SO_IMAGE_TYPE;
1872 GNM_GO_DATA_SCALAR_TYPE;
1873 GNM_GO_DATA_VECTOR_TYPE;
1874 GNM_GO_DATA_MATRIX_TYPE;
1875 GNM_CELL_COMMENT_TYPE;
1877 sheet_object_widget_register ();
1878 sov_so_quark = g_quark_from_static_string ("SheetObject");
1879 sov_container_quark = g_quark_from_static_string ("SheetObjectViewContainer");