1.12.42
[gnumeric.git] / src / cellspan.c
blobea00207522d6ff5b2a85050922400a58a327d3a3
2 /*
3 * cellspan.c: Keep track of the columns on which a cell
4 * displays information.
6 * Author:
7 * Miguel de Icaza (miguel@gnu.org)
8 * Jody Goldberg (jody@gnome.org)
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).
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.
18 #include <gnumeric-config.h>
19 #include <gnumeric.h>
20 #include <cellspan.h>
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>
31 static guint
32 col_hash (gconstpointer key)
34 return GPOINTER_TO_INT(key);
37 static gint
38 col_compare (gconstpointer a, gconstpointer b)
40 if (GPOINTER_TO_INT(a) == GPOINTER_TO_INT(b))
41 return 1;
42 return 0;
45 static void
46 free_hash_value (G_GNUC_UNUSED gpointer key, gpointer value,
47 G_GNUC_UNUSED gpointer user_data)
49 g_free (value);
52 void
53 row_destroy_span (ColRowInfo *ri)
55 if (ri == NULL || ri->spans == NULL)
56 return;
58 g_hash_table_foreach (ri->spans, free_hash_value, NULL);
59 g_hash_table_destroy (ri->spans);
60 ri->spans = NULL;
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
69 * Registers the region
71 void
72 cell_register_span (GnmCell const *cell, int left, int right)
74 ColRowInfo *ri;
75 int row, i;
77 g_return_if_fail (cell != NULL);
78 g_return_if_fail (left <= right);
80 row = cell->pos.row;
81 ri = sheet_row_get (cell->base.sheet, row);
83 if (left == right)
84 return;
86 if (ri->spans == NULL)
87 ri->spans = g_hash_table_new (col_hash, col_compare);
89 for (i = left; i <= right; i++){
90 CellSpanInfo *spaninfo = g_new (CellSpanInfo, 1);
92 spaninfo->cell = cell;
93 spaninfo->left = left;
94 spaninfo->right = right;
96 g_return_if_fail (row_span_get (ri, i) == NULL);
97 g_hash_table_insert (ri->spans, GINT_TO_POINTER(i), spaninfo);
101 static gboolean
102 span_remove (G_GNUC_UNUSED gpointer key, gpointer value,
103 gpointer user_data)
105 CellSpanInfo *span = (CellSpanInfo *)value;
106 GnmCell *cell = user_data;
108 if (cell == span->cell) {
109 g_free (span); /* free the span descriptor */
110 return TRUE;
112 return FALSE;
116 * sheet_cell_unregister_span
117 * @cell: The cell to remove from the span information
119 * Remove all references to this cell in the span hashtable
121 void
122 cell_unregister_span (GnmCell const * const cell)
124 ColRowInfo *ri;
126 g_return_if_fail (cell != NULL);
128 ri = sheet_row_get (cell->base.sheet, cell->pos.row);
130 if (ri->spans == NULL)
131 return;
133 g_hash_table_foreach_remove (ri->spans,
134 &span_remove, (gpointer)cell);
138 * row_span_get
139 * @ri: The ColRowInfo for the row we are looking up
140 * @col: the column position
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.
147 CellSpanInfo const *
148 row_span_get (ColRowInfo const * const ri, int const col)
150 g_return_val_if_fail (ri != NULL, NULL);
152 if (ri->spans == NULL)
153 return NULL;
154 return g_hash_table_lookup (ri->spans, GINT_TO_POINTER(col));
157 /* making CellSpanInfo a boxed type. As this objects are constant, no need
158 * to really copy free them. Right? */
159 static const CellSpanInfo*
160 cell_span_info_copy (CellSpanInfo const *sp)
162 return sp;
165 GType
166 cell_span_info_get_type (void)
168 static GType t = 0;
170 if (t == 0) {
171 t = g_boxed_type_register_static ("CellSpanInfo",
172 (GBoxedCopyFunc)cell_span_info_copy,
173 (GBoxedFreeFunc)cell_span_info_copy);
175 return t;
179 * cellspan_is_empty:
181 * Utility to ensure that a cell is completely empty.
182 * - no spans
183 * - no merged regions
184 * - no content
186 * No need to check for merged cells here. We have already bounded the search region
187 * using adjacent merged cells.
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.
193 static inline gboolean
194 cellspan_is_empty (int col, GnmCell const *ok_span_cell)
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;
202 if (span != NULL && span->cell != ok_span_cell)
203 return FALSE;
205 tmp = sheet_cell_get (sheet, col, row);
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.
212 return (tmp == NULL || tmp->value == NULL ||
213 (VALUE_IS_EMPTY (tmp->value) && !gnm_cell_has_expr(tmp)));
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
222 * This routine returns the column interval used by a GnmCell.
224 void
225 cell_calc_span (GnmCell const *cell, int *col1, int *col2)
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;
236 g_return_if_fail (cell != NULL);
238 sheet = cell->base.sheet;
239 style = gnm_cell_get_style (cell);
240 h_align = gnm_style_default_halign (style, cell);
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"
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;
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;
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;
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);
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);
296 for (; left > 0 && pos < max_col; pos++){
297 ColRowInfo const *ci = sheet_col_get_info (sheet, pos);
299 if (ci->visible) {
300 if (!cellspan_is_empty (pos, cell))
301 return;
303 /* The space consumed is:
304 * - The margin_b from the last column
305 * - The width of the cell
307 left -= ci->size_pixels - 1;
308 *col2 = pos;
311 return;
313 case GNM_HALIGN_RIGHT:
314 pos = cell->pos.col - 1;
315 left = indented_w - COL_INTERNAL_WIDTH (ci);
317 for (; left > 0 && pos > min_col; pos--){
318 ColRowInfo const *ci = sheet_col_get_info (sheet, pos);
320 if (ci->visible) {
321 if (!cellspan_is_empty (pos, cell))
322 return;
324 /* The space consumed is:
325 * - The margin_a from the last column
326 * - The width of this cell
328 left -= ci->size_pixels - 1;
329 *col1 = pos;
332 return;
334 case GNM_HALIGN_CENTER: {
335 int remain_left, remain_right;
336 int pos_l, pos_r;
338 pos_l = pos_r = cell->pos.col;
339 left = cell_width_pixel - COL_INTERNAL_WIDTH (ci);
341 remain_left = left / 2 + (left % 2);
342 remain_right = left / 2;
344 for (; remain_left > 0;)
345 if (--pos_l > min_col){
346 ColRowInfo const *ci = sheet_col_get_info (sheet, pos_l);
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;
355 } else
356 remain_left = 0;
358 for (; remain_right > 0;)
359 if (++pos_r < max_col){
360 ColRowInfo const *ci = sheet_col_get_info (sheet, pos_r);
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;
369 } else
370 remain_right = 0;
371 break;
372 } /* case GNM_HALIGN_CENTER */
374 case GNM_HALIGN_CENTER_ACROSS_SELECTION: {
375 int const row = cell->pos.row;
376 int pos_l, pos_r;
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);
386 if (gnm_style_get_align_h (style) != GNM_HALIGN_CENTER_ACROSS_SELECTION)
387 break;
388 *col1 = pos_l;
389 } else
390 break;
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);
400 if (gnm_style_get_align_h (style) != GNM_HALIGN_CENTER_ACROSS_SELECTION)
401 break;
402 *col2 = pos_r;
403 } else
404 break;
407 break;
410 default:
411 g_warning ("Unknown horizontal alignment type %x.", h_align);
412 } /* switch */
415 void
416 row_calc_spans (ColRowInfo *ri, int row, Sheet const *sheet)
418 int left, right, col;
419 GnmRange const *merged;
420 GnmCell *cell;
421 int const last = sheet->cols.max_used;
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;
436 /* render as necessary */
437 (void)gnm_cell_fetch_rendered_value (cell, TRUE);
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;
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++;
455 ri->needs_respan = FALSE;