1.12.42
[gnumeric.git] / src / item-cursor.c
blobd63c9af04264aba853876e168136f1a2f6834797
1 /*
2 * The Cursor Canvas Item: Implements a rectangular cursor
4 * Author:
5 * Miguel de Icaza (miguel@kernel.org)
6 * Jody Goldberg (jody@gnome.org)
7 * Copyright 2015 by Morten Welinder (terra@gnome.org).
8 */
10 #include <gnumeric-config.h>
11 #include <gnm-i18n.h>
12 #include <gnumeric.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>
19 #include <cell.h>
20 #include <clipboard.h>
21 #include <selection.h>
22 #include <sheet.h>
23 #include <sheet-view.h>
24 #include <sheet-merge.h>
25 #include <value.h>
26 #include <workbook.h>
27 #include <wbc-gtk.h>
28 #include <gui-util.h>
29 #include <cmd-edit.h>
30 #include <commands.h>
31 #include <ranges.h>
32 #include <parse-util.h>
33 #include <gutils.h>
34 #include <gui-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 {
47 GocItem canvas_item;
49 SheetControlGUI *scg;
50 gboolean pos_initialized;
51 GnmRange pos;
53 /* Offset of dragging cell from top left of pos */
54 int col_delta, row_delta;
56 /* Tip for movement */
57 GtkWidget *tip;
58 GnmCellPos last_tip_pos;
60 GnmItemCursorStyle style;
61 guint ant_state;
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
68 * started.
70 int base_x, base_y;
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 ) */
77 struct {
78 gint64 x1, x2, y1, y2;
79 } outline;
80 int drag_button;
81 guint drag_button_state;
83 gboolean use_color;
84 gboolean auto_fill_handle_at_top;
85 gboolean auto_fill_handle_at_left;
87 GdkRGBA color;
89 /* Style: */
90 GdkRGBA normal_color;
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;
99 enum {
100 ITEM_CURSOR_PROP_0,
101 ITEM_CURSOR_PROP_SHEET_CONTROL_GUI,
102 ITEM_CURSOR_PROP_STYLE,
103 ITEM_CURSOR_PROP_BUTTON,
104 ITEM_CURSOR_PROP_COLOR
108 static void
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;
114 struct {
115 const char *class;
116 int fore_offset, back_offset;
117 } substyles[] = {
118 { "normal",
119 G_STRUCT_OFFSET (GnmItemCursor, normal_color),
120 -1 },
121 { "ant",
122 G_STRUCT_OFFSET (GnmItemCursor, ant_color),
123 G_STRUCT_OFFSET (GnmItemCursor, ant_background_color) },
124 { "drag",
125 G_STRUCT_OFFSET (GnmItemCursor, drag_color),
126 G_STRUCT_OFFSET (GnmItemCursor, drag_background_color) },
127 { "autofill",
128 G_STRUCT_OFFSET (GnmItemCursor, autofill_color),
129 G_STRUCT_OFFSET (GnmItemCursor, autofill_background_color) }
131 unsigned ui;
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,
138 "color", &fore,
139 "background-color", &back,
140 NULL);
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.;
156 static int
157 cb_item_cursor_animation (GnmItemCursor *ic)
159 GocItem *item = GOC_ITEM (ic);
160 cairo_region_t *region;
161 cairo_rectangle_int_t rect;
162 int x0, x1, y0, y1;
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 */
170 x1--;
171 } else {
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);
175 ic->ant_state++;
176 rect.x = x0 - 1;
177 rect.y = y0 - 1;
178 rect.width = x1 - x0 + 3;
179 rect.height = y1 - y0 + 3;
180 region = cairo_region_create_rectangle (&rect);
181 rect.x += 3;
182 rect.y += 3;
183 rect.width -= 6;
184 rect.height -= 6;
185 cairo_region_xor_rectangle (region, &rect);
186 goc_canvas_invalidate_region (item->canvas, item, region);
187 cairo_region_destroy (region);
188 return TRUE;
191 static void
192 item_cursor_dispose (GObject *obj)
194 GnmItemCursor *ic = GNM_ITEM_CURSOR (obj);
196 if (ic->tip) {
197 gtk_widget_destroy (gtk_widget_get_toplevel (ic->tip));
198 ic->tip = NULL;
201 G_OBJECT_CLASS (parent_class)->dispose (obj);
204 static void
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,
217 ic);
221 static void
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);
234 static void
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;
240 int tmp;
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;
269 static void
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 */
274 GocPoint points[5];
275 int i, draw_thick, draw_stippled, draw_handle;
276 int premove = 0;
277 gboolean draw_center, draw_external, draw_internal, draw_xor;
278 double scale = item->canvas->pixels_per_unit;
279 GdkRGBA *fore = NULL, *back = NULL;
280 double phase0 = 0;
282 #if 0
283 g_printerr ("draw[%d] %lx,%lx %lx,%lx\n",
284 GNM_PANE (item->canvas)->index,
285 ic->outline.x1,
286 ic->outline.y1,
287 ic->outline.x2,
288 ic->outline.y2);
289 #endif
290 if (!goc_item_is_visible (&ic->canvas_item) || !ic->pos_initialized)
291 return;
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 */
298 x1--;
299 } else {
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)
306 return;
308 cairo_save (cr);
310 draw_external = FALSE;
311 draw_internal = FALSE;
312 draw_handle = 0;
313 draw_thick = 1;
314 draw_center = FALSE;
315 draw_stippled = 4;
316 draw_xor = TRUE;
318 switch (ic->style) {
319 case GNM_ITEM_CURSOR_AUTOFILL:
320 draw_center = TRUE;
321 draw_thick = 3;
322 draw_stippled = 1;
323 fore = &ic->autofill_color;
324 back = &ic->autofill_background_color;
325 draw_xor = FALSE;
326 break;
328 case GNM_ITEM_CURSOR_DRAG:
329 draw_center = TRUE;
330 draw_thick = 3;
331 draw_stippled = 1;
332 fore = &ic->drag_color;
333 back = &ic->drag_background_color;
334 draw_xor = FALSE;
335 break;
337 case GNM_ITEM_CURSOR_EXPR_RANGE:
338 draw_center = TRUE;
339 draw_thick = (item->canvas->last_item == item) ? 3 : 2;
340 draw_xor = FALSE;
341 break;
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;
350 /* In pane */
351 if (ic->pos.end.row <= pane->last_full.row)
352 draw_handle = 1;
353 /* In pane below */
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)
357 draw_handle = 1;
358 /* TODO : do we want to add checking for pane above ? */
359 else if (ic->pos.start.row < pane->first.row)
360 draw_handle = 0;
361 else if (ic->pos.start.row != pane->first.row)
362 draw_handle = 2;
363 else
364 draw_handle = 3;
365 break;
368 case GNM_ITEM_CURSOR_ANTED:
369 draw_center = TRUE;
370 draw_thick = 2;
371 draw_xor = FALSE;
372 fore = &ic->ant_color;
373 back = &ic->ant_background_color;
374 phase0 = (~ic->ant_state & 3) * 0.25;
375 break;
378 if (ic->use_color) {
379 fore = &ic->color;
380 back = &ic->color;
383 ic->auto_fill_handle_at_top = (draw_handle >= 2);
385 if (x0 >= x1 || y0 >= y1)
386 draw_handle = 0;
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);
394 if (draw_xor)
395 cairo_set_operator (cr, CAIRO_OPERATOR_HARD_LIGHT);
396 if (draw_external) {
397 switch (draw_handle) {
398 /* Auto handle at bottom */
399 case 1:
400 premove = AUTO_HANDLE_SPACE;
401 /* Fall through */
403 /* No auto handle */
404 case 0:
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;
415 break;
417 /* Auto handle at top */
418 case 2:
419 premove = AUTO_HANDLE_SPACE;
420 /* Fall through */
422 /* Auto handle at top of sheet */
423 case 3:
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;
434 break;
436 default:
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);
442 cairo_stroke (cr);
445 if (draw_external && draw_internal) {
446 if (draw_handle < 2) {
447 points [0].x -= 2;
448 points [1].x -= 2;
449 points [1].y += 2;
450 points [2].x += 2;
451 points [2].y += 2;
452 points [3].x += 2;
453 points [3].y -= 2;
454 points [4].y -= 2;
455 } else {
456 points [0].x -= 2;
457 points [1].x -= 2;
458 points [1].y -= 2;
459 points [2].x += 2;
460 points [2].y -= 2;
461 points [3].x += 2;
462 points [3].y += 2;
463 points [4].y += 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);
468 cairo_stroke (cr);
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);
477 cairo_fill (cr);
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);
481 cairo_fill (cr);
484 if (draw_center) {
485 double dashes[2];
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);
502 cairo_stroke (cr);
504 cairo_restore (cr);
507 gboolean
508 gnm_item_cursor_bound_set (GnmItemCursor *ic, GnmRange const *new_bound)
510 GocItem *item;
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))
515 return FALSE;
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);
525 return TRUE;
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.
536 void
537 gnm_item_cursor_reposition (GnmItemCursor *ic)
539 g_return_if_fail (GOC_IS_ITEM (ic));
540 goc_item_bounds_changed (GOC_ITEM (ic));
543 static double
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
550 * 1) when invisible
551 * 2) when animated
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)
557 return DBL_MAX;
559 *actual_item = NULL;
561 if (x < item->x0-3)
562 return DBL_MAX;
563 if (x > item->x1+3)
564 return DBL_MAX;
565 if (y < item->y0-3)
566 return DBL_MAX;
567 if (y > item->y1+3)
568 return DBL_MAX;
570 if ((x < (item->x0 + 4)) || (x > (item->x1 - 8)) ||
571 (y < (item->y0 + 4)) || (y > (item->y1 - 8))) {
572 *actual_item = item;
573 return 0.0;
575 return DBL_MAX;
578 static void
579 item_cursor_setup_auto_fill (GnmItemCursor *ic, GnmItemCursor const *parent, int x, int y)
581 Sheet const *sheet = scg_sheet (parent->scg);
582 GSList *merges;
584 ic->base_x = x;
585 ic->base_y = y;
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);
603 } else
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);
627 return FALSE;
630 static void
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;
637 else
638 cursor = GDK_ARROW;
640 gnm_widget_set_cursor_type (GTK_WIDGET (canvas), cursor);
643 static gboolean
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);
649 int style, button;
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);
655 return TRUE;
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;
664 else
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);
678 if (x < 0)
679 x = 0;
680 if (y < 0)
681 y = 0;
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) -
691 ic->pos.start.col;
692 int d_row = gnm_pane_find_row (pane, y, NULL) -
693 ic->pos.start.row;
695 if (d_col >= 0) {
696 int tmp = ic->pos.end.col - ic->pos.start.col;
697 if (d_col > tmp)
698 d_col = tmp;
699 } else
700 d_col = 0;
701 special_cursor->col_delta = d_col;
703 if (d_row >= 0) {
704 int tmp = ic->pos.end.row - ic->pos.start.row;
705 if (d_row > tmp)
706 d_row = tmp;
707 } else
708 d_row = 0;
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.
725 gdk_flush ();
726 return TRUE;
729 typedef enum {
730 ACTION_NONE = 1,
731 ACTION_MOVE_CELLS,
732 ACTION_COPY_CELLS,
733 ACTION_COPY_FORMATS,
734 ACTION_COPY_VALUES,
735 ACTION_SHIFT_DOWN_AND_COPY,
736 ACTION_SHIFT_RIGHT_AND_COPY,
737 ACTION_SHIFT_DOWN_AND_MOVE,
738 ACTION_SHIFT_RIGHT_AND_MOVE
739 } ActionType;
741 static void
742 item_cursor_do_action (GnmItemCursor *ic, ActionType action)
744 SheetView *sv;
745 Sheet *sheet;
746 WorkbookControl *wbc;
747 GnmPasteTarget pt;
749 g_return_if_fail (ic != NULL);
751 if (action == ACTION_NONE) {
752 scg_special_cursor_stop (ic->scg);
753 return;
756 sheet = scg_sheet (ic->scg);
757 sv = scg_view (ic->scg);
758 wbc = scg_wbc (ic->scg);
760 switch (action) {
761 case ACTION_COPY_CELLS:
762 if (!gnm_sheet_view_selection_copy (sv, wbc))
763 break;
764 cmd_paste (wbc,
765 paste_target_init (&pt, sheet, &ic->pos,
766 PASTE_ALL_CELL));
767 break;
769 case ACTION_MOVE_CELLS:
770 if (!gnm_sheet_view_selection_cut (sv, wbc))
771 break;
772 cmd_paste (wbc,
773 paste_target_init (&pt, sheet, &ic->pos,
774 PASTE_ALL_CELL));
775 break;
777 case ACTION_COPY_FORMATS:
778 if (!gnm_sheet_view_selection_copy (sv, wbc))
779 break;
780 cmd_paste (wbc,
781 paste_target_init (&pt, sheet, &ic->pos,
782 PASTE_FORMATS));
783 break;
785 case ACTION_COPY_VALUES:
786 if (!gnm_sheet_view_selection_copy (sv, wbc))
787 break;
788 cmd_paste (wbc,
789 paste_target_init (&pt, sheet, &ic->pos,
790 PASTE_AS_VALUES));
791 break;
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.");
798 break;
800 default:
801 g_warning ("Invalid Operation %d.", action);
804 scg_special_cursor_stop (ic->scg);
807 static void
808 context_menu_hander (GnmPopupMenuElement const *element,
809 gpointer ic)
811 g_return_if_fail (element != NULL);
812 item_cursor_do_action (ic, element->index);
815 static void
816 item_cursor_popup_menu (GnmItemCursor *ic, GdkEvent *event)
818 static GnmPopupMenuElement const popup_elements[] = {
819 { N_("_Move"), NULL,
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,
844 0, 0, ACTION_NONE },
846 { NULL, NULL, 0, 0, 0 }
849 gnm_create_popup_menu (popup_elements,
850 &context_menu_hander, ic, NULL,
851 0, 0, event);
854 static void
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);
864 return;
867 if (event->button.button == 3)
868 item_cursor_popup_menu (ic, event);
869 else
870 item_cursor_do_action (ic, (event->button.state & GDK_CONTROL_MASK)
871 ? ACTION_COPY_CELLS
872 : ACTION_MOVE_CELLS);
875 void
876 gnm_item_cursor_set_visibility (GnmItemCursor *ic, gboolean visible)
878 goc_item_set_visible (GOC_ITEM (ic), visible);
881 static void
882 item_cursor_tip_setlabel (GnmItemCursor *ic, char const *text)
884 if (ic->tip == NULL) {
885 GtkWidget *cw = GTK_WIDGET (GOC_ITEM (ic)->canvas);
886 int x, y;
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);
898 static gboolean
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);
904 GnmRange r;
905 Sheet *sheet = scg_sheet (pane->simple.scg);
907 r.start.col = info->col - ic->col_delta;
908 if (r.start.col < 0)
909 r.start.col = 0;
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;
914 if (r.start.row < 0)
915 r.start.row = 0;
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);
925 return FALSE;
928 static void
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),
935 canvas, x, y,
936 GNM_PANE_SLIDE_X | GNM_PANE_SLIDE_Y | GNM_PANE_SLIDE_AT_COLROW_BOUND,
937 slide_handler, ic);
938 goc_item_bounds_changed (GOC_ITEM (ic));
941 static gboolean
942 item_cursor_drag_motion (GnmItemCursor *ic, double x, double y)
944 item_cursor_handle_motion (ic, x, y, &cb_move_cursor);
945 return TRUE;
948 static void
949 limit_string_height_and_width (GString *s, size_t wmax, size_t hmax)
951 size_t l;
952 size_t p = 0;
953 for (l = 0; l < hmax; l++) {
954 size_t ll = 0;
955 size_t cut = 0;
956 while (s->str[p] != 0 && s->str[p] != '\n') {
957 if (ll == wmax)
958 cut = p;
959 ll++;
960 p += g_utf8_skip[(unsigned char)(s->str[p])];
963 if (cut) {
964 g_string_erase (s, cut, p - cut);
965 p = cut;
967 if (s->str[p] == 0)
968 return;
969 p++;
971 g_string_truncate (s, p);
975 static gboolean
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;
981 int h, w;
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);
994 else
995 r.end.row += ic->autofill_vsize * (int)(south_offset / ic->autofill_vsize);
996 if (col < r.start.col)
997 col = r.start.col;
998 else if (col > r.end.col)
999 col = r.end.col;
1000 } else {
1001 if (col < r.start.col)
1002 r.start.col -= ic->autofill_hsize * (int)(west_offset / ic->autofill_hsize);
1003 else
1004 r.end.col += ic->autofill_hsize * (int)(east_offset / ic->autofill_hsize);
1005 if (row < r.start.row)
1006 row = r.start.row;
1007 else if (row > r.end.row)
1008 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)
1013 return FALSE;
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"));
1025 else {
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);
1032 GString *hint;
1034 if (inverse_autofill)
1035 hint = gnm_autofill_hint
1036 (sheet, default_increment,
1037 ic->pos.end.col, ic->pos.end.row,
1038 w, h,
1039 ic->pos.start.col, ic->pos.start.row);
1040 else
1041 hint = gnm_autofill_hint
1042 (sheet, default_increment,
1043 ic->pos.start.col, ic->pos.start.row,
1044 w, h,
1045 ic->pos.end.col, ic->pos.end.row);
1047 if (hint) {
1048 limit_string_height_and_width (hint, 200, 200);
1049 item_cursor_tip_setlabel (ic, hint->str);
1050 g_string_free (hint, TRUE);
1051 } else
1052 item_cursor_tip_setlabel (ic, "");
1055 return FALSE;
1058 static gboolean
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)
1066 return FALSE;
1068 /* While editing nothing should be draggable */
1069 if (wbcg_is_editing (scg_wbcg (ic->scg)))
1070 return TRUE;
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");
1077 return FALSE;
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 */
1086 if (button > 3)
1087 return FALSE;
1089 /* If another button is already down ignore this one */
1090 if (ic->drag_button >= 0)
1091 return TRUE;
1093 if (button != 3) {
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"));
1103 else
1104 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (scg_wbcg (ic->scg)),
1105 _("Drag to move"));
1107 ic->drag_button = button;
1108 ic->drag_button_state = bevent->state;
1109 gnm_simple_canvas_grab (item);
1110 } else
1111 scg_context_menu (ic->scg, event, FALSE, FALSE);
1112 return TRUE;
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.
1120 return TRUE;
1122 default:
1123 return FALSE;
1125 return TRUE;
1128 static gboolean
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)
1142 return TRUE;
1144 ic->drag_button = -1;
1145 gnm_simple_canvas_ungrab (item);
1147 if (sheet_is_region_empty (sheet, &ic->pos))
1148 return TRUE;
1150 /* If the cell(s) immediately below the ones in the
1151 * auto-fill template are not blank then over-write
1152 * them.
1154 * Otherwise, only go as far as the next non-blank
1155 * cells.
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;
1167 int target_row;
1169 if (template_row < 0 || template_col >= gnm_sheet_get_max_cols (sheet) ||
1170 sheet_is_cell_empty (sheet, template_col,
1171 template_row)) {
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,
1177 template_row))
1178 return TRUE;
1181 if (template_col >= gnm_sheet_get_max_cols (sheet) ||
1182 sheet_is_cell_empty (sheet, template_col,
1183 template_row))
1184 return TRUE;
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)
1189 return TRUE;
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
1204 (sheet,
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;
1218 } else {
1219 int template_row = ic->pos.end.row + 1;
1220 int template_col = ic->pos.start.col - 1;
1221 int boundary_row_for_target;
1222 int target_col;
1224 if (template_col < 0 || template_row >= gnm_sheet_get_max_rows (sheet) ||
1225 sheet_is_cell_empty (sheet, template_col,
1226 template_row)) {
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,
1232 template_row))
1233 return TRUE;
1236 if (template_row >= gnm_sheet_get_max_rows (sheet) ||
1237 sheet_is_cell_empty (sheet, template_col,
1238 template_row))
1239 return TRUE;
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)
1244 return TRUE;
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
1259 (sheet,
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,
1281 FALSE);
1283 return TRUE;
1286 case GNM_ITEM_CURSOR_DRAG:
1287 return TRUE;
1289 default:
1290 return FALSE;
1292 return TRUE;
1295 static gboolean
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;
1300 ic->last_x = x;
1301 ic->last_y = y;
1302 if (ic->drag_button < 0) {
1303 item_cursor_set_cursor (item->canvas, ic, x, y);
1304 return TRUE;
1306 if (ic->style == GNM_ITEM_CURSOR_EXPR_RANGE)
1307 return FALSE;
1309 /* While editing nothing should be draggable */
1310 if (wbcg_is_editing (scg_wbcg (ic->scg)))
1311 return TRUE;
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");
1317 return FALSE;
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);
1327 return TRUE;
1329 default:
1330 return FALSE;
1334 static gboolean
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)
1342 return FALSE;
1344 /* While editing nothing should be draggable */
1345 if (wbcg_is_editing (wbcg))
1346 return TRUE;
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");
1352 return FALSE;
1354 case GNM_ITEM_CURSOR_SELECTION:
1355 if (ic->drag_button != button)
1356 return TRUE;
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),
1364 NULL);
1365 return TRUE;
1367 case GNM_ITEM_CURSOR_DRAG:
1368 if (ic->drag_button != button)
1369 return TRUE;
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),
1376 NULL);
1377 return TRUE;
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,
1395 inverse_autofill);
1397 scg_special_cursor_stop (scg);
1399 go_cmd_context_progress_message_set (GO_CMD_CONTEXT (wbcg),
1400 NULL);
1401 return TRUE;
1403 default:
1404 return FALSE;
1408 static gboolean
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);
1419 return FALSE;
1422 static gboolean
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);
1428 return FALSE;
1431 static void
1432 item_cursor_set_property (GObject *obj, guint param_id,
1433 GValue const *value, GParamSpec *pspec)
1435 GnmItemCursor *ic = GNM_ITEM_CURSOR (obj);
1437 switch (param_id) {
1438 case ITEM_CURSOR_PROP_SHEET_CONTROL_GUI:
1439 ic->scg = g_value_get_object (value);
1440 break;
1441 case ITEM_CURSOR_PROP_STYLE:
1442 ic->style = g_value_get_int (value);
1443 break;
1444 case ITEM_CURSOR_PROP_BUTTON:
1445 ic->drag_button = g_value_get_int (value);
1446 break;
1447 case ITEM_CURSOR_PROP_COLOR:
1448 go_color_to_gdk_rgba (g_value_get_uint (value), &ic->color);
1449 ic->use_color = 1;
1454 * GnmItemCursor class initialization
1456 static void
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"),
1470 GNM_SCG_TYPE,
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",
1474 P_("Style"),
1475 P_("What type of cursor"),
1476 0, G_MAXINT, 0,
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",
1480 P_("Button"),
1481 P_("What button initiated the drag"),
1482 0, G_MAXINT, 0,
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",
1486 P_("Color"),
1487 P_("Name of the cursor's color"),
1488 0, 0xffffffff,
1489 GO_COLOR_BLACK,
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;
1505 static void
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;
1514 ic->col_delta = 0;
1515 ic->row_delta = 0;
1517 ic->tip = NULL;
1518 ic->last_tip_pos.col = -1;
1519 ic->last_tip_pos.row = -1;
1521 ic->last_x = 0;
1522 ic->last_y = 0;
1524 ic->style = GNM_ITEM_CURSOR_SELECTION;
1525 ic->ant_state = 0;
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,
1535 GOC_TYPE_ITEM)