Update Spanish translation
[gnumeric.git] / src / selection.c
blob29d52b833c39fdb856f2fccbda3168c88486085d
1 /*
2 * selection.c: Manage selection regions.
4 * Author:
5 * Miguel de Icaza (miguel@gnu.org)
6 * Jody Goldberg (jody@gnome.org)
8 * (C) 1999-2006 Jody Goldberg
9 */
10 #include <gnumeric-config.h>
11 #include <glib/gi18n-lib.h>
12 #include <gnumeric.h>
13 #include <selection.h>
15 #include <sheet.h>
16 #include <sheet-view.h>
17 #include <sheet-merge.h>
18 #include <sheet-style.h>
19 #include <sheet-private.h>
20 #include <sheet-control.h>
21 #include <parse-util.h>
22 #include <clipboard.h>
23 #include <ranges.h>
24 #include <application.h>
25 #include <command-context.h>
26 #include <workbook-control.h>
27 #include <workbook-view.h>
28 #include <workbook-priv.h>
29 #include <commands.h>
30 #include <value.h>
31 #include <cell.h>
32 #include <goffice/goffice.h>
33 #include <expr.h>
34 #include <graph.h>
36 /**
37 * sv_selection_calc_simplification:
38 * @sv:
39 * @mode:
41 * Create the simplified seelction list if necessary
43 * Returns: the simplified version
44 **/
46 static GSList *
47 sv_selection_calc_simplification (SheetView const *sv)
49 GSList *simp = NULL, *ptr;
50 GnmRange *r_rm;
51 SheetView *sv_mod = (SheetView *)sv;
53 if (sv->selection_mode != GNM_SELECTION_MODE_REMOVE)
54 return sv->selections;
55 if (sv->selections_simplified != NULL)
56 return sv->selections_simplified;
58 g_return_val_if_fail (sv->selections != NULL &&
59 sv->selections->data != NULL,
60 sv->selections);
62 r_rm = sv->selections->data;
64 for (ptr = sv->selections->next; ptr != NULL; ptr = ptr->next) {
65 GnmRange *r = ptr->data;
66 if (range_overlap (r_rm, r)) {
67 GSList *pieces;
68 if (range_contained (r, r_rm))
69 continue;
70 pieces = range_split_ranges (r_rm, r);
71 g_free (pieces->data);
72 pieces = g_slist_delete_link (pieces, pieces);
73 simp = g_slist_concat (pieces, simp);
74 } else {
75 GnmRange *r_new = g_new (GnmRange, 1);
76 *r_new = *r;
77 simp = g_slist_prepend (simp, r_new);
81 if (simp == NULL) {
82 GnmRange *r_new = g_new (GnmRange, 1);
83 range_init_cellpos (r_new, &sv->edit_pos);
84 simp = g_slist_prepend (simp, r_new);
87 sv_mod->selections_simplified = g_slist_reverse (simp);
89 return sv->selections_simplified;
92 /**
93 * sv_is_singleton_selected:
94 * @sv: #SheetView
96 * See if the 1st selected region is a singleton.
98 * Returns: (transfer none) (nullable): A #GnmCellPos if the selection is
99 * a singleton
101 GnmCellPos const *
102 sv_is_singleton_selected (SheetView const *sv)
104 #warning FIXME Should we be using the selection rather than the cursor?
105 if (sv->cursor.move_corner.col == sv->cursor.base_corner.col &&
106 sv->cursor.move_corner.row == sv->cursor.base_corner.row)
107 return &sv->cursor.move_corner;
108 return NULL;
112 * sv_is_pos_selected:
113 * @sv:
114 * @col:
115 * @row:
117 * Returns: %TRUE if the supplied position is selected in view @sv.
119 gboolean
120 sv_is_pos_selected (SheetView const *sv, int col, int row)
122 GSList *ptr;
123 GnmRange const *sr;
125 for (ptr = sv_selection_calc_simplification (sv);
126 ptr != NULL ; ptr = ptr->next) {
127 sr = ptr->data;
128 if (range_contains (sr, col, row))
129 return TRUE;
131 return FALSE;
135 * sv_is_range_selected:
136 * @sv:
137 * @r:
139 * Returns: %TRUE If @r overlaps with any part of the selection in @sv.
141 gboolean
142 sv_is_range_selected (SheetView const *sv, GnmRange const *r)
144 GSList *ptr;
145 GnmRange const *sr;
147 for (ptr = sv_selection_calc_simplification (sv);
148 ptr != NULL ; ptr = ptr->next){
149 sr = ptr->data;
150 if (range_overlap (sr, r))
151 return TRUE;
153 return FALSE;
157 * sv_is_full_range_selected:
158 * @sv:
159 * @r:
161 * Returns: %TRUE if all of @r is contained by the selection in @sv.
163 gboolean
164 sv_is_full_range_selected (SheetView const *sv, GnmRange const *r)
166 GSList *ptr;
167 GnmRange const *sr;
169 for (ptr = sv_selection_calc_simplification (sv);
170 ptr != NULL ; ptr = ptr->next) {
171 sr = ptr->data;
172 if (range_contained (r, sr))
173 return TRUE;
175 return FALSE;
179 * sv_is_colrow_selected:
180 * @sv: containing the selection
181 * @colrow: The column or row number we are interested in.
182 * @is_col: A flag indicating whether this it is a column or a row.
184 * Searches the selection list to see whether the entire col/row specified is
185 * contained by the section regions. Since the selection is stored as the set
186 * overlapping user specifed regions we can safely search for the range directly.
188 * Eventually to be completely correct and deal with the case of someone manually
189 * selection an entire col/row, in separate chunks, we will need to do something
190 * more advanced.
192 gboolean
193 sv_is_colrow_selected (SheetView const *sv, int colrow, gboolean is_col)
195 GSList *l;
197 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
199 for (l = sv_selection_calc_simplification (sv);
200 l != NULL; l = l->next) {
201 GnmRange const *ss = l->data;
203 if (is_col) {
204 if (ss->start.row == 0 &&
205 ss->end.row >= gnm_sheet_get_last_row (sv->sheet) &&
206 ss->start.col <= colrow && colrow <= ss->end.col)
207 return TRUE;
208 } else {
209 if (ss->start.col == 0 &&
210 ss->end.col >= gnm_sheet_get_last_col (sv->sheet) &&
211 ss->start.row <= colrow && colrow <= ss->end.row)
212 return TRUE;
215 return FALSE;
219 * sv_is_full_colrow_selected:
220 * @sv:
221 * @is_cols: %TRUE for columns, %FALSE for rows.
222 * @index: index of column or row, -1 for any.
224 * Returns: %TRUE if all of the selected cols/rows in the selection
225 * are fully selected and the selection contains the specified col.
227 gboolean
228 sv_is_full_colrow_selected (SheetView const *sv, gboolean is_cols, int index)
230 GSList *l;
231 gboolean found = FALSE;
233 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
235 for (l = sv_selection_calc_simplification (sv);
236 l != NULL; l = l->next){
237 GnmRange const *r = l->data;
238 if (is_cols) {
239 if (r->start.row > 0 || r->end.row < gnm_sheet_get_last_row (sv->sheet))
240 return FALSE;
241 if (index == -1 || (r->start.col <= index && index <= r->end.col))
242 found = TRUE;
243 } else {
244 if (r->start.col > 0 || r->end.col < gnm_sheet_get_last_col (sv->sheet))
245 return FALSE;
246 if (index == -1 || (r->start.row <= index && index <= r->end.row))
247 found = TRUE;
251 return found;
255 * sv_selection_col_type:
256 * @sv:
257 * @col:
259 * Returns: How much of column @col is selected in @sv.
261 ColRowSelectionType
262 sv_selection_col_type (SheetView const *sv, int col)
264 GSList *ptr;
265 GnmRange const *sr;
266 int ret = COL_ROW_NO_SELECTION;
268 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), COL_ROW_NO_SELECTION);
270 if (sv->selections == NULL)
271 return COL_ROW_NO_SELECTION;
273 for (ptr = sv_selection_calc_simplification (sv);
274 ptr != NULL; ptr = ptr->next) {
275 sr = ptr->data;
277 if (sr->start.col > col || sr->end.col < col)
278 continue;
280 if (sr->start.row == 0 &&
281 sr->end.row == gnm_sheet_get_last_row (sv->sheet))
282 return COL_ROW_FULL_SELECTION;
284 ret = COL_ROW_PARTIAL_SELECTION;
287 return ret;
291 * sv_selection_row_type:
292 * @sv:
293 * @row:
295 * Returns: How much of column @col is selected in @sv.
297 ColRowSelectionType
298 sv_selection_row_type (SheetView const *sv, int row)
300 GSList *ptr;
301 GnmRange const *sr;
302 int ret = COL_ROW_NO_SELECTION;
304 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), COL_ROW_NO_SELECTION);
306 if (sv->selections == NULL)
307 return COL_ROW_NO_SELECTION;
309 for (ptr = sv_selection_calc_simplification (sv);
310 ptr != NULL; ptr = ptr->next) {
311 sr = ptr->data;
313 if (sr->start.row > row || sr->end.row < row)
314 continue;
316 if (sr->start.col == 0 &&
317 sr->end.col == gnm_sheet_get_last_col (sv->sheet))
318 return COL_ROW_FULL_SELECTION;
320 ret = COL_ROW_PARTIAL_SELECTION;
323 return ret;
327 * Quick utility routine to test intersect of line segments.
328 * Returns : 5 sA == sb eA == eb a == b
329 * 4 --sA--sb--eb--eA-- a contains b
330 * 3 --sA--sb--eA--eb-- overlap left
331 * 2 --sb--sA--eA--eb-- b contains a
332 * 1 --sb--sA--eb--eA-- overlap right
333 * 0 if there is no intersection.
335 static int
336 segments_intersect (int const s_a, int const e_a,
337 int const s_b, int const e_b)
339 /* Assume s_a <= e_a and s_b <= e_b */
340 if (e_a < s_b || e_b < s_a)
341 return 0;
343 if (s_a == s_b)
344 return (e_a >= e_b) ? ((e_a == e_b) ? 5 : 4) : 2;
345 if (e_a == e_b)
346 return (s_a <= s_b) ? 4 : 2;
348 if (s_a < s_b)
349 return (e_a >= e_b) ? 4 : 3;
351 /* We already know that s_a <= e_b */
352 return (e_a <= e_b) ? 2 : 1;
356 * sv_menu_enable_insert:
357 * @sv:
358 * @col:
359 * @row:
361 * control whether or not it is ok to insert cols or rows. An internal routine
362 * used by the selection mechanism to avoid erasing the entire sheet when
363 * inserting the wrong dimension.
365 static void
366 sv_menu_enable_insert (SheetView *sv, gboolean col, gboolean row)
368 int flags = 0;
370 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
372 if (sv->enable_insert_cols != col) {
373 flags |= MS_INSERT_COLS;
374 sv->enable_insert_cols = col;
376 if (sv->enable_insert_rows != row) {
377 flags |= MS_INSERT_ROWS;
378 sv->enable_insert_rows = row;
380 if (sv->enable_insert_cells != (col|row)) {
381 flags |= MS_INSERT_CELLS;
382 sv->enable_insert_cells = (col|row);
385 /* during initialization it does not matter */
386 if (!flags || sv->sheet == NULL)
387 return;
389 WORKBOOK_VIEW_FOREACH_CONTROL(sv_wbv (sv), wbc,
390 wb_control_menu_state_update (wbc, flags););
394 * selection_first_range:
395 * @sv: The #SheetView whose selection we are testing.
396 * @cc: The command context to report errors to
397 * @cmd_name: A string naming the operation requiring a single range.
399 * Returns: (transfer none): the first range, if a control is supplied it
400 * displays an error if there is more than one range.
402 GnmRange const *
403 selection_first_range (SheetView const *sv,
404 GOCmdContext *cc, char const *cmd_name)
406 GnmRange const *r;
407 GSList *l;
409 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
411 l = sv->selections;
413 g_return_val_if_fail (l != NULL && l->data != NULL, NULL);
415 r = l->data;
416 if (cc != NULL && l->next != NULL) {
417 GError *msg = g_error_new (go_error_invalid(), 0,
418 _("%s does not support multiple ranges"), cmd_name);
419 go_cmd_context_error (cc, msg);
420 g_error_free (msg);
421 return NULL;
424 return r;
428 * sv_selection_extend_to:
429 * @sv: the sheet
430 * @col: column that gets covered (negative indicates all cols)
431 * @row: row that gets covered (negative indicates all rows)
433 * This extends the selection to cover col, row and updates the status areas.
435 void
436 sv_selection_extend_to (SheetView *sv, int col, int row)
438 int base_col, base_row;
440 if (col < 0) {
441 base_col = 0;
442 col = gnm_sheet_get_last_col (sv->sheet);
443 } else
444 base_col = sv->cursor.base_corner.col;
445 if (row < 0) {
446 base_row = 0;
447 row = gnm_sheet_get_last_row (sv->sheet);
448 } else
449 base_row = sv->cursor.base_corner.row;
451 /* If nothing was going to change dont redraw */
452 if (sv->cursor.move_corner.col == col &&
453 sv->cursor.move_corner.row == row &&
454 sv->cursor.base_corner.col == base_col &&
455 sv->cursor.base_corner.row == base_row)
456 return;
458 sv_selection_set (sv, &sv->edit_pos, base_col, base_row, col, row);
461 * FIXME : Does this belong here ?
462 * This is a convenient place to put it so that changes to the
463 * selection also update the status region, but this is somewhat lower
464 * level that I want to do this.
466 sheet_update (sv->sheet);
467 WORKBOOK_FOREACH_VIEW (sv->sheet->workbook, view, {
468 if (wb_view_cur_sheet (view) == sv->sheet)
469 wb_view_selection_desc (view, FALSE, NULL);
473 static void
474 sheet_selection_set_internal (SheetView *sv,
475 GnmCellPos const *edit,
476 int base_col, int base_row,
477 int move_col, int move_row,
478 gboolean just_add_it)
480 GSList *list;
481 GnmRange *ss;
482 GnmRange old_sel, new_sel;
483 gboolean do_cols, do_rows;
485 g_return_if_fail (sv->selections != NULL);
487 new_sel.start.col = MIN(base_col, move_col);
488 new_sel.start.row = MIN(base_row, move_row);
489 new_sel.end.col = MAX(base_col, move_col);
490 new_sel.end.row = MAX(base_row, move_row);
492 g_return_if_fail (range_is_sane (&new_sel));
494 if (sv->sheet != NULL) /* beware initialization */
495 gnm_sheet_merge_find_bounding_box (sv->sheet, &new_sel);
496 ss = (GnmRange *)sv->selections->data;
497 if (!just_add_it && range_equal (ss, &new_sel))
498 return;
500 sv_selection_simplified_free (sv);
502 old_sel = *ss;
503 *ss = new_sel;
505 /* Set the cursor boundary */
506 gnm_sheet_view_cursor_set (sv, edit,
507 base_col, base_row,
508 move_col, move_row, ss);
510 if (just_add_it) {
511 gnm_sheet_view_redraw_range (sv, &new_sel);
512 gnm_sheet_view_redraw_headers (sv, TRUE, TRUE, &new_sel);
513 goto set_menu_flags;
516 if (range_overlap (&old_sel, &new_sel)) {
517 GSList *ranges, *l;
519 * Compute the blocks that need to be repainted: those that
520 * are in the complement of the intersection.
522 ranges = range_fragment (&old_sel, &new_sel);
524 for (l = ranges->next; l; l = l->next)
525 gnm_sheet_view_redraw_range (sv, l->data);
526 range_fragment_free (ranges);
527 } else {
528 gnm_sheet_view_redraw_range (sv, &old_sel);
529 gnm_sheet_view_redraw_range (sv, &new_sel);
532 /* Has the entire row been selected/unselected */
533 if (((new_sel.start.row == 0 && new_sel.end.row == gnm_sheet_get_last_row (sv->sheet)) ^
534 (old_sel.start.row == 0 && old_sel.end.row == gnm_sheet_get_last_row (sv->sheet)))
535 || sv->selection_mode != GNM_SELECTION_MODE_ADD) {
536 GnmRange tmp = range_union (&new_sel, &old_sel);
537 gnm_sheet_view_redraw_headers (sv, TRUE, FALSE, &tmp);
538 } else {
539 GnmRange tmp = new_sel;
540 int diff;
542 diff = new_sel.start.col - old_sel.start.col;
543 if (diff != 0) {
544 if (diff > 0) {
545 tmp.start.col = old_sel.start.col;
546 tmp.end.col = new_sel.start.col;
547 } else {
548 tmp.end.col = old_sel.start.col;
549 tmp.start.col = new_sel.start.col;
551 gnm_sheet_view_redraw_headers (sv, TRUE, FALSE, &tmp);
553 diff = new_sel.end.col - old_sel.end.col;
554 if (diff != 0) {
555 if (diff > 0) {
556 tmp.start.col = old_sel.end.col;
557 tmp.end.col = new_sel.end.col;
558 } else {
559 tmp.end.col = old_sel.end.col;
560 tmp.start.col = new_sel.end.col;
562 gnm_sheet_view_redraw_headers (sv, TRUE, FALSE, &tmp);
566 /* Has the entire col been selected/unselected */
567 if (((new_sel.start.col == 0 && new_sel.end.col == gnm_sheet_get_last_col (sv->sheet)) ^
568 (old_sel.start.col == 0 && old_sel.end.col == gnm_sheet_get_last_col (sv->sheet)))
569 || sv->selection_mode != GNM_SELECTION_MODE_ADD) {
570 GnmRange tmp = range_union (&new_sel, &old_sel);
571 gnm_sheet_view_redraw_headers (sv, FALSE, TRUE, &tmp);
572 } else {
573 GnmRange tmp = new_sel;
574 int diff;
576 diff = new_sel.start.row - old_sel.start.row;
577 if (diff != 0) {
578 if (diff > 0) {
579 tmp.start.row = old_sel.start.row;
580 tmp.end.row = new_sel.start.row;
581 } else {
582 tmp.end.row = old_sel.start.row;
583 tmp.start.row = new_sel.start.row;
585 gnm_sheet_view_redraw_headers (sv, FALSE, TRUE, &tmp);
588 diff = new_sel.end.row - old_sel.end.row;
589 if (diff != 0) {
590 if (diff > 0) {
591 tmp.start.row = old_sel.end.row;
592 tmp.end.row = new_sel.end.row;
593 } else {
594 tmp.end.row = old_sel.end.row;
595 tmp.start.row = new_sel.end.row;
597 gnm_sheet_view_redraw_headers (sv, FALSE, TRUE, &tmp);
601 set_menu_flags:
602 gnm_sheet_view_flag_selection_change (sv);
605 * Now see if there is some selection which selects a
606 * whole row, a whole column or the whole sheet and de-activate
607 * insert row/cols and the flags accordingly.
609 do_rows = do_cols = (sv->sheet != NULL);
610 for (list = sv->selections; list && (do_cols || do_rows); list = list->next) {
611 GnmRange const *r = list->data;
613 if (do_cols && range_is_full (r, sv->sheet, TRUE))
614 do_cols = FALSE;
615 if (do_rows && range_is_full (r, sv->sheet, FALSE))
616 do_rows = FALSE;
618 sv_menu_enable_insert (sv, do_cols, do_rows);
621 * FIXME: Enable/disable the show/hide detail menu items here.
622 * We can only do this when the data structures have improved, currently
623 * checking for this will be to slow.
624 * Once it works, use this code:
626 * sheet->priv->enable_showhide_detail = ....
628 * WORKBOOK_FOREACH_VIEW (sheet->workbook, view, {
629 * if (sheet == wb_view_cur_sheet (view)) {
630 * WORKBOOK_VIEW_FOREACH_CONTROL(view, wbc,
631 * wb_control_menu_state_update (wbc, sheet, MS_SHOWHIDE_DETAIL););
633 * });
637 void
638 sv_selection_set (SheetView *sv, GnmCellPos const *edit,
639 int base_col, int base_row,
640 int move_col, int move_row)
642 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
644 sheet_selection_set_internal (sv, edit,
645 base_col, base_row,
646 move_col, move_row, FALSE);
649 void
650 sv_selection_simplify (SheetView *sv)
652 switch (sv->selection_mode) {
653 case GNM_SELECTION_MODE_ADD:
654 /* already simplified */
655 return;
656 case GNM_SELECTION_MODE_REMOVE:
657 sv_selection_calc_simplification (sv);
658 if (sv->selections_simplified != NULL) {
659 sv_selection_free (sv);
660 sv->selections = sv->selections_simplified;
661 sv->selections_simplified = NULL;
663 break;
664 default:
665 case GNM_SELECTION_MODE_TOGGLE:
666 g_warning ("Selection mode %d not implemented!\n", sv->selection_mode);
667 break;
669 sv->selection_mode = GNM_SELECTION_MODE_ADD;
673 * sv_selection_add_full:
674 * @sv: #SheetView whose selection is append to.
675 * @edit_col:
676 * @edit_row: cell to mark as the new edit cursor.
677 * @base_col:
678 * @base_row: stationary corner of the newly selected range.
679 * @move_col:
680 * @move_row: moving corner of the newly selected range.
682 * Prepends a range to the selection list and sets the edit position.
684 void
685 sv_selection_add_full (SheetView *sv,
686 int edit_col, int edit_row,
687 int base_col, int base_row,
688 int move_col, int move_row,
689 GnmSelectionMode mode)
691 GnmRange *ss;
692 GnmCellPos edit;
694 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
695 sv_selection_simplify (sv);
697 /* Create and prepend new selection */
698 ss = g_new0 (GnmRange, 1);
699 sv->selections = g_slist_prepend (sv->selections, ss);
700 sv->selection_mode = mode;
701 edit.col = edit_col;
702 edit.row = edit_row;
703 sheet_selection_set_internal (sv, &edit,
704 base_col, base_row,
705 move_col, move_row, TRUE);
708 void
709 sv_selection_add_range (SheetView *sv, GnmRange const *r)
711 sv_selection_add_full (sv, r->start.col, r->start.row,
712 r->start.col, r->start.row, r->end.col, r->end.row,
713 GNM_SELECTION_MODE_ADD);
715 void
716 sv_selection_add_pos (SheetView *sv, int col, int row, GnmSelectionMode mode)
718 sv_selection_add_full (sv, col, row, col, row, col, row, mode);
722 * sv_selection_free:
723 * @sv: #SheetView
725 * Releases the selection associated with @sv
727 * WARNING: This does not set a new selection and leaves the view in an
728 * INVALID STATE.
730 void
731 sv_selection_free (SheetView *sv)
733 g_slist_free_full (sv->selections, g_free);
734 sv->selections = NULL;
735 sv->selection_mode = GNM_SELECTION_MODE_ADD;
739 * sv_selection_simplified_free:
740 * @sv: #SheetView
742 * Releases the simplified selection associated with @sv
745 void
746 sv_selection_simplified_free (SheetView *sv)
748 g_slist_free_full (sv->selections_simplified, g_free);
749 sv->selections_simplified = NULL;
753 * sv_selection_reset:
754 * @sv: The sheet view
756 * Releases the selection associated with @sv , and forces a redraw of the
757 * previously selected regions and headers.
759 * WARNING: This does not set a new selection and leaves the view in an
760 * INVALID STATE.
762 void
763 sv_selection_reset (SheetView *sv)
765 GSList *list, *tmp;
767 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
769 /* Empty the sheets selection */
770 list = sv->selections;
771 sv->selections = NULL;
772 sv->selection_mode = GNM_SELECTION_MODE_ADD;
774 /* Redraw the grid, & headers for each region */
775 for (tmp = list; tmp; tmp = tmp->next){
776 GnmRange *ss = tmp->data;
777 gnm_sheet_view_redraw_range (sv, ss);
778 gnm_sheet_view_redraw_headers (sv, TRUE, TRUE, ss);
779 g_free (ss);
782 g_slist_free (list);
784 /* Make sure we re-enable the insert col/row and cell menu items */
785 sv_menu_enable_insert (sv, TRUE, TRUE);
789 * selection_get_ranges:
790 * @sv: #SheetView
791 * @allow_intersection: Divide the selection into nonoverlapping subranges.
793 * Caller is responsible for free the list and the content.
794 * Returns: (element-type GnmRange) (transfer full):
796 GSList *
797 selection_get_ranges (SheetView const *sv, gboolean allow_intersection)
799 GSList *l;
800 GSList *proposed = NULL;
802 #undef DEBUG_SELECTION
803 #ifdef DEBUG_SELECTION
804 g_printerr ("============================\n");
805 #endif
807 l = sv_selection_calc_simplification (sv);
810 * Run through all the selection regions to see if any of
811 * the proposed regions overlap. Start the search with the
812 * single user proposed segment and accumulate distict regions.
814 for (; l != NULL; l = l->next) {
815 GnmRange const *r = l->data;
817 /* The set of regions that do not interset with b or
818 * its predecessors */
819 GSList *clear = NULL;
820 GnmRange *tmp, *b = gnm_range_dup (r);
822 if (allow_intersection) {
823 proposed = g_slist_prepend (proposed, b);
824 continue;
827 /* run through the proposed regions and handle any that
828 * overlap with the current selection region
830 while (proposed != NULL) {
831 int row_intersect, col_intersect;
833 /* pop the 1st element off the list */
834 GnmRange *a = proposed->data;
835 proposed = g_slist_remove (proposed, a);
837 /* The region was already subsumed completely by previous
838 * elements */
839 if (b == NULL) {
840 clear = g_slist_prepend (clear, a);
841 continue;
844 #ifdef DEBUG_SELECTION
845 g_printerr ("a = ");
846 range_dump (a, "; b = ");
847 range_dump (b, "\n");
848 #endif
850 col_intersect =
851 segments_intersect (a->start.col, a->end.col,
852 b->start.col, b->end.col);
854 #ifdef DEBUG_SELECTION
855 g_printerr ("col = %d\na = %s", col_intersect, col_name(a->start.col));
856 if (a->start.col != a->end.col)
857 g_printerr (" -> %s", col_name(a->end.col));
858 g_printerr ("\nb = %s", col_name(b->start.col));
859 if (b->start.col != b->end.col)
860 g_printerr (" -> %s\n", col_name(b->end.col));
861 else
862 g_printerr ("\n");
863 #endif
865 /* No intersection */
866 if (col_intersect == 0) {
867 clear = g_slist_prepend (clear, a);
868 continue;
871 row_intersect =
872 segments_intersect (a->start.row, a->end.row,
873 b->start.row, b->end.row);
874 #ifdef DEBUG_SELECTION
875 g_printerr ("row = %d\na = %s", row_intersect, row_name (a->start.row));
876 if (a->start.row != a->end.row)
877 g_printerr (" -> %s", row_name (a->end.row));
878 g_printerr ("\nb = %s", row_name (b->start.row));
879 if (b->start.row != b->end.row)
880 g_printerr (" -> %s\n", row_name (b->end.row));
881 else
882 g_printerr ("\n");
883 #endif
885 /* No intersection */
886 if (row_intersect == 0) {
887 clear = g_slist_prepend (clear, a);
888 continue;
891 /* Simplify our lives by allowing equality to work in our favour */
892 if (col_intersect == 5) {
893 if (row_intersect == 5)
894 row_intersect = 4;
895 if (row_intersect == 4 || row_intersect == 2)
896 col_intersect = row_intersect;
897 else
898 col_intersect = 4;
899 } else if (row_intersect == 5) {
900 if (col_intersect == 4 || col_intersect == 2)
901 row_intersect = col_intersect;
902 else
903 row_intersect = 4;
906 /* Cross product of intersection cases */
907 switch (col_intersect) {
908 case 4 : /* a contains b */
909 switch (row_intersect) {
910 case 4 : /* a contains b */
911 /* Old region contained by new region */
913 /* remove old region */
914 g_free (b);
915 b = NULL;
916 break;
918 case 3 : /* overlap top */
919 /* Shrink existing range */
920 b->start.row = a->end.row + 1;
921 break;
923 case 2 : /* b contains a */
924 if (a->end.col == b->end.col) {
925 /* Shrink existing range */
926 a->end.col = b->start.col - 1;
927 break;
929 if (a->start.col != b->start.col) {
930 /* Split existing range */
931 tmp = gnm_range_dup (a);
932 tmp->end.col = b->start.col - 1;
933 clear = g_slist_prepend (clear, tmp);
935 /* Shrink existing range */
936 a->start.col = b->end.col + 1;
937 break;
939 case 1 : /* overlap bottom */
940 /* Shrink existing range */
941 a->start.row = b->end.row + 1;
942 break;
944 default:
945 g_assert_not_reached ();
947 break;
949 case 3 : /* overlap left */
950 switch (row_intersect) {
951 case 4 : /* a contains b */
952 /* Shrink old region */
953 b->start.col = a->end.col + 1;
954 break;
956 case 3 : /* overlap top */
957 /* Split region */
958 if (b->start.row > 0) {
959 tmp = gnm_range_dup (a);
960 tmp->start.col = b->start.col;
961 tmp->end.row = b->start.row - 1;
962 clear = g_slist_prepend (clear, tmp);
964 /* fall through */
966 case 2 : /* b contains a */
967 /* shrink the left segment */
968 a->end.col = b->start.col - 1;
969 break;
971 case 1 : /* overlap bottom */
972 /* Split region */
973 if (b->end.row < gnm_sheet_get_last_row (sv->sheet)) {
974 tmp = gnm_range_dup (a);
975 tmp->start.col = b->start.col;
976 tmp->start.row = b->end.row + 1;
977 clear = g_slist_prepend (clear, tmp);
980 /* shrink the left segment */
981 if (b->start.col == 0) {
982 g_free (a);
983 a = NULL;
984 continue;
986 a->end.col = b->start.col - 1;
987 break;
989 default:
990 g_assert_not_reached ();
992 break;
994 case 2 : /* b contains a */
995 switch (row_intersect) {
996 case 3 : /* overlap top */
997 /* shrink the top segment */
998 a->end.row = b->start.row - 1;
999 break;
1001 case 2 : /* b contains a */
1002 /* remove the selection */
1003 g_free (a);
1004 a = NULL;
1005 continue;
1007 case 4 : /* a contains b */
1008 if (a->end.row == b->end.row) {
1009 /* Shrink existing range */
1010 a->end.row = b->start.row - 1;
1011 break;
1013 if (a->start.row != b->start.row) {
1014 /* Split region */
1015 tmp = gnm_range_dup (a);
1016 tmp->end.row = b->start.row - 1;
1017 clear = g_slist_prepend (clear, tmp);
1019 /* fall through */
1021 case 1 : /* overlap bottom */
1022 /* shrink the top segment */
1023 a->start.row = b->end.row + 1;
1024 break;
1026 default:
1027 g_assert_not_reached ();
1029 break;
1031 case 1 : /* overlap right */
1032 switch (row_intersect) {
1033 case 4 : /* a contains b */
1034 /* Shrink old region */
1035 b->end.col = a->start.col - 1;
1036 break;
1038 case 3 : /* overlap top */
1039 /* Split region */
1040 tmp = gnm_range_dup (a);
1041 tmp->end.col = b->end.col;
1042 tmp->end.row = b->start.row - 1;
1043 clear = g_slist_prepend (clear, tmp);
1044 /* fall through */
1046 case 2 : /* b contains a */
1047 /* shrink the right segment */
1048 a->start.col = b->end.col + 1;
1049 break;
1051 case 1 : /* overlap bottom */
1052 /* Split region */
1053 tmp = gnm_range_dup (a);
1054 tmp->end.col = b->end.col;
1055 tmp->start.row = b->end.row + 1;
1057 /* shrink the right segment */
1058 a->start.col = b->end.col + 1;
1059 break;
1061 default:
1062 g_assert_not_reached ();
1064 break;
1067 /* WARNING : * Be careful putting code here.
1068 * Some of the cases skips this */
1070 /* continue checking the new region for intersections */
1071 clear = g_slist_prepend (clear, a);
1073 proposed = (b != NULL) ? g_slist_prepend (clear, b) : clear;
1076 return proposed;
1080 * sv_selection_apply:
1081 * @sv: #SheetView
1082 * @func: (scope call): The function to apply.
1083 * @allow_intersection: Call the routine for the non-intersecting subregions.
1084 * @user_data: A parameter to pass to each invocation of @func.
1086 * Applies the specified function for all ranges in the selection. Optionally
1087 * select whether to use the high level potentially over lapped ranges, rather
1088 * than the smaller system created non-intersection regions.
1092 void
1093 sv_selection_apply (SheetView *sv, SelectionApplyFunc const func,
1094 gboolean allow_intersection,
1095 void * closure)
1097 GSList *l;
1098 GSList *proposed = NULL;
1100 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
1102 if (allow_intersection) {
1103 for (l = sv_selection_calc_simplification (sv);
1104 l != NULL; l = l->next) {
1105 GnmRange const *ss = l->data;
1107 (*func) (sv, ss, closure);
1109 } else {
1110 proposed = selection_get_ranges (sv, FALSE);
1111 while (proposed != NULL) {
1112 /* pop the 1st element off the list */
1113 GnmRange *r = proposed->data;
1114 proposed = g_slist_remove (proposed, r);
1116 #ifdef DEBUG_SELECTION
1117 range_dump (r, "\n");
1118 #endif
1120 (*func) (sv, r, closure);
1121 g_free (r);
1126 typedef struct {
1127 GString *str;
1128 gboolean include_sheet_name_prefix;
1129 } selection_to_string_closure;
1131 static void
1132 cb_range_to_string (SheetView *sv, GnmRange const *r, void *closure)
1134 GnmConventionsOut out;
1135 GnmRangeRef rr;
1136 GnmParsePos pp;
1137 selection_to_string_closure *res = closure;
1139 if (res->str->len)
1140 g_string_append_c (res->str, ',');
1142 if (res->include_sheet_name_prefix)
1143 g_string_append_printf (res->str, "%s!", sv->sheet->name_quoted);
1145 out.accum = res->str;
1146 out.pp = parse_pos_init_sheet (&pp, sv->sheet);
1147 out.convs = sheet_get_conventions (sv->sheet);
1149 gnm_cellref_init (&rr.a, NULL, r->start.col, r->start.row, FALSE);
1150 gnm_cellref_init (&rr.b, NULL, r->end.col, r->end.row, FALSE);
1151 rangeref_as_string (&out, &rr);
1154 static void
1155 sv_selection_apply_in_order (SheetView *sv, SelectionApplyFunc const func,
1156 void * closure)
1158 GSList *l, *reverse;
1160 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
1162 reverse = g_slist_copy (sv_selection_calc_simplification (sv));
1163 reverse = g_slist_reverse (reverse);
1164 for (l = reverse; l != NULL; l = l->next) {
1165 GnmRange const *ss = l->data;
1167 (*func) (sv, ss, closure);
1169 g_slist_free (reverse);
1173 char *
1174 selection_to_string (SheetView *sv, gboolean include_sheet_name_prefix)
1176 char *output;
1177 selection_to_string_closure res;
1179 res.str = g_string_new (NULL);
1180 res.include_sheet_name_prefix = include_sheet_name_prefix;
1182 sv_selection_apply_in_order (sv, &cb_range_to_string, &res);
1184 output = res.str->str;
1185 g_string_free (res.str, FALSE);
1186 return output;
1190 * sv_selection_foreach:
1191 * @sv: The whose selection is being iterated.
1192 * @handler: (scope call): A function to call for each selected range.
1193 * @user_data:
1195 * Iterate through the ranges in a selection.
1196 * NOTE : The function assumes that the callback routine does NOT change the
1197 * selection list. This can be changed in the future if it is a requirement.
1199 gboolean
1200 sv_selection_foreach (SheetView *sv,
1201 gboolean (*range_cb) (SheetView *sv,
1202 GnmRange const *range,
1203 gpointer user_data),
1204 gpointer user_data)
1206 GSList *l;
1208 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
1210 for (l = sv_selection_calc_simplification (sv); l != NULL; l = l->next) {
1211 GnmRange *ss = l->data;
1212 if (!range_cb (sv, ss, user_data))
1213 return FALSE;
1215 return TRUE;
1218 /* A protected sheet can limit whether locked and unlocked cells can be
1219 * selected */
1220 gboolean
1221 sheet_selection_is_allowed (Sheet const *sheet, GnmCellPos const *pos)
1223 GnmStyle const *style;
1225 if (!sheet->is_protected)
1226 return TRUE;
1227 style = sheet_style_get (sheet, pos->col, pos->row);
1228 if (gnm_style_get_contents_locked (style))
1229 return sheet->protected_allow.select_locked_cells;
1230 else
1231 return sheet->protected_allow.select_unlocked_cells;
1235 * walk_boundaries: Iterates through a region by row then column.
1236 * @sv: The sheet being iterated in
1237 * @bound: The bounding range
1238 * @forward: iterate forward or backwards
1239 * @horizontal: across then down
1240 * @smart_merge: iterate into merged cells only at their corners
1241 * @res: The result.
1243 * Returns: %TRUE if the cursor leaves the boundary region.
1245 static gboolean
1246 walk_boundaries (SheetView const *sv, GnmRange const * const bound,
1247 gboolean const forward, gboolean const horizontal,
1248 gboolean const smart_merge, GnmCellPos * const res)
1250 ColRowInfo const *cri;
1251 int const step = forward ? 1 : -1;
1252 GnmCellPos pos = sv->edit_pos_real;
1253 GnmRange const *merge;
1255 *res = pos;
1256 loop:
1257 merge = gnm_sheet_merge_contains_pos (sv->sheet, &pos);
1258 if (horizontal) {
1259 if (merge != NULL)
1260 pos.col = (forward) ? merge->end.col : merge->start.col;
1261 if (pos.col + step > bound->end.col) {
1262 if (pos.row + 1 > bound->end.row)
1263 return TRUE;
1264 pos.row++;
1265 pos.col = bound->start.col;
1266 } else if (pos.col + step < bound->start.col) {
1267 if (pos.row - 1 < bound->start.row)
1268 return TRUE;
1269 pos.row--;
1270 pos.col = bound->end.col;
1271 } else
1272 pos.col += step;
1273 } else {
1274 if (merge != NULL)
1275 pos.row = (forward) ? merge->end.row : merge->start.row;
1276 if (pos.row + step > bound->end.row) {
1277 if (pos.col + 1 > bound->end.col)
1278 return TRUE;
1279 pos.row = bound->start.row;
1280 pos.col++;
1281 } else if (pos.row + step < bound->start.row) {
1282 if (pos.col - 1 < bound->start.col)
1283 return TRUE;
1284 pos.row = bound->end.row;
1285 pos.col--;
1286 } else
1287 pos.row += step;
1290 cri = sheet_col_get (sv->sheet, pos.col);
1291 if (cri != NULL && !cri->visible)
1292 goto loop;
1293 cri = sheet_row_get (sv->sheet, pos.row);
1294 if (cri != NULL && !cri->visible)
1295 goto loop;
1297 if (!sheet_selection_is_allowed (sv->sheet, &pos))
1298 goto loop;
1300 if (smart_merge) {
1301 merge = gnm_sheet_merge_contains_pos (sv->sheet, &pos);
1302 if (merge != NULL) {
1303 if (forward) {
1304 if (pos.col != merge->start.col ||
1305 pos.row != merge->start.row)
1306 goto loop;
1307 } else if (horizontal) {
1308 if (pos.col != merge->end.col ||
1309 pos.row != merge->start.row)
1310 goto loop;
1311 } else {
1312 if (pos.col != merge->start.col ||
1313 pos.row != merge->end.row)
1314 goto loop;
1319 *res = pos;
1320 return FALSE;
1324 * sv_selection_walk_step:
1325 * @sv: #SheetView
1326 * @forward:
1327 * @horizontal:
1329 * Move the edit_pos of @sv 1 step according to @forward and @horizontal. The
1330 * behavior depends several factors
1331 * - How many ranges are selected
1332 * - The shape of the selected ranges
1333 * - Previous movements (A sequence of tabs followed by an enter can jump
1334 * to the 1st col).
1336 void
1337 sv_selection_walk_step (SheetView *sv, gboolean forward, gboolean horizontal)
1339 int selections_count;
1340 GnmCellPos destination;
1341 GnmRange const *ss;
1342 gboolean is_singleton = FALSE;
1343 GSList *selections;
1345 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
1346 g_return_if_fail (sv->selections != NULL);
1348 selections = sv_selection_calc_simplification (sv);
1350 ss = selections->data;
1351 selections_count = g_slist_length (selections);
1353 /* If there is no selection besides the cursor iterate through the
1354 * entire sheet. Move the cursor and selection as we go. Ignore
1355 * wrapping. At that scale it is irrelevant. */
1356 if (selections_count == 1) {
1357 if (range_is_singleton (ss))
1358 is_singleton = TRUE;
1359 else if (ss->start.col == sv->edit_pos.col &&
1360 ss->start.row == sv->edit_pos.row) {
1361 GnmRange const *merge = gnm_sheet_merge_is_corner (sv->sheet,
1362 &sv->edit_pos);
1363 if (merge != NULL && range_equal (merge, ss))
1364 is_singleton = TRUE;
1368 if (is_singleton) {
1369 int const first_tab_col = sv->first_tab_col;
1370 int const cur_col = sv->edit_pos.col;
1371 GnmRange bound;
1373 /* Interesting : Normally we bound the movement to the current
1374 * col/row. However, if a sheet is protected, and
1375 * differentiates between selecting locked vs
1376 * unlocked cells, then we do not bound things, and allow
1377 * movement to any cell that is acceptable. */
1378 if (sv->sheet->is_protected &&
1379 (sv->sheet->protected_allow.select_locked_cells ^
1380 sv->sheet->protected_allow.select_unlocked_cells))
1381 range_init_full_sheet (&bound, sv->sheet);
1382 else if (horizontal)
1383 range_init_rows (&bound, sv->sheet, ss->start.row, ss->start.row);
1384 else
1385 range_init_cols (&bound, sv->sheet, ss->start.col, ss->start.col);
1387 /* Ignore attempts to move outside the boundary region */
1388 if (!walk_boundaries (sv, &bound, forward, horizontal,
1389 FALSE, &destination)) {
1391 /* <Enter> after some tabs jumps to the first col we tabbed from */
1392 if (forward && !horizontal && first_tab_col >= 0)
1393 destination.col = first_tab_col;
1395 sv_selection_set (sv, &destination,
1396 destination.col, destination.row,
1397 destination.col, destination.row);
1398 gnm_sheet_view_make_cell_visible (sv, sv->edit_pos.col,
1399 sv->edit_pos.row, FALSE);
1400 if (horizontal)
1401 sv->first_tab_col = (first_tab_col < 0 || cur_col < first_tab_col) ? cur_col : first_tab_col;
1403 return;
1406 if (walk_boundaries (sv, ss, forward, horizontal,
1407 TRUE, &destination)) {
1408 if (forward) {
1409 GSList *tmp = g_slist_last (sv->selections);
1410 sv->selections = g_slist_concat (tmp,
1411 g_slist_remove_link (sv->selections, tmp));
1412 ss = sv->selections->data;
1413 destination = ss->start;
1414 } else {
1415 GSList *tmp = sv->selections;
1416 sv->selections = g_slist_concat (
1417 g_slist_remove_link (sv->selections, tmp),
1418 tmp);
1419 ss = sv->selections->data;
1420 destination = ss->end;
1422 if (selections_count != 1)
1423 gnm_sheet_view_cursor_set (sv, &destination,
1424 ss->start.col, ss->start.row,
1425 ss->end.col, ss->end.row, NULL);
1428 gnm_sheet_view_set_edit_pos (sv, &destination);
1429 gnm_sheet_view_make_cell_visible (sv, destination.col, destination.row, FALSE);
1432 /* characterize a vector based on the last non-blank cell in the range.
1433 * optionally expand the vector to merge multiple string vectors */
1434 static gboolean
1435 characterize_vec (Sheet *sheet, GnmRange *vector,
1436 gboolean as_cols, gboolean expand_text)
1438 GnmCell *cell;
1439 GnmValue const *v;
1440 GnmRange tmp;
1441 int dx = 0, dy = 0;
1442 gboolean is_string = FALSE;
1444 while (1) {
1445 tmp = *vector;
1446 if (!sheet_range_trim (sheet, &tmp, as_cols, !as_cols)) {
1447 cell = sheet_cell_get (sheet, tmp.end.col+dx, tmp.end.row+dy);
1448 if (cell == NULL)
1449 return is_string;
1450 gnm_cell_eval (cell);
1451 v = cell->value;
1453 if (v == NULL || !VALUE_IS_STRING(v))
1454 return is_string;
1455 is_string = TRUE;
1456 if (!expand_text)
1457 return TRUE;
1458 if (as_cols) {
1459 if (vector->end.col >= gnm_sheet_get_last_col (sheet))
1460 return TRUE;
1461 vector->end.col += dx;
1462 dx = 1;
1463 } else {
1464 if (vector->end.row >= gnm_sheet_get_last_row (sheet))
1465 return TRUE;
1466 vector->end.row += dy;
1467 dy = 1;
1469 } else
1470 return is_string;
1473 return is_string; /* NOTREACHED */
1476 void
1477 sv_selection_to_plot (SheetView *sv, GogPlot *go_plot)
1479 GSList *ptr, *sels, *selections;
1480 GnmRange const *r;
1481 int num_cols, num_rows;
1483 Sheet *sheet = sv_sheet (sv);
1484 GnmCellRef header;
1485 GogPlot *plot = go_plot;
1486 GogPlotDesc const *desc;
1487 GogSeries *series;
1488 GogGraph *graph = gog_object_get_graph (GOG_OBJECT (go_plot));
1489 GnmGraphDataClosure *data = g_object_get_data (G_OBJECT (graph), "data-closure");
1490 gboolean is_string_vec, first_series = TRUE, first_value_dim = TRUE;
1491 unsigned i, count, cur_dim = 0, num_series = 1;
1492 gboolean has_header = FALSE, as_cols;
1493 GOData *shared_x = NULL;
1495 gboolean default_to_cols;
1497 selections = sv_selection_calc_simplification (sv);
1499 /* Use the total number of cols vs rows in all of the selected regions.
1500 * We can not use just one in case one of the others happens to be the transpose
1501 * eg select A1 + A:B would default_to_cols = FALSE, then produce a vector for each row */
1502 num_cols = num_rows = 0;
1503 for (ptr = selections; ptr != NULL ; ptr = ptr->next) {
1504 r = ptr->data;
1505 num_cols += range_width (r);
1506 num_rows += range_height (r);
1509 /* Excel docs claim that rows == cols uses rows */
1510 default_to_cols = (!data || data->colrowmode == 0)? (num_cols < num_rows): data->colrowmode == 1;
1512 desc = gog_plot_description (plot);
1513 series = gog_plot_new_series (plot);
1515 header.sheet = sheet;
1516 header.col_relative = header.row_relative = FALSE;
1519 /* FIXME : a cheesy quick implementation */
1520 cur_dim = desc->series.num_dim - 1;
1521 if (desc->series.dim[cur_dim].val_type == GOG_DIM_MATRIX) {
1522 /* Here, only the first range is used. It is assumed it is large enough
1523 to retrieve the axis data and the matrix z values. We probably should raise
1524 an error condition if it is not the case */
1525 /* selections are in reverse order so walk them backwards */
1526 GSList const *ptr = g_slist_last (selections);
1527 GnmRange vector = *((GnmRange const *) ptr->data);
1528 int start_row = vector.start.row;
1529 int start_col = vector.start.col;
1530 int end_row = vector.end.row;
1531 int end_col = vector.end.col;
1532 /* check if we need X and Y axis labels */
1533 if (desc->series.num_dim > 1) {
1534 /* first row will be used as X labels */
1535 if (end_row > start_row) {
1536 vector.start.row = vector.end.row = start_row;
1537 vector.start.col = (start_col < end_col)? start_col + 1: start_col;
1538 vector.end.col = end_col;
1539 /* we assume that there are at most three dims (X, Y and Z) */
1540 gog_series_set_dim (series, 0,
1541 gnm_go_data_vector_new_expr (sheet,
1542 gnm_expr_top_new_constant (
1543 value_new_cellrange_r (sheet, &vector))), NULL);
1544 start_row ++;
1546 if (desc->series.num_dim > 2 && start_col < end_col) {
1547 /* first column will be used as Y labels */
1548 vector.start.row = start_row;
1549 vector.end.row = end_row;
1550 vector.start.col = vector.end.col = start_col;
1551 gog_series_set_dim (series, cur_dim - 1,
1552 gnm_go_data_vector_new_expr (sheet,
1553 gnm_expr_top_new_constant (
1554 value_new_cellrange_r (sheet, &vector))), NULL);
1555 start_col ++;
1558 vector.start.row = start_row;
1559 vector.start.col = start_col;
1560 vector.end.col = end_col;
1561 gog_series_set_dim (series, cur_dim,
1562 gnm_go_data_matrix_new_expr (sheet,
1563 gnm_expr_top_new_constant (
1564 value_new_cellrange_r (sheet, &vector))), NULL);
1565 return;
1568 /* selections are in reverse order so walk them backwards */
1569 cur_dim = 0;
1570 sels = ptr = g_slist_reverse (g_slist_copy (selections));
1571 /* first determine if there is a header in at least one range, see #675913 */
1572 for (; ptr != NULL && !has_header; ptr = ptr->next) {
1573 GnmRange vector = *((GnmRange const *)ptr->data);
1574 as_cols = (vector.start.col == vector.end.col || default_to_cols);
1575 has_header = sheet_range_has_heading (sheet, &vector, as_cols, TRUE);
1577 for (ptr = sels; ptr != NULL; ptr = ptr->next) {
1578 GnmRange vector = *((GnmRange const *)ptr->data);
1580 /* Special case the handling of a vector rather than a range.
1581 * it should stay in its orientation, only ranges get split */
1582 as_cols = (vector.start.col == vector.end.col || default_to_cols);
1583 header.col = vector.start.col;
1584 header.row = vector.start.row;
1586 if (as_cols) {
1587 if (has_header)
1588 vector.start.row++;
1589 count = vector.end.col - vector.start.col;
1590 vector.end.col = vector.start.col;
1591 } else {
1592 if (has_header)
1593 vector.start.col++;
1594 count = vector.end.row - vector.start.row;
1595 vector.end.row = vector.start.row;
1598 for (i = 0 ; i <= count ; ) {
1599 if (cur_dim >= desc->series.num_dim) {
1600 if (num_series >= desc->num_series_max)
1601 break;
1603 series = gog_plot_new_series (plot);
1604 first_series = FALSE;
1605 first_value_dim = TRUE;
1606 cur_dim = 0;
1607 num_series++;
1610 /* skip over shared dimensions already assigned */
1611 while (cur_dim < desc->series.num_dim &&
1612 !first_series && desc->series.dim[cur_dim].is_shared)
1613 ++cur_dim;
1615 /* skip over index series if shared */
1616 while (data->share_x && cur_dim < desc->series.num_dim &&
1617 !first_series && desc->series.dim[cur_dim].val_type == GOG_DIM_INDEX) {
1618 if (shared_x) {
1619 g_object_ref (shared_x);
1620 gog_series_set_dim (series, cur_dim, shared_x, NULL);
1622 ++cur_dim;
1625 while (cur_dim < desc->series.num_dim && desc->series.dim[cur_dim].priority == GOG_SERIES_ERRORS)
1626 ++cur_dim;
1627 if (cur_dim >= desc->series.num_dim)
1628 continue;
1630 is_string_vec = characterize_vec (sheet, &vector, as_cols,
1631 desc->series.dim[cur_dim].val_type == GOG_DIM_LABEL);
1632 while ((desc->series.dim[cur_dim].val_type == GOG_DIM_LABEL && !is_string_vec
1633 && (!first_series || !data->share_x)) ||
1634 (desc->series.dim[cur_dim].val_type == GOG_DIM_VALUE && is_string_vec)) {
1635 if (desc->series.dim[cur_dim].priority == GOG_SERIES_REQUIRED)
1636 /* we used to go to the skip label, but see #674341 */
1637 break;
1638 cur_dim++;
1641 if (data->share_x && first_series && desc->series.dim[cur_dim].val_type == GOG_DIM_INDEX) {
1642 shared_x = gnm_go_data_vector_new_expr (sheet,
1643 gnm_expr_top_new_constant (
1644 value_new_cellrange_r (sheet, &vector)));
1645 gog_series_set_dim (series, cur_dim, shared_x, NULL);
1646 } else
1647 gog_series_set_dim (series, cur_dim,
1648 gnm_go_data_vector_new_expr (sheet,
1649 gnm_expr_top_new_constant (
1650 value_new_cellrange_r (sheet, &vector))), NULL);
1652 if (has_header && first_value_dim &&
1653 desc->series.dim[cur_dim].val_type == GOG_DIM_VALUE) {
1654 first_value_dim = FALSE;
1655 gog_series_set_name (series,
1656 GO_DATA_SCALAR (gnm_go_data_scalar_new_expr (sheet,
1657 gnm_expr_top_new (gnm_expr_new_cellref (&header)))), NULL);
1660 cur_dim++;
1661 /*skip :*/
1663 if (as_cols) {
1664 i += range_width (&vector);
1665 header.col = vector.start.col = ++vector.end.col;
1666 } else {
1667 i += range_height (&vector);
1668 header.row = vector.start.row = ++vector.end.row;
1673 g_slist_free (sels);
1675 #warning TODO If last series is incomplete try to shift data out of optional dimensions.