1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * A canvas item implementing row/col headers with support for outlining.
6 * Miguel de Icaza (miguel@kernel.org)
7 * Jody Goldberg (jody@gnome.org)
10 #include <gnumeric-config.h>
14 #include "gnm-pane-impl.h"
16 #include "style-color.h"
18 #include "sheet-control-gui.h"
19 #include "sheet-control-gui-priv.h"
20 #include "application.h"
21 #include "selection.h"
22 #include "wbc-gtk-impl.h"
24 #include "parse-util.h"
28 #include <goffice/goffice.h>
29 #include <gsf/gsf-impl-utils.h>
31 #define GNUMERIC_ITEM "BAR"
39 GdkCursor
*normal_cursor
;
40 GdkCursor
*change_cursor
;
41 GtkWidget
*tip
; /* Tip for scrolling */
43 gboolean is_col_header
;
44 gboolean has_resize_guides
;
45 int indent
, cell_width
, cell_height
;
46 int start_selection
; /* Where selection started */
47 int colrow_being_resized
;
48 int colrow_resize_size
;
53 PangoGlyphString
*glyphs
;
58 /* [ColRowSelectionType] */
59 GdkRGBA selection_colors
[3];
60 PangoFont
*selection_fonts
[3];
61 int selection_font_ascents
[3];
62 PangoRectangle selection_logical_sizes
[3];
63 GtkStyleContext
*styles
[3];
65 GdkRGBA grouping_color
;
70 typedef GocItemClass GnmItemBarClass
;
71 static GocItemClass
*parent_class
;
75 GNM_ITEM_BAR_PROP_PANE
,
76 GNM_ITEM_BAR_PROP_IS_COL_HEADER
80 ib_compute_pixels_from_indent (GnmItemBar
*ib
, Sheet
const *sheet
)
82 gboolean is_cols
= ib
->is_col_header
;
84 sheet
->last_zoom_factor_used
*
85 gnm_app_display_dpi_get (is_cols
) / 72.;
86 int const indent
= is_cols
87 ? sheet
->cols
.max_outline_level
88 : sheet
->rows
.max_outline_level
;
89 if (!sheet
->display_outlines
|| indent
<= 0)
91 return (int)(ib
->padding
.left
+ (indent
+ 1) * 14 * scale
+ 0.5);
95 ib_dispose_fonts (GnmItemBar
*ib
)
99 for (ui
= 0; ui
< G_N_ELEMENTS (ib
->selection_fonts
); ui
++)
100 g_clear_object (&ib
->selection_fonts
[ui
]);
103 static const GtkStateFlags selection_type_flags
[3] = {
104 GTK_STATE_FLAG_NORMAL
,
105 GTK_STATE_FLAG_PRELIGHT
,
106 GTK_STATE_FLAG_ACTIVE
109 static const char * const selection_styles
[3] = {
111 "button.itembar:hover",
112 "button.itembar:active"
116 ib_reload_color_style (GnmItemBar
*ib
)
118 GocItem
*item
= GOC_ITEM (ib
);
119 GtkStyleContext
*context
= goc_item_get_style_context (item
);
122 gnm_style_context_get_color (context
, GTK_STATE_FLAG_NORMAL
,
123 &ib
->grouping_color
);
125 for (ui
= 0; ui
< G_N_ELEMENTS (selection_type_flags
); ui
++) {
126 GtkStateFlags state
= selection_type_flags
[ui
];
127 gnm_style_context_get_color
128 (context
, state
, &ib
->selection_colors
[ui
]);
134 ib_reload_sizing_style (GnmItemBar
*ib
)
136 GocItem
*item
= GOC_ITEM (ib
);
137 SheetControlGUI
* const scg
= ib
->pane
->simple
.scg
;
138 Sheet
const *sheet
= scg_sheet (scg
);
139 double const zoom_factor
= sheet
->last_zoom_factor_used
;
140 gboolean
const char_label
=
141 ib
->is_col_header
&& !sheet
->convs
->r1c1_addresses
;
143 PangoContext
*pcontext
=
144 gtk_widget_get_pango_context (GTK_WIDGET (item
->canvas
));
145 PangoLayout
*layout
= pango_layout_new (pcontext
);
146 PangoAttrList
*attr_list
;
149 for (ui
= 0; ui
< G_N_ELEMENTS (selection_type_flags
); ui
++) {
150 GtkStateFlags state
= selection_type_flags
[ui
];
151 PangoFontDescription
*desc
;
152 PangoRectangle ink_rect
;
153 const char *long_name
;
154 GtkStyleContext
*context
;
156 g_clear_object (&ib
->styles
[ui
]);
157 #if GTK_CHECK_VERSION(3,20,0)
158 context
= go_style_context_from_selector (NULL
, selection_styles
[ui
]);
160 context
= g_object_ref (goc_item_get_style_context (item
));
163 ib
->styles
[ui
] = context
;
164 gtk_style_context_save (context
);
165 #if !GTK_CHECK_VERSION(3,20,0)
166 gtk_style_context_set_state (context
, state
);
168 gtk_style_context_get (context
, state
, "font", &desc
, NULL
);
169 pango_font_description_set_size (desc
,
170 zoom_factor
* pango_font_description_get_size (desc
));
171 ib
->selection_fonts
[ui
] =
172 pango_context_load_font (pcontext
, desc
);
173 if (!ib
->selection_fonts
[ui
]) {
175 pango_font_description_set_family (desc
, "Sans");
176 ib
->selection_fonts
[ui
] =
177 pango_context_load_font (pcontext
, desc
);
181 * Figure out how tall the label can be.
182 * (Note that we avoid J/Q/Y which may go below the line.)
184 pango_layout_set_text (layout
,
185 char_label
? "AHW" : "0123456789",
187 pango_layout_set_font_description (layout
, desc
);
188 pango_font_description_free (desc
);
189 pango_layout_get_extents (layout
, &ink_rect
, NULL
);
190 ib
->selection_font_ascents
[ui
] =
191 PANGO_PIXELS (ink_rect
.height
+ ink_rect
.y
);
193 /* The width of the widest string I can think of + padding */
194 if (ib
->is_col_header
) {
195 int last
= gnm_sheet_get_last_col (sheet
);
196 long_name
= char_label
? col_name (last
) : /* HACK: */row_name (last
);
198 long_name
= row_name (gnm_sheet_get_last_row (sheet
));
199 pango_layout_set_text
201 char_label
? "WWWWWWWWWW" : "8888888888",
203 pango_layout_get_extents (layout
, NULL
,
204 &ib
->selection_logical_sizes
[ui
]);
206 if (state
== GTK_STATE_FLAG_NORMAL
)
207 gtk_style_context_get_padding (context
, state
,
210 gtk_style_context_restore (context
);
213 attr_list
= pango_attr_list_new ();
214 item_list
= pango_itemize (pcontext
, "A", 0, 1, attr_list
, NULL
);
215 pango_attr_list_unref (attr_list
);
217 pango_item_free (ib
->pango
.item
);
218 ib
->pango
.item
= item_list
->data
;
219 item_list
->data
= NULL
;
220 if (item_list
->next
!= NULL
)
221 g_warning ("Leaking pango items");
222 g_list_free (item_list
);
224 g_object_unref (layout
);
228 * gnm_item_bar_calc_size:
231 * Scale fonts and sizes by the pixels_per_unit of the associated sheet.
233 * Returns : the size of the fixed dimension.
236 gnm_item_bar_calc_size (GnmItemBar
*ib
)
238 SheetControlGUI
* const scg
= ib
->pane
->simple
.scg
;
239 Sheet
const *sheet
= scg_sheet (scg
);
243 ib_dispose_fonts (ib
);
244 ib_reload_sizing_style (ib
);
248 for (ui
= 0; ui
< G_N_ELEMENTS (ib
->selection_logical_sizes
); ui
++) {
249 int h
= PANGO_PIXELS (ib
->selection_logical_sizes
[ui
].height
) +
250 (ib
->padding
.top
+ ib
->padding
.bottom
);
251 int w
= PANGO_PIXELS (ib
->selection_logical_sizes
[ui
].width
) +
252 (ib
->padding
.left
+ ib
->padding
.right
);
253 ib
->cell_height
= MAX (ib
->cell_height
, h
);
254 ib
->cell_width
= MAX (ib
->cell_width
, w
);
257 size
= ib_compute_pixels_from_indent (ib
, sheet
);
258 if (size
!= ib
->indent
) {
260 goc_item_bounds_changed (GOC_ITEM (ib
));
264 (ib
->is_col_header
? ib
->cell_height
: ib
->cell_width
);
268 * item_bar_normal_font:
271 * Returns: (transfer full): the bar normal font.
273 PangoFontDescription
*
274 item_bar_normal_font (GnmItemBar
const *ib
)
276 return pango_font_describe (ib
->selection_fonts
[COL_ROW_NO_SELECTION
]);
280 gnm_item_bar_indent (GnmItemBar
const *ib
)
286 item_bar_update_bounds (GocItem
*item
)
288 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
291 if (ib
->is_col_header
) {
292 item
->x1
= G_MAXINT64
/2;
293 item
->y1
= (ib
->cell_height
+ ib
->indent
);
295 item
->x1
= (ib
->cell_width
+ ib
->indent
);
296 item
->y1
= G_MAXINT64
/2;
301 item_bar_realize (GocItem
*item
)
303 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
306 parent_class
->realize (item
);
308 display
= gtk_widget_get_display (GTK_WIDGET (item
->canvas
));
310 gdk_cursor_new_for_display (display
, GDK_LEFT_PTR
);
312 gdk_cursor_new_for_display (display
,
314 ? GDK_SB_H_DOUBLE_ARROW
315 : GDK_SB_V_DOUBLE_ARROW
);
317 gnm_item_bar_calc_size (ib
);
321 item_bar_unrealize (GocItem
*item
)
323 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
325 g_clear_object (&ib
->change_cursor
);
326 g_clear_object (&ib
->normal_cursor
);
328 parent_class
->unrealize (item
);
332 ib_draw_cell (GnmItemBar
const * const ib
, cairo_t
*cr
,
333 ColRowSelectionType
const type
,
334 char const * const str
, GocRect
*rect
)
336 GtkStyleContext
*ctxt
= ib
->styles
[type
];
338 g_return_if_fail ((size_t)type
< G_N_ELEMENTS (selection_type_flags
));
342 gtk_style_context_save (ctxt
);
343 #if !GTK_CHECK_VERSION(3,20,0)
344 gtk_style_context_set_state (ctxt
, selection_type_flags
[type
]);
346 gtk_render_background (ctxt
, cr
, rect
->x
, rect
->y
,
347 rect
->width
+ 1, rect
->height
+ 1);
349 /* When we are really small leave out the shadow and the text */
350 if (rect
->width
>= 2 && rect
->height
>= 2) {
352 PangoFont
*font
= ib
->selection_fonts
[type
];
353 int ascent
= ib
->selection_font_ascents
[type
];
357 g_return_if_fail (font
!= NULL
);
358 g_object_unref (ib
->pango
.item
->analysis
.font
);
359 ib
->pango
.item
->analysis
.font
= g_object_ref (font
);
360 pango_shape (str
, strlen (str
),
361 &(ib
->pango
.item
->analysis
), ib
->pango
.glyphs
);
362 pango_glyph_string_extents (ib
->pango
.glyphs
, font
,
365 gtk_render_frame (ctxt
, cr
, rect
->x
, rect
->y
,
366 rect
->width
+ 1, rect
->height
+ 1);
367 w
= rect
->width
- (ib
->padding
.left
+ ib
->padding
.right
);
368 h
= rect
->height
- (ib
->padding
.top
+ ib
->padding
.bottom
);
369 cairo_rectangle (cr
, rect
->x
+ 1, rect
->y
+ 1,
370 rect
->width
- 2, rect
->height
- 2);
373 gtk_style_context_get_color (ctxt
, selection_type_flags
[type
], &c
);
374 gdk_cairo_set_source_rgba (cr
, &c
);
377 rect
->x
+ ib
->padding
.left
+
378 (w
- PANGO_PIXELS (size
.width
)) / 2,
379 rect
->y
+ ib
->padding
.top
+ ascent
+
380 (h
- PANGO_PIXELS (size
.height
)) / 2);
381 pango_cairo_show_glyph_string (cr
, font
, ib
->pango
.glyphs
);
383 gtk_style_context_restore (ctxt
);
388 gnm_item_bar_group_size (GnmItemBar
const *ib
, int max_outline
)
390 return max_outline
> 0
391 ? (ib
->indent
- 2) / (max_outline
+ 1)
396 item_bar_draw_region (GocItem
const *item
, cairo_t
*cr
,
397 double x_0
, double y_0
, double x_1
, double y_1
)
399 double scale
= item
->canvas
->pixels_per_unit
;
401 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
402 GnmPane
const *pane
= ib
->pane
;
403 SheetControlGUI
const *scg
= pane
->simple
.scg
;
404 Sheet
const *sheet
= scg_sheet (scg
);
405 SheetView
const *sv
= scg_view (scg
);
406 ColRowInfo
const *cri
, *next
= NULL
;
408 gboolean prev_visible
;
409 gboolean
const draw_below
= sheet
->outline_symbols_below
!= FALSE
;
410 gboolean
const draw_right
= sheet
->outline_symbols_right
!= FALSE
;
414 gboolean
const has_object
= scg
->wbcg
->new_object
!= NULL
|| scg
->selected_objects
!= NULL
;
415 gboolean
const rtl
= sheet
->text_is_rtl
!= FALSE
;
417 int first_line_offset
= 1;
418 GtkStyleContext
*ctxt
= goc_item_get_style_context (item
);
420 gtk_style_context_save (ctxt
);
421 goc_canvas_c2w (item
->canvas
, x_0
, y_0
, &x0
, &y0
);
422 goc_canvas_c2w (item
->canvas
, x_1
, y_1
, &x1
, &y1
);
424 ib_reload_color_style (ib
);
426 if (ib
->is_col_header
) {
427 int const inc
= gnm_item_bar_group_size (ib
, sheet
->cols
.max_outline_level
);
428 int const base_pos
= .2 * inc
;
429 int const len
= (inc
> 4) ? 4 : inc
;
430 int end
, total
, col
= pane
->first
.col
;
431 gboolean
const char_label
= !sheet
->convs
->r1c1_addresses
;
433 /* shadow type selection must be keep in sync with code in ib_draw_cell */
434 goc_canvas_c2w (item
->canvas
, pane
->first_offset
.x
/ scale
, 0, &total
, NULL
);
437 rect
.height
= ib
->cell_height
;
438 shadow
= (col
> 0 && !has_object
&& sv_selection_col_type (sv
, col
-1) == COL_ROW_FULL_SELECTION
)
439 ? GTK_SHADOW_IN
: GTK_SHADOW_OUT
;
442 cri
= sheet_col_get_info (sheet
, col
-1);
443 prev_visible
= cri
->visible
;
444 prev_level
= cri
->outline_level
;
451 if (col
>= gnm_sheet_get_max_cols (sheet
))
454 /* DO NOT enable resizing all until we get rid of
455 * resize_start_pos. It will be wrong if things ahead
458 cri
= sheet_col_get_info (sheet
, col
);
459 if (col
!= -1 && ib
->colrow_being_resized
== col
)
460 /* || sv_is_colrow_selected (sheet, col, TRUE))) */
461 pixels
= ib
->colrow_resize_size
;
463 pixels
= cri
->size_pixels
;
466 int left
, level
, i
= 0, pos
= base_pos
;
469 left
= (total
-= pixels
);
472 rect
.x
= left
= total
;
477 ib_draw_cell (ib
, cr
,
479 ? COL_ROW_NO_SELECTION
480 : sv_selection_col_type (sv
, col
),
488 cairo_set_line_width (cr
, 2.0);
489 cairo_set_line_cap (cr
, CAIRO_LINE_CAP_BUTT
);
490 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_MITER
);
491 gdk_cairo_set_source_rgba (cr
, &ib
->grouping_color
);
493 next
= sheet_col_get_info (sheet
, col
+ 1);
494 prev_level
= next
->outline_level
;
495 prev_visible
= next
->visible
;
496 points
[0].x
= rtl
? (total
+ pixels
) : left
;
500 /* draw the start or end marks and the vertical lines */
501 points
[1].x
= points
[2].x
= left
+ pixels
/2;
502 for (level
= cri
->outline_level
; i
++ < level
; pos
+= inc
) {
503 points
[0].y
= points
[1].y
= pos
;
504 points
[2].y
= pos
+ len
;
505 if (i
> prev_level
) {
506 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
507 cairo_line_to (cr
, points
[1].x
, points
[1].y
);
508 cairo_line_to (cr
, points
[2].x
, points
[2].y
);
510 cairo_move_to (cr
, left
- first_line_offset
, pos
);
511 cairo_line_to (cr
, total
+ pixels
, pos
);
513 cairo_move_to (cr
, left
- first_line_offset
, pos
);
514 cairo_line_to (cr
, total
, pos
);
518 first_line_offset
= 0;
520 if (draw_right
^ rtl
) {
521 if (prev_level
> level
) {
523 int top
= pos
- base_pos
+ 2; /* inside cell's shadow */
524 int size
= inc
< pixels
? inc
: pixels
;
531 gtk_style_context_set_state (ctxt
, prev_visible
?
532 GTK_STATE_FLAG_NORMAL
: GTK_STATE_FLAG_SELECTED
);
533 gtk_render_frame (ctxt
, cr
,
534 left
, top
+safety
, size
, size
);
539 cairo_move_to (cr
, left
+size
/2, top
+3);
540 cairo_line_to (cr
, left
+size
/2, top
+size
-4);
542 cairo_move_to (cr
, left
+3, top
+size
/2);
543 cairo_line_to (cr
, left
+size
-4, top
+size
/2);
547 cairo_move_to (cr
, left
+pixels
/2, pos
);
548 cairo_line_to (cr
, left
+pixels
/2, pos
+len
);
551 if (prev_level
> level
) {
553 int top
= pos
- base_pos
+ 2; /* inside cell's shadow */
554 int size
= inc
< pixels
? inc
: pixels
;
562 right
= (rtl
? (total
+ pixels
) : total
) - size
;
563 gtk_style_context_set_state (ctxt
, prev_visible
?
564 GTK_STATE_FLAG_NORMAL
: GTK_STATE_FLAG_SELECTED
);
565 gtk_render_frame (ctxt
, cr
,
566 right
, top
+safety
, size
, size
);
571 cairo_move_to (cr
, right
+size
/2, top
+3);
572 cairo_line_to (cr
, right
+size
/2, top
+size
-4);
574 cairo_move_to (cr
, right
+3, top
+size
/2);
575 cairo_line_to (cr
, right
+size
-4, top
+size
/2);
577 } else if (level
> 0) {
578 cairo_move_to (cr
, left
+pixels
/2, pos
);
579 cairo_line_to (cr
, left
+pixels
/2, pos
+len
);
586 prev_visible
= cri
->visible
;
587 prev_level
= cri
->outline_level
;
589 } while ((rtl
&& end
<= total
) || (!rtl
&& total
<= end
));
591 int const inc
= gnm_item_bar_group_size (ib
, sheet
->rows
.max_outline_level
);
592 int base_pos
= .2 * inc
;
593 int const len
= (inc
> 4) ? 4 : inc
;
595 int const dir
= rtl
? -1 : 1;
597 int total
= pane
->first_offset
.y
- item
->canvas
->scroll_y1
* scale
;
598 int row
= pane
->first
.row
;
601 base_pos
= ib
->indent
+ ib
->cell_width
- base_pos
;
602 /* Move header bar 1 pixel to the left. */
606 rect
.width
= ib
->cell_width
;
609 cri
= sheet_row_get_info (sheet
, row
-1);
610 prev_visible
= cri
->visible
;
611 prev_level
= cri
->outline_level
;
618 if (row
>= gnm_sheet_get_max_rows (sheet
))
621 /* DO NOT enable resizing all until we get rid of
622 * resize_start_pos. It will be wrong if things ahead
625 cri
= sheet_row_get_info (sheet
, row
);
626 if (row
!= -1 && ib
->colrow_being_resized
== row
)
627 /* || sv_is_colrow_selected (sheet, row, FALSE))) */
628 pixels
= ib
->colrow_resize_size
;
630 pixels
= cri
->size_pixels
;
633 int level
, i
= 0, pos
= base_pos
;
638 rect
.height
= pixels
;
639 ib_draw_cell (ib
, cr
,
641 ? COL_ROW_NO_SELECTION
642 : sv_selection_row_type (sv
, row
),
643 row_name (row
), &rect
);
647 cairo_set_line_width (cr
, 2.0);
648 cairo_set_line_cap (cr
, CAIRO_LINE_CAP_BUTT
);
649 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_MITER
);
650 gdk_cairo_set_source_rgba (cr
, &ib
->grouping_color
);
652 next
= sheet_row_get_info (sheet
, row
+ 1);
657 /* draw the start or end marks and the vertical lines */
658 points
[1].y
= points
[2].y
= top
+ pixels
/2;
659 for (level
= cri
->outline_level
; i
++ < level
; pos
+= inc
* dir
) {
660 points
[0].x
= points
[1].x
= pos
;
661 points
[2].x
= pos
+ len
* dir
;
662 if (draw_below
&& i
> prev_level
) {
663 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
664 cairo_line_to (cr
, points
[1].x
, points
[1].y
);
665 cairo_line_to (cr
, points
[2].x
, points
[2].y
);
666 } else if (!draw_below
&& i
> next
->outline_level
) {
667 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
668 cairo_line_to (cr
, points
[1].x
, points
[1].y
);
669 cairo_line_to (cr
, points
[2].x
, points
[2].y
);
671 cairo_move_to (cr
, pos
, top
- first_line_offset
);
672 cairo_line_to (cr
, pos
, total
);
676 first_line_offset
= 0;
679 if (prev_level
> level
) {
680 int left
, safety
= 0;
681 int size
= inc
< pixels
? inc
: pixels
;
688 /* inside cell's shadow */
689 left
= pos
- dir
* (.2 * inc
- 2);
692 gtk_style_context_set_state (ctxt
, prev_visible
?
693 GTK_STATE_FLAG_NORMAL
: GTK_STATE_FLAG_SELECTED
);
694 gtk_render_frame (ctxt
, cr
,
695 left
+safety
, top
, size
, size
);
700 cairo_move_to (cr
, left
+size
/2, top
+3);
701 cairo_line_to (cr
, left
+size
/2, top
+size
-4);
703 cairo_move_to (cr
, left
+3, top
+size
/2);
704 cairo_line_to (cr
, left
+size
-4, top
+size
/2);
708 cairo_move_to (cr
, pos
, top
+pixels
/2);
709 cairo_line_to (cr
, pos
+len
, top
+pixels
/2);
712 if (next
->outline_level
> level
) {
713 int left
, safety
= 0;
714 int size
= inc
< pixels
? inc
: pixels
;
722 /* inside cell's shadow */
723 left
= pos
- dir
* (.2 * inc
- 2);
726 bottom
= total
- size
;
727 gtk_style_context_set_state (ctxt
, prev_visible
?
728 GTK_STATE_FLAG_NORMAL
: GTK_STATE_FLAG_SELECTED
);
729 gtk_render_frame (ctxt
,cr
,
730 left
+safety
*dir
, bottom
, size
, size
);
732 if (!next
->visible
) {
735 cairo_move_to (cr
, left
+size
/2, bottom
+3);
736 cairo_line_to (cr
, left
+size
/2, bottom
+size
-4);
738 cairo_move_to (cr
, left
+3, bottom
+size
/2);
739 cairo_line_to (cr
, left
+size
-4, bottom
+size
/2);
741 } else if (level
> 0)
742 cairo_move_to (cr
, pos
, top
+pixels
/2);
743 cairo_line_to (cr
, pos
+len
, top
+pixels
/2);
749 prev_visible
= cri
->visible
;
750 prev_level
= cri
->outline_level
;
752 } while (total
<= end
);
754 gtk_style_context_restore (ctxt
);
759 item_bar_distance (GocItem
*item
, double x
, double y
,
760 GocItem
**actual_item
)
767 * is_pointer_on_division:
769 * @x: in world coords
770 * @y: in world coords
775 * NOTE : this could easily be optimized. We need not start at 0 every time.
776 * We could potentially use the routines in gnm-pane.
778 * Returns non-NULL if point (@x,@y) is on a division
780 static ColRowInfo
const *
781 is_pointer_on_division (GnmItemBar
const *ib
, gint64 x
, gint64 y
,
782 gint64
*the_total
, int *the_element
, gint64
*minor_pos
)
784 Sheet
*sheet
= scg_sheet (ib
->pane
->simple
.scg
);
785 ColRowInfo
const *cri
;
786 gint64 major
, minor
, total
= 0;
789 if (ib
->is_col_header
) {
792 i
= ib
->pane
->first
.col
;
793 total
= ib
->pane
->first_offset
.x
;
797 i
= ib
->pane
->first
.row
;
798 total
= ib
->pane
->first_offset
.y
;
800 if (NULL
!= minor_pos
)
802 if (NULL
!= the_element
)
804 for (; total
< major
; i
++) {
805 if (ib
->is_col_header
) {
806 if (i
>= gnm_sheet_get_max_cols (sheet
))
808 cri
= sheet_col_get_info (sheet
, i
);
810 if (i
>= gnm_sheet_get_max_rows (sheet
))
812 cri
= sheet_row_get_info (sheet
, i
);
816 WBCGtk
*wbcg
= scg_wbcg (ib
->pane
->simple
.scg
);
817 total
+= cri
->size_pixels
;
819 if (wbc_gtk_get_guru (wbcg
) == NULL
&&
820 !wbcg_is_editing (wbcg
) &&
821 (total
- 4 < major
) && (major
< total
+ 4)) {
826 return (minor
>= ib
->indent
) ? cri
: NULL
;
839 /* x & y in world coords */
841 ib_set_cursor (GnmItemBar
*ib
, gint64 x
, gint64 y
)
843 GdkWindow
*window
= gtk_widget_get_window (GTK_WIDGET (ib
->base
.canvas
));
844 GdkCursor
*cursor
= ib
->normal_cursor
;
846 /* We might be invoked before we are realized */
849 if (NULL
!= is_pointer_on_division (ib
, x
, y
, NULL
, NULL
, NULL
))
850 cursor
= ib
->change_cursor
;
851 gdk_window_set_cursor (window
, cursor
);
855 colrow_tip_setlabel (GnmItemBar
*ib
, gboolean
const is_cols
, int size_pixels
)
857 if (ib
->tip
!= NULL
) {
858 char *buffer
, *points
, *pixels
;
859 char const *label
= is_cols
? _("Width:") : _("Height");
860 double const scale
= 72. / gnm_app_display_dpi_get (!is_cols
);
861 double size_points
= scale
*size_pixels
;
863 /* xgettext: This is input to ngettext based on the number of pixels. */
864 pixels
= g_strdup_printf (ngettext ("(%d pixel)", "(%d pixels)", size_pixels
),
867 if (size_points
== gnm_floor (size_points
))
868 /* xgettext: This is input to ngettext based on the integer number of points. */
869 points
= g_strdup_printf (ngettext (_("%d.00 pt"), _("%d.00 pts"), (int) gnm_floor (size_points
)),
870 (int) gnm_floor (size_points
));
872 /* xgettext: The number of points here is always a fractional number, ie. not an integer. */
873 points
= g_strdup_printf (_("%.2f pts"), size_points
);
875 buffer
= g_strconcat (label
, " ", points
, " ", pixels
, NULL
);
878 gtk_label_set_text (GTK_LABEL (ib
->tip
), buffer
);
884 item_bar_resize_stop (GnmItemBar
*ib
, int new_size
)
886 if (ib
->colrow_being_resized
!= -1) {
888 scg_colrow_size_set (ib
->pane
->simple
.scg
,
890 ib
->colrow_being_resized
,
892 ib
->colrow_being_resized
= -1;
894 if (ib
->has_resize_guides
) {
895 ib
->has_resize_guides
= FALSE
;
896 scg_size_guide_stop (ib
->pane
->simple
.scg
);
898 if (ib
->tip
!= NULL
) {
899 gtk_widget_destroy (gtk_widget_get_toplevel (ib
->tip
));
905 cb_extend_selection (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
907 GnmItemBar
* const ib
= info
->user_data
;
908 gboolean
const is_cols
= ib
->is_col_header
;
909 scg_colrow_select (pane
->simple
.scg
,
910 is_cols
, is_cols
? info
->col
: info
->row
, GDK_SHIFT_MASK
);
915 outline_button_press (GnmItemBar
const *ib
, int element
, int pixel
)
917 SheetControlGUI
*scg
= ib
->pane
->simple
.scg
;
918 Sheet
* const sheet
= scg_sheet (scg
);
921 if (ib
->is_col_header
) {
922 if (sheet
->cols
.max_outline_level
<= 0)
924 inc
= (ib
->indent
- 2) / (sheet
->cols
.max_outline_level
+ 1);
926 if (sheet
->rows
.max_outline_level
<= 0)
928 inc
= (ib
->indent
- 2) / (sheet
->rows
.max_outline_level
+ 1);
933 cmd_selection_outline_change (scg_wbc (scg
), ib
->is_col_header
,
939 item_bar_button_pressed (GocItem
*item
, int button
, double x_
, double y_
)
941 ColRowInfo
const *cri
;
942 GocCanvas
* const canvas
= item
->canvas
;
943 GnmItemBar
* const ib
= GNM_ITEM_BAR (item
);
944 GnmPane
* const pane
= ib
->pane
;
945 SheetControlGUI
* const scg
= pane
->simple
.scg
;
946 SheetControl
* const sc
= (SheetControl
*) pane
->simple
.scg
;
947 Sheet
* const sheet
= sc_sheet (sc
);
948 WBCGtk
* const wbcg
= scg_wbcg (scg
);
949 gboolean
const is_cols
= ib
->is_col_header
;
950 gint64 minor_pos
, start
;
952 GdkEvent
*event
= goc_canvas_get_cur_event (item
->canvas
);
953 GdkEventButton
*bevent
= &event
->button
;
954 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
956 if (ib
->colrow_being_resized
!= -1 || ib
->start_selection
!= -1) {
957 // This happens with repeated clicks on colrow divider.
958 // Ignore it. Definitely don't regrab.
965 if (wbc_gtk_get_guru (wbcg
) == NULL
)
968 cri
= is_pointer_on_division (ib
, x
, y
,
969 &start
, &element
, &minor_pos
);
972 if (minor_pos
< ib
->indent
)
973 return outline_button_press (ib
, element
, minor_pos
);
976 if (wbc_gtk_get_guru (wbcg
) != NULL
)
978 /* If the selection does not contain the current row/col
979 * then clear the selection and add it.
981 if (!sv_is_colrow_selected (sc_view (sc
), element
, is_cols
))
982 scg_colrow_select (scg
, is_cols
,
983 element
, bevent
->state
);
985 scg_context_menu (scg
, event
, is_cols
, !is_cols
);
987 } else if (cri
!= NULL
) {
989 * Record the important bits.
991 * By setting colrow_being_resized to a non -1 value,
992 * we know that we are being resized (used in the
993 * other event handlers).
995 ib
->colrow_being_resized
= element
;
996 ib
->resize_start_pos
= (is_cols
&& sheet
->text_is_rtl
)
997 ? start
: (start
- cri
->size_pixels
);
998 ib
->colrow_resize_size
= cri
->size_pixels
;
1000 if (ib
->tip
== NULL
) {
1001 GtkWidget
*cw
= GTK_WIDGET (canvas
);
1003 ib
->tip
= gnm_create_tooltip (cw
);
1004 colrow_tip_setlabel (ib
, is_cols
, ib
->colrow_resize_size
);
1005 /* Position above the current point for both
1006 * col and row headers. trying to put it
1007 * beside for row headers often ends up pushing
1008 * the tip under the cursor which can have odd
1009 * effects on the event stream. win32 was
1010 * different from X. */
1012 gnm_canvas_get_position (canvas
, &wx
, &wy
,x
, y
);
1013 gnm_position_tooltip (ib
->tip
,
1015 gtk_widget_show_all (gtk_widget_get_toplevel (ib
->tip
));
1018 if (wbc_gtk_get_guru (wbcg
) != NULL
&&
1019 !wbcg_entry_has_logical (wbcg
))
1022 /* If we're editing it is possible for this to fail */
1023 if (!scg_colrow_select (scg
, is_cols
, element
, bevent
->state
))
1026 ib
->start_selection
= element
;
1027 gnm_pane_slide_init (pane
);
1029 gnm_simple_canvas_grab (item
);
1034 item_bar_2button_pressed (GocItem
*item
, int button
, double x
, double y
)
1036 GnmItemBar
* const ib
= GNM_ITEM_BAR (item
);
1041 item_bar_resize_stop (ib
, -1);
1046 item_bar_enter_notify (GocItem
*item
, double x_
, double y_
)
1048 GnmItemBar
* const ib
= GNM_ITEM_BAR (item
);
1049 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1050 ib_set_cursor (ib
, x
, y
);
1055 item_bar_motion (GocItem
*item
, double x_
, double y_
)
1057 ColRowInfo
const *cri
;
1058 GocCanvas
* const canvas
= item
->canvas
;
1059 GnmItemBar
* const ib
= GNM_ITEM_BAR (item
);
1060 GnmPane
* const pane
= ib
->pane
;
1061 SheetControlGUI
* const scg
= pane
->simple
.scg
;
1062 SheetControl
* const sc
= (SheetControl
*) pane
->simple
.scg
;
1063 Sheet
* const sheet
= sc_sheet (sc
);
1064 gboolean
const is_cols
= ib
->is_col_header
;
1066 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1068 if (ib
->colrow_being_resized
!= -1) {
1070 if (!ib
->has_resize_guides
) {
1071 ib
->has_resize_guides
= TRUE
;
1072 scg_size_guide_start (ib
->pane
->simple
.scg
,
1074 ib
->colrow_being_resized
,
1078 cri
= sheet_colrow_get_info (sheet
,
1079 ib
->colrow_being_resized
, is_cols
);
1080 pos
= is_cols
? x
: y
;
1081 new_size
= pos
- ib
->resize_start_pos
;
1082 if (is_cols
&& sheet
->text_is_rtl
)
1083 new_size
+= cri
->size_pixels
;
1085 /* Ensure we always have enough room for the margins */
1087 if (new_size
<= (GNM_COL_MARGIN
+ GNM_COL_MARGIN
)) {
1088 new_size
= GNM_COL_MARGIN
+ GNM_COL_MARGIN
+ 1;
1089 pos
= pane
->first_offset
.x
+
1090 scg_colrow_distance_get (scg
, TRUE
,
1092 ib
->colrow_being_resized
);
1096 if (new_size
<= (GNM_ROW_MARGIN
+ GNM_ROW_MARGIN
)) {
1097 new_size
= GNM_ROW_MARGIN
+ GNM_ROW_MARGIN
+ 1;
1098 pos
= pane
->first_offset
.y
+
1099 scg_colrow_distance_get (scg
, FALSE
,
1101 ib
->colrow_being_resized
);
1106 ib
->colrow_resize_size
= new_size
;
1107 colrow_tip_setlabel (ib
, is_cols
, new_size
);
1108 scg_size_guide_motion (scg
, is_cols
, pos
);
1110 /* Redraw the GnmItemBar to show nice incremental progress */
1111 goc_canvas_invalidate (canvas
, 0, 0, G_MAXINT
/2, G_MAXINT
/2);
1113 } else if (ib
->start_selection
!= -1) {
1114 gnm_pane_handle_motion (ib
->pane
,
1116 GNM_PANE_SLIDE_AT_COLROW_BOUND
|
1117 (is_cols
? GNM_PANE_SLIDE_X
: GNM_PANE_SLIDE_Y
),
1118 cb_extend_selection
, ib
);
1120 ib_set_cursor (ib
, x
, y
);
1125 item_bar_button_released (GocItem
*item
, int button
, double x
, double y
)
1127 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
1128 if (item
== goc_canvas_get_grabbed_item (item
->canvas
))
1129 gnm_simple_canvas_ungrab (item
);
1131 if (ib
->colrow_being_resized
>= 0) {
1132 if (ib
->has_resize_guides
)
1133 item_bar_resize_stop (ib
, ib
->colrow_resize_size
);
1136 * No need to resize, nothing changed.
1137 * This will handle the case of a double click.
1139 item_bar_resize_stop (ib
, 0);
1141 ib
->start_selection
= -1;
1146 item_bar_set_property (GObject
*obj
, guint param_id
,
1147 GValue
const *value
, GParamSpec
*pspec
)
1149 GnmItemBar
*ib
= GNM_ITEM_BAR (obj
);
1152 case GNM_ITEM_BAR_PROP_PANE
:
1153 ib
->pane
= g_value_get_object (value
);
1155 case GNM_ITEM_BAR_PROP_IS_COL_HEADER
:
1156 ib
->is_col_header
= g_value_get_boolean (value
);
1157 goc_item_bounds_changed (GOC_ITEM (obj
));
1163 item_bar_dispose (GObject
*obj
)
1165 GnmItemBar
*ib
= GNM_ITEM_BAR (obj
);
1168 ib_dispose_fonts (ib
);
1171 gtk_widget_destroy (ib
->tip
);
1175 if (ib
->pango
.glyphs
!= NULL
) {
1176 pango_glyph_string_free (ib
->pango
.glyphs
);
1177 ib
->pango
.glyphs
= NULL
;
1179 if (ib
->pango
.item
!= NULL
) {
1180 pango_item_free (ib
->pango
.item
);
1181 ib
->pango
.item
= NULL
;
1183 for (ui
= 0; ui
< G_N_ELEMENTS(ib
->styles
); ui
++)
1184 g_clear_object (&ib
->styles
[ui
]);
1186 G_OBJECT_CLASS (parent_class
)->dispose (obj
);
1190 gnm_item_bar_init (GnmItemBar
*ib
)
1197 ib
->dragging
= FALSE
;
1198 ib
->is_col_header
= FALSE
;
1199 ib
->cell_width
= ib
->cell_height
= 1;
1201 ib
->start_selection
= -1;
1205 ib
->colrow_being_resized
= -1;
1206 ib
->has_resize_guides
= FALSE
;
1207 ib
->pango
.item
= NULL
;
1208 ib
->pango
.glyphs
= pango_glyph_string_new ();
1210 #if !GTK_CHECK_VERSION(3,20,0)
1211 /* Style-wise we are a button. */
1212 gtk_style_context_add_class
1213 (goc_item_get_style_context (GOC_ITEM (ib
)),
1214 GTK_STYLE_CLASS_BUTTON
);
1219 gnm_item_bar_class_init (GObjectClass
*gobject_klass
)
1221 GocItemClass
*item_klass
= (GocItemClass
*) gobject_klass
;
1223 parent_class
= g_type_class_peek_parent (gobject_klass
);
1225 gobject_klass
->dispose
= item_bar_dispose
;
1226 gobject_klass
->set_property
= item_bar_set_property
;
1227 g_object_class_install_property (gobject_klass
, GNM_ITEM_BAR_PROP_PANE
,
1228 g_param_spec_object ("pane",
1230 P_("The pane containing the associated grid"),
1232 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1233 g_object_class_install_property (gobject_klass
, GNM_ITEM_BAR_PROP_IS_COL_HEADER
,
1234 g_param_spec_boolean ("IsColHeader",
1236 P_("Is the item-bar a header for columns or rows"),
1238 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1240 item_klass
->realize
= item_bar_realize
;
1241 item_klass
->unrealize
= item_bar_unrealize
;
1242 item_klass
->draw_region
= item_bar_draw_region
;
1243 item_klass
->update_bounds
= item_bar_update_bounds
;
1244 item_klass
->distance
= item_bar_distance
;
1245 item_klass
->button_pressed
= item_bar_button_pressed
;
1246 item_klass
->button_released
= item_bar_button_released
;
1247 item_klass
->button2_pressed
= item_bar_2button_pressed
;
1248 item_klass
->enter_notify
= item_bar_enter_notify
;
1249 item_klass
->motion
= item_bar_motion
;
1252 GSF_CLASS (GnmItemBar
, gnm_item_bar
,
1253 gnm_item_bar_class_init
, gnm_item_bar_init
,