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.
7 * Miguel de Icaza (miguel@kernel.org)
8 * Jody Goldberg (jody@gnome.org)
12 #include <gnumeric-config.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>
21 #include <sheet-view.h>
22 #include <sheet-style.h>
23 #include <sheet-merge.h>
24 #include <sheet-object-impl.h>
26 #include <cell-draw.h>
29 #include <selection.h>
30 #include <parse-util.h>
32 #include <style-conditions.h>
33 #include <position.h> /* to eval conditions */
34 #include <style-border.h>
35 #include <style-color.h>
42 #include <goffice/goffice.h>
43 #include <gsf/gsf-impl-utils.h>
46 #define GNUMERIC_ITEM "GRID"
49 #define MERGE_DEBUG(range, str) do { range_dump (range, str); } while (0)
51 #define MERGE_DEBUG(range, str)
55 GNM_ITEM_GRID_NO_SELECTION
,
56 GNM_ITEM_GRID_SELECTING_CELL_RANGE
,
57 GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
58 } ItemGridSelectionType
;
65 ItemGridSelectionType selecting
;
69 /* information for the cursor motion handler */
71 gint64 last_x
, last_y
;
72 GnmHLink
*cur_link
; /* do not dereference, just a pointer */
76 GdkCursor
*cursor_link
, *cursor_cross
;
78 guint32 last_click_time
;
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
;
94 GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI
,
95 GNM_ITEM_GRID_PROP_BOUND
99 ig_reload_style (GnmItemGrid
*ig
)
101 GocItem
*item
= GOC_ITEM (ig
);
102 GtkStyleContext
*context
= goc_item_get_style_context (item
);
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
,
133 ig_clear_hlink_tip (GnmItemGrid
*ig
)
135 if (ig
->tip_timer
!= 0) {
136 g_source_remove (ig
->tip_timer
);
140 if (ig
->tip
!= NULL
) {
141 gtk_widget_destroy (gtk_widget_get_toplevel (ig
->tip
));
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
);
158 (*G_OBJECT_CLASS (parent_class
)->finalize
) (object
);
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
);
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
));
195 item_grid_realize (GocItem
*item
)
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
);
212 gdk_cursor_new_from_pixbuf (display
,
215 g_object_unref (cursor_cross_pixbuf
);
216 cb_cursor_motion (ig
);
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
);
229 item_grid_update_bounds (GocItem
*item
)
233 item
->x1
= G_MAXINT64
/2;
234 item
->y1
= G_MAXINT64
/2;
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
))
248 cairo_rectangle (cr
, x
, y
, w
+1, h
+1);
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.);
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);
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 */
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);
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
)
312 conds
= gnm_style_get_conditions (style
);
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
) ||
324 /* Remember X excludes the far pixels */
326 cairo_rectangle (cr
, l
, t
, r
-l
+1, b
-t
+1);
328 cairo_rectangle (cr
, r
, t
, l
-r
+1, b
-t
+1);
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);
348 ColRowInfo
*ri
= sheet_row_get (sheet
, range
->start
.row
);
350 if (ri
->needs_respan
)
351 row_calc_spans (ri
, cell
->pos
.row
, sheet
);
354 if (show_function_cell_markers
)
355 draw_function_marker (ig
, cell
, cr
, l
, t
,
358 l
, t
, r
- l
, b
- t
, -1,
359 show_extension_markers
);
361 if (show_function_cell_markers
)
362 draw_function_marker (ig
, cell
, cr
, r
, t
,
365 r
, t
, l
- r
, b
- t
, -1,
366 show_extension_markers
);
370 gnm_style_border_draw_diag (style
, cr
, l
, t
, r
, b
);
372 gnm_style_border_draw_diag (style
, cr
, r
, t
, l
, b
);
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
390 g_printerr ("x = %d, w = %d\n", x
, w
+1);
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);
399 gnm_style_border_draw_diag (style
, cr
, x
, y
, x
+w
, y
+h
);
403 merged_col_cmp (GnmRange
const *a
, GnmRange
const *b
)
405 return a
->start
.col
- b
->start
.col
;
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);
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.
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
;
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
);
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
));
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
);
499 for (; start_row
<= end_row
; ++start_row
) {
500 ri
= sheet_row_get_info (sheet
, start_row
);
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
)
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) */
521 if (canvas
->direction
== GOC_DIRECTION_LTR
)
522 gtk_render_background (ctxt
,
524 x0
- canvas
->scroll_x1
* scale
,
525 y0
- canvas
->scroll_y1
* scale
,
528 gtk_render_background (ctxt
,
530 canvas
->width
- x0
+ canvas
->scroll_x1
* scale
- width
,
531 y0
- canvas
->scroll_y1
* scale
,
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
);
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
);
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 */
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
);
594 for (col
= start_col
; col
<= end_col
; ++col
)
595 next_sr
.vertical
[col
] =
596 next_sr
.bottom
[col
] = none
;
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
) {
610 ptr
= *lag
= tmp
->next
;
611 if (r
->end
.row
< row
) {
612 tmp
->next
= merged_used
;
614 MERGE_DEBUG (r
, " : unused -> used\n");
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");
624 item_grid_draw_merged_range (cr
, ig
,
625 start_x
, y
, &view
, r
,
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
);
644 if (merged_active
!= NULL
) {
645 GnmRange
const *r
= merged_active
->data
;
646 if (r
->end
.col
== col
) {
648 merged_active
= merged_active
->next
;
649 if (r
->end
.row
<= row
) {
650 ptr
->next
= merged_used
;
652 MERGE_DEBUG (r
, " : active2 -> used\n");
654 ptr
->next
= merged_active_seen
;
655 merged_active_seen
= ptr
;
656 MERGE_DEBUG (r
, " : active2 -> seen\n");
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
;
672 merged_active
= merged_active
->next
;
673 if (r
->end
.row
<= row
) {
674 ptr
->next
= merged_used
;
676 MERGE_DEBUG (r
, " : active -> used\n");
678 /* in case something managed the bottom of a merge */
679 if (r
->end
.row
< row
)
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
)
689 x
+= dir
* scg_colrow_distance_get (
690 pane
->simple
.scg
, TRUE
, col
, last
+1);
693 if (first
< start_col
) {
695 sr
.vertical
[first
] = NULL
;
697 if (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
++) {
708 sr
.bottom
[i
] = NULL
;
710 sr
.vertical
[i
] = NULL
;
716 plain_draw
: /* a quick hack to deal with 142267 */
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
,
747 x
, y
, ci
->size_pixels
,
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
;
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
,
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
);
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);
799 if (show_function_cell_markers
)
800 draw_function_marker (ig
, cell
, cr
, real_x
, y
,
802 ri
->size_pixels
, dir
);
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
;
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
;
822 merged_active
= merged_active
->next
;
823 if (r
->end
.row
<= row
) {
824 ptr
->next
= merged_used
;
826 MERGE_DEBUG (r
, " : active3 -> used\n");
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
);
864 item_grid_distance (GocItem
*item
, G_GNUC_UNUSED
double x
, G_GNUC_UNUSED
double y
,
865 GocItem
**actual_item
)
871 /***********************************************************************/
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
;
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
);
896 /***************************************************************************/
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
);
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
))
923 if (pos
.row
>= gnm_sheet_get_max_rows (sheet
))
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
932 if (scg
->selected_objects
== NULL
)
933 wbcg_focus_cur_scg (wbcg
);
934 else if (wbc_gtk_get_guru (wbcg
) == NULL
)
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
);
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
);
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
);
962 /* While a guru is up ignore clicks */
963 if (wbc_gtk_get_guru (wbcg
) != NULL
)
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
))
971 if (button
== 1 && !sheet_selection_is_allowed (sheet
, &pos
))
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 */
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
,
1014 if ((ig
->last_click_time
+ double_click_time
) > gdk_event_get_time (event
) &&
1015 wbcg_edit_start (wbcg
, FALSE
, FALSE
)) {
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
);
1027 case 3: scg_context_menu (scg
, event
, FALSE
, FALSE
);
1037 * Handle the selection
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
);
1049 cb_extend_expr_range (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
1051 scg_rangesel_extend_to (pane
->simple
.scg
, info
->col
, info
->row
);
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
);
1064 char const *tiptext
;
1066 /* Be anal and look it up in case something has destroyed the link
1067 * since the last motion */
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
);
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.
1088 gtk_window_move (GTK_WINDOW (gtk_widget_get_toplevel (ig
->tip
)),
1090 gtk_widget_show_all (gtk_widget_get_toplevel (ig
->tip
));
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
);
1118 case GNM_ITEM_GRID_SELECTING_CELL_RANGE
:
1119 slide_handler
= &cb_extend_cell_range
;
1121 case GNM_ITEM_GRID_SELECTING_FORMULA_RANGE
:
1122 slide_handler
= &cb_extend_expr_range
;
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
);
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)
1147 gnm_pane_slide_stop (pane
);
1149 switch (selecting
) {
1150 case GNM_ITEM_GRID_NO_SELECTION
:
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); */
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
);
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
));
1180 /* check for hyper links */
1181 lnk
= gnm_sheet_hlink_find (sheet
, pos
);
1183 gnm_hlink_activate (lnk
, scg_wbcg (scg
));
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
);
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;
1210 gnm_item_grid_init (GnmItemGrid
*ig
)
1212 GocItem
*item
= GOC_ITEM (ig
);
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
;
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
);
1238 case GNM_ITEM_GRID_PROP_SHEET_CONTROL_GUI
:
1239 ig
->scg
= g_value_get_object (value
);
1242 case GNM_ITEM_GRID_PROP_BOUND
:
1243 r
= g_value_get_pointer (value
);
1244 g_return_if_fail (r
!= NULL
);
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"),
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",
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
,