Updated Czech translation
[gnumeric.git] / src / item-grid.c
blob8ee1efca4dce76852d3b74a03abf5dcfebc697f4
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * item-grid.c : A canvas item that is responsible for drawing gridlines and
5 * cell content. One item per sheet displays all the cells.
7 * Authors:
8 * Miguel de Icaza (miguel@kernel.org)
9 * Jody Goldberg (jody@gnome.org)
13 #include <gnumeric-config.h>
14 #include "gnumeric.h"
15 #include "item-grid.h"
17 #include "gnm-pane-impl.h"
18 #include "wbc-gtk-impl.h"
19 #include "workbook-view.h"
20 #include "sheet-control-gui-priv.h"
21 #include "sheet.h"
22 #include "sheet-view.h"
23 #include "sheet-style.h"
24 #include "sheet-merge.h"
25 #include "sheet-object-impl.h"
26 #include "cell.h"
27 #include "cell-draw.h"
28 #include "cellspan.h"
29 #include "ranges.h"
30 #include "selection.h"
31 #include "parse-util.h"
32 #include "mstyle.h"
33 #include "style-conditions.h"
34 #include "gnm-style-impl.h" /* cheesy */
35 #include "position.h" /* to eval conditions */
36 #include "style-border.h"
37 #include "style-color.h"
38 #include "pattern.h"
39 #include "commands.h"
40 #include "hlink.h"
41 #include "gui-util.h"
42 #include "gnm-i18n.h"
44 #include <goffice/goffice.h>
45 #include <gtk/gtk.h>
46 #include <gsf/gsf-impl-utils.h>
47 #include <math.h>
48 #include <string.h>
49 #define GNUMERIC_ITEM "GRID"
51 #if 0
52 #define MERGE_DEBUG(range, str) do { range_dump (range, str); } while (0)
53 #else
54 #define MERGE_DEBUG(range, str)
55 #endif
57 typedef enum {
58 GNM_ITEM_GRID_NO_SELECTION,
59 GNM_ITEM_GRID_SELECTING_CELL_RANGE,
60 GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
61 } ItemGridSelectionType;
63 struct _GnmItemGrid {
64 GocItem canvas_item;
66 SheetControlGUI *scg;
68 ItemGridSelectionType selecting;
70 GnmRange bound;
72 /* information for the cursor motion handler */
73 guint cursor_timer;
74 gint64 last_x, last_y;
75 GnmHLink *cur_link; /* do not dereference, just a pointer */
76 GtkWidget *tip;
77 guint tip_timer;
79 GdkCursor *cursor_link, *cursor_cross;
81 guint32 last_click_time;
83 /* Style: */
84 GdkRGBA function_marker_color;
85 GdkRGBA function_marker_border_color;
86 int function_marker_size;
88 GdkRGBA pane_divider_color;
89 int pane_divider_width;
92 typedef GocItemClass GnmItemGridClass;
93 static GocItemClass *parent_class;
95 enum {
96 GNM_ITEM_GRID_PROP_0,
97 GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI,
98 GNM_ITEM_GRID_PROP_BOUND
101 static void
102 ig_reload_style (GnmItemGrid *ig)
104 GocItem *item = GOC_ITEM (ig);
105 GtkStyleContext *context = goc_item_get_style_context (item);
106 GtkBorder border;
107 GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
108 GnmPane *pane = GNM_PANE (item->canvas);
110 gtk_style_context_save (context);
111 gtk_style_context_add_region (context, "function-marker", 0);
112 gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL,
113 &ig->function_marker_color);
114 gtk_style_context_get_border_color (context, state,
115 &ig->function_marker_border_color);
116 gtk_style_context_restore (context);
118 gtk_style_context_save (context);
119 gtk_style_context_add_region (context, "pane-divider", 0);
120 gtk_style_context_get_color (context, GTK_STATE_FLAG_NORMAL,
121 &ig->pane_divider_color);
122 gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
123 ig->pane_divider_width = border.top; /* Hack? */
124 gtk_style_context_restore (context);
126 /* ---------------------------------------- */
128 context = gtk_widget_get_style_context (GTK_WIDGET (pane));
129 gtk_widget_style_get (GTK_WIDGET (pane),
130 "function-indicator-size",
131 &ig->function_marker_size,
132 NULL);
135 static void
136 ig_clear_hlink_tip (GnmItemGrid *ig)
138 if (ig->tip_timer != 0) {
139 g_source_remove (ig->tip_timer);
140 ig->tip_timer = 0;
143 if (ig->tip != NULL) {
144 gtk_widget_destroy (gtk_widget_get_toplevel (ig->tip));
145 ig->tip = NULL;
149 static void
150 item_grid_finalize (GObject *object)
152 GnmItemGrid *ig = GNM_ITEM_GRID (object);
154 if (ig->cursor_timer != 0) {
155 g_source_remove (ig->cursor_timer);
156 ig->cursor_timer = 0;
158 ig_clear_hlink_tip (ig);
159 ig->cur_link = NULL;
161 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
164 static gint
165 cb_cursor_motion (GnmItemGrid *ig)
167 Sheet const *sheet = scg_sheet (ig->scg);
168 GocCanvas *canvas = ig->canvas_item.canvas;
169 GnmPane *pane = GNM_PANE (canvas);
170 GdkCursor *cursor;
171 GnmCellPos pos;
172 GnmHLink *old_link;
174 pos.col = gnm_pane_find_col (pane, ig->last_x, NULL);
175 pos.row = gnm_pane_find_row (pane, ig->last_y, NULL);
177 old_link = ig->cur_link;
178 ig->cur_link = sheet_hlink_find (sheet, &pos);
179 cursor = (ig->cur_link != NULL) ? ig->cursor_link : ig->cursor_cross;
180 if (pane->mouse_cursor != cursor) {
181 gnm_pane_mouse_cursor_set (pane, cursor);
182 scg_set_display_cursor (ig->scg);
185 if (ig->cursor_timer != 0) {
186 g_source_remove (ig->cursor_timer);
187 ig->cursor_timer = 0;
190 if (old_link != ig->cur_link && ig->tip != NULL) {
191 gtk_widget_destroy (gtk_widget_get_toplevel (ig->tip));
192 ig->tip = NULL;
194 return FALSE;
197 static void
198 item_grid_realize (GocItem *item)
200 GdkDisplay *display;
201 GnmItemGrid *ig;
202 GdkPixbuf *cursor_cross_pixbuf;
204 parent_class->realize (item);
206 ig = GNM_ITEM_GRID (item);
207 ig_reload_style (ig);
209 display = gtk_widget_get_display (GTK_WIDGET (item->canvas));
210 ig->cursor_link = gdk_cursor_new_for_display (display, GDK_HAND2);
211 cursor_cross_pixbuf =
212 gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
213 "cursor_cross", 32, 0, NULL);
214 ig->cursor_cross =
215 gdk_cursor_new_from_pixbuf (display,
216 cursor_cross_pixbuf,
217 17, 17);
218 g_object_unref (cursor_cross_pixbuf);
219 cb_cursor_motion (ig);
222 static void
223 item_grid_unrealize (GocItem *item)
225 GnmItemGrid *ig = GNM_ITEM_GRID (item);
226 g_clear_object (&ig->cursor_link);
227 g_clear_object (&ig->cursor_cross);
228 parent_class->unrealize (item);
231 static void
232 item_grid_update_bounds (GocItem *item)
234 item->x0 = 0;
235 item->y0 = 0;
236 item->x1 = G_MAXINT64/2;
237 item->y1 = G_MAXINT64/2;
240 static void
241 draw_function_marker (GnmItemGrid *ig,
242 GnmCell const *cell, cairo_t *cr,
243 double x, double y, double w, double h, int const dir)
245 int size = ig->function_marker_size;
246 if (cell == NULL || !gnm_cell_has_expr (cell))
247 return;
249 cairo_save (cr);
250 cairo_new_path (cr);
251 cairo_rectangle (cr, x, y, w+1, h+1);
252 cairo_clip (cr);
253 cairo_new_path (cr);
254 if (dir > 0) {
255 cairo_move_to (cr, x, y);
256 cairo_line_to (cr, x + size, y);
257 cairo_arc (cr, x, y, size, 0., M_PI / 2.);
258 } else {
259 cairo_move_to (cr, x + w, y);
260 cairo_line_to (cr, x + w, y + size);
261 cairo_arc (cr, x + w, y, size, M_PI/2., M_PI);
263 cairo_close_path (cr);
264 gdk_cairo_set_source_rgba (cr, &ig->function_marker_color);
265 cairo_fill_preserve (cr);
266 gdk_cairo_set_source_rgba (cr, &ig->function_marker_border_color);
267 cairo_set_line_width (cr, 0.5);
268 cairo_stroke (cr);
269 cairo_restore (cr);
272 static void
273 item_grid_draw_merged_range (cairo_t *cr, GnmItemGrid *ig,
274 int start_x, int start_y,
275 GnmRange const *view, GnmRange const *range,
276 gboolean draw_selection, GtkStyleContext *ctxt)
278 int l, r, t, b, last;
279 SheetView const *sv = scg_view (ig->scg);
280 WorkbookView *wbv = sv_wbv (sv);
281 gboolean show_function_cell_markers = wbv->show_function_cell_markers;
282 gboolean show_extension_markers = wbv->show_extension_markers;
283 Sheet const *sheet = sv->sheet;
284 GnmCell const *cell = sheet_cell_get (sheet, range->start.col, range->start.row);
285 int const dir = sheet->text_is_rtl ? -1 : 1;
287 /* load style from corner which may not be visible */
288 GnmStyle const *style = sheet_style_get (sheet, range->start.col, range->start.row);
289 gboolean const is_selected = draw_selection &&
290 (sv->edit_pos.col != range->start.col ||
291 sv->edit_pos.row != range->start.row) &&
292 sv_is_full_range_selected (sv, range);
294 /* Get the coordinates of the visible region */
295 l = r = start_x;
296 if (view->start.col < range->start.col)
297 l += dir * scg_colrow_distance_get (ig->scg, TRUE,
298 view->start.col, range->start.col);
299 if (range->end.col <= (last = view->end.col))
300 last = range->end.col;
301 r += dir * scg_colrow_distance_get (ig->scg, TRUE, view->start.col, last+1);
303 t = b = start_y;
304 if (view->start.row < range->start.row)
305 t += scg_colrow_distance_get (ig->scg, FALSE,
306 view->start.row, range->start.row);
307 if (range->end.row <= (last = view->end.row))
308 last = range->end.row;
309 b += scg_colrow_distance_get (ig->scg, FALSE, view->start.row, last+1);
311 if (l == r || t == b)
312 return;
314 if (style->conditions) {
315 GnmEvalPos ep;
316 int res;
317 eval_pos_init (&ep, (Sheet *)sheet, range->start.col, range->start.row);
318 if ((res = gnm_style_conditions_eval (style->conditions, &ep)) >= 0)
319 style = g_ptr_array_index (style->cond_styles, res);
322 /* Check for background THEN selection */
323 if (gnumeric_background_set (style, cr, is_selected, ctxt) ||
324 is_selected) {
325 /* Remember X excludes the far pixels */
326 if (dir > 0)
327 cairo_rectangle (cr, l, t, r-l+1, b-t+1);
328 else
329 cairo_rectangle (cr, r, t, l-r+1, b-t+1);
330 cairo_fill (cr);
333 /* Expand the coords to include non-visible areas too. The clipped
334 * region is only necessary when drawing the background */
335 if (range->start.col < view->start.col)
336 l -= dir * scg_colrow_distance_get (ig->scg, TRUE,
337 range->start.col, view->start.col);
338 if (view->end.col < range->end.col)
339 r += dir * scg_colrow_distance_get (ig->scg, TRUE,
340 view->end.col+1, range->end.col+1);
341 if (range->start.row < view->start.row)
342 t -= scg_colrow_distance_get (ig->scg, FALSE,
343 range->start.row, view->start.row);
344 if (view->end.row < range->end.row)
345 b += scg_colrow_distance_get (ig->scg, FALSE,
346 view->end.row+1, range->end.row+1);
348 if (cell != NULL) {
349 ColRowInfo *ri = sheet_row_get (sheet, range->start.row);
351 if (ri->needs_respan)
352 row_calc_spans (ri, cell->pos.row, sheet);
354 if (dir > 0) {
355 if (show_function_cell_markers)
356 draw_function_marker (ig, cell, cr, l, t,
357 r - l, b - t, dir);
358 cell_draw (cell, cr,
359 l, t, r - l, b - t, -1,
360 show_extension_markers);
361 } else {
362 if (show_function_cell_markers)
363 draw_function_marker (ig, cell, cr, r, t,
364 l - r, b - t, dir);
365 cell_draw (cell, cr,
366 r, t, l - r, b - t, -1,
367 show_extension_markers);
370 if (dir > 0)
371 gnm_style_border_draw_diag (style, cr, l, t, r, b);
372 else
373 gnm_style_border_draw_diag (style, cr, r, t, l, b);
376 static void
377 item_grid_draw_background (cairo_t *cr, GnmItemGrid *ig,
378 GnmStyle const *style,
379 int col, int row, int x, int y, int w, int h,
380 gboolean draw_selection, GtkStyleContext *ctxt)
382 SheetView const *sv = scg_view (ig->scg);
383 gboolean const is_selected = draw_selection &&
384 (sv->edit_pos.col != col || sv->edit_pos.row != row) &&
385 sv_is_pos_selected (sv, col, row);
386 gboolean const has_back =
387 gnumeric_background_set (style, cr, is_selected, ctxt);
389 #if DEBUG_SELECTION_PAINT
390 if (is_selected) {
391 g_printerr ("x = %d, w = %d\n", x, w+1);
393 #endif
394 if (has_back || is_selected) {
395 /* Fill the entire cell (API excludes far pixel) */
396 cairo_rectangle (cr, x, y, w+1, h+1);
397 cairo_fill (cr);
400 gnm_style_border_draw_diag (style, cr, x, y, x+w, y+h);
403 static gint
404 merged_col_cmp (GnmRange const *a, GnmRange const *b)
406 return a->start.col - b->start.col;
409 static void
410 ig_cairo_draw_bound (GnmItemGrid *ig, cairo_t* cr, int x0, int y0, int x1, int y1)
412 double width = ig->pane_divider_width;
413 cairo_set_line_width (cr, width);
414 cairo_set_dash (cr, NULL, 0, 0.);
415 cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
416 cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
417 gdk_cairo_set_source_rgba (cr, &ig->pane_divider_color);
418 cairo_move_to (cr, x0 - width / 2, y0 - width / 2);
419 cairo_line_to (cr, x1 - width / 2, y1 - width / 2);
420 cairo_stroke (cr);
423 static gboolean
424 item_grid_draw_region (GocItem const *item, cairo_t *cr,
425 double x_0, double y_0, double x_1, double y_1)
427 GocCanvas *canvas = item->canvas;
428 double scale = canvas->pixels_per_unit;
429 gint64 x0 = x_0 * scale, y0 = y_0 * scale, x1 = x_1 * scale, y1 = y_1 * scale;
430 gint width = x1 - x0;
431 gint height = y1 - y0;
432 GnmPane *pane = GNM_PANE (canvas);
433 Sheet const *sheet = scg_sheet (pane->simple.scg);
434 WBCGtk *wbcg = scg_wbcg (pane->simple.scg);
435 GnmCell const * const edit_cell = wbcg->editing_cell;
436 GnmItemGrid *ig = GNM_ITEM_GRID (item);
437 ColRowInfo const *ri = NULL, *next_ri = NULL;
438 int const dir = sheet->text_is_rtl ? -1 : 1;
439 SheetView const *sv = scg_view (ig->scg);
440 WorkbookView *wbv = sv_wbv (sv);
441 gboolean show_function_cell_markers = wbv->show_function_cell_markers;
442 gboolean show_extension_markers = wbv->show_extension_markers;
443 /* we use the selected background color from an entry for selected cells */
444 GtkWidget *entry = gtk_entry_new ();
445 GtkStyleContext *ctxt = gtk_widget_get_style_context (entry);
447 /* To ensure that far and near borders get drawn we pretend to draw +-2
448 * pixels around the target area which would include the surrounding
449 * borders if necessary */
450 /* TODO : there is an opportunity to speed up the redraw loop by only
451 * painting the borders of the edges and not the content.
452 * However, that feels like more hassle that it is worth. Look into this someday.
454 int x;
455 gint64 y, start_x, offset;
456 int col, row, n, start_col, end_col;
457 int start_row = gnm_pane_find_row (pane, y0-2, &y);
458 int end_row = gnm_pane_find_row (pane, y1+2, NULL);
459 gint64 const start_y = y - canvas->scroll_y1 * scale;
461 GnmStyleRow sr, next_sr;
462 GnmStyle const **styles;
463 GnmBorder const **borders, **prev_vert;
464 GnmBorder const *none =
465 sheet->hide_grid ? NULL : gnm_style_border_none ();
467 GnmRange view;
468 GSList *merged_active, *merged_active_seen,
469 *merged_used, *merged_unused, *ptr, **lag;
471 int *colwidths = NULL;
473 gboolean const draw_selection =
474 ig->scg->selected_objects == NULL &&
475 wbcg->new_object == NULL;
477 start_col = gnm_pane_find_col (pane, x0-2, &start_x);
478 end_col = gnm_pane_find_col (pane, x1+2, NULL);
480 g_return_val_if_fail (start_col <= end_col, TRUE);
482 #if 0
483 g_printerr ("%s:", cell_coord_name (start_col, start_row));
484 g_printerr ("%s <= %ld vs ???", cell_coord_name(end_col, end_row), (long)y);
485 g_printerr (" [%s]\n", cell_coord_name (ig->bound.end.col, ig->bound.end.row));
487 #endif
489 /* clip to bounds */
490 if (end_col > ig->bound.end.col)
491 end_col = ig->bound.end.col;
492 if (end_row > ig->bound.end.row)
493 end_row = ig->bound.end.row;
495 /* Skip any hidden cols/rows at the start */
496 for (; start_col <= end_col ; ++start_col) {
497 ri = sheet_col_get_info (sheet, start_col);
498 if (ri->visible)
499 break;
501 for (; start_row <= end_row ; ++start_row) {
502 ri = sheet_row_get_info (sheet, start_row);
503 if (ri->visible)
504 break;
507 /* if everything is hidden no need to draw */
508 if (end_col < ig->bound.start.col || start_col > ig->bound.end.col ||
509 end_row < ig->bound.start.row || start_row > ig->bound.end.row)
510 return TRUE;
512 /* Respan all rows that need it. */
513 for (row = start_row; row <= end_row; row++) {
514 ColRowInfo const *ri = sheet_row_get_info (sheet, row);
515 if (ri->visible && ri->needs_respan)
516 row_calc_spans ((ColRowInfo *)ri, row, sheet);
519 sheet_style_update_grid_color (sheet);
521 /* Fill entire region with default background (even past far edge) */
522 cairo_save (cr);
523 if (canvas->direction == GOC_DIRECTION_LTR)
524 gtk_render_background (goc_item_get_style_context (item),
526 x0 - canvas->scroll_x1 * scale,
527 y0 - canvas->scroll_y1 * scale,
528 width, height);
529 else
530 gtk_render_background (goc_item_get_style_context (item),
532 canvas->width - x0 + canvas->scroll_x1 * scale - width,
533 y0 - canvas->scroll_y1 * scale,
534 width, height);
535 cairo_restore (cr);
537 /* Get ordered list of merged regions */
538 merged_active = merged_active_seen = merged_used = NULL;
539 merged_unused = gnm_sheet_merge_get_overlap (sheet,
540 range_init (&view, start_col, start_row, end_col, end_row));
543 * allocate a single blob of memory for all 8 arrays of pointers.
544 * - 6 arrays of n GnmBorder const *
545 * - 2 arrays of n GnmStyle const *
547 * then alias the arrays for easy access so that array [col] is valid
548 * for all elements start_col-1 .. end_col+1 inclusive.
549 * Note that this means that in some cases array [-1] is legal.
551 n = end_col - start_col + 3; /* 1 before, 1 after, 1 fencepost */
552 style_row_init (&prev_vert, &sr, &next_sr, start_col, end_col,
553 g_alloca (n * 8 * sizeof (gpointer)), sheet->hide_grid);
555 /* load up the styles for the first row */
556 next_sr.row = sr.row = row = start_row;
557 sheet_style_get_row (sheet, &sr);
559 /* Collect the column widths */
560 colwidths = g_alloca (n * sizeof (int));
561 colwidths -= start_col;
562 for (col = start_col; col <= end_col; col++) {
563 ColRowInfo const *ci = sheet_col_get_info (sheet, col);
564 colwidths[col] = ci->visible ? ci->size_pixels : -1;
567 goc_canvas_c2w (canvas, start_x / scale, 0, &x, NULL);
568 start_x = x;
569 for (y = start_y; row <= end_row; row = sr.row = next_sr.row, ri = next_ri) {
570 /* Restore the set of ranges seen, but still active.
571 * Reinverting list to maintain the original order */
572 g_return_val_if_fail (merged_active == NULL, TRUE);
574 #if DEBUG_SELECTION_PAINT
575 g_printerr ("row = %d (startcol = %d)\n", row, start_col);
576 #endif
577 while (merged_active_seen != NULL) {
578 GSList *tmp = merged_active_seen->next;
579 merged_active_seen->next = merged_active;
580 merged_active = merged_active_seen;
581 merged_active_seen = tmp;
582 MERGE_DEBUG (merged_active->data, " : seen -> active\n");
585 /* find the next visible row */
586 while (1) {
587 ++next_sr.row;
588 if (next_sr.row <= end_row) {
589 next_ri = sheet_row_get_info (sheet, next_sr.row);
590 if (next_ri->visible) {
591 sheet_style_get_row (sheet, &next_sr);
592 break;
594 } else {
595 for (col = start_col ; col <= end_col; ++col)
596 next_sr.vertical [col] =
597 next_sr.bottom [col] = none;
598 break;
602 /* look for merges that start on this row, on the first painted row
603 * also check for merges that start above. */
604 view.start.row = row;
605 lag = &merged_unused;
606 for (ptr = merged_unused; ptr != NULL; ) {
607 GnmRange * const r = ptr->data;
609 if (r->start.row <= row) {
610 GSList *tmp = ptr;
611 ptr = *lag = tmp->next;
612 if (r->end.row < row) {
613 tmp->next = merged_used;
614 merged_used = tmp;
615 MERGE_DEBUG (r, " : unused -> used\n");
616 } else {
617 ColRowInfo const *ci =
618 sheet_col_get_info (sheet, r->start.col);
619 g_slist_free_1 (tmp);
620 merged_active = g_slist_insert_sorted (merged_active, r,
621 (GCompareFunc)merged_col_cmp);
622 MERGE_DEBUG (r, " : unused -> active\n");
624 if (ci->visible)
625 item_grid_draw_merged_range (cr, ig,
626 start_x, y, &view, r,
627 draw_selection,
628 ctxt);
630 } else {
631 lag = &(ptr->next);
632 ptr = ptr->next;
636 for (col = start_col, x = start_x; col <= end_col ; col++) {
637 GnmStyle const *style;
638 CellSpanInfo const *span;
639 ColRowInfo const *ci = sheet_col_get_info (sheet, col);
641 #if DEBUG_SELECTION_PAINT
642 g_printerr ("col [%d] = %d\n", col, x);
643 #endif
644 if (!ci->visible) {
645 if (merged_active != NULL) {
646 GnmRange const *r = merged_active->data;
647 if (r->end.col == col) {
648 ptr = merged_active;
649 merged_active = merged_active->next;
650 if (r->end.row <= row) {
651 ptr->next = merged_used;
652 merged_used = ptr;
653 MERGE_DEBUG (r, " : active2 -> used\n");
654 } else {
655 ptr->next = merged_active_seen;
656 merged_active_seen = ptr;
657 MERGE_DEBUG (r, " : active2 -> seen\n");
661 continue;
664 /* Skip any merged regions */
665 if (merged_active != NULL) {
666 GnmRange const *r = merged_active->data;
667 if (r->start.col <= col) {
668 gboolean clear_top, clear_bottom = FALSE;
669 int i, first = r->start.col;
670 int last = r->end.col;
672 ptr = merged_active;
673 merged_active = merged_active->next;
674 if (r->end.row <= row) {
675 ptr->next = merged_used;
676 merged_used = ptr;
677 MERGE_DEBUG (r, " : active -> used\n");
679 /* in case something managed the bottom of a merge */
680 if (r->end.row < row)
681 goto plain_draw;
682 } else {
683 ptr->next = merged_active_seen;
684 merged_active_seen = ptr;
685 MERGE_DEBUG (r, " : active -> seen\n");
686 if (next_sr.row <= r->end.row)
687 clear_bottom = TRUE;
690 x += dir * scg_colrow_distance_get (
691 pane->simple.scg, TRUE, col, last+1);
692 col = last;
694 if (first < start_col) {
695 first = start_col;
696 sr.vertical [first] = NULL;
698 if (last > end_col) {
699 last = end_col;
700 sr.vertical [last+1] = NULL;
702 clear_top = (r->start.row != row);
704 /* Clear the borders */
705 for (i = first ; i <= last ; i++) {
706 if (clear_top)
707 sr.top [i] = NULL;
708 if (clear_bottom)
709 sr.bottom [i] = NULL;
710 if (i > first)
711 sr.vertical [i] = NULL;
713 continue;
717 plain_draw : /* a quick hack to deal with 142267 */
718 if (dir < 0)
719 x -= ci->size_pixels;
720 style = sr.styles [col];
721 item_grid_draw_background (cr, ig,
722 style, col, row, x, y,
723 ci->size_pixels, ri->size_pixels,
724 draw_selection, ctxt);
727 /* Is this part of a span?
728 * 1) There are cells allocated in the row
729 * (indicated by ri->spans != NULL)
730 * 2) Look in the rows hash table to see if
731 * there is a span descriptor.
733 if (NULL == ri->spans || NULL == (span = row_span_get (ri, col))) {
735 /* If it is being edited pretend it is empty to
736 * avoid problems with long cells'
737 * contents extending past the edge of the edit
738 * box. Ignore blanks too.
740 GnmCell const *cell = sheet_cell_get (sheet, col, row);
741 if (!gnm_cell_is_empty (cell) && cell != edit_cell) {
742 if (show_function_cell_markers)
743 draw_function_marker (ig, cell, cr, x, y,
744 ci->size_pixels,
745 ri->size_pixels,
746 dir);
747 cell_draw (cell, cr,
748 x, y, ci->size_pixels,
749 ri->size_pixels, -1,
750 show_extension_markers);
752 /* Only draw spaning cells after all the backgrounds
753 * that we are going to draw have been drawn. No need
754 * to draw the edit cell, or blanks. */
755 } else if (edit_cell != span->cell &&
756 (col == span->right || col == end_col)) {
757 GnmCell const *cell = span->cell;
758 int const start_span_col = span->left;
759 int const end_span_col = span->right;
760 int real_x = x;
761 ColRowInfo const *cell_col =
762 sheet_col_get_info (sheet, cell->pos.col);
763 int center_offset = cell_col->size_pixels/2;
764 int tmp_width = ci->size_pixels;
766 if (col != cell->pos.col)
767 style = sheet_style_get (sheet,
768 cell->pos.col, row);
770 /* x, y are relative to this cell origin, but the cell
771 * might be using columns to the left (if it is set to right
772 * justify or center justify) compute the pixel difference */
773 if (dir > 0 && start_span_col != cell->pos.col)
774 center_offset += scg_colrow_distance_get (
775 pane->simple.scg, TRUE,
776 start_span_col, cell->pos.col);
777 else if (dir < 0 && end_span_col != cell->pos.col)
778 center_offset += scg_colrow_distance_get (
779 pane->simple.scg, TRUE,
780 cell->pos.col, end_span_col);
782 if (start_span_col != col) {
783 offset = scg_colrow_distance_get (
784 pane->simple.scg, TRUE,
785 start_span_col, col);
786 tmp_width += offset;
787 if (dir > 0)
788 real_x -= offset;
789 sr.vertical [col] = NULL;
791 if (end_span_col != col) {
792 offset = scg_colrow_distance_get (
793 pane->simple.scg, TRUE,
794 col+1, end_span_col + 1);
795 tmp_width += offset;
796 if (dir < 0)
797 real_x -= offset;
800 if (show_function_cell_markers)
801 draw_function_marker (ig, cell, cr, real_x, y,
802 tmp_width,
803 ri->size_pixels, dir);
804 cell_draw (cell, cr,
805 real_x, y, tmp_width,
806 ri->size_pixels, center_offset,
807 show_extension_markers);
809 } else if (col != span->left)
810 sr.vertical [col] = NULL;
812 if (dir > 0)
813 x += ci->size_pixels;
815 gnm_style_borders_row_draw (prev_vert, &sr,
816 cr, start_x, y, y+ri->size_pixels,
817 colwidths, TRUE, dir);
819 /* In case there were hidden merges that trailed off the end */
820 while (merged_active != NULL) {
821 GnmRange const *r = merged_active->data;
822 ptr = merged_active;
823 merged_active = merged_active->next;
824 if (r->end.row <= row) {
825 ptr->next = merged_used;
826 merged_used = ptr;
827 MERGE_DEBUG (r, " : active3 -> used\n");
828 } else {
829 ptr->next = merged_active_seen;
830 merged_active_seen = ptr;
831 MERGE_DEBUG (r, " : active3 -> seen\n");
835 /* roll the pointers */
836 borders = prev_vert; prev_vert = sr.vertical;
837 sr.vertical = next_sr.vertical; next_sr.vertical = borders;
838 borders = sr.top; sr.top = sr.bottom;
839 sr.bottom = next_sr.top = next_sr.bottom; next_sr.bottom = borders;
840 styles = sr.styles; sr.styles = next_sr.styles; next_sr.styles = styles;
842 y += ri->size_pixels;
845 if (ig->bound.start.row > 0 && start_y < 1)
846 ig_cairo_draw_bound (ig, cr, start_x, 1, x, 1);
847 if (ig->bound.start.col > 0) {
848 if (canvas->direction == GOC_DIRECTION_RTL && start_x >= goc_canvas_get_width (canvas)) {
849 x = goc_canvas_get_width (canvas);
850 ig_cairo_draw_bound (ig, cr, x, start_y, x, y);
851 } else if (canvas->direction == GOC_DIRECTION_LTR && start_x < 1)
852 ig_cairo_draw_bound (ig, cr, 1, start_y, 1, y);
855 g_object_ref_sink (entry);
856 g_object_unref (entry);
858 g_slist_free (merged_used); /* merges with bottom in view */
859 g_slist_free (merged_active_seen); /* merges with bottom the view */
860 g_slist_free (merged_unused); /* merges in hidden rows */
861 g_return_val_if_fail (merged_active == NULL, TRUE);
862 return TRUE;
865 static double
866 item_grid_distance (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y,
867 GocItem **actual_item)
869 *actual_item = item;
870 return 0.0;
873 /***********************************************************************/
875 static gboolean
876 ig_obj_create_begin (GnmItemGrid *ig, int button, gint64 x, gint64 y)
878 GnmPane *pane = GNM_PANE (GOC_ITEM (ig)->canvas);
879 SheetObject *so = ig->scg->wbcg->new_object;
880 SheetObjectAnchor anchor;
881 double coords[4];
883 g_return_val_if_fail (ig->scg->selected_objects == NULL, TRUE);
884 g_return_val_if_fail (so != NULL, TRUE);
886 coords[0] = coords[2] = x;
887 coords[1] = coords[3] = y;
888 sheet_object_anchor_init (&anchor, NULL, NULL, GOD_ANCHOR_DIR_DOWN_RIGHT);
889 scg_object_coords_to_anchor (ig->scg, coords, &anchor);
890 sheet_object_set_anchor (so, &anchor);
891 sheet_object_set_sheet (so, scg_sheet (ig->scg));
892 scg_object_select (ig->scg, so);
893 gnm_pane_object_start_resize (pane, button, x, y, so, 7, TRUE);
895 return TRUE;
898 /***************************************************************************/
900 static int
901 item_grid_button_pressed (GocItem *item, int button, double x_, double y_)
903 GnmItemGrid *ig = GNM_ITEM_GRID (item);
904 GocCanvas *canvas = item->canvas;
905 GnmPane *pane = GNM_PANE (canvas);
906 SheetControlGUI *scg = ig->scg;
907 WBCGtk *wbcg = scg_wbcg (scg);
908 SheetControl *sc = (SheetControl *)scg;
909 SheetView *sv = sc_view (sc);
910 Sheet *sheet = sv_sheet (sv);
911 GnmCellPos pos;
912 gboolean edit_showed_dialog;
913 gboolean already_selected;
914 GdkEvent *event = goc_canvas_get_cur_event (item->canvas);
915 gint64 x = x_ * canvas->pixels_per_unit, y = y_ * canvas->pixels_per_unit;
917 gnm_pane_slide_stop (pane);
919 pos.col = gnm_pane_find_col (pane, x, NULL);
920 pos.row = gnm_pane_find_row (pane, y, NULL);
922 /* GnmRange check first */
923 if (pos.col >= gnm_sheet_get_max_cols (sheet))
924 return TRUE;
925 if (pos.row >= gnm_sheet_get_max_rows (sheet))
926 return TRUE;
928 /* A new object is ready to be realized and inserted */
929 if (wbcg->new_object != NULL)
930 return ig_obj_create_begin (ig, button, x, y);
932 /* If we are not configuring an object then clicking on the sheet
933 * ends the edit. */
934 if (scg->selected_objects == NULL)
935 wbcg_focus_cur_scg (wbcg);
936 else if (wbc_gtk_get_guru (wbcg) == NULL)
937 scg_mode_edit (scg);
939 /* If we were already selecting a range of cells for a formula,
940 * reset the location to a new place, or extend the selection.
942 if (button == 1 && scg->rangesel.active) {
943 ig->selecting = GNM_ITEM_GRID_SELECTING_FORMULA_RANGE;
944 if (event->button.state & GDK_SHIFT_MASK)
945 scg_rangesel_extend_to (scg, pos.col, pos.row);
946 else
947 scg_rangesel_bound (scg, pos.col, pos.row, pos.col, pos.row);
948 gnm_pane_slide_init (pane);
949 gnm_simple_canvas_grab (item,
950 GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
951 NULL, gdk_event_get_time (event));
952 return TRUE;
955 /* If the user is editing a formula (wbcg_rangesel_possible) then we
956 * enable the dynamic cell selection mode.
958 if (button == 1 && wbcg_rangesel_possible (wbcg)) {
959 scg_rangesel_start (scg, pos.col, pos.row, pos.col, pos.row);
960 ig->selecting = GNM_ITEM_GRID_SELECTING_FORMULA_RANGE;
961 gnm_pane_slide_init (pane);
962 gnm_simple_canvas_grab (item,
963 GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
964 NULL, gdk_event_get_time (event));
965 return TRUE;
968 /* While a guru is up ignore clicks */
969 if (wbc_gtk_get_guru (wbcg) != NULL)
970 return TRUE;
972 /* This was a regular click on a cell on the spreadsheet. Select it.
973 * but only if the entered expression is valid */
974 if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, &edit_showed_dialog))
975 return TRUE;
977 if (button == 1 && !sheet_selection_is_allowed (sheet, &pos))
978 return TRUE;
980 /* Button == 1 is used to trigger hyperlinks (and possibly similar */
981 /* special cases. Otherwise button == 2 should behave exactly like */
982 /* button == 1. See bug #700792 */
984 /* buttons 1 and 2 will always change the selection, the other buttons will
985 * only effect things if the target is not already selected. */
986 already_selected = sv_is_pos_selected (sv, pos.col, pos.row);
987 if (button == 1 || button == 2 || !already_selected) {
988 if (!(event->button.state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)))
989 sv_selection_reset (sv);
991 if ((event->button.button != 1 && event->button.button != 2)
992 || !(event->button.state & GDK_SHIFT_MASK) ||
993 sv->selections == NULL) {
994 sv_selection_add_pos (sv, pos.col, pos.row,
995 (already_selected && (event->button.state & GDK_CONTROL_MASK)) ?
996 GNM_SELECTION_MODE_REMOVE :
997 GNM_SELECTION_MODE_ADD);
998 sv_make_cell_visible (sv, pos.col, pos.row, FALSE);
999 } else sv_selection_extend_to (sv, pos.col, pos.row);
1000 sheet_update (sheet);
1003 if (edit_showed_dialog)
1004 return TRUE; /* we already ignored the button release */
1006 switch (button) {
1007 case 1:
1008 case 2: {
1009 guint32 double_click_time;
1012 * If the second click is on a different cell than the
1013 * first one this cannot be a double-click
1015 if (already_selected) {
1016 g_object_get (gtk_widget_get_settings (GTK_WIDGET (canvas)),
1017 "gtk-double-click-time", &double_click_time,
1018 NULL);
1020 if ((ig->last_click_time + double_click_time) > gdk_event_get_time (event) &&
1021 wbcg_edit_start (wbcg, FALSE, FALSE)) {
1022 break;
1026 ig->last_click_time = gdk_event_get_time (event);
1027 ig->selecting = GNM_ITEM_GRID_SELECTING_CELL_RANGE;
1028 gnm_pane_slide_init (pane);
1029 gnm_simple_canvas_grab (item,
1030 GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
1031 NULL, gdk_event_get_time (event));
1032 break;
1035 case 3: scg_context_menu (scg, event, FALSE, FALSE);
1036 break;
1037 default :
1038 break;
1041 return TRUE;
1045 * Handle the selection
1048 static gboolean
1049 cb_extend_cell_range (GnmPane *pane, GnmPaneSlideInfo const *info)
1051 sv_selection_extend_to (scg_view (pane->simple.scg),
1052 info->col, info->row);
1053 return TRUE;
1056 static gboolean
1057 cb_extend_expr_range (GnmPane *pane, GnmPaneSlideInfo const *info)
1059 scg_rangesel_extend_to (pane->simple.scg, info->col, info->row);
1060 return TRUE;
1063 static gint
1064 cb_cursor_come_to_rest (GnmItemGrid *ig)
1066 Sheet const *sheet = scg_sheet (ig->scg);
1067 GocCanvas *canvas = ig->canvas_item.canvas;
1068 GnmPane *pane = GNM_PANE (canvas);
1069 GnmHLink *link;
1070 gint64 x, y;
1071 GnmCellPos pos;
1072 char const *tiptext;
1074 /* Be anal and look it up in case something has destroyed the link
1075 * since the last motion */
1076 x = ig->last_x;
1077 y = ig->last_y;
1078 pos.col = gnm_pane_find_col (pane, x, NULL);
1079 pos.row = gnm_pane_find_row (pane, y, NULL);
1081 link = sheet_hlink_find (sheet, &pos);
1082 if (link != NULL && (tiptext = gnm_hlink_get_tip (link)) != NULL) {
1083 g_return_val_if_fail (link == ig->cur_link, FALSE);
1085 if (ig->tip == NULL && strlen (tiptext) > 0) {
1086 GtkWidget *cw = GTK_WIDGET (canvas);
1087 int wx, wy;
1089 gnm_canvas_get_position (canvas, &wx, &wy,
1090 ig->last_x, ig->last_y);
1091 ig->tip = gnumeric_create_tooltip (cw);
1092 gtk_label_set_text (GTK_LABEL (ig->tip), tiptext);
1093 /* moving the tip window some pixels from wx,wy in order to
1094 * avoid a leave_notify event that would destroy the tip.
1095 * see #706659 */
1096 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (ig->tip)),
1097 wx + 10, wy + 10);
1098 gtk_widget_show_all (gtk_widget_get_toplevel (ig->tip));
1102 ig->tip_timer = 0;
1103 return FALSE;
1106 static gboolean
1107 item_grid_motion (GocItem *item, double x_, double y_)
1109 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1110 GocCanvas *canvas = item->canvas;
1111 GnmPane *pane = GNM_PANE (canvas);
1112 GnmPaneSlideHandler slide_handler = NULL;
1113 gint64 x = x_ * canvas->pixels_per_unit, y = y_ * canvas->pixels_per_unit;
1114 switch (ig->selecting) {
1115 case GNM_ITEM_GRID_NO_SELECTION:
1116 if (ig->cursor_timer == 0)
1117 ig->cursor_timer = g_timeout_add (100,
1118 (GSourceFunc)cb_cursor_motion, ig);
1119 if (ig->tip_timer != 0)
1120 g_source_remove (ig->tip_timer);
1121 ig->tip_timer = g_timeout_add (500,
1122 (GSourceFunc)cb_cursor_come_to_rest, ig);
1123 ig->last_x = x;
1124 ig->last_y = y;
1125 return TRUE;
1126 case GNM_ITEM_GRID_SELECTING_CELL_RANGE :
1127 slide_handler = &cb_extend_cell_range;
1128 break;
1129 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE :
1130 slide_handler = &cb_extend_expr_range;
1131 break;
1132 default:
1133 g_assert_not_reached ();
1136 gnm_pane_handle_motion (pane, canvas, x, y,
1137 GNM_PANE_SLIDE_X | GNM_PANE_SLIDE_Y |
1138 GNM_PANE_SLIDE_AT_COLROW_BOUND,
1139 slide_handler, NULL);
1140 return TRUE;
1143 static gboolean
1144 item_grid_button_released (GocItem *item, int button, G_GNUC_UNUSED double x_, G_GNUC_UNUSED double y_)
1146 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1147 GnmPane *pane = GNM_PANE (item->canvas);
1148 SheetControlGUI *scg = ig->scg;
1149 Sheet *sheet = scg_sheet (scg);
1150 ItemGridSelectionType selecting = ig->selecting;
1151 GdkEvent *event = goc_canvas_get_cur_event (item->canvas);
1153 if (button != 1 && button != 2)
1154 return FALSE;
1156 gnm_pane_slide_stop (pane);
1158 switch (selecting) {
1159 case GNM_ITEM_GRID_NO_SELECTION:
1160 return TRUE;
1162 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE :
1163 /* Removal of this code (2 lines) */
1164 /* should fix http://bugzilla.gnome.org/show_bug.cgi?id=63485 */
1165 /* sheet_make_cell_visible (sheet, */
1166 /* sheet->edit_pos.col, sheet->edit_pos.row, FALSE); */
1167 /* Fall through */
1168 case GNM_ITEM_GRID_SELECTING_CELL_RANGE :
1169 sv_selection_simplify (scg_view (scg));
1170 wb_view_selection_desc (
1171 wb_control_view (scg_wbc (scg)), TRUE, NULL);
1172 break;
1174 default:
1175 g_assert_not_reached ();
1178 ig->selecting = GNM_ITEM_GRID_NO_SELECTION;
1179 gnm_simple_canvas_ungrab (item, gdk_event_get_time (event));
1181 if (selecting == GNM_ITEM_GRID_SELECTING_FORMULA_RANGE)
1182 gnm_expr_entry_signal_update (
1183 wbcg_get_entry_logical (scg_wbcg (scg)), TRUE);
1185 if (selecting == GNM_ITEM_GRID_SELECTING_CELL_RANGE && button == 1) {
1186 GnmCellPos const *pos = sv_is_singleton_selected (scg_view (scg));
1187 if (pos != NULL) {
1188 GnmHLink *link;
1189 /* check for hyper links */
1190 link = sheet_hlink_find (sheet, pos);
1191 if (link != NULL)
1192 gnm_hlink_activate (link, scg_wbcg (scg));
1195 return TRUE;
1198 static gboolean
1199 item_grid_enter_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
1201 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1202 scg_set_display_cursor (ig->scg);
1203 return TRUE;
1206 static gboolean
1207 item_grid_leave_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
1209 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1210 ig_clear_hlink_tip (ig);
1211 if (ig->cursor_timer != 0) {
1212 g_source_remove (ig->cursor_timer);
1213 ig->cursor_timer = 0;
1215 return TRUE;
1218 static void
1219 gnm_item_grid_init (GnmItemGrid *ig)
1221 GocItem *item = GOC_ITEM (ig);
1223 item->x0 = 0;
1224 item->y0 = 0;
1225 item->x1 = 0;
1226 item->y1 = 0;
1228 ig->selecting = GNM_ITEM_GRID_NO_SELECTION;
1229 /* We need something at least as big as any sheet. */
1230 ig->bound.start.col = ig->bound.start.row = 0;
1231 ig->bound.end.col = GNM_MAX_COLS - 1;
1232 ig->bound.end.row = GNM_MAX_ROWS - 1;
1233 ig->cursor_timer = 0;
1234 ig->cur_link = NULL;
1235 ig->tip_timer = 0;
1236 ig->tip = NULL;
1239 static void
1240 item_grid_set_property (GObject *obj, guint param_id,
1241 GValue const *value, G_GNUC_UNUSED GParamSpec *pspec)
1243 GnmItemGrid *ig = GNM_ITEM_GRID (obj);
1244 GnmRange const *r;
1246 switch (param_id) {
1247 case GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI :
1248 ig->scg = g_value_get_object (value);
1249 break;
1251 case GNM_ITEM_GRID_PROP_BOUND :
1252 r = g_value_get_pointer (value);
1253 g_return_if_fail (r != NULL);
1254 ig->bound = *r;
1255 break;
1259 static void
1260 gnm_item_grid_class_init (GObjectClass *gobject_klass)
1262 GocItemClass *item_klass = (GocItemClass *) gobject_klass;
1264 parent_class = g_type_class_peek_parent (gobject_klass);
1266 gobject_klass->finalize = item_grid_finalize;
1267 gobject_klass->set_property = item_grid_set_property;
1268 g_object_class_install_property (gobject_klass, GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI,
1269 g_param_spec_object ("SheetControlGUI",
1270 P_("SheetControlGUI"),
1271 P_("The sheet control gui controlling the item"),
1272 SHEET_CONTROL_GUI_TYPE,
1273 GSF_PARAM_STATIC | G_PARAM_WRITABLE));
1274 g_object_class_install_property (gobject_klass, GNM_ITEM_GRID_PROP_BOUND,
1275 g_param_spec_pointer ("bound",
1276 P_("Bound"),
1277 P_("The display bounds"),
1278 GSF_PARAM_STATIC | G_PARAM_WRITABLE));
1280 item_klass->realize = item_grid_realize;
1281 item_klass->unrealize = item_grid_unrealize;
1282 item_klass->draw_region = item_grid_draw_region;
1283 item_klass->update_bounds = item_grid_update_bounds;
1284 item_klass->button_pressed = item_grid_button_pressed;
1285 item_klass->button_released = item_grid_button_released;
1286 item_klass->motion = item_grid_motion;
1287 item_klass->enter_notify = item_grid_enter_notify;
1288 item_klass->leave_notify = item_grid_leave_notify;
1289 item_klass->distance = item_grid_distance;
1292 GSF_CLASS (GnmItemGrid, gnm_item_grid,
1293 gnm_item_grid_class_init, gnm_item_grid_init,
1294 GOC_TYPE_ITEM)