Code cleanup
[gnumeric.git] / src / sheet-object.c
blobea85423a750b5e82a320204664fcccca7acebae8
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 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): 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: a ptr into which to store the default_width.
905 * @h: 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: array of 4 doubles
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: if non-NULL 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
1217 * Removes the objects in the region.
1219 void
1220 sheet_objects_clear (Sheet const *sheet, GnmRange const *r, GType t,
1221 GOUndo **pundo)
1223 GSList *ptr, *next;
1225 g_return_if_fail (IS_SHEET (sheet));
1227 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = next ) {
1228 GObject *obj = G_OBJECT (ptr->data);
1229 next = ptr->next;
1230 if ((t == G_TYPE_NONE && G_OBJECT_TYPE (obj) != GNM_FILTER_COMBO_TYPE)
1231 || t == G_OBJECT_TYPE (obj)) {
1232 SheetObject *so = GNM_SO (obj);
1233 if (r == NULL || range_contained (&so->anchor.cell_bound, r))
1234 clear_sheet (so, pundo);
1240 * sheet_object_dup:
1241 * @so: a #SheetObject to duplicate
1243 * Returns: (transfer full): A copy of @so that is not attached to a sheet.
1245 SheetObject *
1246 sheet_object_dup (SheetObject const *so)
1248 SheetObject *new_so = NULL;
1250 if (!SO_CLASS (so)->copy)
1251 return NULL;
1253 new_so = g_object_new (G_OBJECT_TYPE (so), NULL);
1255 g_return_val_if_fail (new_so != NULL, NULL);
1257 SO_CLASS (so)->copy (new_so, so);
1258 new_so->flags = so->flags;
1259 new_so->anchor = so->anchor;
1261 return new_so;
1264 static void
1265 cb_sheet_objects_dup (GnmDependent *dep, SheetObject *so, gpointer user)
1267 Sheet *src = user;
1268 Sheet *dst = sheet_object_get_sheet (so);
1269 GnmExprTop const *texpr;
1271 if (!dep->texpr)
1272 return;
1274 texpr = gnm_expr_top_relocate_sheet (dep->texpr, src, dst);
1275 if (texpr != dep->texpr) {
1276 gboolean was_linked= dependent_is_linked (dep);
1277 dependent_set_expr (dep, texpr);
1278 if (was_linked)
1279 dependent_link (dep);
1281 gnm_expr_top_unref (texpr);
1286 * sheet_objects_dup:
1287 * @src: The source sheet to read the objects from
1288 * @dst: The destination sheet to attach the objects to
1289 * @range: (nullable): #GnmRange to look in
1291 * Clones the objects of the src sheet and attaches them into the dst sheet
1293 void
1294 sheet_objects_dup (Sheet const *src, Sheet *dst, GnmRange *range)
1296 GSList *list;
1298 g_return_if_fail (IS_SHEET (dst));
1299 g_return_if_fail (dst->sheet_objects == NULL);
1301 for (list = src->sheet_objects; list != NULL; list = list->next) {
1302 SheetObject *so = list->data;
1303 if (range == NULL || range_overlap (range, &so->anchor.cell_bound)) {
1304 SheetObject *new_so = sheet_object_dup (so);
1305 if (new_so != NULL) {
1306 sheet_object_set_sheet (new_so, dst);
1307 sheet_object_foreach_dep (new_so, cb_sheet_objects_dup,
1308 (gpointer)src);
1309 g_object_unref (new_so);
1314 dst->sheet_objects = g_slist_reverse (dst->sheet_objects);
1319 * sheet_object_direction_set:
1320 * @so: The sheet object that we are calculating the direction for
1321 * @coords: (in) (array fixed-size=4): array of coordinates in L,T,R,B order
1323 * Sets the object direction from the given the new coordinates
1324 * The original coordinates are assumed to be normalized (so that top
1325 * is above bottom and right is at the right of left)
1327 void
1328 sheet_object_direction_set (SheetObject *so, gdouble const *coords)
1330 if (so->anchor.base.direction == GOD_ANCHOR_DIR_UNKNOWN)
1331 return;
1333 so->anchor.base.direction = GOD_ANCHOR_DIR_NONE_MASK;
1335 if (coords [1] < coords [3])
1336 so->anchor.base.direction |= GOD_ANCHOR_DIR_DOWN;
1337 if (coords [0] < coords [2])
1338 so->anchor.base.direction |= GOD_ANCHOR_DIR_RIGHT;
1342 * sheet_object_rubber_band_directly:
1343 * @so:
1345 * Returns: %TRUE if we should draw the object as we are laying it out on
1346 * an sheet. If %FALSE we draw a rectangle where the object is going to go
1348 * Return Value:
1350 gboolean
1351 sheet_object_rubber_band_directly (G_GNUC_UNUSED SheetObject const *so)
1353 return FALSE;
1357 * sheet_object_anchor_init:
1358 * @anchor: #SheetObjectAnchor
1359 * @r: (nullable): #GnmRange to look in
1360 * @offsets: (in) (array fixed-size=4) (nullable):
1361 * @direction: #GODrawingAnchorDir
1362 * @mode: #GnmSOAnchorMode
1364 * A utility routine to initialize an anchor. Useful in case we change fields
1365 * in the future and want to ensure that everything is initialized.
1367 void
1368 sheet_object_anchor_init (SheetObjectAnchor *anchor,
1369 GnmRange const *r, const double *offsets,
1370 GODrawingAnchorDir direction,
1371 GnmSOAnchorMode mode)
1373 int i;
1375 if (r == NULL) {
1376 static GnmRange const defaultVal = { { 0, 0 }, { 1, 1 } };
1377 r = &defaultVal;
1379 anchor->cell_bound = *r;
1381 if (offsets == NULL) {
1382 static double const defaultVal [4] = { 0., 0., 0., 0. };
1383 offsets = defaultVal;
1385 for (i = 4; i-- > 0 ; )
1386 anchor->offset[i] = offsets [i];
1388 anchor->base.direction = direction;
1389 anchor->mode = mode;
1390 /* TODO : add sanity checking to handle offsets past edges of col/row */
1393 /*****************************************************************************/
1396 * sheet_object_get_stacking:
1397 * @so: #SheetObject
1399 * Returns: @so's position in the stack of sheet objects.
1401 gint
1402 sheet_object_get_stacking (SheetObject *so)
1404 g_return_val_if_fail (so != NULL, 0);
1405 g_return_val_if_fail (so->sheet != NULL, 0);
1407 return g_slist_index (so->sheet->sheet_objects, so);
1410 gint
1411 sheet_object_adjust_stacking (SheetObject *so, gint offset)
1413 GList *l;
1414 GSList **ptr, *node = NULL;
1415 int i, target, cur = 0;
1417 g_return_val_if_fail (so != NULL, 0);
1418 g_return_val_if_fail (so->sheet != NULL, 0);
1420 for (ptr = &so->sheet->sheet_objects ; *ptr ; ptr = &(*ptr)->next, cur++)
1421 if ((*ptr)->data == so) {
1422 node = *ptr;
1423 *ptr = (*ptr)->next;
1424 break;
1427 g_return_val_if_fail (node != NULL, 0);
1429 /* Start at the begining when moving things towards the front */
1430 if (offset > 0) {
1431 ptr = &so->sheet->sheet_objects;
1432 i = 0;
1433 } else
1434 i = cur;
1436 for (target = cur - offset; *ptr && i < target ; ptr = &(*ptr)->next)
1437 i++;
1439 node->next = *ptr;
1440 *ptr = node;
1442 /* TODO : Move this to the container */
1443 for (l = so->realized_list; l; l = l->next) {
1444 GocItem *item = GOC_ITEM (l->data);
1445 if (offset > 0)
1446 goc_item_raise (item, offset);
1447 else
1448 goc_item_lower (item, - offset);
1450 return cur - i;
1453 void
1454 sheet_object_set_anchor_mode (SheetObject *so, GnmSOAnchorMode const *mode)
1456 /* FIXME: adjust offsets according to the old and new modes */
1457 double pts[4];
1459 if (so->anchor.mode == *mode)
1460 return;
1461 sheet_object_anchor_to_pts (&so->anchor, so->sheet, pts);
1462 so->anchor.mode = *mode;
1463 sheet_object_pts_to_anchor (&so->anchor, so->sheet, pts);
1466 /*****************************************************************************/
1468 static GObjectClass *view_parent_class;
1470 void
1471 sheet_object_view_set_bounds (SheetObjectView *sov,
1472 double const *coords, gboolean visible)
1474 SheetObjectViewClass *klass;
1476 g_return_if_fail (GNM_IS_SO_VIEW (sov));
1477 klass = GNM_SO_VIEW_GET_CLASS (sov);
1478 if (NULL != klass->set_bounds)
1479 (klass->set_bounds) (sov, coords, visible);
1483 * sheet_object_view_get_so:
1484 * @sov: #SheetObjectView
1486 * Returns: (transfer none): the #SheetObject owning @view.
1488 SheetObject *
1489 sheet_object_view_get_so (SheetObjectView *view)
1491 return g_object_get_qdata (G_OBJECT (view), sov_so_quark);
1494 static gboolean
1495 sheet_object_view_enter_notify (GocItem *item, double x, double y)
1497 SheetObject *so;
1499 if (GNM_IS_PANE (item->canvas) && scg_wbcg (GNM_SIMPLE_CANVAS (item->canvas)->scg)->new_object) {
1500 GnmItemGrid *grid = GNM_PANE (item->canvas)->grid;
1501 return GOC_ITEM_GET_CLASS (grid)->enter_notify (GOC_ITEM (grid), x, y);
1504 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1505 gnm_widget_set_cursor_type (GTK_WIDGET (item->canvas),
1506 (so->flags & SHEET_OBJECT_CAN_PRESS) ? GDK_HAND2 : GDK_ARROW);
1507 return FALSE;
1510 static void
1511 cb_so_menu_activate (GObject *menu, GocItem *view)
1513 SheetObjectAction const *a = g_object_get_data (menu, "action");
1515 if (a->func) {
1516 SheetObject *so = sheet_object_view_get_so (GNM_SO_VIEW (view));
1517 gpointer data = g_object_get_data (G_OBJECT (view->canvas), "sheet-control");
1519 if (data == NULL)
1520 data = GNM_SIMPLE_CANVAS (view->canvas)->scg;
1522 (a->func) (so, GNM_SC (data));
1526 static void
1527 cb_ptr_array_free (GPtrArray *actions)
1529 g_ptr_array_free (actions, TRUE);
1533 * sheet_object_build_menu:
1534 * @view: #SheetObjectView
1535 * @actions: (element-type SheetObjectAction): array of actions.
1536 * @i: index of first action to add in the array.
1538 * Builds the contextual menu for @view.
1539 * Returns: (transfer full): the newly constructed #GtkMenu.
1541 GtkWidget *
1542 sheet_object_build_menu (SheetObjectView *view,
1543 GPtrArray const *actions, unsigned *i)
1545 SheetObjectAction const *a;
1546 GtkWidget *item, *menu = gtk_menu_new ();
1548 while (*i < actions->len) {
1549 a = g_ptr_array_index (actions, *i);
1550 (*i)++;
1551 if (a->submenu < 0)
1552 break;
1553 if (a->icon != NULL) {
1554 if (a->label != NULL) {
1555 item = gtk_image_menu_item_new_with_mnemonic (_(a->label));
1556 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
1557 gtk_image_new_from_icon_name (a->icon, GTK_ICON_SIZE_MENU));
1558 } else
1559 item = gtk_image_menu_item_new_from_stock (a->icon, NULL);
1560 } else if (a->label != NULL)
1561 item = gtk_menu_item_new_with_mnemonic (_(a->label));
1562 else
1563 item = gtk_separator_menu_item_new ();
1564 if (a->submenu > 0)
1565 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
1566 sheet_object_build_menu (view, actions, i));
1567 else if (a->label != NULL || a->icon != NULL) { /* not a separator or menu */
1568 g_object_set_data (G_OBJECT (item), "action", (gpointer)a);
1569 g_signal_connect_object (G_OBJECT (item), "activate",
1570 G_CALLBACK (cb_so_menu_activate), view, 0);
1571 gtk_widget_set_sensitive (item, a->enable_func == NULL
1572 || a->enable_func (sheet_object_view_get_so (view)));
1574 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1576 return menu;
1579 static gboolean
1580 sheet_object_view_button_pressed (GocItem *item, int button, double x, double y)
1582 GnmPane *pane;
1583 SheetObject *so;
1584 if (GNM_IS_PANE (item->canvas)) {
1585 if (scg_wbcg (GNM_SIMPLE_CANVAS (item->canvas)->scg)->new_object) {
1586 GnmItemGrid *grid = GNM_PANE (item->canvas)->grid;
1587 return GOC_ITEM_GET_CLASS (grid)->button_pressed (GOC_ITEM (grid), button, x, y);
1590 if (button > 3)
1591 return FALSE;
1593 pane = GNM_PANE (item->canvas);
1594 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1596 x *= goc_canvas_get_pixels_per_unit (item->canvas);
1597 y *= goc_canvas_get_pixels_per_unit (item->canvas);
1598 /* cb_sheet_object_widget_canvas_event calls even if selected */
1599 if (NULL == g_hash_table_lookup (pane->drag.ctrl_pts, so)) {
1600 SheetObjectClass *soc =
1601 G_TYPE_INSTANCE_GET_CLASS (so, GNM_SO_TYPE, SheetObjectClass);
1602 GdkEventButton *event = (GdkEventButton *) goc_canvas_get_cur_event (item->canvas);
1604 if (soc->interactive && button != 3)
1605 return FALSE;
1607 if (!(event->state & GDK_SHIFT_MASK))
1608 scg_object_unselect (pane->simple.scg, NULL);
1609 scg_object_select (pane->simple.scg, so);
1610 if (NULL == g_hash_table_lookup (pane->drag.ctrl_pts, so))
1611 return FALSE; /* protected ? */
1614 if (button < 3)
1615 gnm_pane_object_start_resize (pane, button, x, y, so, 8, FALSE);
1616 else
1617 gnm_pane_display_object_menu (pane, so, goc_canvas_get_cur_event (item->canvas));
1618 } else {
1619 if (button == 3) {
1620 GPtrArray *actions = g_ptr_array_new ();
1621 GtkWidget *menu;
1622 unsigned i = 0;
1624 so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1625 sheet_object_populate_menu (so, actions);
1627 if (actions->len == 0) {
1628 g_ptr_array_free (actions, TRUE);
1629 return FALSE;
1632 menu = sheet_object_build_menu
1633 (sheet_object_get_view (so, (SheetObjectViewContainer *) item->canvas),
1634 actions, &i);
1635 g_object_set_data_full (G_OBJECT (menu), "actions", actions,
1636 (GDestroyNotify) cb_ptr_array_free);
1637 gtk_widget_show_all (menu);
1638 gnumeric_popup_menu (GTK_MENU (menu),
1639 goc_canvas_get_cur_event (item->canvas));
1642 return TRUE;
1645 static gboolean
1646 sheet_object_view_button2_pressed (GocItem *item, int button, double x, double y)
1648 if (button == 1 && !GNM_IS_PANE (item->canvas)) {
1649 SheetControl *sc = GNM_SC (g_object_get_data (G_OBJECT (item->canvas), "sheet-control"));
1650 SheetObject *so = (SheetObject *) g_object_get_qdata (G_OBJECT (item), sov_so_quark);
1652 if (sc && sheet_object_can_edit (so))
1653 sheet_object_get_editor (so, sc);
1655 return TRUE;
1658 static void
1659 sheet_object_view_finalize (GObject *obj)
1661 SheetObject *so = (SheetObject *) g_object_get_qdata (obj, sov_so_quark);
1662 if (so)
1663 so->realized_list = g_list_remove (so->realized_list, obj);
1664 view_parent_class->finalize (obj);
1667 static void
1668 sheet_object_view_class_init (GocItemClass *item_klass)
1670 GObjectClass *obj_klass = (GObjectClass *) item_klass;
1671 view_parent_class = g_type_class_peek_parent (item_klass);
1673 obj_klass->finalize = sheet_object_view_finalize;
1675 item_klass->enter_notify = sheet_object_view_enter_notify;
1676 item_klass->button_pressed = sheet_object_view_button_pressed;
1677 item_klass->button2_pressed = sheet_object_view_button2_pressed;
1680 GSF_CLASS (SheetObjectView, sheet_object_view,
1681 sheet_object_view_class_init, NULL,
1682 GOC_TYPE_GROUP)
1684 /*****************************************************************************/
1686 GType
1687 sheet_object_imageable_get_type (void)
1689 static GType type = 0;
1691 if (!type) {
1692 static GTypeInfo const type_info = {
1693 sizeof (SheetObjectImageableIface), /* class_size */
1694 NULL, /* base_init */
1695 NULL, /* base_finalize */
1696 NULL, NULL, NULL, 0, 0, NULL, NULL
1699 type = g_type_register_static (G_TYPE_INTERFACE,
1700 "SheetObjectImageable", &type_info, 0);
1703 return type;
1706 #define GNM_SO_IMAGEABLE_CLASS(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GNM_SO_IMAGEABLE_TYPE, SheetObjectImageableIface))
1708 GtkTargetList *
1709 sheet_object_get_target_list (SheetObject const *so)
1711 if (!GNM_IS_SO_IMAGEABLE (so))
1712 return NULL;
1714 return GNM_SO_IMAGEABLE_CLASS (so)->get_target_list (so);
1717 void
1718 sheet_object_write_image (SheetObject const *so, char const *format, double resolution,
1719 GsfOutput *output, GError **err)
1721 g_return_if_fail (GNM_IS_SO_IMAGEABLE (so));
1723 GNM_SO_IMAGEABLE_CLASS (so)->write_image (so, format, resolution,
1724 output, err);
1728 /*****************************************************************************/
1730 GType
1731 sheet_object_exportable_get_type (void)
1733 static GType type = 0;
1735 if (!type) {
1736 static GTypeInfo const type_info = {
1737 sizeof (SheetObjectExportableIface), /* class_size */
1738 NULL, /* base_init */
1739 NULL, /* base_finalize */
1740 NULL, NULL, NULL, 0, 0, NULL, NULL
1743 type = g_type_register_static (G_TYPE_INTERFACE,
1744 "SheetObjectExportable", &type_info, 0);
1747 return type;
1750 #define GNM_SO_EXPORTABLE_CLASS(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), GNM_SO_EXPORTABLE_TYPE, SheetObjectExportableIface))
1752 GtkTargetList *
1753 sheet_object_exportable_get_target_list (SheetObject const *so)
1755 if (!GNM_IS_SO_EXPORTABLE (so))
1756 return NULL;
1758 return GNM_SO_EXPORTABLE_CLASS (so)->get_target_list (so);
1761 void
1762 sheet_object_write_object (SheetObject const *so, char const *format,
1763 GsfOutput *output, GError **err,
1764 GnmConventions const *convs)
1766 GnmLocale *locale;
1768 g_return_if_fail (GNM_IS_SO_EXPORTABLE (so));
1770 locale = gnm_push_C_locale ();
1771 GNM_SO_EXPORTABLE_CLASS (so)->
1772 write_object (so, format, output, err, convs);
1773 gnm_pop_C_locale (locale);
1777 * sheet_object_move_undo:
1778 * @objects: (element-type SheetObject):
1779 * @objects_created:
1781 * Returns: (transfer full): the newly allocated #GOUndo.
1783 GOUndo *
1784 sheet_object_move_undo (GSList *objects, gboolean objects_created)
1786 GOUndo *undo = NULL;
1787 GSList *objs = objects;
1789 g_return_val_if_fail (NULL != objects, NULL);
1791 for (; objs; objs = objs->next) {
1792 SheetObject *obj = objs->data;
1793 SheetObjectAnchor *tmp;
1795 if (objects_created) {
1796 undo = go_undo_combine
1797 (undo,
1798 go_undo_unary_new
1799 (g_object_ref (obj),
1800 (GOUndoUnaryFunc) sheet_object_clear_sheet,
1801 (GFreeFunc) g_object_unref));
1804 tmp = g_new (SheetObjectAnchor, 1);
1805 *tmp = *sheet_object_get_anchor (obj);
1806 undo = go_undo_combine
1807 (undo, go_undo_binary_new
1808 (g_object_ref (obj), tmp,
1809 (GOUndoBinaryFunc) sheet_object_set_anchor,
1810 (GFreeFunc) g_object_unref,
1811 (GFreeFunc) g_free));
1813 return undo;
1817 * sheet_object_move_do:
1818 * @objects: (element-type SheetObject):
1819 * @anchors: (element-type SheetObjectAnchor):
1820 * @objects_created:
1822 * Returns: (transfer full): the newly allocated #GOUndo.
1824 GOUndo *
1825 sheet_object_move_do (GSList *objects, GSList *anchors,
1826 gboolean objects_created)
1828 GOUndo *undo = NULL;
1829 GSList *objs = objects, *anchs = anchors;
1831 g_return_val_if_fail (NULL != objects, NULL);
1832 g_return_val_if_fail (NULL != anchors, NULL);
1833 g_return_val_if_fail (g_slist_length (objects)
1834 == g_slist_length (anchors), NULL);
1836 for (; objs && anchs; objs = objs->next, anchs = anchs->next) {
1837 SheetObject *obj = objs->data;
1838 SheetObjectAnchor *anch = anchs->data;
1839 SheetObjectAnchor *tmp;
1841 if (objects_created) {
1842 undo = go_undo_combine
1843 (undo,
1844 go_undo_binary_new
1845 (g_object_ref (obj),
1846 sheet_object_get_sheet (obj),
1847 (GOUndoBinaryFunc) sheet_object_set_sheet,
1848 (GFreeFunc) g_object_unref,
1849 NULL));
1851 tmp = g_new (SheetObjectAnchor, 1);
1852 *tmp = *anch;
1853 undo = go_undo_combine
1854 (go_undo_binary_new
1855 (g_object_ref (obj), tmp,
1856 (GOUndoBinaryFunc) sheet_object_set_anchor,
1857 (GFreeFunc) g_object_unref,
1858 (GFreeFunc) g_free), undo);
1860 return undo;
1864 /*****************************************************************************/
1867 * sheet_objects_init: (skip)
1869 void
1870 sheet_objects_init (void)
1872 GNM_SO_LINE_TYPE;
1873 GNM_SO_FILLED_TYPE;
1874 GNM_SO_GRAPH_TYPE;
1875 GNM_SO_IMAGE_TYPE;
1876 GNM_GO_DATA_SCALAR_TYPE;
1877 GNM_GO_DATA_VECTOR_TYPE;
1878 GNM_GO_DATA_MATRIX_TYPE;
1879 GNM_CELL_COMMENT_TYPE;
1881 sheet_object_widget_register ();
1882 sov_so_quark = g_quark_from_static_string ("SheetObject");
1883 sov_container_quark = g_quark_from_static_string ("SheetObjectViewContainer");