Whitespace.
[gnumeric.git] / src / item-bar.c
blobe03c1a862b4bf016aa143e392fe5da57bef0fc28
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * A canvas item implementing row/col headers with support for outlining.
5 * Author:
6 * Miguel de Icaza (miguel@kernel.org)
7 * Jody Goldberg (jody@gnome.org)
8 */
10 #include <gnumeric-config.h>
11 #include "gnm-i18n.h"
12 #include "gnumeric.h"
13 #include "item-bar.h"
14 #include "gnm-pane-impl.h"
16 #include "style-color.h"
17 #include "sheet.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"
23 #include "gui-util.h"
24 #include "parse-util.h"
25 #include "commands.h"
26 #include "gutils.h"
28 #include <goffice/goffice.h>
29 #include <gsf/gsf-impl-utils.h>
30 #include <gtk/gtk.h>
31 #define GNUMERIC_ITEM "BAR"
33 #include <string.h>
35 struct _GnmItemBar {
36 GocItem base;
38 GnmPane *pane;
39 GdkCursor *normal_cursor;
40 GdkCursor *change_cursor;
41 GtkWidget *tip; /* Tip for scrolling */
42 gboolean dragging;
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;
49 int resize_start_pos;
51 struct {
52 PangoItem *item;
53 PangoGlyphString *glyphs;
54 } pango;
56 /* Style: */
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;
67 GtkBorder padding;
70 typedef GocItemClass GnmItemBarClass;
71 static GocItemClass *parent_class;
73 enum {
74 GNM_ITEM_BAR_PROP_0,
75 GNM_ITEM_BAR_PROP_PANE,
76 GNM_ITEM_BAR_PROP_IS_COL_HEADER
79 static int
80 ib_compute_pixels_from_indent (GnmItemBar *ib, Sheet const *sheet)
82 gboolean is_cols = ib->is_col_header;
83 double const scale =
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)
90 return 0;
91 return (int)(ib->padding.left + (indent + 1) * 14 * scale + 0.5);
94 static void
95 ib_dispose_fonts (GnmItemBar *ib)
97 unsigned ui;
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] = {
110 "button.itembar",
111 "button.itembar:hover",
112 "button.itembar:active"
115 static void
116 ib_reload_color_style (GnmItemBar *ib)
118 GocItem *item = GOC_ITEM (ib);
119 GtkStyleContext *context = goc_item_get_style_context (item);
120 unsigned ui;
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]);
133 static void
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;
142 unsigned ui;
143 PangoContext *pcontext =
144 gtk_widget_get_pango_context (GTK_WIDGET (item->canvas));
145 PangoLayout *layout = pango_layout_new (pcontext);
146 PangoAttrList *attr_list;
147 GList *item_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]);
159 #else
160 context = g_object_ref (goc_item_get_style_context (item));
161 #endif
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);
167 #endif
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]) {
174 /* Fallback. */
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",
186 -1);
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);
197 } else
198 long_name = row_name (gnm_sheet_get_last_row (sheet));
199 pango_layout_set_text
200 (layout,
201 char_label ? "WWWWWWWWWW" : "8888888888",
202 strlen (long_name));
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,
208 &ib->padding);
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);
216 if (ib->pango.item)
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:
229 * @ib: #GnmItemBar
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);
240 int size;
241 unsigned ui;
243 ib_dispose_fonts (ib);
244 ib_reload_sizing_style (ib);
246 ib->cell_height = 0;
247 ib->cell_width = 0;
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) {
259 ib->indent = size;
260 goc_item_bounds_changed (GOC_ITEM (ib));
263 return ib->indent +
264 (ib->is_col_header ? ib->cell_height : ib->cell_width);
268 * item_bar_normal_font:
269 * @ib: #GnmItemBar
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)
282 return ib->indent;
285 static void
286 item_bar_update_bounds (GocItem *item)
288 GnmItemBar *ib = GNM_ITEM_BAR (item);
289 item->x0 = 0;
290 item->y0 = 0;
291 if (ib->is_col_header) {
292 item->x1 = G_MAXINT64/2;
293 item->y1 = (ib->cell_height + ib->indent);
294 } else {
295 item->x1 = (ib->cell_width + ib->indent);
296 item->y1 = G_MAXINT64/2;
300 static void
301 item_bar_realize (GocItem *item)
303 GnmItemBar *ib = GNM_ITEM_BAR (item);
304 GdkDisplay *display;
306 parent_class->realize (item);
308 display = gtk_widget_get_display (GTK_WIDGET (item->canvas));
309 ib->normal_cursor =
310 gdk_cursor_new_for_display (display, GDK_LEFT_PTR);
311 ib->change_cursor =
312 gdk_cursor_new_for_display (display,
313 ib->is_col_header
314 ? GDK_SB_H_DOUBLE_ARROW
315 : GDK_SB_V_DOUBLE_ARROW);
317 gnm_item_bar_calc_size (ib);
320 static void
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);
331 static void
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));
340 cairo_save (cr);
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]);
345 #endif
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) {
351 PangoRectangle size;
352 PangoFont *font = ib->selection_fonts[type];
353 int ascent = ib->selection_font_ascents[type];
354 int w, h;
355 GdkRGBA c;
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,
363 NULL, &size);
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);
371 cairo_clip (cr);
373 gtk_style_context_get_color (ctxt, selection_type_flags[type], &c);
374 gdk_cairo_set_source_rgba (cr, &c);
376 cairo_translate (cr,
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);
384 cairo_restore (cr);
388 gnm_item_bar_group_size (GnmItemBar const *ib, int max_outline)
390 return max_outline > 0
391 ? (ib->indent - 2) / (max_outline + 1)
392 : 0;
395 static gboolean
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;
400 int x0, x1, y0, y1;
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;
407 int pixels;
408 gboolean prev_visible;
409 gboolean const draw_below = sheet->outline_symbols_below != FALSE;
410 gboolean const draw_right = sheet->outline_symbols_right != FALSE;
411 int prev_level;
412 GocRect rect;
413 GocPoint points[3];
414 gboolean const has_object = scg->wbcg->new_object != NULL || scg->selected_objects != NULL;
415 gboolean const rtl = sheet->text_is_rtl != FALSE;
416 int shadow;
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);
435 end = x1;
436 rect.y = ib->indent;
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;
441 if (col > 0) {
442 cri = sheet_col_get_info (sheet, col-1);
443 prev_visible = cri->visible;
444 prev_level = cri->outline_level;
445 } else {
446 prev_visible = TRUE;
447 prev_level = 0;
450 do {
451 if (col >= gnm_sheet_get_max_cols (sheet))
452 return TRUE;
454 /* DO NOT enable resizing all until we get rid of
455 * resize_start_pos. It will be wrong if things ahead
456 * of it move
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;
462 else
463 pixels = cri->size_pixels;
465 if (cri->visible) {
466 int left, level, i = 0, pos = base_pos;
468 if (rtl) {
469 left = (total -= pixels);
470 rect.x = total;
471 } else {
472 rect.x = left = total;
473 total += pixels;
476 rect.width = pixels;
477 ib_draw_cell (ib, cr,
478 has_object
479 ? COL_ROW_NO_SELECTION
480 : sv_selection_col_type (sv, col),
481 char_label
482 ? col_name (col)
483 : row_name (col),
484 &rect);
486 if (len > 0) {
487 cairo_save (cr);
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);
492 if (!draw_right) {
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;
497 } else
498 points[0].x = total;
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);
509 } else if (rtl) {
510 cairo_move_to (cr, left - first_line_offset, pos);
511 cairo_line_to (cr, total + pixels, pos);
512 } else {
513 cairo_move_to (cr, left - first_line_offset, pos);
514 cairo_line_to (cr, total, pos);
517 cairo_stroke (cr);
518 first_line_offset = 0;
520 if (draw_right ^ rtl) {
521 if (prev_level > level) {
522 int safety = 0;
523 int top = pos - base_pos + 2; /* inside cell's shadow */
524 int size = inc < pixels ? inc : pixels;
526 if (size > 15)
527 size = 15;
528 else if (size < 6)
529 safety = 6 - size;
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);
535 if (size > 9) {
536 if (!prev_visible) {
537 top++;
538 left++;
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);
546 if (level > 0) {
547 cairo_move_to (cr, left+pixels/2, pos);
548 cairo_line_to (cr, left+pixels/2, pos+len);
550 } else {
551 if (prev_level > level) {
552 int safety = 0;
553 int top = pos - base_pos + 2; /* inside cell's shadow */
554 int size = inc < pixels ? inc : pixels;
555 int right;
557 if (size > 15)
558 size = 15;
559 else if (size < 6)
560 safety = 6 - size;
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);
567 if (size > 9) {
568 if (!prev_visible) {
569 top++;
570 right++;
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);
582 cairo_stroke (cr);
583 cairo_restore (cr);
586 prev_visible = cri->visible;
587 prev_level = cri->outline_level;
588 ++col;
589 } while ((rtl && end <= total) || (!rtl && total <= end));
590 } else {
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;
594 int const end = y1;
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;
600 if (rtl) {
601 base_pos = ib->indent + ib->cell_width - base_pos;
602 /* Move header bar 1 pixel to the left. */
603 rect.x = -1;
604 } else
605 rect.x = ib->indent;
606 rect.width = ib->cell_width;
608 if (row > 0) {
609 cri = sheet_row_get_info (sheet, row-1);
610 prev_visible = cri->visible;
611 prev_level = cri->outline_level;
612 } else {
613 prev_visible = TRUE;
614 prev_level = 0;
617 do {
618 if (row >= gnm_sheet_get_max_rows (sheet))
619 return TRUE;
621 /* DO NOT enable resizing all until we get rid of
622 * resize_start_pos. It will be wrong if things ahead
623 * of it move
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;
629 else
630 pixels = cri->size_pixels;
632 if (cri->visible) {
633 int level, i = 0, pos = base_pos;
634 int top = total;
636 total += pixels;
637 rect.y = top;
638 rect.height = pixels;
639 ib_draw_cell (ib, cr,
640 has_object
641 ? COL_ROW_NO_SELECTION
642 : sv_selection_row_type (sv, row),
643 row_name (row), &rect);
645 if (len > 0) {
646 cairo_save (cr);
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);
651 if (!draw_below) {
652 next = sheet_row_get_info (sheet, row + 1);
653 points[0].y = top;
654 } else
655 points[0].y = total;
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);
670 } else {
671 cairo_move_to (cr, pos, top - first_line_offset);
672 cairo_line_to (cr, pos, total);
675 cairo_stroke (cr);
676 first_line_offset = 0;
678 if (draw_below) {
679 if (prev_level > level) {
680 int left, safety = 0;
681 int size = inc < pixels ? inc : pixels;
683 if (size > 15)
684 size = 15;
685 else if (size < 6)
686 safety = 6 - size;
688 /* inside cell's shadow */
689 left = pos - dir * (.2 * inc - 2);
690 if (rtl)
691 left -= size;
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);
696 if (size > 9) {
697 if (!prev_visible) {
698 left += dir;
699 top++;
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);
707 if (level > 0) {
708 cairo_move_to (cr, pos, top+pixels/2);
709 cairo_line_to (cr, pos+len, top+pixels/2);
711 } else {
712 if (next->outline_level > level) {
713 int left, safety = 0;
714 int size = inc < pixels ? inc : pixels;
715 int bottom;
717 if (size > 15)
718 size = 15;
719 else if (size < 6)
720 safety = 6 - size;
722 /* inside cell's shadow */
723 left = pos - dir * (.2 * inc - 2);
724 if (rtl)
725 left -= size;
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);
731 if (size > 9) {
732 if (!next->visible) {
733 left += dir;
734 top++;
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);
745 cairo_stroke (cr);
746 cairo_restore (cr);
749 prev_visible = cri->visible;
750 prev_level = cri->outline_level;
751 ++row;
752 } while (total <= end);
754 gtk_style_context_restore (ctxt);
755 return TRUE;
758 static double
759 item_bar_distance (GocItem *item, double x, double y,
760 GocItem **actual_item)
762 *actual_item = item;
763 return 0.0;
767 * is_pointer_on_division:
768 * @ib: #GnmItemBar
769 * @x: in world coords
770 * @y: in world coords
771 * @the_total:
772 * @the_element:
773 * @minor_pos:
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;
787 int i;
789 if (ib->is_col_header) {
790 major = x;
791 minor = y;
792 i = ib->pane->first.col;
793 total = ib->pane->first_offset.x;
794 } else {
795 major = y;
796 minor = x;
797 i = ib->pane->first.row;
798 total = ib->pane->first_offset.y;
800 if (NULL != minor_pos)
801 *minor_pos = minor;
802 if (NULL != the_element)
803 *the_element = -1;
804 for (; total < major; i++) {
805 if (ib->is_col_header) {
806 if (i >= gnm_sheet_get_max_cols (sheet))
807 return NULL;
808 cri = sheet_col_get_info (sheet, i);
809 } else {
810 if (i >= gnm_sheet_get_max_rows (sheet))
811 return NULL;
812 cri = sheet_row_get_info (sheet, i);
815 if (cri->visible) {
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)) {
822 if (the_total)
823 *the_total = total;
824 if (the_element)
825 *the_element = i;
826 return (minor >= ib->indent) ? cri : NULL;
830 if (total > major) {
831 if (the_element)
832 *the_element = i;
833 return NULL;
836 return NULL;
839 /* x & y in world coords */
840 static void
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 */
847 if (NULL == window)
848 return;
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);
854 static void
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),
865 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));
871 else
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);
876 g_free (pixels);
877 g_free (points);
878 gtk_label_set_text (GTK_LABEL (ib->tip), buffer);
879 g_free(buffer);
883 static void
884 item_bar_resize_stop (GnmItemBar *ib, int new_size)
886 if (ib->colrow_being_resized != -1) {
887 if (new_size != 0)
888 scg_colrow_size_set (ib->pane->simple.scg,
889 ib->is_col_header,
890 ib->colrow_being_resized,
891 new_size);
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));
900 ib->tip = NULL;
904 static gboolean
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);
911 return TRUE;
914 static gint
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);
919 int inc, step;
921 if (ib->is_col_header) {
922 if (sheet->cols.max_outline_level <= 0)
923 return TRUE;
924 inc = (ib->indent - 2) / (sheet->cols.max_outline_level + 1);
925 } else {
926 if (sheet->rows.max_outline_level <= 0)
927 return TRUE;
928 inc = (ib->indent - 2) / (sheet->rows.max_outline_level + 1);
931 step = pixel / inc;
933 cmd_selection_outline_change (scg_wbc (scg), ib->is_col_header,
934 element, step);
935 return TRUE;
938 static gboolean
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;
951 int element;
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.
959 return TRUE;
962 if (button > 3)
963 return FALSE;
965 if (wbc_gtk_get_guru (wbcg) == NULL)
966 scg_mode_edit (scg);
968 cri = is_pointer_on_division (ib, x, y,
969 &start, &element, &minor_pos);
970 if (element < 0)
971 return FALSE;
972 if (minor_pos < ib->indent)
973 return outline_button_press (ib, element, minor_pos);
975 if (button == 3) {
976 if (wbc_gtk_get_guru (wbcg) != NULL)
977 return TRUE;
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);
986 return TRUE;
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);
1002 int wx, wy;
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,
1014 wx, wy, TRUE);
1015 gtk_widget_show_all (gtk_widget_get_toplevel (ib->tip));
1017 } else {
1018 if (wbc_gtk_get_guru (wbcg) != NULL &&
1019 !wbcg_entry_has_logical (wbcg))
1020 return TRUE;
1022 /* If we're editing it is possible for this to fail */
1023 if (!scg_colrow_select (scg, is_cols, element, bevent->state))
1024 return TRUE;
1026 ib->start_selection = element;
1027 gnm_pane_slide_init (pane);
1029 gnm_simple_canvas_grab (item);
1030 return TRUE;
1033 static gboolean
1034 item_bar_2button_pressed (GocItem *item, int button, double x, double y)
1036 GnmItemBar * const ib = GNM_ITEM_BAR (item);
1037 if (button > 3)
1038 return FALSE;
1040 if (button != 3)
1041 item_bar_resize_stop (ib, -1);
1042 return TRUE;
1045 static gboolean
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);
1051 return TRUE;
1054 static gboolean
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;
1065 gint64 pos;
1066 gint64 x = x_ * item->canvas->pixels_per_unit, y = y_ * item->canvas->pixels_per_unit;
1068 if (ib->colrow_being_resized != -1) {
1069 int new_size;
1070 if (!ib->has_resize_guides) {
1071 ib->has_resize_guides = TRUE;
1072 scg_size_guide_start (ib->pane->simple.scg,
1073 ib->is_col_header,
1074 ib->colrow_being_resized,
1075 TRUE);
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 */
1086 if (is_cols) {
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,
1091 pane->first.col,
1092 ib->colrow_being_resized);
1093 pos += new_size;
1095 } else {
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,
1100 pane->first.row,
1101 ib->colrow_being_resized);
1102 pos += new_size;
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,
1115 canvas, x, y,
1116 GNM_PANE_SLIDE_AT_COLROW_BOUND |
1117 (is_cols ? GNM_PANE_SLIDE_X : GNM_PANE_SLIDE_Y),
1118 cb_extend_selection, ib);
1119 } else
1120 ib_set_cursor (ib, x, y);
1121 return TRUE;
1124 static gboolean
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);
1134 else
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;
1142 return TRUE;
1145 static void
1146 item_bar_set_property (GObject *obj, guint param_id,
1147 GValue const *value, GParamSpec *pspec)
1149 GnmItemBar *ib = GNM_ITEM_BAR (obj);
1151 switch (param_id){
1152 case GNM_ITEM_BAR_PROP_PANE:
1153 ib->pane = g_value_get_object (value);
1154 break;
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));
1158 break;
1162 static void
1163 item_bar_dispose (GObject *obj)
1165 GnmItemBar *ib = GNM_ITEM_BAR (obj);
1166 unsigned ui;
1168 ib_dispose_fonts (ib);
1170 if (ib->tip) {
1171 gtk_widget_destroy (ib->tip);
1172 ib->tip = NULL;
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);
1189 static void
1190 gnm_item_bar_init (GnmItemBar *ib)
1192 ib->base.x0 = 0;
1193 ib->base.y0 = 0;
1194 ib->base.x1 = 0;
1195 ib->base.y1 = 0;
1197 ib->dragging = FALSE;
1198 ib->is_col_header = FALSE;
1199 ib->cell_width = ib->cell_height = 1;
1200 ib->indent = 0;
1201 ib->start_selection = -1;
1203 ib->tip = NULL;
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);
1215 #endif
1218 static void
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",
1229 P_("Pane"),
1230 P_("The pane containing the associated grid"),
1231 GNM_PANE_TYPE,
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",
1235 P_("IsColHeader"),
1236 P_("Is the item-bar a header for columns or rows"),
1237 FALSE,
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,
1254 GOC_TYPE_ITEM)