2 * The Cursor Canvas Item: Implements a rectangular cursor
5 * Miguel de Icaza (miguel@kernel.org)
6 * Jody Goldberg (jody@gnome.org)
7 * Copyright 2015 by Morten Welinder (terra@gnome.org).
10 #include <gnumeric-config.h>
13 #include <item-cursor.h>
14 #include <gnm-pane-impl.h>
16 #include <sheet-control-gui.h>
17 #include <sheet-control-priv.h>
18 #include <style-color.h>
20 #include <clipboard.h>
21 #include <selection.h>
23 #include <sheet-view.h>
24 #include <sheet-merge.h>
32 #include <parse-util.h>
35 #include <sheet-autofill.h>
36 #include <gsf/gsf-impl-utils.h>
37 #include <goffice/goffice.h>
38 #define GNUMERIC_ITEM "CURSOR"
40 #define ITEM_CURSOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), gnm_item_cursor_get_type (), GnmItemCursorClass))
42 #define AUTO_HANDLE_WIDTH 2
43 #define AUTO_HANDLE_SPACE (AUTO_HANDLE_WIDTH * 2)
44 #define CLIP_SAFETY_MARGIN (AUTO_HANDLE_SPACE + 5)
46 struct _GnmItemCursor
{
50 gboolean pos_initialized
;
53 /* Offset of dragging cell from top left of pos */
54 int col_delta
, row_delta
;
56 /* Tip for movement */
58 GnmCellPos last_tip_pos
;
60 GnmItemCursorStyle style
;
62 guint animation_timer
;
65 * For the autofill mode:
66 * Where the action started (base_x, base_y) and the
67 * width and height of the selection when the autofill
71 GnmRange autofill_src
;
72 int autofill_hsize
, autofill_vsize
;
73 gint64 last_x
, last_y
;
75 /* cursor outline in canvas coords, the bounding box (Item::[xy][01])
76 * is slightly larger ) */
78 gint64 x1
, x2
, y1
, y2
;
81 guint drag_button_state
;
84 gboolean auto_fill_handle_at_top
;
85 gboolean auto_fill_handle_at_left
;
91 GdkRGBA ant_color
, ant_background_color
;
92 GdkRGBA drag_color
, drag_background_color
;
93 GdkRGBA autofill_color
, autofill_background_color
;
95 typedef GocItemClass GnmItemCursorClass
;
97 static GocItemClass
*parent_class
;
101 ITEM_CURSOR_PROP_SHEET_CONTROL_GUI
,
102 ITEM_CURSOR_PROP_STYLE
,
103 ITEM_CURSOR_PROP_BUTTON
,
104 ITEM_CURSOR_PROP_COLOR
109 ic_reload_style (GnmItemCursor
*ic
)
111 GocItem
*item
= GOC_ITEM (ic
);
112 GtkStyleContext
*context
= goc_item_get_style_context (item
);
113 GtkStateFlags state
= GTK_STATE_FLAG_NORMAL
;
116 int fore_offset
, back_offset
;
119 G_STRUCT_OFFSET (GnmItemCursor
, normal_color
),
122 G_STRUCT_OFFSET (GnmItemCursor
, ant_color
),
123 G_STRUCT_OFFSET (GnmItemCursor
, ant_background_color
) },
125 G_STRUCT_OFFSET (GnmItemCursor
, drag_color
),
126 G_STRUCT_OFFSET (GnmItemCursor
, drag_background_color
) },
128 G_STRUCT_OFFSET (GnmItemCursor
, autofill_color
),
129 G_STRUCT_OFFSET (GnmItemCursor
, autofill_background_color
) }
133 for (ui
= 0; ui
< G_N_ELEMENTS (substyles
); ui
++) {
134 GdkRGBA
*fore
, *back
;
135 gtk_style_context_save (context
);
136 gtk_style_context_add_class (context
, substyles
[ui
].class);
137 gtk_style_context_get (context
, state
,
139 "background-color", &back
,
141 *(GdkRGBA
*)((char *)ic
+ substyles
[ui
].fore_offset
) = *fore
;
142 if (substyles
[ui
].back_offset
>= 0)
143 *(GdkRGBA
*)((char *)ic
+ substyles
[ui
].back_offset
) = *back
;
144 gdk_rgba_free (fore
);
145 gdk_rgba_free (back
);
146 gtk_style_context_restore (context
);
150 * Ensure we don't use transparency to avoid compositing issues
151 * when redrawing the ants in the timer callback.
153 ic
->ant_color
.alpha
= ic
->ant_background_color
.alpha
= 1.;
157 cb_item_cursor_animation (GnmItemCursor
*ic
)
159 GocItem
*item
= GOC_ITEM (ic
);
160 cairo_region_t
*region
;
161 cairo_rectangle_int_t rect
;
163 double scale
= item
->canvas
->pixels_per_unit
;
165 /* we need to use canvas coordinates in goc_canvas_c2w, hence the divisions by scale. */
166 if (goc_canvas_get_direction (item
->canvas
) == GOC_DIRECTION_RTL
) {
167 goc_canvas_c2w (item
->canvas
, ic
->outline
.x2
/ scale
, ic
->outline
.y2
/ scale
, &x0
, &y1
);
168 goc_canvas_c2w (item
->canvas
, ic
->outline
.x1
/ scale
, ic
->outline
.y1
/ scale
, &x1
, &y0
);
169 x0
--; /* because of the +.5, things are not symetric */
172 goc_canvas_c2w (item
->canvas
, ic
->outline
.x1
/ scale
, ic
->outline
.y1
/ scale
, &x0
, &y0
);
173 goc_canvas_c2w (item
->canvas
, ic
->outline
.x2
/ scale
, ic
->outline
.y2
/ scale
, &x1
, &y1
);
178 rect
.width
= x1
- x0
+ 3;
179 rect
.height
= y1
- y0
+ 3;
180 region
= cairo_region_create_rectangle (&rect
);
185 cairo_region_xor_rectangle (region
, &rect
);
186 goc_canvas_invalidate_region (item
->canvas
, item
, region
);
187 cairo_region_destroy (region
);
192 item_cursor_dispose (GObject
*obj
)
194 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (obj
);
197 gtk_widget_destroy (gtk_widget_get_toplevel (ic
->tip
));
201 G_OBJECT_CLASS (parent_class
)->dispose (obj
);
205 item_cursor_realize (GocItem
*item
)
207 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
209 parent_class
->realize (item
);
211 ic_reload_style (ic
);
213 if (ic
->style
== GNM_ITEM_CURSOR_ANTED
) {
214 g_return_if_fail (ic
->animation_timer
== 0);
215 ic
->animation_timer
= g_timeout_add (
216 75, (GSourceFunc
) cb_item_cursor_animation
,
222 item_cursor_unrealize (GocItem
*item
)
224 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
226 if (ic
->animation_timer
!= 0) {
227 g_source_remove (ic
->animation_timer
);
228 ic
->animation_timer
= 0;
231 parent_class
->unrealize (item
);
235 item_cursor_update_bounds (GocItem
*item
)
237 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
238 GnmPane
*pane
= GNM_PANE (item
->canvas
);
239 SheetControlGUI
const * const scg
= ic
->scg
;
241 double scale
= item
->canvas
->pixels_per_unit
;
243 int const left
= ic
->pos
.start
.col
;
244 int const right
= ic
->pos
.end
.col
;
245 int const top
= ic
->pos
.start
.row
;
246 int const bottom
= ic
->pos
.end
.row
;
247 ic
->outline
.x1
= pane
->first_offset
.x
+
248 scg_colrow_distance_get (scg
, TRUE
, pane
->first
.col
, left
);
249 ic
->outline
.x2
= ic
->outline
.x1
+
250 scg_colrow_distance_get (scg
, TRUE
, left
, right
+1);
251 ic
->outline
.y1
= pane
->first_offset
.y
+
252 scg_colrow_distance_get (scg
, FALSE
, pane
->first
.row
, top
);
253 ic
->outline
.y2
= ic
->outline
.y1
+
254 scg_colrow_distance_get (scg
, FALSE
,top
, bottom
+1);
256 /* NOTE : sometimes y1 > y2 || x1 > x2 when we create a cursor in an
257 * invisible region such as above a frozen pane */
259 /* jean: I don't know why we now need 2 instead of one in the next two lines */
260 item
->x0
= (ic
->outline
.x1
- 2) / scale
;
261 item
->y0
= (ic
->outline
.y1
- 2) / scale
;
263 /* for the autohandle */
264 tmp
= (ic
->style
== GNM_ITEM_CURSOR_SELECTION
) ? AUTO_HANDLE_WIDTH
: 0;
265 item
->x1
= (ic
->outline
.x2
+ 3 + tmp
) / scale
;
266 item
->y1
= (ic
->outline
.y2
+ 3 + tmp
) / scale
;
270 item_cursor_draw (GocItem
const *item
, cairo_t
*cr
)
272 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
273 int x0
, y0
, x1
, y1
; /* in widget coordinates */
275 int i
, draw_thick
, draw_stippled
, draw_handle
;
277 gboolean draw_center
, draw_external
, draw_internal
, draw_xor
;
278 double scale
= item
->canvas
->pixels_per_unit
;
279 GdkRGBA
*fore
= NULL
, *back
= NULL
;
283 g_printerr ("draw[%d] %lx,%lx %lx,%lx\n",
284 GNM_PANE (item
->canvas
)->index
,
290 if (!goc_item_is_visible (&ic
->canvas_item
) || !ic
->pos_initialized
)
293 /* we need to use canvas coordinates in goc_canvas_c2w, hence the divisions by scale. */
294 if (goc_canvas_get_direction (item
->canvas
) == GOC_DIRECTION_RTL
) {
295 goc_canvas_c2w (item
->canvas
, ic
->outline
.x2
/ scale
, ic
->outline
.y2
/ scale
, &x0
, &y1
);
296 goc_canvas_c2w (item
->canvas
, ic
->outline
.x1
/ scale
, ic
->outline
.y1
/ scale
, &x1
, &y0
);
297 x0
--; /* because of the +.5, things are not symetric */
300 goc_canvas_c2w (item
->canvas
, ic
->outline
.x1
/ scale
, ic
->outline
.y1
/ scale
, &x0
, &y0
);
301 goc_canvas_c2w (item
->canvas
, ic
->outline
.x2
/ scale
, ic
->outline
.y2
/ scale
, &x1
, &y1
);
304 /* only mostly in invisible areas (eg on creation of frozen panes) */
305 if (x0
> x1
|| y0
> y1
)
310 draw_external
= FALSE
;
311 draw_internal
= FALSE
;
319 case GNM_ITEM_CURSOR_AUTOFILL
:
323 fore
= &ic
->autofill_color
;
324 back
= &ic
->autofill_background_color
;
328 case GNM_ITEM_CURSOR_DRAG
:
332 fore
= &ic
->drag_color
;
333 back
= &ic
->drag_background_color
;
337 case GNM_ITEM_CURSOR_EXPR_RANGE
:
339 draw_thick
= (item
->canvas
->last_item
== item
) ? 3 : 2;
343 case GNM_ITEM_CURSOR_SELECTION
: {
344 GnmPane
const *pane
= GNM_PANE (item
->canvas
);
345 GnmPane
const *pane0
= scg_pane (pane
->simple
.scg
, 0);
347 draw_internal
= TRUE
;
348 draw_external
= TRUE
;
351 if (ic
->pos
.end
.row
<= pane
->last_full
.row
)
354 else if ((pane
->index
== 2 || pane
->index
== 3) &&
355 ic
->pos
.end
.row
>= pane0
->first
.row
&&
356 ic
->pos
.end
.row
<= pane0
->last_full
.row
)
358 /* TODO : do we want to add checking for pane above ? */
359 else if (ic
->pos
.start
.row
< pane
->first
.row
)
361 else if (ic
->pos
.start
.row
!= pane
->first
.row
)
368 case GNM_ITEM_CURSOR_ANTED
:
372 fore
= &ic
->ant_color
;
373 back
= &ic
->ant_background_color
;
374 phase0
= (~ic
->ant_state
& 3) * 0.25;
383 ic
->auto_fill_handle_at_top
= (draw_handle
>= 2);
385 if (x0
>= x1
|| y0
>= y1
)
388 cairo_set_dash (cr
, NULL
, 0, 0.);
389 cairo_set_line_width (cr
, 1.);
390 cairo_set_line_cap (cr
, CAIRO_LINE_CAP_BUTT
);
391 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_MITER
);
392 gdk_cairo_set_source_rgba (cr
, &ic
->normal_color
);
395 cairo_set_operator (cr
, CAIRO_OPERATOR_HARD_LIGHT
);
397 switch (draw_handle
) {
398 /* Auto handle at bottom */
400 premove
= AUTO_HANDLE_SPACE
;
405 points
[0].x
= x1
+ 1.5;
406 points
[0].y
= y1
+ 1 - premove
;
407 points
[1].x
= points
[0].x
;
408 points
[1].y
= y0
- .5;
409 points
[2].x
= x0
- .5;
410 points
[2].y
= y0
- .5;
411 points
[3].x
= x0
- .5;
412 points
[3].y
= y1
+ 1.5;
413 points
[4].x
= x1
+ 1 - premove
;
414 points
[4].y
= points
[3].y
;
417 /* Auto handle at top */
419 premove
= AUTO_HANDLE_SPACE
;
422 /* Auto handle at top of sheet */
424 points
[0].x
= x1
+ 1.5;
425 points
[0].y
= y0
- .5 + AUTO_HANDLE_SPACE
;
426 points
[1].x
= points
[0].x
;
427 points
[1].y
= y1
+ 1.5;
428 points
[2].x
= x0
- .5;
429 points
[2].y
= points
[1].y
;
430 points
[3].x
= points
[2].x
;
431 points
[3].y
= y0
- .5;
432 points
[4].x
= x1
+ 1 - premove
;
433 points
[4].y
= points
[3].y
;
437 g_assert_not_reached ();
439 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
440 for (i
= 1; i
< 5; i
++)
441 cairo_line_to (cr
, points
[i
].x
, points
[i
].y
);
445 if (draw_external
&& draw_internal
) {
446 if (draw_handle
< 2) {
465 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
466 for (i
= 1; i
< 5; i
++)
467 cairo_line_to (cr
, points
[i
].x
, points
[i
].y
);
471 if (draw_handle
== 1 || draw_handle
== 2) {
472 int const y_off
= (draw_handle
== 1) ? y1
- y0
: 0;
473 cairo_rectangle (cr
, x1
- 2, y0
+ y_off
- 2, 2, 2);
474 cairo_rectangle (cr
, x1
+ 1, y0
+ y_off
- 2, 2, 2);
475 cairo_rectangle (cr
, x1
- 2, y0
+ y_off
+ 1, 2, 2);
476 cairo_rectangle (cr
, x1
+ 1, y0
+ y_off
+ 1, 2, 2);
478 } else if (draw_handle
== 3) {
479 cairo_rectangle (cr
, x1
- 2, y0
+ 1, 2, 4);
480 cairo_rectangle (cr
, x1
+ 1, y0
+ 1, 2, 4);
486 double phase1
= fmod (phase0
+ 0.5, 1);
488 /* Stay in the boundary */
489 x0
+= (draw_thick
/ 2.0);
490 y0
+= (draw_thick
/ 2.0);
492 cairo_set_line_width (cr
, draw_thick
);
493 cairo_rectangle (cr
, x0
, y0
, abs (x1
- x0
), abs (y1
- y0
));
494 dashes
[0] = dashes
[1] = draw_stippled
;
496 cairo_set_dash (cr
, dashes
, 2, phase0
* 2 * draw_stippled
);
497 gdk_cairo_set_source_rgba (cr
, back
);
498 cairo_stroke_preserve (cr
);
500 cairo_set_dash (cr
, dashes
, 2, phase1
* 2 * draw_stippled
);
501 gdk_cairo_set_source_rgba (cr
, fore
);
508 gnm_item_cursor_bound_set (GnmItemCursor
*ic
, GnmRange
const *new_bound
)
511 g_return_val_if_fail (GNM_IS_ITEM_CURSOR (ic
), FALSE
);
512 g_return_val_if_fail (range_is_sane (new_bound
), FALSE
);
514 if (ic
->pos_initialized
&& range_equal (&ic
->pos
, new_bound
))
517 item
= GOC_ITEM (ic
);
518 goc_item_invalidate (item
);
519 ic
->pos
= *new_bound
;
520 ic
->pos_initialized
= TRUE
;
522 goc_item_bounds_changed (item
);
523 goc_item_invalidate (item
);
529 * gnm_item_cursor_reposition:
531 * Re-compute the pixel position of the cursor.
533 * When a sheet is zoomed. The pixel coords shift slightly. The item cursor
534 * must regenerate to stay in sync.
537 gnm_item_cursor_reposition (GnmItemCursor
*ic
)
539 g_return_if_fail (GOC_IS_ITEM (ic
));
540 goc_item_bounds_changed (GOC_ITEM (ic
));
544 item_cursor_distance (GocItem
*item
, double x
, double y
,
545 GocItem
**actual_item
)
547 GnmItemCursor
const *ic
= GNM_ITEM_CURSOR (item
);
549 /* Cursor should not always receive events
552 * 3) while a guru is up
554 if (!goc_item_is_visible (item
) ||
555 ic
->style
== GNM_ITEM_CURSOR_ANTED
||
556 wbc_gtk_get_guru (scg_wbcg (ic
->scg
)) != NULL
)
570 if ((x
< (item
->x0
+ 4)) || (x
> (item
->x1
- 8)) ||
571 (y
< (item
->y0
+ 4)) || (y
> (item
->y1
- 8))) {
579 item_cursor_setup_auto_fill (GnmItemCursor
*ic
, GnmItemCursor
const *parent
, int x
, int y
)
581 Sheet
const *sheet
= scg_sheet (parent
->scg
);
587 ic
->autofill_src
= parent
->pos
;
589 /* If there are arrays or merges in the region ensure that we
590 * need to ensure that an integer multiple of the original size
591 * is filled. We could be fancy about this an allow filling as long
592 * as the merges would not be split, bu that is more work than it is
593 * worth right now (FIXME this is a nice project).
595 * We do not have to be too careful, the sheet guarantees that the
596 * cursor does not split merges, all we need is existence.
598 merges
= gnm_sheet_merge_get_overlap (sheet
, &ic
->autofill_src
);
599 if (merges
!= NULL
) {
600 g_slist_free (merges
);
601 ic
->autofill_hsize
= range_width (&ic
->autofill_src
);
602 ic
->autofill_vsize
= range_height (&ic
->autofill_src
);
604 ic
->autofill_hsize
= ic
->autofill_vsize
= 1;
607 static inline gboolean
608 item_cursor_in_drag_handle (GnmItemCursor
*ic
, gint64 x
, gint64 y
)
610 double scale
= ic
->canvas_item
.canvas
->pixels_per_unit
;
611 gint64
const y_test
= ic
->auto_fill_handle_at_top
612 ? ic
->canvas_item
.y0
* scale
+ AUTO_HANDLE_WIDTH
613 : ic
->canvas_item
.y1
* scale
- AUTO_HANDLE_WIDTH
;
615 if ((y_test
-AUTO_HANDLE_SPACE
) <= y
&&
616 y
<= (y_test
+AUTO_HANDLE_SPACE
)) {
617 gint64
const x_test
= ic
->auto_fill_handle_at_left
618 ? (ic
->canvas_item
.canvas
->direction
== GOC_DIRECTION_RTL
?
619 ic
->canvas_item
.x1
* scale
- AUTO_HANDLE_WIDTH
:
620 ic
->canvas_item
.x0
* scale
+ AUTO_HANDLE_WIDTH
)
621 : (ic
->canvas_item
.canvas
->direction
== GOC_DIRECTION_RTL
?
622 ic
->canvas_item
.x0
* scale
+ AUTO_HANDLE_WIDTH
:
623 ic
->canvas_item
.x1
* scale
- AUTO_HANDLE_WIDTH
);
624 return (x_test
-AUTO_HANDLE_SPACE
) <= x
&&
625 x
<= (x_test
+AUTO_HANDLE_SPACE
);
631 item_cursor_set_cursor (GocCanvas
*canvas
, GnmItemCursor
*ic
, gint64 x
, gint64 y
)
633 GdkCursorType cursor
;
635 if (item_cursor_in_drag_handle (ic
, x
, y
))
636 cursor
= GDK_CROSSHAIR
;
640 gnm_widget_set_cursor_type (GTK_WIDGET (canvas
), cursor
);
644 item_cursor_selection_motion (GocItem
*item
, double x_
, double y_
)
646 GocCanvas
*canvas
= item
->canvas
;
647 GnmPane
*pane
= GNM_PANE (canvas
);
648 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
650 gint64 x
= x_
* canvas
->pixels_per_unit
, y
= y_
* canvas
->pixels_per_unit
;
651 GnmItemCursor
*special_cursor
;
653 if (ic
->drag_button
< 0) {
654 item_cursor_set_cursor (canvas
, ic
, x
, y
);
659 * determine which part of the cursor was clicked:
660 * the border or the handlebox
662 if (item_cursor_in_drag_handle (ic
, x_
, y_
))
663 style
= GNM_ITEM_CURSOR_AUTOFILL
;
665 style
= GNM_ITEM_CURSOR_DRAG
;
667 button
= ic
->drag_button
;
668 ic
->drag_button
= -1;
669 gnm_simple_canvas_ungrab (item
);
671 scg_special_cursor_start (ic
->scg
, style
, button
);
672 special_cursor
= pane
->cursor
.special
;
673 special_cursor
->drag_button_state
= ic
->drag_button_state
;
674 if (style
== GNM_ITEM_CURSOR_AUTOFILL
)
675 item_cursor_setup_auto_fill (
676 special_cursor
, ic
, x
, y
);
683 * Capture the offset of the current cell relative to
684 * the upper left corner. Be careful handling the position
685 * of the cursor. it is possible to select the exterior or
686 * interior of the cursor edge which behaves as if the cursor
687 * selection was offset by one.
690 int d_col
= gnm_pane_find_col (pane
, x
, NULL
) -
692 int d_row
= gnm_pane_find_row (pane
, y
, NULL
) -
696 int tmp
= ic
->pos
.end
.col
- ic
->pos
.start
.col
;
701 special_cursor
->col_delta
= d_col
;
704 int tmp
= ic
->pos
.end
.row
- ic
->pos
.start
.row
;
709 special_cursor
->row_delta
= d_row
;
712 scg_special_cursor_bound_set (ic
->scg
, &ic
->pos
);
714 gnm_simple_canvas_grab (GOC_ITEM (special_cursor
));
715 gnm_pane_slide_init (pane
);
717 goc_item_bounds_changed (GOC_ITEM (ic
));
720 * We flush after the grab to ensure that the new item-cursor
721 * gets created. If it is not ready in time double click
722 * events will be disrupted and it will appear as if we are
723 * doing an button_press with a missing release.
735 ACTION_SHIFT_DOWN_AND_COPY
,
736 ACTION_SHIFT_RIGHT_AND_COPY
,
737 ACTION_SHIFT_DOWN_AND_MOVE
,
738 ACTION_SHIFT_RIGHT_AND_MOVE
742 item_cursor_do_action (GnmItemCursor
*ic
, ActionType action
)
746 WorkbookControl
*wbc
;
749 g_return_if_fail (ic
!= NULL
);
751 if (action
== ACTION_NONE
) {
752 scg_special_cursor_stop (ic
->scg
);
756 sheet
= scg_sheet (ic
->scg
);
757 sv
= scg_view (ic
->scg
);
758 wbc
= scg_wbc (ic
->scg
);
761 case ACTION_COPY_CELLS
:
762 if (!gnm_sheet_view_selection_copy (sv
, wbc
))
765 paste_target_init (&pt
, sheet
, &ic
->pos
,
769 case ACTION_MOVE_CELLS
:
770 if (!gnm_sheet_view_selection_cut (sv
, wbc
))
773 paste_target_init (&pt
, sheet
, &ic
->pos
,
777 case ACTION_COPY_FORMATS
:
778 if (!gnm_sheet_view_selection_copy (sv
, wbc
))
781 paste_target_init (&pt
, sheet
, &ic
->pos
,
785 case ACTION_COPY_VALUES
:
786 if (!gnm_sheet_view_selection_copy (sv
, wbc
))
789 paste_target_init (&pt
, sheet
, &ic
->pos
,
793 case ACTION_SHIFT_DOWN_AND_COPY
:
794 case ACTION_SHIFT_RIGHT_AND_COPY
:
795 case ACTION_SHIFT_DOWN_AND_MOVE
:
796 case ACTION_SHIFT_RIGHT_AND_MOVE
:
797 g_warning ("Operation not yet implemented.");
801 g_warning ("Invalid Operation %d.", action
);
804 scg_special_cursor_stop (ic
->scg
);
808 context_menu_hander (GnmPopupMenuElement
const *element
,
811 g_return_if_fail (element
!= NULL
);
812 item_cursor_do_action (ic
, element
->index
);
816 item_cursor_popup_menu (GnmItemCursor
*ic
, GdkEvent
*event
)
818 static GnmPopupMenuElement
const popup_elements
[] = {
820 0, 0, ACTION_MOVE_CELLS
},
822 { N_("_Copy"), "edit-copy",
823 0, 0, ACTION_COPY_CELLS
},
825 { N_("Copy _Formats"), NULL
,
826 0, 0, ACTION_COPY_FORMATS
},
827 { N_("Copy _Values"), NULL
,
828 0, 0, ACTION_COPY_VALUES
},
830 { "", NULL
, 0, 0, 0 },
832 { N_("Shift _Down and Copy"), NULL
,
833 0, 0, ACTION_SHIFT_DOWN_AND_COPY
},
834 { N_("Shift _Right and Copy"), NULL
,
835 0, 0, ACTION_SHIFT_RIGHT_AND_COPY
},
836 { N_("Shift Dow_n and Move"), NULL
,
837 0, 0, ACTION_SHIFT_DOWN_AND_MOVE
},
838 { N_("Shift Righ_t and Move"), NULL
,
839 0, 0, ACTION_SHIFT_RIGHT_AND_MOVE
},
841 { "", NULL
, 0, 0, 0 },
843 { N_("C_ancel"), NULL
,
846 { NULL
, NULL
, 0, 0, 0 }
849 gnm_create_popup_menu (popup_elements
,
850 &context_menu_hander
, ic
, NULL
,
855 item_cursor_do_drop (GnmItemCursor
*ic
, GdkEvent
*event
)
857 /* Only do the operation if something moved */
858 SheetView
const *sv
= scg_view (ic
->scg
);
859 GnmRange
const *target
= selection_first_range (sv
, NULL
, NULL
);
861 wbcg_set_status_text (scg_wbcg (ic
->scg
), "");
862 if (range_equal (target
, &ic
->pos
)) {
863 scg_special_cursor_stop (ic
->scg
);
867 if (event
->button
.button
== 3)
868 item_cursor_popup_menu (ic
, event
);
870 item_cursor_do_action (ic
, (event
->button
.state
& GDK_CONTROL_MASK
)
872 : ACTION_MOVE_CELLS
);
876 gnm_item_cursor_set_visibility (GnmItemCursor
*ic
, gboolean visible
)
878 goc_item_set_visible (GOC_ITEM (ic
), visible
);
882 item_cursor_tip_setlabel (GnmItemCursor
*ic
, char const *text
)
884 if (ic
->tip
== NULL
) {
885 GtkWidget
*cw
= GTK_WIDGET (GOC_ITEM (ic
)->canvas
);
887 ic
->tip
= gnm_create_tooltip (cw
);
889 gnm_canvas_get_position (GOC_CANVAS (cw
), &x
, &y
, ic
->last_x
, ic
->last_y
);
890 gnm_position_tooltip (ic
->tip
, x
, y
, TRUE
);
891 gtk_widget_show_all (gtk_widget_get_toplevel (ic
->tip
));
894 g_return_if_fail (ic
->tip
!= NULL
);
895 gtk_label_set_text (GTK_LABEL (ic
->tip
), text
);
899 cb_move_cursor (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
901 GnmItemCursor
*ic
= info
->user_data
;
902 int const w
= (ic
->pos
.end
.col
- ic
->pos
.start
.col
);
903 int const h
= (ic
->pos
.end
.row
- ic
->pos
.start
.row
);
905 Sheet
*sheet
= scg_sheet (pane
->simple
.scg
);
907 r
.start
.col
= info
->col
- ic
->col_delta
;
910 else if (r
.start
.col
>= (gnm_sheet_get_max_cols (sheet
) - w
))
911 r
.start
.col
= gnm_sheet_get_max_cols (sheet
) - w
- 1;
913 r
.start
.row
= info
->row
- ic
->row_delta
;
916 else if (r
.start
.row
>= (gnm_sheet_get_max_rows (sheet
) - h
))
917 r
.start
.row
= gnm_sheet_get_max_rows (sheet
) - h
- 1;
919 item_cursor_tip_setlabel (ic
, range_as_string (&ic
->pos
));
921 r
.end
.col
= r
.start
.col
+ w
;
922 r
.end
.row
= r
.start
.row
+ h
;
923 scg_special_cursor_bound_set (ic
->scg
, &r
);
924 scg_make_cell_visible (ic
->scg
, info
->col
, info
->row
, FALSE
, TRUE
);
929 item_cursor_handle_motion (GnmItemCursor
*ic
, double x
, double y
,
930 GnmPaneSlideHandler slide_handler
)
932 GocCanvas
*canvas
= GOC_ITEM (ic
)->canvas
;
934 gnm_pane_handle_motion (GNM_PANE (canvas
),
936 GNM_PANE_SLIDE_X
| GNM_PANE_SLIDE_Y
| GNM_PANE_SLIDE_AT_COLROW_BOUND
,
938 goc_item_bounds_changed (GOC_ITEM (ic
));
942 item_cursor_drag_motion (GnmItemCursor
*ic
, double x
, double y
)
944 item_cursor_handle_motion (ic
, x
, y
, &cb_move_cursor
);
949 limit_string_height_and_width (GString
*s
, size_t wmax
, size_t hmax
)
953 for (l
= 0; l
< hmax
; l
++) {
956 while (s
->str
[p
] != 0 && s
->str
[p
] != '\n') {
960 p
+= g_utf8_skip
[(unsigned char)(s
->str
[p
])];
964 g_string_erase (s
, cut
, p
- cut
);
971 g_string_truncate (s
, p
);
976 cb_autofill_scroll (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
978 GnmItemCursor
*ic
= info
->user_data
;
979 GnmRange r
= ic
->autofill_src
;
980 int col
= info
->col
, row
= info
->row
;
983 /* compass offsets are distances (in cells) from the edges of the
984 * selected area to the mouse cursor */
985 int north_offset
= r
.start
.row
- row
;
986 int south_offset
= row
- r
.end
.row
;
987 int west_offset
= r
.start
.col
- col
;
988 int east_offset
= col
- r
.end
.col
;
990 /* Autofill by row or by col, NOT both. */
991 if ( MAX (north_offset
, south_offset
) > MAX (west_offset
, east_offset
) ) {
992 if (row
< r
.start
.row
)
993 r
.start
.row
-= ic
->autofill_vsize
* (int)(north_offset
/ ic
->autofill_vsize
);
995 r
.end
.row
+= ic
->autofill_vsize
* (int)(south_offset
/ ic
->autofill_vsize
);
996 if (col
< r
.start
.col
)
998 else if (col
> r
.end
.col
)
1001 if (col
< r
.start
.col
)
1002 r
.start
.col
-= ic
->autofill_hsize
* (int)(west_offset
/ ic
->autofill_hsize
);
1004 r
.end
.col
+= ic
->autofill_hsize
* (int)(east_offset
/ ic
->autofill_hsize
);
1005 if (row
< r
.start
.row
)
1007 else if (row
> r
.end
.row
)
1011 /* Check if we have moved to a new cell. */
1012 if (col
== ic
->last_tip_pos
.col
&& row
== ic
->last_tip_pos
.row
)
1014 ic
->last_tip_pos
.col
= col
;
1015 ic
->last_tip_pos
.row
= row
;
1017 scg_special_cursor_bound_set (ic
->scg
, &r
);
1018 scg_make_cell_visible (ic
->scg
, col
, row
, FALSE
, TRUE
);
1020 w
= range_width (&ic
->autofill_src
);
1021 h
= range_height (&ic
->autofill_src
);
1022 if (ic
->pos
.start
.col
+ w
- 1 == ic
->pos
.end
.col
&&
1023 ic
->pos
.start
.row
+ h
- 1 == ic
->pos
.end
.row
)
1024 item_cursor_tip_setlabel (ic
, _("Autofill"));
1026 gboolean inverse_autofill
=
1027 (ic
->pos
.start
.col
< ic
->autofill_src
.start
.col
||
1028 ic
->pos
.start
.row
< ic
->autofill_src
.start
.row
);
1029 gboolean default_increment
=
1030 ic
->drag_button_state
& GDK_CONTROL_MASK
;
1031 Sheet
*sheet
= scg_sheet (ic
->scg
);
1034 if (inverse_autofill
)
1035 hint
= gnm_autofill_hint
1036 (sheet
, default_increment
,
1037 ic
->pos
.end
.col
, ic
->pos
.end
.row
,
1039 ic
->pos
.start
.col
, ic
->pos
.start
.row
);
1041 hint
= gnm_autofill_hint
1042 (sheet
, default_increment
,
1043 ic
->pos
.start
.col
, ic
->pos
.start
.row
,
1045 ic
->pos
.end
.col
, ic
->pos
.end
.row
);
1048 limit_string_height_and_width (hint
, 200, 200);
1049 item_cursor_tip_setlabel (ic
, hint
->str
);
1050 g_string_free (hint
, TRUE
);
1052 item_cursor_tip_setlabel (ic
, "");
1059 item_cursor_button_pressed (GocItem
*item
, int button
, double x_
, double y_
)
1061 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1062 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1063 GdkEvent
*event
= goc_canvas_get_cur_event (item
->canvas
);
1064 GdkEventButton
*bevent
= &event
->button
;
1065 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
)
1068 /* While editing nothing should be draggable */
1069 if (wbcg_is_editing (scg_wbcg (ic
->scg
)))
1072 switch (ic
->style
) {
1074 case GNM_ITEM_CURSOR_ANTED
:
1075 g_warning ("Animated cursors should not receive events, "
1076 "the point method should preclude that");
1079 case GNM_ITEM_CURSOR_SELECTION
:
1080 /* NOTE : this cannot be called while we are editing. because
1081 * the point routine excludes events. so we do not need to
1082 * call wbcg_edit_finish.
1085 /* scroll wheel events dont have corresponding release events */
1089 /* If another button is already down ignore this one */
1090 if (ic
->drag_button
>= 0)
1094 /* prepare to create fill or drag cursors, but dont until we
1095 * move. If we did create them here there would be problems
1096 * with race conditions when the new cursors pop into existence
1097 * during a double-click
1100 if (item_cursor_in_drag_handle (ic
, x
, y
))
1101 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (scg_wbcg (ic
->scg
)),
1102 _("Drag to autofill"));
1104 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (scg_wbcg (ic
->scg
)),
1107 ic
->drag_button
= button
;
1108 ic
->drag_button_state
= bevent
->state
;
1109 gnm_simple_canvas_grab (item
);
1111 scg_context_menu (ic
->scg
, event
, FALSE
, FALSE
);
1114 case GNM_ITEM_CURSOR_DRAG
:
1115 /* This kind of cursor is created and grabbed. Then destroyed
1116 * when the button is released. If we are seeing a press it
1117 * means that someone has pressed another button WHILE THE
1118 * FIRST IS STILL DOWN. Ignore this event.
1129 item_cursor_button2_pressed (GocItem
*item
, int button
, double x_
, double y_
)
1131 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1132 GdkEvent
*event
= goc_canvas_get_cur_event (item
->canvas
);
1134 switch (ic
->style
) {
1136 case GNM_ITEM_CURSOR_SELECTION
: {
1137 Sheet
*sheet
= scg_sheet (ic
->scg
);
1138 int final_col
= ic
->pos
.end
.col
;
1139 int final_row
= ic
->pos
.end
.row
;
1141 if (ic
->drag_button
!= button
)
1144 ic
->drag_button
= -1;
1145 gnm_simple_canvas_ungrab (item
);
1147 if (sheet_is_region_empty (sheet
, &ic
->pos
))
1150 /* If the cell(s) immediately below the ones in the
1151 * auto-fill template are not blank then over-write
1154 * Otherwise, only go as far as the next non-blank
1157 * The code below uses find_boundary twice. a. to
1158 * find the boundary of the column/row that acts as a
1159 * template to define the region to file and b. to
1160 * find the boundary of the region being filled.
1163 if (event
->button
.state
& GDK_MOD1_MASK
) {
1164 int template_col
= ic
->pos
.end
.col
+ 1;
1165 int template_row
= ic
->pos
.start
.row
- 1;
1166 int boundary_col_for_target
;
1169 if (template_row
< 0 || template_col
>= gnm_sheet_get_max_cols (sheet
) ||
1170 sheet_is_cell_empty (sheet
, template_col
,
1173 template_row
= ic
->pos
.end
.row
+ 1;
1174 if (template_row
>= gnm_sheet_get_max_rows (sheet
) ||
1175 template_col
>= gnm_sheet_get_max_cols (sheet
) ||
1176 sheet_is_cell_empty (sheet
, template_col
,
1181 if (template_col
>= gnm_sheet_get_max_cols (sheet
) ||
1182 sheet_is_cell_empty (sheet
, template_col
,
1185 final_col
= sheet_find_boundary_horizontal (sheet
,
1186 ic
->pos
.end
.col
, template_row
,
1187 template_row
, 1, TRUE
);
1188 if (final_col
<= ic
->pos
.end
.col
)
1192 Find the boundary of the target region.
1193 We don't want to go beyond this boundary.
1195 for (target_row
= ic
->pos
.start
.row
; target_row
<= ic
->pos
.end
.row
; target_row
++) {
1196 /* find_boundary is designed for Ctrl-arrow movement. (Ab)using it for
1197 * finding autofill regions works fairly well. One little gotcha is
1198 * that if the current col is the last row of a block of data Ctrl-arrow
1199 * will take you to then next block. The workaround for this is to
1200 * start the search at the last col of the selection, rather than
1201 * the first col of the region being filled.
1203 boundary_col_for_target
= sheet_find_boundary_horizontal
1205 ic
->pos
.end
.col
, target_row
,
1206 target_row
, 1, TRUE
);
1208 if (sheet_is_cell_empty (sheet
, boundary_col_for_target
-1, target_row
) &&
1209 ! sheet_is_cell_empty (sheet
, boundary_col_for_target
, target_row
)) {
1210 /* target region was empty, we are now one col
1211 beyond where it is safe to autofill. */
1212 boundary_col_for_target
--;
1214 if (boundary_col_for_target
< final_col
) {
1215 final_col
= boundary_col_for_target
;
1219 int template_row
= ic
->pos
.end
.row
+ 1;
1220 int template_col
= ic
->pos
.start
.col
- 1;
1221 int boundary_row_for_target
;
1224 if (template_col
< 0 || template_row
>= gnm_sheet_get_max_rows (sheet
) ||
1225 sheet_is_cell_empty (sheet
, template_col
,
1228 template_col
= ic
->pos
.end
.col
+ 1;
1229 if (template_col
>= gnm_sheet_get_max_cols (sheet
) ||
1230 template_row
>= gnm_sheet_get_max_rows (sheet
) ||
1231 sheet_is_cell_empty (sheet
, template_col
,
1236 if (template_row
>= gnm_sheet_get_max_rows (sheet
) ||
1237 sheet_is_cell_empty (sheet
, template_col
,
1240 final_row
= sheet_find_boundary_vertical (sheet
,
1241 template_col
, ic
->pos
.end
.row
,
1242 template_col
, 1, TRUE
);
1243 if (final_row
<= ic
->pos
.end
.row
)
1247 Find the boundary of the target region.
1248 We don't want to go beyond this boundary.
1250 for (target_col
= ic
->pos
.start
.col
; target_col
<= ic
->pos
.end
.col
; target_col
++) {
1251 /* find_boundary is designed for Ctrl-arrow movement. (Ab)using it for
1252 * finding autofill regions works fairly well. One little gotcha is
1253 * that if the current row is the last row of a block of data Ctrl-arrow
1254 * will take you to then next block. The workaround for this is to
1255 * start the search at the last row of the selection, rather than
1256 * the first row of the region being filled.
1258 boundary_row_for_target
= sheet_find_boundary_vertical
1260 target_col
, ic
->pos
.end
.row
,
1261 target_col
, 1, TRUE
);
1262 if (sheet_is_cell_empty (sheet
, target_col
, boundary_row_for_target
-1) &&
1263 ! sheet_is_cell_empty (sheet
, target_col
, boundary_row_for_target
)) {
1264 /* target region was empty, we are now one row
1265 beyond where it is safe to autofill. */
1266 boundary_row_for_target
--;
1269 if (boundary_row_for_target
< final_row
) {
1270 final_row
= boundary_row_for_target
;
1275 /* fill the row/column */
1276 cmd_autofill (scg_wbc (ic
->scg
), sheet
, FALSE
,
1277 ic
->pos
.start
.col
, ic
->pos
.start
.row
,
1278 ic
->pos
.end
.col
- ic
->pos
.start
.col
+ 1,
1279 ic
->pos
.end
.row
- ic
->pos
.start
.row
+ 1,
1280 final_col
, final_row
,
1286 case GNM_ITEM_CURSOR_DRAG
:
1296 item_cursor_motion (GocItem
*item
, double x_
, double y_
)
1298 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1299 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1302 if (ic
->drag_button
< 0) {
1303 item_cursor_set_cursor (item
->canvas
, ic
, x
, y
);
1306 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
)
1309 /* While editing nothing should be draggable */
1310 if (wbcg_is_editing (scg_wbcg (ic
->scg
)))
1312 switch (ic
->style
) {
1314 case GNM_ITEM_CURSOR_ANTED
:
1315 g_warning ("Animated cursors should not receive events, "
1316 "the point method should preclude that");
1319 case GNM_ITEM_CURSOR_SELECTION
:
1320 return item_cursor_selection_motion (item
, x
, y
);
1322 case GNM_ITEM_CURSOR_DRAG
:
1323 return item_cursor_drag_motion (ic
, x
, y
);
1325 case GNM_ITEM_CURSOR_AUTOFILL
:
1326 item_cursor_handle_motion (GNM_ITEM_CURSOR (item
), x
, y
, &cb_autofill_scroll
);
1335 item_cursor_button_released (GocItem
*item
, int button
, G_GNUC_UNUSED
double x
, G_GNUC_UNUSED
double y
)
1337 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1338 GdkEvent
*event
= goc_canvas_get_cur_event (item
->canvas
);
1339 WBCGtk
*wbcg
= scg_wbcg (ic
->scg
);
1341 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
)
1344 /* While editing nothing should be draggable */
1345 if (wbcg_is_editing (wbcg
))
1348 switch (ic
->style
) {
1349 case GNM_ITEM_CURSOR_ANTED
:
1350 g_warning ("Animated cursors should not receive events, "
1351 "the point method should preclude that");
1354 case GNM_ITEM_CURSOR_SELECTION
:
1355 if (ic
->drag_button
!= button
)
1358 /* Double clicks may have already released the drag prep */
1359 if (ic
->drag_button
>= 0) {
1360 gnm_simple_canvas_ungrab (item
);
1361 ic
->drag_button
= -1;
1363 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (wbcg
),
1367 case GNM_ITEM_CURSOR_DRAG
:
1368 if (ic
->drag_button
!= button
)
1371 gnm_pane_slide_stop (GNM_PANE (item
->canvas
));
1372 gnm_simple_canvas_ungrab (item
);
1373 item_cursor_do_drop (ic
, event
);
1375 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (wbcg
),
1379 case GNM_ITEM_CURSOR_AUTOFILL
: {
1380 gboolean inverse_autofill
=
1381 (ic
->pos
.start
.col
< ic
->autofill_src
.start
.col
||
1382 ic
->pos
.start
.row
< ic
->autofill_src
.start
.row
);
1383 gboolean default_increment
=
1384 ic
->drag_button_state
& GDK_CONTROL_MASK
;
1385 SheetControlGUI
*scg
= ic
->scg
;
1387 gnm_pane_slide_stop (GNM_PANE (item
->canvas
));
1388 gnm_simple_canvas_ungrab (item
);
1390 cmd_autofill (scg_wbc (scg
), scg_sheet (scg
), default_increment
,
1391 ic
->pos
.start
.col
, ic
->pos
.start
.row
,
1392 range_width (&ic
->autofill_src
),
1393 range_height (&ic
->autofill_src
),
1394 ic
->pos
.end
.col
, ic
->pos
.end
.row
,
1397 scg_special_cursor_stop (scg
);
1399 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (wbcg
),
1409 item_cursor_enter_notify (GocItem
*item
, double x_
, double y_
)
1411 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1412 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1413 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
) {
1414 gnm_widget_set_cursor_type (GTK_WIDGET (item
->canvas
), GDK_ARROW
);
1415 goc_item_invalidate (item
);
1417 else if (ic
->style
== GNM_ITEM_CURSOR_SELECTION
)
1418 item_cursor_set_cursor (item
->canvas
, ic
, x
, y
);
1423 item_cursor_leave_notify (GocItem
*item
, G_GNUC_UNUSED
double x
, G_GNUC_UNUSED
double y
)
1425 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (item
);
1426 if (ic
->style
== GNM_ITEM_CURSOR_EXPR_RANGE
)
1427 goc_item_invalidate (item
);
1432 item_cursor_set_property (GObject
*obj
, guint param_id
,
1433 GValue
const *value
, GParamSpec
*pspec
)
1435 GnmItemCursor
*ic
= GNM_ITEM_CURSOR (obj
);
1438 case ITEM_CURSOR_PROP_SHEET_CONTROL_GUI
:
1439 ic
->scg
= g_value_get_object (value
);
1441 case ITEM_CURSOR_PROP_STYLE
:
1442 ic
->style
= g_value_get_int (value
);
1444 case ITEM_CURSOR_PROP_BUTTON
:
1445 ic
->drag_button
= g_value_get_int (value
);
1447 case ITEM_CURSOR_PROP_COLOR
:
1448 go_color_to_gdk_rgba (g_value_get_uint (value
), &ic
->color
);
1454 * GnmItemCursor class initialization
1457 gnm_item_cursor_class_init (GObjectClass
*gobject_klass
)
1460 GocItemClass
*item_klass
= (GocItemClass
*) gobject_klass
;
1462 parent_class
= g_type_class_peek_parent (gobject_klass
);
1464 gobject_klass
->set_property
= item_cursor_set_property
;
1465 gobject_klass
->dispose
= item_cursor_dispose
;
1466 g_object_class_install_property (gobject_klass
, ITEM_CURSOR_PROP_SHEET_CONTROL_GUI
,
1467 g_param_spec_object ("SheetControlGUI",
1468 P_("SheetControlGUI"),
1469 P_("The sheet control gui controlling the item"),
1471 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1472 g_object_class_install_property (gobject_klass
, ITEM_CURSOR_PROP_STYLE
,
1473 g_param_spec_int ("style",
1475 P_("What type of cursor"),
1477 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1478 g_object_class_install_property (gobject_klass
, ITEM_CURSOR_PROP_BUTTON
,
1479 g_param_spec_int ("button",
1481 P_("What button initiated the drag"),
1483 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1484 g_object_class_install_property (gobject_klass
, ITEM_CURSOR_PROP_COLOR
,
1485 g_param_spec_uint ("color",
1487 P_("Name of the cursor's color"),
1490 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1492 item_klass
->realize
= item_cursor_realize
;
1493 item_klass
->unrealize
= item_cursor_unrealize
;
1494 item_klass
->draw
= item_cursor_draw
;
1495 item_klass
->update_bounds
= item_cursor_update_bounds
;
1496 item_klass
->distance
= item_cursor_distance
;
1497 item_klass
->button_pressed
= item_cursor_button_pressed
;
1498 item_klass
->button2_pressed
= item_cursor_button2_pressed
;
1499 item_klass
->button_released
= item_cursor_button_released
;
1500 item_klass
->motion
= item_cursor_motion
;
1501 item_klass
->enter_notify
= item_cursor_enter_notify
;
1502 item_klass
->leave_notify
= item_cursor_leave_notify
;
1506 gnm_item_cursor_init (GnmItemCursor
*ic
)
1508 ic
->pos_initialized
= FALSE
;
1509 ic
->pos
.start
.col
= 0;
1510 ic
->pos
.end
.col
= 0;
1511 ic
->pos
.start
.row
= 0;
1512 ic
->pos
.end
.row
= 0;
1518 ic
->last_tip_pos
.col
= -1;
1519 ic
->last_tip_pos
.row
= -1;
1524 ic
->style
= GNM_ITEM_CURSOR_SELECTION
;
1526 ic
->animation_timer
= 0;
1528 ic
->auto_fill_handle_at_top
= FALSE
;
1529 ic
->auto_fill_handle_at_left
= FALSE
;
1530 ic
->drag_button
= -1;
1533 GSF_CLASS (GnmItemCursor
, gnm_item_cursor
,
1534 gnm_item_cursor_class_init
, gnm_item_cursor_init
,