2 * selection.c: Manage selection regions.
5 * Miguel de Icaza (miguel@gnu.org)
6 * Jody Goldberg (jody@gnome.org)
8 * (C) 1999-2006 Jody Goldberg
10 #include <gnumeric-config.h>
11 #include <glib/gi18n-lib.h>
13 #include <selection.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>
24 #include <application.h>
25 #include <command-context.h>
26 #include <workbook-control.h>
27 #include <workbook-view.h>
28 #include <workbook-priv.h>
32 #include <goffice/goffice.h>
37 * sv_selection_calc_simplification:
41 * Create the simplified seelction list if necessary
43 * Returns: the simplified version
47 sv_selection_calc_simplification (SheetView
const *sv
)
49 GSList
*simp
= NULL
, *ptr
;
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
,
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
)) {
68 if (range_contained (r
, r_rm
))
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
);
75 GnmRange
*r_new
= g_new (GnmRange
, 1);
77 simp
= g_slist_prepend (simp
, r_new
);
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
;
93 * sv_is_singleton_selected:
96 * See if the 1st selected region is a singleton.
98 * Returns: (transfer none) (nullable): A #GnmCellPos if the selection is
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
;
112 * sv_is_pos_selected:
117 * Returns: %TRUE if the supplied position is selected in view @sv.
120 sv_is_pos_selected (SheetView
const *sv
, int col
, int row
)
125 for (ptr
= sv_selection_calc_simplification (sv
);
126 ptr
!= NULL
; ptr
= ptr
->next
) {
128 if (range_contains (sr
, col
, row
))
135 * sv_is_range_selected:
139 * Returns: %TRUE If @r overlaps with any part of the selection in @sv.
142 sv_is_range_selected (SheetView
const *sv
, GnmRange
const *r
)
147 for (ptr
= sv_selection_calc_simplification (sv
);
148 ptr
!= NULL
; ptr
= ptr
->next
){
150 if (range_overlap (sr
, r
))
157 * sv_is_full_range_selected:
161 * Returns: %TRUE if all of @r is contained by the selection in @sv.
164 sv_is_full_range_selected (SheetView
const *sv
, GnmRange
const *r
)
169 for (ptr
= sv_selection_calc_simplification (sv
);
170 ptr
!= NULL
; ptr
= ptr
->next
) {
172 if (range_contained (r
, sr
))
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
193 sv_is_colrow_selected (SheetView
const *sv
, int colrow
, gboolean is_col
)
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
;
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
)
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
)
219 * sv_is_full_colrow_selected:
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.
228 sv_is_full_colrow_selected (SheetView
const *sv
, gboolean is_cols
, int index
)
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
;
239 if (r
->start
.row
> 0 || r
->end
.row
< gnm_sheet_get_last_row (sv
->sheet
))
241 if (index
== -1 || (r
->start
.col
<= index
&& index
<= r
->end
.col
))
244 if (r
->start
.col
> 0 || r
->end
.col
< gnm_sheet_get_last_col (sv
->sheet
))
246 if (index
== -1 || (r
->start
.row
<= index
&& index
<= r
->end
.row
))
255 * sv_selection_col_type:
259 * Returns: How much of column @col is selected in @sv.
262 sv_selection_col_type (SheetView
const *sv
, int col
)
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
) {
277 if (sr
->start
.col
> col
|| sr
->end
.col
< col
)
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
;
291 * sv_selection_row_type:
295 * Returns: How much of column @col is selected in @sv.
298 sv_selection_row_type (SheetView
const *sv
, int row
)
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
) {
313 if (sr
->start
.row
> row
|| sr
->end
.row
< row
)
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
;
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.
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
)
344 return (e_a
>= e_b
) ? ((e_a
== e_b
) ? 5 : 4) : 2;
346 return (s_a
<= s_b
) ? 4 : 2;
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:
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.
366 sv_menu_enable_insert (SheetView
*sv
, gboolean col
, gboolean row
)
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
)
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.
403 selection_first_range (SheetView
const *sv
,
404 GOCmdContext
*cc
, char const *cmd_name
)
409 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv
), NULL
);
413 g_return_val_if_fail (l
!= NULL
&& l
->data
!= NULL
, NULL
);
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
);
428 * sv_selection_extend_to:
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.
436 sv_selection_extend_to (SheetView
*sv
, int col
, int row
)
438 int base_col
, base_row
;
442 col
= gnm_sheet_get_last_col (sv
->sheet
);
444 base_col
= sv
->cursor
.base_corner
.col
;
447 row
= gnm_sheet_get_last_row (sv
->sheet
);
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
)
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
);
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
)
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
))
500 sv_selection_simplified_free (sv
);
505 /* Set the cursor boundary */
506 gnm_sheet_view_cursor_set (sv
, edit
,
508 move_col
, move_row
, ss
);
511 gnm_sheet_view_redraw_range (sv
, &new_sel
);
512 gnm_sheet_view_redraw_headers (sv
, TRUE
, TRUE
, &new_sel
);
516 if (range_overlap (&old_sel
, &new_sel
)) {
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
);
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
);
539 GnmRange tmp
= new_sel
;
542 diff
= new_sel
.start
.col
- old_sel
.start
.col
;
545 tmp
.start
.col
= old_sel
.start
.col
;
546 tmp
.end
.col
= new_sel
.start
.col
;
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
;
556 tmp
.start
.col
= old_sel
.end
.col
;
557 tmp
.end
.col
= new_sel
.end
.col
;
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
);
573 GnmRange tmp
= new_sel
;
576 diff
= new_sel
.start
.row
- old_sel
.start
.row
;
579 tmp
.start
.row
= old_sel
.start
.row
;
580 tmp
.end
.row
= new_sel
.start
.row
;
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
;
591 tmp
.start
.row
= old_sel
.end
.row
;
592 tmp
.end
.row
= new_sel
.end
.row
;
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
);
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
))
615 if (do_rows
&& range_is_full (r
, sv
->sheet
, 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););
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
,
646 move_col
, move_row
, FALSE
);
650 sv_selection_simplify (SheetView
*sv
)
652 switch (sv
->selection_mode
) {
653 case GNM_SELECTION_MODE_ADD
:
654 /* already simplified */
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
;
665 case GNM_SELECTION_MODE_TOGGLE
:
666 g_warning ("Selection mode %d not implemented!\n", sv
->selection_mode
);
669 sv
->selection_mode
= GNM_SELECTION_MODE_ADD
;
673 * sv_selection_add_full:
674 * @sv: #SheetView whose selection is append to.
676 * @edit_row: cell to mark as the new edit cursor.
678 * @base_row: stationary corner of the newly selected range.
680 * @move_row: moving corner of the newly selected range.
682 * Prepends a range to the selection list and sets the edit position.
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
)
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
;
703 sheet_selection_set_internal (sv
, &edit
,
705 move_col
, move_row
, TRUE
);
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
);
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
);
725 * Releases the selection associated with @sv
727 * WARNING: This does not set a new selection and leaves the view in an
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:
742 * Releases the simplified selection associated with @sv
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
763 sv_selection_reset (SheetView
*sv
)
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
);
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:
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):
797 selection_get_ranges (SheetView
const *sv
, gboolean allow_intersection
)
800 GSList
*proposed
= NULL
;
802 #undef DEBUG_SELECTION
803 #ifdef DEBUG_SELECTION
804 g_printerr ("============================\n");
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
);
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
840 clear
= g_slist_prepend (clear
, a
);
844 #ifdef DEBUG_SELECTION
846 range_dump (a
, "; b = ");
847 range_dump (b
, "\n");
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
));
865 /* No intersection */
866 if (col_intersect
== 0) {
867 clear
= g_slist_prepend (clear
, a
);
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
));
885 /* No intersection */
886 if (row_intersect
== 0) {
887 clear
= g_slist_prepend (clear
, a
);
891 /* Simplify our lives by allowing equality to work in our favour */
892 if (col_intersect
== 5) {
893 if (row_intersect
== 5)
895 if (row_intersect
== 4 || row_intersect
== 2)
896 col_intersect
= row_intersect
;
899 } else if (row_intersect
== 5) {
900 if (col_intersect
== 4 || col_intersect
== 2)
901 row_intersect
= col_intersect
;
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 */
918 case 3 : /* overlap top */
919 /* Shrink existing range */
920 b
->start
.row
= a
->end
.row
+ 1;
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;
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;
939 case 1 : /* overlap bottom */
940 /* Shrink existing range */
941 a
->start
.row
= b
->end
.row
+ 1;
945 g_assert_not_reached ();
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;
956 case 3 : /* overlap top */
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
);
966 case 2 : /* b contains a */
967 /* shrink the left segment */
968 a
->end
.col
= b
->start
.col
- 1;
971 case 1 : /* overlap bottom */
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) {
986 a
->end
.col
= b
->start
.col
- 1;
990 g_assert_not_reached ();
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;
1001 case 2 : /* b contains a */
1002 /* remove the selection */
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;
1013 if (a
->start
.row
!= b
->start
.row
) {
1015 tmp
= gnm_range_dup (a
);
1016 tmp
->end
.row
= b
->start
.row
- 1;
1017 clear
= g_slist_prepend (clear
, tmp
);
1021 case 1 : /* overlap bottom */
1022 /* shrink the top segment */
1023 a
->start
.row
= b
->end
.row
+ 1;
1027 g_assert_not_reached ();
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;
1038 case 3 : /* overlap top */
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
);
1046 case 2 : /* b contains a */
1047 /* shrink the right segment */
1048 a
->start
.col
= b
->end
.col
+ 1;
1051 case 1 : /* overlap bottom */
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;
1062 g_assert_not_reached ();
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
;
1080 * sv_selection_apply:
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.
1093 sv_selection_apply (SheetView
*sv
, SelectionApplyFunc
const func
,
1094 gboolean allow_intersection
,
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
);
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");
1120 (*func
) (sv
, r
, closure
);
1128 gboolean include_sheet_name_prefix
;
1129 } selection_to_string_closure
;
1132 cb_range_to_string (SheetView
*sv
, GnmRange
const *r
, void *closure
)
1134 GnmConventionsOut out
;
1137 selection_to_string_closure
*res
= closure
;
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
);
1155 sv_selection_apply_in_order (SheetView
*sv
, SelectionApplyFunc
const func
,
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
);
1174 selection_to_string (SheetView
*sv
, gboolean include_sheet_name_prefix
)
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
);
1190 * sv_selection_foreach:
1191 * @sv: The whose selection is being iterated.
1192 * @handler: (scope call): A function to call for each selected range.
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.
1200 sv_selection_foreach (SheetView
*sv
,
1201 gboolean (*range_cb
) (SheetView
*sv
,
1202 GnmRange
const *range
,
1203 gpointer user_data
),
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
))
1218 /* A protected sheet can limit whether locked and unlocked cells can be
1221 sheet_selection_is_allowed (Sheet
const *sheet
, GnmCellPos
const *pos
)
1223 GnmStyle
const *style
;
1225 if (!sheet
->is_protected
)
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
;
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
1243 * Returns: %TRUE if the cursor leaves the boundary region.
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
;
1257 merge
= gnm_sheet_merge_contains_pos (sv
->sheet
, &pos
);
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
)
1265 pos
.col
= bound
->start
.col
;
1266 } else if (pos
.col
+ step
< bound
->start
.col
) {
1267 if (pos
.row
- 1 < bound
->start
.row
)
1270 pos
.col
= bound
->end
.col
;
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
)
1279 pos
.row
= bound
->start
.row
;
1281 } else if (pos
.row
+ step
< bound
->start
.row
) {
1282 if (pos
.col
- 1 < bound
->start
.col
)
1284 pos
.row
= bound
->end
.row
;
1290 cri
= sheet_col_get (sv
->sheet
, pos
.col
);
1291 if (cri
!= NULL
&& !cri
->visible
)
1293 cri
= sheet_row_get (sv
->sheet
, pos
.row
);
1294 if (cri
!= NULL
&& !cri
->visible
)
1297 if (!sheet_selection_is_allowed (sv
->sheet
, &pos
))
1301 merge
= gnm_sheet_merge_contains_pos (sv
->sheet
, &pos
);
1302 if (merge
!= NULL
) {
1304 if (pos
.col
!= merge
->start
.col
||
1305 pos
.row
!= merge
->start
.row
)
1307 } else if (horizontal
) {
1308 if (pos
.col
!= merge
->end
.col
||
1309 pos
.row
!= merge
->start
.row
)
1312 if (pos
.col
!= merge
->start
.col
||
1313 pos
.row
!= merge
->end
.row
)
1324 * sv_selection_walk_step:
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
1337 sv_selection_walk_step (SheetView
*sv
, gboolean forward
, gboolean horizontal
)
1339 int selections_count
;
1340 GnmCellPos destination
;
1342 gboolean is_singleton
= FALSE
;
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
,
1363 if (merge
!= NULL
&& range_equal (merge
, ss
))
1364 is_singleton
= TRUE
;
1369 int const first_tab_col
= sv
->first_tab_col
;
1370 int const cur_col
= sv
->edit_pos
.col
;
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
);
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
);
1401 sv
->first_tab_col
= (first_tab_col
< 0 || cur_col
< first_tab_col
) ? cur_col
: first_tab_col
;
1406 if (walk_boundaries (sv
, ss
, forward
, horizontal
,
1407 TRUE
, &destination
)) {
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
;
1415 GSList
*tmp
= sv
->selections
;
1416 sv
->selections
= g_slist_concat (
1417 g_slist_remove_link (sv
->selections
, 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 */
1435 characterize_vec (Sheet
*sheet
, GnmRange
*vector
,
1436 gboolean as_cols
, gboolean expand_text
)
1442 gboolean is_string
= FALSE
;
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
);
1450 gnm_cell_eval (cell
);
1453 if (v
== NULL
|| !VALUE_IS_STRING(v
))
1459 if (vector
->end
.col
>= gnm_sheet_get_last_col (sheet
))
1461 vector
->end
.col
+= dx
;
1464 if (vector
->end
.row
>= gnm_sheet_get_last_row (sheet
))
1466 vector
->end
.row
+= dy
;
1473 return is_string
; /* NOTREACHED */
1477 sv_selection_to_plot (SheetView
*sv
, GogPlot
*go_plot
)
1479 GSList
*ptr
, *sels
, *selections
;
1481 int num_cols
, num_rows
;
1483 Sheet
*sheet
= sv_sheet (sv
);
1485 GogPlot
*plot
= go_plot
;
1486 GogPlotDesc
const *desc
;
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
) {
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
);
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
);
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
);
1568 /* selections are in reverse order so walk them backwards */
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
;
1589 count
= vector
.end
.col
- vector
.start
.col
;
1590 vector
.end
.col
= 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
)
1603 series
= gog_plot_new_series (plot
);
1604 first_series
= FALSE
;
1605 first_value_dim
= TRUE
;
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
)
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
) {
1619 g_object_ref (shared_x
);
1620 gog_series_set_dim (series
, cur_dim
, shared_x
, NULL
);
1625 while (cur_dim
< desc
->series
.num_dim
&& desc
->series
.dim
[cur_dim
].priority
== GOG_SERIES_ERRORS
)
1627 if (cur_dim
>= desc
->series
.num_dim
)
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 */
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
);
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
);
1664 i
+= range_width (&vector
);
1665 header
.col
= vector
.start
.col
= ++vector
.end
.col
;
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.