2 * A canvas item implementing row/col headers with support for outlining.
5 * Miguel de Icaza (miguel@kernel.org)
6 * Jody Goldberg (jody@gnome.org)
9 #include <gnumeric-config.h>
13 #include <gnm-pane-impl.h>
15 #include <style-color.h>
17 #include <sheet-control-gui.h>
18 #include <sheet-control-gui-priv.h>
19 #include <application.h>
20 #include <selection.h>
21 #include <wbc-gtk-impl.h>
23 #include <parse-util.h>
27 #include <goffice/goffice.h>
28 #include <gsf/gsf-impl-utils.h>
29 #define GNUMERIC_ITEM "BAR"
37 GdkCursor
*normal_cursor
;
38 GdkCursor
*change_cursor
;
39 GtkWidget
*tip
; /* Tip for scrolling */
41 gboolean is_col_header
;
42 gboolean has_resize_guides
;
43 int indent
, cell_width
, cell_height
;
44 int start_selection
; /* Where selection started */
45 int colrow_being_resized
;
46 int colrow_resize_size
;
51 PangoGlyphString
*glyphs
;
56 /* [ColRowSelectionType] */
57 GdkRGBA selection_colors
[3];
58 PangoFont
*selection_fonts
[3];
59 int selection_font_ascents
[3];
60 PangoRectangle selection_logical_sizes
[3];
61 GtkStyleContext
*styles
[3];
63 GdkRGBA grouping_color
;
68 typedef GocItemClass GnmItemBarClass
;
69 static GocItemClass
*parent_class
;
73 GNM_ITEM_BAR_PROP_PANE
,
74 GNM_ITEM_BAR_PROP_IS_COL_HEADER
78 ib_compute_pixels_from_indent (GnmItemBar
*ib
, Sheet
const *sheet
)
80 gboolean is_cols
= ib
->is_col_header
;
82 sheet
->last_zoom_factor_used
*
83 gnm_app_display_dpi_get (is_cols
) / 72.;
84 int const indent
= is_cols
85 ? sheet
->cols
.max_outline_level
86 : sheet
->rows
.max_outline_level
;
87 if (!sheet
->display_outlines
|| indent
<= 0)
89 return (int)(ib
->padding
.left
+ (indent
+ 1) * 14 * scale
+ 0.5);
93 ib_dispose_fonts (GnmItemBar
*ib
)
97 for (ui
= 0; ui
< G_N_ELEMENTS (ib
->selection_fonts
); ui
++)
98 g_clear_object (&ib
->selection_fonts
[ui
]);
101 static const GtkStateFlags selection_type_flags
[3] = {
102 GTK_STATE_FLAG_NORMAL
,
103 GTK_STATE_FLAG_PRELIGHT
,
104 GTK_STATE_FLAG_ACTIVE
107 static const char * const selection_styles
[3] = {
109 "button.itembar:hover",
110 "button.itembar:active"
114 ib_reload_color_style (GnmItemBar
*ib
)
116 GocItem
*item
= GOC_ITEM (ib
);
117 GtkStyleContext
*context
= goc_item_get_style_context (item
);
120 gnm_style_context_get_color (context
, GTK_STATE_FLAG_NORMAL
,
121 &ib
->grouping_color
);
123 for (ui
= 0; ui
< G_N_ELEMENTS (selection_type_flags
); ui
++) {
124 GtkStateFlags state
= selection_type_flags
[ui
];
125 gnm_style_context_get_color
126 (context
, state
, &ib
->selection_colors
[ui
]);
132 ib_reload_sizing_style (GnmItemBar
*ib
)
134 GocItem
*item
= GOC_ITEM (ib
);
135 SheetControlGUI
* const scg
= ib
->pane
->simple
.scg
;
136 Sheet
const *sheet
= scg_sheet (scg
);
137 double const zoom_factor
= sheet
->last_zoom_factor_used
;
138 gboolean
const char_label
=
139 ib
->is_col_header
&& !sheet
->convs
->r1c1_addresses
;
141 PangoContext
*pcontext
=
142 gtk_widget_get_pango_context (GTK_WIDGET (item
->canvas
));
143 PangoLayout
*layout
= pango_layout_new (pcontext
);
144 PangoAttrList
*attr_list
;
147 for (ui
= 0; ui
< G_N_ELEMENTS (selection_type_flags
); ui
++) {
148 GtkStateFlags state
= selection_type_flags
[ui
];
149 PangoFontDescription
*desc
;
150 PangoRectangle ink_rect
;
151 const char *long_name
;
152 GtkStyleContext
*context
;
154 g_clear_object (&ib
->styles
[ui
]);
155 #if GTK_CHECK_VERSION(3,20,0)
156 context
= go_style_context_from_selector (NULL
, selection_styles
[ui
]);
158 context
= g_object_ref (goc_item_get_style_context (item
));
161 ib
->styles
[ui
] = context
;
162 gtk_style_context_save (context
);
163 #if !GTK_CHECK_VERSION(3,20,0)
164 gtk_style_context_set_state (context
, state
);
166 gtk_style_context_get (context
, state
, "font", &desc
, NULL
);
167 pango_font_description_set_size (desc
,
168 zoom_factor
* pango_font_description_get_size (desc
));
169 ib
->selection_fonts
[ui
] =
170 pango_context_load_font (pcontext
, desc
);
171 if (!ib
->selection_fonts
[ui
]) {
173 pango_font_description_set_family (desc
, "Sans");
174 ib
->selection_fonts
[ui
] =
175 pango_context_load_font (pcontext
, desc
);
179 * Figure out how tall the label can be.
180 * (Note that we avoid J/Q/Y which may go below the line.)
182 pango_layout_set_text (layout
,
183 char_label
? "AHW" : "0123456789",
185 pango_layout_set_font_description (layout
, desc
);
186 pango_font_description_free (desc
);
187 pango_layout_get_extents (layout
, &ink_rect
, NULL
);
188 ib
->selection_font_ascents
[ui
] =
189 PANGO_PIXELS (ink_rect
.height
+ ink_rect
.y
);
191 /* The width of the widest string I can think of + padding */
192 if (ib
->is_col_header
) {
193 int last
= gnm_sheet_get_last_col (sheet
);
194 long_name
= char_label
? col_name (last
) : /* HACK: */row_name (last
);
196 long_name
= row_name (gnm_sheet_get_last_row (sheet
));
197 pango_layout_set_text
199 char_label
? "WWWWWWWWWW" : "8888888888",
201 pango_layout_get_extents (layout
, NULL
,
202 &ib
->selection_logical_sizes
[ui
]);
204 if (state
== GTK_STATE_FLAG_NORMAL
)
205 gtk_style_context_get_padding (context
, state
,
208 gtk_style_context_restore (context
);
211 attr_list
= pango_attr_list_new ();
212 item_list
= pango_itemize (pcontext
, "A", 0, 1, attr_list
, NULL
);
213 pango_attr_list_unref (attr_list
);
215 pango_item_free (ib
->pango
.item
);
216 ib
->pango
.item
= item_list
->data
;
217 item_list
->data
= NULL
;
218 if (item_list
->next
!= NULL
)
219 g_warning ("Leaking pango items");
220 g_list_free (item_list
);
222 g_object_unref (layout
);
226 * gnm_item_bar_calc_size:
229 * Scale fonts and sizes by the pixels_per_unit of the associated sheet.
231 * Returns : the size of the fixed dimension.
234 gnm_item_bar_calc_size (GnmItemBar
*ib
)
236 SheetControlGUI
* const scg
= ib
->pane
->simple
.scg
;
237 Sheet
const *sheet
= scg_sheet (scg
);
241 ib_dispose_fonts (ib
);
242 ib_reload_sizing_style (ib
);
246 for (ui
= 0; ui
< G_N_ELEMENTS (ib
->selection_logical_sizes
); ui
++) {
247 int h
= PANGO_PIXELS (ib
->selection_logical_sizes
[ui
].height
) +
248 (ib
->padding
.top
+ ib
->padding
.bottom
);
249 int w
= PANGO_PIXELS (ib
->selection_logical_sizes
[ui
].width
) +
250 (ib
->padding
.left
+ ib
->padding
.right
);
251 ib
->cell_height
= MAX (ib
->cell_height
, h
);
252 ib
->cell_width
= MAX (ib
->cell_width
, w
);
255 size
= ib_compute_pixels_from_indent (ib
, sheet
);
256 if (size
!= ib
->indent
) {
258 goc_item_bounds_changed (GOC_ITEM (ib
));
262 (ib
->is_col_header
? ib
->cell_height
: ib
->cell_width
);
266 * item_bar_normal_font:
269 * Returns: (transfer full): the bar normal font.
271 PangoFontDescription
*
272 item_bar_normal_font (GnmItemBar
const *ib
)
274 return pango_font_describe (ib
->selection_fonts
[COL_ROW_NO_SELECTION
]);
278 gnm_item_bar_indent (GnmItemBar
const *ib
)
284 item_bar_update_bounds (GocItem
*item
)
286 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
289 if (ib
->is_col_header
) {
290 item
->x1
= G_MAXINT64
/2;
291 item
->y1
= (ib
->cell_height
+ ib
->indent
);
293 item
->x1
= (ib
->cell_width
+ ib
->indent
);
294 item
->y1
= G_MAXINT64
/2;
299 item_bar_realize (GocItem
*item
)
301 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
304 parent_class
->realize (item
);
306 display
= gtk_widget_get_display (GTK_WIDGET (item
->canvas
));
308 gdk_cursor_new_for_display (display
, GDK_LEFT_PTR
);
310 gdk_cursor_new_for_display (display
,
312 ? GDK_SB_H_DOUBLE_ARROW
313 : GDK_SB_V_DOUBLE_ARROW
);
315 gnm_item_bar_calc_size (ib
);
319 item_bar_unrealize (GocItem
*item
)
321 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
323 g_clear_object (&ib
->change_cursor
);
324 g_clear_object (&ib
->normal_cursor
);
326 parent_class
->unrealize (item
);
330 ib_draw_cell (GnmItemBar
const * const ib
, cairo_t
*cr
,
331 ColRowSelectionType
const type
,
332 char const * const str
, GocRect
*rect
)
334 GtkStyleContext
*ctxt
= ib
->styles
[type
];
336 g_return_if_fail ((size_t)type
< G_N_ELEMENTS (selection_type_flags
));
340 gtk_style_context_save (ctxt
);
341 #if !GTK_CHECK_VERSION(3,20,0)
342 gtk_style_context_set_state (ctxt
, selection_type_flags
[type
]);
344 gtk_render_background (ctxt
, cr
, rect
->x
, rect
->y
,
345 rect
->width
+ 1, rect
->height
+ 1);
347 /* When we are really small leave out the shadow and the text */
348 if (rect
->width
>= 2 && rect
->height
>= 2) {
350 PangoFont
*font
= ib
->selection_fonts
[type
];
351 int ascent
= ib
->selection_font_ascents
[type
];
355 g_return_if_fail (font
!= NULL
);
356 g_object_unref (ib
->pango
.item
->analysis
.font
);
357 ib
->pango
.item
->analysis
.font
= g_object_ref (font
);
358 pango_shape (str
, strlen (str
),
359 &(ib
->pango
.item
->analysis
), ib
->pango
.glyphs
);
360 pango_glyph_string_extents (ib
->pango
.glyphs
, font
,
363 gtk_render_frame (ctxt
, cr
, rect
->x
, rect
->y
,
364 rect
->width
+ 1, rect
->height
+ 1);
365 w
= rect
->width
- (ib
->padding
.left
+ ib
->padding
.right
);
366 h
= rect
->height
- (ib
->padding
.top
+ ib
->padding
.bottom
);
367 cairo_rectangle (cr
, rect
->x
+ 1, rect
->y
+ 1,
368 rect
->width
- 2, rect
->height
- 2);
371 gtk_style_context_get_color (ctxt
, selection_type_flags
[type
], &c
);
372 gdk_cairo_set_source_rgba (cr
, &c
);
375 rect
->x
+ ib
->padding
.left
+
376 (w
- PANGO_PIXELS (size
.width
)) / 2,
377 rect
->y
+ ib
->padding
.top
+ ascent
+
378 (h
- PANGO_PIXELS (size
.height
)) / 2);
379 pango_cairo_show_glyph_string (cr
, font
, ib
->pango
.glyphs
);
381 gtk_style_context_restore (ctxt
);
386 gnm_item_bar_group_size (GnmItemBar
const *ib
, int max_outline
)
388 return max_outline
> 0
389 ? (ib
->indent
- 2) / (max_outline
+ 1)
394 item_bar_draw_region (GocItem
const *item
, cairo_t
*cr
,
395 double x_0
, double y_0
, double x_1
, double y_1
)
397 double scale
= item
->canvas
->pixels_per_unit
;
399 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
400 GnmPane
const *pane
= ib
->pane
;
401 SheetControlGUI
const *scg
= pane
->simple
.scg
;
402 Sheet
const *sheet
= scg_sheet (scg
);
403 SheetView
const *sv
= scg_view (scg
);
404 ColRowInfo
const *cri
, *next
= NULL
;
406 gboolean prev_visible
;
407 gboolean
const draw_below
= sheet
->outline_symbols_below
!= FALSE
;
408 gboolean
const draw_right
= sheet
->outline_symbols_right
!= FALSE
;
412 gboolean
const has_object
= scg
->wbcg
->new_object
!= NULL
|| scg
->selected_objects
!= NULL
;
413 gboolean
const rtl
= sheet
->text_is_rtl
!= FALSE
;
415 int first_line_offset
= 1;
416 GtkStyleContext
*ctxt
= goc_item_get_style_context (item
);
418 gtk_style_context_save (ctxt
);
419 goc_canvas_c2w (item
->canvas
, x_0
, y_0
, &x0
, &y0
);
420 goc_canvas_c2w (item
->canvas
, x_1
, y_1
, &x1
, &y1
);
422 ib_reload_color_style (ib
);
424 if (ib
->is_col_header
) {
425 int const inc
= gnm_item_bar_group_size (ib
, sheet
->cols
.max_outline_level
);
426 int const base_pos
= .2 * inc
;
427 int const len
= (inc
> 4) ? 4 : inc
;
428 int end
, total
, col
= pane
->first
.col
;
429 gboolean
const char_label
= !sheet
->convs
->r1c1_addresses
;
431 /* shadow type selection must be keep in sync with code in ib_draw_cell */
432 goc_canvas_c2w (item
->canvas
, pane
->first_offset
.x
/ scale
, 0, &total
, NULL
);
435 rect
.height
= ib
->cell_height
;
436 shadow
= (col
> 0 && !has_object
&& sv_selection_col_type (sv
, col
-1) == COL_ROW_FULL_SELECTION
)
437 ? GTK_SHADOW_IN
: GTK_SHADOW_OUT
;
440 cri
= sheet_col_get_info (sheet
, col
-1);
441 prev_visible
= cri
->visible
;
442 prev_level
= cri
->outline_level
;
449 if (col
>= gnm_sheet_get_max_cols (sheet
))
452 /* DO NOT enable resizing all until we get rid of
453 * resize_start_pos. It will be wrong if things ahead
456 cri
= sheet_col_get_info (sheet
, col
);
457 if (col
!= -1 && ib
->colrow_being_resized
== col
)
458 /* || sv_is_colrow_selected (sheet, col, TRUE))) */
459 pixels
= ib
->colrow_resize_size
;
461 pixels
= cri
->size_pixels
;
464 int left
, level
, i
= 0, pos
= base_pos
;
467 left
= (total
-= pixels
);
470 rect
.x
= left
= total
;
475 ib_draw_cell (ib
, cr
,
477 ? COL_ROW_NO_SELECTION
478 : sv_selection_col_type (sv
, col
),
486 cairo_set_line_width (cr
, 2.0);
487 cairo_set_line_cap (cr
, CAIRO_LINE_CAP_BUTT
);
488 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_MITER
);
489 gdk_cairo_set_source_rgba (cr
, &ib
->grouping_color
);
491 next
= sheet_col_get_info (sheet
, col
+ 1);
492 prev_level
= next
->outline_level
;
493 prev_visible
= next
->visible
;
494 points
[0].x
= rtl
? (total
+ pixels
) : left
;
498 /* draw the start or end marks and the vertical lines */
499 points
[1].x
= points
[2].x
= left
+ pixels
/2;
500 for (level
= cri
->outline_level
; i
++ < level
; pos
+= inc
) {
501 points
[0].y
= points
[1].y
= pos
;
502 points
[2].y
= pos
+ len
;
503 if (i
> prev_level
) {
504 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
505 cairo_line_to (cr
, points
[1].x
, points
[1].y
);
506 cairo_line_to (cr
, points
[2].x
, points
[2].y
);
508 cairo_move_to (cr
, left
- first_line_offset
, pos
);
509 cairo_line_to (cr
, total
+ pixels
, pos
);
511 cairo_move_to (cr
, left
- first_line_offset
, pos
);
512 cairo_line_to (cr
, total
, pos
);
516 first_line_offset
= 0;
518 if (draw_right
^ rtl
) {
519 if (prev_level
> level
) {
521 int top
= pos
- base_pos
+ 2; /* inside cell's shadow */
522 int size
= inc
< pixels
? inc
: pixels
;
529 gtk_style_context_set_state (ctxt
, prev_visible
?
530 GTK_STATE_FLAG_NORMAL
: GTK_STATE_FLAG_SELECTED
);
531 gtk_render_frame (ctxt
, cr
,
532 left
, top
+safety
, size
, size
);
537 cairo_move_to (cr
, left
+size
/2, top
+3);
538 cairo_line_to (cr
, left
+size
/2, top
+size
-4);
540 cairo_move_to (cr
, left
+3, top
+size
/2);
541 cairo_line_to (cr
, left
+size
-4, top
+size
/2);
545 cairo_move_to (cr
, left
+pixels
/2, pos
);
546 cairo_line_to (cr
, left
+pixels
/2, pos
+len
);
549 if (prev_level
> level
) {
551 int top
= pos
- base_pos
+ 2; /* inside cell's shadow */
552 int size
= inc
< pixels
? inc
: pixels
;
560 right
= (rtl
? (total
+ pixels
) : total
) - size
;
561 gtk_style_context_set_state (ctxt
, prev_visible
?
562 GTK_STATE_FLAG_NORMAL
: GTK_STATE_FLAG_SELECTED
);
563 gtk_render_frame (ctxt
, cr
,
564 right
, top
+safety
, size
, size
);
569 cairo_move_to (cr
, right
+size
/2, top
+3);
570 cairo_line_to (cr
, right
+size
/2, top
+size
-4);
572 cairo_move_to (cr
, right
+3, top
+size
/2);
573 cairo_line_to (cr
, right
+size
-4, top
+size
/2);
575 } else if (level
> 0) {
576 cairo_move_to (cr
, left
+pixels
/2, pos
);
577 cairo_line_to (cr
, left
+pixels
/2, pos
+len
);
584 prev_visible
= cri
->visible
;
585 prev_level
= cri
->outline_level
;
587 } while ((rtl
&& end
<= total
) || (!rtl
&& total
<= end
));
589 int const inc
= gnm_item_bar_group_size (ib
, sheet
->rows
.max_outline_level
);
590 int base_pos
= .2 * inc
;
591 int const len
= (inc
> 4) ? 4 : inc
;
593 int const dir
= rtl
? -1 : 1;
595 int total
= pane
->first_offset
.y
- item
->canvas
->scroll_y1
* scale
;
596 int row
= pane
->first
.row
;
599 base_pos
= ib
->indent
+ ib
->cell_width
- base_pos
;
600 /* Move header bar 1 pixel to the left. */
604 rect
.width
= ib
->cell_width
;
607 cri
= sheet_row_get_info (sheet
, row
-1);
608 prev_visible
= cri
->visible
;
609 prev_level
= cri
->outline_level
;
616 if (row
>= gnm_sheet_get_max_rows (sheet
))
619 /* DO NOT enable resizing all until we get rid of
620 * resize_start_pos. It will be wrong if things ahead
623 cri
= sheet_row_get_info (sheet
, row
);
624 if (row
!= -1 && ib
->colrow_being_resized
== row
)
625 /* || sv_is_colrow_selected (sheet, row, FALSE))) */
626 pixels
= ib
->colrow_resize_size
;
628 pixels
= cri
->size_pixels
;
631 int level
, i
= 0, pos
= base_pos
;
636 rect
.height
= pixels
;
637 ib_draw_cell (ib
, cr
,
639 ? COL_ROW_NO_SELECTION
640 : sv_selection_row_type (sv
, row
),
641 row_name (row
), &rect
);
645 cairo_set_line_width (cr
, 2.0);
646 cairo_set_line_cap (cr
, CAIRO_LINE_CAP_BUTT
);
647 cairo_set_line_join (cr
, CAIRO_LINE_JOIN_MITER
);
648 gdk_cairo_set_source_rgba (cr
, &ib
->grouping_color
);
650 next
= sheet_row_get_info (sheet
, row
+ 1);
655 /* draw the start or end marks and the vertical lines */
656 points
[1].y
= points
[2].y
= top
+ pixels
/2;
657 for (level
= cri
->outline_level
; i
++ < level
; pos
+= inc
* dir
) {
658 points
[0].x
= points
[1].x
= pos
;
659 points
[2].x
= pos
+ len
* dir
;
660 if (draw_below
&& i
> prev_level
) {
661 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
662 cairo_line_to (cr
, points
[1].x
, points
[1].y
);
663 cairo_line_to (cr
, points
[2].x
, points
[2].y
);
664 } else if (!draw_below
&& i
> next
->outline_level
) {
665 cairo_move_to (cr
, points
[0].x
, points
[0].y
);
666 cairo_line_to (cr
, points
[1].x
, points
[1].y
);
667 cairo_line_to (cr
, points
[2].x
, points
[2].y
);
669 cairo_move_to (cr
, pos
, top
- first_line_offset
);
670 cairo_line_to (cr
, pos
, total
);
674 first_line_offset
= 0;
677 if (prev_level
> level
) {
678 int left
, safety
= 0;
679 int size
= inc
< pixels
? inc
: pixels
;
686 /* inside cell's shadow */
687 left
= pos
- dir
* (.2 * inc
- 2);
690 gtk_style_context_set_state (ctxt
, prev_visible
?
691 GTK_STATE_FLAG_NORMAL
: GTK_STATE_FLAG_SELECTED
);
692 gtk_render_frame (ctxt
, cr
,
693 left
+safety
, top
, size
, size
);
698 cairo_move_to (cr
, left
+size
/2, top
+3);
699 cairo_line_to (cr
, left
+size
/2, top
+size
-4);
701 cairo_move_to (cr
, left
+3, top
+size
/2);
702 cairo_line_to (cr
, left
+size
-4, top
+size
/2);
706 cairo_move_to (cr
, pos
, top
+pixels
/2);
707 cairo_line_to (cr
, pos
+len
, top
+pixels
/2);
710 if (next
->outline_level
> level
) {
711 int left
, safety
= 0;
712 int size
= inc
< pixels
? inc
: pixels
;
720 /* inside cell's shadow */
721 left
= pos
- dir
* (.2 * inc
- 2);
724 bottom
= total
- size
;
725 gtk_style_context_set_state (ctxt
, prev_visible
?
726 GTK_STATE_FLAG_NORMAL
: GTK_STATE_FLAG_SELECTED
);
727 gtk_render_frame (ctxt
,cr
,
728 left
+safety
*dir
, bottom
, size
, size
);
730 if (!next
->visible
) {
733 cairo_move_to (cr
, left
+size
/2, bottom
+3);
734 cairo_line_to (cr
, left
+size
/2, bottom
+size
-4);
736 cairo_move_to (cr
, left
+3, bottom
+size
/2);
737 cairo_line_to (cr
, left
+size
-4, bottom
+size
/2);
739 } else if (level
> 0) {
740 cairo_move_to (cr
, pos
, top
+pixels
/2);
741 cairo_line_to (cr
, pos
+len
, top
+pixels
/2);
748 prev_visible
= cri
->visible
;
749 prev_level
= cri
->outline_level
;
751 } while (total
<= end
);
753 gtk_style_context_restore (ctxt
);
758 item_bar_distance (GocItem
*item
, double x
, double y
,
759 GocItem
**actual_item
)
766 * is_pointer_on_division:
768 * @x: in world coords
769 * @y: in world coords
774 * NOTE : this could easily be optimized. We need not start at 0 every time.
775 * We could potentially use the routines in gnm-pane.
777 * Returns non-%NULL if point (@x,@y) is on a division
779 static ColRowInfo
const *
780 is_pointer_on_division (GnmItemBar
const *ib
, gint64 x
, gint64 y
,
781 gint64
*the_total
, int *the_element
, gint64
*minor_pos
)
783 Sheet
*sheet
= scg_sheet (ib
->pane
->simple
.scg
);
784 ColRowInfo
const *cri
;
785 gint64 major
, minor
, total
= 0;
788 if (ib
->is_col_header
) {
791 i
= ib
->pane
->first
.col
;
792 total
= ib
->pane
->first_offset
.x
;
796 i
= ib
->pane
->first
.row
;
797 total
= ib
->pane
->first_offset
.y
;
799 if (NULL
!= minor_pos
)
801 if (NULL
!= the_element
)
803 for (; total
< major
; i
++) {
804 if (ib
->is_col_header
) {
805 if (i
>= gnm_sheet_get_max_cols (sheet
))
807 cri
= sheet_col_get_info (sheet
, i
);
809 if (i
>= gnm_sheet_get_max_rows (sheet
))
811 cri
= sheet_row_get_info (sheet
, i
);
815 WBCGtk
*wbcg
= scg_wbcg (ib
->pane
->simple
.scg
);
816 total
+= cri
->size_pixels
;
818 if (wbc_gtk_get_guru (wbcg
) == NULL
&&
819 !wbcg_is_editing (wbcg
) &&
820 (total
- 4 < major
) && (major
< total
+ 4)) {
825 return (minor
>= ib
->indent
) ? cri
: NULL
;
838 /* x & y in world coords */
840 ib_set_cursor (GnmItemBar
*ib
, gint64 x
, gint64 y
)
842 GdkWindow
*window
= gtk_widget_get_window (GTK_WIDGET (ib
->base
.canvas
));
843 GdkCursor
*cursor
= ib
->normal_cursor
;
845 /* We might be invoked before we are realized */
848 if (NULL
!= is_pointer_on_division (ib
, x
, y
, NULL
, NULL
, NULL
))
849 cursor
= ib
->change_cursor
;
850 gdk_window_set_cursor (window
, cursor
);
854 colrow_tip_setlabel (GnmItemBar
*ib
, gboolean
const is_cols
, int size_pixels
)
856 if (ib
->tip
!= NULL
) {
857 char *buffer
, *points
, *pixels
;
858 char const *label
= is_cols
? _("Width:") : _("Height");
859 double const scale
= 72. / gnm_app_display_dpi_get (!is_cols
);
860 double size_points
= scale
*size_pixels
;
862 /* xgettext: This is input to ngettext based on the number of pixels. */
863 pixels
= g_strdup_printf (ngettext ("(%d pixel)", "(%d pixels)", size_pixels
),
866 if (size_points
== gnm_floor (size_points
))
867 /* xgettext: This is input to ngettext based on the integer number of points. */
868 points
= g_strdup_printf (ngettext (_("%d.00 pt"), _("%d.00 pts"), (int) gnm_floor (size_points
)),
869 (int) gnm_floor (size_points
));
871 /* xgettext: The number of points here is always a fractional number, ie. not an integer. */
872 points
= g_strdup_printf (_("%.2f pts"), size_points
);
874 buffer
= g_strconcat (label
, " ", points
, " ", pixels
, NULL
);
877 gtk_label_set_text (GTK_LABEL (ib
->tip
), buffer
);
883 item_bar_resize_stop (GnmItemBar
*ib
, int new_size
)
885 if (ib
->colrow_being_resized
!= -1) {
887 scg_colrow_size_set (ib
->pane
->simple
.scg
,
889 ib
->colrow_being_resized
,
891 ib
->colrow_being_resized
= -1;
893 if (ib
->has_resize_guides
) {
894 ib
->has_resize_guides
= FALSE
;
895 scg_size_guide_stop (ib
->pane
->simple
.scg
);
897 if (ib
->tip
!= NULL
) {
898 gtk_widget_destroy (gtk_widget_get_toplevel (ib
->tip
));
904 cb_extend_selection (GnmPane
*pane
, GnmPaneSlideInfo
const *info
)
906 GnmItemBar
* const ib
= info
->user_data
;
907 gboolean
const is_cols
= ib
->is_col_header
;
908 scg_colrow_select (pane
->simple
.scg
,
909 is_cols
, is_cols
? info
->col
: info
->row
, GDK_SHIFT_MASK
);
914 outline_button_press (GnmItemBar
const *ib
, int element
, int pixel
)
916 SheetControlGUI
*scg
= ib
->pane
->simple
.scg
;
917 Sheet
* const sheet
= scg_sheet (scg
);
920 if (ib
->is_col_header
) {
921 if (sheet
->cols
.max_outline_level
<= 0)
923 inc
= (ib
->indent
- 2) / (sheet
->cols
.max_outline_level
+ 1);
925 if (sheet
->rows
.max_outline_level
<= 0)
927 inc
= (ib
->indent
- 2) / (sheet
->rows
.max_outline_level
+ 1);
932 cmd_selection_outline_change (scg_wbc (scg
), ib
->is_col_header
,
938 item_bar_button_pressed (GocItem
*item
, int button
, double x_
, double y_
)
940 ColRowInfo
const *cri
;
941 GocCanvas
* const canvas
= item
->canvas
;
942 GnmItemBar
* const ib
= GNM_ITEM_BAR (item
);
943 GnmPane
* const pane
= ib
->pane
;
944 SheetControlGUI
* const scg
= pane
->simple
.scg
;
945 SheetControl
* const sc
= (SheetControl
*) pane
->simple
.scg
;
946 Sheet
* const sheet
= sc_sheet (sc
);
947 WBCGtk
* const wbcg
= scg_wbcg (scg
);
948 gboolean
const is_cols
= ib
->is_col_header
;
949 gint64 minor_pos
, start
;
951 GdkEvent
*event
= goc_canvas_get_cur_event (item
->canvas
);
952 GdkEventButton
*bevent
= &event
->button
;
953 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
955 if (ib
->colrow_being_resized
!= -1 || ib
->start_selection
!= -1) {
956 // This happens with repeated clicks on colrow divider.
957 // Ignore it. Definitely don't regrab.
964 if (wbc_gtk_get_guru (wbcg
) == NULL
)
967 cri
= is_pointer_on_division (ib
, x
, y
,
968 &start
, &element
, &minor_pos
);
971 if (minor_pos
< ib
->indent
)
972 return outline_button_press (ib
, element
, minor_pos
);
975 if (wbc_gtk_get_guru (wbcg
) != NULL
)
977 /* If the selection does not contain the current row/col
978 * then clear the selection and add it.
980 if (!sv_is_colrow_selected (sc_view (sc
), element
, is_cols
))
981 scg_colrow_select (scg
, is_cols
,
982 element
, bevent
->state
);
984 scg_context_menu (scg
, event
, is_cols
, !is_cols
);
986 } else if (cri
!= NULL
) {
988 * Record the important bits.
990 * By setting colrow_being_resized to a non -1 value,
991 * we know that we are being resized (used in the
992 * other event handlers).
994 ib
->colrow_being_resized
= element
;
995 ib
->resize_start_pos
= (is_cols
&& sheet
->text_is_rtl
)
996 ? start
: (start
- cri
->size_pixels
);
997 ib
->colrow_resize_size
= cri
->size_pixels
;
999 if (ib
->tip
== NULL
) {
1000 GtkWidget
*cw
= GTK_WIDGET (canvas
);
1002 ib
->tip
= gnm_create_tooltip (cw
);
1003 colrow_tip_setlabel (ib
, is_cols
, ib
->colrow_resize_size
);
1004 /* Position above the current point for both
1005 * col and row headers. trying to put it
1006 * beside for row headers often ends up pushing
1007 * the tip under the cursor which can have odd
1008 * effects on the event stream. win32 was
1009 * different from X. */
1011 gnm_canvas_get_position (canvas
, &wx
, &wy
,x
, y
);
1012 gnm_position_tooltip (ib
->tip
,
1014 gtk_widget_show_all (gtk_widget_get_toplevel (ib
->tip
));
1017 if (wbc_gtk_get_guru (wbcg
) != NULL
&&
1018 !wbcg_entry_has_logical (wbcg
))
1021 /* If we're editing it is possible for this to fail */
1022 if (!scg_colrow_select (scg
, is_cols
, element
, bevent
->state
))
1025 ib
->start_selection
= element
;
1026 gnm_pane_slide_init (pane
);
1028 gnm_simple_canvas_grab (item
);
1033 item_bar_2button_pressed (GocItem
*item
, int button
, double x
, double y
)
1035 GnmItemBar
* const ib
= GNM_ITEM_BAR (item
);
1040 item_bar_resize_stop (ib
, -1);
1045 item_bar_enter_notify (GocItem
*item
, double x_
, double y_
)
1047 GnmItemBar
* const ib
= GNM_ITEM_BAR (item
);
1048 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1049 ib_set_cursor (ib
, x
, y
);
1054 item_bar_motion (GocItem
*item
, double x_
, double y_
)
1056 ColRowInfo
const *cri
;
1057 GocCanvas
* const canvas
= item
->canvas
;
1058 GnmItemBar
* const ib
= GNM_ITEM_BAR (item
);
1059 GnmPane
* const pane
= ib
->pane
;
1060 SheetControlGUI
* const scg
= pane
->simple
.scg
;
1061 SheetControl
* const sc
= (SheetControl
*) pane
->simple
.scg
;
1062 Sheet
* const sheet
= sc_sheet (sc
);
1063 gboolean
const is_cols
= ib
->is_col_header
;
1065 gint64 x
= x_
* item
->canvas
->pixels_per_unit
, y
= y_
* item
->canvas
->pixels_per_unit
;
1067 if (ib
->colrow_being_resized
!= -1) {
1069 if (!ib
->has_resize_guides
) {
1070 ib
->has_resize_guides
= TRUE
;
1071 scg_size_guide_start (ib
->pane
->simple
.scg
,
1073 ib
->colrow_being_resized
,
1077 cri
= sheet_colrow_get_info (sheet
,
1078 ib
->colrow_being_resized
, is_cols
);
1079 pos
= is_cols
? x
: y
;
1080 new_size
= pos
- ib
->resize_start_pos
;
1081 if (is_cols
&& sheet
->text_is_rtl
)
1082 new_size
+= cri
->size_pixels
;
1084 /* Ensure we always have enough room for the margins */
1086 if (new_size
<= (GNM_COL_MARGIN
+ GNM_COL_MARGIN
)) {
1087 new_size
= GNM_COL_MARGIN
+ GNM_COL_MARGIN
+ 1;
1088 pos
= pane
->first_offset
.x
+
1089 scg_colrow_distance_get (scg
, TRUE
,
1091 ib
->colrow_being_resized
);
1095 if (new_size
<= (GNM_ROW_MARGIN
+ GNM_ROW_MARGIN
)) {
1096 new_size
= GNM_ROW_MARGIN
+ GNM_ROW_MARGIN
+ 1;
1097 pos
= pane
->first_offset
.y
+
1098 scg_colrow_distance_get (scg
, FALSE
,
1100 ib
->colrow_being_resized
);
1105 ib
->colrow_resize_size
= new_size
;
1106 colrow_tip_setlabel (ib
, is_cols
, new_size
);
1107 scg_size_guide_motion (scg
, is_cols
, pos
);
1109 /* Redraw the GnmItemBar to show nice incremental progress */
1110 goc_canvas_invalidate (canvas
, 0, 0, G_MAXINT
/2, G_MAXINT
/2);
1112 } else if (ib
->start_selection
!= -1) {
1113 gnm_pane_handle_motion (ib
->pane
,
1115 GNM_PANE_SLIDE_AT_COLROW_BOUND
|
1116 (is_cols
? GNM_PANE_SLIDE_X
: GNM_PANE_SLIDE_Y
),
1117 cb_extend_selection
, ib
);
1119 ib_set_cursor (ib
, x
, y
);
1124 item_bar_button_released (GocItem
*item
, int button
, double x
, double y
)
1126 GnmItemBar
*ib
= GNM_ITEM_BAR (item
);
1127 if (item
== goc_canvas_get_grabbed_item (item
->canvas
))
1128 gnm_simple_canvas_ungrab (item
);
1130 if (ib
->colrow_being_resized
>= 0) {
1131 if (ib
->has_resize_guides
)
1132 item_bar_resize_stop (ib
, ib
->colrow_resize_size
);
1135 * No need to resize, nothing changed.
1136 * This will handle the case of a double click.
1138 item_bar_resize_stop (ib
, 0);
1140 ib
->start_selection
= -1;
1145 item_bar_set_property (GObject
*obj
, guint param_id
,
1146 GValue
const *value
, GParamSpec
*pspec
)
1148 GnmItemBar
*ib
= GNM_ITEM_BAR (obj
);
1151 case GNM_ITEM_BAR_PROP_PANE
:
1152 ib
->pane
= g_value_get_object (value
);
1154 case GNM_ITEM_BAR_PROP_IS_COL_HEADER
:
1155 ib
->is_col_header
= g_value_get_boolean (value
);
1156 goc_item_bounds_changed (GOC_ITEM (obj
));
1162 item_bar_dispose (GObject
*obj
)
1164 GnmItemBar
*ib
= GNM_ITEM_BAR (obj
);
1167 ib_dispose_fonts (ib
);
1170 gtk_widget_destroy (ib
->tip
);
1174 if (ib
->pango
.glyphs
!= NULL
) {
1175 pango_glyph_string_free (ib
->pango
.glyphs
);
1176 ib
->pango
.glyphs
= NULL
;
1178 if (ib
->pango
.item
!= NULL
) {
1179 pango_item_free (ib
->pango
.item
);
1180 ib
->pango
.item
= NULL
;
1182 for (ui
= 0; ui
< G_N_ELEMENTS(ib
->styles
); ui
++)
1183 g_clear_object (&ib
->styles
[ui
]);
1185 G_OBJECT_CLASS (parent_class
)->dispose (obj
);
1189 gnm_item_bar_init (GnmItemBar
*ib
)
1196 ib
->dragging
= FALSE
;
1197 ib
->is_col_header
= FALSE
;
1198 ib
->cell_width
= ib
->cell_height
= 1;
1200 ib
->start_selection
= -1;
1204 ib
->colrow_being_resized
= -1;
1205 ib
->has_resize_guides
= FALSE
;
1206 ib
->pango
.item
= NULL
;
1207 ib
->pango
.glyphs
= pango_glyph_string_new ();
1209 #if !GTK_CHECK_VERSION(3,20,0)
1210 /* Style-wise we are a button. */
1211 gtk_style_context_add_class
1212 (goc_item_get_style_context (GOC_ITEM (ib
)),
1213 GTK_STYLE_CLASS_BUTTON
);
1218 gnm_item_bar_class_init (GObjectClass
*gobject_klass
)
1220 GocItemClass
*item_klass
= (GocItemClass
*) gobject_klass
;
1222 parent_class
= g_type_class_peek_parent (gobject_klass
);
1224 gobject_klass
->dispose
= item_bar_dispose
;
1225 gobject_klass
->set_property
= item_bar_set_property
;
1226 g_object_class_install_property (gobject_klass
, GNM_ITEM_BAR_PROP_PANE
,
1227 g_param_spec_object ("pane",
1229 P_("The pane containing the associated grid"),
1231 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1232 g_object_class_install_property (gobject_klass
, GNM_ITEM_BAR_PROP_IS_COL_HEADER
,
1233 g_param_spec_boolean ("IsColHeader",
1235 P_("Is the item-bar a header for columns or rows"),
1237 GSF_PARAM_STATIC
| G_PARAM_WRITABLE
));
1239 item_klass
->realize
= item_bar_realize
;
1240 item_klass
->unrealize
= item_bar_unrealize
;
1241 item_klass
->draw_region
= item_bar_draw_region
;
1242 item_klass
->update_bounds
= item_bar_update_bounds
;
1243 item_klass
->distance
= item_bar_distance
;
1244 item_klass
->button_pressed
= item_bar_button_pressed
;
1245 item_klass
->button_released
= item_bar_button_released
;
1246 item_klass
->button2_pressed
= item_bar_2button_pressed
;
1247 item_klass
->enter_notify
= item_bar_enter_notify
;
1248 item_klass
->motion
= item_bar_motion
;
1251 GSF_CLASS (GnmItemBar
, gnm_item_bar
,
1252 gnm_item_bar_class_init
, gnm_item_bar_init
,