Update Spanish translation
[gnumeric.git] / src / sheet-merge.c
blob9e8621f933a7ad1a2f058bb60789acee555506ca
2 /*
3 * sheet-merge.c: merged cell support
5 * Copyright (C) 2000-2002 Jody Goldberg (jody@gnome.org)
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
24 #include <gnumeric.h>
25 #include <sheet-merge.h>
27 #include <sheet-object.h>
28 #include <sheet.h>
29 #include <sheet-view.h>
30 #include <sheet-private.h>
31 #include <ranges.h>
32 #include <cell.h>
33 #include <cellspan.h>
34 #include <sheet-style.h>
35 #include <mstyle.h>
36 #include <expr.h>
37 #include <command-context.h>
39 static gint
40 range_row_cmp (GnmRange const *a, GnmRange const *b)
42 int tmp = b->start.row - a->start.row;
43 if (tmp == 0)
44 tmp = a->start.col - b->start.col; /* YES I DO MEAN a - b */
45 return tmp;
48 /**
49 * gnm_sheet_merge_add:
50 * @sheet: the sheet which will contain the region
51 * @r: The region to merge
52 * @clear: should the non-corner content of the region be cleared and the
53 * style from the corner applied.
54 * @cc: (nullable): the calling context
56 * Add a range to the list of merge targets. Checks for array spliting returns
57 * %TRUE if there was an error. Queues a respan. Only queus a redraw if @clear
58 * is %TRUE.
60 gboolean
61 gnm_sheet_merge_add (Sheet *sheet, GnmRange const *r, gboolean clear,
62 GOCmdContext *cc)
64 GSList *test;
65 GnmRange *r_copy;
66 GnmCell *cell;
67 GnmComment *comment;
68 GnmRange r2;
70 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
71 g_return_val_if_fail (range_is_sane (r), TRUE);
72 g_return_val_if_fail (r->end.col < gnm_sheet_get_max_cols (sheet), TRUE);
73 g_return_val_if_fail (r->end.row < gnm_sheet_get_max_rows (sheet), TRUE);
75 r2 = *r;
76 range_ensure_sanity (&r2, sheet);
78 if (sheet_range_splits_array (sheet, &r2, NULL, cc, _("Merge")))
79 return TRUE;
81 test = gnm_sheet_merge_get_overlap (sheet, &r2);
82 if (test != NULL) {
83 if (cc != NULL)
84 go_cmd_context_error (cc, g_error_new (go_error_invalid(), 0,
85 _("There is already a merged region that intersects\n%s!%s"),
86 sheet->name_unquoted, range_as_string (&r2)));
87 g_slist_free (test);
88 return TRUE;
91 if (clear) {
92 int i;
93 GnmStyle *style;
95 sheet_redraw_range (sheet, &r2);
97 /* Clear the non-corner content */
98 if (r2.start.col != r2.end.col)
99 sheet_clear_region (sheet,
100 r2.start.col+1, r2.start.row,
101 r2.end.col, r2.end.row,
102 CLEAR_VALUES | CLEAR_COMMENTS | CLEAR_NOCHECKARRAY | CLEAR_NORESPAN,
103 cc);
104 if (r2.start.row != r2.end.row)
105 sheet_clear_region (sheet,
106 r2.start.col, r2.start.row+1,
107 /* yes I mean start.col */ r2.start.col, r2.end.row,
108 CLEAR_VALUES | CLEAR_COMMENTS | CLEAR_NOCHECKARRAY | CLEAR_NORESPAN,
109 cc);
111 /* Apply the corner style to the entire region */
112 style = gnm_style_dup (sheet_style_get (sheet, r2.start.col,
113 r2.start.row));
114 for (i = MSTYLE_BORDER_TOP; i <= MSTYLE_BORDER_DIAGONAL; i++)
115 gnm_style_unset_element (style, i);
116 sheet_style_apply_range (sheet, &r2, style);
117 sheet_region_queue_recalc (sheet, &r2);
120 r_copy = gnm_range_dup (&r2);
121 g_hash_table_insert (sheet->hash_merged, &r_copy->start, r_copy);
123 /* Store in order from bottom to top then LEFT TO RIGHT (by start coord) */
124 sheet->list_merged = g_slist_insert_sorted (sheet->list_merged, r_copy,
125 (GCompareFunc)range_row_cmp);
127 cell = sheet_cell_get (sheet, r2.start.col, r2.start.row);
128 if (cell != NULL) {
129 cell->base.flags |= GNM_CELL_IS_MERGED;
130 cell_unregister_span (cell);
132 sheet_queue_respan (sheet, r2.start.row, r2.end.row);
134 /* Ensure that edit pos is not in the center of a region. */
135 SHEET_FOREACH_VIEW (sheet, sv, {
136 sv->reposition_selection = TRUE;
137 if (range_contains (&r2, sv->edit_pos.col, sv->edit_pos.row))
138 gnm_sheet_view_set_edit_pos (sv, &r2.start);
141 comment = sheet_get_comment (sheet, &r2.start);
142 if (comment != NULL)
143 sheet_object_update_bounds (GNM_SO (comment), NULL);
145 sheet_flag_status_update_range (sheet, &r2);
146 if (sheet->cols.max_used < r2.end.col) {
147 sheet->cols.max_used = r2.end.col;
148 sheet->priv->resize_scrollbar = TRUE;
150 if (sheet->rows.max_used < r2.end.row) {
151 sheet->rows.max_used = r2.end.row;
152 sheet->priv->resize_scrollbar = TRUE;
154 return FALSE;
158 * gnm_sheet_merge_remove:
159 * @sheet: the sheet which will contain the region
160 * @r: The region
162 * Remove a merged range.
163 * Queues a redraw.
165 * Returns: %TRUE if there was an error.
167 gboolean
168 gnm_sheet_merge_remove (Sheet *sheet, GnmRange const *r)
170 GnmRange *r_copy;
171 GnmCell *cell;
172 GnmComment *comment;
174 g_return_val_if_fail (IS_SHEET (sheet), TRUE);
175 g_return_val_if_fail (r != NULL, TRUE);
177 r_copy = g_hash_table_lookup (sheet->hash_merged, &r->start);
179 g_return_val_if_fail (r_copy != NULL, TRUE);
180 g_return_val_if_fail (range_equal (r, r_copy), TRUE);
182 g_hash_table_remove (sheet->hash_merged, &r_copy->start);
183 sheet->list_merged = g_slist_remove (sheet->list_merged, r_copy);
185 cell = sheet_cell_get (sheet, r->start.col, r->start.row);
186 if (cell != NULL)
187 cell->base.flags &= ~GNM_CELL_IS_MERGED;
189 comment = sheet_get_comment (sheet, &r->start);
190 if (comment != NULL)
191 sheet_object_update_bounds (GNM_SO (comment), NULL);
193 sheet_redraw_range (sheet, r);
194 sheet_flag_status_update_range (sheet, r);
195 SHEET_FOREACH_VIEW (sheet, sv, sv->reposition_selection = TRUE;);
196 g_free (r_copy);
197 return FALSE;
201 * gnm_sheet_merge_get_overlap:
203 * Returns: (element-type GnmRange) (transfer container): a list of the merged
204 * regions that overlap the target region.
205 * The list is ordered from top to bottom and RIGHT TO LEFT (by start coord).
207 GSList *
208 gnm_sheet_merge_get_overlap (Sheet const *sheet, GnmRange const *range)
210 GSList *ptr, *res = NULL;
212 g_return_val_if_fail (IS_SHEET (sheet), NULL);
213 g_return_val_if_fail (range != NULL, NULL);
215 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
216 GnmRange * const test = ptr->data;
218 if (range_overlap (range, test))
219 res = g_slist_prepend (res, test);
222 return res;
226 * gnm_sheet_merge_contains_pos:
227 * @sheet: #Sheet to query
228 * @pos: Position to look for a merged range.
230 * Returns: (transfer none) (nullable): the merged range covering @pos, or
231 * %NULL if @pos is not within a merged region.
233 GnmRange const *
234 gnm_sheet_merge_contains_pos (Sheet const *sheet, GnmCellPos const *pos)
236 GSList *ptr;
238 g_return_val_if_fail (IS_SHEET (sheet), NULL);
239 g_return_val_if_fail (pos != NULL, NULL);
241 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
242 GnmRange const * const range = ptr->data;
243 if (range_contains (range, pos->col, pos->row))
244 return range;
246 return NULL;
250 * gnm_sheet_merge_get_adjacent:
251 * @sheet: The sheet to look in.
252 * @pos: the cell to test for adjacent regions.
253 * @left: the return for a region on the left
254 * @right: the return for a region on the right
256 * Returns the nearest regions to either side of @pos.
258 void
259 gnm_sheet_merge_get_adjacent (Sheet const *sheet, GnmCellPos const *pos,
260 GnmRange const **left, GnmRange const **right)
262 GSList *ptr;
264 g_return_if_fail (IS_SHEET (sheet));
265 g_return_if_fail (pos != NULL);
267 *left = *right = NULL;
268 for (ptr = sheet->list_merged ; ptr != NULL ; ptr = ptr->next) {
269 GnmRange const * const test = ptr->data;
270 if (test->start.row <= pos->row && pos->row <= test->end.row) {
271 int const diff = test->end.col - pos->col;
273 g_return_if_fail (diff != 0);
275 if (diff < 0) {
276 if (*left == NULL || (*left)->end.col < test->end.col)
277 *left = test;
278 } else {
279 if (*right == NULL || (*right)->start.col > test->start.col)
280 *right = test;
287 * gnm_sheet_merge_is_corner:
288 * @sheet: #Sheet to query
289 * @pos: cellpos if top left corner
291 * Returns: (transfer none): a merged #GnmRange covering @pos if is the
292 * top-left corner of a merged region.
294 GnmRange const *
295 gnm_sheet_merge_is_corner (Sheet const *sheet, GnmCellPos const *pos)
297 g_return_val_if_fail (IS_SHEET (sheet), NULL);
298 g_return_val_if_fail (pos != NULL, NULL);
300 return g_hash_table_lookup (sheet->hash_merged, pos);
303 static void
304 cb_restore_merge (Sheet *sheet, GSList *restore)
306 GSList *l;
307 for (l = restore; l; l = l->next) {
308 GnmRange const *r = l->data;
309 GnmRange const *r2 = g_hash_table_lookup (sheet->hash_merged,
310 &r->start);
311 if (r2 && range_equal (r, r2))
312 continue;
314 // The only reason for r2 to be different from r is that we
315 // clipped. Moving the clipped region back didn't restore
316 // the old state, so we'll have to remove the merge and
317 // create a new.
318 if (r2)
319 gnm_sheet_merge_remove (sheet, r2);
321 gnm_sheet_merge_add (sheet, r, FALSE, NULL);
325 static void
326 cb_restore_list_free (GSList *restore)
328 g_slist_free_full (restore, g_free);
332 * gnm_sheet_merge_relocate:
333 * @ri: Descriptor of what is moving.
334 * @pundo: (out) (optional) (transfer full): Undo information.
336 * Shifts merged regions that need to move.
338 void
339 gnm_sheet_merge_relocate (GnmExprRelocateInfo const *ri, GOUndo **pundo)
341 GSList *ptr, *copy, *reapply = NULL, *restore = NULL;
342 GnmRange dest;
343 gboolean change_sheets;
345 g_return_if_fail (ri != NULL);
346 g_return_if_fail (IS_SHEET (ri->origin_sheet));
347 g_return_if_fail (IS_SHEET (ri->target_sheet));
349 dest = ri->origin;
350 range_translate (&dest, ri->target_sheet, ri->col_offset, ri->row_offset);
351 change_sheets = (ri->origin_sheet != ri->target_sheet);
353 /* Clear the destination range on the target sheet */
354 if (change_sheets) {
355 copy = g_slist_copy (ri->target_sheet->list_merged);
356 for (ptr = copy; ptr != NULL ; ptr = ptr->next) {
357 GnmRange const *r = ptr->data;
358 if (range_contains (&dest, r->start.col, r->start.row))
359 gnm_sheet_merge_remove (ri->target_sheet, r);
361 g_slist_free (copy);
364 copy = g_slist_copy (ri->origin_sheet->list_merged);
365 for (ptr = copy; ptr != NULL ; ptr = ptr->next ) {
366 GnmRange const *r = ptr->data;
367 GnmRange r0 = *r; // Copy because removal invalidates r
368 GnmRange r2 = *r;
369 gboolean needs_restore = FALSE;
370 gboolean needs_reapply = FALSE;
372 if (range_contains (&ri->origin, r->start.col, r->start.row)) {
373 range_translate (&r2, ri->target_sheet,
374 ri->col_offset, ri->row_offset);
375 range_ensure_sanity (&r2, ri->target_sheet);
377 gnm_sheet_merge_remove (ri->origin_sheet, r);
378 if (range_is_singleton (&r2))
379 needs_restore = TRUE;
380 else if (r2.start.col <= r2.end.col &&
381 r2.start.row <= r2.end.row) {
382 needs_restore = TRUE;
383 needs_reapply = TRUE;
384 } else {
385 // Completely deleted.
387 } else if (range_contains (&ri->origin, r->end.col, r->end.row)) {
388 r2.end.col += ri->col_offset;
389 r2.end.row += ri->row_offset;
390 range_ensure_sanity (&r2, ri->target_sheet);
391 gnm_sheet_merge_remove (ri->origin_sheet, r);
392 needs_restore = TRUE;
393 needs_reapply = !range_is_singleton (&r2);
394 } else if (!change_sheets &&
395 range_contains (&dest, r->start.col, r->start.row))
396 gnm_sheet_merge_remove (ri->origin_sheet, r);
398 if (needs_reapply)
399 reapply = g_slist_prepend (reapply,
400 gnm_range_dup (&r2));
401 if (needs_restore && pundo)
402 restore = g_slist_prepend (restore,
403 gnm_range_dup (&r0));
405 g_slist_free (copy);
407 // Reapply surviving, changed ranges.
408 for (ptr = reapply ; ptr != NULL ; ptr = ptr->next) {
409 GnmRange *dest = ptr->data;
410 gnm_sheet_merge_add (ri->target_sheet, dest, TRUE, NULL);
411 g_free (dest);
413 g_slist_free (reapply);
415 if (restore) {
416 GOUndo *u = go_undo_binary_new
417 (ri->origin_sheet, restore,
418 (GOUndoBinaryFunc)cb_restore_merge,
419 NULL,
420 (GFreeFunc)cb_restore_list_free);
421 *pundo = go_undo_combine (*pundo, u);
426 * gnm_sheet_merge_find_bounding_box:
427 * @sheet: sheet
428 * @r: the range to exten
430 * Extends @r such that no merged range is split by its boundary.
432 void
433 gnm_sheet_merge_find_bounding_box (Sheet const *sheet, GnmRange *target)
435 gboolean changed;
436 GSList *merged, *ptr;
438 /* expand to include any merged regions */
439 do {
440 changed = FALSE;
441 merged = gnm_sheet_merge_get_overlap (sheet, target);
442 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
443 GnmRange const *r = ptr->data;
444 if (target->start.col > r->start.col) {
445 target->start.col = r->start.col;
446 changed = TRUE;
448 if (target->start.row > r->start.row) {
449 target->start.row = r->start.row;
450 changed = TRUE;
452 if (target->end.col < r->end.col) {
453 target->end.col = r->end.col;
454 changed = TRUE;
456 if (target->end.row < r->end.row) {
457 target->end.row = r->end.row;
458 changed = TRUE;
461 g_slist_free (merged);
462 } while (changed);