Introspection fixes.
[gnumeric.git] / src / selection.c
blob7b74710ed0e3453e0872e536d7a12149d687bce2
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * selection.c: Manage selection regions.
5 * Author:
6 * Miguel de Icaza (miguel@gnu.org)
7 * Jody Goldberg (jody@gnome.org)
9 * (C) 1999-2006 Jody Goldberg
11 #include <gnumeric-config.h>
12 #include <glib/gi18n-lib.h>
13 #include "gnumeric.h"
14 #include "selection.h"
16 #include "sheet.h"
17 #include "sheet-view.h"
18 #include "sheet-merge.h"
19 #include "sheet-style.h"
20 #include "sheet-private.h"
21 #include "sheet-control.h"
22 #include "parse-util.h"
23 #include "clipboard.h"
24 #include "ranges.h"
25 #include "application.h"
26 #include "command-context.h"
27 #include "workbook-control.h"
28 #include "workbook-view.h"
29 #include "workbook-priv.h"
30 #include "commands.h"
31 #include "value.h"
32 #include "cell.h"
34 /**
35 * sv_selection_calc_simplification:
36 * @sv:
37 * @mode:
39 * Create the simplified seelction list if necessary
41 * Returns: the simplified version
42 **/
44 static GSList *
45 sv_selection_calc_simplification (SheetView const *sv)
47 GSList *simp = NULL, *ptr;
48 GnmRange *r_rm;
49 SheetView *sv_mod = (SheetView *)sv;
51 if (sv->selection_mode != GNM_SELECTION_MODE_REMOVE)
52 return sv->selections;
53 if (sv->selections_simplified != NULL)
54 return sv->selections_simplified;
56 g_return_val_if_fail (sv->selections != NULL &&
57 sv->selections->data != NULL,
58 sv->selections);
60 r_rm = sv->selections->data;
62 for (ptr = sv->selections->next; ptr != NULL; ptr = ptr->next) {
63 GnmRange *r = ptr->data;
64 if (range_overlap (r_rm, r)) {
65 GSList *pieces;
66 if (range_contained (r, r_rm))
67 continue;
68 pieces = range_split_ranges (r_rm, r);
69 g_free (pieces->data);
70 pieces = g_slist_delete_link (pieces, pieces);
71 simp = g_slist_concat (pieces, simp);
72 } else {
73 GnmRange *r_new = g_new (GnmRange, 1);
74 *r_new = *r;
75 simp = g_slist_prepend (simp, r_new);
79 if (simp == NULL) {
80 GnmRange *r_new = g_new (GnmRange, 1);
81 range_init_cellpos (r_new, &sv->edit_pos);
82 simp = g_slist_prepend (simp, r_new);
85 sv_mod->selections_simplified = g_slist_reverse (simp);
87 return sv->selections_simplified;
90 /**
91 * sv_is_singleton_selected:
92 * @sv:
94 * See if the 1st selected region is a singleton.
96 * Returns A GnmCellPos pointer if the selection is a singleton, and NULL if not.
97 **/
98 GnmCellPos const *
99 sv_is_singleton_selected (SheetView const *sv)
101 #warning FIXME Should we be using the selection rather than the cursor?
102 if (sv->cursor.move_corner.col == sv->cursor.base_corner.col &&
103 sv->cursor.move_corner.row == sv->cursor.base_corner.row)
104 return &sv->cursor.move_corner;
105 return NULL;
109 * sv_is_pos_selected:
110 * @sv:
111 * @col:
112 * @row:
114 * Returns: TRUE if the supplied position is selected in view @sv.
116 gboolean
117 sv_is_pos_selected (SheetView const *sv, int col, int row)
119 GSList *ptr;
120 GnmRange const *sr;
122 for (ptr = sv_selection_calc_simplification (sv);
123 ptr != NULL ; ptr = ptr->next) {
124 sr = ptr->data;
125 if (range_contains (sr, col, row))
126 return TRUE;
128 return FALSE;
132 * sv_is_range_selected:
133 * @sv:
134 * @r:
136 * Returns: TRUE If @r overlaps with any part of the selection in @sv.
138 gboolean
139 sv_is_range_selected (SheetView const *sv, GnmRange const *r)
141 GSList *ptr;
142 GnmRange const *sr;
144 for (ptr = sv_selection_calc_simplification (sv);
145 ptr != NULL ; ptr = ptr->next){
146 sr = ptr->data;
147 if (range_overlap (sr, r))
148 return TRUE;
150 return FALSE;
154 * sv_is_full_range_selected:
155 * @sv:
156 * @r:
158 * Returns TRUE if all of @r is contained by the selection in @sv.
160 gboolean
161 sv_is_full_range_selected (SheetView const *sv, GnmRange const *r)
163 GSList *ptr;
164 GnmRange const *sr;
166 for (ptr = sv_selection_calc_simplification (sv);
167 ptr != NULL ; ptr = ptr->next) {
168 sr = ptr->data;
169 if (range_contained (r, sr))
170 return TRUE;
172 return FALSE;
176 * sv_is_colrow_selected:
177 * @sv: containing the selection
178 * @colrow: The column or row number we are interested in.
179 * @is_col: A flag indicating whether this it is a column or a row.
181 * Searches the selection list to see whether the entire col/row specified is
182 * contained by the section regions. Since the selection is stored as the set
183 * overlapping user specifed regions we can safely search for the range directly.
185 * Eventually to be completely correct and deal with the case of someone manually
186 * selection an entire col/row, in separate chunks, we will need to do something
187 * more advanced.
189 gboolean
190 sv_is_colrow_selected (SheetView const *sv, int colrow, gboolean is_col)
192 GSList *l;
194 g_return_val_if_fail (GNM_IS_SV (sv), FALSE);
196 for (l = sv_selection_calc_simplification (sv);
197 l != NULL; l = l->next) {
198 GnmRange const *ss = l->data;
200 if (is_col) {
201 if (ss->start.row == 0 &&
202 ss->end.row >= gnm_sheet_get_last_row (sv->sheet) &&
203 ss->start.col <= colrow && colrow <= ss->end.col)
204 return TRUE;
205 } else {
206 if (ss->start.col == 0 &&
207 ss->end.col >= gnm_sheet_get_last_col (sv->sheet) &&
208 ss->start.row <= colrow && colrow <= ss->end.row)
209 return TRUE;
212 return FALSE;
216 * sv_is_full_colrow_selected:
217 * @sv:
218 * @is_cols:
219 * @index: index of column or row, -1 for any.
221 * Returns: %TRUE if all of the selected cols/rows in the selection
222 * are fully selected and the selection contains the specified col.
224 gboolean
225 sv_is_full_colrow_selected (SheetView const *sv, gboolean is_cols, int index)
227 GSList *l;
228 gboolean found = FALSE;
230 g_return_val_if_fail (GNM_IS_SV (sv), FALSE);
232 for (l = sv_selection_calc_simplification (sv);
233 l != NULL; l = l->next){
234 GnmRange const *r = l->data;
235 if (is_cols) {
236 if (r->start.row > 0 || r->end.row < gnm_sheet_get_last_row (sv->sheet))
237 return FALSE;
238 if (index == -1 || (r->start.col <= index && index <= r->end.col))
239 found = TRUE;
240 } else {
241 if (r->start.col > 0 || r->end.col < gnm_sheet_get_last_col (sv->sheet))
242 return FALSE;
243 if (index == -1 || (r->start.row <= index && index <= r->end.row))
244 found = TRUE;
248 return found;
252 * sv_selection_col_type:
253 * @sv:
254 * @col:
256 * Returns: How much of column @col is selected in @sv.
258 ColRowSelectionType
259 sv_selection_col_type (SheetView const *sv, int col)
261 GSList *ptr;
262 GnmRange const *sr;
263 int ret = COL_ROW_NO_SELECTION;
265 g_return_val_if_fail (GNM_IS_SV (sv), COL_ROW_NO_SELECTION);
267 if (sv->selections == NULL)
268 return COL_ROW_NO_SELECTION;
270 for (ptr = sv_selection_calc_simplification (sv);
271 ptr != NULL; ptr = ptr->next) {
272 sr = ptr->data;
274 if (sr->start.col > col || sr->end.col < col)
275 continue;
277 if (sr->start.row == 0 &&
278 sr->end.row == gnm_sheet_get_last_row (sv->sheet))
279 return COL_ROW_FULL_SELECTION;
281 ret = COL_ROW_PARTIAL_SELECTION;
284 return ret;
288 * sv_selection_row_type:
289 * @sv:
290 * @row:
292 * Returns: How much of column @col is selected in @sv.
294 ColRowSelectionType
295 sv_selection_row_type (SheetView const *sv, int row)
297 GSList *ptr;
298 GnmRange const *sr;
299 int ret = COL_ROW_NO_SELECTION;
301 g_return_val_if_fail (GNM_IS_SV (sv), COL_ROW_NO_SELECTION);
303 if (sv->selections == NULL)
304 return COL_ROW_NO_SELECTION;
306 for (ptr = sv_selection_calc_simplification (sv);
307 ptr != NULL; ptr = ptr->next) {
308 sr = ptr->data;
310 if (sr->start.row > row || sr->end.row < row)
311 continue;
313 if (sr->start.col == 0 &&
314 sr->end.col == gnm_sheet_get_last_col (sv->sheet))
315 return COL_ROW_FULL_SELECTION;
317 ret = COL_ROW_PARTIAL_SELECTION;
320 return ret;
324 * Quick utility routine to test intersect of line segments.
325 * Returns : 5 sA == sb eA == eb a == b
326 * 4 --sA--sb--eb--eA-- a contains b
327 * 3 --sA--sb--eA--eb-- overlap left
328 * 2 --sb--sA--eA--eb-- b contains a
329 * 1 --sb--sA--eb--eA-- overlap right
330 * 0 if there is no intersection.
332 static int
333 segments_intersect (int const s_a, int const e_a,
334 int const s_b, int const e_b)
336 /* Assume s_a <= e_a and s_b <= e_b */
337 if (e_a < s_b || e_b < s_a)
338 return 0;
340 if (s_a == s_b)
341 return (e_a >= e_b) ? ((e_a == e_b) ? 5 : 4) : 2;
342 if (e_a == e_b)
343 return (s_a <= s_b) ? 4 : 2;
345 if (s_a < s_b)
346 return (e_a >= e_b) ? 4 : 3;
348 /* We already know that s_a <= e_b */
349 return (e_a <= e_b) ? 2 : 1;
353 * sv_menu_enable_insert:
354 * @sv:
355 * @col:
356 * @row:
358 * control whether or not it is ok to insert cols or rows. An internal routine
359 * used by the selection mechanism to avoid erasing the entire sheet when
360 * inserting the wrong dimension.
362 static void
363 sv_menu_enable_insert (SheetView *sv, gboolean col, gboolean row)
365 int flags = 0;
367 g_return_if_fail (GNM_IS_SV (sv));
369 if (sv->enable_insert_cols != col) {
370 flags |= MS_INSERT_COLS;
371 sv->enable_insert_cols = col;
373 if (sv->enable_insert_rows != row) {
374 flags |= MS_INSERT_ROWS;
375 sv->enable_insert_rows = row;
377 if (sv->enable_insert_cells != (col|row)) {
378 flags |= MS_INSERT_CELLS;
379 sv->enable_insert_cells = (col|row);
382 /* during initialization it does not matter */
383 if (!flags || sv->sheet == NULL)
384 return;
386 WORKBOOK_VIEW_FOREACH_CONTROL(sv_wbv (sv), wbc,
387 wb_control_menu_state_update (wbc, flags););
391 * selection_first_range:
392 * @sv: The #SheetView whose selection we are testing.
393 * @cc: The command context to report errors to
394 * @cmd_name: A string naming the operation requiring a single range.
396 * Returns the first range, if a control is supplied it displays an error if
397 * there is more than one range.
399 GnmRange const *
400 selection_first_range (SheetView const *sv,
401 GOCmdContext *cc, char const *cmd_name)
403 GnmRange const *r;
404 GSList *l;
406 g_return_val_if_fail (GNM_IS_SV (sv), NULL);
408 l = sv->selections;
410 g_return_val_if_fail (l != NULL && l->data != NULL, NULL);
412 r = l->data;
413 if (cc != NULL && l->next != NULL) {
414 GError *msg = g_error_new (go_error_invalid(), 0,
415 _("%s does not support multiple ranges"), cmd_name);
416 go_cmd_context_error (cc, msg);
417 g_error_free (msg);
418 return NULL;
421 return r;
425 * sv_selection_extend_to:
426 * @sv: the sheet
427 * @col: column that gets covered (negative indicates all cols)
428 * @row: row that gets covered (negative indicates all rows)
430 * This extends the selection to cover col, row and updates the status areas.
432 void
433 sv_selection_extend_to (SheetView *sv, int col, int row)
435 int base_col, base_row;
437 if (col < 0) {
438 base_col = 0;
439 col = gnm_sheet_get_last_col (sv->sheet);
440 } else
441 base_col = sv->cursor.base_corner.col;
442 if (row < 0) {
443 base_row = 0;
444 row = gnm_sheet_get_last_row (sv->sheet);
445 } else
446 base_row = sv->cursor.base_corner.row;
448 /* If nothing was going to change dont redraw */
449 if (sv->cursor.move_corner.col == col &&
450 sv->cursor.move_corner.row == row &&
451 sv->cursor.base_corner.col == base_col &&
452 sv->cursor.base_corner.row == base_row)
453 return;
455 sv_selection_set (sv, &sv->edit_pos, base_col, base_row, col, row);
458 * FIXME : Does this belong here ?
459 * This is a convenient place to put it so that changes to the
460 * selection also update the status region, but this is somewhat lower
461 * level that I want to do this.
463 sheet_update (sv->sheet);
464 WORKBOOK_FOREACH_VIEW (sv->sheet->workbook, view, {
465 if (wb_view_cur_sheet (view) == sv->sheet)
466 wb_view_selection_desc (view, FALSE, NULL);
470 static void
471 sheet_selection_set_internal (SheetView *sv,
472 GnmCellPos const *edit,
473 int base_col, int base_row,
474 int move_col, int move_row,
475 gboolean just_add_it)
477 GSList *list;
478 GnmRange *ss;
479 GnmRange old_sel, new_sel;
480 gboolean do_cols, do_rows;
482 g_return_if_fail (sv->selections != NULL);
484 new_sel.start.col = MIN(base_col, move_col);
485 new_sel.start.row = MIN(base_row, move_row);
486 new_sel.end.col = MAX(base_col, move_col);
487 new_sel.end.row = MAX(base_row, move_row);
489 g_return_if_fail (range_is_sane (&new_sel));
491 if (sv->sheet != NULL) /* beware initialization */
492 gnm_sheet_merge_find_bounding_box (sv->sheet, &new_sel);
493 ss = (GnmRange *)sv->selections->data;
494 if (!just_add_it && range_equal (ss, &new_sel))
495 return;
497 sv_selection_simplified_free (sv);
499 old_sel = *ss;
500 *ss = new_sel;
502 /* Set the cursor boundary */
503 sv_cursor_set (sv, edit,
504 base_col, base_row,
505 move_col, move_row, ss);
507 if (just_add_it) {
508 sv_redraw_range (sv, &new_sel);
509 sv_redraw_headers (sv, TRUE, TRUE, &new_sel);
510 goto set_menu_flags;
513 if (range_overlap (&old_sel, &new_sel)) {
514 GSList *ranges, *l;
516 * Compute the blocks that need to be repainted: those that
517 * are in the complement of the intersection.
519 ranges = range_fragment (&old_sel, &new_sel);
521 for (l = ranges->next; l; l = l->next)
522 sv_redraw_range (sv, l->data);
523 range_fragment_free (ranges);
524 } else {
525 sv_redraw_range (sv, &old_sel);
526 sv_redraw_range (sv, &new_sel);
529 /* Has the entire row been selected/unselected */
530 if (((new_sel.start.row == 0 && new_sel.end.row == gnm_sheet_get_last_row (sv->sheet)) ^
531 (old_sel.start.row == 0 && old_sel.end.row == gnm_sheet_get_last_row (sv->sheet)))
532 || sv->selection_mode != GNM_SELECTION_MODE_ADD) {
533 GnmRange tmp = range_union (&new_sel, &old_sel);
534 sv_redraw_headers (sv, TRUE, FALSE, &tmp);
535 } else {
536 GnmRange tmp = new_sel;
537 int diff;
539 diff = new_sel.start.col - old_sel.start.col;
540 if (diff != 0) {
541 if (diff > 0) {
542 tmp.start.col = old_sel.start.col;
543 tmp.end.col = new_sel.start.col;
544 } else {
545 tmp.end.col = old_sel.start.col;
546 tmp.start.col = new_sel.start.col;
548 sv_redraw_headers (sv, TRUE, FALSE, &tmp);
550 diff = new_sel.end.col - old_sel.end.col;
551 if (diff != 0) {
552 if (diff > 0) {
553 tmp.start.col = old_sel.end.col;
554 tmp.end.col = new_sel.end.col;
555 } else {
556 tmp.end.col = old_sel.end.col;
557 tmp.start.col = new_sel.end.col;
559 sv_redraw_headers (sv, TRUE, FALSE, &tmp);
563 /* Has the entire col been selected/unselected */
564 if (((new_sel.start.col == 0 && new_sel.end.col == gnm_sheet_get_last_col (sv->sheet)) ^
565 (old_sel.start.col == 0 && old_sel.end.col == gnm_sheet_get_last_col (sv->sheet)))
566 || sv->selection_mode != GNM_SELECTION_MODE_ADD) {
567 GnmRange tmp = range_union (&new_sel, &old_sel);
568 sv_redraw_headers (sv, FALSE, TRUE, &tmp);
569 } else {
570 GnmRange tmp = new_sel;
571 int diff;
573 diff = new_sel.start.row - old_sel.start.row;
574 if (diff != 0) {
575 if (diff > 0) {
576 tmp.start.row = old_sel.start.row;
577 tmp.end.row = new_sel.start.row;
578 } else {
579 tmp.end.row = old_sel.start.row;
580 tmp.start.row = new_sel.start.row;
582 sv_redraw_headers (sv, FALSE, TRUE, &tmp);
585 diff = new_sel.end.row - old_sel.end.row;
586 if (diff != 0) {
587 if (diff > 0) {
588 tmp.start.row = old_sel.end.row;
589 tmp.end.row = new_sel.end.row;
590 } else {
591 tmp.end.row = old_sel.end.row;
592 tmp.start.row = new_sel.end.row;
594 sv_redraw_headers (sv, FALSE, TRUE, &tmp);
598 set_menu_flags:
599 sv_flag_selection_change (sv);
602 * Now see if there is some selection which selects a
603 * whole row, a whole column or the whole sheet and de-activate
604 * insert row/cols and the flags accordingly.
606 do_rows = do_cols = (sv->sheet != NULL);
607 for (list = sv->selections; list && (do_cols || do_rows); list = list->next) {
608 GnmRange const *r = list->data;
610 if (do_cols && range_is_full (r, sv->sheet, TRUE))
611 do_cols = FALSE;
612 if (do_rows && range_is_full (r, sv->sheet, FALSE))
613 do_rows = FALSE;
615 sv_menu_enable_insert (sv, do_cols, do_rows);
618 * FIXME: Enable/disable the show/hide detail menu items here.
619 * We can only do this when the data structures have improved, currently
620 * checking for this will be to slow.
621 * Once it works, use this code :
623 * sheet->priv->enable_showhide_detail = ....
625 * WORKBOOK_FOREACH_VIEW (sheet->workbook, view, {
626 * if (sheet == wb_view_cur_sheet (view)) {
627 * WORKBOOK_VIEW_FOREACH_CONTROL(view, wbc,
628 * wb_control_menu_state_update (wbc, sheet, MS_SHOWHIDE_DETAIL););
630 * });
634 void
635 sv_selection_set (SheetView *sv, GnmCellPos const *edit,
636 int base_col, int base_row,
637 int move_col, int move_row)
639 g_return_if_fail (GNM_IS_SV (sv));
641 sheet_selection_set_internal (sv, edit,
642 base_col, base_row,
643 move_col, move_row, FALSE);
646 void
647 sv_selection_simplify (SheetView *sv)
649 switch (sv->selection_mode) {
650 case GNM_SELECTION_MODE_ADD:
651 /* already simplified */
652 return;
653 case GNM_SELECTION_MODE_REMOVE:
654 sv_selection_calc_simplification (sv);
655 if (sv->selections_simplified != NULL) {
656 sv_selection_free (sv);
657 sv->selections = sv->selections_simplified;
658 sv->selections_simplified = NULL;
660 break;
661 default:
662 case GNM_SELECTION_MODE_TOGGLE:
663 g_warning ("Selection mode %d not implemented!\n", sv->selection_mode);
664 break;
666 sv->selection_mode = GNM_SELECTION_MODE_ADD;
670 * sv_selection_add_full:
671 * @sv: #SheetView whose selection is append to.
672 * @edit_col:
673 * @edit_row: cell to mark as the new edit cursor.
674 * @base_col:
675 * @base_row: stationary corner of the newly selected range.
676 * @move_col:
677 * @move_row: moving corner of the newly selected range.
679 * Prepends a range to the selection list and sets the edit position.
681 void
682 sv_selection_add_full (SheetView *sv,
683 int edit_col, int edit_row,
684 int base_col, int base_row,
685 int move_col, int move_row,
686 GnmSelectionMode mode)
688 GnmRange *ss;
689 GnmCellPos edit;
691 g_return_if_fail (GNM_IS_SV (sv));
692 sv_selection_simplify (sv);
694 /* Create and prepend new selection */
695 ss = g_new0 (GnmRange, 1);
696 sv->selections = g_slist_prepend (sv->selections, ss);
697 sv->selection_mode = mode;
698 edit.col = edit_col;
699 edit.row = edit_row;
700 sheet_selection_set_internal (sv, &edit,
701 base_col, base_row,
702 move_col, move_row, TRUE);
705 void
706 sv_selection_add_range (SheetView *sv, GnmRange const *r)
708 sv_selection_add_full (sv, r->start.col, r->start.row,
709 r->start.col, r->start.row, r->end.col, r->end.row,
710 GNM_SELECTION_MODE_ADD);
712 void
713 sv_selection_add_pos (SheetView *sv, int col, int row, GnmSelectionMode mode)
715 sv_selection_add_full (sv, col, row, col, row, col, row, mode);
719 * sv_selection_free:
720 * @sv: #SheetView
722 * Releases the selection associated with @sv
724 * WARNING: This does not set a new selection and leaves the view in an
725 * INVALID STATE.
727 void
728 sv_selection_free (SheetView *sv)
730 g_slist_free_full (sv->selections, g_free);
731 sv->selections = NULL;
732 sv->selection_mode = GNM_SELECTION_MODE_ADD;
736 * sv_selection_simplified_free:
737 * @sv: #SheetView
739 * Releases the simplified selection associated with @sv
742 void
743 sv_selection_simplified_free (SheetView *sv)
745 g_slist_free_full (sv->selections_simplified, g_free);
746 sv->selections_simplified = NULL;
750 * sv_selection_reset:
751 * @sv: The sheet view
753 * Releases the selection associated with @sv , and forces a redraw of the
754 * previously selected regions and headers.
756 * WARNING: This does not set a new selection and leaves the view in an
757 * INVALID STATE.
759 void
760 sv_selection_reset (SheetView *sv)
762 GSList *list, *tmp;
764 g_return_if_fail (GNM_IS_SV (sv));
766 /* Empty the sheets selection */
767 list = sv->selections;
768 sv->selections = NULL;
769 sv->selection_mode = GNM_SELECTION_MODE_ADD;
771 /* Redraw the grid, & headers for each region */
772 for (tmp = list; tmp; tmp = tmp->next){
773 GnmRange *ss = tmp->data;
774 sv_redraw_range (sv, ss);
775 sv_redraw_headers (sv, TRUE, TRUE, ss);
776 g_free (ss);
779 g_slist_free (list);
781 /* Make sure we re-enable the insert col/row and cell menu items */
782 sv_menu_enable_insert (sv, TRUE, TRUE);
786 * selection_get_ranges:
787 * @sv: #SheetView
788 * @allow_intersection: Divide the selection into nonoverlapping subranges.
790 * Caller is responsible for free the list and the content.
791 * Returns: (element-type GnmRange) (transfer full):
793 GSList *
794 selection_get_ranges (SheetView const *sv, gboolean allow_intersection)
796 GSList *l;
797 GSList *proposed = NULL;
799 #undef DEBUG_SELECTION
800 #ifdef DEBUG_SELECTION
801 g_printerr ("============================\n");
802 #endif
804 l = sv_selection_calc_simplification (sv);
807 * Run through all the selection regions to see if any of
808 * the proposed regions overlap. Start the search with the
809 * single user proposed segment and accumulate distict regions.
811 for (; l != NULL; l = l->next) {
812 GnmRange const *r = l->data;
814 /* The set of regions that do not interset with b or
815 * its predecessors */
816 GSList *clear = NULL;
817 GnmRange *tmp, *b = gnm_range_dup (r);
819 if (allow_intersection) {
820 proposed = g_slist_prepend (proposed, b);
821 continue;
824 /* run through the proposed regions and handle any that
825 * overlap with the current selection region
827 while (proposed != NULL) {
828 int row_intersect, col_intersect;
830 /* pop the 1st element off the list */
831 GnmRange *a = proposed->data;
832 proposed = g_slist_remove (proposed, a);
834 /* The region was already subsumed completely by previous
835 * elements */
836 if (b == NULL) {
837 clear = g_slist_prepend (clear, a);
838 continue;
841 #ifdef DEBUG_SELECTION
842 g_printerr ("a = ");
843 range_dump (a, "; b = ");
844 range_dump (b, "\n");
845 #endif
847 col_intersect =
848 segments_intersect (a->start.col, a->end.col,
849 b->start.col, b->end.col);
851 #ifdef DEBUG_SELECTION
852 g_printerr ("col = %d\na = %s", col_intersect, col_name(a->start.col));
853 if (a->start.col != a->end.col)
854 g_printerr (" -> %s", col_name(a->end.col));
855 g_printerr ("\nb = %s", col_name(b->start.col));
856 if (b->start.col != b->end.col)
857 g_printerr (" -> %s\n", col_name(b->end.col));
858 else
859 g_printerr ("\n");
860 #endif
862 /* No intersection */
863 if (col_intersect == 0) {
864 clear = g_slist_prepend (clear, a);
865 continue;
868 row_intersect =
869 segments_intersect (a->start.row, a->end.row,
870 b->start.row, b->end.row);
871 #ifdef DEBUG_SELECTION
872 g_printerr ("row = %d\na = %s", row_intersect, row_name (a->start.row));
873 if (a->start.row != a->end.row)
874 g_printerr (" -> %s", row_name (a->end.row));
875 g_printerr ("\nb = %s", row_name (b->start.row));
876 if (b->start.row != b->end.row)
877 g_printerr (" -> %s\n", row_name (b->end.row));
878 else
879 g_printerr ("\n");
880 #endif
882 /* No intersection */
883 if (row_intersect == 0) {
884 clear = g_slist_prepend (clear, a);
885 continue;
888 /* Simplify our lives by allowing equality to work in our favour */
889 if (col_intersect == 5) {
890 if (row_intersect == 5)
891 row_intersect = 4;
892 if (row_intersect == 4 || row_intersect == 2)
893 col_intersect = row_intersect;
894 else
895 col_intersect = 4;
896 } else if (row_intersect == 5) {
897 if (col_intersect == 4 || col_intersect == 2)
898 row_intersect = col_intersect;
899 else
900 row_intersect = 4;
903 /* Cross product of intersection cases */
904 switch (col_intersect) {
905 case 4 : /* a contains b */
906 switch (row_intersect) {
907 case 4 : /* a contains b */
908 /* Old region contained by new region */
910 /* remove old region */
911 g_free (b);
912 b = NULL;
913 break;
915 case 3 : /* overlap top */
916 /* Shrink existing range */
917 b->start.row = a->end.row + 1;
918 break;
920 case 2 : /* b contains a */
921 if (a->end.col == b->end.col) {
922 /* Shrink existing range */
923 a->end.col = b->start.col - 1;
924 break;
926 if (a->start.col != b->start.col) {
927 /* Split existing range */
928 tmp = gnm_range_dup (a);
929 tmp->end.col = b->start.col - 1;
930 clear = g_slist_prepend (clear, tmp);
932 /* Shrink existing range */
933 a->start.col = b->end.col + 1;
934 break;
936 case 1 : /* overlap bottom */
937 /* Shrink existing range */
938 a->start.row = b->end.row + 1;
939 break;
941 default :
942 g_assert_not_reached ();
944 break;
946 case 3 : /* overlap left */
947 switch (row_intersect) {
948 case 4 : /* a contains b */
949 /* Shrink old region */
950 b->start.col = a->end.col + 1;
951 break;
953 case 3 : /* overlap top */
954 /* Split region */
955 if (b->start.row > 0) {
956 tmp = gnm_range_dup (a);
957 tmp->start.col = b->start.col;
958 tmp->end.row = b->start.row - 1;
959 clear = g_slist_prepend (clear, tmp);
961 /* fall through */
963 case 2 : /* b contains a */
964 /* shrink the left segment */
965 a->end.col = b->start.col - 1;
966 break;
968 case 1 : /* overlap bottom */
969 /* Split region */
970 if (b->end.row < gnm_sheet_get_last_row (sv->sheet)) {
971 tmp = gnm_range_dup (a);
972 tmp->start.col = b->start.col;
973 tmp->start.row = b->end.row + 1;
974 clear = g_slist_prepend (clear, tmp);
977 /* shrink the left segment */
978 if (b->start.col == 0) {
979 g_free (a);
980 a = NULL;
981 continue;
983 a->end.col = b->start.col - 1;
984 break;
986 default :
987 g_assert_not_reached ();
989 break;
991 case 2 : /* b contains a */
992 switch (row_intersect) {
993 case 3 : /* overlap top */
994 /* shrink the top segment */
995 a->end.row = b->start.row - 1;
996 break;
998 case 2 : /* b contains a */
999 /* remove the selection */
1000 g_free (a);
1001 a = NULL;
1002 continue;
1004 case 4 : /* a contains b */
1005 if (a->end.row == b->end.row) {
1006 /* Shrink existing range */
1007 a->end.row = b->start.row - 1;
1008 break;
1010 if (a->start.row != b->start.row) {
1011 /* Split region */
1012 tmp = gnm_range_dup (a);
1013 tmp->end.row = b->start.row - 1;
1014 clear = g_slist_prepend (clear, tmp);
1016 /* fall through */
1018 case 1 : /* overlap bottom */
1019 /* shrink the top segment */
1020 a->start.row = b->end.row + 1;
1021 break;
1023 default :
1024 g_assert_not_reached ();
1026 break;
1028 case 1 : /* overlap right */
1029 switch (row_intersect) {
1030 case 4 : /* a contains b */
1031 /* Shrink old region */
1032 b->end.col = a->start.col - 1;
1033 break;
1035 case 3 : /* overlap top */
1036 /* Split region */
1037 tmp = gnm_range_dup (a);
1038 tmp->end.col = b->end.col;
1039 tmp->end.row = b->start.row - 1;
1040 clear = g_slist_prepend (clear, tmp);
1041 /* fall through */
1043 case 2 : /* b contains a */
1044 /* shrink the right segment */
1045 a->start.col = b->end.col + 1;
1046 break;
1048 case 1 : /* overlap bottom */
1049 /* Split region */
1050 tmp = gnm_range_dup (a);
1051 tmp->end.col = b->end.col;
1052 tmp->start.row = b->end.row + 1;
1054 /* shrink the right segment */
1055 a->start.col = b->end.col + 1;
1056 break;
1058 default :
1059 g_assert_not_reached ();
1061 break;
1064 /* WARNING : * Be careful putting code here.
1065 * Some of the cases skips this */
1067 /* continue checking the new region for intersections */
1068 clear = g_slist_prepend (clear, a);
1070 proposed = (b != NULL) ? g_slist_prepend (clear, b) : clear;
1073 return proposed;
1077 * sv_selection_apply:
1078 * @sv: #SheetView
1079 * @func: (scope call): The function to apply.
1080 * @allow_intersection: Call the routine for the non-intersecting subregions.
1081 * @user_data: A parameter to pass to each invocation of @func.
1083 * Applies the specified function for all ranges in the selection. Optionally
1084 * select whether to use the high level potentially over lapped ranges, rather
1085 * than the smaller system created non-intersection regions.
1089 void
1090 sv_selection_apply (SheetView *sv, SelectionApplyFunc const func,
1091 gboolean allow_intersection,
1092 void * closure)
1094 GSList *l;
1095 GSList *proposed = NULL;
1097 g_return_if_fail (GNM_IS_SV (sv));
1099 if (allow_intersection) {
1100 for (l = sv_selection_calc_simplification (sv);
1101 l != NULL; l = l->next) {
1102 GnmRange const *ss = l->data;
1104 (*func) (sv, ss, closure);
1106 } else {
1107 proposed = selection_get_ranges (sv, FALSE);
1108 while (proposed != NULL) {
1109 /* pop the 1st element off the list */
1110 GnmRange *r = proposed->data;
1111 proposed = g_slist_remove (proposed, r);
1113 #ifdef DEBUG_SELECTION
1114 range_dump (r, "\n");
1115 #endif
1117 (*func) (sv, r, closure);
1118 g_free (r);
1123 typedef struct {
1124 GString *str;
1125 gboolean include_sheet_name_prefix;
1126 } selection_to_string_closure;
1128 static void
1129 cb_range_to_string (SheetView *sv, GnmRange const *r, void *closure)
1131 GnmConventionsOut out;
1132 GnmRangeRef rr;
1133 GnmParsePos pp;
1134 selection_to_string_closure *res = closure;
1136 if (res->str->len)
1137 g_string_append_c (res->str, ',');
1139 if (res->include_sheet_name_prefix)
1140 g_string_append_printf (res->str, "%s!", sv->sheet->name_quoted);
1142 out.accum = res->str;
1143 out.pp = parse_pos_init_sheet (&pp, sv->sheet);
1144 out.convs = sheet_get_conventions (sv->sheet);
1146 gnm_cellref_init (&rr.a, NULL, r->start.col, r->start.row, FALSE);
1147 gnm_cellref_init (&rr.b, NULL, r->end.col, r->end.row, FALSE);
1148 rangeref_as_string (&out, &rr);
1151 static void
1152 sv_selection_apply_in_order (SheetView *sv, SelectionApplyFunc const func,
1153 void * closure)
1155 GSList *l, *reverse;
1157 g_return_if_fail (GNM_IS_SV (sv));
1159 reverse = g_slist_copy (sv_selection_calc_simplification (sv));
1160 reverse = g_slist_reverse (reverse);
1161 for (l = reverse; l != NULL; l = l->next) {
1162 GnmRange const *ss = l->data;
1164 (*func) (sv, ss, closure);
1166 g_slist_free (reverse);
1170 char *
1171 selection_to_string (SheetView *sv, gboolean include_sheet_name_prefix)
1173 char *output;
1174 selection_to_string_closure res;
1176 res.str = g_string_new (NULL);
1177 res.include_sheet_name_prefix = include_sheet_name_prefix;
1179 sv_selection_apply_in_order (sv, &cb_range_to_string, &res);
1181 output = res.str->str;
1182 g_string_free (res.str, FALSE);
1183 return output;
1187 * sv_selection_foreach:
1188 * @sv: The whose selection is being iterated.
1189 * @handler: (scope call): A function to call for each selected range.
1190 * @user_data:
1192 * Iterate through the ranges in a selection.
1193 * NOTE : The function assumes that the callback routine does NOT change the
1194 * selection list. This can be changed in the future if it is a requirement.
1196 gboolean
1197 sv_selection_foreach (SheetView *sv,
1198 gboolean (*range_cb) (SheetView *sv,
1199 GnmRange const *range,
1200 gpointer user_data),
1201 gpointer user_data)
1203 GSList *l;
1205 g_return_val_if_fail (GNM_IS_SV (sv), FALSE);
1207 for (l = sv_selection_calc_simplification (sv); l != NULL; l = l->next) {
1208 GnmRange *ss = l->data;
1209 if (!range_cb (sv, ss, user_data))
1210 return FALSE;
1212 return TRUE;
1215 /* A protected sheet can limit whether locked and unlocked cells can be
1216 * selected */
1217 gboolean
1218 sheet_selection_is_allowed (Sheet const *sheet, GnmCellPos const *pos)
1220 GnmStyle const *style;
1222 if (!sheet->is_protected)
1223 return TRUE;
1224 style = sheet_style_get (sheet, pos->col, pos->row);
1225 if (gnm_style_get_contents_locked (style))
1226 return sheet->protected_allow.select_locked_cells;
1227 else
1228 return sheet->protected_allow.select_unlocked_cells;
1232 * walk_boundaries: Iterates through a region by row then column.
1233 * @sv: The sheet being iterated in
1234 * @bound: The bounding range
1235 * @forward: iterate forward or backwards
1236 * @horizontal: across then down
1237 * @smart_merge: iterate into merged cells only at their corners
1238 * @res: The result.
1240 * Returns: TRUE if the cursor leaves the boundary region.
1242 static gboolean
1243 walk_boundaries (SheetView const *sv, GnmRange const * const bound,
1244 gboolean const forward, gboolean const horizontal,
1245 gboolean const smart_merge, GnmCellPos * const res)
1247 ColRowInfo const *cri;
1248 int const step = forward ? 1 : -1;
1249 GnmCellPos pos = sv->edit_pos_real;
1250 GnmRange const *merge;
1252 *res = pos;
1253 loop :
1254 merge = gnm_sheet_merge_contains_pos (sv->sheet, &pos);
1255 if (horizontal) {
1256 if (merge != NULL)
1257 pos.col = (forward) ? merge->end.col : merge->start.col;
1258 if (pos.col + step > bound->end.col) {
1259 if (pos.row + 1 > bound->end.row)
1260 return TRUE;
1261 pos.row++;
1262 pos.col = bound->start.col;
1263 } else if (pos.col + step < bound->start.col) {
1264 if (pos.row - 1 < bound->start.row)
1265 return TRUE;
1266 pos.row--;
1267 pos.col = bound->end.col;
1268 } else
1269 pos.col += step;
1270 } else {
1271 if (merge != NULL)
1272 pos.row = (forward) ? merge->end.row : merge->start.row;
1273 if (pos.row + step > bound->end.row) {
1274 if (pos.col + 1 > bound->end.col)
1275 return TRUE;
1276 pos.row = bound->start.row;
1277 pos.col++;
1278 } else if (pos.row + step < bound->start.row) {
1279 if (pos.col - 1 < bound->start.col)
1280 return TRUE;
1281 pos.row = bound->end.row;
1282 pos.col--;
1283 } else
1284 pos.row += step;
1287 cri = sheet_col_get (sv->sheet, pos.col);
1288 if (cri != NULL && !cri->visible)
1289 goto loop;
1290 cri = sheet_row_get (sv->sheet, pos.row);
1291 if (cri != NULL && !cri->visible)
1292 goto loop;
1294 if (!sheet_selection_is_allowed (sv->sheet, &pos))
1295 goto loop;
1297 if (smart_merge) {
1298 merge = gnm_sheet_merge_contains_pos (sv->sheet, &pos);
1299 if (merge != NULL) {
1300 if (forward) {
1301 if (pos.col != merge->start.col ||
1302 pos.row != merge->start.row)
1303 goto loop;
1304 } else if (horizontal) {
1305 if (pos.col != merge->end.col ||
1306 pos.row != merge->start.row)
1307 goto loop;
1308 } else {
1309 if (pos.col != merge->start.col ||
1310 pos.row != merge->end.row)
1311 goto loop;
1316 *res = pos;
1317 return FALSE;
1321 * sv_selection_walk_step:
1322 * @sv: #SheetView
1323 * @forward:
1324 * @horizontal:
1326 * Move the edit_pos of @sv 1 step according to @forward and @horizontal. The
1327 * behavior depends several factors
1328 * - How many ranges are selected
1329 * - The shape of the selected ranges
1330 * - Previous movements (A sequence of tabs followed by an enter can jump
1331 * to the 1st col).
1333 void
1334 sv_selection_walk_step (SheetView *sv, gboolean forward, gboolean horizontal)
1336 int selections_count;
1337 GnmCellPos destination;
1338 GnmRange const *ss;
1339 gboolean is_singleton = FALSE;
1340 GSList *selections;
1342 g_return_if_fail (GNM_IS_SV (sv));
1343 g_return_if_fail (sv->selections != NULL);
1345 selections = sv_selection_calc_simplification (sv);
1347 ss = selections->data;
1348 selections_count = g_slist_length (selections);
1350 /* If there is no selection besides the cursor iterate through the
1351 * entire sheet. Move the cursor and selection as we go. Ignore
1352 * wrapping. At that scale it is irrelevant. */
1353 if (selections_count == 1) {
1354 if (range_is_singleton (ss))
1355 is_singleton = TRUE;
1356 else if (ss->start.col == sv->edit_pos.col &&
1357 ss->start.row == sv->edit_pos.row) {
1358 GnmRange const *merge = gnm_sheet_merge_is_corner (sv->sheet,
1359 &sv->edit_pos);
1360 if (merge != NULL && range_equal (merge, ss))
1361 is_singleton = TRUE;
1365 if (is_singleton) {
1366 int const first_tab_col = sv->first_tab_col;
1367 int const cur_col = sv->edit_pos.col;
1368 GnmRange bound;
1370 /* Interesting : Normally we bound the movement to the current
1371 * col/row. However, if a sheet is protected, and
1372 * differentiates between selecting locked vs
1373 * unlocked cells, then we do not bound things, and allow
1374 * movement to any cell that is acceptable. */
1375 if (sv->sheet->is_protected &&
1376 (sv->sheet->protected_allow.select_locked_cells ^
1377 sv->sheet->protected_allow.select_unlocked_cells))
1378 range_init_full_sheet (&bound, sv->sheet);
1379 else if (horizontal)
1380 range_init_rows (&bound, sv->sheet, ss->start.row, ss->start.row);
1381 else
1382 range_init_cols (&bound, sv->sheet, ss->start.col, ss->start.col);
1384 /* Ignore attempts to move outside the boundary region */
1385 if (!walk_boundaries (sv, &bound, forward, horizontal,
1386 FALSE, &destination)) {
1388 /* <Enter> after some tabs jumps to the first col we tabbed from */
1389 if (forward && !horizontal && first_tab_col >= 0)
1390 destination.col = first_tab_col;
1392 sv_selection_set (sv, &destination,
1393 destination.col, destination.row,
1394 destination.col, destination.row);
1395 sv_make_cell_visible (sv, sv->edit_pos.col,
1396 sv->edit_pos.row, FALSE);
1397 if (horizontal)
1398 sv->first_tab_col = (first_tab_col < 0 || cur_col < first_tab_col) ? cur_col : first_tab_col;
1400 return;
1403 if (walk_boundaries (sv, ss, forward, horizontal,
1404 TRUE, &destination)) {
1405 if (forward) {
1406 GSList *tmp = g_slist_last (sv->selections);
1407 sv->selections = g_slist_concat (tmp,
1408 g_slist_remove_link (sv->selections, tmp));
1409 ss = sv->selections->data;
1410 destination = ss->start;
1411 } else {
1412 GSList *tmp = sv->selections;
1413 sv->selections = g_slist_concat (
1414 g_slist_remove_link (sv->selections, tmp),
1415 tmp);
1416 ss = sv->selections->data;
1417 destination = ss->end;
1419 if (selections_count != 1)
1420 sv_cursor_set (sv, &destination,
1421 ss->start.col, ss->start.row,
1422 ss->end.col, ss->end.row, NULL);
1425 sv_set_edit_pos (sv, &destination);
1426 sv_make_cell_visible (sv, destination.col, destination.row, FALSE);
1429 #include <goffice/goffice.h>
1430 #include <expr.h>
1431 #include <graph.h>
1433 /* characterize a vector based on the last non-blank cell in the range.
1434 * optionally expand the vector to merge multiple string vectors */
1435 static gboolean
1436 characterize_vec (Sheet *sheet, GnmRange *vector,
1437 gboolean as_cols, gboolean expand_text)
1439 GnmCell *cell;
1440 GnmValue const *v;
1441 GnmRange tmp;
1442 int dx = 0, dy = 0;
1443 gboolean is_string = FALSE;
1445 while (1) {
1446 tmp = *vector;
1447 if (!sheet_range_trim (sheet, &tmp, as_cols, !as_cols)) {
1448 cell = sheet_cell_get (sheet, tmp.end.col+dx, tmp.end.row+dy);
1449 if (cell == NULL)
1450 return is_string;
1451 gnm_cell_eval (cell);
1452 v = cell->value;
1454 if (v == NULL || !VALUE_IS_STRING(v))
1455 return is_string;
1456 is_string = TRUE;
1457 if (!expand_text)
1458 return TRUE;
1459 if (as_cols) {
1460 if (vector->end.col >= gnm_sheet_get_last_col (sheet))
1461 return TRUE;
1462 vector->end.col += dx;
1463 dx = 1;
1464 } else {
1465 if (vector->end.row >= gnm_sheet_get_last_row (sheet))
1466 return TRUE;
1467 vector->end.row += dy;
1468 dy = 1;
1470 } else
1471 return is_string;
1474 return is_string; /* NOTREACHED */
1477 void
1478 sv_selection_to_plot (SheetView *sv, GogPlot *go_plot)
1480 GSList *ptr, *sels, *selections;
1481 GnmRange const *r;
1482 int num_cols, num_rows;
1484 Sheet *sheet = sv_sheet (sv);
1485 GnmCellRef header;
1486 GogPlot *plot = go_plot;
1487 GogPlotDesc const *desc;
1488 GogSeries *series;
1489 GogGraph *graph = gog_object_get_graph (GOG_OBJECT (go_plot));
1490 GnmGraphDataClosure *data = g_object_get_data (G_OBJECT (graph), "data-closure");
1491 gboolean is_string_vec, first_series = TRUE, first_value_dim = TRUE;
1492 unsigned i, count, cur_dim = 0, num_series = 1;
1493 gboolean has_header = FALSE, as_cols;
1494 GOData *shared_x = NULL;
1496 gboolean default_to_cols;
1498 selections = sv_selection_calc_simplification (sv);
1500 /* Use the total number of cols vs rows in all of the selected regions.
1501 * We can not use just one in case one of the others happens to be the transpose
1502 * eg select A1 + A:B would default_to_cols = FALSE, then produce a vector for each row */
1503 num_cols = num_rows = 0;
1504 for (ptr = selections; ptr != NULL ; ptr = ptr->next) {
1505 r = ptr->data;
1506 num_cols += range_width (r);
1507 num_rows += range_height (r);
1510 /* Excel docs claim that rows == cols uses rows */
1511 default_to_cols = (!data || data->colrowmode == 0)? (num_cols < num_rows): data->colrowmode == 1;
1513 desc = gog_plot_description (plot);
1514 series = gog_plot_new_series (plot);
1516 header.sheet = sheet;
1517 header.col_relative = header.row_relative = FALSE;
1520 /* FIXME : a cheesy quick implementation */
1521 cur_dim = desc->series.num_dim - 1;
1522 if (desc->series.dim[cur_dim].val_type == GOG_DIM_MATRIX) {
1523 /* Here, only the first range is used. It is assumed it is large enough
1524 to retrieve the axis data and the matrix z values. We probably should raise
1525 an error condition if it is not the case */
1526 /* selections are in reverse order so walk them backwards */
1527 GSList const *ptr = g_slist_last (selections);
1528 GnmRange vector = *((GnmRange const *) ptr->data);
1529 int start_row = vector.start.row;
1530 int start_col = vector.start.col;
1531 int end_row = vector.end.row;
1532 int end_col = vector.end.col;
1533 /* check if we need X and Y axis labels */
1534 if (desc->series.num_dim > 1) {
1535 /* first row will be used as X labels */
1536 if (end_row > start_row) {
1537 vector.start.row = vector.end.row = start_row;
1538 vector.start.col = (start_col < end_col)? start_col + 1: start_col;
1539 vector.end.col = end_col;
1540 /* we assume that there are at most three dims (X, Y and Z) */
1541 gog_series_set_dim (series, 0,
1542 gnm_go_data_vector_new_expr (sheet,
1543 gnm_expr_top_new_constant (
1544 value_new_cellrange_r (sheet, &vector))), NULL);
1545 start_row ++;
1547 if (desc->series.num_dim > 2 && start_col < end_col) {
1548 /* first column will be used as Y labels */
1549 vector.start.row = start_row;
1550 vector.end.row = end_row;
1551 vector.start.col = vector.end.col = start_col;
1552 gog_series_set_dim (series, cur_dim - 1,
1553 gnm_go_data_vector_new_expr (sheet,
1554 gnm_expr_top_new_constant (
1555 value_new_cellrange_r (sheet, &vector))), NULL);
1556 start_col ++;
1559 vector.start.row = start_row;
1560 vector.start.col = start_col;
1561 vector.end.col = end_col;
1562 gog_series_set_dim (series, cur_dim,
1563 gnm_go_data_matrix_new_expr (sheet,
1564 gnm_expr_top_new_constant (
1565 value_new_cellrange_r (sheet, &vector))), NULL);
1566 return;
1569 /* selections are in reverse order so walk them backwards */
1570 cur_dim = 0;
1571 sels = ptr = g_slist_reverse (g_slist_copy (selections));
1572 /* first determine if there is a header in at least one range, see #675913 */
1573 for (; ptr != NULL && !has_header; ptr = ptr->next) {
1574 GnmRange vector = *((GnmRange const *)ptr->data);
1575 as_cols = (vector.start.col == vector.end.col || default_to_cols);
1576 has_header = sheet_range_has_heading (sheet, &vector, as_cols, TRUE);
1578 for (ptr = sels; ptr != NULL; ptr = ptr->next) {
1579 GnmRange vector = *((GnmRange const *)ptr->data);
1581 /* Special case the handling of a vector rather than a range.
1582 * it should stay in its orientation, only ranges get split */
1583 as_cols = (vector.start.col == vector.end.col || default_to_cols);
1584 header.col = vector.start.col;
1585 header.row = vector.start.row;
1587 if (as_cols) {
1588 if (has_header)
1589 vector.start.row++;
1590 count = vector.end.col - vector.start.col;
1591 vector.end.col = vector.start.col;
1592 } else {
1593 if (has_header)
1594 vector.start.col++;
1595 count = vector.end.row - vector.start.row;
1596 vector.end.row = vector.start.row;
1599 for (i = 0 ; i <= count ; ) {
1600 if (cur_dim >= desc->series.num_dim) {
1601 if (num_series >= desc->num_series_max)
1602 break;
1604 series = gog_plot_new_series (plot);
1605 first_series = FALSE;
1606 first_value_dim = TRUE;
1607 cur_dim = 0;
1608 num_series++;
1611 /* skip over shared dimensions already assigned */
1612 while (cur_dim < desc->series.num_dim &&
1613 !first_series && desc->series.dim[cur_dim].is_shared)
1614 ++cur_dim;
1616 /* skip over index series if shared */
1617 while (data->share_x && cur_dim < desc->series.num_dim &&
1618 !first_series && desc->series.dim[cur_dim].val_type == GOG_DIM_INDEX) {
1619 if (shared_x) {
1620 g_object_ref (shared_x);
1621 gog_series_set_dim (series, cur_dim, shared_x, NULL);
1623 ++cur_dim;
1626 while (cur_dim < desc->series.num_dim && desc->series.dim[cur_dim].priority == GOG_SERIES_ERRORS)
1627 ++cur_dim;
1628 if (cur_dim >= desc->series.num_dim)
1629 continue;
1631 is_string_vec = characterize_vec (sheet, &vector, as_cols,
1632 desc->series.dim[cur_dim].val_type == GOG_DIM_LABEL);
1633 while ((desc->series.dim[cur_dim].val_type == GOG_DIM_LABEL && !is_string_vec
1634 && (!first_series || !data->share_x)) ||
1635 (desc->series.dim[cur_dim].val_type == GOG_DIM_VALUE && is_string_vec)) {
1636 if (desc->series.dim[cur_dim].priority == GOG_SERIES_REQUIRED)
1637 /* we used to go to the skip label, but see #674341 */
1638 break;
1639 cur_dim++;
1642 if (data->share_x && first_series && desc->series.dim[cur_dim].val_type == GOG_DIM_INDEX) {
1643 shared_x = gnm_go_data_vector_new_expr (sheet,
1644 gnm_expr_top_new_constant (
1645 value_new_cellrange_r (sheet, &vector)));
1646 gog_series_set_dim (series, cur_dim, shared_x, NULL);
1647 } else
1648 gog_series_set_dim (series, cur_dim,
1649 gnm_go_data_vector_new_expr (sheet,
1650 gnm_expr_top_new_constant (
1651 value_new_cellrange_r (sheet, &vector))), NULL);
1653 if (has_header && first_value_dim &&
1654 desc->series.dim[cur_dim].val_type == GOG_DIM_VALUE) {
1655 first_value_dim = FALSE;
1656 gog_series_set_name (series,
1657 GO_DATA_SCALAR (gnm_go_data_scalar_new_expr (sheet,
1658 gnm_expr_top_new (gnm_expr_new_cellref (&header)))), NULL);
1661 cur_dim++;
1662 /*skip :*/
1664 if (as_cols) {
1665 i += range_width (&vector);
1666 header.col = vector.start.col = ++vector.end.col;
1667 } else {
1668 i += range_height (&vector);
1669 header.row = vector.start.row = ++vector.end.row;
1674 g_slist_free (sels);
1676 #warning TODO If last series is incomplete try to shift data out of optional dimensions.