1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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.
8 * Miguel de Icaza (miguel@kernel.org)
9 * Jody Goldberg (jody@gnome.org)
13 #include <gnumeric-config.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"
22 #include "sheet-view.h"
23 #include "sheet-style.h"
24 #include "sheet-merge.h"
25 #include "sheet-object-impl.h"
27 #include "cell-draw.h"
30 #include "selection.h"
31 #include "parse-util.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"
44 #include <goffice/goffice.h>
46 #include <gsf/gsf-impl-utils.h>
49 #define GNUMERIC_ITEM "GRID"
52 #define MERGE_DEBUG(range, str) do { range_dump (range, str); } while (0)
54 #define MERGE_DEBUG(range, str)
58 GNM_ITEM_GRID_NO_SELECTION
,
59 GNM_ITEM_GRID_SELECTING_CELL_RANGE
,
60 GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
61 } ItemGridSelectionType
;
68 ItemGridSelectionType selecting
;
72 /* information for the cursor motion handler */
74 gint64 last_x
, last_y
;
75 GnmHLink
*cur_link
; /* do not dereference, just a pointer */
79 GdkCursor
*cursor_link
, *cursor_cross
;
81 guint32 last_click_time
;
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
;
97 GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI
,
98 GNM_ITEM_GRID_PROP_BOUND
102 ig_reload_style (GnmItemGrid
*ig
)
104 GocItem
*item
= GOC_ITEM (ig
);
105 GtkStyleContext
*context
= goc_item_get_style_context (item
);
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
,
136 ig_clear_hlink_tip (GnmItemGrid
*ig
)
138 if (ig
->tip_timer
!= 0) {
139 g_source_remove (ig
->tip_timer
);
143 if (ig
->tip
!= NULL
) {
144 gtk_widget_destroy (gtk_widget_get_toplevel (ig
->tip
));
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
);
161 (*G_OBJECT_CLASS (parent_class
)->finalize
) (object
);
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
);
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
));
198 item_grid_realize (GocItem
*item
)
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
);
215 gdk_cursor_new_from_pixbuf (display
,
218 g_object_unref (cursor_cross_pixbuf
);
219 cb_cursor_motion (ig
);
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
);
232 item_grid_update_bounds (GocItem
*item
)
236 item
->x1
= G_MAXINT64
/2;
237 item
->y1
= G_MAXINT64
/2;
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
))
251 cairo_rectangle (cr
, x
, y
, w
+1, h
+1);
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.);
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);
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 */
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);
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
)
314 if (style
->conditions
) {
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
) ||
325 /* Remember X excludes the far pixels */
327 cairo_rectangle (cr
, l
, t
, r
-l
+1, b
-t
+1);
329 cairo_rectangle (cr
, r
, t
, l
-r
+1, b
-t
+1);
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);
349 ColRowInfo
*ri
= sheet_row_get (sheet
, range
->start
.row
);
351 if (ri
->needs_respan
)
352 row_calc_spans (ri
, cell
->pos
.row
, sheet
);
355 if (show_function_cell_markers
)
356 draw_function_marker (ig
, cell
, cr
, l
, t
,
359 l
, t
, r
- l
, b
- t
, -1,
360 show_extension_markers
);
362 if (show_function_cell_markers
)
363 draw_function_marker (ig
, cell
, cr
, r
, t
,
366 r
, t
, l
- r
, b
- t
, -1,
367 show_extension_markers
);
371 gnm_style_border_draw_diag (style
, cr
, l
, t
, r
, b
);
373 gnm_style_border_draw_diag (style
, cr
, r
, t
, l
, b
);
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
391 g_printerr ("x = %d, w = %d\n", x
, w
+1);
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);
400 gnm_style_border_draw_diag (style
, cr
, x
, y
, x
+w
, y
+h
);
404 merged_col_cmp (GnmRange
const *a
, GnmRange
const *b
)
406 return a
->start
.col
- b
->start
.col
;
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);
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.
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 ();
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
);
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
));
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
);
501 for (; start_row
<= end_row
; ++start_row
) {
502 ri
= sheet_row_get_info (sheet
, start_row
);
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
)
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) */
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
,
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
,
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
);
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
);
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 */
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
);
595 for (col
= start_col
; col
<= end_col
; ++col
)
596 next_sr
.vertical
[col
] =
597 next_sr
.bottom
[col
] = none
;
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
) {
611 ptr
= *lag
= tmp
->next
;
612 if (r
->end
.row
< row
) {
613 tmp
->next
= merged_used
;
615 MERGE_DEBUG (r
, " : unused -> used\n");
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");
625 item_grid_draw_merged_range (cr
, ig
,
626 start_x
, y
, &view
, r
,
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
);
645 if (merged_active
!= NULL
) {
646 GnmRange
const *r
= merged_active
->data
;
647 if (r
->end
.col
== col
) {
649 merged_active
= merged_active
->next
;
650 if (r
->end
.row
<= row
) {
651 ptr
->next
= merged_used
;
653 MERGE_DEBUG (r
, " : active2 -> used\n");
655 ptr
->next
= merged_active_seen
;
656 merged_active_seen
= ptr
;
657 MERGE_DEBUG (r
, " : active2 -> seen\n");
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
;
673 merged_active
= merged_active
->next
;
674 if (r
->end
.row
<= row
) {
675 ptr
->next
= merged_used
;
677 MERGE_DEBUG (r
, " : active -> used\n");
679 /* in case something managed the bottom of a merge */
680 if (r
->end
.row
< row
)
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
)
690 x
+= dir
* scg_colrow_distance_get (
691 pane
->simple
.scg
, TRUE
, col
, last
+1);
694 if (first
< start_col
) {
696 sr
.vertical
[first
] = NULL
;
698 if (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
++) {
709 sr
.bottom
[i
] = NULL
;
711 sr
.vertical
[i
] = NULL
;
717 plain_draw
: /* a quick hack to deal with 142267 */
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
,
748 x
, y
, ci
->size_pixels
,
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
;
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
,
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
);
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);
800 if (show_function_cell_markers
)
801 draw_function_marker (ig
, cell
, cr
, real_x
, y
,
803 ri
->size_pixels
, dir
);
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
;
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
;
823 merged_active
= merged_active
->next
;
824 if (r
->end
.row
<= row
) {
825 ptr
->next
= merged_used
;
827 MERGE_DEBUG (r
, " : active3 -> used\n");
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
);
866 item_grid_distance (GocItem
*item
, G_GNUC_UNUSED
double x
, G_GNUC_UNUSED
double y
,
867 GocItem
**actual_item
)
873 /***********************************************************************/
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
;
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
);
898 /***************************************************************************/
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
);
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
))
925 if (pos
.row
>= gnm_sheet_get_max_rows (sheet
))
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
934 if (scg
->selected_objects
== NULL
)
935 wbcg_focus_cur_scg (wbcg
);
936 else if (wbc_gtk_get_guru (wbcg
) == NULL
)
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
);
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
));
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
));
968 /* While a guru is up ignore clicks */
969 if (wbc_gtk_get_guru (wbcg
) != NULL
)
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
))
977 if (button
== 1 && !sheet_selection_is_allowed (sheet
, &pos
))
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 */
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
,
1020 if ((ig
->last_click_time
+ double_click_time
) > gdk_event_get_time (event
) &&
1021 wbcg_edit_start (wbcg
, FALSE
, FALSE
)) {
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
));
1035 case 3: scg_context_menu (scg
, event
, FALSE
, FALSE
);
1045 * Handle the selection
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
);
1057 cb_extend_expr_range (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
1059 scg_rangesel_extend_to (pane
->simple
.scg
, info
->col
, info
->row
);
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
);
1072 char const *tiptext
;
1074 /* Be anal and look it up in case something has destroyed the link
1075 * since the last motion */
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
);
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.
1096 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (ig
->tip
)),
1098 gtk_widget_show_all (gtk_widget_get_toplevel (ig
->tip
));
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
);
1126 case GNM_ITEM_GRID_SELECTING_CELL_RANGE
:
1127 slide_handler
= &cb_extend_cell_range
;
1129 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
:
1130 slide_handler
= &cb_extend_expr_range
;
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
);
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)
1156 gnm_pane_slide_stop (pane
);
1158 switch (selecting
) {
1159 case GNM_ITEM_GRID_NO_SELECTION
:
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); */
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
);
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
));
1189 /* check for hyper links */
1190 link
= sheet_hlink_find (sheet
, pos
);
1192 gnm_hlink_activate (link
, scg_wbcg (scg
));
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
);
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;
1219 gnm_item_grid_init (GnmItemGrid
*ig
)
1221 GocItem
*item
= GOC_ITEM (ig
);
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
;
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
);
1247 case GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI
:
1248 ig
->scg
= g_value_get_object (value
);
1251 case GNM_ITEM_GRID_PROP_BOUND
:
1252 r
= g_value_get_pointer (value
);
1253 g_return_if_fail (r
!= NULL
);
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",
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
,