1 /*
2 * cell-draw.c: Cell drawing on screen
3 *
4 * Author:
5 * Miguel de Icaza 1998, 1999 (miguel@kernel.org)
6 * Jody Goldberg 2000-2002 (jody@gnome.org)
7 * Morten Welinder 2003 (terra@gnome.org)
8 */
9 #include <gnumeric-config.h>
10 #include <gnumeric.h>
11 #include <cell-draw.h>
12
13 #include <style.h>
14 #include <cell.h>
15 #include <sheet.h>
16 #include <gnm-format.h>
17 #include <rendered-value.h>
18 #include <parse-util.h>
19 #include <sheet-merge.h>
20 #include <goffice/goffice.h>
21
22 #include <gdk/gdk.h>
23 #include <string.h>
24 #include <math.h>
25
26 static char const hashes[] =
27 "################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################################";
28
29 static gboolean
cell_draw_simplify_cb(PangoAttribute * attribute,gboolean * recalc_height)30 cell_draw_simplify_cb (PangoAttribute *attribute,
31 gboolean *recalc_height)
32 {
33 if ((attribute->klass->type == PANGO_ATTR_RISE) ||
34 (attribute->klass->type == PANGO_ATTR_SCALE)) {
35 *recalc_height = TRUE;
36 return TRUE;
37 }
38 return (attribute->klass->type == PANGO_ATTR_SHAPE);
39 }
40
41 static void
cell_draw_simplify_attributes(GnmRenderedValue * rv)42 cell_draw_simplify_attributes (GnmRenderedValue *rv)
43 {
44 PangoAttrList *pal = pango_layout_get_attributes (rv->layout);
45 gboolean recalc_height = FALSE;
46 pango_attr_list_unref
47 (pango_attr_list_filter
48 (pal, (PangoAttrFilterFunc) cell_draw_simplify_cb, &recalc_height));
49 if (recalc_height)
50 pango_layout_get_size (rv->layout, NULL,
51 &rv->layout_natural_height);
52 }
53
54 /*
55 * G G
56 * r r
57 * i i
58 * d d
59 *
60 * Grid line a------+
61 * |mmmmmm|
62 * |m m|
63 * |mmmmmm|
64 * Grid line +------+
65 *
66 * 'm' == margin
67 * ' ' == space for contents
68 *
69 * @h_center: The number of pango units from x1 marking the logical center
70 * of the cell. NOTE This can be asymmetric. Passing
71 * <= 0 will use width / 2
72 */
73 gboolean
cell_calc_layout(G_GNUC_UNUSED GnmCell const * cell,GnmRenderedValue * rv,int y_direction,int width,int height,int h_center,GOColor * res_color,gint * res_x,gint * res_y)74 cell_calc_layout (G_GNUC_UNUSED GnmCell const *cell, GnmRenderedValue *rv, int y_direction,
75 int width, int height, int h_center,
76 GOColor *res_color, gint *res_x, gint *res_y)
77 {
78 int text_base;
79 PangoLayout *layout;
80 int indent;
81 int hoffset;
82 int rect_x, rect_y;
83 gboolean was_drawn;
84
85 g_return_val_if_fail (rv != NULL, FALSE);
86
87 layout = rv->layout;
88 indent = (rv->indent_left + rv->indent_right) * PANGO_SCALE;
89
90 was_drawn = rv->drawn;
91 rv->drawn = TRUE;
92
93 if (width <= 0 || height <= 0)
94 return FALSE;
95
96 hoffset = rv->indent_left * PANGO_SCALE;
97
98 #if 0
99 g_print ("%s: w=%d h=%d\n", cell_name (cell), width, height);
100 #endif
101
102 /* This rectangle has the whole area used by this cell
103 * excluding the surrounding grid lines and margins */
104 rect_x = PANGO_SCALE * (1 + GNM_COL_MARGIN);
105 rect_y = PANGO_SCALE * y_direction * (1 + GNM_ROW_MARGIN);
106
107 /* if a number overflows, do special drawing */
108 if (rv->layout_natural_width > width - indent &&
109 rv->might_overflow &&
110 !rv->numeric_overflow) {
111 char const *text = pango_layout_get_text (layout);
112 size_t textlen = strlen (text);
113 #if 0
114 g_print ("nat=%d w=%d i=%d\n", rv->layout_natural_width, width, indent);
115 #endif
116 /* This assumes that two hash marks are wider than
117 the characters in the number. Probably ok. */
118 pango_layout_set_text (layout, hashes,
119 MIN (sizeof (hashes) - 1, 2 * textlen));
120 cell_draw_simplify_attributes (rv);
121 rv->numeric_overflow = TRUE;
122 rv->variable_width = TRUE;
123 rv->hfilled = TRUE;
124 }
125
126 /* Special handling of error dates. */
127 if (!was_drawn && rv->numeric_overflow) {
128 pango_layout_set_text (layout, hashes, -1);
129 cell_draw_simplify_attributes (rv);
130 rv->variable_width = TRUE;
131 rv->hfilled = TRUE;
132 }
133
134 if (rv->rotation && !rv->noborders) {
135 GnmRenderedRotatedValue const *rrv = (GnmRenderedRotatedValue *)rv;
136 if (rv->wrap_text) {
137 /* quick fix for #394, may be not perfect */
138 double rot = rv->rotation / 180. * M_PI, actual_width;
139 actual_width = MAX (0, width - indent) * cos (rot) + height * fabs (sin (rot));
140 if (actual_width > pango_layout_get_width (layout)) {
141 pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
142 pango_layout_set_width (layout, actual_width);
143 gnm_rendered_value_remeasure (rv);
144 }
145 }
146 if (rrv->sin_a_neg) {
147 hoffset += (width - indent) - rv->layout_natural_width;
148 }
149 } else if (!rv->rotation && rv->wrap_text
150 && (rv->effective_halign != GNM_HALIGN_FILL)) {
151 int wanted_width = MAX (0, width - indent);
152 if (wanted_width != pango_layout_get_width (layout)) {
153 pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
154 pango_layout_set_width (layout, wanted_width);
155 gnm_rendered_value_remeasure (rv);
156 }
157 } else {
158 switch (rv->effective_halign) {
159 case GNM_HALIGN_RIGHT:
160 hoffset += (width - indent) - rv->layout_natural_width;
161 break;
162 case GNM_HALIGN_DISTRIBUTED:
163 case GNM_HALIGN_CENTER:
164 if (h_center == -1)
165 h_center = width / 2;
166 hoffset += h_center + (-indent - rv->layout_natural_width) / 2;
167 break;
168 case GNM_HALIGN_CENTER_ACROSS_SELECTION:
169 hoffset += ((width - indent) - rv->layout_natural_width) / 2;
170 break;
171 case GNM_HALIGN_FILL: {
172 PangoDirection dir = PANGO_DIRECTION_LTR;
173 if (!rv->hfilled &&
174 rv->layout_natural_width > 0 &&
175 width - indent >= 2 * rv->layout_natural_width) {
176 /*
177 * We ignore kerning between copies in calculating the number
178 * of copies needed. Instead we toss in a zero-width-space.
179 */
180 int copies = (width - indent) / rv->layout_natural_width;
181 char const *copy1 = pango_layout_get_text (layout);
182 size_t len1 = strlen (copy1);
183 GString *multi = g_string_sized_new ((len1 + 6) * copies);
184 int i;
185 PangoAttrList *attr = pango_layout_get_attributes (layout);
186
187 dir = pango_find_base_dir (copy1, -1);
188 for (i = 0; i < copies; i++) {
189 if (i)
190 g_string_append_unichar (multi, UNICODE_ZERO_WIDTH_SPACE_C);
191 g_string_append_len (multi, copy1, len1);
192 }
193 pango_layout_set_text (layout, multi->str, multi->len);
194 g_string_free (multi, TRUE);
195
196 if (attr != NULL && !go_pango_attr_list_is_empty (attr)) {
197 PangoAttrList *attr_c = pango_attr_list_copy (attr);
198 size_t len = len1 + UNICODE_ZERO_WIDTH_SPACE_C_UTF8_LENGTH;
199 for (i = 1; i < copies;
200 i++, len += len1 + UNICODE_ZERO_WIDTH_SPACE_C_UTF8_LENGTH)
201 pango_attr_list_splice (attr, attr_c, len, len1);
202 pango_attr_list_unref (attr_c);
203 }
204 } else
205 dir = pango_find_base_dir (pango_layout_get_text (layout), -1);
206 /* right align if text is RTL */
207 if (dir == PANGO_DIRECTION_RTL) {
208 PangoRectangle r;
209 pango_layout_get_extents (layout, NULL, &r);
210 hoffset += (width - indent) - r.width;
211 }
212
213 rv->hfilled = TRUE;
214 break;
215 }
216
217 #ifndef DEBUG_SWITCH_ENUM
218 default:
219 #endif
220 case GNM_HALIGN_GENERAL:
221 g_warning ("Unhandled horizontal alignment.");
222 case GNM_HALIGN_LEFT:
223 break;
224 }
225 }
226
227 /* Note that Excel always truncates the cell content only at the */
228 /* bottom even if the request is to align it at the bottom or to */
229 /* center it. We do the same for compatibilities sake. Also see */
230 /* bug #662368 */
231 switch (rv->effective_valign) {
232 #ifndef DEBUG_SWITCH_ENUM
233 default:
234 g_warning ("Unhandled vertical alignment.");
235 /* Fall through. */
236 #endif
237 case GNM_VALIGN_TOP:
238 text_base = rect_y;
239 break;
240
241 case GNM_VALIGN_BOTTOM: {
242 int dh = height - rv->layout_natural_height;
243 if (rv->rotation == 0 && dh < 0)
244 dh = 0;
245 text_base = rect_y + y_direction * dh;
246 break;
247 }
248
249 case GNM_VALIGN_DISTRIBUTED: /* dunno what this does yet */
250 case GNM_VALIGN_CENTER: {
251 int dh = (height - rv->layout_natural_height) / 2;
252 if (rv->rotation == 0 && dh < 0)
253 dh = 0;
254 text_base = rect_y + y_direction * dh;
255 break;
256 }
257
258 case GNM_VALIGN_JUSTIFY:
259 text_base = rect_y;
260 if (!rv->vfilled && height > rv->layout_natural_height) {
261 int line_count = pango_layout_get_line_count (layout);
262 if (line_count > 1) {
263 int spacing = (height - rv->layout_natural_height) /
264 (line_count - 1);
265 pango_layout_set_spacing (layout, spacing);
266 gnm_rendered_value_remeasure (rv);
267 }
268 }
269 rv->vfilled = TRUE;
270 break;
271 }
272
273 #if 0
274 if (rv->rotation)
275 g_print ("hoffset=%d, text_base=%d, n_width=%d, n_height=%d\n",
276 hoffset, text_base,
277 rv->layout_natural_width, rv->layout_natural_height);
278 #endif
279
280 *res_color = gnm_rendered_value_get_color (rv);
281 *res_x = rect_x + hoffset;
282 *res_y = text_base;
283
284 return TRUE;
285 }
286
287 /*
288 * This finishes a layout by pretending to draw it. The effect is to
289 * handler numerical overflow, filling, etc.
290 * (Doesn't currently handle vertical fill.)
291 */
292 void
cell_finish_layout(GnmCell * cell,GnmRenderedValue * rv,int col_width,gboolean inhibit_overflow)293 cell_finish_layout (GnmCell *cell, GnmRenderedValue *rv,
294 int col_width,
295 gboolean inhibit_overflow)
296 {
297 gint dummy_x, dummy_y;
298 GOColor dummy_fore_color;
299 int dummy_h_center = -1; /* Affects position only. */
300 int dummy_height = 1; /* Unhandled. */
301 gboolean might_overflow;
302 GnmRenderedValue *cell_rv;
303
304 cell_rv = gnm_cell_get_rendered_value (cell);
305
306 if (!rv)
307 rv = cell_rv;
308
309 if (rv->drawn)
310 return;
311
312 if (rv->variable_width && rv == cell_rv &&
313 !go_format_is_general (gnm_cell_get_format (cell))) {
314 /*
315 * We get here when entering a new value in a cell
316 * with a format that has a filler, for example
317 * one of the standard accounting formats. We need
318 * to rerender such that the filler gets a chance
319 * to expand.
320 */
321 rv = gnm_cell_render_value (cell, TRUE);
322 }
323
324 might_overflow = rv->might_overflow;
325 if (inhibit_overflow) rv->might_overflow = FALSE;
326 cell_calc_layout (cell, rv, -1, col_width * PANGO_SCALE,
327 dummy_height, dummy_h_center, &dummy_fore_color,
328 &dummy_x, &dummy_y);
329 rv->might_overflow = might_overflow;
330 }
331
332
333 static void
cell_draw_extension_mark_bottom(cairo_t * cr,int x1,int y1,int height,int h_center)334 cell_draw_extension_mark_bottom (cairo_t *cr, int x1, int y1, int height, int h_center)
335 {
336 cairo_set_source_rgba (cr, 1, 0, 0, 0.7);
337 cairo_new_path (cr);
338 cairo_move_to (cr, x1 + h_center, y1 + height);
339 cairo_rel_line_to (cr, -3, -3);
340 cairo_rel_line_to (cr, 6, 0);
341 cairo_close_path (cr);
342 cairo_fill (cr);
343 }
344
345 static void
cell_draw_extension_mark_left(cairo_t * cr,int x1,int y1,int height)346 cell_draw_extension_mark_left (cairo_t *cr, int x1, int y1, int height)
347 {
348 cairo_set_source_rgba (cr, 1, 0, 0, 0.7);
349 cairo_new_path (cr);
350 cairo_move_to (cr, x1, y1 + height/2);
351 cairo_rel_line_to (cr, 3, -3);
352 cairo_rel_line_to (cr, 0, 6);
353 cairo_close_path (cr);
354 cairo_fill (cr);
355 }
356
357 static void
cell_draw_extension_mark_right(cairo_t * cr,int x1,int y1,int width,int height)358 cell_draw_extension_mark_right (cairo_t *cr, int x1, int y1, int width, int height)
359 {
360 cairo_set_source_rgba (cr, 1, 0, 0, 0.7);
361 cairo_new_path (cr);
362 cairo_move_to (cr, x1 + width, y1 + height/2);
363 cairo_rel_line_to (cr, -3, -3);
364 cairo_rel_line_to (cr, 0, 6);
365 cairo_close_path (cr);
366 cairo_fill (cr);
367
368 }
369
370
371 static void
cell_draw_h_extension_markers(cairo_t * cr,GnmRenderedValue * rv,int x1,int y1,int width,int height)372 cell_draw_h_extension_markers (cairo_t *cr, GnmRenderedValue *rv,
373 int x1, int y1,
374 int width, int height)
375 {
376 switch (rv->effective_halign) {
377 case GNM_HALIGN_GENERAL:
378 case GNM_HALIGN_LEFT:
379 cell_draw_extension_mark_right (cr, x1, y1, width, height);
380 break;
381 case GNM_HALIGN_RIGHT:
382 cell_draw_extension_mark_left (cr, x1, y1, height);
383 break;
384 case GNM_HALIGN_DISTRIBUTED:
385 case GNM_HALIGN_CENTER:
386 case GNM_HALIGN_CENTER_ACROSS_SELECTION:
387 cell_draw_extension_mark_right (cr, x1, y1, width, height);
388 cell_draw_extension_mark_left (cr, x1, y1, height);
389 break;
390 case GNM_HALIGN_FILL:
391 default:
392 break;
393 }
394 }
395
396 static void
cell_draw_v_extension_markers(cairo_t * cr,int x1,int y1,int width,int height,int h_center)397 cell_draw_v_extension_markers (cairo_t *cr,
398 int x1, int y1,
399 int width, int height,
400 int h_center)
401 {
402 if (h_center == -1)
403 h_center = width / 2;
404 cell_draw_extension_mark_bottom (cr, x1, y1, height, h_center);
405 }
406
407 /**
408 * cell_draw:
409 * @cell: #GnmCell const
410 * @cr: #cairo_t
411 * @x:
412 * @y:
413 * @width: including margins and leading grid line
414 * @height: including margins and leading grid line
415 * @h_center:
416 * @show_extension_markers:
417 **/
418 void
cell_draw(GnmCell const * cell,cairo_t * cr,int x1,int y1,int width,int height,int h_center,gboolean show_extension_markers)419 cell_draw (GnmCell const *cell, cairo_t *cr,
420 int x1, int y1, int width, int height, int h_center,
421 gboolean show_extension_markers)
422 {
423 GOColor fore_color;
424 gint x;
425 gint y;
426 GnmRenderedValue *rv;
427
428 /* Get the sizes exclusive of margins and grids */
429 /* Note: +1 because size_pixels includes leading gridline. */
430 height -= GNM_ROW_MARGIN + GNM_ROW_MARGIN + 1;
431 width -= GNM_COL_MARGIN + GNM_COL_MARGIN + 1;
432
433 if (h_center > GNM_COL_MARGIN)
434 h_center = h_center - GNM_COL_MARGIN - 1 + (h_center % 2);
435
436 rv = gnm_cell_fetch_rendered_value (cell, TRUE);
437
438 if (cell_calc_layout (cell, rv, +1,
439 width * PANGO_SCALE,
440 height * PANGO_SCALE,
441 h_center == -1 ? -1 : (h_center * PANGO_SCALE),
442 &fore_color, &x, &y)) {
443
444 cairo_save (cr);
445
446 /*
447 * HACK -- do not clip rotated cells. This gives an
448 * approximation to the right effect. (The right way
449 * would be to create a proper cellspan type.)
450 */
451 if (!rv->rotation) {
452 cairo_new_path (cr);
453 /* +1 to get past left grid-line. */
454 cairo_rectangle (cr, x1 + 1 + GNM_COL_MARGIN,
455 y1 + 1 + GNM_ROW_MARGIN,
456 width, height);
457
458 cairo_clip (cr);
459 }
460
461 /* See http://bugzilla.gnome.org/show_bug.cgi?id=105322 */
462 cairo_set_source_rgba (cr, GO_COLOR_TO_CAIRO (fore_color));
463
464 if (rv->rotation) {
465 GnmRenderedRotatedValue *rrv = (GnmRenderedRotatedValue *)rv;
466 struct GnmRenderedRotatedValueInfo const *li = rrv->lines;
467 GSList *lines;
468
469 for (lines = pango_layout_get_lines (rv->layout);
470 lines;
471 lines = lines->next, li++) {
472 cairo_save (cr);
473 cairo_move_to (cr, x1 + PANGO_PIXELS (x + li->dx), y1 + PANGO_PIXELS (y + li->dy));
474 cairo_rotate (cr, rv->rotation * (-M_PI / 180));
475 pango_cairo_show_layout_line (cr, lines->data);
476 cairo_restore (cr);
477 }
478 } else {
479 cairo_save (cr);
480 cairo_translate (cr, x1 + PANGO_PIXELS (x), y1 + PANGO_PIXELS (y));
481 pango_cairo_show_layout (cr, rv->layout);
482 cairo_restore (cr);
483
484 if (show_extension_markers &&
485 width < PANGO_PIXELS (rv->layout_natural_width)) {
486 cairo_save (cr);
487 cell_draw_h_extension_markers
488 (cr, rv,
489 x1 + 1 + GNM_COL_MARGIN,
490 y1 + 1 + GNM_ROW_MARGIN,
491 width, height);
492 cairo_restore (cr);
493 }
494
495 if (show_extension_markers &&
496 height < PANGO_PIXELS (rv->layout_natural_height)) {
497 cairo_save (cr);
498 cell_draw_v_extension_markers
499 (cr, x1 + 1 + GNM_COL_MARGIN,
500 y1 + 1 + GNM_ROW_MARGIN,
501 width, height, h_center);
502 cairo_restore (cr);
503 }
504 }
505 cairo_restore (cr);
506 }
507 }
508