1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * selection.c: Manage selection regions.
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>
14 #include "selection.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"
25 #include "application.h"
26 #include "command-context.h"
27 #include "workbook-control.h"
28 #include "workbook-view.h"
29 #include "workbook-priv.h"
35 * sv_selection_calc_simplification:
39 * Create the simplified seelction list if necessary
41 * Returns: the simplified version
45 sv_selection_calc_simplification (SheetView
const *sv
)
47 GSList
*simp
= NULL
, *ptr
;
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
,
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
)) {
66 if (range_contained (r
, r_rm
))
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
);
73 GnmRange
*r_new
= g_new (GnmRange
, 1);
75 simp
= g_slist_prepend (simp
, r_new
);
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
;
91 * sv_is_singleton_selected:
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.
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
;
109 * sv_is_pos_selected:
114 * Returns: TRUE if the supplied position is selected in view @sv.
117 sv_is_pos_selected (SheetView
const *sv
, int col
, int row
)
122 for (ptr
= sv_selection_calc_simplification (sv
);
123 ptr
!= NULL
; ptr
= ptr
->next
) {
125 if (range_contains (sr
, col
, row
))
132 * sv_is_range_selected:
136 * Returns: TRUE If @r overlaps with any part of the selection in @sv.
139 sv_is_range_selected (SheetView
const *sv
, GnmRange
const *r
)
144 for (ptr
= sv_selection_calc_simplification (sv
);
145 ptr
!= NULL
; ptr
= ptr
->next
){
147 if (range_overlap (sr
, r
))
154 * sv_is_full_range_selected:
158 * Returns TRUE if all of @r is contained by the selection in @sv.
161 sv_is_full_range_selected (SheetView
const *sv
, GnmRange
const *r
)
166 for (ptr
= sv_selection_calc_simplification (sv
);
167 ptr
!= NULL
; ptr
= ptr
->next
) {
169 if (range_contained (r
, sr
))
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
190 sv_is_colrow_selected (SheetView
const *sv
, int colrow
, gboolean is_col
)
194 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv
), FALSE
);
196 for (l
= sv_selection_calc_simplification (sv
);
197 l
!= NULL
; l
= l
->next
) {
198 GnmRange
const *ss
= l
->data
;
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
)
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
)
216 * sv_is_full_colrow_selected:
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.
225 sv_is_full_colrow_selected (SheetView
const *sv
, gboolean is_cols
, int index
)
228 gboolean found
= FALSE
;
230 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv
), FALSE
);
232 for (l
= sv_selection_calc_simplification (sv
);
233 l
!= NULL
; l
= l
->next
){
234 GnmRange
const *r
= l
->data
;
236 if (r
->start
.row
> 0 || r
->end
.row
< gnm_sheet_get_last_row (sv
->sheet
))
238 if (index
== -1 || (r
->start
.col
<= index
&& index
<= r
->end
.col
))
241 if (r
->start
.col
> 0 || r
->end
.col
< gnm_sheet_get_last_col (sv
->sheet
))
243 if (index
== -1 || (r
->start
.row
<= index
&& index
<= r
->end
.row
))
252 * sv_selection_col_type:
256 * Returns: How much of column @col is selected in @sv.
259 sv_selection_col_type (SheetView
const *sv
, int col
)
263 int ret
= COL_ROW_NO_SELECTION
;
265 g_return_val_if_fail (GNM_IS_SHEET_VIEW (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
) {
274 if (sr
->start
.col
> col
|| sr
->end
.col
< col
)
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
;
288 * sv_selection_row_type:
292 * Returns: How much of column @col is selected in @sv.
295 sv_selection_row_type (SheetView
const *sv
, int row
)
299 int ret
= COL_ROW_NO_SELECTION
;
301 g_return_val_if_fail (GNM_IS_SHEET_VIEW (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
) {
310 if (sr
->start
.row
> row
|| sr
->end
.row
< row
)
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
;
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.
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
)
341 return (e_a
>= e_b
) ? ((e_a
== e_b
) ? 5 : 4) : 2;
343 return (s_a
<= s_b
) ? 4 : 2;
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:
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.
363 sv_menu_enable_insert (SheetView
*sv
, gboolean col
, gboolean row
)
367 g_return_if_fail (GNM_IS_SHEET_VIEW (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
)
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.
400 selection_first_range (SheetView
const *sv
,
401 GOCmdContext
*cc
, char const *cmd_name
)
406 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv
), NULL
);
410 g_return_val_if_fail (l
!= NULL
&& l
->data
!= NULL
, NULL
);
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
);
425 * sv_selection_extend_to:
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.
433 sv_selection_extend_to (SheetView
*sv
, int col
, int row
)
435 int base_col
, base_row
;
439 col
= gnm_sheet_get_last_col (sv
->sheet
);
441 base_col
= sv
->cursor
.base_corner
.col
;
444 row
= gnm_sheet_get_last_row (sv
->sheet
);
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
)
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
);
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
)
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
))
497 sv_selection_simplified_free (sv
);
502 /* Set the cursor boundary */
503 gnm_sheet_view_cursor_set (sv
, edit
,
505 move_col
, move_row
, ss
);
508 gnm_sheet_view_redraw_range (sv
, &new_sel
);
509 gnm_sheet_view_redraw_headers (sv
, TRUE
, TRUE
, &new_sel
);
513 if (range_overlap (&old_sel
, &new_sel
)) {
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 gnm_sheet_view_redraw_range (sv
, l
->data
);
523 range_fragment_free (ranges
);
525 gnm_sheet_view_redraw_range (sv
, &old_sel
);
526 gnm_sheet_view_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 gnm_sheet_view_redraw_headers (sv
, TRUE
, FALSE
, &tmp
);
536 GnmRange tmp
= new_sel
;
539 diff
= new_sel
.start
.col
- old_sel
.start
.col
;
542 tmp
.start
.col
= old_sel
.start
.col
;
543 tmp
.end
.col
= new_sel
.start
.col
;
545 tmp
.end
.col
= old_sel
.start
.col
;
546 tmp
.start
.col
= new_sel
.start
.col
;
548 gnm_sheet_view_redraw_headers (sv
, TRUE
, FALSE
, &tmp
);
550 diff
= new_sel
.end
.col
- old_sel
.end
.col
;
553 tmp
.start
.col
= old_sel
.end
.col
;
554 tmp
.end
.col
= new_sel
.end
.col
;
556 tmp
.end
.col
= old_sel
.end
.col
;
557 tmp
.start
.col
= new_sel
.end
.col
;
559 gnm_sheet_view_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 gnm_sheet_view_redraw_headers (sv
, FALSE
, TRUE
, &tmp
);
570 GnmRange tmp
= new_sel
;
573 diff
= new_sel
.start
.row
- old_sel
.start
.row
;
576 tmp
.start
.row
= old_sel
.start
.row
;
577 tmp
.end
.row
= new_sel
.start
.row
;
579 tmp
.end
.row
= old_sel
.start
.row
;
580 tmp
.start
.row
= new_sel
.start
.row
;
582 gnm_sheet_view_redraw_headers (sv
, FALSE
, TRUE
, &tmp
);
585 diff
= new_sel
.end
.row
- old_sel
.end
.row
;
588 tmp
.start
.row
= old_sel
.end
.row
;
589 tmp
.end
.row
= new_sel
.end
.row
;
591 tmp
.end
.row
= old_sel
.end
.row
;
592 tmp
.start
.row
= new_sel
.end
.row
;
594 gnm_sheet_view_redraw_headers (sv
, FALSE
, TRUE
, &tmp
);
599 gnm_sheet_view_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
))
612 if (do_rows
&& range_is_full (r
, sv
->sheet
, 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););
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_SHEET_VIEW (sv
));
641 sheet_selection_set_internal (sv
, edit
,
643 move_col
, move_row
, FALSE
);
647 sv_selection_simplify (SheetView
*sv
)
649 switch (sv
->selection_mode
) {
650 case GNM_SELECTION_MODE_ADD
:
651 /* already simplified */
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
;
662 case GNM_SELECTION_MODE_TOGGLE
:
663 g_warning ("Selection mode %d not implemented!\n", sv
->selection_mode
);
666 sv
->selection_mode
= GNM_SELECTION_MODE_ADD
;
670 * sv_selection_add_full:
671 * @sv: #SheetView whose selection is append to.
673 * @edit_row: cell to mark as the new edit cursor.
675 * @base_row: stationary corner of the newly selected range.
677 * @move_row: moving corner of the newly selected range.
679 * Prepends a range to the selection list and sets the edit position.
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
)
691 g_return_if_fail (GNM_IS_SHEET_VIEW (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
;
700 sheet_selection_set_internal (sv
, &edit
,
702 move_col
, move_row
, TRUE
);
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
);
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
);
722 * Releases the selection associated with @sv
724 * WARNING: This does not set a new selection and leaves the view in an
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:
739 * Releases the simplified selection associated with @sv
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
760 sv_selection_reset (SheetView
*sv
)
764 g_return_if_fail (GNM_IS_SHEET_VIEW (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 gnm_sheet_view_redraw_range (sv
, ss
);
775 gnm_sheet_view_redraw_headers (sv
, TRUE
, TRUE
, ss
);
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:
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):
794 selection_get_ranges (SheetView
const *sv
, gboolean allow_intersection
)
797 GSList
*proposed
= NULL
;
799 #undef DEBUG_SELECTION
800 #ifdef DEBUG_SELECTION
801 g_printerr ("============================\n");
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
);
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
837 clear
= g_slist_prepend (clear
, a
);
841 #ifdef DEBUG_SELECTION
843 range_dump (a
, "; b = ");
844 range_dump (b
, "\n");
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
));
862 /* No intersection */
863 if (col_intersect
== 0) {
864 clear
= g_slist_prepend (clear
, a
);
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
));
882 /* No intersection */
883 if (row_intersect
== 0) {
884 clear
= g_slist_prepend (clear
, a
);
888 /* Simplify our lives by allowing equality to work in our favour */
889 if (col_intersect
== 5) {
890 if (row_intersect
== 5)
892 if (row_intersect
== 4 || row_intersect
== 2)
893 col_intersect
= row_intersect
;
896 } else if (row_intersect
== 5) {
897 if (col_intersect
== 4 || col_intersect
== 2)
898 row_intersect
= col_intersect
;
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 */
915 case 3 : /* overlap top */
916 /* Shrink existing range */
917 b
->start
.row
= a
->end
.row
+ 1;
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;
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;
936 case 1 : /* overlap bottom */
937 /* Shrink existing range */
938 a
->start
.row
= b
->end
.row
+ 1;
942 g_assert_not_reached ();
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;
953 case 3 : /* overlap top */
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
);
963 case 2 : /* b contains a */
964 /* shrink the left segment */
965 a
->end
.col
= b
->start
.col
- 1;
968 case 1 : /* overlap bottom */
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) {
983 a
->end
.col
= b
->start
.col
- 1;
987 g_assert_not_reached ();
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;
998 case 2 : /* b contains a */
999 /* remove the selection */
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;
1010 if (a
->start
.row
!= b
->start
.row
) {
1012 tmp
= gnm_range_dup (a
);
1013 tmp
->end
.row
= b
->start
.row
- 1;
1014 clear
= g_slist_prepend (clear
, tmp
);
1018 case 1 : /* overlap bottom */
1019 /* shrink the top segment */
1020 a
->start
.row
= b
->end
.row
+ 1;
1024 g_assert_not_reached ();
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;
1035 case 3 : /* overlap top */
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
);
1043 case 2 : /* b contains a */
1044 /* shrink the right segment */
1045 a
->start
.col
= b
->end
.col
+ 1;
1048 case 1 : /* overlap bottom */
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;
1059 g_assert_not_reached ();
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
;
1077 * sv_selection_apply:
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.
1090 sv_selection_apply (SheetView
*sv
, SelectionApplyFunc
const func
,
1091 gboolean allow_intersection
,
1095 GSList
*proposed
= NULL
;
1097 g_return_if_fail (GNM_IS_SHEET_VIEW (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
);
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");
1117 (*func
) (sv
, r
, closure
);
1125 gboolean include_sheet_name_prefix
;
1126 } selection_to_string_closure
;
1129 cb_range_to_string (SheetView
*sv
, GnmRange
const *r
, void *closure
)
1131 GnmConventionsOut out
;
1134 selection_to_string_closure
*res
= closure
;
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
);
1152 sv_selection_apply_in_order (SheetView
*sv
, SelectionApplyFunc
const func
,
1155 GSList
*l
, *reverse
;
1157 g_return_if_fail (GNM_IS_SHEET_VIEW (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
);
1171 selection_to_string (SheetView
*sv
, gboolean include_sheet_name_prefix
)
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
);
1187 * sv_selection_foreach:
1188 * @sv: The whose selection is being iterated.
1189 * @handler: (scope call): A function to call for each selected range.
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.
1197 sv_selection_foreach (SheetView
*sv
,
1198 gboolean (*range_cb
) (SheetView
*sv
,
1199 GnmRange
const *range
,
1200 gpointer user_data
),
1205 g_return_val_if_fail (GNM_IS_SHEET_VIEW (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
))
1215 /* A protected sheet can limit whether locked and unlocked cells can be
1218 sheet_selection_is_allowed (Sheet
const *sheet
, GnmCellPos
const *pos
)
1220 GnmStyle
const *style
;
1222 if (!sheet
->is_protected
)
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
;
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
1240 * Returns: TRUE if the cursor leaves the boundary region.
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
;
1254 merge
= gnm_sheet_merge_contains_pos (sv
->sheet
, &pos
);
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
)
1262 pos
.col
= bound
->start
.col
;
1263 } else if (pos
.col
+ step
< bound
->start
.col
) {
1264 if (pos
.row
- 1 < bound
->start
.row
)
1267 pos
.col
= bound
->end
.col
;
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
)
1276 pos
.row
= bound
->start
.row
;
1278 } else if (pos
.row
+ step
< bound
->start
.row
) {
1279 if (pos
.col
- 1 < bound
->start
.col
)
1281 pos
.row
= bound
->end
.row
;
1287 cri
= sheet_col_get (sv
->sheet
, pos
.col
);
1288 if (cri
!= NULL
&& !cri
->visible
)
1290 cri
= sheet_row_get (sv
->sheet
, pos
.row
);
1291 if (cri
!= NULL
&& !cri
->visible
)
1294 if (!sheet_selection_is_allowed (sv
->sheet
, &pos
))
1298 merge
= gnm_sheet_merge_contains_pos (sv
->sheet
, &pos
);
1299 if (merge
!= NULL
) {
1301 if (pos
.col
!= merge
->start
.col
||
1302 pos
.row
!= merge
->start
.row
)
1304 } else if (horizontal
) {
1305 if (pos
.col
!= merge
->end
.col
||
1306 pos
.row
!= merge
->start
.row
)
1309 if (pos
.col
!= merge
->start
.col
||
1310 pos
.row
!= merge
->end
.row
)
1321 * sv_selection_walk_step:
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
1334 sv_selection_walk_step (SheetView
*sv
, gboolean forward
, gboolean horizontal
)
1336 int selections_count
;
1337 GnmCellPos destination
;
1339 gboolean is_singleton
= FALSE
;
1342 g_return_if_fail (GNM_IS_SHEET_VIEW (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
,
1360 if (merge
!= NULL
&& range_equal (merge
, ss
))
1361 is_singleton
= TRUE
;
1366 int const first_tab_col
= sv
->first_tab_col
;
1367 int const cur_col
= sv
->edit_pos
.col
;
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
);
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 gnm_sheet_view_make_cell_visible (sv
, sv
->edit_pos
.col
,
1396 sv
->edit_pos
.row
, FALSE
);
1398 sv
->first_tab_col
= (first_tab_col
< 0 || cur_col
< first_tab_col
) ? cur_col
: first_tab_col
;
1403 if (walk_boundaries (sv
, ss
, forward
, horizontal
,
1404 TRUE
, &destination
)) {
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
;
1412 GSList
*tmp
= sv
->selections
;
1413 sv
->selections
= g_slist_concat (
1414 g_slist_remove_link (sv
->selections
, tmp
),
1416 ss
= sv
->selections
->data
;
1417 destination
= ss
->end
;
1419 if (selections_count
!= 1)
1420 gnm_sheet_view_cursor_set (sv
, &destination
,
1421 ss
->start
.col
, ss
->start
.row
,
1422 ss
->end
.col
, ss
->end
.row
, NULL
);
1425 gnm_sheet_view_set_edit_pos (sv
, &destination
);
1426 gnm_sheet_view_make_cell_visible (sv
, destination
.col
, destination
.row
, FALSE
);
1429 #include <goffice/goffice.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 */
1436 characterize_vec (Sheet
*sheet
, GnmRange
*vector
,
1437 gboolean as_cols
, gboolean expand_text
)
1443 gboolean is_string
= FALSE
;
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
);
1451 gnm_cell_eval (cell
);
1454 if (v
== NULL
|| !VALUE_IS_STRING(v
))
1460 if (vector
->end
.col
>= gnm_sheet_get_last_col (sheet
))
1462 vector
->end
.col
+= dx
;
1465 if (vector
->end
.row
>= gnm_sheet_get_last_row (sheet
))
1467 vector
->end
.row
+= dy
;
1474 return is_string
; /* NOTREACHED */
1478 sv_selection_to_plot (SheetView
*sv
, GogPlot
*go_plot
)
1480 GSList
*ptr
, *sels
, *selections
;
1482 int num_cols
, num_rows
;
1484 Sheet
*sheet
= sv_sheet (sv
);
1486 GogPlot
*plot
= go_plot
;
1487 GogPlotDesc
const *desc
;
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
) {
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
);
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
);
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
);
1569 /* selections are in reverse order so walk them backwards */
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
;
1590 count
= vector
.end
.col
- vector
.start
.col
;
1591 vector
.end
.col
= 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
)
1604 series
= gog_plot_new_series (plot
);
1605 first_series
= FALSE
;
1606 first_value_dim
= TRUE
;
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
)
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
) {
1620 g_object_ref (shared_x
);
1621 gog_series_set_dim (series
, cur_dim
, shared_x
, NULL
);
1626 while (cur_dim
< desc
->series
.num_dim
&& desc
->series
.dim
[cur_dim
].priority
== GOG_SERIES_ERRORS
)
1628 if (cur_dim
>= desc
->series
.num_dim
)
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 */
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
);
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
);
1665 i
+= range_width (&vector
);
1666 header
.col
= vector
.start
.col
= ++vector
.end
.col
;
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.