1
2 /*
3 * cellspan.c: Keep track of the columns on which a cell
4 * displays information.
5 *
6 * Author:
7 * Miguel de Icaza (miguel@gnu.org)
8 * Jody Goldberg (jody@gnome.org)
9 *
10 * The information on cell spanning is attached in the row ColRowInfo
11 * structures. The actual representation of this information is
12 * opaque to the code that uses it (the idea is: this first
13 * implementation is not really awesome).
14 *
15 * The reason we need this is that the Grid draw code expects to find
16 * the "owner" of the cell to be able to repaint its contents.
17 */
18 #include <gnumeric-config.h>
19 #include <gnumeric.h>
20 #include <cellspan.h>
21
22 #include <cell.h>
23 #include <sheet.h>
24 #include <sheet-merge.h>
25 #include <sheet-style.h>
26 #include <style.h>
27 #include <colrow.h>
28 #include <value.h>
29 #include <rendered-value.h>
30
31 static guint
col_hash(gconstpointer key)32 col_hash (gconstpointer key)
33 {
34 return GPOINTER_TO_INT(key);
35 }
36
37 static gint
col_compare(gconstpointer a,gconstpointer b)38 col_compare (gconstpointer a, gconstpointer b)
39 {
40 if (GPOINTER_TO_INT(a) == GPOINTER_TO_INT(b))
41 return 1;
42 return 0;
43 }
44
45 static void
free_hash_value(G_GNUC_UNUSED gpointer key,gpointer value,G_GNUC_UNUSED gpointer user_data)46 free_hash_value (G_GNUC_UNUSED gpointer key, gpointer value,
47 G_GNUC_UNUSED gpointer user_data)
48 {
49 g_free (value);
50 }
51
52 void
row_destroy_span(ColRowInfo * ri)53 row_destroy_span (ColRowInfo *ri)
54 {
55 if (ri == NULL || ri->spans == NULL)
56 return;
57
58 g_hash_table_foreach (ri->spans, free_hash_value, NULL);
59 g_hash_table_destroy (ri->spans);
60 ri->spans = NULL;
61 }
62
63 /*
64 * cell_register_span
65 * @cell: The cell to register the span
66 * @left: the leftmost column used by the cell
67 * @right: the rightmost column used by the cell
68 *
69 * Registers the region
70 */
71 void
cell_register_span(GnmCell const * cell,int left,int right)72 cell_register_span (GnmCell const *cell, int left, int right)
73 {
74 ColRowInfo *ri;
75 int row, i;
76
77 g_return_if_fail (cell != NULL);
78 g_return_if_fail (left <= right);
79
80 row = cell->pos.row;
81 ri = sheet_row_get (cell->base.sheet, row);
82
83 if (left == right)
84 return;
85
86 if (ri->spans == NULL)
87 ri->spans = g_hash_table_new (col_hash, col_compare);
88
89 for (i = left; i <= right; i++){
90 CellSpanInfo *spaninfo = g_new (CellSpanInfo, 1);
91
92 spaninfo->cell = cell;
93 spaninfo->left = left;
94 spaninfo->right = right;
95
96 g_return_if_fail (row_span_get (ri, i) == NULL);
97 g_hash_table_insert (ri->spans, GINT_TO_POINTER(i), spaninfo);
98 }
99 }
100
101 static gboolean
span_remove(G_GNUC_UNUSED gpointer key,gpointer value,gpointer user_data)102 span_remove (G_GNUC_UNUSED gpointer key, gpointer value,
103 gpointer user_data)
104 {
105 CellSpanInfo *span = (CellSpanInfo *)value;
106 GnmCell *cell = user_data;
107
108 if (cell == span->cell) {
109 g_free (span); /* free the span descriptor */
110 return TRUE;
111 }
112 return FALSE;
113 }
114
115 /*
116 * sheet_cell_unregister_span
117 * @cell: The cell to remove from the span information
118 *
119 * Remove all references to this cell in the span hashtable
120 */
121 void
cell_unregister_span(GnmCell const * const cell)122 cell_unregister_span (GnmCell const * const cell)
123 {
124 ColRowInfo *ri;
125
126 g_return_if_fail (cell != NULL);
127
128 ri = sheet_row_get (cell->base.sheet, cell->pos.row);
129
130 if (ri->spans == NULL)
131 return;
132
133 g_hash_table_foreach_remove (ri->spans,
134 &span_remove, (gpointer)cell);
135 }
136
137 /*
138 * row_span_get
139 * @ri: The ColRowInfo for the row we are looking up
140 * @col: the column position
141 *
142 * Returns SpanInfo of the spanning cell being display at the
143 * column. Including
144 * - the cell whose contents span.
145 * - The first and last col in the span.
146 */
147 CellSpanInfo const *
row_span_get(ColRowInfo const * const ri,int const col)148 row_span_get (ColRowInfo const * const ri, int const col)
149 {
150 g_return_val_if_fail (ri != NULL, NULL);
151
152 if (ri->spans == NULL)
153 return NULL;
154 return g_hash_table_lookup (ri->spans, GINT_TO_POINTER(col));
155 }
156
157 /* making CellSpanInfo a boxed type. As this objects are constant, no need
158 * to really copy free them. Right? */
159 static const CellSpanInfo*
cell_span_info_copy(CellSpanInfo const * sp)160 cell_span_info_copy (CellSpanInfo const *sp)
161 {
162 return sp;
163 }
164
165 GType
cell_span_info_get_type(void)166 cell_span_info_get_type (void)
167 {
168 static GType t = 0;
169
170 if (t == 0) {
171 t = g_boxed_type_register_static ("CellSpanInfo",
172 (GBoxedCopyFunc)cell_span_info_copy,
173 (GBoxedFreeFunc)cell_span_info_copy);
174 }
175 return t;
176 }
177
178 /**
179 * cellspan_is_empty:
180 *
181 * Utility to ensure that a cell is completely empty.
182 * - no spans
183 * - no merged regions
184 * - no content
185 *
186 * No need to check for merged cells here. We have already bounded the search region
187 * using adjacent merged cells.
188 *
189 * We could probably have done the same thing with the span regions too, but
190 * the current representation is not well suited to that type of search
191 * returns %TRUE if the cell is empty.
192 */
193 static inline gboolean
cellspan_is_empty(int col,GnmCell const * ok_span_cell)194 cellspan_is_empty (int col, GnmCell const *ok_span_cell)
195 {
196 Sheet *sheet = ok_span_cell->base.sheet;
197 int row = ok_span_cell->pos.row;
198 ColRowInfo *ri = sheet_row_get (sheet, row);
199 CellSpanInfo const *span = row_span_get (ri, col);
200 GnmCell const *tmp;
201
202 if (span != NULL && span->cell != ok_span_cell)
203 return FALSE;
204
205 tmp = sheet_cell_get (sheet, col, row);
206
207 /* FIXME : cannot use gnm_cell_is_empty until expressions can span.
208 * because cells with expressions start out with value Empty
209 * existing spans continue to flow through, but never get removed
210 * because we don't respan expression results.
211 */
212 return (tmp == NULL || tmp->value == NULL ||
213 (VALUE_IS_EMPTY (tmp->value) && !gnm_cell_has_expr(tmp)));
214 }
215
216 /*
217 * cell_calc_span:
218 * @cell: The cell we will examine
219 * @col1: return value: the first column used by this cell
220 * @col2: return value: the last column used by this cell
221 *
222 * This routine returns the column interval used by a GnmCell.
223 */
224 void
cell_calc_span(GnmCell const * cell,int * col1,int * col2)225 cell_calc_span (GnmCell const *cell, int *col1, int *col2)
226 {
227 Sheet *sheet;
228 int h_align, v_align, left, max_col, min_col;
229 int pos;
230 int cell_width_pixel, indented_w;
231 GnmStyle const *style;
232 ColRowInfo const *ci;
233 GnmRange const *merge_left;
234 GnmRange const *merge_right;
235
236 g_return_if_fail (cell != NULL);
237
238 sheet = cell->base.sheet;
239 style = gnm_cell_get_effective_style (cell);
240 h_align = gnm_style_default_halign (style, cell);
241
242 /*
243 * Report only one column is used if
244 * - Cell is in a hidden col
245 * - Cell is a number
246 * - Cell is the top left of a merged cell
247 * - The text fits inside column (for non center across selection)
248 * - The alignment mode are set to "justify"
249 */
250 if (sheet != NULL &&
251 h_align != GNM_HALIGN_CENTER_ACROSS_SELECTION &&
252 (gnm_cell_is_merged (cell) ||
253 (!sheet->display_formulas && gnm_cell_is_number (cell)))) {
254 *col1 = *col2 = cell->pos.col;
255 return;
256 }
257
258 v_align = gnm_style_get_align_v (style);
259 indented_w = cell_width_pixel = gnm_cell_rendered_width (cell);
260 if (h_align == GNM_HALIGN_LEFT || h_align == GNM_HALIGN_RIGHT) {
261 GnmRenderedValue *rv = gnm_cell_get_rendered_value (cell);
262 char const *text = (rv)? pango_layout_get_text (rv->layout): NULL;
263 PangoDirection dir = (text && *text)? pango_find_base_dir (text, -1): PANGO_DIRECTION_LTR;
264 if (gnm_style_get_align_h (style) == GNM_HALIGN_GENERAL && dir == PANGO_DIRECTION_RTL)
265 h_align = GNM_HALIGN_RIGHT;
266 indented_w += gnm_cell_rendered_offset (cell);
267 if (sheet->text_is_rtl)
268 h_align = (h_align == GNM_HALIGN_LEFT) ? GNM_HALIGN_RIGHT : GNM_HALIGN_LEFT;
269 }
270
271 ci = sheet_col_get_info (sheet, cell->pos.col);
272 if (gnm_cell_is_empty (cell) ||
273 !ci->visible ||
274 (h_align != GNM_HALIGN_CENTER_ACROSS_SELECTION &&
275 (gnm_style_get_wrap_text (style) ||
276 indented_w <= COL_INTERNAL_WIDTH (ci))) ||
277 h_align == GNM_HALIGN_JUSTIFY ||
278 h_align == GNM_HALIGN_FILL ||
279 h_align == GNM_HALIGN_DISTRIBUTED ||
280 v_align == GNM_VALIGN_JUSTIFY ||
281 v_align == GNM_VALIGN_DISTRIBUTED) {
282 *col1 = *col2 = cell->pos.col;
283 return;
284 }
285
286 gnm_sheet_merge_get_adjacent (sheet, &cell->pos, &merge_left, &merge_right);
287 min_col = (merge_left != NULL) ? merge_left->end.col : -1;
288 max_col = (merge_right != NULL) ? merge_right->start.col : gnm_sheet_get_max_cols (sheet);
289
290 *col1 = *col2 = cell->pos.col;
291 switch (h_align) {
292 case GNM_HALIGN_LEFT:
293 pos = cell->pos.col + 1;
294 left = indented_w - COL_INTERNAL_WIDTH (ci);
295
296 for (; left > 0 && pos < max_col; pos++){
297 ColRowInfo const *ci = sheet_col_get_info (sheet, pos);
298
299 if (ci->visible) {
300 if (!cellspan_is_empty (pos, cell))
301 return;
302
303 /* The space consumed is:
304 * - The margin_b from the last column
305 * - The width of the cell
306 */
307 left -= ci->size_pixels - 1;
308 *col2 = pos;
309 }
310 }
311 return;
312
313 case GNM_HALIGN_RIGHT:
314 pos = cell->pos.col - 1;
315 left = indented_w - COL_INTERNAL_WIDTH (ci);
316
317 for (; left > 0 && pos > min_col; pos--){
318 ColRowInfo const *ci = sheet_col_get_info (sheet, pos);
319
320 if (ci->visible) {
321 if (!cellspan_is_empty (pos, cell))
322 return;
323
324 /* The space consumed is:
325 * - The margin_a from the last column
326 * - The width of this cell
327 */
328 left -= ci->size_pixels - 1;
329 *col1 = pos;
330 }
331 }
332 return;
333
334 case GNM_HALIGN_CENTER: {
335 int remain_left, remain_right;
336 int pos_l, pos_r;
337
338 pos_l = pos_r = cell->pos.col;
339 left = cell_width_pixel - COL_INTERNAL_WIDTH (ci);
340
341 remain_left = left / 2 + (left % 2);
342 remain_right = left / 2;
343
344 for (; remain_left > 0;)
345 if (--pos_l > min_col){
346 ColRowInfo const *ci = sheet_col_get_info (sheet, pos_l);
347
348 if (ci->visible) {
349 if (cellspan_is_empty (pos_l, cell)) {
350 remain_left -= ci->size_pixels - 1;
351 *col1 = pos_l;
352 } else
353 remain_left = 0;
354 }
355 } else
356 remain_left = 0;
357
358 for (; remain_right > 0;)
359 if (++pos_r < max_col){
360 ColRowInfo const *ci = sheet_col_get_info (sheet, pos_r);
361
362 if (ci->visible) {
363 if (cellspan_is_empty (pos_r, cell)) {
364 remain_right -= ci->size_pixels - 1;
365 *col2 = pos_r;
366 } else
367 max_col = remain_right = 0;
368 }
369 } else
370 remain_right = 0;
371 break;
372 } /* case GNM_HALIGN_CENTER */
373
374 case GNM_HALIGN_CENTER_ACROSS_SELECTION: {
375 int const row = cell->pos.row;
376 int pos_l, pos_r;
377
378 pos_l = pos_r = cell->pos.col;
379 while (--pos_l > min_col) {
380 ColRowInfo const *ci = sheet_col_get_info (sheet, pos_l);
381 if (ci->visible) {
382 if (cellspan_is_empty (pos_l, cell)) {
383 GnmStyle const * const style =
384 sheet_style_get (cell->base.sheet, pos_l, row);
385
386 if (gnm_style_get_align_h (style) != GNM_HALIGN_CENTER_ACROSS_SELECTION)
387 break;
388 *col1 = pos_l;
389 } else
390 break;
391 }
392 }
393 while (++pos_r < max_col) {
394 ColRowInfo const *ci = sheet_col_get_info (sheet, pos_r);
395 if (ci->visible) {
396 if (cellspan_is_empty (pos_r, cell)) {
397 GnmStyle const * const style =
398 sheet_style_get (cell->base.sheet, pos_r, row);
399
400 if (gnm_style_get_align_h (style) != GNM_HALIGN_CENTER_ACROSS_SELECTION)
401 break;
402 *col2 = pos_r;
403 } else
404 break;
405 }
406 }
407 break;
408 }
409
410 default:
411 g_warning ("Unknown horizontal alignment type %x.", h_align);
412 } /* switch */
413 }
414
415 void
row_calc_spans(ColRowInfo * ri,int row,Sheet const * sheet)416 row_calc_spans (ColRowInfo *ri, int row, Sheet const *sheet)
417 {
418 int left, right, col;
419 GnmRange const *merged;
420 GnmCell *cell;
421 int const last = sheet->cols.max_used;
422
423 row_destroy_span (ri);
424 for (col = 0 ; col <= last ; ) {
425 cell = sheet_cell_get (sheet, col, row);
426 if (cell == NULL) {
427 /* skip segments with no cells */
428 if (col == COLROW_SEGMENT_START (col) &&
429 NULL == COLROW_GET_SEGMENT (&(sheet->cols), col))
430 col = COLROW_SEGMENT_END (col) + 1;
431 else
432 col++;
433 continue;
434 }
435
436 /* render as necessary */
437 (void)gnm_cell_fetch_rendered_value (cell, TRUE);
438
439 if (gnm_cell_is_merged (cell)) {
440 merged = gnm_sheet_merge_is_corner (sheet, &cell->pos);
441 if (NULL != merged) {
442 col = merged->end.col + 1;
443 continue;
444 }
445 }
446
447 cell_calc_span (cell, &left, &right);
448 if (left != right) {
449 cell_register_span (cell, left, right);
450 col = right + 1;
451 } else
452 col++;
453 }
454
455 ri->needs_respan = FALSE;
456 }
457