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 "position.h" /* to eval conditions */
35 #include "style-border.h"
36 #include "style-color.h"
43 #include <goffice/goffice.h>
45 #include <gsf/gsf-impl-utils.h>
48 #define GNUMERIC_ITEM "GRID"
51 #define MERGE_DEBUG(range, str) do { range_dump (range, str); } while (0)
53 #define MERGE_DEBUG(range, str)
57 GNM_ITEM_GRID_NO_SELECTION
,
58 GNM_ITEM_GRID_SELECTING_CELL_RANGE
,
59 GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
60 } ItemGridSelectionType
;
67 ItemGridSelectionType selecting
;
71 /* information for the cursor motion handler */
73 gint64 last_x
, last_y
;
74 GnmHLink
*cur_link
; /* do not dereference, just a pointer */
78 GdkCursor
*cursor_link
, *cursor_cross
;
80 guint32 last_click_time
;
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
;
96 GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI
,
97 GNM_ITEM_GRID_PROP_BOUND
101 ig_reload_style (GnmItemGrid
*ig
)
103 GocItem
*item
= GOC_ITEM (ig
);
104 GtkStyleContext
*context
= goc_item_get_style_context (item
);
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
,
135 ig_clear_hlink_tip (GnmItemGrid
*ig
)
137 if (ig
->tip_timer
!= 0) {
138 g_source_remove (ig
->tip_timer
);
142 if (ig
->tip
!= NULL
) {
143 gtk_widget_destroy (gtk_widget_get_toplevel (ig
->tip
));
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
);
160 (*G_OBJECT_CLASS (parent_class
)->finalize
) (object
);
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
);
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
));
197 item_grid_realize (GocItem
*item
)
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
);
214 gdk_cursor_new_from_pixbuf (display
,
217 g_object_unref (cursor_cross_pixbuf
);
218 cb_cursor_motion (ig
);
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
);
231 item_grid_update_bounds (GocItem
*item
)
235 item
->x1
= G_MAXINT64
/2;
236 item
->y1
= G_MAXINT64
/2;
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
))
250 cairo_rectangle (cr
, x
, y
, w
+1, h
+1);
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.);
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);
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 */
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 conds
= gnm_style_get_conditions (style
);
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
) ||
326 /* Remember X excludes the far pixels */
328 cairo_rectangle (cr
, l
, t
, r
-l
+1, b
-t
+1);
330 cairo_rectangle (cr
, r
, t
, l
-r
+1, b
-t
+1);
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);
350 ColRowInfo
*ri
= sheet_row_get (sheet
, range
->start
.row
);
352 if (ri
->needs_respan
)
353 row_calc_spans (ri
, cell
->pos
.row
, sheet
);
356 if (show_function_cell_markers
)
357 draw_function_marker (ig
, cell
, cr
, l
, t
,
360 l
, t
, r
- l
, b
- t
, -1,
361 show_extension_markers
);
363 if (show_function_cell_markers
)
364 draw_function_marker (ig
, cell
, cr
, r
, t
,
367 r
, t
, l
- r
, b
- t
, -1,
368 show_extension_markers
);
372 gnm_style_border_draw_diag (style
, cr
, l
, t
, r
, b
);
374 gnm_style_border_draw_diag (style
, cr
, r
, t
, l
, b
);
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
392 g_printerr ("x = %d, w = %d\n", x
, w
+1);
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);
401 gnm_style_border_draw_diag (style
, cr
, x
, y
, x
+w
, y
+h
);
405 merged_col_cmp (GnmRange
const *a
, GnmRange
const *b
)
407 return a
->start
.col
- b
->start
.col
;
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);
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.
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
;
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 (ctxt
,
526 x0
- canvas
->scroll_x1
* scale
,
527 y0
- canvas
->scroll_y1
* scale
,
530 gtk_render_background (ctxt
,
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 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
);
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
);
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 */
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
);
596 for (col
= start_col
; col
<= end_col
; ++col
)
597 next_sr
.vertical
[col
] =
598 next_sr
.bottom
[col
] = none
;
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
) {
612 ptr
= *lag
= tmp
->next
;
613 if (r
->end
.row
< row
) {
614 tmp
->next
= merged_used
;
616 MERGE_DEBUG (r
, " : unused -> used\n");
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");
626 item_grid_draw_merged_range (cr
, ig
,
627 start_x
, y
, &view
, r
,
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
);
646 if (merged_active
!= NULL
) {
647 GnmRange
const *r
= merged_active
->data
;
648 if (r
->end
.col
== col
) {
650 merged_active
= merged_active
->next
;
651 if (r
->end
.row
<= row
) {
652 ptr
->next
= merged_used
;
654 MERGE_DEBUG (r
, " : active2 -> used\n");
656 ptr
->next
= merged_active_seen
;
657 merged_active_seen
= ptr
;
658 MERGE_DEBUG (r
, " : active2 -> seen\n");
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
;
674 merged_active
= merged_active
->next
;
675 if (r
->end
.row
<= row
) {
676 ptr
->next
= merged_used
;
678 MERGE_DEBUG (r
, " : active -> used\n");
680 /* in case something managed the bottom of a merge */
681 if (r
->end
.row
< row
)
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
)
691 x
+= dir
* scg_colrow_distance_get (
692 pane
->simple
.scg
, TRUE
, col
, last
+1);
695 if (first
< start_col
) {
697 sr
.vertical
[first
] = NULL
;
699 if (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
++) {
710 sr
.bottom
[i
] = NULL
;
712 sr
.vertical
[i
] = NULL
;
718 plain_draw
: /* a quick hack to deal with 142267 */
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
,
749 x
, y
, ci
->size_pixels
,
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
;
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
,
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
);
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);
801 if (show_function_cell_markers
)
802 draw_function_marker (ig
, cell
, cr
, real_x
, y
,
804 ri
->size_pixels
, dir
);
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
;
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
;
824 merged_active
= merged_active
->next
;
825 if (r
->end
.row
<= row
) {
826 ptr
->next
= merged_used
;
828 MERGE_DEBUG (r
, " : active3 -> used\n");
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
);
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
, 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
);
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
);
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
);
964 /* While a guru is up ignore clicks */
965 if (wbc_gtk_get_guru (wbcg
) != NULL
)
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
))
973 if (button
== 1 && !sheet_selection_is_allowed (sheet
, &pos
))
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 gnm_sheet_view_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 */
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
,
1016 if ((ig
->last_click_time
+ double_click_time
) > gdk_event_get_time (event
) &&
1017 wbcg_edit_start (wbcg
, FALSE
, FALSE
)) {
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
);
1029 case 3: scg_context_menu (scg
, event
, FALSE
, FALSE
);
1039 * Handle the selection
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
);
1051 cb_extend_expr_range (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
1053 scg_rangesel_extend_to (pane
->simple
.scg
, info
->col
, info
->row
);
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
);
1066 char const *tiptext
;
1068 /* Be anal and look it up in case something has destroyed the link
1069 * since the last motion */
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
);
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.
1090 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (ig
->tip
)),
1092 gtk_widget_show_all (gtk_widget_get_toplevel (ig
->tip
));
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
);
1120 case GNM_ITEM_GRID_SELECTING_CELL_RANGE
:
1121 slide_handler
= &cb_extend_cell_range
;
1123 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
:
1124 slide_handler
= &cb_extend_expr_range
;
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
);
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)
1149 gnm_pane_slide_stop (pane
);
1151 switch (selecting
) {
1152 case GNM_ITEM_GRID_NO_SELECTION
:
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); */
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
);
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
));
1182 /* check for hyper links */
1183 lnk
= gnm_sheet_hlink_find (sheet
, pos
);
1185 gnm_hlink_activate (lnk
, scg_wbcg (scg
));
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
);
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;
1212 gnm_item_grid_init (GnmItemGrid
*ig
)
1214 GocItem
*item
= GOC_ITEM (ig
);
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
;
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
);
1240 case GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI
:
1241 ig
->scg
= g_value_get_object (value
);
1244 case GNM_ITEM_GRID_PROP_BOUND
:
1245 r
= g_value_get_pointer (value
);
1246 g_return_if_fail (r
!= NULL
);
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"),
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",
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
,