Update Spanish translation
[gnumeric.git] / src / item-grid.c
blobe3cf381da6b5f904abfe9e3ae7567b5b7197b1af
2 /*
3 * item-grid.c : A canvas item that is responsible for drawing gridlines and
4 * cell content. One item per sheet displays all the cells.
6 * Authors:
7 * Miguel de Icaza (miguel@kernel.org)
8 * Jody Goldberg (jody@gnome.org)
12 #include <gnumeric-config.h>
13 #include <gnumeric.h>
14 #include <item-grid.h>
16 #include <gnm-pane-impl.h>
17 #include <wbc-gtk-impl.h>
18 #include <workbook-view.h>
19 #include <sheet-control-gui-priv.h>
20 #include <sheet.h>
21 #include <sheet-view.h>
22 #include <sheet-style.h>
23 #include <sheet-merge.h>
24 #include <sheet-object-impl.h>
25 #include <cell.h>
26 #include <cell-draw.h>
27 #include <cellspan.h>
28 #include <ranges.h>
29 #include <selection.h>
30 #include <parse-util.h>
31 #include <mstyle.h>
32 #include <style-conditions.h>
33 #include <position.h> /* to eval conditions */
34 #include <style-border.h>
35 #include <style-color.h>
36 #include <pattern.h>
37 #include <commands.h>
38 #include <hlink.h>
39 #include <gui-util.h>
40 #include <gnm-i18n.h>
42 #include <goffice/goffice.h>
43 #include <gsf/gsf-impl-utils.h>
44 #include <math.h>
45 #include <string.h>
46 #define GNUMERIC_ITEM "GRID"
48 #if 0
49 #define MERGE_DEBUG(range, str) do { range_dump (range, str); } while (0)
50 #else
51 #define MERGE_DEBUG(range, str)
52 #endif
54 typedef enum {
55 GNM_ITEM_GRID_NO_SELECTION,
56 GNM_ITEM_GRID_SELECTING_CELL_RANGE,
57 GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
58 } ItemGridSelectionType;
60 struct _GnmItemGrid {
61 GocItem canvas_item;
63 SheetControlGUI *scg;
65 ItemGridSelectionType selecting;
67 GnmRange bound;
69 /* information for the cursor motion handler */
70 guint cursor_timer;
71 gint64 last_x, last_y;
72 GnmHLink *cur_link; /* do not dereference, just a pointer */
73 GtkWidget *tip;
74 guint tip_timer;
76 GdkCursor *cursor_link, *cursor_cross;
78 guint32 last_click_time;
80 /* Style: */
81 GdkRGBA function_marker_color;
82 GdkRGBA function_marker_border_color;
83 int function_marker_size;
85 GdkRGBA pane_divider_color;
86 int pane_divider_width;
89 typedef GocItemClass GnmItemGridClass;
90 static GocItemClass *parent_class;
92 enum {
93 GNM_ITEM_GRID_PROP_0,
94 GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI,
95 GNM_ITEM_GRID_PROP_BOUND
98 static void
99 ig_reload_style (GnmItemGrid *ig)
101 GocItem *item = GOC_ITEM (ig);
102 GtkStyleContext *context = goc_item_get_style_context (item);
103 GtkBorder border;
104 GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
105 GnmPane *pane = GNM_PANE (item->canvas);
107 gtk_style_context_save (context);
108 gtk_style_context_add_region (context, "function-marker", 0);
109 gnm_style_context_get_color (context, GTK_STATE_FLAG_NORMAL,
110 &ig->function_marker_color);
111 gtk_style_context_get_border_color (context, state,
112 &ig->function_marker_border_color);
113 gtk_style_context_restore (context);
115 gtk_style_context_save (context);
116 gtk_style_context_add_region (context, "pane-divider", 0);
117 gnm_style_context_get_color (context, GTK_STATE_FLAG_NORMAL,
118 &ig->pane_divider_color);
119 gtk_style_context_get_border (context, GTK_STATE_FLAG_NORMAL, &border);
120 ig->pane_divider_width = border.top; /* Hack? */
121 gtk_style_context_restore (context);
123 /* ---------------------------------------- */
125 context = gtk_widget_get_style_context (GTK_WIDGET (pane));
126 gtk_widget_style_get (GTK_WIDGET (pane),
127 "function-indicator-size",
128 &ig->function_marker_size,
129 NULL);
132 static void
133 ig_clear_hlink_tip (GnmItemGrid *ig)
135 if (ig->tip_timer != 0) {
136 g_source_remove (ig->tip_timer);
137 ig->tip_timer = 0;
140 if (ig->tip != NULL) {
141 gtk_widget_destroy (gtk_widget_get_toplevel (ig->tip));
142 ig->tip = NULL;
146 static void
147 item_grid_finalize (GObject *object)
149 GnmItemGrid *ig = GNM_ITEM_GRID (object);
151 if (ig->cursor_timer != 0) {
152 g_source_remove (ig->cursor_timer);
153 ig->cursor_timer = 0;
155 ig_clear_hlink_tip (ig);
156 ig->cur_link = NULL;
158 (*G_OBJECT_CLASS (parent_class)->finalize) (object);
161 static gint
162 cb_cursor_motion (GnmItemGrid *ig)
164 Sheet const *sheet = scg_sheet (ig->scg);
165 GocCanvas *canvas = ig->canvas_item.canvas;
166 GnmPane *pane = GNM_PANE (canvas);
167 GdkCursor *cursor;
168 GnmCellPos pos;
169 GnmHLink *old_link;
171 pos.col = gnm_pane_find_col (pane, ig->last_x, NULL);
172 pos.row = gnm_pane_find_row (pane, ig->last_y, NULL);
174 old_link = ig->cur_link;
175 ig->cur_link = gnm_sheet_hlink_find (sheet, &pos);
176 cursor = (ig->cur_link != NULL) ? ig->cursor_link : ig->cursor_cross;
177 if (pane->mouse_cursor != cursor) {
178 gnm_pane_mouse_cursor_set (pane, cursor);
179 scg_set_display_cursor (ig->scg);
182 if (ig->cursor_timer != 0) {
183 g_source_remove (ig->cursor_timer);
184 ig->cursor_timer = 0;
187 if (old_link != ig->cur_link && ig->tip != NULL) {
188 gtk_widget_destroy (gtk_widget_get_toplevel (ig->tip));
189 ig->tip = NULL;
191 return FALSE;
194 static void
195 item_grid_realize (GocItem *item)
197 GdkDisplay *display;
198 GnmItemGrid *ig;
199 GdkPixbuf *cursor_cross_pixbuf;
201 parent_class->realize (item);
203 ig = GNM_ITEM_GRID (item);
204 ig_reload_style (ig);
206 display = gtk_widget_get_display (GTK_WIDGET (item->canvas));
207 ig->cursor_link = gdk_cursor_new_for_display (display, GDK_HAND2);
208 cursor_cross_pixbuf =
209 gtk_icon_theme_load_icon (gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (item->canvas))),
210 "cursor-cross", 32, 0, NULL);
211 ig->cursor_cross =
212 gdk_cursor_new_from_pixbuf (display,
213 cursor_cross_pixbuf,
214 17, 17);
215 g_object_unref (cursor_cross_pixbuf);
216 cb_cursor_motion (ig);
219 static void
220 item_grid_unrealize (GocItem *item)
222 GnmItemGrid *ig = GNM_ITEM_GRID (item);
223 g_clear_object (&ig->cursor_link);
224 g_clear_object (&ig->cursor_cross);
225 parent_class->unrealize (item);
228 static void
229 item_grid_update_bounds (GocItem *item)
231 item->x0 = 0;
232 item->y0 = 0;
233 item->x1 = G_MAXINT64/2;
234 item->y1 = G_MAXINT64/2;
237 static void
238 draw_function_marker (GnmItemGrid *ig,
239 GnmCell const *cell, cairo_t *cr,
240 double x, double y, double w, double h, int const dir)
242 int size = ig->function_marker_size;
243 if (cell == NULL || !gnm_cell_has_expr (cell))
244 return;
246 cairo_save (cr);
247 cairo_new_path (cr);
248 cairo_rectangle (cr, x, y, w+1, h+1);
249 cairo_clip (cr);
250 cairo_new_path (cr);
251 if (dir > 0) {
252 cairo_move_to (cr, x, y);
253 cairo_line_to (cr, x + size, y);
254 cairo_arc (cr, x, y, size, 0., M_PI / 2.);
255 } else {
256 cairo_move_to (cr, x + w, y);
257 cairo_line_to (cr, x + w, y + size);
258 cairo_arc (cr, x + w, y, size, M_PI/2., M_PI);
260 cairo_close_path (cr);
261 gdk_cairo_set_source_rgba (cr, &ig->function_marker_color);
262 cairo_fill_preserve (cr);
263 gdk_cairo_set_source_rgba (cr, &ig->function_marker_border_color);
264 cairo_set_line_width (cr, 0.5);
265 cairo_stroke (cr);
266 cairo_restore (cr);
269 static void
270 item_grid_draw_merged_range (cairo_t *cr, GnmItemGrid *ig,
271 int start_x, int start_y,
272 GnmRange const *view, GnmRange const *range,
273 gboolean draw_selection, GtkStyleContext *ctxt)
275 int l, r, t, b, last;
276 SheetView const *sv = scg_view (ig->scg);
277 WorkbookView *wbv = sv_wbv (sv);
278 gboolean show_function_cell_markers = wbv->show_function_cell_markers;
279 gboolean show_extension_markers = wbv->show_extension_markers;
280 Sheet const *sheet = sv->sheet;
281 GnmCell const *cell = sheet_cell_get (sheet, range->start.col, range->start.row);
282 int const dir = sheet->text_is_rtl ? -1 : 1;
283 GnmStyleConditions *conds;
285 /* load style from corner which may not be visible */
286 GnmStyle const *style = sheet_style_get (sheet, range->start.col, range->start.row);
287 gboolean const is_selected = draw_selection &&
288 (sv->edit_pos.col != range->start.col ||
289 sv->edit_pos.row != range->start.row) &&
290 sv_is_full_range_selected (sv, range);
292 /* Get the coordinates of the visible region */
293 l = r = start_x;
294 if (view->start.col < range->start.col)
295 l += dir * scg_colrow_distance_get (ig->scg, TRUE,
296 view->start.col, range->start.col);
297 if (range->end.col <= (last = view->end.col))
298 last = range->end.col;
299 r += dir * scg_colrow_distance_get (ig->scg, TRUE, view->start.col, last+1);
301 t = b = start_y;
302 if (view->start.row < range->start.row)
303 t += scg_colrow_distance_get (ig->scg, FALSE,
304 view->start.row, range->start.row);
305 if (range->end.row <= (last = view->end.row))
306 last = range->end.row;
307 b += scg_colrow_distance_get (ig->scg, FALSE, view->start.row, last+1);
309 if (l == r || t == b)
310 return;
312 conds = gnm_style_get_conditions (style);
313 if (conds) {
314 GnmEvalPos ep;
315 int res;
316 eval_pos_init (&ep, (Sheet *)sheet, range->start.col, range->start.row);
317 if ((res = gnm_style_conditions_eval (conds, &ep)) >= 0)
318 style = gnm_style_get_cond_style (style, res);
321 /* Check for background THEN selection */
322 if (gnm_pattern_background_set (style, cr, is_selected, ctxt) ||
323 is_selected) {
324 /* Remember X excludes the far pixels */
325 if (dir > 0)
326 cairo_rectangle (cr, l, t, r-l+1, b-t+1);
327 else
328 cairo_rectangle (cr, r, t, l-r+1, b-t+1);
329 cairo_fill (cr);
332 /* Expand the coords to include non-visible areas too. The clipped
333 * region is only necessary when drawing the background */
334 if (range->start.col < view->start.col)
335 l -= dir * scg_colrow_distance_get (ig->scg, TRUE,
336 range->start.col, view->start.col);
337 if (view->end.col < range->end.col)
338 r += dir * scg_colrow_distance_get (ig->scg, TRUE,
339 view->end.col+1, range->end.col+1);
340 if (range->start.row < view->start.row)
341 t -= scg_colrow_distance_get (ig->scg, FALSE,
342 range->start.row, view->start.row);
343 if (view->end.row < range->end.row)
344 b += scg_colrow_distance_get (ig->scg, FALSE,
345 view->end.row+1, range->end.row+1);
347 if (cell != NULL) {
348 ColRowInfo *ri = sheet_row_get (sheet, range->start.row);
350 if (ri->needs_respan)
351 row_calc_spans (ri, cell->pos.row, sheet);
353 if (dir > 0) {
354 if (show_function_cell_markers)
355 draw_function_marker (ig, cell, cr, l, t,
356 r - l, b - t, dir);
357 cell_draw (cell, cr,
358 l, t, r - l, b - t, -1,
359 show_extension_markers);
360 } else {
361 if (show_function_cell_markers)
362 draw_function_marker (ig, cell, cr, r, t,
363 l - r, b - t, dir);
364 cell_draw (cell, cr,
365 r, t, l - r, b - t, -1,
366 show_extension_markers);
369 if (dir > 0)
370 gnm_style_border_draw_diag (style, cr, l, t, r, b);
371 else
372 gnm_style_border_draw_diag (style, cr, r, t, l, b);
375 static void
376 item_grid_draw_background (cairo_t *cr, GnmItemGrid *ig,
377 GnmStyle const *style,
378 int col, int row, int x, int y, int w, int h,
379 gboolean draw_selection, GtkStyleContext *ctxt)
381 SheetView const *sv = scg_view (ig->scg);
382 gboolean const is_selected = draw_selection &&
383 (sv->edit_pos.col != col || sv->edit_pos.row != row) &&
384 sv_is_pos_selected (sv, col, row);
385 gboolean const has_back =
386 gnm_pattern_background_set (style, cr, is_selected, ctxt);
388 #if DEBUG_SELECTION_PAINT
389 if (is_selected) {
390 g_printerr ("x = %d, w = %d\n", x, w+1);
392 #endif
393 if (has_back || is_selected) {
394 /* Fill the entire cell (API excludes far pixel) */
395 cairo_rectangle (cr, x, y, w+1, h+1);
396 cairo_fill (cr);
399 gnm_style_border_draw_diag (style, cr, x, y, x+w, y+h);
402 static gint
403 merged_col_cmp (GnmRange const *a, GnmRange const *b)
405 return a->start.col - b->start.col;
408 static void
409 ig_cairo_draw_bound (GnmItemGrid *ig, cairo_t* cr, int x0, int y0, int x1, int y1)
411 double width = ig->pane_divider_width;
412 cairo_set_line_width (cr, width);
413 cairo_set_dash (cr, NULL, 0, 0.);
414 cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
415 cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
416 gdk_cairo_set_source_rgba (cr, &ig->pane_divider_color);
417 cairo_move_to (cr, x0 - width / 2, y0 - width / 2);
418 cairo_line_to (cr, x1 - width / 2, y1 - width / 2);
419 cairo_stroke (cr);
422 static gboolean
423 item_grid_draw_region (GocItem const *item, cairo_t *cr,
424 double x_0, double y_0, double x_1, double y_1)
426 GocCanvas *canvas = item->canvas;
427 double scale = canvas->pixels_per_unit;
428 gint64 x0 = x_0 * scale, y0 = y_0 * scale, x1 = x_1 * scale, y1 = y_1 * scale;
429 gint width = x1 - x0;
430 gint height = y1 - y0;
431 GnmPane *pane = GNM_PANE (canvas);
432 Sheet const *sheet = scg_sheet (pane->simple.scg);
433 WBCGtk *wbcg = scg_wbcg (pane->simple.scg);
434 GnmCell const * const edit_cell = wbcg->editing_cell;
435 GnmItemGrid *ig = GNM_ITEM_GRID (item);
436 ColRowInfo const *ri = NULL, *next_ri = NULL;
437 int const dir = sheet->text_is_rtl ? -1 : 1;
438 SheetView const *sv = scg_view (ig->scg);
439 WorkbookView *wbv = sv_wbv (sv);
440 gboolean show_function_cell_markers = wbv->show_function_cell_markers;
441 gboolean show_extension_markers = wbv->show_extension_markers;
442 GtkStyleContext *ctxt = goc_item_get_style_context (item);
444 /* To ensure that far and near borders get drawn we pretend to draw +-2
445 * pixels around the target area which would include the surrounding
446 * borders if necessary */
447 /* TODO : there is an opportunity to speed up the redraw loop by only
448 * painting the borders of the edges and not the content.
449 * However, that feels like more hassle that it is worth. Look into this someday.
451 int x;
452 gint64 y, start_x, offset;
453 int col, row, n, start_col, end_col;
454 int start_row = gnm_pane_find_row (pane, y0-2, &y);
455 int end_row = gnm_pane_find_row (pane, y1+2, NULL);
456 gint64 const start_y = y - canvas->scroll_y1 * scale;
458 GnmStyleRow sr, next_sr;
459 GnmStyle const **styles;
460 GnmBorder const **borders, **prev_vert;
461 GnmBorder const *none =
462 sheet->hide_grid ? NULL : gnm_style_border_none ();
463 gpointer *sr_array_data;
465 GnmRange view;
466 GSList *merged_active, *merged_active_seen,
467 *merged_used, *merged_unused, *ptr, **lag;
469 int *colwidths = NULL;
471 gboolean const draw_selection =
472 ig->scg->selected_objects == NULL &&
473 wbcg->new_object == NULL;
475 start_col = gnm_pane_find_col (pane, x0-2, &start_x);
476 end_col = gnm_pane_find_col (pane, x1+2, NULL);
478 g_return_val_if_fail (start_col <= end_col, TRUE);
480 #if 0
481 g_printerr ("%s:", cell_coord_name (start_col, start_row));
482 g_printerr ("%s <= %ld vs ???", cell_coord_name(end_col, end_row), (long)y);
483 g_printerr (" [%s]\n", cell_coord_name (ig->bound.end.col, ig->bound.end.row));
485 #endif
487 /* clip to bounds */
488 if (end_col > ig->bound.end.col)
489 end_col = ig->bound.end.col;
490 if (end_row > ig->bound.end.row)
491 end_row = ig->bound.end.row;
493 /* Skip any hidden cols/rows at the start */
494 for (; start_col <= end_col ; ++start_col) {
495 ri = sheet_col_get_info (sheet, start_col);
496 if (ri->visible)
497 break;
499 for (; start_row <= end_row ; ++start_row) {
500 ri = sheet_row_get_info (sheet, start_row);
501 if (ri->visible)
502 break;
505 /* if everything is hidden no need to draw */
506 if (end_col < ig->bound.start.col || start_col > ig->bound.end.col ||
507 end_row < ig->bound.start.row || start_row > ig->bound.end.row)
508 return TRUE;
510 /* Respan all rows that need it. */
511 for (row = start_row; row <= end_row; row++) {
512 ColRowInfo const *ri = sheet_row_get_info (sheet, row);
513 if (ri->visible && ri->needs_respan)
514 row_calc_spans ((ColRowInfo *)ri, row, sheet);
517 sheet_style_update_grid_color (sheet);
519 /* Fill entire region with default background (even past far edge) */
520 cairo_save (cr);
521 if (canvas->direction == GOC_DIRECTION_LTR)
522 gtk_render_background (ctxt,
524 x0 - canvas->scroll_x1 * scale,
525 y0 - canvas->scroll_y1 * scale,
526 width, height);
527 else
528 gtk_render_background (ctxt,
530 canvas->width - x0 + canvas->scroll_x1 * scale - width,
531 y0 - canvas->scroll_y1 * scale,
532 width, height);
533 cairo_restore (cr);
535 /* Get ordered list of merged regions */
536 merged_active = merged_active_seen = merged_used = NULL;
537 merged_unused = gnm_sheet_merge_get_overlap (sheet,
538 range_init (&view, start_col, start_row, end_col, end_row));
541 * allocate a single blob of memory for all 8 arrays of pointers.
542 * - 6 arrays of n GnmBorder const *
543 * - 2 arrays of n GnmStyle const *
545 * then alias the arrays for easy access so that array [col] is valid
546 * for all elements start_col-1 .. end_col+1 inclusive.
547 * Note that this means that in some cases array [-1] is legal.
549 n = end_col - start_col + 3; /* 1 before, 1 after, 1 fencepost */
550 sr_array_data = g_new (gpointer, n * 8);
551 style_row_init (&prev_vert, &sr, &next_sr, start_col, end_col,
552 sr_array_data, sheet->hide_grid);
554 /* load up the styles for the first row */
555 next_sr.row = sr.row = row = start_row;
556 sheet_style_get_row (sheet, &sr);
558 /* Collect the column widths */
559 colwidths = g_new (int, n);
560 colwidths -= start_col;
561 for (col = start_col; col <= end_col; col++) {
562 ColRowInfo const *ci = sheet_col_get_info (sheet, col);
563 colwidths[col] = ci->visible ? ci->size_pixels : -1;
566 goc_canvas_c2w (canvas, start_x / scale, 0, &x, NULL);
567 start_x = x;
568 for (y = start_y; row <= end_row; row = sr.row = next_sr.row, ri = next_ri) {
569 /* Restore the set of ranges seen, but still active.
570 * Reinverting list to maintain the original order */
571 g_return_val_if_fail (merged_active == NULL, TRUE);
573 #if DEBUG_SELECTION_PAINT
574 g_printerr ("row = %d (startcol = %d)\n", row, start_col);
575 #endif
576 while (merged_active_seen != NULL) {
577 GSList *tmp = merged_active_seen->next;
578 merged_active_seen->next = merged_active;
579 merged_active = merged_active_seen;
580 merged_active_seen = tmp;
581 MERGE_DEBUG (merged_active->data, " : seen -> active\n");
584 /* find the next visible row */
585 while (1) {
586 ++next_sr.row;
587 if (next_sr.row <= end_row) {
588 next_ri = sheet_row_get_info (sheet, next_sr.row);
589 if (next_ri->visible) {
590 sheet_style_get_row (sheet, &next_sr);
591 break;
593 } else {
594 for (col = start_col ; col <= end_col; ++col)
595 next_sr.vertical [col] =
596 next_sr.bottom [col] = none;
597 break;
601 /* look for merges that start on this row, on the first painted row
602 * also check for merges that start above. */
603 view.start.row = row;
604 lag = &merged_unused;
605 for (ptr = merged_unused; ptr != NULL; ) {
606 GnmRange * const r = ptr->data;
608 if (r->start.row <= row) {
609 GSList *tmp = ptr;
610 ptr = *lag = tmp->next;
611 if (r->end.row < row) {
612 tmp->next = merged_used;
613 merged_used = tmp;
614 MERGE_DEBUG (r, " : unused -> used\n");
615 } else {
616 ColRowInfo const *ci =
617 sheet_col_get_info (sheet, r->start.col);
618 g_slist_free_1 (tmp);
619 merged_active = g_slist_insert_sorted (merged_active, r,
620 (GCompareFunc)merged_col_cmp);
621 MERGE_DEBUG (r, " : unused -> active\n");
623 if (ci->visible)
624 item_grid_draw_merged_range (cr, ig,
625 start_x, y, &view, r,
626 draw_selection,
627 ctxt);
629 } else {
630 lag = &(ptr->next);
631 ptr = ptr->next;
635 for (col = start_col, x = start_x; col <= end_col ; col++) {
636 GnmStyle const *style;
637 CellSpanInfo const *span;
638 ColRowInfo const *ci = sheet_col_get_info (sheet, col);
640 #if DEBUG_SELECTION_PAINT
641 g_printerr ("col [%d] = %d\n", col, x);
642 #endif
643 if (!ci->visible) {
644 if (merged_active != NULL) {
645 GnmRange const *r = merged_active->data;
646 if (r->end.col == col) {
647 ptr = merged_active;
648 merged_active = merged_active->next;
649 if (r->end.row <= row) {
650 ptr->next = merged_used;
651 merged_used = ptr;
652 MERGE_DEBUG (r, " : active2 -> used\n");
653 } else {
654 ptr->next = merged_active_seen;
655 merged_active_seen = ptr;
656 MERGE_DEBUG (r, " : active2 -> seen\n");
660 continue;
663 /* Skip any merged regions */
664 if (merged_active != NULL) {
665 GnmRange const *r = merged_active->data;
666 if (r->start.col <= col) {
667 gboolean clear_top, clear_bottom = FALSE;
668 int i, first = r->start.col;
669 int last = r->end.col;
671 ptr = merged_active;
672 merged_active = merged_active->next;
673 if (r->end.row <= row) {
674 ptr->next = merged_used;
675 merged_used = ptr;
676 MERGE_DEBUG (r, " : active -> used\n");
678 /* in case something managed the bottom of a merge */
679 if (r->end.row < row)
680 goto plain_draw;
681 } else {
682 ptr->next = merged_active_seen;
683 merged_active_seen = ptr;
684 MERGE_DEBUG (r, " : active -> seen\n");
685 if (next_sr.row <= r->end.row)
686 clear_bottom = TRUE;
689 x += dir * scg_colrow_distance_get (
690 pane->simple.scg, TRUE, col, last+1);
691 col = last;
693 if (first < start_col) {
694 first = start_col;
695 sr.vertical [first] = NULL;
697 if (last > end_col) {
698 last = end_col;
699 sr.vertical [last+1] = NULL;
701 clear_top = (r->start.row != row);
703 /* Clear the borders */
704 for (i = first ; i <= last ; i++) {
705 if (clear_top)
706 sr.top [i] = NULL;
707 if (clear_bottom)
708 sr.bottom [i] = NULL;
709 if (i > first)
710 sr.vertical [i] = NULL;
712 continue;
716 plain_draw : /* a quick hack to deal with 142267 */
717 if (dir < 0)
718 x -= ci->size_pixels;
719 style = sr.styles [col];
720 item_grid_draw_background (cr, ig,
721 style, col, row, x, y,
722 ci->size_pixels, ri->size_pixels,
723 draw_selection, ctxt);
726 /* Is this part of a span?
727 * 1) There are cells allocated in the row
728 * (indicated by ri->spans != NULL)
729 * 2) Look in the rows hash table to see if
730 * there is a span descriptor.
732 if (NULL == ri->spans || NULL == (span = row_span_get (ri, col))) {
734 /* If it is being edited pretend it is empty to
735 * avoid problems with long cells'
736 * contents extending past the edge of the edit
737 * box. Ignore blanks too.
739 GnmCell const *cell = sheet_cell_get (sheet, col, row);
740 if (!gnm_cell_is_empty (cell) && cell != edit_cell) {
741 if (show_function_cell_markers)
742 draw_function_marker (ig, cell, cr, x, y,
743 ci->size_pixels,
744 ri->size_pixels,
745 dir);
746 cell_draw (cell, cr,
747 x, y, ci->size_pixels,
748 ri->size_pixels, -1,
749 show_extension_markers);
751 /* Only draw spaning cells after all the backgrounds
752 * that we are going to draw have been drawn. No need
753 * to draw the edit cell, or blanks. */
754 } else if (edit_cell != span->cell &&
755 (col == span->right || col == end_col)) {
756 GnmCell const *cell = span->cell;
757 int const start_span_col = span->left;
758 int const end_span_col = span->right;
759 int real_x = x;
760 ColRowInfo const *cell_col =
761 sheet_col_get_info (sheet, cell->pos.col);
762 int center_offset = cell_col->size_pixels/2;
763 int tmp_width = ci->size_pixels;
765 if (col != cell->pos.col)
766 style = sheet_style_get (sheet,
767 cell->pos.col, row);
769 /* x, y are relative to this cell origin, but the cell
770 * might be using columns to the left (if it is set to right
771 * justify or center justify) compute the pixel difference */
772 if (dir > 0 && start_span_col != cell->pos.col)
773 center_offset += scg_colrow_distance_get (
774 pane->simple.scg, TRUE,
775 start_span_col, cell->pos.col);
776 else if (dir < 0 && end_span_col != cell->pos.col)
777 center_offset += scg_colrow_distance_get (
778 pane->simple.scg, TRUE,
779 cell->pos.col, end_span_col);
781 if (start_span_col != col) {
782 offset = scg_colrow_distance_get (
783 pane->simple.scg, TRUE,
784 start_span_col, col);
785 tmp_width += offset;
786 if (dir > 0)
787 real_x -= offset;
788 sr.vertical [col] = NULL;
790 if (end_span_col != col) {
791 offset = scg_colrow_distance_get (
792 pane->simple.scg, TRUE,
793 col+1, end_span_col + 1);
794 tmp_width += offset;
795 if (dir < 0)
796 real_x -= offset;
799 if (show_function_cell_markers)
800 draw_function_marker (ig, cell, cr, real_x, y,
801 tmp_width,
802 ri->size_pixels, dir);
803 cell_draw (cell, cr,
804 real_x, y, tmp_width,
805 ri->size_pixels, center_offset,
806 show_extension_markers);
808 } else if (col != span->left)
809 sr.vertical [col] = NULL;
811 if (dir > 0)
812 x += ci->size_pixels;
814 gnm_style_borders_row_draw (prev_vert, &sr,
815 cr, start_x, y, y+ri->size_pixels,
816 colwidths, TRUE, dir);
818 /* In case there were hidden merges that trailed off the end */
819 while (merged_active != NULL) {
820 GnmRange const *r = merged_active->data;
821 ptr = merged_active;
822 merged_active = merged_active->next;
823 if (r->end.row <= row) {
824 ptr->next = merged_used;
825 merged_used = ptr;
826 MERGE_DEBUG (r, " : active3 -> used\n");
827 } else {
828 ptr->next = merged_active_seen;
829 merged_active_seen = ptr;
830 MERGE_DEBUG (r, " : active3 -> seen\n");
834 /* roll the pointers */
835 borders = prev_vert; prev_vert = sr.vertical;
836 sr.vertical = next_sr.vertical; next_sr.vertical = borders;
837 borders = sr.top; sr.top = sr.bottom;
838 sr.bottom = next_sr.top = next_sr.bottom; next_sr.bottom = borders;
839 styles = sr.styles; sr.styles = next_sr.styles; next_sr.styles = styles;
841 y += ri->size_pixels;
844 if (ig->bound.start.row > 0 && start_y < 1)
845 ig_cairo_draw_bound (ig, cr, start_x, 1, x, 1);
846 if (ig->bound.start.col > 0) {
847 if (canvas->direction == GOC_DIRECTION_RTL && start_x >= goc_canvas_get_width (canvas)) {
848 x = goc_canvas_get_width (canvas);
849 ig_cairo_draw_bound (ig, cr, x, start_y, x, y);
850 } else if (canvas->direction == GOC_DIRECTION_LTR && start_x < 1)
851 ig_cairo_draw_bound (ig, cr, 1, start_y, 1, y);
854 g_slist_free (merged_used); /* merges with bottom in view */
855 g_slist_free (merged_active_seen); /* merges with bottom the view */
856 g_slist_free (merged_unused); /* merges in hidden rows */
857 g_free (sr_array_data);
858 g_free (colwidths + start_col); // Offset reverts -= from above
859 g_return_val_if_fail (merged_active == NULL, TRUE);
860 return TRUE;
863 static double
864 item_grid_distance (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y,
865 GocItem **actual_item)
867 *actual_item = item;
868 return 0.0;
871 /***********************************************************************/
873 static gboolean
874 ig_obj_create_begin (GnmItemGrid *ig, int button, gint64 x, gint64 y)
876 GnmPane *pane = GNM_PANE (GOC_ITEM (ig)->canvas);
877 SheetObject *so = ig->scg->wbcg->new_object;
878 SheetObjectAnchor anchor;
879 double coords[4];
881 g_return_val_if_fail (ig->scg->selected_objects == NULL, TRUE);
882 g_return_val_if_fail (so != NULL, TRUE);
884 coords[0] = coords[2] = x;
885 coords[1] = coords[3] = y;
886 sheet_object_anchor_init (&anchor, NULL, NULL, GOD_ANCHOR_DIR_DOWN_RIGHT, so->anchor.mode);
887 scg_object_coords_to_anchor (ig->scg, coords, &anchor);
888 sheet_object_set_anchor (so, &anchor);
889 sheet_object_set_sheet (so, scg_sheet (ig->scg));
890 scg_object_select (ig->scg, so);
891 gnm_pane_object_start_resize (pane, button, x, y, so, 7, TRUE);
893 return TRUE;
896 /***************************************************************************/
898 static int
899 item_grid_button_pressed (GocItem *item, int button, double x_, double y_)
901 GnmItemGrid *ig = GNM_ITEM_GRID (item);
902 GocCanvas *canvas = item->canvas;
903 GnmPane *pane = GNM_PANE (canvas);
904 SheetControlGUI *scg = ig->scg;
905 WBCGtk *wbcg = scg_wbcg (scg);
906 SheetControl *sc = (SheetControl *)scg;
907 SheetView *sv = sc_view (sc);
908 Sheet *sheet = sv_sheet (sv);
909 GnmCellPos pos;
910 gboolean edit_showed_dialog;
911 gboolean already_selected;
912 GdkEvent *event = goc_canvas_get_cur_event (item->canvas);
913 gint64 x = x_ * canvas->pixels_per_unit, y = y_ * canvas->pixels_per_unit;
915 gnm_pane_slide_stop (pane);
917 pos.col = gnm_pane_find_col (pane, x, NULL);
918 pos.row = gnm_pane_find_row (pane, y, NULL);
920 /* GnmRange check first */
921 if (pos.col >= gnm_sheet_get_max_cols (sheet))
922 return TRUE;
923 if (pos.row >= gnm_sheet_get_max_rows (sheet))
924 return TRUE;
926 /* A new object is ready to be realized and inserted */
927 if (wbcg->new_object != NULL)
928 return ig_obj_create_begin (ig, button, x, y);
930 /* If we are not configuring an object then clicking on the sheet
931 * ends the edit. */
932 if (scg->selected_objects == NULL)
933 wbcg_focus_cur_scg (wbcg);
934 else if (wbc_gtk_get_guru (wbcg) == NULL)
935 scg_mode_edit (scg);
937 /* If we were already selecting a range of cells for a formula,
938 * reset the location to a new place, or extend the selection.
940 if (button == 1 && scg->rangesel.active) {
941 ig->selecting = GNM_ITEM_GRID_SELECTING_FORMULA_RANGE;
942 if (event->button.state & GDK_SHIFT_MASK)
943 scg_rangesel_extend_to (scg, pos.col, pos.row);
944 else
945 scg_rangesel_bound (scg, pos.col, pos.row, pos.col, pos.row);
946 gnm_pane_slide_init (pane);
947 gnm_simple_canvas_grab (item);
948 return TRUE;
951 /* If the user is editing a formula (wbcg_rangesel_possible) then we
952 * enable the dynamic cell selection mode.
954 if (button == 1 && wbcg_rangesel_possible (wbcg)) {
955 scg_rangesel_start (scg, pos.col, pos.row, pos.col, pos.row);
956 ig->selecting = GNM_ITEM_GRID_SELECTING_FORMULA_RANGE;
957 gnm_pane_slide_init (pane);
958 gnm_simple_canvas_grab (item);
959 return TRUE;
962 /* While a guru is up ignore clicks */
963 if (wbc_gtk_get_guru (wbcg) != NULL)
964 return TRUE;
966 /* This was a regular click on a cell on the spreadsheet. Select it.
967 * but only if the entered expression is valid */
968 if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, &edit_showed_dialog))
969 return TRUE;
971 if (button == 1 && !sheet_selection_is_allowed (sheet, &pos))
972 return TRUE;
974 /* Button == 1 is used to trigger hyperlinks (and possibly similar */
975 /* special cases. Otherwise button == 2 should behave exactly like */
976 /* button == 1. See bug #700792 */
978 /* buttons 1 and 2 will always change the selection, the other buttons will
979 * only effect things if the target is not already selected. */
980 already_selected = sv_is_pos_selected (sv, pos.col, pos.row);
981 if (button == 1 || button == 2 || !already_selected) {
982 if (!(event->button.state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)))
983 sv_selection_reset (sv);
985 if ((event->button.button != 1 && event->button.button != 2)
986 || !(event->button.state & GDK_SHIFT_MASK) ||
987 sv->selections == NULL) {
988 sv_selection_add_pos (sv, pos.col, pos.row,
989 (already_selected && (event->button.state & GDK_CONTROL_MASK)) ?
990 GNM_SELECTION_MODE_REMOVE :
991 GNM_SELECTION_MODE_ADD);
992 gnm_sheet_view_make_cell_visible (sv, pos.col, pos.row, FALSE);
993 } else sv_selection_extend_to (sv, pos.col, pos.row);
994 sheet_update (sheet);
997 if (edit_showed_dialog)
998 return TRUE; /* we already ignored the button release */
1000 switch (button) {
1001 case 1:
1002 case 2: {
1003 guint32 double_click_time;
1006 * If the second click is on a different cell than the
1007 * first one this cannot be a double-click
1009 if (already_selected) {
1010 g_object_get (gtk_widget_get_settings (GTK_WIDGET (canvas)),
1011 "gtk-double-click-time", &double_click_time,
1012 NULL);
1014 if ((ig->last_click_time + double_click_time) > gdk_event_get_time (event) &&
1015 wbcg_edit_start (wbcg, FALSE, FALSE)) {
1016 break;
1020 ig->last_click_time = gdk_event_get_time (event);
1021 ig->selecting = GNM_ITEM_GRID_SELECTING_CELL_RANGE;
1022 gnm_pane_slide_init (pane);
1023 gnm_simple_canvas_grab (item);
1024 break;
1027 case 3: scg_context_menu (scg, event, FALSE, FALSE);
1028 break;
1029 default:
1030 break;
1033 return TRUE;
1037 * Handle the selection
1040 static gboolean
1041 cb_extend_cell_range (GnmPane *pane, GnmPaneSlideInfo const *info)
1043 sv_selection_extend_to (scg_view (pane->simple.scg),
1044 info->col, info->row);
1045 return TRUE;
1048 static gboolean
1049 cb_extend_expr_range (GnmPane *pane, GnmPaneSlideInfo const *info)
1051 scg_rangesel_extend_to (pane->simple.scg, info->col, info->row);
1052 return TRUE;
1055 static gint
1056 cb_cursor_come_to_rest (GnmItemGrid *ig)
1058 Sheet const *sheet = scg_sheet (ig->scg);
1059 GocCanvas *canvas = ig->canvas_item.canvas;
1060 GnmPane *pane = GNM_PANE (canvas);
1061 GnmHLink *lnk;
1062 gint64 x, y;
1063 GnmCellPos pos;
1064 char const *tiptext;
1066 /* Be anal and look it up in case something has destroyed the link
1067 * since the last motion */
1068 x = ig->last_x;
1069 y = ig->last_y;
1070 pos.col = gnm_pane_find_col (pane, x, NULL);
1071 pos.row = gnm_pane_find_row (pane, y, NULL);
1073 lnk = gnm_sheet_hlink_find (sheet, &pos);
1074 if (lnk != NULL && (tiptext = gnm_hlink_get_tip (lnk)) != NULL) {
1075 g_return_val_if_fail (lnk == ig->cur_link, FALSE);
1077 if (ig->tip == NULL && strlen (tiptext) > 0) {
1078 GtkWidget *cw = GTK_WIDGET (canvas);
1079 int wx, wy;
1081 gnm_canvas_get_position (canvas, &wx, &wy,
1082 ig->last_x, ig->last_y);
1083 ig->tip = gnm_create_tooltip (cw);
1084 gtk_label_set_text (GTK_LABEL (ig->tip), tiptext);
1085 /* moving the tip window some pixels from wx,wy in order to
1086 * avoid a leave_notify event that would destroy the tip.
1087 * see #706659 */
1088 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (ig->tip)),
1089 wx + 10, wy + 10);
1090 gtk_widget_show_all (gtk_widget_get_toplevel (ig->tip));
1094 ig->tip_timer = 0;
1095 return FALSE;
1098 static gboolean
1099 item_grid_motion (GocItem *item, double x_, double y_)
1101 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1102 GocCanvas *canvas = item->canvas;
1103 GnmPane *pane = GNM_PANE (canvas);
1104 GnmPaneSlideHandler slide_handler = NULL;
1105 gint64 x = x_ * canvas->pixels_per_unit, y = y_ * canvas->pixels_per_unit;
1106 switch (ig->selecting) {
1107 case GNM_ITEM_GRID_NO_SELECTION:
1108 if (ig->cursor_timer == 0)
1109 ig->cursor_timer = g_timeout_add (100,
1110 (GSourceFunc)cb_cursor_motion, ig);
1111 if (ig->tip_timer != 0)
1112 g_source_remove (ig->tip_timer);
1113 ig->tip_timer = g_timeout_add (500,
1114 (GSourceFunc)cb_cursor_come_to_rest, ig);
1115 ig->last_x = x;
1116 ig->last_y = y;
1117 return TRUE;
1118 case GNM_ITEM_GRID_SELECTING_CELL_RANGE:
1119 slide_handler = &cb_extend_cell_range;
1120 break;
1121 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE:
1122 slide_handler = &cb_extend_expr_range;
1123 break;
1124 default:
1125 g_assert_not_reached ();
1128 gnm_pane_handle_motion (pane, canvas, x, y,
1129 GNM_PANE_SLIDE_X | GNM_PANE_SLIDE_Y |
1130 GNM_PANE_SLIDE_AT_COLROW_BOUND,
1131 slide_handler, NULL);
1132 return TRUE;
1135 static gboolean
1136 item_grid_button_released (GocItem *item, int button, G_GNUC_UNUSED double x_, G_GNUC_UNUSED double y_)
1138 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1139 GnmPane *pane = GNM_PANE (item->canvas);
1140 SheetControlGUI *scg = ig->scg;
1141 Sheet *sheet = scg_sheet (scg);
1142 ItemGridSelectionType selecting = ig->selecting;
1144 if (button != 1 && button != 2)
1145 return FALSE;
1147 gnm_pane_slide_stop (pane);
1149 switch (selecting) {
1150 case GNM_ITEM_GRID_NO_SELECTION:
1151 return TRUE;
1153 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE:
1154 /* Removal of this code (2 lines) */
1155 /* should fix http://bugzilla.gnome.org/show_bug.cgi?id=63485 */
1156 /* sheet_make_cell_visible (sheet, */
1157 /* sheet->edit_pos.col, sheet->edit_pos.row, FALSE); */
1158 /* Fall through */
1159 case GNM_ITEM_GRID_SELECTING_CELL_RANGE:
1160 sv_selection_simplify (scg_view (scg));
1161 wb_view_selection_desc (
1162 wb_control_view (scg_wbc (scg)), TRUE, NULL);
1163 break;
1165 default:
1166 g_assert_not_reached ();
1169 ig->selecting = GNM_ITEM_GRID_NO_SELECTION;
1170 gnm_simple_canvas_ungrab (item);
1172 if (selecting == GNM_ITEM_GRID_SELECTING_FORMULA_RANGE)
1173 gnm_expr_entry_signal_update (
1174 wbcg_get_entry_logical (scg_wbcg (scg)), TRUE);
1176 if (selecting == GNM_ITEM_GRID_SELECTING_CELL_RANGE && button == 1) {
1177 GnmCellPos const *pos = sv_is_singleton_selected (scg_view (scg));
1178 if (pos != NULL) {
1179 GnmHLink *lnk;
1180 /* check for hyper links */
1181 lnk = gnm_sheet_hlink_find (sheet, pos);
1182 if (lnk != NULL)
1183 gnm_hlink_activate (lnk, scg_wbcg (scg));
1186 return TRUE;
1189 static gboolean
1190 item_grid_enter_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
1192 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1193 scg_set_display_cursor (ig->scg);
1194 return TRUE;
1197 static gboolean
1198 item_grid_leave_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
1200 GnmItemGrid *ig = GNM_ITEM_GRID (item);
1201 ig_clear_hlink_tip (ig);
1202 if (ig->cursor_timer != 0) {
1203 g_source_remove (ig->cursor_timer);
1204 ig->cursor_timer = 0;
1206 return TRUE;
1209 static void
1210 gnm_item_grid_init (GnmItemGrid *ig)
1212 GocItem *item = GOC_ITEM (ig);
1214 item->x0 = 0;
1215 item->y0 = 0;
1216 item->x1 = 0;
1217 item->y1 = 0;
1219 ig->selecting = GNM_ITEM_GRID_NO_SELECTION;
1220 /* We need something at least as big as any sheet. */
1221 ig->bound.start.col = ig->bound.start.row = 0;
1222 ig->bound.end.col = GNM_MAX_COLS - 1;
1223 ig->bound.end.row = GNM_MAX_ROWS - 1;
1224 ig->cursor_timer = 0;
1225 ig->cur_link = NULL;
1226 ig->tip_timer = 0;
1227 ig->tip = NULL;
1230 static void
1231 item_grid_set_property (GObject *obj, guint param_id,
1232 GValue const *value, G_GNUC_UNUSED GParamSpec *pspec)
1234 GnmItemGrid *ig = GNM_ITEM_GRID (obj);
1235 GnmRange const *r;
1237 switch (param_id) {
1238 case GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI:
1239 ig->scg = g_value_get_object (value);
1240 break;
1242 case GNM_ITEM_GRID_PROP_BOUND:
1243 r = g_value_get_pointer (value);
1244 g_return_if_fail (r != NULL);
1245 ig->bound = *r;
1246 break;
1250 static void
1251 gnm_item_grid_class_init (GObjectClass *gobject_klass)
1253 GocItemClass *item_klass = (GocItemClass *) gobject_klass;
1255 parent_class = g_type_class_peek_parent (gobject_klass);
1257 gobject_klass->finalize = item_grid_finalize;
1258 gobject_klass->set_property = item_grid_set_property;
1259 g_object_class_install_property (gobject_klass, GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI,
1260 g_param_spec_object ("SheetControlGUI",
1261 P_("SheetControlGUI"),
1262 P_("The sheet control gui controlling the item"),
1263 GNM_SCG_TYPE,
1264 GSF_PARAM_STATIC | G_PARAM_WRITABLE));
1265 g_object_class_install_property (gobject_klass, GNM_ITEM_GRID_PROP_BOUND,
1266 g_param_spec_pointer ("bound",
1267 P_("Bound"),
1268 P_("The display bounds"),
1269 GSF_PARAM_STATIC | G_PARAM_WRITABLE));
1271 item_klass->realize = item_grid_realize;
1272 item_klass->unrealize = item_grid_unrealize;
1273 item_klass->draw_region = item_grid_draw_region;
1274 item_klass->update_bounds = item_grid_update_bounds;
1275 item_klass->button_pressed = item_grid_button_pressed;
1276 item_klass->button_released = item_grid_button_released;
1277 item_klass->motion = item_grid_motion;
1278 item_klass->enter_notify = item_grid_enter_notify;
1279 item_klass->leave_notify = item_grid_leave_notify;
1280 item_klass->distance = item_grid_distance;
1283 GSF_CLASS (GnmItemGrid, gnm_item_grid,
1284 gnm_item_grid_class_init, gnm_item_grid_init,
1285 GOC_TYPE_ITEM)