3 * The Cursor Canvas Item: Implements a rectangular cursor
6 * Miguel de Icaza (miguel@kernel.org)
7 * Jody Goldberg (jody@gnome.org)
8 * Copyright 2015 by Morten Welinder (terra@gnome.org).
11 #include <gnumeric-config.h>
14 #include "item-cursor.h"
15 #include "gnm-pane-impl.h"
17 #include "sheet-control-gui.h"
18 #include "sheet-control-priv.h"
19 #include "style-color.h"
21 #include "clipboard.h"
22 #include "selection.h"
24 #include "sheet-view.h"
25 #include "sheet-merge.h"
33 #include "parse-util.h"
36 #include "sheet-autofill.h"
37 #include <gsf/gsf-impl-utils.h>
39 #include <goffice/goffice.h>
40 #define GNUMERIC_ITEM "CURSOR"
42 #define ITEM_CURSOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), gnm_item_cursor_get_type (), GnmItemCursorClass))
44 #define AUTO_HANDLE_WIDTH 2
45 #define AUTO_HANDLE_SPACE (AUTO_HANDLE_WIDTH * 2)
46 #define CLIP_SAFETY_MARGIN (AUTO_HANDLE_SPACE + 5)
48 struct _GnmItemCursor
{
52 gboolean pos_initialized
;
55 /* Offset of dragging cell from top left of pos */
56 int col_delta
, row_delta
;
58 /* Tip for movement */
60 GnmCellPos last_tip_pos
;
62 GnmItemCursorStyle style
;
64 guint animation_timer
;
67 * For the autofill mode:
68 * Where the action started (base_x, base_y) and the
69 * width and height of the selection when the autofill
73 GnmRange autofill_src
;
74 int autofill_hsize
, autofill_vsize
;
75 gint64 last_x
, last_y
;
77 /* cursor outline in canvas coords, the bounding box (Item::[xy][01])
78 * is slightly larger ) */
80 gint64 x1
, x2
, y1
, y2
;
83 guint drag_button_state
;
86 gboolean auto_fill_handle_at_top
;
87 gboolean auto_fill_handle_at_left
;
93 GdkRGBA ant_color
, ant_background_color
;
94 GdkRGBA drag_color
, drag_background_color
;
95 GdkRGBA autofill_color
, autofill_background_color
;
97 typedef GocItemClass GnmItemCursorClass
;
99 static GocItemClass
*parent_class
;
103 ITEM_CURSOR_PROP_SHEET_CONTROL_GUI
,
104 ITEM_CURSOR_PROP_STYLE
,
105 ITEM_CURSOR_PROP_BUTTON
,
106 ITEM_CURSOR_PROP_COLOR
111 ic_reload_style (GnmItemCursor
*ic
)
113 GocItem
*item
= GOC_ITEM (ic
);
114 GtkStyleContext
*context
= goc_item_get_style_context (item
);
115 GtkStateFlags state
= GTK_STATE_FLAG_NORMAL
;
118 int fore_offset
, back_offset
;
121 G_STRUCT_OFFSET (GnmItemCursor
, normal_color
),
124 G_STRUCT_OFFSET (GnmItemCursor
, ant_color
),
125 G_STRUCT_OFFSET (GnmItemCursor
, ant_background_color
) },
127 G_STRUCT_OFFSET (GnmItemCursor
, drag_color
),
128 G_STRUCT_OFFSET (GnmItemCursor
, drag_background_color
) },
130 G_STRUCT_OFFSET (GnmItemCursor
, autofill_color
),
131 G_STRUCT_OFFSET (GnmItemCursor
, autofill_background_color
) }
135 for (ui
= 0; ui
< G_N_ELEMENTS (substyles
); ui
++) {
136 GdkRGBA
*fore
, *back
;
137 gtk_style_context_save (context
);
138 gtk_style_context_add_class (context
, substyles
[ui
].class);
139 gtk_style_context_get (context
, state
,
141 "background-color", &back
,
143 *(GdkRGBA
*)((char *)ic
+ substyles
[ui
].fore_offset
) = *fore
;
144 if (substyles
[ui
].back_offset
>= 0)
145 *(GdkRGBA
*)((char *)ic
+ substyles
[ui
].back_offset
) = *back
;
146 gdk_rgba_free (fore
);
147 gdk_rgba_free (back
);
148 gtk_style_context_restore (context
);
152 * Ensure we don't use transparency to avoid compositing issues
153 * when redrawing the ants in the timer callback.
155 ic
->ant_color
.alpha
= ic
->ant_background_color
.alpha
= 1.;
159 cb_item_cursor_animation (GnmItemCursor
*ic
)
161 GocItem
*item
= GOC_ITEM (ic
);
162 cairo_region_t
*region
;
163 cairo_rectangle_int_t rect
;
165 double scale
= item
->canvas
->pixels_per_unit
;
167 /* we need to use canvas coordinates in goc_canvas_c2w, hence the divisions by scale. */
168 if (goc_canvas_get_direction (item
->canvas
) == GOC_DIRECTION_RTL
) {
169 goc_canvas_c2w (item
->canvas
, ic
->outline
.x2
/ scale
, ic
->outline
.y2
/ scale
, &x0
, &y1
);
170 goc_canvas_c2w (item
->canvas
, ic
->outline
.x1
/ scale
, ic
->outline
.y1
/ scale
, &x1
, &y0
);
171 x0
--; /* because of the +.5, things are not symetric */
174 goc_canvas_c2w (item
->canvas
, ic
->outline
.x1
/ scale
, ic
->outline
.y1
/ scale
, &x0
, &y0
);
175 goc_canvas_c2w (item
->canvas
, ic
->outline
.x2
/ scale
, ic
->outline
.y2
/ scale
, &x1
, &y1
);
180 rect
.width
= x1
- x0
+ 3;
181 rect
.height
= y1
- y0
+ 3;
182 region
= cairo_region_create_rectangle (&rect
);
187 cairo_region_xor_rectangle (region
, &rect
);
188 goc_canvas_invalidate_region (item
->canvas
, item
, region
);
189 cairo_region_destroy (region
);
194 item_cursor_dispose (GObject
*obj
)
196 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (obj
);
199 gtk_widget_destroy (gtk_widget_get_toplevel (ic
->tip
));
203 G_OBJECT_CLASS (parent_class
)->dispose (obj
);
207 item_cursor_realize (GocItem
*item
)
209 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
211 parent_class
->realize (item
);
213 ic_reload_style (ic
);
215 if (ic
->style
== GNM_ITEM_CURSOR_ANTED
) {
216 g_return_if_fail (ic
->animation_timer
== 0);
217 ic
->animation_timer
= g_timeout_add (
218 75, (GSourceFunc
) cb_item_cursor_animation
,
224 item_cursor_unrealize (GocItem
*item
)
226 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
228 if (ic
->animation_timer
!= 0) {
229 g_source_remove (ic
->animation_timer
);
230 ic
->animation_timer
= 0;
233 parent_class
->unrealize (item
);
237 item_cursor_update_bounds (GocItem
*item
)
239 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
240 GnmPane
*pane
= GNM_PANE (item
->canvas
);
241 SheetControlGUI
const * const scg
= ic
->scg
;
243 double scale
= item
->canvas
->pixels_per_unit
;
245 int const left
= ic
->pos
.start
.col
;
246 int const right
= ic
->pos
.end
.col
;
247 int const top
= ic
->pos
.start
.row
;
248 int const bottom
= ic
->pos
.end
.row
;
249 ic
->outline
.x1
= pane
->first_offset
.x
+
250 scg_colrow_distance_get (scg
, TRUE
, pane
->first
.col
, left
);
251 ic
->outline
.x2
= ic
->outline
.x1
+
252 scg_colrow_distance_get (scg
, TRUE
, left
, right
+1);
253 ic
->outline
.y1
= pane
->first_offset
.y
+
254 scg_colrow_distance_get (scg
, FALSE
, pane
->first
.row
, top
);
255 ic
->outline
.y2
= ic
->outline
.y1
+
256 scg_colrow_distance_get (scg
, FALSE
,top
, bottom
+1);
258 /* NOTE : sometimes y1 > y2 || x1 > x2 when we create a cursor in an
259 * invisible region such as above a frozen pane */
261 /* jean: I don't know why we now need 2 instead of one in the next two lines */
262 item
->x0
= (ic
->outline
.x1
- 2) / scale
;
263 item
->y0
= (ic
->outline
.y1
- 2) / scale
;
265 /* for the autohandle */
266 tmp
= (ic
->style
== GNM_ITEM_CURSOR_SELECTION
) ? AUTO_HANDLE_WIDTH
: 0;
267 item
->x1
= (ic
->outline
.x2
+ 3 + tmp
) / scale
;
268 item
->y1
= (ic
->outline
.y2
+ 3 + tmp
) / scale
;
272 item_cursor_draw (GocItem
const *item
, cairo_t
*cr
)
274 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
275 int x0
, y0
, x1
, y1
; /* in widget coordinates */
277 int i
, draw_thick
, draw_stippled
, draw_handle
;
279 gboolean draw_center
, draw_external
, draw_internal
, draw_xor
;
280 double scale
= item
->canvas
->pixels_per_unit
;
281 GdkRGBA
*fore
= NULL
, *back
= NULL
;
285 g_printerr ("draw[%d] %lx,%lx %lx,%lx\n",
286 GNM_PANE (item
->canvas
)->index
,
292 if (!goc_item_is_visible (&ic
->canvas_item
) || !ic
->pos_initialized
)
295 /* we need to use canvas coordinates in goc_canvas_c2w, hence the divisions by scale. */
296 if (goc_canvas_get_direction (item
->canvas
) == GOC_DIRECTION_RTL
) {
297 goc_canvas_c2w (item
->canvas
, ic
->outline
.x2
/ scale
, ic
->outline
.y2
/ scale
, &x0
, &y1
);
298 goc_canvas_c2w (item
->canvas
, ic
->outline
.x1
/ scale
, ic
->outline
.y1
/ scale
, &x1
, &y0
);
299 x0
--; /* because of the +.5, things are not symetric */
302 goc_canvas_c2w (item
->canvas
, ic
->outline
.x1
/ scale
, ic
->outline
.y1
/ scale
, &x0
, &y0
);
303 goc_canvas_c2w (item
->canvas
, ic
->outline
.x2
/ scale
, ic
->outline
.y2
/ scale
, &x1
, &y1
);
306 /* only mostly in invisible areas (eg on creation of frozen panes) */
307 if (x0
> x1
|| y0
> y1
)
312 draw_external
= FALSE
;
313 draw_internal
= FALSE
;
321 case GNM_ITEM_CURSOR_AUTOFILL
:
325 fore
= &ic
->autofill_color
;
326 back
= &ic
->autofill_background_color
;
330 case GNM_ITEM_CURSOR_DRAG
:
334 fore
= &ic
->drag_color
;
335 back
= &ic
->drag_background_color
;
339 case GNM_ITEM_CURSOR_EXPR_RANGE
:
341 draw_thick
= (item
->canvas
->last_item
== item
) ? 3 : 2;
345 case GNM_ITEM_CURSOR_SELECTION
: {
346 GnmPane
const *pane
= GNM_PANE (item
->canvas
);
347 GnmPane
const *pane0
= scg_pane (pane
->simple
.scg
, 0);
349 draw_internal
= TRUE
;
350 draw_external
= TRUE
;
353 if (ic
->pos
.end
.row
<= pane
->last_full
.row
)
356 else if ((pane
->index
== 2 || pane
->index
== 3) &&
357 ic
->pos
.end
.row
>= pane0
->first
.row
&&
358 ic
->pos
.end
.row
<= pane0
->last_full
.row
)
360 /* TODO : do we want to add checking for pane above ? */
361 else if (ic
->pos
.start
.row
< pane
->first
.row
)
363 else if (ic
->pos
.start
.row
!= pane
->first
.row
)
370 case GNM_ITEM_CURSOR_ANTED
:
374 fore
= &ic
->ant_color
;
375 back
= &ic
->ant_background_color
;
376 phase0
= (~ic
->ant_state
& 3) * 0.25;
385 ic
->auto_fill_handle_at_top
= (draw_handle
>= 2);
387 if (x0
>= x1
|| y0
>= y1
)
390 cairo_set_dash (cr
, NULL
, 0, 0.);
391 cairo_set_line_width (cr
, 1.);
392 cairo_set_line_cap (cr
, CAIRO_LINE_CAP_BUTT
);
393 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_MITER
);
394 gdk_cairo_set_source_rgba (cr
, &ic
->normal_color
);
397 cairo_set_operator (cr
, CAIRO_OPERATOR_HARD_LIGHT
);
399 switch (draw_handle
) {
400 /* Auto handle at bottom */
402 premove
= AUTO_HANDLE_SPACE
;
407 points
[0].x
= x1
+ 1.5;
408 points
[0].y
= y1
+ 1 - premove
;
409 points
[1].x
= points
[0].x
;
410 points
[1].y
= y0
- .5;
411 points
[2].x
= x0
- .5;
412 points
[2].y
= y0
- .5;
413 points
[3].x
= x0
- .5;
414 points
[3].y
= y1
+ 1.5;
415 points
[4].x
= x1
+ 1 - premove
;
416 points
[4].y
= points
[3].y
;
419 /* Auto handle at top */
421 premove
= AUTO_HANDLE_SPACE
;
424 /* Auto handle at top of sheet */
426 points
[0].x
= x1
+ 1.5;
427 points
[0].y
= y0
- .5 + AUTO_HANDLE_SPACE
;
428 points
[1].x
= points
[0].x
;
429 points
[1].y
= y1
+ 1.5;
430 points
[2].x
= x0
- .5;
431 points
[2].y
= points
[1].y
;
432 points
[3].x
= points
[2].x
;
433 points
[3].y
= y0
- .5;
434 points
[4].x
= x1
+ 1 - premove
;
435 points
[4].y
= points
[3].y
;
439 g_assert_not_reached ();
441 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
442 for (i
= 1; i
< 5; i
++)
443 cairo_line_to (cr
, points
[i
].x
, points
[i
].y
);
447 if (draw_external
&& draw_internal
) {
448 if (draw_handle
< 2) {
467 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
468 for (i
= 1; i
< 5; i
++)
469 cairo_line_to (cr
, points
[i
].x
, points
[i
].y
);
473 if (draw_handle
== 1 || draw_handle
== 2) {
474 int const y_off
= (draw_handle
== 1) ? y1
- y0
: 0;
475 cairo_rectangle (cr
, x1
- 2, y0
+ y_off
- 2, 2, 2);
476 cairo_rectangle (cr
, x1
+ 1, y0
+ y_off
- 2, 2, 2);
477 cairo_rectangle (cr
, x1
- 2, y0
+ y_off
+ 1, 2, 2);
478 cairo_rectangle (cr
, x1
+ 1, y0
+ y_off
+ 1, 2, 2);
480 } else if (draw_handle
== 3) {
481 cairo_rectangle (cr
, x1
- 2, y0
+ 1, 2, 4);
482 cairo_rectangle (cr
, x1
+ 1, y0
+ 1, 2, 4);
488 double phase1
= fmod (phase0
+ 0.5, 1);
490 /* Stay in the boundary */
491 x0
+= (draw_thick
/ 2.0);
492 y0
+= (draw_thick
/ 2.0);
494 cairo_set_line_width (cr
, draw_thick
);
495 cairo_rectangle (cr
, x0
, y0
, abs (x1
- x0
), abs (y1
- y0
));
496 dashes
[0] = dashes
[1] = draw_stippled
;
498 cairo_set_dash (cr
, dashes
, 2, phase0
* 2 * draw_stippled
);
499 gdk_cairo_set_source_rgba (cr
, back
);
500 cairo_stroke_preserve (cr
);
502 cairo_set_dash (cr
, dashes
, 2, phase1
* 2 * draw_stippled
);
503 gdk_cairo_set_source_rgba (cr
, fore
);
510 gnm_item_cursor_bound_set (GnmItemCursor
*ic
, GnmRange
const *new_bound
)
513 g_return_val_if_fail (GNM_IS_ITEM_CURSOR (ic
), FALSE
);
514 g_return_val_if_fail (range_is_sane (new_bound
), FALSE
);
516 if (ic
->pos_initialized
&& range_equal (&ic
->pos
, new_bound
))
519 item
= GOC_ITEM (ic
);
520 goc_item_invalidate (item
);
521 ic
->pos
= *new_bound
;
522 ic
->pos_initialized
= TRUE
;
524 goc_item_bounds_changed (item
);
525 goc_item_invalidate (item
);
531 * gnm_item_cursor_reposition:
533 * Re-compute the pixel position of the cursor.
535 * When a sheet is zoomed. The pixel coords shift slightly. The item cursor
536 * must regenerate to stay in sync.
539 gnm_item_cursor_reposition (GnmItemCursor
*ic
)
541 g_return_if_fail (GOC_IS_ITEM (ic
));
542 goc_item_bounds_changed (GOC_ITEM (ic
));
546 item_cursor_distance (GocItem
*item
, double x
, double y
,
547 GocItem
**actual_item
)
549 GnmItemCursor
const *ic
= GNM_ITEM_CURSOR (item
);
551 /* Cursor should not always receive events
554 * 3) while a guru is up
556 if (!goc_item_is_visible (item
) ||
557 ic
->style
== GNM_ITEM_CURSOR_ANTED
||
558 wbc_gtk_get_guru (scg_wbcg (ic
->scg
)) != NULL
)
572 if ((x
< (item
->x0
+ 4)) || (x
> (item
->x1
- 8)) ||
573 (y
< (item
->y0
+ 4)) || (y
> (item
->y1
- 8))) {
581 item_cursor_setup_auto_fill (GnmItemCursor
*ic
, GnmItemCursor
const *parent
, int x
, int y
)
583 Sheet
const *sheet
= scg_sheet (parent
->scg
);
589 ic
->autofill_src
= parent
->pos
;
591 /* If there are arrays or merges in the region ensure that we
592 * need to ensure that an integer multiple of the original size
593 * is filled. We could be fancy about this an allow filling as long
594 * as the merges would not be split, bu that is more work than it is
595 * worth right now (FIXME this is a nice project).
597 * We do not have to be too careful, the sheet guarantees that the
598 * cursor does not split merges, all we need is existence.
600 merges
= gnm_sheet_merge_get_overlap (sheet
, &ic
->autofill_src
);
601 if (merges
!= NULL
) {
602 g_slist_free (merges
);
603 ic
->autofill_hsize
= range_width (&ic
->autofill_src
);
604 ic
->autofill_vsize
= range_height (&ic
->autofill_src
);
606 ic
->autofill_hsize
= ic
->autofill_vsize
= 1;
609 static inline gboolean
610 item_cursor_in_drag_handle (GnmItemCursor
*ic
, gint64 x
, gint64 y
)
612 double scale
= ic
->canvas_item
.canvas
->pixels_per_unit
;
613 gint64
const y_test
= ic
->auto_fill_handle_at_top
614 ? ic
->canvas_item
.y0
* scale
+ AUTO_HANDLE_WIDTH
615 : ic
->canvas_item
.y1
* scale
- AUTO_HANDLE_WIDTH
;
617 if ((y_test
-AUTO_HANDLE_SPACE
) <= y
&&
618 y
<= (y_test
+AUTO_HANDLE_SPACE
)) {
619 gint64
const x_test
= ic
->auto_fill_handle_at_left
620 ? (ic
->canvas_item
.canvas
->direction
== GOC_DIRECTION_RTL
?
621 ic
->canvas_item
.x1
* scale
- AUTO_HANDLE_WIDTH
:
622 ic
->canvas_item
.x0
* scale
+ AUTO_HANDLE_WIDTH
)
623 : (ic
->canvas_item
.canvas
->direction
== GOC_DIRECTION_RTL
?
624 ic
->canvas_item
.x0
* scale
+ AUTO_HANDLE_WIDTH
:
625 ic
->canvas_item
.x1
* scale
- AUTO_HANDLE_WIDTH
);
626 return (x_test
-AUTO_HANDLE_SPACE
) <= x
&&
627 x
<= (x_test
+AUTO_HANDLE_SPACE
);
633 item_cursor_set_cursor (GocCanvas
*canvas
, GnmItemCursor
*ic
, gint64 x
, gint64 y
)
635 GdkCursorType cursor
;
637 if (item_cursor_in_drag_handle (ic
, x
, y
))
638 cursor
= GDK_CROSSHAIR
;
642 gnm_widget_set_cursor_type (GTK_WIDGET (canvas
), cursor
);
646 item_cursor_selection_motion (GocItem
*item
, double x_
, double y_
)
648 GocCanvas
*canvas
= item
->canvas
;
649 GnmPane
*pane
= GNM_PANE (canvas
);
650 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
652 gint64 x
= x_
* canvas
->pixels_per_unit
, y
= y_
* canvas
->pixels_per_unit
;
653 GnmItemCursor
*special_cursor
;
655 if (ic
->drag_button
< 0) {
656 item_cursor_set_cursor (canvas
, ic
, x
, y
);
661 * determine which part of the cursor was clicked:
662 * the border or the handlebox
664 if (item_cursor_in_drag_handle (ic
, x_
, y_
))
665 style
= GNM_ITEM_CURSOR_AUTOFILL
;
667 style
= GNM_ITEM_CURSOR_DRAG
;
669 button
= ic
->drag_button
;
670 ic
->drag_button
= -1;
671 gnm_simple_canvas_ungrab (item
);
673 scg_special_cursor_start (ic
->scg
, style
, button
);
674 special_cursor
= pane
->cursor
.special
;
675 special_cursor
->drag_button_state
= ic
->drag_button_state
;
676 if (style
== GNM_ITEM_CURSOR_AUTOFILL
)
677 item_cursor_setup_auto_fill (
678 special_cursor
, ic
, x
, y
);
685 * Capture the offset of the current cell relative to
686 * the upper left corner. Be careful handling the position
687 * of the cursor. it is possible to select the exterior or
688 * interior of the cursor edge which behaves as if the cursor
689 * selection was offset by one.
692 int d_col
= gnm_pane_find_col (pane
, x
, NULL
) -
694 int d_row
= gnm_pane_find_row (pane
, y
, NULL
) -
698 int tmp
= ic
->pos
.end
.col
- ic
->pos
.start
.col
;
703 special_cursor
->col_delta
= d_col
;
706 int tmp
= ic
->pos
.end
.row
- ic
->pos
.start
.row
;
711 special_cursor
->row_delta
= d_row
;
714 scg_special_cursor_bound_set (ic
->scg
, &ic
->pos
);
716 gnm_simple_canvas_grab (GOC_ITEM (special_cursor
));
717 gnm_pane_slide_init (pane
);
719 goc_item_bounds_changed (GOC_ITEM (ic
));
722 * We flush after the grab to ensure that the new item-cursor
723 * gets created. If it is not ready in time double click
724 * events will be disrupted and it will appear as if we are
725 * doing an button_press with a missing release.
737 ACTION_SHIFT_DOWN_AND_COPY
,
738 ACTION_SHIFT_RIGHT_AND_COPY
,
739 ACTION_SHIFT_DOWN_AND_MOVE
,
740 ACTION_SHIFT_RIGHT_AND_MOVE
744 item_cursor_do_action (GnmItemCursor
*ic
, ActionType action
)
748 WorkbookControl
*wbc
;
751 g_return_if_fail (ic
!= NULL
);
753 if (action
== ACTION_NONE
) {
754 scg_special_cursor_stop (ic
->scg
);
758 sheet
= scg_sheet (ic
->scg
);
759 sv
= scg_view (ic
->scg
);
760 wbc
= scg_wbc (ic
->scg
);
763 case ACTION_COPY_CELLS
:
764 if (!sv_selection_copy (sv
, wbc
))
767 paste_target_init (&pt
, sheet
, &ic
->pos
,
771 case ACTION_MOVE_CELLS
:
772 if (!sv_selection_cut (sv
, wbc
))
775 paste_target_init (&pt
, sheet
, &ic
->pos
,
779 case ACTION_COPY_FORMATS
:
780 if (!sv_selection_copy (sv
, wbc
))
783 paste_target_init (&pt
, sheet
, &ic
->pos
,
787 case ACTION_COPY_VALUES
:
788 if (!sv_selection_copy (sv
, wbc
))
791 paste_target_init (&pt
, sheet
, &ic
->pos
,
795 case ACTION_SHIFT_DOWN_AND_COPY
:
796 case ACTION_SHIFT_RIGHT_AND_COPY
:
797 case ACTION_SHIFT_DOWN_AND_MOVE
:
798 case ACTION_SHIFT_RIGHT_AND_MOVE
:
799 g_warning ("Operation not yet implemented.");
803 g_warning ("Invalid Operation %d.", action
);
806 scg_special_cursor_stop (ic
->scg
);
810 context_menu_hander (GnmPopupMenuElement
const *element
,
813 g_return_if_fail (element
!= NULL
);
814 item_cursor_do_action (ic
, element
->index
);
818 item_cursor_popup_menu (GnmItemCursor
*ic
, GdkEvent
*event
)
820 static GnmPopupMenuElement
const popup_elements
[] = {
822 0, 0, ACTION_MOVE_CELLS
},
824 { N_("_Copy"), "edit-copy",
825 0, 0, ACTION_COPY_CELLS
},
827 { N_("Copy _Formats"), NULL
,
828 0, 0, ACTION_COPY_FORMATS
},
829 { N_("Copy _Values"), NULL
,
830 0, 0, ACTION_COPY_VALUES
},
832 { "", NULL
, 0, 0, 0 },
834 { N_("Shift _Down and Copy"), NULL
,
835 0, 0, ACTION_SHIFT_DOWN_AND_COPY
},
836 { N_("Shift _Right and Copy"), NULL
,
837 0, 0, ACTION_SHIFT_RIGHT_AND_COPY
},
838 { N_("Shift Dow_n and Move"), NULL
,
839 0, 0, ACTION_SHIFT_DOWN_AND_MOVE
},
840 { N_("Shift Righ_t and Move"), NULL
,
841 0, 0, ACTION_SHIFT_RIGHT_AND_MOVE
},
843 { "", NULL
, 0, 0, 0 },
845 { N_("C_ancel"), NULL
,
848 { NULL
, NULL
, 0, 0, 0 }
851 gnm_create_popup_menu (popup_elements
,
852 &context_menu_hander
, ic
,
857 item_cursor_do_drop (GnmItemCursor
*ic
, GdkEvent
*event
)
859 /* Only do the operation if something moved */
860 SheetView
const *sv
= scg_view (ic
->scg
);
861 GnmRange
const *target
= selection_first_range (sv
, NULL
, NULL
);
863 wbcg_set_status_text (scg_wbcg (ic
->scg
), "");
864 if (range_equal (target
, &ic
->pos
)) {
865 scg_special_cursor_stop (ic
->scg
);
869 if (event
->button
.button
== 3)
870 item_cursor_popup_menu (ic
, event
);
872 item_cursor_do_action (ic
, (event
->button
.state
& GDK_CONTROL_MASK
)
874 : ACTION_MOVE_CELLS
);
878 gnm_item_cursor_set_visibility (GnmItemCursor
*ic
, gboolean visible
)
880 goc_item_set_visible (GOC_ITEM (ic
), visible
);
884 item_cursor_tip_setlabel (GnmItemCursor
*ic
, char const *text
)
886 if (ic
->tip
== NULL
) {
887 GtkWidget
*cw
= GTK_WIDGET (GOC_ITEM (ic
)->canvas
);
889 ic
->tip
= gnm_create_tooltip (cw
);
891 gnm_canvas_get_position (GOC_CANVAS (cw
), &x
, &y
, ic
->last_x
, ic
->last_y
);
892 gnm_position_tooltip (ic
->tip
, x
, y
, TRUE
);
893 gtk_widget_show_all (gtk_widget_get_toplevel (ic
->tip
));
896 g_return_if_fail (ic
->tip
!= NULL
);
897 gtk_label_set_text (GTK_LABEL (ic
->tip
), text
);
901 cb_move_cursor (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
903 GnmItemCursor
*ic
= info
->user_data
;
904 int const w
= (ic
->pos
.end
.col
- ic
->pos
.start
.col
);
905 int const h
= (ic
->pos
.end
.row
- ic
->pos
.start
.row
);
907 Sheet
*sheet
= scg_sheet (pane
->simple
.scg
);
909 r
.start
.col
= info
->col
- ic
->col_delta
;
912 else if (r
.start
.col
>= (gnm_sheet_get_max_cols (sheet
) - w
))
913 r
.start
.col
= gnm_sheet_get_max_cols (sheet
) - w
- 1;
915 r
.start
.row
= info
->row
- ic
->row_delta
;
918 else if (r
.start
.row
>= (gnm_sheet_get_max_rows (sheet
) - h
))
919 r
.start
.row
= gnm_sheet_get_max_rows (sheet
) - h
- 1;
921 item_cursor_tip_setlabel (ic
, range_as_string (&ic
->pos
));
923 r
.end
.col
= r
.start
.col
+ w
;
924 r
.end
.row
= r
.start
.row
+ h
;
925 scg_special_cursor_bound_set (ic
->scg
, &r
);
926 scg_make_cell_visible (ic
->scg
, info
->col
, info
->row
, FALSE
, TRUE
);
931 item_cursor_handle_motion (GnmItemCursor
*ic
, double x
, double y
,
932 GnmPaneSlideHandler slide_handler
)
934 GocCanvas
*canvas
= GOC_ITEM (ic
)->canvas
;
936 gnm_pane_handle_motion (GNM_PANE (canvas
),
938 GNM_PANE_SLIDE_X
| GNM_PANE_SLIDE_Y
| GNM_PANE_SLIDE_AT_COLROW_BOUND
,
940 goc_item_bounds_changed (GOC_ITEM (ic
));
944 item_cursor_drag_motion (GnmItemCursor
*ic
, double x
, double y
)
946 item_cursor_handle_motion (ic
, x
, y
, &cb_move_cursor
);
951 limit_string_height_and_width (GString
*s
, size_t wmax
, size_t hmax
)
955 for (l
= 0; l
< hmax
; l
++) {
958 while (s
->str
[p
] != 0 && s
->str
[p
] != '\n') {
962 p
+= g_utf8_skip
[(unsigned char)(s
->str
[p
])];
966 g_string_erase (s
, cut
, p
- cut
);
973 g_string_truncate (s
, p
);
978 cb_autofill_scroll (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
980 GnmItemCursor
*ic
= info
->user_data
;
981 GnmRange r
= ic
->autofill_src
;
982 int col
= info
->col
, row
= info
->row
;
985 /* compass offsets are distances (in cells) from the edges of the
986 * selected area to the mouse cursor */
987 int north_offset
= r
.start
.row
- row
;
988 int south_offset
= row
- r
.end
.row
;
989 int west_offset
= r
.start
.col
- col
;
990 int east_offset
= col
- r
.end
.col
;
992 /* Autofill by row or by col, NOT both. */
993 if ( MAX (north_offset
, south_offset
) > MAX (west_offset
, east_offset
) ) {
994 if (row
< r
.start
.row
)
995 r
.start
.row
-= ic
->autofill_vsize
* (int)(north_offset
/ ic
->autofill_vsize
);
997 r
.end
.row
+= ic
->autofill_vsize
* (int)(south_offset
/ ic
->autofill_vsize
);
998 if (col
< r
.start
.col
)
1000 else if (col
> r
.end
.col
)
1003 if (col
< r
.start
.col
)
1004 r
.start
.col
-= ic
->autofill_hsize
* (int)(west_offset
/ ic
->autofill_hsize
);
1006 r
.end
.col
+= ic
->autofill_hsize
* (int)(east_offset
/ ic
->autofill_hsize
);
1007 if (row
< r
.start
.row
)
1009 else if (row
> r
.end
.row
)
1013 /* Check if we have moved to a new cell. */
1014 if (col
== ic
->last_tip_pos
.col
&& row
== ic
->last_tip_pos
.row
)
1016 ic
->last_tip_pos
.col
= col
;
1017 ic
->last_tip_pos
.row
= row
;
1019 scg_special_cursor_bound_set (ic
->scg
, &r
);
1020 scg_make_cell_visible (ic
->scg
, col
, row
, FALSE
, TRUE
);
1022 w
= range_width (&ic
->autofill_src
);
1023 h
= range_height (&ic
->autofill_src
);
1024 if (ic
->pos
.start
.col
+ w
- 1 == ic
->pos
.end
.col
&&
1025 ic
->pos
.start
.row
+ h
- 1 == ic
->pos
.end
.row
)
1026 item_cursor_tip_setlabel (ic
, _("Autofill"));
1028 gboolean inverse_autofill
=
1029 (ic
->pos
.start
.col
< ic
->autofill_src
.start
.col
||
1030 ic
->pos
.start
.row
< ic
->autofill_src
.start
.row
);
1031 gboolean default_increment
=
1032 ic
->drag_button_state
& GDK_CONTROL_MASK
;
1033 Sheet
*sheet
= scg_sheet (ic
->scg
);
1036 if (inverse_autofill
)
1037 hint
= gnm_autofill_hint
1038 (sheet
, default_increment
,
1039 ic
->pos
.end
.col
, ic
->pos
.end
.row
,
1041 ic
->pos
.start
.col
, ic
->pos
.start
.row
);
1043 hint
= gnm_autofill_hint
1044 (sheet
, default_increment
,
1045 ic
->pos
.start
.col
, ic
->pos
.start
.row
,
1047 ic
->pos
.end
.col
, ic
->pos
.end
.row
);
1050 limit_string_height_and_width (hint
, 200, 200);
1051 item_cursor_tip_setlabel (ic
, hint
->str
);
1052 g_string_free (hint
, TRUE
);
1054 item_cursor_tip_setlabel (ic
, "");
1061 item_cursor_button_pressed (GocItem
*item
, int button
, double x_
, double y_
)
1063 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1064 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1065 GdkEvent
*event
= goc_canvas_get_cur_event (item
->canvas
);
1066 GdkEventButton
*bevent
= &event
->button
;
1067 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
)
1070 /* While editing nothing should be draggable */
1071 if (wbcg_is_editing (scg_wbcg (ic
->scg
)))
1074 switch (ic
->style
) {
1076 case GNM_ITEM_CURSOR_ANTED
:
1077 g_warning ("Animated cursors should not receive events, "
1078 "the point method should preclude that");
1081 case GNM_ITEM_CURSOR_SELECTION
:
1082 /* NOTE : this cannot be called while we are editing. because
1083 * the point routine excludes events. so we do not need to
1084 * call wbcg_edit_finish.
1087 /* scroll wheel events dont have corresponding release events */
1091 /* If another button is already down ignore this one */
1092 if (ic
->drag_button
>= 0)
1096 /* prepare to create fill or drag cursors, but dont until we
1097 * move. If we did create them here there would be problems
1098 * with race conditions when the new cursors pop into existence
1099 * during a double-click
1102 if (item_cursor_in_drag_handle (ic
, x
, y
))
1103 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (scg_wbcg (ic
->scg
)),
1104 _("Drag to autofill"));
1106 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (scg_wbcg (ic
->scg
)),
1109 ic
->drag_button
= button
;
1110 ic
->drag_button_state
= bevent
->state
;
1111 gnm_simple_canvas_grab (item
);
1113 scg_context_menu (ic
->scg
, event
, FALSE
, FALSE
);
1116 case GNM_ITEM_CURSOR_DRAG
:
1117 /* This kind of cursor is created and grabbed. Then destroyed
1118 * when the button is released. If we are seeing a press it
1119 * means that someone has pressed another button WHILE THE
1120 * FIRST IS STILL DOWN. Ignore this event.
1131 item_cursor_button2_pressed (GocItem
*item
, int button
, double x_
, double y_
)
1133 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1134 GdkEvent
*event
= goc_canvas_get_cur_event (item
->canvas
);
1136 switch (ic
->style
) {
1138 case GNM_ITEM_CURSOR_SELECTION
: {
1139 Sheet
*sheet
= scg_sheet (ic
->scg
);
1140 int final_col
= ic
->pos
.end
.col
;
1141 int final_row
= ic
->pos
.end
.row
;
1143 if (ic
->drag_button
!= button
)
1146 ic
->drag_button
= -1;
1147 gnm_simple_canvas_ungrab (item
);
1149 if (sheet_is_region_empty (sheet
, &ic
->pos
))
1152 /* If the cell(s) immediately below the ones in the
1153 * auto-fill template are not blank then over-write
1156 * Otherwise, only go as far as the next non-blank
1159 * The code below uses find_boundary twice. a. to
1160 * find the boundary of the column/row that acts as a
1161 * template to define the region to file and b. to
1162 * find the boundary of the region being filled.
1165 if (event
->button
.state
& GDK_MOD1_MASK
) {
1166 int template_col
= ic
->pos
.end
.col
+ 1;
1167 int template_row
= ic
->pos
.start
.row
- 1;
1168 int boundary_col_for_target
;
1171 if (template_row
< 0 || template_col
>= gnm_sheet_get_max_cols (sheet
) ||
1172 sheet_is_cell_empty (sheet
, template_col
,
1175 template_row
= ic
->pos
.end
.row
+ 1;
1176 if (template_row
>= gnm_sheet_get_max_rows (sheet
) ||
1177 template_col
>= gnm_sheet_get_max_cols (sheet
) ||
1178 sheet_is_cell_empty (sheet
, template_col
,
1183 if (template_col
>= gnm_sheet_get_max_cols (sheet
) ||
1184 sheet_is_cell_empty (sheet
, template_col
,
1187 final_col
= sheet_find_boundary_horizontal (sheet
,
1188 ic
->pos
.end
.col
, template_row
,
1189 template_row
, 1, TRUE
);
1190 if (final_col
<= ic
->pos
.end
.col
)
1194 Find the boundary of the target region.
1195 We don't want to go beyond this boundary.
1197 for (target_row
= ic
->pos
.start
.row
; target_row
<= ic
->pos
.end
.row
; target_row
++) {
1198 /* find_boundary is designed for Ctrl-arrow movement. (Ab)using it for
1199 * finding autofill regions works fairly well. One little gotcha is
1200 * that if the current col is the last row of a block of data Ctrl-arrow
1201 * will take you to then next block. The workaround for this is to
1202 * start the search at the last col of the selection, rather than
1203 * the first col of the region being filled.
1205 boundary_col_for_target
= sheet_find_boundary_horizontal
1207 ic
->pos
.end
.col
, target_row
,
1208 target_row
, 1, TRUE
);
1210 if (sheet_is_cell_empty (sheet
, boundary_col_for_target
-1, target_row
) &&
1211 ! sheet_is_cell_empty (sheet
, boundary_col_for_target
, target_row
)) {
1212 /* target region was empty, we are now one col
1213 beyond where it is safe to autofill. */
1214 boundary_col_for_target
--;
1216 if (boundary_col_for_target
< final_col
) {
1217 final_col
= boundary_col_for_target
;
1221 int template_row
= ic
->pos
.end
.row
+ 1;
1222 int template_col
= ic
->pos
.start
.col
- 1;
1223 int boundary_row_for_target
;
1226 if (template_col
< 0 || template_row
>= gnm_sheet_get_max_rows (sheet
) ||
1227 sheet_is_cell_empty (sheet
, template_col
,
1230 template_col
= ic
->pos
.end
.col
+ 1;
1231 if (template_col
>= gnm_sheet_get_max_cols (sheet
) ||
1232 template_row
>= gnm_sheet_get_max_rows (sheet
) ||
1233 sheet_is_cell_empty (sheet
, template_col
,
1238 if (template_row
>= gnm_sheet_get_max_rows (sheet
) ||
1239 sheet_is_cell_empty (sheet
, template_col
,
1242 final_row
= sheet_find_boundary_vertical (sheet
,
1243 template_col
, ic
->pos
.end
.row
,
1244 template_col
, 1, TRUE
);
1245 if (final_row
<= ic
->pos
.end
.row
)
1249 Find the boundary of the target region.
1250 We don't want to go beyond this boundary.
1252 for (target_col
= ic
->pos
.start
.col
; target_col
<= ic
->pos
.end
.col
; target_col
++) {
1253 /* find_boundary is designed for Ctrl-arrow movement. (Ab)using it for
1254 * finding autofill regions works fairly well. One little gotcha is
1255 * that if the current row is the last row of a block of data Ctrl-arrow
1256 * will take you to then next block. The workaround for this is to
1257 * start the search at the last row of the selection, rather than
1258 * the first row of the region being filled.
1260 boundary_row_for_target
= sheet_find_boundary_vertical
1262 target_col
, ic
->pos
.end
.row
,
1263 target_col
, 1, TRUE
);
1264 if (sheet_is_cell_empty (sheet
, target_col
, boundary_row_for_target
-1) &&
1265 ! sheet_is_cell_empty (sheet
, target_col
, boundary_row_for_target
)) {
1266 /* target region was empty, we are now one row
1267 beyond where it is safe to autofill. */
1268 boundary_row_for_target
--;
1271 if (boundary_row_for_target
< final_row
) {
1272 final_row
= boundary_row_for_target
;
1277 /* fill the row/column */
1278 cmd_autofill (scg_wbc (ic
->scg
), sheet
, FALSE
,
1279 ic
->pos
.start
.col
, ic
->pos
.start
.row
,
1280 ic
->pos
.end
.col
- ic
->pos
.start
.col
+ 1,
1281 ic
->pos
.end
.row
- ic
->pos
.start
.row
+ 1,
1282 final_col
, final_row
,
1288 case GNM_ITEM_CURSOR_DRAG
:
1298 item_cursor_motion (GocItem
*item
, double x_
, double y_
)
1300 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1301 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1304 if (ic
->drag_button
< 0) {
1305 item_cursor_set_cursor (item
->canvas
, ic
, x
, y
);
1308 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
)
1311 /* While editing nothing should be draggable */
1312 if (wbcg_is_editing (scg_wbcg (ic
->scg
)))
1314 switch (ic
->style
) {
1316 case GNM_ITEM_CURSOR_ANTED
:
1317 g_warning ("Animated cursors should not receive events, "
1318 "the point method should preclude that");
1321 case GNM_ITEM_CURSOR_SELECTION
:
1322 return item_cursor_selection_motion (item
, x
, y
);
1324 case GNM_ITEM_CURSOR_DRAG
:
1325 return item_cursor_drag_motion (ic
, x
, y
);
1327 case GNM_ITEM_CURSOR_AUTOFILL
:
1328 item_cursor_handle_motion (GNM_ITEM_CURSOR (item
), x
, y
, &cb_autofill_scroll
);
1337 item_cursor_button_released (GocItem
*item
, int button
, G_GNUC_UNUSED
double x
, G_GNUC_UNUSED
double y
)
1339 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1340 GdkEvent
*event
= goc_canvas_get_cur_event (item
->canvas
);
1341 WBCGtk
*wbcg
= scg_wbcg (ic
->scg
);
1343 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
)
1346 /* While editing nothing should be draggable */
1347 if (wbcg_is_editing (wbcg
))
1350 switch (ic
->style
) {
1351 case GNM_ITEM_CURSOR_ANTED
:
1352 g_warning ("Animated cursors should not receive events, "
1353 "the point method should preclude that");
1356 case GNM_ITEM_CURSOR_SELECTION
:
1357 if (ic
->drag_button
!= button
)
1360 /* Double clicks may have already released the drag prep */
1361 if (ic
->drag_button
>= 0) {
1362 gnm_simple_canvas_ungrab (item
);
1363 ic
->drag_button
= -1;
1365 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (wbcg
),
1369 case GNM_ITEM_CURSOR_DRAG
:
1370 if (ic
->drag_button
!= button
)
1373 gnm_pane_slide_stop (GNM_PANE (item
->canvas
));
1374 gnm_simple_canvas_ungrab (item
);
1375 item_cursor_do_drop (ic
, event
);
1377 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (wbcg
),
1381 case GNM_ITEM_CURSOR_AUTOFILL
: {
1382 gboolean inverse_autofill
=
1383 (ic
->pos
.start
.col
< ic
->autofill_src
.start
.col
||
1384 ic
->pos
.start
.row
< ic
->autofill_src
.start
.row
);
1385 gboolean default_increment
=
1386 ic
->drag_button_state
& GDK_CONTROL_MASK
;
1387 SheetControlGUI
*scg
= ic
->scg
;
1389 gnm_pane_slide_stop (GNM_PANE (item
->canvas
));
1390 gnm_simple_canvas_ungrab (item
);
1392 cmd_autofill (scg_wbc (scg
), scg_sheet (scg
), default_increment
,
1393 ic
->pos
.start
.col
, ic
->pos
.start
.row
,
1394 range_width (&ic
->autofill_src
),
1395 range_height (&ic
->autofill_src
),
1396 ic
->pos
.end
.col
, ic
->pos
.end
.row
,
1399 scg_special_cursor_stop (scg
);
1401 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (wbcg
),
1411 item_cursor_enter_notify (GocItem
*item
, double x_
, double y_
)
1413 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1414 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1415 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
) {
1416 gnm_widget_set_cursor_type (GTK_WIDGET (item
->canvas
), GDK_ARROW
);
1417 goc_item_invalidate (item
);
1419 else if (ic
->style
== GNM_ITEM_CURSOR_SELECTION
)
1420 item_cursor_set_cursor (item
->canvas
, ic
, x
, y
);
1425 item_cursor_leave_notify (GocItem
*item
, G_GNUC_UNUSED
double x
, G_GNUC_UNUSED
double y
)
1427 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1428 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
)
1429 goc_item_invalidate (item
);
1434 item_cursor_set_property (GObject
*obj
, guint param_id
,
1435 GValue
const *value
, GParamSpec
*pspec
)
1437 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (obj
);
1440 case ITEM_CURSOR_PROP_SHEET_CONTROL_GUI
:
1441 ic
->scg
= g_value_get_object (value
);
1443 case ITEM_CURSOR_PROP_STYLE
:
1444 ic
->style
= g_value_get_int (value
);
1446 case ITEM_CURSOR_PROP_BUTTON
:
1447 ic
->drag_button
= g_value_get_int (value
);
1449 case ITEM_CURSOR_PROP_COLOR
:
1450 go_color_to_gdk_rgba (g_value_get_uint (value
), &ic
->color
);
1456 * GnmItemCursor class initialization
1459 gnm_item_cursor_class_init (GObjectClass
*gobject_klass
)
1462 GocItemClass
*item_klass
= (GocItemClass
*) gobject_klass
;
1464 parent_class
= g_type_class_peek_parent (gobject_klass
);
1466 gobject_klass
->set_property
= item_cursor_set_property
;
1467 gobject_klass
->dispose
= item_cursor_dispose
;
1468 g_object_class_install_property (gobject_klass
, ITEM_CURSOR_PROP_SHEET_CONTROL_GUI
,
1469 g_param_spec_object ("SheetControlGUI",
1470 P_("SheetControlGUI"),
1471 P_("The sheet control gui controlling the item"),
1473 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1474 g_object_class_install_property (gobject_klass
, ITEM_CURSOR_PROP_STYLE
,
1475 g_param_spec_int ("style",
1477 P_("What type of cursor"),
1479 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1480 g_object_class_install_property (gobject_klass
, ITEM_CURSOR_PROP_BUTTON
,
1481 g_param_spec_int ("button",
1483 P_("What button initiated the drag"),
1485 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1486 g_object_class_install_property (gobject_klass
, ITEM_CURSOR_PROP_COLOR
,
1487 g_param_spec_uint ("color",
1489 P_("Name of the cursor's color"),
1492 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1494 item_klass
->realize
= item_cursor_realize
;
1495 item_klass
->unrealize
= item_cursor_unrealize
;
1496 item_klass
->draw
= item_cursor_draw
;
1497 item_klass
->update_bounds
= item_cursor_update_bounds
;
1498 item_klass
->distance
= item_cursor_distance
;
1499 item_klass
->button_pressed
= item_cursor_button_pressed
;
1500 item_klass
->button2_pressed
= item_cursor_button2_pressed
;
1501 item_klass
->button_released
= item_cursor_button_released
;
1502 item_klass
->motion
= item_cursor_motion
;
1503 item_klass
->enter_notify
= item_cursor_enter_notify
;
1504 item_klass
->leave_notify
= item_cursor_leave_notify
;
1508 gnm_item_cursor_init (GnmItemCursor
*ic
)
1510 ic
->pos_initialized
= FALSE
;
1511 ic
->pos
.start
.col
= 0;
1512 ic
->pos
.end
.col
= 0;
1513 ic
->pos
.start
.row
= 0;
1514 ic
->pos
.end
.row
= 0;
1520 ic
->last_tip_pos
.col
= -1;
1521 ic
->last_tip_pos
.row
= -1;
1526 ic
->style
= GNM_ITEM_CURSOR_SELECTION
;
1528 ic
->animation_timer
= 0;
1530 ic
->auto_fill_handle_at_top
= FALSE
;
1531 ic
->auto_fill_handle_at_left
= FALSE
;
1532 ic
->drag_button
= -1;
1535 GSF_CLASS (GnmItemCursor
, gnm_item_cursor
,
1536 gnm_item_cursor_class_init
, gnm_item_cursor_init
,