Whitespace.
[gnumeric.git] / src / item-grid.c
blob45973163da18699767b18704a76f669cfb10cea8
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 "position.h" /* to eval conditions */
35 #include "style-border.h"
36 #include "style-color.h"
37 #include "pattern.h"
38 #include "commands.h"
39 #include "hlink.h"
40 #include "gui-util.h"
41 #include "gnm-i18n.h"
43 #include <goffice/goffice.h>
44 #include <gtk/gtk.h>
45 #include <gsf/gsf-impl-utils.h>
46 #include <math.h>
47 #include <string.h>
48 #define GNUMERIC_ITEM "GRID"
50 #if 0
51 #define MERGE_DEBUG(range, str) do { range_dump (range, str); } while (0)
52 #else
53 #define MERGE_DEBUG(range, str)
54 #endif
56 typedef enum {
57 GNM_ITEM_GRID_NO_SELECTION,
58 GNM_ITEM_GRID_SELECTING_CELL_RANGE,
59 GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
60 } ItemGridSelectionType;
62 struct _GnmItemGrid {
63 GocItem canvas_item;
65 SheetControlGUI *scg;
67 ItemGridSelectionType selecting;
69 GnmRange bound;
71 /* information for the cursor motion handler */
72 guint cursor_timer;
73 gint64 last_x, last_y;
74 GnmHLink *cur_link; /* do not dereference, just a pointer */
75 GtkWidget *tip;
76 guint tip_timer;
78 GdkCursor *cursor_link, *cursor_cross;
80 guint32 last_click_time;
82 /* Style: */
83 GdkRGBA function_marker_color;
84 GdkRGBA function_marker_border_color;
85 int function_marker_size;
87 GdkRGBA pane_divider_color;
88 int pane_divider_width;
91 typedef GocItemClass GnmItemGridClass;
92 static GocItemClass *parent_class;
94 enum {
95 GNM_ITEM_GRID_PROP_0,
96 GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI,
97 GNM_ITEM_GRID_PROP_BOUND
100 static void
101 ig_reload_style (GnmItemGrid *ig)
103 GocItem *item = GOC_ITEM (ig);
104 GtkStyleContext *context = goc_item_get_style_context (item);
105 GtkBorder border;
106 GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
107 GnmPane *pane = GNM_PANE (item->canvas);
109 gtk_style_context_save (context);
110 gtk_style_context_add_region (context, "function-marker", 0);
111 gnm_style_context_get_color (context, GTK_STATE_FLAG_NORMAL,
112 &ig->function_marker_color);
113 gtk_style_context_get_border_color (context, state,
114 &ig->function_marker_border_color);
115 gtk_style_context_restore (context);
117 gtk_style_context_save (context);
118 gtk_style_context_add_region (context, "pane-divider", 0);
119 gnm_style_context_get_color (context, GTK_STATE_FLAG_NORMAL,
120 &ig->pane_divider_color);
121 gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
122 ig->pane_divider_width = border.top; /* Hack? */
123 gtk_style_context_restore (context);
125 /* ---------------------------------------- */
127 context = gtk_widget_get_style_context (GTK_WIDGET (pane));
128 gtk_widget_style_get (GTK_WIDGET (pane),
129 "function-indicator-size",
130 &ig->function_marker_size,
131 NULL);
134 static void
135 ig_clear_hlink_tip (GnmItemGrid *ig)
137 if (ig->tip_timer != 0) {
138 g_source_remove (ig->tip_timer);
139 ig->tip_timer = 0;
142 if (ig->tip != NULL) {
143 gtk_widget_destroy (gtk_widget_get_toplevel (ig->tip));
144 ig->tip = NULL;
148 static void
149 item_grid_finalize (GObject *object)
151 GnmItemGrid *ig = GNM_ITEM_GRID (object);
153 if (ig->cursor_timer != 0) {
154 g_source_remove (ig->cursor_timer);
155 ig->cursor_timer = 0;
157 ig_clear_hlink_tip (ig);
158 ig->cur_link = NULL;
160 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
163 static gint
164 cb_cursor_motion (GnmItemGrid *ig)
166 Sheet const *sheet = scg_sheet (ig->scg);
167 GocCanvas *canvas = ig->canvas_item.canvas;
168 GnmPane *pane = GNM_PANE (canvas);
169 GdkCursor *cursor;
170 GnmCellPos pos;
171 GnmHLink *old_link;
173 pos.col = gnm_pane_find_col (pane, ig->last_x, NULL);
174 pos.row = gnm_pane_find_row (pane, ig->last_y, NULL);
176 old_link = ig->cur_link;
177 ig->cur_link = gnm_sheet_hlink_find (sheet, &pos);
178 cursor = (ig->cur_link != NULL) ? ig->cursor_link : ig->cursor_cross;
179 if (pane->mouse_cursor != cursor) {
180 gnm_pane_mouse_cursor_set (pane, cursor);
181 scg_set_display_cursor (ig->scg);
184 if (ig->cursor_timer != 0) {
185 g_source_remove (ig->cursor_timer);
186 ig->cursor_timer = 0;
189 if (old_link != ig->cur_link && ig->tip != NULL) {
190 gtk_widget_destroy (gtk_widget_get_toplevel (ig->tip));
191 ig->tip = NULL;
193 return FALSE;
196 static void
197 item_grid_realize (GocItem *item)
199 GdkDisplay *display;
200 GnmItemGrid *ig;
201 GdkPixbuf *cursor_cross_pixbuf;
203 parent_class->realize (item);
205 ig = GNM_ITEM_GRID (item);
206 ig_reload_style (ig);
208 display = gtk_widget_get_display (GTK_WIDGET (item->canvas));
209 ig->cursor_link = gdk_cursor_new_for_display (display, GDK_HAND2);
210 cursor_cross_pixbuf =
211 gtk_icon_theme_load_icon (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (item->canvas))),
212 "cursor-cross", 32, 0, NULL);
213 ig->cursor_cross =
214 gdk_cursor_new_from_pixbuf (display,
215 cursor_cross_pixbuf,
216 17, 17);
217 g_object_unref (cursor_cross_pixbuf);
218 cb_cursor_motion (ig);
221 static void
222 item_grid_unrealize (GocItem *item)
224 GnmItemGrid *ig = GNM_ITEM_GRID (item);
225 g_clear_object (&ig->cursor_link);
226 g_clear_object (&ig->cursor_cross);
227 parent_class->unrealize (item);
230 static void
231 item_grid_update_bounds (GocItem *item)
233 item->x0 = 0;
234 item->y0 = 0;
235 item->x1 = G_MAXINT64/2;
236 item->y1 = G_MAXINT64/2;
239 static void
240 draw_function_marker (GnmItemGrid *ig,
241 GnmCell const *cell, cairo_t *cr,
242 double x, double y, double w, double h, int const dir)
244 int size = ig->function_marker_size;
245 if (cell == NULL || !gnm_cell_has_expr (cell))
246 return;
248 cairo_save (cr);
249 cairo_new_path (cr);
250 cairo_rectangle (cr, x, y, w+1, h+1);
251 cairo_clip (cr);
252 cairo_new_path (cr);
253 if (dir > 0) {
254 cairo_move_to (cr, x, y);
255 cairo_line_to (cr, x + size, y);
256 cairo_arc (cr, x, y, size, 0., M_PI / 2.);
257 } else {
258 cairo_move_to (cr, x + w, y);
259 cairo_line_to (cr, x + w, y + size);
260 cairo_arc (cr, x + w, y, size, M_PI/2., M_PI);
262 cairo_close_path (cr);
263 gdk_cairo_set_source_rgba (cr, &ig->function_marker_color);
264 cairo_fill_preserve (cr);
265 gdk_cairo_set_source_rgba (cr, &ig->function_marker_border_color);
266 cairo_set_line_width (cr, 0.5);
267 cairo_stroke (cr);
268 cairo_restore (cr);
271 static void
272 item_grid_draw_merged_range (cairo_t *cr, GnmItemGrid *ig,
273 int start_x, int start_y,
274 GnmRange const *view, GnmRange const *range,
275 gboolean draw_selection, GtkStyleContext *ctxt)
277 int l, r, t, b, last;
278 SheetView const *sv = scg_view (ig->scg);
279 WorkbookView *wbv = sv_wbv (sv);
280 gboolean show_function_cell_markers = wbv->show_function_cell_markers;
281 gboolean show_extension_markers = wbv->show_extension_markers;
282 Sheet const *sheet = sv->sheet;
283 GnmCell const *cell = sheet_cell_get (sheet, range->start.col, range->start.row);
284 int const dir = sheet->text_is_rtl ? -1 : 1;
285 GnmStyleConditions *conds;
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 conds = gnm_style_get_conditions (style);
315 if (conds) {
316 GnmEvalPos ep;
317 int res;
318 eval_pos_init (&ep, (Sheet *)sheet, range->start.col, range->start.row);
319 if ((res = gnm_style_conditions_eval (conds, &ep)) >= 0)
320 style = gnm_style_get_cond_style (style, res);
323 /* Check for background THEN selection */
324 if (gnm_pattern_background_set (style, cr, is_selected, ctxt) ||
325 is_selected) {
326 /* Remember X excludes the far pixels */
327 if (dir > 0)
328 cairo_rectangle (cr, l, t, r-l+1, b-t+1);
329 else
330 cairo_rectangle (cr, r, t, l-r+1, b-t+1);
331 cairo_fill (cr);
334 /* Expand the coords to include non-visible areas too. The clipped
335 * region is only necessary when drawing the background */
336 if (range->start.col < view->start.col)
337 l -= dir * scg_colrow_distance_get (ig->scg, TRUE,
338 range->start.col, view->start.col);
339 if (view->end.col < range->end.col)
340 r += dir * scg_colrow_distance_get (ig->scg, TRUE,
341 view->end.col+1, range->end.col+1);
342 if (range->start.row < view->start.row)
343 t -= scg_colrow_distance_get (ig->scg, FALSE,
344 range->start.row, view->start.row);
345 if (view->end.row < range->end.row)
346 b += scg_colrow_distance_get (ig->scg, FALSE,
347 view->end.row+1, range->end.row+1);
349 if (cell != NULL) {
350 ColRowInfo *ri = sheet_row_get (sheet, range->start.row);
352 if (ri->needs_respan)
353 row_calc_spans (ri, cell->pos.row, sheet);
355 if (dir > 0) {
356 if (show_function_cell_markers)
357 draw_function_marker (ig, cell, cr, l, t,
358 r - l, b - t, dir);
359 cell_draw (cell, cr,
360 l, t, r - l, b - t, -1,
361 show_extension_markers);
362 } else {
363 if (show_function_cell_markers)
364 draw_function_marker (ig, cell, cr, r, t,
365 l - r, b - t, dir);
366 cell_draw (cell, cr,
367 r, t, l - r, b - t, -1,
368 show_extension_markers);
371 if (dir > 0)
372 gnm_style_border_draw_diag (style, cr, l, t, r, b);
373 else
374 gnm_style_border_draw_diag (style, cr, r, t, l, b);
377 static void
378 item_grid_draw_background (cairo_t *cr, GnmItemGrid *ig,
379 GnmStyle const *style,
380 int col, int row, int x, int y, int w, int h,
381 gboolean draw_selection, GtkStyleContext *ctxt)
383 SheetView const *sv = scg_view (ig->scg);
384 gboolean const is_selected = draw_selection &&
385 (sv->edit_pos.col != col || sv->edit_pos.row != row) &&
386 sv_is_pos_selected (sv, col, row);
387 gboolean const has_back =
388 gnm_pattern_background_set (style, cr, is_selected, ctxt);
390 #if DEBUG_SELECTION_PAINT
391 if (is_selected) {
392 g_printerr ("x = %d, w = %d\n", x, w+1);
394 #endif
395 if (has_back || is_selected) {
396 /* Fill the entire cell (API excludes far pixel) */
397 cairo_rectangle (cr, x, y, w+1, h+1);
398 cairo_fill (cr);
401 gnm_style_border_draw_diag (style, cr, x, y, x+w, y+h);
404 static gint
405 merged_col_cmp (GnmRange const *a, GnmRange const *b)
407 return a->start.col - b->start.col;
410 static void
411 ig_cairo_draw_bound (GnmItemGrid *ig, cairo_t* cr, int x0, int y0, int x1, int y1)
413 double width = ig->pane_divider_width;
414 cairo_set_line_width (cr, width);
415 cairo_set_dash (cr, NULL, 0, 0.);
416 cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
417 cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
418 gdk_cairo_set_source_rgba (cr, &ig->pane_divider_color);
419 cairo_move_to (cr, x0 - width / 2, y0 - width / 2);
420 cairo_line_to (cr, x1 - width / 2, y1 - width / 2);
421 cairo_stroke (cr);
424 static gboolean
425 item_grid_draw_region (GocItem const *item, cairo_t *cr,
426 double x_0, double y_0, double x_1, double y_1)
428 GocCanvas *canvas = item->canvas;
429 double scale = canvas->pixels_per_unit;
430 gint64 x0 = x_0 * scale, y0 = y_0 * scale, x1 = x_1 * scale, y1 = y_1 * scale;
431 gint width = x1 - x0;
432 gint height = y1 - y0;
433 GnmPane *pane = GNM_PANE (canvas);
434 Sheet const *sheet = scg_sheet (pane->simple.scg);
435 WBCGtk *wbcg = scg_wbcg (pane->simple.scg);
436 GnmCell const * const edit_cell = wbcg->editing_cell;
437 GnmItemGrid *ig = GNM_ITEM_GRID (item);
438 ColRowInfo const *ri = NULL, *next_ri = NULL;
439 int const dir = sheet->text_is_rtl ? -1 : 1;
440 SheetView const *sv = scg_view (ig->scg);
441 WorkbookView *wbv = sv_wbv (sv);
442 gboolean show_function_cell_markers = wbv->show_function_cell_markers;
443 gboolean show_extension_markers = wbv->show_extension_markers;
444 GtkStyleContext *ctxt = goc_item_get_style_context (item);
446 /* To ensure that far and near borders get drawn we pretend to draw +-2
447 * pixels around the target area which would include the surrounding
448 * borders if necessary */
449 /* TODO : there is an opportunity to speed up the redraw loop by only
450 * painting the borders of the edges and not the content.
451 * However, that feels like more hassle that it is worth. Look into this someday.
453 int x;
454 gint64 y, start_x, offset;
455 int col, row, n, start_col, end_col;
456 int start_row = gnm_pane_find_row (pane, y0-2, &y);
457 int end_row = gnm_pane_find_row (pane, y1+2, NULL);
458 gint64 const start_y = y - canvas->scroll_y1 * scale;
460 GnmStyleRow sr, next_sr;
461 GnmStyle const **styles;
462 GnmBorder const **borders, **prev_vert;
463 GnmBorder const *none =
464 sheet->hide_grid ? NULL : gnm_style_border_none ();
465 gpointer *sr_array_data;
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 (ctxt,
526 x0 - canvas->scroll_x1 * scale,
527 y0 - canvas->scroll_y1 * scale,
528 width, height);
529 else
530 gtk_render_background (ctxt,
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 sr_array_data = g_new (gpointer, n * 8);
553 style_row_init (&prev_vert, &sr, &next_sr, start_col, end_col,
554 sr_array_data, sheet->hide_grid);
556 /* load up the styles for the first row */
557 next_sr.row = sr.row = row = start_row;
558 sheet_style_get_row (sheet, &sr);
560 /* Collect the column widths */
561 colwidths = g_new (int, n);
562 colwidths -= start_col;
563 for (col = start_col; col <= end_col; col++) {
564 ColRowInfo const *ci = sheet_col_get_info (sheet, col);
565 colwidths[col] = ci->visible ? ci->size_pixels : -1;
568 goc_canvas_c2w (canvas, start_x / scale, 0, &x, NULL);
569 start_x = x;
570 for (y = start_y; row <= end_row; row = sr.row = next_sr.row, ri = next_ri) {
571 /* Restore the set of ranges seen, but still active.
572 * Reinverting list to maintain the original order */
573 g_return_val_if_fail (merged_active == NULL, TRUE);
575 #if DEBUG_SELECTION_PAINT
576 g_printerr ("row = %d (startcol = %d)\n", row, start_col);
577 #endif
578 while (merged_active_seen != NULL) {
579 GSList *tmp = merged_active_seen->next;
580 merged_active_seen->next = merged_active;
581 merged_active = merged_active_seen;
582 merged_active_seen = tmp;
583 MERGE_DEBUG (merged_active->data, " : seen -> active\n");
586 /* find the next visible row */
587 while (1) {
588 ++next_sr.row;
589 if (next_sr.row <= end_row) {
590 next_ri = sheet_row_get_info (sheet, next_sr.row);
591 if (next_ri->visible) {
592 sheet_style_get_row (sheet, &next_sr);
593 break;
595 } else {
596 for (col = start_col ; col <= end_col; ++col)
597 next_sr.vertical [col] =
598 next_sr.bottom [col] = none;
599 break;
603 /* look for merges that start on this row, on the first painted row
604 * also check for merges that start above. */
605 view.start.row = row;
606 lag = &merged_unused;
607 for (ptr = merged_unused; ptr != NULL; ) {
608 GnmRange * const r = ptr->data;
610 if (r->start.row <= row) {
611 GSList *tmp = ptr;
612 ptr = *lag = tmp->next;
613 if (r->end.row < row) {
614 tmp->next = merged_used;
615 merged_used = tmp;
616 MERGE_DEBUG (r, " : unused -> used\n");
617 } else {
618 ColRowInfo const *ci =
619 sheet_col_get_info (sheet, r->start.col);
620 g_slist_free_1 (tmp);
621 merged_active = g_slist_insert_sorted (merged_active, r,
622 (GCompareFunc)merged_col_cmp);
623 MERGE_DEBUG (r, " : unused -> active\n");
625 if (ci->visible)
626 item_grid_draw_merged_range (cr, ig,
627 start_x, y, &view, r,
628 draw_selection,
629 ctxt);
631 } else {
632 lag = &(ptr->next);
633 ptr = ptr->next;
637 for (col = start_col, x = start_x; col <= end_col ; col++) {
638 GnmStyle const *style;
639 CellSpanInfo const *span;
640 ColRowInfo const *ci = sheet_col_get_info (sheet, col);
642 #if DEBUG_SELECTION_PAINT
643 g_printerr ("col [%d] = %d\n", col, x);
644 #endif
645 if (!ci->visible) {
646 if (merged_active != NULL) {
647 GnmRange const *r = merged_active->data;
648 if (r->end.col == col) {
649 ptr = merged_active;
650 merged_active = merged_active->next;
651 if (r->end.row <= row) {
652 ptr->next = merged_used;
653 merged_used = ptr;
654 MERGE_DEBUG (r, " : active2 -> used\n");
655 } else {
656 ptr->next = merged_active_seen;
657 merged_active_seen = ptr;
658 MERGE_DEBUG (r, " : active2 -> seen\n");
662 continue;
665 /* Skip any merged regions */
666 if (merged_active != NULL) {
667 GnmRange const *r = merged_active->data;
668 if (r->start.col <= col) {
669 gboolean clear_top, clear_bottom = FALSE;
670 int i, first = r->start.col;
671 int last = r->end.col;
673 ptr = merged_active;
674 merged_active = merged_active->next;
675 if (r->end.row <= row) {
676 ptr->next = merged_used;
677 merged_used = ptr;
678 MERGE_DEBUG (r, " : active -> used\n");
680 /* in case something managed the bottom of a merge */
681 if (r->end.row < row)
682 goto plain_draw;
683 } else {
684 ptr->next = merged_active_seen;
685 merged_active_seen = ptr;
686 MERGE_DEBUG (r, " : active -> seen\n");
687 if (next_sr.row <= r->end.row)
688 clear_bottom = TRUE;
691 x += dir * scg_colrow_distance_get (
692 pane->simple.scg, TRUE, col, last+1);
693 col = last;
695 if (first < start_col) {
696 first = start_col;
697 sr.vertical [first] = NULL;
699 if (last > end_col) {
700 last = end_col;
701 sr.vertical [last+1] = NULL;
703 clear_top = (r->start.row != row);
705 /* Clear the borders */
706 for (i = first ; i <= last ; i++) {
707 if (clear_top)
708 sr.top [i] = NULL;
709 if (clear_bottom)
710 sr.bottom [i] = NULL;
711 if (i > first)
712 sr.vertical [i] = NULL;
714 continue;
718 plain_draw : /* a quick hack to deal with 142267 */
719 if (dir < 0)
720 x -= ci->size_pixels;
721 style = sr.styles [col];
722 item_grid_draw_background (cr, ig,
723 style, col, row, x, y,
724 ci->size_pixels, ri->size_pixels,
725 draw_selection, ctxt);
728 /* Is this part of a span?
729 * 1) There are cells allocated in the row
730 * (indicated by ri->spans != NULL)
731 * 2) Look in the rows hash table to see if
732 * there is a span descriptor.
734 if (NULL == ri->spans || NULL == (span = row_span_get (ri, col))) {
736 /* If it is being edited pretend it is empty to
737 * avoid problems with long cells'
738 * contents extending past the edge of the edit
739 * box. Ignore blanks too.
741 GnmCell const *cell = sheet_cell_get (sheet, col, row);
742 if (!gnm_cell_is_empty (cell) && cell != edit_cell) {
743 if (show_function_cell_markers)
744 draw_function_marker (ig, cell, cr, x, y,
745 ci->size_pixels,
746 ri->size_pixels,
747 dir);
748 cell_draw (cell, cr,
749 x, y, ci->size_pixels,
750 ri->size_pixels, -1,
751 show_extension_markers);
753 /* Only draw spaning cells after all the backgrounds
754 * that we are going to draw have been drawn. No need
755 * to draw the edit cell, or blanks. */
756 } else if (edit_cell != span->cell &&
757 (col == span->right || col == end_col)) {
758 GnmCell const *cell = span->cell;
759 int const start_span_col = span->left;
760 int const end_span_col = span->right;
761 int real_x = x;
762 ColRowInfo const *cell_col =
763 sheet_col_get_info (sheet, cell->pos.col);
764 int center_offset = cell_col->size_pixels/2;
765 int tmp_width = ci->size_pixels;
767 if (col != cell->pos.col)
768 style = sheet_style_get (sheet,
769 cell->pos.col, row);
771 /* x, y are relative to this cell origin, but the cell
772 * might be using columns to the left (if it is set to right
773 * justify or center justify) compute the pixel difference */
774 if (dir > 0 && start_span_col != cell->pos.col)
775 center_offset += scg_colrow_distance_get (
776 pane->simple.scg, TRUE,
777 start_span_col, cell->pos.col);
778 else if (dir < 0 && end_span_col != cell->pos.col)
779 center_offset += scg_colrow_distance_get (
780 pane->simple.scg, TRUE,
781 cell->pos.col, end_span_col);
783 if (start_span_col != col) {
784 offset = scg_colrow_distance_get (
785 pane->simple.scg, TRUE,
786 start_span_col, col);
787 tmp_width += offset;
788 if (dir > 0)
789 real_x -= offset;
790 sr.vertical [col] = NULL;
792 if (end_span_col != col) {
793 offset = scg_colrow_distance_get (
794 pane->simple.scg, TRUE,
795 col+1, end_span_col + 1);
796 tmp_width += offset;
797 if (dir < 0)
798 real_x -= offset;
801 if (show_function_cell_markers)
802 draw_function_marker (ig, cell, cr, real_x, y,
803 tmp_width,
804 ri->size_pixels, dir);
805 cell_draw (cell, cr,
806 real_x, y, tmp_width,
807 ri->size_pixels, center_offset,
808 show_extension_markers);
810 } else if (col != span->left)
811 sr.vertical [col] = NULL;
813 if (dir > 0)
814 x += ci->size_pixels;
816 gnm_style_borders_row_draw (prev_vert, &sr,
817 cr, start_x, y, y+ri->size_pixels,
818 colwidths, TRUE, dir);
820 /* In case there were hidden merges that trailed off the end */
821 while (merged_active != NULL) {
822 GnmRange const *r = merged_active->data;
823 ptr = merged_active;
824 merged_active = merged_active->next;
825 if (r->end.row <= row) {
826 ptr->next = merged_used;
827 merged_used = ptr;
828 MERGE_DEBUG (r, " : active3 -> used\n");
829 } else {
830 ptr->next = merged_active_seen;
831 merged_active_seen = ptr;
832 MERGE_DEBUG (r, " : active3 -> seen\n");
836 /* roll the pointers */
837 borders = prev_vert; prev_vert = sr.vertical;
838 sr.vertical = next_sr.vertical; next_sr.vertical = borders;
839 borders = sr.top; sr.top = sr.bottom;
840 sr.bottom = next_sr.top = next_sr.bottom; next_sr.bottom = borders;
841 styles = sr.styles; sr.styles = next_sr.styles; next_sr.styles = styles;
843 y += ri->size_pixels;
846 if (ig->bound.start.row > 0 && start_y < 1)
847 ig_cairo_draw_bound (ig, cr, start_x, 1, x, 1);
848 if (ig->bound.start.col > 0) {
849 if (canvas->direction == GOC_DIRECTION_RTL && start_x >= goc_canvas_get_width (canvas)) {
850 x = goc_canvas_get_width (canvas);
851 ig_cairo_draw_bound (ig, cr, x, start_y, x, y);
852 } else if (canvas->direction == GOC_DIRECTION_LTR && start_x < 1)
853 ig_cairo_draw_bound (ig, cr, 1, start_y, 1, y);
856 g_slist_free (merged_used); /* merges with bottom in view */
857 g_slist_free (merged_active_seen); /* merges with bottom the view */
858 g_slist_free (merged_unused); /* merges in hidden rows */
859 g_free (sr_array_data);
860 g_free (colwidths + start_col); // Offset reverts -= from above
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, so->anchor.mode);
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 return TRUE;
953 /* If the user is editing a formula (wbcg_rangesel_possible) then we
954 * enable the dynamic cell selection mode.
956 if (button == 1 && wbcg_rangesel_possible (wbcg)) {
957 scg_rangesel_start (scg, pos.col, pos.row, pos.col, pos.row);
958 ig->selecting = GNM_ITEM_GRID_SELECTING_FORMULA_RANGE;
959 gnm_pane_slide_init (pane);
960 gnm_simple_canvas_grab (item);
961 return TRUE;
964 /* While a guru is up ignore clicks */
965 if (wbc_gtk_get_guru (wbcg) != NULL)
966 return TRUE;
968 /* This was a regular click on a cell on the spreadsheet. Select it.
969 * but only if the entered expression is valid */
970 if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, &edit_showed_dialog))
971 return TRUE;
973 if (button == 1 && !sheet_selection_is_allowed (sheet, &pos))
974 return TRUE;
976 /* Button == 1 is used to trigger hyperlinks (and possibly similar */
977 /* special cases. Otherwise button == 2 should behave exactly like */
978 /* button == 1. See bug #700792 */
980 /* buttons 1 and 2 will always change the selection, the other buttons will
981 * only effect things if the target is not already selected. */
982 already_selected = sv_is_pos_selected (sv, pos.col, pos.row);
983 if (button == 1 || button == 2 || !already_selected) {
984 if (!(event->button.state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)))
985 sv_selection_reset (sv);
987 if ((event->button.button != 1 && event->button.button != 2)
988 || !(event->button.state & GDK_SHIFT_MASK) ||
989 sv->selections == NULL) {
990 sv_selection_add_pos (sv, pos.col, pos.row,
991 (already_selected && (event->button.state & GDK_CONTROL_MASK)) ?
992 GNM_SELECTION_MODE_REMOVE :
993 GNM_SELECTION_MODE_ADD);
994 sv_make_cell_visible (sv, pos.col, pos.row, FALSE);
995 } else sv_selection_extend_to (sv, pos.col, pos.row);
996 sheet_update (sheet);
999 if (edit_showed_dialog)
1000 return TRUE; /* we already ignored the button release */
1002 switch (button) {
1003 case 1:
1004 case 2: {
1005 guint32 double_click_time;
1008 * If the second click is on a different cell than the
1009 * first one this cannot be a double-click
1011 if (already_selected) {
1012 g_object_get (gtk_widget_get_settings (GTK_WIDGET (canvas)),
1013 "gtk-double-click-time", &double_click_time,
1014 NULL);
1016 if ((ig->last_click_time + double_click_time) > gdk_event_get_time (event) &&
1017 wbcg_edit_start (wbcg, FALSE, FALSE)) {
1018 break;
1022 ig->last_click_time = gdk_event_get_time (event);
1023 ig->selecting = GNM_ITEM_GRID_SELECTING_CELL_RANGE;
1024 gnm_pane_slide_init (pane);
1025 gnm_simple_canvas_grab (item);
1026 break;
1029 case 3: scg_context_menu (scg, event, FALSE, FALSE);
1030 break;
1031 default:
1032 break;
1035 return TRUE;
1039 * Handle the selection
1042 static gboolean
1043 cb_extend_cell_range (GnmPane *pane, GnmPaneSlideInfo const *info)
1045 sv_selection_extend_to (scg_view (pane->simple.scg),
1046 info->col, info->row);
1047 return TRUE;
1050 static gboolean
1051 cb_extend_expr_range (GnmPane *pane, GnmPaneSlideInfo const *info)
1053 scg_rangesel_extend_to (pane->simple.scg, info->col, info->row);
1054 return TRUE;
1057 static gint
1058 cb_cursor_come_to_rest (GnmItemGrid *ig)
1060 Sheet const *sheet = scg_sheet (ig->scg);
1061 GocCanvas *canvas = ig->canvas_item.canvas;
1062 GnmPane *pane = GNM_PANE (canvas);
1063 GnmHLink *lnk;
1064 gint64 x, y;
1065 GnmCellPos pos;
1066 char const *tiptext;
1068 /* Be anal and look it up in case something has destroyed the link
1069 * since the last motion */
1070 x = ig->last_x;
1071 y = ig->last_y;
1072 pos.col = gnm_pane_find_col (pane, x, NULL);
1073 pos.row = gnm_pane_find_row (pane, y, NULL);
1075 lnk = gnm_sheet_hlink_find (sheet, &pos);
1076 if (lnk != NULL && (tiptext = gnm_hlink_get_tip (lnk)) != NULL) {
1077 g_return_val_if_fail (lnk == ig->cur_link, FALSE);
1079 if (ig->tip == NULL && strlen (tiptext) > 0) {
1080 GtkWidget *cw = GTK_WIDGET (canvas);
1081 int wx, wy;
1083 gnm_canvas_get_position (canvas, &wx, &wy,
1084 ig->last_x, ig->last_y);
1085 ig->tip = gnm_create_tooltip (cw);
1086 gtk_label_set_text (GTK_LABEL (ig->tip), tiptext);
1087 /* moving the tip window some pixels from wx,wy in order to
1088 * avoid a leave_notify event that would destroy the tip.
1089 * see #706659 */
1090 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (ig->tip)),
1091 wx + 10, wy + 10);
1092 gtk_widget_show_all (gtk_widget_get_toplevel (ig->tip));
1096 ig->tip_timer = 0;
1097 return FALSE;
1100 static gboolean
1101 item_grid_motion (GocItem *item, double x_, double y_)
1103 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1104 GocCanvas *canvas = item->canvas;
1105 GnmPane *pane = GNM_PANE (canvas);
1106 GnmPaneSlideHandler slide_handler = NULL;
1107 gint64 x = x_ * canvas->pixels_per_unit, y = y_ * canvas->pixels_per_unit;
1108 switch (ig->selecting) {
1109 case GNM_ITEM_GRID_NO_SELECTION:
1110 if (ig->cursor_timer == 0)
1111 ig->cursor_timer = g_timeout_add (100,
1112 (GSourceFunc)cb_cursor_motion, ig);
1113 if (ig->tip_timer != 0)
1114 g_source_remove (ig->tip_timer);
1115 ig->tip_timer = g_timeout_add (500,
1116 (GSourceFunc)cb_cursor_come_to_rest, ig);
1117 ig->last_x = x;
1118 ig->last_y = y;
1119 return TRUE;
1120 case GNM_ITEM_GRID_SELECTING_CELL_RANGE:
1121 slide_handler = &cb_extend_cell_range;
1122 break;
1123 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE:
1124 slide_handler = &cb_extend_expr_range;
1125 break;
1126 default:
1127 g_assert_not_reached ();
1130 gnm_pane_handle_motion (pane, canvas, x, y,
1131 GNM_PANE_SLIDE_X | GNM_PANE_SLIDE_Y |
1132 GNM_PANE_SLIDE_AT_COLROW_BOUND,
1133 slide_handler, NULL);
1134 return TRUE;
1137 static gboolean
1138 item_grid_button_released (GocItem *item, int button, G_GNUC_UNUSED double x_, G_GNUC_UNUSED double y_)
1140 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1141 GnmPane *pane = GNM_PANE (item->canvas);
1142 SheetControlGUI *scg = ig->scg;
1143 Sheet *sheet = scg_sheet (scg);
1144 ItemGridSelectionType selecting = ig->selecting;
1146 if (button != 1 && button != 2)
1147 return FALSE;
1149 gnm_pane_slide_stop (pane);
1151 switch (selecting) {
1152 case GNM_ITEM_GRID_NO_SELECTION:
1153 return TRUE;
1155 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE:
1156 /* Removal of this code (2 lines) */
1157 /* should fix http://bugzilla.gnome.org/show_bug.cgi?id=63485 */
1158 /* sheet_make_cell_visible (sheet, */
1159 /* sheet->edit_pos.col, sheet->edit_pos.row, FALSE); */
1160 /* Fall through */
1161 case GNM_ITEM_GRID_SELECTING_CELL_RANGE:
1162 sv_selection_simplify (scg_view (scg));
1163 wb_view_selection_desc (
1164 wb_control_view (scg_wbc (scg)), TRUE, NULL);
1165 break;
1167 default:
1168 g_assert_not_reached ();
1171 ig->selecting = GNM_ITEM_GRID_NO_SELECTION;
1172 gnm_simple_canvas_ungrab (item);
1174 if (selecting == GNM_ITEM_GRID_SELECTING_FORMULA_RANGE)
1175 gnm_expr_entry_signal_update (
1176 wbcg_get_entry_logical (scg_wbcg (scg)), TRUE);
1178 if (selecting == GNM_ITEM_GRID_SELECTING_CELL_RANGE && button == 1) {
1179 GnmCellPos const *pos = sv_is_singleton_selected (scg_view (scg));
1180 if (pos != NULL) {
1181 GnmHLink *lnk;
1182 /* check for hyper links */
1183 lnk = gnm_sheet_hlink_find (sheet, pos);
1184 if (lnk != NULL)
1185 gnm_hlink_activate (lnk, scg_wbcg (scg));
1188 return TRUE;
1191 static gboolean
1192 item_grid_enter_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
1194 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1195 scg_set_display_cursor (ig->scg);
1196 return TRUE;
1199 static gboolean
1200 item_grid_leave_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
1202 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1203 ig_clear_hlink_tip (ig);
1204 if (ig->cursor_timer != 0) {
1205 g_source_remove (ig->cursor_timer);
1206 ig->cursor_timer = 0;
1208 return TRUE;
1211 static void
1212 gnm_item_grid_init (GnmItemGrid *ig)
1214 GocItem *item = GOC_ITEM (ig);
1216 item->x0 = 0;
1217 item->y0 = 0;
1218 item->x1 = 0;
1219 item->y1 = 0;
1221 ig->selecting = GNM_ITEM_GRID_NO_SELECTION;
1222 /* We need something at least as big as any sheet. */
1223 ig->bound.start.col = ig->bound.start.row = 0;
1224 ig->bound.end.col = GNM_MAX_COLS - 1;
1225 ig->bound.end.row = GNM_MAX_ROWS - 1;
1226 ig->cursor_timer = 0;
1227 ig->cur_link = NULL;
1228 ig->tip_timer = 0;
1229 ig->tip = NULL;
1232 static void
1233 item_grid_set_property (GObject *obj, guint param_id,
1234 GValue const *value, G_GNUC_UNUSED GParamSpec *pspec)
1236 GnmItemGrid *ig = GNM_ITEM_GRID (obj);
1237 GnmRange const *r;
1239 switch (param_id) {
1240 case GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI:
1241 ig->scg = g_value_get_object (value);
1242 break;
1244 case GNM_ITEM_GRID_PROP_BOUND:
1245 r = g_value_get_pointer (value);
1246 g_return_if_fail (r != NULL);
1247 ig->bound = *r;
1248 break;
1252 static void
1253 gnm_item_grid_class_init (GObjectClass *gobject_klass)
1255 GocItemClass *item_klass = (GocItemClass *) gobject_klass;
1257 parent_class = g_type_class_peek_parent (gobject_klass);
1259 gobject_klass->finalize = item_grid_finalize;
1260 gobject_klass->set_property = item_grid_set_property;
1261 g_object_class_install_property (gobject_klass, GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI,
1262 g_param_spec_object ("SheetControlGUI",
1263 P_("SheetControlGUI"),
1264 P_("The sheet control gui controlling the item"),
1265 GNM_SCG_TYPE,
1266 GSF_PARAM_STATIC | G_PARAM_WRITABLE));
1267 g_object_class_install_property (gobject_klass, GNM_ITEM_GRID_PROP_BOUND,
1268 g_param_spec_pointer ("bound",
1269 P_("Bound"),
1270 P_("The display bounds"),
1271 GSF_PARAM_STATIC | G_PARAM_WRITABLE));
1273 item_klass->realize = item_grid_realize;
1274 item_klass->unrealize = item_grid_unrealize;
1275 item_klass->draw_region = item_grid_draw_region;
1276 item_klass->update_bounds = item_grid_update_bounds;
1277 item_klass->button_pressed = item_grid_button_pressed;
1278 item_klass->button_released = item_grid_button_released;
1279 item_klass->motion = item_grid_motion;
1280 item_klass->enter_notify = item_grid_enter_notify;
1281 item_klass->leave_notify = item_grid_leave_notify;
1282 item_klass->distance = item_grid_distance;
1285 GSF_CLASS (GnmItemGrid, gnm_item_grid,
1286 gnm_item_grid_class_init, gnm_item_grid_init,
1287 GOC_TYPE_ITEM)