3 * cellspan.c: Keep track of the columns on which a cell
4 * displays information.
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>
24 #include <sheet-merge.h>
25 #include <sheet-style.h>
29 #include <rendered-value.h>
32 col_hash (gconstpointer key
)
34 return GPOINTER_TO_INT(key
);
38 col_compare (gconstpointer a
, gconstpointer b
)
40 if (GPOINTER_TO_INT(a
) == GPOINTER_TO_INT(b
))
46 free_hash_value (G_GNUC_UNUSED gpointer key
, gpointer value
,
47 G_GNUC_UNUSED gpointer user_data
)
53 row_destroy_span (ColRowInfo
*ri
)
55 if (ri
== NULL
|| ri
->spans
== NULL
)
58 g_hash_table_foreach (ri
->spans
, free_hash_value
, NULL
);
59 g_hash_table_destroy (ri
->spans
);
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
72 cell_register_span (GnmCell
const *cell
, int left
, int right
)
77 g_return_if_fail (cell
!= NULL
);
78 g_return_if_fail (left
<= right
);
81 ri
= sheet_row_get (cell
->base
.sheet
, row
);
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
);
102 span_remove (G_GNUC_UNUSED gpointer key
, gpointer value
,
105 CellSpanInfo
*span
= (CellSpanInfo
*)value
;
106 GnmCell
*cell
= user_data
;
108 if (cell
== span
->cell
) {
109 g_free (span
); /* free the span descriptor */
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
122 cell_unregister_span (GnmCell
const * const cell
)
126 g_return_if_fail (cell
!= NULL
);
128 ri
= sheet_row_get (cell
->base
.sheet
, cell
->pos
.row
);
130 if (ri
->spans
== NULL
)
133 g_hash_table_foreach_remove (ri
->spans
,
134 &span_remove
, (gpointer
)cell
);
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
144 * - the cell whose contents span.
145 * - The first and last col in the span.
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
)
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
)
166 cell_span_info_get_type (void)
171 t
= g_boxed_type_register_static ("CellSpanInfo",
172 (GBoxedCopyFunc
)cell_span_info_copy
,
173 (GBoxedFreeFunc
)cell_span_info_copy
);
181 * Utility to ensure that a cell is completely empty.
183 * - no merged regions
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
);
202 if (span
!= NULL
&& span
->cell
!= ok_span_cell
)
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
)));
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.
225 cell_calc_span (GnmCell
const *cell
, int *col1
, int *col2
)
228 int h_align
, v_align
, left
, max_col
, min_col
;
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
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"
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
;
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
) ||
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
;
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
;
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
);
300 if (!cellspan_is_empty (pos
, cell
))
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;
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
);
321 if (!cellspan_is_empty (pos
, cell
))
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;
334 case GNM_HALIGN_CENTER
: {
335 int remain_left
, remain_right
;
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
);
349 if (cellspan_is_empty (pos_l
, cell
)) {
350 remain_left
-= ci
->size_pixels
- 1;
358 for (; remain_right
> 0;)
359 if (++pos_r
< max_col
){
360 ColRowInfo
const *ci
= sheet_col_get_info (sheet
, pos_r
);
363 if (cellspan_is_empty (pos_r
, cell
)) {
364 remain_right
-= ci
->size_pixels
- 1;
367 max_col
= remain_right
= 0;
372 } /* case GNM_HALIGN_CENTER */
374 case GNM_HALIGN_CENTER_ACROSS_SELECTION
: {
375 int const row
= cell
->pos
.row
;
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
);
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
)
393 while (++pos_r
< max_col
) {
394 ColRowInfo
const *ci
= sheet_col_get_info (sheet
, pos_r
);
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
)
411 g_warning ("Unknown horizontal alignment type %x.", h_align
);
416 row_calc_spans (ColRowInfo
*ri
, int row
, Sheet
const *sheet
)
418 int left
, right
, col
;
419 GnmRange
const *merged
;
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
);
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;
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;
447 cell_calc_span (cell
, &left
, &right
);
449 cell_register_span (cell
, left
, right
);
455 ri
->needs_respan
= FALSE
;