Introspection: add col/row manipulations
[gnumeric.git] / src / item-cursor.c
blob9724ff2cf98ab6380f139b2b806da620cf6587c0
1 /* vim: set sw=8: */
2 /*
3 * The Cursor Canvas Item: Implements a rectangular cursor
5 * Author:
6 * Miguel de Icaza (miguel@kernel.org)
7 * Jody Goldberg (jody@gnome.org)
8 * Copyright 2015 by Morten Welinder (terra@gnome.org).
9 */
11 #include <gnumeric-config.h>
12 #include "gnm-i18n.h"
13 #include "gnumeric.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"
20 #include "cell.h"
21 #include "clipboard.h"
22 #include "selection.h"
23 #include "sheet.h"
24 #include "sheet-view.h"
25 #include "sheet-merge.h"
26 #include "value.h"
27 #include "workbook.h"
28 #include "wbc-gtk.h"
29 #include "gui-util.h"
30 #include "cmd-edit.h"
31 #include "commands.h"
32 #include "ranges.h"
33 #include "parse-util.h"
34 #include "gutils.h"
35 #include "gui-util.h"
36 #include "sheet-autofill.h"
37 #include <gsf/gsf-impl-utils.h>
38 #include <gtk/gtk.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 {
49 GocItem canvas_item;
51 SheetControlGUI *scg;
52 gboolean pos_initialized;
53 GnmRange pos;
55 /* Offset of dragging cell from top left of pos */
56 int col_delta, row_delta;
58 /* Tip for movement */
59 GtkWidget *tip;
60 GnmCellPos last_tip_pos;
62 GnmItemCursorStyle style;
63 guint ant_state;
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
70 * started.
72 int base_x, base_y;
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 ) */
79 struct {
80 gint64 x1, x2, y1, y2;
81 } outline;
82 int drag_button;
83 guint drag_button_state;
85 gboolean use_color;
86 gboolean auto_fill_handle_at_top;
87 gboolean auto_fill_handle_at_left;
89 GdkRGBA color;
91 /* Style: */
92 GdkRGBA normal_color;
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;
101 enum {
102 ITEM_CURSOR_PROP_0,
103 ITEM_CURSOR_PROP_SHEET_CONTROL_GUI,
104 ITEM_CURSOR_PROP_STYLE,
105 ITEM_CURSOR_PROP_BUTTON,
106 ITEM_CURSOR_PROP_COLOR
110 static void
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;
116 struct {
117 const char *class;
118 int fore_offset, back_offset;
119 } substyles[] = {
120 { "normal",
121 G_STRUCT_OFFSET (GnmItemCursor, normal_color),
122 -1 },
123 { "ant",
124 G_STRUCT_OFFSET (GnmItemCursor, ant_color),
125 G_STRUCT_OFFSET (GnmItemCursor, ant_background_color) },
126 { "drag",
127 G_STRUCT_OFFSET (GnmItemCursor, drag_color),
128 G_STRUCT_OFFSET (GnmItemCursor, drag_background_color) },
129 { "autofill",
130 G_STRUCT_OFFSET (GnmItemCursor, autofill_color),
131 G_STRUCT_OFFSET (GnmItemCursor, autofill_background_color) }
133 unsigned ui;
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,
140 "color", &fore,
141 "background-color", &back,
142 NULL);
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.;
158 static int
159 cb_item_cursor_animation (GnmItemCursor *ic)
161 GocItem *item = GOC_ITEM (ic);
162 cairo_region_t *region;
163 cairo_rectangle_int_t rect;
164 int x0, x1, y0, y1;
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 */
172 x1--;
173 } else {
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);
177 ic->ant_state++;
178 rect.x = x0 - 1;
179 rect.y = y0 - 1;
180 rect.width = x1 - x0 + 3;
181 rect.height = y1 - y0 + 3;
182 region = cairo_region_create_rectangle (&rect);
183 rect.x += 3;
184 rect.y += 3;
185 rect.width -= 6;
186 rect.height -= 6;
187 cairo_region_xor_rectangle (region, &rect);
188 goc_canvas_invalidate_region (item->canvas, item, region);
189 cairo_region_destroy (region);
190 return TRUE;
193 static void
194 item_cursor_dispose (GObject *obj)
196 GnmItemCursor *ic = GNM_ITEM_CURSOR (obj);
198 if (ic->tip) {
199 gtk_widget_destroy (gtk_widget_get_toplevel (ic->tip));
200 ic->tip = NULL;
203 G_OBJECT_CLASS (parent_class)->dispose (obj);
206 static void
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,
219 ic);
223 static void
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);
236 static void
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;
242 int tmp;
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;
271 static void
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 */
276 GocPoint points[5];
277 int i, draw_thick, draw_stippled, draw_handle;
278 int premove = 0;
279 gboolean draw_center, draw_external, draw_internal, draw_xor;
280 double scale = item->canvas->pixels_per_unit;
281 GdkRGBA *fore = NULL, *back = NULL;
282 double phase0 = 0;
284 #if 0
285 g_printerr ("draw[%d] %lx,%lx %lx,%lx\n",
286 GNM_PANE (item->canvas)->index,
287 ic->outline.x1,
288 ic->outline.y1,
289 ic->outline.x2,
290 ic->outline.y2);
291 #endif
292 if (!goc_item_is_visible (&ic->canvas_item) || !ic->pos_initialized)
293 return;
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 */
300 x1--;
301 } else {
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)
308 return;
310 cairo_save (cr);
312 draw_external = FALSE;
313 draw_internal = FALSE;
314 draw_handle = 0;
315 draw_thick = 1;
316 draw_center = FALSE;
317 draw_stippled = 4;
318 draw_xor = TRUE;
320 switch (ic->style) {
321 case GNM_ITEM_CURSOR_AUTOFILL:
322 draw_center = TRUE;
323 draw_thick = 3;
324 draw_stippled = 1;
325 fore = &ic->autofill_color;
326 back = &ic->autofill_background_color;
327 draw_xor = FALSE;
328 break;
330 case GNM_ITEM_CURSOR_DRAG:
331 draw_center = TRUE;
332 draw_thick = 3;
333 draw_stippled = 1;
334 fore = &ic->drag_color;
335 back = &ic->drag_background_color;
336 draw_xor = FALSE;
337 break;
339 case GNM_ITEM_CURSOR_EXPR_RANGE:
340 draw_center = TRUE;
341 draw_thick = (item->canvas->last_item == item) ? 3 : 2;
342 draw_xor = FALSE;
343 break;
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;
352 /* In pane */
353 if (ic->pos.end.row <= pane->last_full.row)
354 draw_handle = 1;
355 /* In pane below */
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)
359 draw_handle = 1;
360 /* TODO : do we want to add checking for pane above ? */
361 else if (ic->pos.start.row < pane->first.row)
362 draw_handle = 0;
363 else if (ic->pos.start.row != pane->first.row)
364 draw_handle = 2;
365 else
366 draw_handle = 3;
367 break;
370 case GNM_ITEM_CURSOR_ANTED:
371 draw_center = TRUE;
372 draw_thick = 2;
373 draw_xor = FALSE;
374 fore = &ic->ant_color;
375 back = &ic->ant_background_color;
376 phase0 = (~ic->ant_state & 3) * 0.25;
377 break;
380 if (ic->use_color) {
381 fore = &ic->color;
382 back = &ic->color;
385 ic->auto_fill_handle_at_top = (draw_handle >= 2);
387 if (x0 >= x1 || y0 >= y1)
388 draw_handle = 0;
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);
396 if (draw_xor)
397 cairo_set_operator (cr, CAIRO_OPERATOR_HARD_LIGHT);
398 if (draw_external) {
399 switch (draw_handle) {
400 /* Auto handle at bottom */
401 case 1:
402 premove = AUTO_HANDLE_SPACE;
403 /* Fall through */
405 /* No auto handle */
406 case 0:
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;
417 break;
419 /* Auto handle at top */
420 case 2:
421 premove = AUTO_HANDLE_SPACE;
422 /* Fall through */
424 /* Auto handle at top of sheet */
425 case 3:
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;
436 break;
438 default:
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);
444 cairo_stroke (cr);
447 if (draw_external && draw_internal) {
448 if (draw_handle < 2) {
449 points [0].x -= 2;
450 points [1].x -= 2;
451 points [1].y += 2;
452 points [2].x += 2;
453 points [2].y += 2;
454 points [3].x += 2;
455 points [3].y -= 2;
456 points [4].y -= 2;
457 } else {
458 points [0].x -= 2;
459 points [1].x -= 2;
460 points [1].y -= 2;
461 points [2].x += 2;
462 points [2].y -= 2;
463 points [3].x += 2;
464 points [3].y += 2;
465 points [4].y += 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);
470 cairo_stroke (cr);
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);
479 cairo_fill (cr);
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);
483 cairo_fill (cr);
486 if (draw_center) {
487 double dashes[2];
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);
504 cairo_stroke (cr);
506 cairo_restore (cr);
509 gboolean
510 gnm_item_cursor_bound_set (GnmItemCursor *ic, GnmRange const *new_bound)
512 GocItem *item;
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))
517 return FALSE;
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);
527 return TRUE;
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.
538 void
539 gnm_item_cursor_reposition (GnmItemCursor *ic)
541 g_return_if_fail (GOC_IS_ITEM (ic));
542 goc_item_bounds_changed (GOC_ITEM (ic));
545 static double
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
552 * 1) when invisible
553 * 2) when animated
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)
559 return DBL_MAX;
561 *actual_item = NULL;
563 if (x < item->x0-3)
564 return DBL_MAX;
565 if (x > item->x1+3)
566 return DBL_MAX;
567 if (y < item->y0-3)
568 return DBL_MAX;
569 if (y > item->y1+3)
570 return DBL_MAX;
572 if ((x < (item->x0 + 4)) || (x > (item->x1 - 8)) ||
573 (y < (item->y0 + 4)) || (y > (item->y1 - 8))) {
574 *actual_item = item;
575 return 0.0;
577 return DBL_MAX;
580 static void
581 item_cursor_setup_auto_fill (GnmItemCursor *ic, GnmItemCursor const *parent, int x, int y)
583 Sheet const *sheet = scg_sheet (parent->scg);
584 GSList *merges;
586 ic->base_x = x;
587 ic->base_y = y;
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);
605 } else
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);
629 return FALSE;
632 static void
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;
639 else
640 cursor = GDK_ARROW;
642 gnm_widget_set_cursor_type (GTK_WIDGET (canvas), cursor);
645 static gboolean
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);
651 int style, button;
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);
657 return TRUE;
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;
666 else
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);
680 if (x < 0)
681 x = 0;
682 if (y < 0)
683 y = 0;
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) -
693 ic->pos.start.col;
694 int d_row = gnm_pane_find_row (pane, y, NULL) -
695 ic->pos.start.row;
697 if (d_col >= 0) {
698 int tmp = ic->pos.end.col - ic->pos.start.col;
699 if (d_col > tmp)
700 d_col = tmp;
701 } else
702 d_col = 0;
703 special_cursor->col_delta = d_col;
705 if (d_row >= 0) {
706 int tmp = ic->pos.end.row - ic->pos.start.row;
707 if (d_row > tmp)
708 d_row = tmp;
709 } else
710 d_row = 0;
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.
727 gdk_flush ();
728 return TRUE;
731 typedef enum {
732 ACTION_NONE = 1,
733 ACTION_MOVE_CELLS,
734 ACTION_COPY_CELLS,
735 ACTION_COPY_FORMATS,
736 ACTION_COPY_VALUES,
737 ACTION_SHIFT_DOWN_AND_COPY,
738 ACTION_SHIFT_RIGHT_AND_COPY,
739 ACTION_SHIFT_DOWN_AND_MOVE,
740 ACTION_SHIFT_RIGHT_AND_MOVE
741 } ActionType;
743 static void
744 item_cursor_do_action (GnmItemCursor *ic, ActionType action)
746 SheetView *sv;
747 Sheet *sheet;
748 WorkbookControl *wbc;
749 GnmPasteTarget pt;
751 g_return_if_fail (ic != NULL);
753 if (action == ACTION_NONE) {
754 scg_special_cursor_stop (ic->scg);
755 return;
758 sheet = scg_sheet (ic->scg);
759 sv = scg_view (ic->scg);
760 wbc = scg_wbc (ic->scg);
762 switch (action) {
763 case ACTION_COPY_CELLS:
764 if (!sv_selection_copy (sv, wbc))
765 break;
766 cmd_paste (wbc,
767 paste_target_init (&pt, sheet, &ic->pos,
768 PASTE_ALL_TYPES));
769 break;
771 case ACTION_MOVE_CELLS:
772 if (!sv_selection_cut (sv, wbc))
773 break;
774 cmd_paste (wbc,
775 paste_target_init (&pt, sheet, &ic->pos,
776 PASTE_ALL_TYPES));
777 break;
779 case ACTION_COPY_FORMATS:
780 if (!sv_selection_copy (sv, wbc))
781 break;
782 cmd_paste (wbc,
783 paste_target_init (&pt, sheet, &ic->pos,
784 PASTE_FORMATS));
785 break;
787 case ACTION_COPY_VALUES:
788 if (!sv_selection_copy (sv, wbc))
789 break;
790 cmd_paste (wbc,
791 paste_target_init (&pt, sheet, &ic->pos,
792 PASTE_AS_VALUES));
793 break;
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.");
800 break;
802 default :
803 g_warning ("Invalid Operation %d.", action);
806 scg_special_cursor_stop (ic->scg);
809 static void
810 context_menu_hander (GnmPopupMenuElement const *element,
811 gpointer ic)
813 g_return_if_fail (element != NULL);
814 item_cursor_do_action (ic, element->index);
817 static void
818 item_cursor_popup_menu (GnmItemCursor *ic, GdkEvent *event)
820 static GnmPopupMenuElement const popup_elements[] = {
821 { N_("_Move"), NULL,
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,
846 0, 0, ACTION_NONE },
848 { NULL, NULL, 0, 0, 0 }
851 gnm_create_popup_menu (popup_elements,
852 &context_menu_hander, ic,
853 0, 0, event);
856 static void
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);
866 return;
869 if (event->button.button == 3)
870 item_cursor_popup_menu (ic, event);
871 else
872 item_cursor_do_action (ic, (event->button.state & GDK_CONTROL_MASK)
873 ? ACTION_COPY_CELLS
874 : ACTION_MOVE_CELLS);
877 void
878 gnm_item_cursor_set_visibility (GnmItemCursor *ic, gboolean visible)
880 goc_item_set_visible (GOC_ITEM (ic), visible);
883 static void
884 item_cursor_tip_setlabel (GnmItemCursor *ic, char const *text)
886 if (ic->tip == NULL) {
887 GtkWidget *cw = GTK_WIDGET (GOC_ITEM (ic)->canvas);
888 int x, y;
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);
900 static gboolean
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);
906 GnmRange r;
907 Sheet *sheet = scg_sheet (pane->simple.scg);
909 r.start.col = info->col - ic->col_delta;
910 if (r.start.col < 0)
911 r.start.col = 0;
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;
916 if (r.start.row < 0)
917 r.start.row = 0;
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);
927 return FALSE;
930 static void
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),
937 canvas, x, y,
938 GNM_PANE_SLIDE_X | GNM_PANE_SLIDE_Y | GNM_PANE_SLIDE_AT_COLROW_BOUND,
939 slide_handler, ic);
940 goc_item_bounds_changed (GOC_ITEM (ic));
943 static gboolean
944 item_cursor_drag_motion (GnmItemCursor *ic, double x, double y)
946 item_cursor_handle_motion (ic, x, y, &cb_move_cursor);
947 return TRUE;
950 static void
951 limit_string_height_and_width (GString *s, size_t wmax, size_t hmax)
953 size_t l;
954 size_t p = 0;
955 for (l = 0; l < hmax; l++) {
956 size_t ll = 0;
957 size_t cut = 0;
958 while (s->str[p] != 0 && s->str[p] != '\n') {
959 if (ll == wmax)
960 cut = p;
961 ll++;
962 p += g_utf8_skip[(unsigned char)(s->str[p])];
965 if (cut) {
966 g_string_erase (s, cut, p - cut);
967 p = cut;
969 if (s->str[p] == 0)
970 return;
971 p++;
973 g_string_truncate (s, p);
977 static gboolean
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;
983 int h, w;
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);
996 else
997 r.end.row += ic->autofill_vsize * (int)(south_offset / ic->autofill_vsize);
998 if (col < r.start.col)
999 col = r.start.col;
1000 else if (col > r.end.col)
1001 col = r.end.col;
1002 } else {
1003 if (col < r.start.col)
1004 r.start.col -= ic->autofill_hsize * (int)(west_offset / ic->autofill_hsize);
1005 else
1006 r.end.col += ic->autofill_hsize * (int)(east_offset / ic->autofill_hsize);
1007 if (row < r.start.row)
1008 row = r.start.row;
1009 else if (row > r.end.row)
1010 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)
1015 return FALSE;
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"));
1027 else {
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);
1034 GString *hint;
1036 if (inverse_autofill)
1037 hint = gnm_autofill_hint
1038 (sheet, default_increment,
1039 ic->pos.end.col, ic->pos.end.row,
1040 w, h,
1041 ic->pos.start.col, ic->pos.start.row);
1042 else
1043 hint = gnm_autofill_hint
1044 (sheet, default_increment,
1045 ic->pos.start.col, ic->pos.start.row,
1046 w, h,
1047 ic->pos.end.col, ic->pos.end.row);
1049 if (hint) {
1050 limit_string_height_and_width (hint, 200, 200);
1051 item_cursor_tip_setlabel (ic, hint->str);
1052 g_string_free (hint, TRUE);
1053 } else
1054 item_cursor_tip_setlabel (ic, "");
1057 return FALSE;
1060 static gboolean
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)
1068 return FALSE;
1070 /* While editing nothing should be draggable */
1071 if (wbcg_is_editing (scg_wbcg (ic->scg)))
1072 return TRUE;
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");
1079 return FALSE;
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 */
1088 if (button > 3)
1089 return FALSE;
1091 /* If another button is already down ignore this one */
1092 if (ic->drag_button >= 0)
1093 return TRUE;
1095 if (button != 3) {
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"));
1105 else
1106 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (scg_wbcg (ic->scg)),
1107 _("Drag to move"));
1109 ic->drag_button = button;
1110 ic->drag_button_state = bevent->state;
1111 gnm_simple_canvas_grab (item);
1112 } else
1113 scg_context_menu (ic->scg, event, FALSE, FALSE);
1114 return TRUE;
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.
1122 return TRUE;
1124 default:
1125 return FALSE;
1127 return TRUE;
1130 static gboolean
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)
1144 return TRUE;
1146 ic->drag_button = -1;
1147 gnm_simple_canvas_ungrab (item);
1149 if (sheet_is_region_empty (sheet, &ic->pos))
1150 return TRUE;
1152 /* If the cell(s) immediately below the ones in the
1153 * auto-fill template are not blank then over-write
1154 * them.
1156 * Otherwise, only go as far as the next non-blank
1157 * cells.
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;
1169 int target_row;
1171 if (template_row < 0 || template_col >= gnm_sheet_get_max_cols (sheet) ||
1172 sheet_is_cell_empty (sheet, template_col,
1173 template_row)) {
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,
1179 template_row))
1180 return TRUE;
1183 if (template_col >= gnm_sheet_get_max_cols (sheet) ||
1184 sheet_is_cell_empty (sheet, template_col,
1185 template_row))
1186 return TRUE;
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)
1191 return TRUE;
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
1206 (sheet,
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;
1220 } else {
1221 int template_row = ic->pos.end.row + 1;
1222 int template_col = ic->pos.start.col - 1;
1223 int boundary_row_for_target;
1224 int target_col;
1226 if (template_col < 0 || template_row >= gnm_sheet_get_max_rows (sheet) ||
1227 sheet_is_cell_empty (sheet, template_col,
1228 template_row)) {
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,
1234 template_row))
1235 return TRUE;
1238 if (template_row >= gnm_sheet_get_max_rows (sheet) ||
1239 sheet_is_cell_empty (sheet, template_col,
1240 template_row))
1241 return TRUE;
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)
1246 return TRUE;
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
1261 (sheet,
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,
1283 FALSE);
1285 return TRUE;
1288 case GNM_ITEM_CURSOR_DRAG:
1289 return TRUE;
1291 default:
1292 return FALSE;
1294 return TRUE;
1297 static gboolean
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;
1302 ic->last_x = x;
1303 ic->last_y = y;
1304 if (ic->drag_button < 0) {
1305 item_cursor_set_cursor (item->canvas, ic, x, y);
1306 return TRUE;
1308 if (ic->style == GNM_ITEM_CURSOR_EXPR_RANGE)
1309 return FALSE;
1311 /* While editing nothing should be draggable */
1312 if (wbcg_is_editing (scg_wbcg (ic->scg)))
1313 return TRUE;
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");
1319 return FALSE;
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);
1329 return TRUE;
1331 default:
1332 return FALSE;
1336 static gboolean
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)
1344 return FALSE;
1346 /* While editing nothing should be draggable */
1347 if (wbcg_is_editing (wbcg))
1348 return TRUE;
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");
1354 return FALSE;
1356 case GNM_ITEM_CURSOR_SELECTION:
1357 if (ic->drag_button != button)
1358 return TRUE;
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),
1366 NULL);
1367 return TRUE;
1369 case GNM_ITEM_CURSOR_DRAG:
1370 if (ic->drag_button != button)
1371 return TRUE;
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),
1378 NULL);
1379 return TRUE;
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,
1397 inverse_autofill);
1399 scg_special_cursor_stop (scg);
1401 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (wbcg),
1402 NULL);
1403 return TRUE;
1405 default:
1406 return FALSE;
1410 static gboolean
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);
1421 return FALSE;
1424 static gboolean
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);
1430 return FALSE;
1433 static void
1434 item_cursor_set_property (GObject *obj, guint param_id,
1435 GValue const *value, GParamSpec *pspec)
1437 GnmItemCursor *ic = GNM_ITEM_CURSOR (obj);
1439 switch (param_id) {
1440 case ITEM_CURSOR_PROP_SHEET_CONTROL_GUI:
1441 ic->scg = g_value_get_object (value);
1442 break;
1443 case ITEM_CURSOR_PROP_STYLE:
1444 ic->style = g_value_get_int (value);
1445 break;
1446 case ITEM_CURSOR_PROP_BUTTON :
1447 ic->drag_button = g_value_get_int (value);
1448 break;
1449 case ITEM_CURSOR_PROP_COLOR:
1450 go_color_to_gdk_rgba (g_value_get_uint (value), &ic->color);
1451 ic->use_color = 1;
1456 * GnmItemCursor class initialization
1458 static void
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"),
1472 GNM_SCG_TYPE,
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",
1476 P_("Style"),
1477 P_("What type of cursor"),
1478 0, G_MAXINT, 0,
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",
1482 P_("Button"),
1483 P_("What button initiated the drag"),
1484 0, G_MAXINT, 0,
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",
1488 P_("Color"),
1489 P_("Name of the cursor's color"),
1490 0, 0xffffffff,
1491 GO_COLOR_BLACK,
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;
1507 static void
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;
1516 ic->col_delta = 0;
1517 ic->row_delta = 0;
1519 ic->tip = NULL;
1520 ic->last_tip_pos.col = -1;
1521 ic->last_tip_pos.row = -1;
1523 ic->last_x = 0;
1524 ic->last_y = 0;
1526 ic->style = GNM_ITEM_CURSOR_SELECTION;
1527 ic->ant_state = 0;
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,
1537 GOC_TYPE_ITEM)