1 /*
2 * ranges.c: various functions for common operations on cell ranges.
3 *
4 * Author:
5 * Jody Goldberg (jody@gnome.org)
6 * Miguel de Icaza (miguel@gnu.org).
7 * Michael Meeks (mmeeks@gnu.org).
8 * Copyright (C) 2007-2009 Morten Welinder (terra@gnome.org)
9 */
10 #include <gnumeric-config.h>
11 #include <gnumeric.h>
12 #include <ranges.h>
13
14 #include <commands.h>
15 #include <numbers.h>
16 #include <expr.h>
17 #include <expr-impl.h>
18 #include <expr-name.h>
19 #include <sheet.h>
20 #include <sheet-style.h>
21 #include <parse-util.h>
22 #include <value.h>
23 #include <cell.h>
24 #include <style.h>
25 #include <workbook.h>
26 #include <gnumeric-conf.h>
27
28 #include <stdlib.h>
29 #include <glib.h>
30 #include <glib/gi18n-lib.h>
31 #include <string.h>
32
33 #undef RANGE_DEBUG
34 #define UNICODE_ELLIPSIS "\xe2\x80\xa6"
35
36 /**
37 * range_init_full_sheet:
38 * @r: #GnmRange
39 * @sheet: #Sheet
40 *
41 * Updates @r to fill @sheet in its entirety.
42 *
43 * Returns: (type void): @r
44 */
45 GnmRange *
range_init_full_sheet(GnmRange * r,Sheet const * sheet)46 range_init_full_sheet (GnmRange *r, Sheet const *sheet)
47 {
48 r->start.col = 0;
49 r->start.row = 0;
50 r->end.col = gnm_sheet_get_last_col (sheet);
51 r->end.row = gnm_sheet_get_last_row (sheet);
52 return r;
53 }
54
55 /**
56 * range_init_cols:
57 * @r: #GnmRange
58 * @sheet: #Sheet
59 * @start_col: Starting column
60 * @end_col: Ending column
61 *
62 * Updates @r to span full columns @start_col through @end_col completely.
63 *
64 * Returns: (type void): @r
65 */
66 GnmRange *
range_init_cols(GnmRange * r,Sheet const * sheet,int start_col,int end_col)67 range_init_cols (GnmRange *r, Sheet const *sheet, int start_col, int end_col)
68 {
69 r->start.col = start_col;
70 r->start.row = 0;
71 r->end.col = end_col;
72 r->end.row = gnm_sheet_get_last_row (sheet);
73 return r;
74 }
75
76 /**
77 * range_init_rows:
78 * @r: #GnmRange
79 * @sheet: #Sheet
80 * @start_row: Starting row
81 * @end_row: Ending row
82 *
83 * Updates @r to span full rows @start_row through @end_row completely.
84 *
85 * Returns: (type void): @r
86 */
87 GnmRange *
range_init_rows(GnmRange * r,Sheet const * sheet,int start_row,int end_row)88 range_init_rows (GnmRange *r, Sheet const *sheet, int start_row, int end_row)
89 {
90 r->start.col = 0;
91 r->start.row = start_row;
92 r->end.col = gnm_sheet_get_last_col (sheet);
93 r->end.row = end_row;
94 return r;
95 }
96
97 /**
98 * range_init_invalid: (skip)
99 * @r: #GnmRange
100 *
101 * Updates @r to a meaningless range
102 *
103 * Returns: (type void): @r
104 */
105 GnmRange *
range_init_invalid(GnmRange * r)106 range_init_invalid (GnmRange *r)
107 {
108 r->start.col = -1;
109 r->start.row = -1;
110 r->end.col = -2;
111 r->end.row = -2;
112 return r;
113 }
114
115 /**
116 * range_init_rangeref:
117 * @r: #GnmRange
118 * @rr: #GnmRangeRef
119 *
120 * Updates @r to be the same as the range part of @rr.
121 *
122 * Returns: (type void): @r
123 */
124 GnmRange *
range_init_rangeref(GnmRange * range,GnmRangeRef const * rr)125 range_init_rangeref (GnmRange *range, GnmRangeRef const *rr)
126 {
127 g_return_val_if_fail (range != NULL && rr != NULL, NULL);
128
129 range->start.col = rr->a.col;
130 range->start.row = rr->a.row;
131 range->end.col = rr->b.col;
132 range->end.row = rr->b.row;
133 return range;
134 }
135
136
137 /**
138 * range_init_value:
139 * @r: A #GnmRange to change
140 * @v: A #GnmValue containing a cell range
141 *
142 * Updates @r to be the same as the cell range of @v.
143 *
144 * Returns: (type void): @r
145 */
146 GnmRange *
range_init_value(GnmRange * range,GnmValue const * v)147 range_init_value (GnmRange *range, GnmValue const *v)
148 {
149 g_return_val_if_fail (range != NULL, NULL);
150 g_return_val_if_fail (v != NULL && VALUE_IS_CELLRANGE (v), NULL);
151
152 return range_init_rangeref (range, &v->v_range.cell);
153 }
154
155 /**
156 * range_init_cellpos:
157 * @r: A #GnmRange to change
158 * @pos: A #GnmCellPos
159 *
160 * Updates @r to be the singleton range of @pos
161 *
162 * Returns: (type void): @r
163 */
164 GnmRange *
range_init_cellpos(GnmRange * r,GnmCellPos const * pos)165 range_init_cellpos (GnmRange *r, GnmCellPos const *pos)
166 {
167 r->start = *pos;
168 r->end = *pos;
169
170 return r;
171 }
172
173 /**
174 * range_init_cellpos_size:
175 * @r: A #GnmRange to change
176 * @start: A #GnmCellPos for the upper left corner of the desired range
177 * @cols: number of columns
178 * @rows: number of rows
179 *
180 * Updates @r to start at @start and spanning @cols columns and @rows rows.
181 *
182 * Returns: (type void): @r
183 */
184 GnmRange *
range_init_cellpos_size(GnmRange * r,GnmCellPos const * start,int cols,int rows)185 range_init_cellpos_size (GnmRange *r,
186 GnmCellPos const *start, int cols, int rows)
187 {
188 r->start = *start;
189 r->end.col = start->col + cols - 1;
190 r->end.row = start->row + rows - 1;
191
192 return r;
193 }
194
195 /**
196 * range_init:
197 * @r: A #GnmRange to change
198 * @start_col: Column
199 * @start_row: Row
200 * @end_col: Column
201 * @end_row: Row
202 *
203 * Updates @r to start at (@start_col,@start_row) and end
204 * at (@end_col,@end_row).
205 *
206 * Returns: (type void): @r
207 */
208 GnmRange *
range_init(GnmRange * r,int start_col,int start_row,int end_col,int end_row)209 range_init (GnmRange *r, int start_col, int start_row,
210 int end_col, int end_row)
211 {
212 g_return_val_if_fail (r != NULL, r);
213
214 r->start.col = start_col;
215 r->start.row = start_row;
216 r->end.col = end_col;
217 r->end.row = end_row;
218
219 return r;
220 }
221
222 /**
223 * range_parse:
224 * @r: #GnmRange
225 * @text: text to parse
226 * @ss: #GnmSheetSize describing the size of the sheet in which @r lives.
227 *
228 * Parse a simple range (no abs/rel refs, no sheet refs)
229 * Store the result in @r.
230 *
231 * Returns: %TRUE on success.
232 **/
233 gboolean
range_parse(GnmRange * r,char const * text,GnmSheetSize const * ss)234 range_parse (GnmRange *r, char const *text, GnmSheetSize const *ss)
235 {
236 text = cellpos_parse (text, ss, &r->start, FALSE);
237 if (!text)
238 return FALSE;
239
240 if (*text == '\0') {
241 r->end = r->start;
242 return TRUE;
243 }
244
245 if (*text != ':')
246 return FALSE;
247
248 text = cellpos_parse (text + 1, ss, &r->end, TRUE);
249 return text != NULL;
250 }
251
252 /**
253 * range_list_destroy:
254 * @ranges: (element-type GnmValue) (transfer full): a list of value ranges
255 * to destroy.
256 *
257 * Destroys a list of ranges returned from parse_cell_range_list. Note:
258 * the element type here is GnmValue, not GnmRange.
259 **/
260 void
range_list_destroy(GSList * ranges)261 range_list_destroy (GSList *ranges)
262 {
263 g_slist_free_full (ranges, (GDestroyNotify)value_release);
264 }
265
266
267 /**
268 * range_as_string:
269 * @r: A #GnmRange
270 *
271 * Returns: (transfer none): a string representation of @src
272 **/
273 char const *
range_as_string(GnmRange const * r)274 range_as_string (GnmRange const *r)
275 {
276 static char buffer[(6 + 4 * sizeof (long)) * 2 + 1];
277
278 g_return_val_if_fail (r != NULL, "");
279
280 sprintf (buffer, "%s%s",
281 col_name (r->start.col), row_name (r->start.row));
282
283 if (r->start.col != r->end.col || r->start.row != r->end.row)
284 sprintf (buffer + strlen(buffer), ":%s%s",
285 col_name (r->end.col), row_name (r->end.row));
286
287 return buffer;
288 }
289
290 void
range_dump(GnmRange const * src,char const * suffix)291 range_dump (GnmRange const *src, char const *suffix)
292 {
293 /*
294 * keep these as 2 print statements, because
295 * col_name and row_name use a static buffer
296 */
297 g_printerr ("%s%s",
298 col_name (src->start.col),
299 row_name (src->start.row));
300
301 if (src->start.col != src->end.col ||
302 src->start.row != src->end.row)
303 g_printerr (":%s%s",
304 col_name (src->end.col),
305 row_name (src->end.row));
306 g_printerr ("%s", suffix);
307 }
308
309 #ifdef RANGE_DEBUG
310 static void
ranges_dump(GList * l,char const * txt)311 ranges_dump (GList *l, char const *txt)
312 {
313 g_printerr ("%s: ", txt);
314 for (; l; l = l->next) {
315 range_dump (l->data, "");
316 if (l->next)
317 g_printerr (", ");
318 }
319 g_printerr ("\n");
320 }
321 #endif
322
323 /**
324 * range_contained:
325 * @a:
326 * @b:
327 *
328 * Is @a totally contained by @b
329 *
330 * Return value:
331 **/
332 gboolean
range_contained(GnmRange const * a,GnmRange const * b)333 range_contained (GnmRange const *a, GnmRange const *b)
334 {
335 if (a->start.row < b->start.row)
336 return FALSE;
337
338 if (a->end.row > b->end.row)
339 return FALSE;
340
341 if (a->start.col < b->start.col)
342 return FALSE;
343
344 if (a->end.col > b->end.col)
345 return FALSE;
346
347 return TRUE;
348 }
349
350 /**
351 * range_split_ranges:
352 * @hard: The region that is split against
353 * @soft: The region that is split
354 *
355 * Splits soft into several chunks, and returns the still
356 * overlapping remainder of soft as the first list item
357 * (the central region in the pathological case).
358 *
359 * Returns: (element-type GnmRange) (transfer full): A list of fragments.
360 **/
361 GSList *
range_split_ranges(GnmRange const * hard,GnmRange const * soft)362 range_split_ranges (GnmRange const *hard, GnmRange const *soft)
363 {
364 /*
365 * There are lots of cases so think carefully.
366 *
367 * Original Methodology ( approximately )
368 * a) Get a vertex: is it contained ?
369 * b) Yes: split so it isn't
370 * c) Continue for all vertices.
371 *
372 * NB. We prefer to have long columns at the expense
373 * of long rows.
374 */
375 GSList *split = NULL;
376 GnmRange *middle, *sp;
377 gboolean split_left = FALSE;
378 gboolean split_right = FALSE;
379
380 g_return_val_if_fail (range_overlap (hard, soft), NULL);
381
382 middle = g_new (GnmRange, 1);
383 *middle = *soft;
384
385 /* Split off left entirely */
386 if (hard->start.col > soft->start.col) {
387 sp = g_new (GnmRange, 1);
388 sp->start.col = soft->start.col;
389 sp->start.row = soft->start.row;
390 sp->end.col = hard->start.col - 1;
391 sp->end.row = soft->end.row;
392 split = g_slist_prepend (split, sp);
393
394 middle->start.col = hard->start.col;
395 split_left = TRUE;
396 } /* else shared edge */
397
398 /* Split off right entirely */
399 if (hard->end.col < soft->end.col) {
400 sp = g_new (GnmRange, 1);
401 sp->start.col = hard->end.col + 1;
402 sp->start.row = soft->start.row;
403 sp->end.col = soft->end.col;
404 sp->end.row = soft->end.row;
405 split = g_slist_prepend (split, sp);
406
407 middle->end.col = hard->end.col;
408 split_right = TRUE;
409 } /* else shared edge */
410
411 /* Top */
412 if (split_left && split_right) {
413 if (hard->start.row > soft->start.row) {
414 /* The top middle bit */
415 sp = g_new (GnmRange, 1);
416 sp->start.col = hard->start.col;
417 sp->start.row = soft->start.row;
418 sp->end.col = hard->end.col;
419 sp->end.row = hard->start.row - 1;
420 split = g_slist_prepend (split, sp);
421
422 middle->start.row = hard->start.row;
423 } /* else shared edge */
424 } else if (split_left) {
425 if (hard->start.row > soft->start.row) {
426 /* The top middle + right bits */
427 sp = g_new (GnmRange, 1);
428 sp->start.col = hard->start.col;
429 sp->start.row = soft->start.row;
430 sp->end.col = soft->end.col;
431 sp->end.row = hard->start.row - 1;
432 split = g_slist_prepend (split, sp);
433
434 middle->start.row = hard->start.row;
435 } /* else shared edge */
436 } else if (split_right) {
437 if (hard->start.row > soft->start.row) {
438 /* The top middle + left bits */
439 sp = g_new (GnmRange, 1);
440 sp->start.col = soft->start.col;
441 sp->start.row = soft->start.row;
442 sp->end.col = hard->end.col;
443 sp->end.row = hard->start.row - 1;
444 split = g_slist_prepend (split, sp);
445
446 middle->start.row = hard->start.row;
447 } /* else shared edge */
448 } else {
449 if (hard->start.row > soft->start.row) {
450 /* Hack off the top bit */
451 sp = g_new (GnmRange, 1);
452 sp->start.col = soft->start.col;
453 sp->start.row = soft->start.row;
454 sp->end.col = soft->end.col;
455 sp->end.row = hard->start.row - 1;
456 split = g_slist_prepend (split, sp);
457
458 middle->start.row = hard->start.row;
459 } /* else shared edge */
460 }
461
462 /* Bottom */
463 if (split_left && split_right) {
464 if (hard->end.row < soft->end.row) {
465 /* The bottom middle bit */
466 sp = g_new (GnmRange, 1);
467 sp->start.col = hard->start.col;
468 sp->start.row = hard->end.row + 1;
469 sp->end.col = hard->end.col;
470 sp->end.row = soft->end.row;
471 split = g_slist_prepend (split, sp);
472
473 middle->end.row = hard->end.row;
474 } /* else shared edge */
475 } else if (split_left) {
476 if (hard->end.row < soft->end.row) {
477 /* The bottom middle + right bits */
478 sp = g_new (GnmRange, 1);
479 sp->start.col = hard->start.col;
480 sp->start.row = hard->end.row + 1;
481 sp->end.col = soft->end.col;
482 sp->end.row = soft->end.row;
483 split = g_slist_prepend (split, sp);
484
485 middle->end.row = hard->end.row;
486 } /* else shared edge */
487 } else if (split_right) {
488 if (hard->end.row < soft->end.row) {
489 /* The bottom middle + left bits */
490 sp = g_new (GnmRange, 1);
491 sp->start.col = soft->start.col;
492 sp->start.row = hard->end.row + 1;
493 sp->end.col = hard->end.col;
494 sp->end.row = soft->end.row;
495 split = g_slist_prepend (split, sp);
496
497 middle->end.row = hard->end.row;
498 } /* else shared edge */
499 } else {
500 if (hard->end.row < soft->end.row) {
501 /* Hack off the bottom bit */
502 sp = g_new (GnmRange, 1);
503 sp->start.col = soft->start.col;
504 sp->start.row = hard->end.row + 1;
505 sp->end.col = soft->end.col;
506 sp->end.row = soft->end.row;
507 split = g_slist_prepend (split, sp);
508
509 middle->end.row = hard->end.row;
510 } /* else shared edge */
511 }
512
513 return g_slist_prepend (split, middle);
514 }
515
516 /**
517 * gnm_range_dup:
518 * @r: Source range to copy
519 *
520 * Copies the @r range.
521 *
522 * Returns: (transfer full): A copy of the GnmRange.
523 **/
524 GnmRange *
gnm_range_dup(GnmRange const * r)525 gnm_range_dup (GnmRange const *r)
526 {
527 return g_memdup (r, sizeof (*r));
528 }
529
530 /**
531 * range_fragment:
532 * @a: First #GnmRange
533 * @b: Second #GnmRange
534 *
535 * Fragments the ranges into totally non-overlapping regions,
536 *
537 * Returns: (element-type GnmRange) (transfer full): A list of fragmented
538 * ranges or at minimum simply @a and @b.
539 **/
540 GSList *
range_fragment(GnmRange const * a,GnmRange const * b)541 range_fragment (GnmRange const *a, GnmRange const *b)
542 {
543 GSList *split, *ans = NULL;
544
545 split = range_split_ranges (a, b);
546 ans = g_slist_concat (ans, split);
547
548 split = range_split_ranges (b, a);
549 if (split) {
550 g_free (split->data);
551 split = g_slist_remove (split, split->data);
552 }
553 ans = g_slist_concat (ans, split);
554
555 return ans;
556 }
557
558 /**
559 * range_intersection:
560 * @r: intersection range
561 * @a: First #GnmRange
562 * @b: Second #GnmRange
563 *
564 * This computes the intersection of two ranges; on a Venn
565 * diagram this would be A (upside down U) B.
566 * If the ranges do not intersect, false is returned an the
567 * values of @r are unpredictable.
568 *
569 * NB. totally commutative
570 *
571 * Return value: %TRUE if the ranges intersect, %FALSE otherwise
572 **/
573 gboolean
range_intersection(GnmRange * r,GnmRange const * a,GnmRange const * b)574 range_intersection (GnmRange *r, GnmRange const *a, GnmRange const *b)
575 {
576 if (!range_overlap (a, b)) {
577 *r = *a; // Something
578 return FALSE;
579 }
580
581 r->start.col = MAX (a->start.col, b->start.col);
582 r->start.row = MAX (a->start.row, b->start.row);
583
584 r->end.col = MIN (a->end.col, b->end.col);
585 r->end.row = MIN (a->end.row, b->end.row);
586
587 return TRUE;
588 }
589
590 /**
591 * range_normalize:
592 * @src: a range
593 *
594 * Ensures that start <= end for rows and cols.
595 **/
596 void
range_normalize(GnmRange * src)597 range_normalize (GnmRange *src)
598 {
599 int tmp;
600
601 tmp = src->end.col;
602 if (src->start.col > tmp) {
603 src->end.col = src->start.col;
604 src->start.col = tmp;
605 }
606 tmp = src->end.row;
607 if (src->start.row > tmp) {
608 src->end.row = src->start.row;
609 src->start.row = tmp;
610 }
611 }
612
613 /**
614 * range_union:
615 * @a: range a
616 * @b: range b
617 *
618 * This computes the union; on a Venn
619 * diagram this would be A U B
620 * NB. totally commutative. Also, this may
621 * include cells not in either range since
622 * it must return a #GnmRange.
623 *
624 * Return value: the union
625 **/
626 GnmRange
range_union(GnmRange const * a,GnmRange const * b)627 range_union (GnmRange const *a, GnmRange const *b)
628 {
629 GnmRange ans;
630
631 if (a->start.col < b->start.col)
632 ans.start.col = a->start.col;
633 else
634 ans.start.col = b->start.col;
635
636 if (a->end.col > b->end.col)
637 ans.end.col = a->end.col;
638 else
639 ans.end.col = b->end.col;
640
641 if (a->start.row < b->start.row)
642 ans.start.row = a->start.row;
643 else
644 ans.start.row = b->start.row;
645
646 if (a->end.row > b->end.row)
647 ans.end.row = a->end.row;
648 else
649 ans.end.row = b->end.row;
650
651 return ans;
652 }
653
654 /**
655 * range_is_singleton:
656 * @r: the range.
657 *
658 * Returns: %TRUE if @r is a single-cell range.
659 */
660 gboolean
range_is_singleton(GnmRange const * r)661 range_is_singleton (GnmRange const *r)
662 {
663 return r->start.col == r->end.col && r->start.row == r->end.row;
664 }
665
666 /**
667 * range_is_full:
668 * @r: the range.
669 * @sheet: the sheet in which @r lives
670 * @horiz: %TRUE to check for a horizontal full ref (all _cols_); %FALSE
671 * to check for a vertical full ref (all _rows_).
672 *
673 * This determines whether @r completely spans a sheet in the dimension
674 * specified by @horiz.
675 *
676 * Returns: %TRUE if it is infinite, %FALSE otherwise.
677 **/
678 gboolean
range_is_full(GnmRange const * r,Sheet const * sheet,gboolean horiz)679 range_is_full (GnmRange const *r, Sheet const *sheet, gboolean horiz)
680 {
681 if (horiz)
682 return (r->start.col <= 0 &&
683 r->end.col >= gnm_sheet_get_last_col (sheet));
684 else
685 return (r->start.row <= 0 &&
686 r->end.row >= gnm_sheet_get_last_row (sheet));
687 }
688
689 /**
690 * range_clip_to_finite:
691 * @range:
692 * @sheet: the sheet in which @range lives
693 *
694 * Clip the range to the area of the sheet with content.
695 * if the range reaches the edge
696 *
697 * The idea here is that users may select a whole column or row when they
698 * really are only concerned with the extent of the sheet.
699 * On the otehr hand, if users select any smaller region they probably
700 * intend to select just that.
701 *
702 * WARNING THIS IS EXPENSIVE!
703 **/
704 void
range_clip_to_finite(GnmRange * range,Sheet * sheet)705 range_clip_to_finite (GnmRange *range, Sheet *sheet)
706 {
707 GnmRange extent;
708
709 /* FIXME : This seems expensive. We should see if there is a faster
710 * way of doing this. possibly using a flag for content changes, and
711 * using the current values as a cache
712 */
713 extent = sheet_get_extent (sheet, FALSE, TRUE);
714 if (range->end.col >= gnm_sheet_get_max_cols (sheet) - 1)
715 range->end.col = extent.end.col;
716 if (range->end.row >= gnm_sheet_get_max_rows (sheet) - 1)
717 range->end.row = extent.end.row;
718 }
719
720 /**
721 * range_width:
722 * @r: #GnmRange
723 *
724 * Returns: width of @r.
725 */
726 int
range_width(GnmRange const * r)727 range_width (GnmRange const *r)
728 {
729 g_return_val_if_fail (r != NULL, 0);
730 return ABS (r->end.col - r->start.col) + 1;
731 }
732
733 /**
734 * range_height:
735 * @r: #GnmRange
736 *
737 * Returns: height of @r.
738 */
739 int
range_height(GnmRange const * r)740 range_height (GnmRange const *r)
741 {
742 g_return_val_if_fail (r != NULL, 0);
743 return ABS (r->end.row - r->start.row) + 1;
744 }
745
746 /**
747 * range_translate:
748 * @range:
749 * @sheet: the sheet in which @range lives
750 * @col_offset:
751 * @row_offset:
752 *
753 * Translate the range and return %TRUE if it is invalidated.
754 *
755 * Return: %TRUE if the range is no longer valid.
756 **/
757 gboolean
range_translate(GnmRange * range,Sheet const * sheet,int col_offset,int row_offset)758 range_translate (GnmRange *range, Sheet const *sheet, int col_offset, int row_offset)
759 {
760 /*
761 * FIXME: we should probably check for overflow without actually
762 * performing it.
763 */
764 range->start.col += col_offset;
765 range->end.col += col_offset;
766 range->start.row += row_offset;
767 range->end.row += row_offset;
768
769 /* check for completely out of bounds */
770 if (range->start.col >= gnm_sheet_get_max_cols (sheet) || range->start.col < 0 ||
771 range->start.row >= gnm_sheet_get_max_rows (sheet) || range->start.row < 0 ||
772 range->end.col >= gnm_sheet_get_max_cols (sheet) || range->end.col < 0 ||
773 range->end.row >= gnm_sheet_get_max_rows (sheet) || range->end.row < 0)
774 return TRUE;
775
776 return FALSE;
777 }
778
779 /**
780 * range_ensure_sanity:
781 * @range: the range to check
782 * @sheet: the sheet in which @range lives
783 *
784 * Silently clip a range to ensure that it does not contain areas
785 * outside the valid bounds. Does NOT fix inverted ranges.
786 **/
787 void
range_ensure_sanity(GnmRange * range,Sheet const * sheet)788 range_ensure_sanity (GnmRange *range, Sheet const *sheet)
789 {
790 range->start.col = MAX (0, range->start.col);
791 range->end.col = MIN (range->end.col, gnm_sheet_get_last_col (sheet));
792
793 range->start.row = MAX (0, range->start.row);
794 range->end.row = MIN (range->end.row, gnm_sheet_get_last_row (sheet));
795 }
796
797 /**
798 * range_is_sane:
799 * @range: the range to check
800 *
801 * Generate warnings if the range is out of bounds or inverted.
802 **/
803 gboolean
range_is_sane(GnmRange const * range)804 range_is_sane (GnmRange const *range)
805 {
806 g_return_val_if_fail (range != NULL, FALSE);
807 g_return_val_if_fail (range->start.col >= 0, FALSE);
808 g_return_val_if_fail (range->end.col >= range->start.col, FALSE);
809 g_return_val_if_fail (range->end.col <= G_MAXINT / 2, FALSE);
810 g_return_val_if_fail (range->start.row >= 0, FALSE);
811 g_return_val_if_fail (range->end.row >= range->start.row, FALSE);
812 g_return_val_if_fail (range->end.row <= G_MAXINT / 2, FALSE);
813
814 return TRUE;
815 }
816
817 /**
818 * range_transpose:
819 * @range: The range.
820 * @sheet: the sheet in which @range lives
821 * @origin: The box to transpose inside
822 *
823 * Effectively mirrors the ranges in 'boundary' around a
824 * leading diagonal projected from offset.
825 *
826 * Return value: whether we clipped the range.
827 **/
828 gboolean
range_transpose(GnmRange * range,Sheet const * sheet,GnmCellPos const * origin)829 range_transpose (GnmRange *range, Sheet const *sheet, GnmCellPos const *origin)
830 {
831 gboolean clipped = FALSE;
832 GnmRange src;
833 int t;
834 int last_col = gnm_sheet_get_last_col (sheet);
835 int last_row = gnm_sheet_get_last_row (sheet);
836
837 g_return_val_if_fail (range != NULL, TRUE);
838
839 src = *range;
840
841 /* Start col */
842 t = origin->col + (src.start.row - origin->row);
843 if (t > last_col) {
844 clipped = TRUE;
845 range->start.col = last_col;
846 } else if (t < 0) {
847 clipped = TRUE;
848 range->start.col = 0;
849 }
850 range->start.col = t;
851
852 /* Start row */
853 t = origin->row + (src.start.col - origin->col);
854 if (t > last_row) {
855 clipped = TRUE;
856 range->start.row = last_row;
857 } else if (t < 0) {
858 clipped = TRUE;
859 range->start.row = 0;
860 }
861 range->start.row = t;
862
863
864 /* End col */
865 t = origin->col + (src.end.row - origin->row);
866 if (t > last_col) {
867 clipped = TRUE;
868 range->end.col = last_col;
869 } else if (t < 0) {
870 clipped = TRUE;
871 range->end.col = 0;
872 }
873 range->end.col = t;
874
875 /* End row */
876 t = origin->row + (src.end.col - origin->col);
877 if (t > last_row) {
878 clipped = TRUE;
879 range->end.row = last_row;
880 } else if (t < 0) {
881 clipped = TRUE;
882 range->end.row = 0;
883 }
884 range->end.row = t;
885
886 g_assert (range_valid (range));
887
888 return clipped;
889 }
890
891 gboolean
gnm_range_equal(const GnmRange * a,const GnmRange * b)892 gnm_range_equal (const GnmRange *a, const GnmRange *b)
893 {
894 return range_equal (a, b);
895 }
896
897 /**
898 * gnm_range_compare:
899 * @a: first range
900 * @b: second range
901 *
902 * Returns: a value that is negative if range @a comes before range @b;
903 * zero if the two ranges are equal; positive if range @a comes after
904 * range @b. The order imposed is lexicographical by starting row,
905 * then column, then ending row, then column. In other words, the order
906 * is A1, B1, A2, B2.
907 */
908 int
gnm_range_compare(GnmRange const * a,GnmRange const * b)909 gnm_range_compare (GnmRange const *a, GnmRange const *b)
910 {
911 int i = 0;
912 if (!i) i = a->start.row - b->start.row;
913 if (!i) i = a->start.col - b->start.col;
914 if (!i) i = a->end.row - b->end.row;
915 if (!i) i = a->end.col - b->end.col;
916 return i;
917 }
918
919 // Alternative with order A1, A2, B1, B2
920 static int
gnm_range_compare_alt(GnmRange const * a,GnmRange const * b)921 gnm_range_compare_alt (GnmRange const *a, GnmRange const *b)
922 {
923 int i = 0;
924 if (!i) i = a->start.col - b->start.col;
925 if (!i) i = a->start.row - b->start.row;
926 if (!i) i = a->end.col - b->end.col;
927 if (!i) i = a->end.row - b->end.row;
928 return i;
929 }
930
931
932 GnmSheetRange *
gnm_sheet_range_new(Sheet * sheet,GnmRange const * r)933 gnm_sheet_range_new (Sheet *sheet, GnmRange const *r)
934 {
935 GnmSheetRange *gr;
936
937 g_return_val_if_fail (IS_SHEET (sheet), NULL);
938 g_return_val_if_fail (r != NULL, NULL);
939
940 gr = g_new0 (GnmSheetRange, 1);
941 gr->sheet = sheet;
942 gr->range = *r;
943
944 return gr;
945 }
946
947 GnmSheetRange *
gnm_sheet_range_dup(GnmSheetRange const * sr)948 gnm_sheet_range_dup (GnmSheetRange const *sr)
949 {
950 g_return_val_if_fail (sr != NULL, NULL);
951 return gnm_sheet_range_new (sr->sheet, &sr->range);
952 }
953
954 /**
955 * gnm_sheet_range_from_value:
956 * @r: #GnmSheetRange to change
957 * @v: #GnmValue containing a cell range.
958 *
959 * Convert @v into a GnmSheetRange.
960 *
961 * Returns: %TRUE
962 **/
963 gboolean
gnm_sheet_range_from_value(GnmSheetRange * r,GnmValue const * v)964 gnm_sheet_range_from_value (GnmSheetRange *r, GnmValue const *v)
965 {
966 g_return_val_if_fail (VALUE_IS_CELLRANGE (v), FALSE);
967
968 r->sheet = v->v_range.cell.a.sheet;
969 range_init_value (&r->range, v);
970
971 return TRUE;
972 }
973
974 void
gnm_sheet_range_free(GnmSheetRange * gr)975 gnm_sheet_range_free (GnmSheetRange *gr)
976 {
977 g_free (gr);
978 }
979
980 GType
gnm_sheet_range_get_type(void)981 gnm_sheet_range_get_type (void)
982 {
983 static GType t = 0;
984
985 if (t == 0)
986 t = g_boxed_type_register_static ("GnmSheetRange",
987 (GBoxedCopyFunc)gnm_sheet_range_dup,
988 (GBoxedFreeFunc)gnm_sheet_range_free);
989 return t;
990 }
991
992 gboolean
gnm_sheet_range_equal(const GnmSheetRange * a,const GnmSheetRange * b)993 gnm_sheet_range_equal (const GnmSheetRange *a,
994 const GnmSheetRange *b)
995 {
996 return a->sheet == b->sheet && range_equal (&a->range, &b->range);
997 }
998
999 gboolean
gnm_sheet_range_overlap(GnmSheetRange const * a,GnmSheetRange const * b)1000 gnm_sheet_range_overlap (GnmSheetRange const *a, GnmSheetRange const *b)
1001 {
1002 g_return_val_if_fail (a != NULL, FALSE);
1003 g_return_val_if_fail (b != NULL, FALSE);
1004
1005 if (a->sheet == b->sheet && range_overlap (&a->range, &b->range))
1006 return TRUE;
1007
1008 return FALSE;
1009 }
1010
1011 char *
global_range_name(Sheet const * sheet,GnmRange const * r)1012 global_range_name (Sheet const *sheet, GnmRange const *r)
1013 {
1014 char const * the_range_name = range_as_string (r);
1015
1016 if (sheet == NULL)
1017 return g_strdup (the_range_name);
1018
1019 return g_strdup_printf ("%s!%s", sheet->name_quoted, the_range_name);
1020 }
1021
1022 /**
1023 * undo_cell_pos_name:
1024 * @sheet:
1025 * @pos:
1026 *
1027 * Returns the range name depending on the preference setting.
1028 **/
1029 char *
undo_cell_pos_name(Sheet const * sheet,GnmCellPos const * pos)1030 undo_cell_pos_name (Sheet const *sheet, GnmCellPos const *pos)
1031 {
1032 GnmRange r;
1033 r.end = r.start = *pos;
1034 return undo_range_name (sheet, &r);
1035 }
1036
1037 /**
1038 * undo_range_name:
1039 * @sheet:
1040 * @r:
1041 *
1042 * Returns the range name depending on the preference setting.
1043 **/
1044 char *
undo_range_name(Sheet const * sheet,GnmRange const * r)1045 undo_range_name (Sheet const *sheet, GnmRange const *r)
1046 {
1047 char const *the_range_name = range_as_string (r);
1048
1049 if (sheet != NULL && gnm_conf_get_undo_show_sheet_name ()) {
1050 GString *str = g_string_new (NULL);
1051 gboolean truncated = FALSE;
1052
1053 g_string_printf (str, "%s!%s", sheet->name_quoted, the_range_name);
1054 gnm_cmd_trunc_descriptor (str, &truncated);
1055
1056 if (!truncated)
1057 return g_string_free (str, FALSE);
1058
1059 g_string_printf (str, UNICODE_ELLIPSIS "!%s", the_range_name);
1060 gnm_cmd_trunc_descriptor (str, &truncated);
1061
1062 if (!truncated)
1063 return g_string_free (str, FALSE);
1064 g_string_free (str, TRUE);
1065 }
1066
1067 return g_string_free
1068 (gnm_cmd_trunc_descriptor (g_string_new (the_range_name), NULL), FALSE);
1069 }
1070
1071
1072 /*
1073 * Create range list name, but don't exceed max_width.
1074 * Returns: %TRUE iff the name is complete.
1075 */
1076 static gboolean
range_list_name_try(GString * names,char const * sheet,GSList const * ranges)1077 range_list_name_try (GString *names, char const *sheet, GSList const *ranges)
1078 {
1079 GSList const *l;
1080 char const *n = range_as_string (ranges->data);
1081 gboolean truncated;
1082
1083 if (sheet == NULL)
1084 g_string_assign (names, n);
1085 else
1086 g_string_printf (names, "%s!%s", sheet, n);
1087
1088 gnm_cmd_trunc_descriptor (names, &truncated);
1089
1090 if (truncated)
1091 return FALSE;
1092
1093 for (l = ranges->next; l != NULL; l = l->next) {
1094 n = range_as_string (l->data);
1095
1096 if (sheet == NULL)
1097 g_string_append_printf (names, ", %s", n);
1098 else
1099 g_string_append_printf (names, ", %s!%s",
1100 sheet, n);
1101
1102 gnm_cmd_trunc_descriptor (names, &truncated);
1103
1104 if (truncated)
1105 return FALSE;
1106 }
1107
1108 /* Have we reached the end? */
1109 return TRUE;
1110 }
1111
1112
1113 /**
1114 * undo_range_list_name:
1115 * @sheet:
1116 * @ranges: (element-type GnmRange): list of ranges
1117 *
1118 * Returns: the range list name depending on the preference setting.
1119 * (The result will be something like: "A1:C3, D4:E5"). The string will be
1120 * truncated to max_descriptor_width.
1121 **/
1122 char *
undo_range_list_name(Sheet const * sheet,GSList const * ranges)1123 undo_range_list_name (Sheet const *sheet, GSList const *ranges)
1124 {
1125 GString *names_with_sheet = NULL, *names_with_ellipsis, *names;
1126
1127 g_return_val_if_fail (ranges != NULL, NULL);
1128
1129 /* With the sheet name. */
1130 if (sheet != NULL && gnm_conf_get_undo_show_sheet_name ()) {
1131 names_with_sheet = g_string_new (NULL);
1132 if (range_list_name_try (names_with_sheet, sheet->name_quoted, ranges)) {
1133 /* We have reached the end, return the data from names. */
1134 return g_string_free (names_with_sheet, FALSE);
1135 }
1136 names_with_ellipsis = g_string_new (NULL);
1137 if (range_list_name_try (names_with_ellipsis, UNICODE_ELLIPSIS, ranges)) {
1138 /* We have reached the end, return the data from names. */
1139 g_string_free (names_with_sheet, TRUE);
1140 return g_string_free (names_with_ellipsis, FALSE);
1141 }
1142 g_string_free (names_with_ellipsis, TRUE);
1143 }
1144
1145 /* Without the sheet name. */
1146 names = g_string_new (NULL);
1147 if (range_list_name_try (names, NULL, ranges)) {
1148 /* We have reached the end, return the data from names. */
1149 if (names_with_sheet != NULL)
1150 g_string_free (names_with_sheet, TRUE);
1151 return g_string_free (names, FALSE);
1152 }
1153
1154 /* We have to use a truncated version. */
1155 if (names_with_sheet != NULL) {
1156 g_string_free (names, TRUE);
1157 return g_string_free (names_with_sheet, FALSE);
1158 }
1159 return g_string_free (names, FALSE);
1160 }
1161
1162 /**
1163 * global_range_list_parse:
1164 * @sheet: Sheet where the range specification is relatively parsed to
1165 * @str: a range or list of ranges to parse (ex: "A1", "A1:B1,C2,Sheet2!D2:D4")
1166 *
1167 * Parses a list of ranges, relative to the @sheet and returns a list with the
1168 * results.
1169 *
1170 * Returns: (element-type GnmValue) (transfer full): a #GSList of #GnmValue.
1171 **/
1172 GSList *
global_range_list_parse(Sheet * sheet,char const * str)1173 global_range_list_parse (Sheet *sheet, char const *str)
1174 {
1175 GnmParsePos pp;
1176 GnmExprTop const *texpr;
1177 GSList *ranges = NULL;
1178 GnmValue *v;
1179
1180 g_return_val_if_fail (IS_SHEET (sheet), NULL);
1181 g_return_val_if_fail (str != NULL, NULL);
1182
1183 texpr = gnm_expr_parse_str (str,
1184 parse_pos_init_sheet (&pp, sheet),
1185 GNM_EXPR_PARSE_FORCE_EXPLICIT_SHEET_REFERENCES |
1186 GNM_EXPR_PARSE_PERMIT_MULTIPLE_EXPRESSIONS |
1187 GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS,
1188 NULL, NULL);
1189
1190 if (texpr != NULL) {
1191 if (GNM_EXPR_GET_OPER (texpr->expr) == GNM_EXPR_OP_SET) {
1192 GnmExpr const *expr = texpr->expr;
1193 int i;
1194 for (i = 0; i < expr->set.argc; i++) {
1195 v = gnm_expr_get_range (expr->set.argv[i]);
1196 if (v == NULL) {
1197 range_list_destroy (ranges);
1198 ranges = NULL;
1199 break;
1200 } else
1201 ranges = g_slist_prepend (ranges, v);
1202 }
1203 } else {
1204 v = gnm_expr_top_get_range (texpr);
1205 if (v != NULL)
1206 ranges = g_slist_prepend (ranges, v);
1207 }
1208 gnm_expr_top_unref (texpr);
1209 }
1210
1211 return g_slist_reverse (ranges);
1212 }
1213
1214 /**
1215 * global_range_list_foreach:
1216 * @gr_list: (element-type GnmRange): list of ranges.
1217 * @ep:
1218 * @flags:
1219 * @handler: (scope call):
1220 * @closure: user data.
1221 *
1222 * Returns: (transfer none):
1223 **/
1224 GnmValue *
global_range_list_foreach(GSList * gr_list,GnmEvalPos const * ep,CellIterFlags flags,CellIterFunc handler,gpointer closure)1225 global_range_list_foreach (GSList *gr_list, GnmEvalPos const *ep,
1226 CellIterFlags flags,
1227 CellIterFunc handler,
1228 gpointer closure)
1229 {
1230 GnmValue *v;
1231 for (; gr_list != NULL; gr_list = gr_list->next) {
1232 v = workbook_foreach_cell_in_range (ep, gr_list->data,
1233 flags, handler, closure);
1234 if (v != NULL)
1235 return v;
1236 }
1237
1238 return NULL;
1239 }
1240
1241
1242 /**
1243 * global_range_contained:
1244 * @sheet: The calling context #Sheet for references without sheet.
1245 * @a: A #GnmValue representing a range
1246 * @b: A #GnmValue representing a range
1247 *
1248 * Returns: %TRUE if @a is contained in @b. We do not handle 3d ranges
1249 **/
1250 gboolean
global_range_contained(Sheet const * sheet,GnmValue const * a,GnmValue const * b)1251 global_range_contained (Sheet const *sheet, GnmValue const *a, GnmValue const *b)
1252 {
1253 Sheet const *target;
1254
1255 g_return_val_if_fail (a != NULL, FALSE);
1256 g_return_val_if_fail (b != NULL, FALSE);
1257
1258 if (!VALUE_IS_CELLRANGE (a) || !VALUE_IS_CELLRANGE (b))
1259 return FALSE;
1260
1261 target = eval_sheet (a->v_range.cell.a.sheet, sheet);
1262 if (target != eval_sheet (a->v_range.cell.b.sheet, sheet))
1263 return FALSE;
1264
1265 if (target != eval_sheet (b->v_range.cell.a.sheet, sheet) ||
1266 target != eval_sheet (b->v_range.cell.b.sheet, sheet))
1267 return FALSE;
1268
1269 if (a->v_range.cell.a.row < b->v_range.cell.a.row)
1270 return FALSE;
1271
1272 if (a->v_range.cell.b.row > b->v_range.cell.b.row)
1273 return FALSE;
1274
1275 if (a->v_range.cell.a.col < b->v_range.cell.a.col)
1276 return FALSE;
1277
1278 if (a->v_range.cell.b.col > b->v_range.cell.b.col)
1279 return FALSE;
1280
1281 return TRUE;
1282 }
1283
1284 GType
gnm_range_get_type(void)1285 gnm_range_get_type (void)
1286 {
1287 static GType t = 0;
1288
1289 if (t == 0)
1290 t = g_boxed_type_register_static ("GnmRange",
1291 (GBoxedCopyFunc)gnm_range_dup,
1292 (GBoxedFreeFunc)g_free);
1293 return t;
1294 }
1295
1296 static gboolean
merge_ranges(GnmRange * a,GnmRange const * b)1297 merge_ranges (GnmRange *a, GnmRange const *b)
1298 {
1299 if (a->start.row == b->start.row &&
1300 a->end.row == b->end.row &&
1301 a->end.col + 1 >= b->start.col) {
1302 // "a" is just left of "b", possibly with overlap
1303 a->end.col = MAX (a->end.col, b->end.col);
1304 return TRUE;
1305 }
1306
1307 if (a->start.col == b->start.col &&
1308 a->end.col == b->end.col &&
1309 a->end.row + 1 >= b->start.row) {
1310 // "a" is just on top of "b", possibly with overlap
1311 a->end.row = MAX (a->end.row, b->end.row);
1312 return TRUE;
1313 }
1314
1315 if (range_contained (b, a)) {
1316 // "b" is inside "a"
1317 return TRUE;
1318 }
1319
1320 // Punt.
1321 return FALSE;
1322 }
1323
1324 static gboolean
try_merge_pair(GArray * arr,unsigned ui1,unsigned ui2)1325 try_merge_pair (GArray *arr, unsigned ui1, unsigned ui2)
1326 {
1327 GnmRange *ra = &g_array_index (arr, GnmRange, ui1);
1328 GnmRange *rb = &g_array_index (arr, GnmRange, ui2);
1329
1330 if (merge_ranges (ra, rb)) {
1331 g_array_remove_index (arr, ui2);
1332 return TRUE;
1333 } else
1334 return FALSE;
1335 }
1336
1337 /**
1338 * gnm_range_simplify:
1339 * @arr: (element-type GnmRange) (inout): array of ranges to simplify
1340 *
1341 * Simplifies the array of ranges by merging small ranges into larger ones.
1342 */
1343 void
gnm_range_simplify(GArray * arr)1344 gnm_range_simplify (GArray *arr)
1345 {
1346 unsigned ui;
1347
1348 if (arr->len < 2)
1349 return;
1350
1351 g_array_sort (arr, (GCompareFunc) gnm_range_compare);
1352 // Two cheap passes through the ranges.
1353 for (ui = arr->len - 1; ui > 0; ui--)
1354 try_merge_pair (arr, ui - 1, ui);
1355 for (ui = arr->len - 1; ui > 0; ui--)
1356 try_merge_pair (arr, ui - 1, ui);
1357
1358 g_array_sort (arr, (GCompareFunc) gnm_range_compare_alt);
1359 for (ui = arr->len - 1; ui > 0; ui--)
1360 try_merge_pair (arr, ui - 1, ui);
1361 }
1362