1 /*
2  * selection.c:  Manage selection regions.
3  *
4  * Author:
5  *  Miguel de Icaza (miguel@gnu.org)
6  *  Jody Goldberg (jody@gnome.org)
7  *
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>
14 
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>
35 
36 /**
37  * sv_selection_calc_simplification:
38  * @sv:
39  * @mode:
40  *
41  * Create the simplified selection list if necessary
42  *
43  * Returns: the simplified version
44  **/
45 
46 static GSList *
sv_selection_calc_simplification(SheetView const * sv)47 sv_selection_calc_simplification (SheetView const *sv)
48 {
49 	GSList *simp = NULL, *ptr;
50 	GnmRange *r_rm;
51 	SheetView *sv_mod = (SheetView *)sv;
52 
53 	if (sv->selection_mode != GNM_SELECTION_MODE_REMOVE)
54 		return sv->selections;
55 	if (sv->selections_simplified != NULL)
56 		return sv->selections_simplified;
57 
58 	g_return_val_if_fail (sv->selections != NULL &&
59 			      sv->selections->data != NULL,
60 			      sv->selections);
61 
62 	r_rm = sv->selections->data;
63 
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);
78 		}
79 	}
80 
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);
85 	}
86 
87 	sv_mod->selections_simplified = g_slist_reverse (simp);
88 
89 	return sv->selections_simplified;
90 }
91 
92 /**
93  * sv_is_singleton_selected:
94  * @sv: #SheetView
95  *
96  * See if the 1st selected region is a singleton.
97  *
98  * Returns: (transfer none) (nullable): A #GnmCellPos if the selection is
99  * a singleton
100  **/
101 GnmCellPos const *
sv_is_singleton_selected(SheetView const * sv)102 sv_is_singleton_selected (SheetView const *sv)
103 {
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;
109 }
110 
111 /**
112  * sv_is_pos_selected:
113  * @sv:
114  * @col:
115  * @row:
116  *
117  * Returns: %TRUE if the supplied position is selected in view @sv.
118  **/
119 gboolean
sv_is_pos_selected(SheetView const * sv,int col,int row)120 sv_is_pos_selected (SheetView const *sv, int col, int row)
121 {
122 	GSList *ptr;
123 	GnmRange const *sr;
124 
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;
130 	}
131 	return FALSE;
132 }
133 
134 /**
135  * sv_is_range_selected:
136  * @sv:
137  * @r:
138  *
139  * Returns: %TRUE If @r overlaps with any part of the selection in @sv.
140  **/
141 gboolean
sv_is_range_selected(SheetView const * sv,GnmRange const * r)142 sv_is_range_selected (SheetView const *sv, GnmRange const *r)
143 {
144 	GSList *ptr;
145 	GnmRange const *sr;
146 
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;
152 	}
153 	return FALSE;
154 }
155 
156 /**
157  * sv_is_full_range_selected:
158  * @sv:
159  * @r:
160  *
161  * Returns: %TRUE if all of @r is contained by the selection in @sv.
162  **/
163 gboolean
sv_is_full_range_selected(SheetView const * sv,GnmRange const * r)164 sv_is_full_range_selected (SheetView const *sv, GnmRange const *r)
165 {
166 	GSList *ptr;
167 	GnmRange const *sr;
168 
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;
174 	}
175 	return FALSE;
176 }
177 
178 /*
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.
183  *
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.
187  *
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.
191  */
192 gboolean
sv_is_colrow_selected(SheetView const * sv,int colrow,gboolean is_col)193 sv_is_colrow_selected (SheetView const *sv, int colrow, gboolean is_col)
194 {
195 	GSList *l;
196 
197 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
198 
199 	for (l = sv_selection_calc_simplification (sv);
200 	     l != NULL; l = l->next) {
201 		GnmRange const *ss = l->data;
202 
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;
213 		}
214 	}
215 	return FALSE;
216 }
217 
218 /**
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.
223  *
224  * Returns: %TRUE if all of the selected cols/rows in the selection
225  *	are fully selected and the selection contains the specified col.
226  **/
227 gboolean
sv_is_full_colrow_selected(SheetView const * sv,gboolean is_cols,int index)228 sv_is_full_colrow_selected (SheetView const *sv, gboolean is_cols, int index)
229 {
230 	GSList *l;
231 	gboolean found = FALSE;
232 
233 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
234 
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;
248 		}
249 	}
250 
251 	return found;
252 }
253 
254 /**
255  * sv_selection_col_type:
256  * @sv:
257  * @col:
258  *
259  * Returns: How much of column @col is selected in @sv.
260  **/
261 ColRowSelectionType
sv_selection_col_type(SheetView const * sv,int col)262 sv_selection_col_type (SheetView const *sv, int col)
263 {
264 	GSList *ptr;
265 	GnmRange const *sr;
266 	int ret = COL_ROW_NO_SELECTION;
267 
268 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), COL_ROW_NO_SELECTION);
269 
270 	if (sv->selections == NULL)
271 		return COL_ROW_NO_SELECTION;
272 
273 	for (ptr = sv_selection_calc_simplification (sv);
274 	     ptr != NULL; ptr = ptr->next) {
275 		sr = ptr->data;
276 
277 		if (sr->start.col > col || sr->end.col < col)
278 			continue;
279 
280 		if (sr->start.row == 0 &&
281 		    sr->end.row == gnm_sheet_get_last_row (sv->sheet))
282 			return COL_ROW_FULL_SELECTION;
283 
284 		ret = COL_ROW_PARTIAL_SELECTION;
285 	}
286 
287 	return ret;
288 }
289 
290 /**
291  * sv_selection_row_type:
292  * @sv:
293  * @row:
294  *
295  * Returns: How much of column @col is selected in @sv.
296  **/
297 ColRowSelectionType
sv_selection_row_type(SheetView const * sv,int row)298 sv_selection_row_type (SheetView const *sv, int row)
299 {
300 	GSList *ptr;
301 	GnmRange const *sr;
302 	int ret = COL_ROW_NO_SELECTION;
303 
304 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), COL_ROW_NO_SELECTION);
305 
306 	if (sv->selections == NULL)
307 		return COL_ROW_NO_SELECTION;
308 
309 	for (ptr = sv_selection_calc_simplification (sv);
310 	     ptr != NULL; ptr = ptr->next) {
311 		sr = ptr->data;
312 
313 		if (sr->start.row > row || sr->end.row < row)
314 			continue;
315 
316 		if (sr->start.col == 0 &&
317 		    sr->end.col == gnm_sheet_get_last_col (sv->sheet))
318 			return COL_ROW_FULL_SELECTION;
319 
320 		ret = COL_ROW_PARTIAL_SELECTION;
321 	}
322 
323 	return ret;
324 }
325 
326 /*
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.
334  */
335 static int
segments_intersect(int const s_a,int const e_a,int const s_b,int const e_b)336 segments_intersect (int const s_a, int const e_a,
337 		    int const s_b, int const e_b)
338 {
339 	/* Assume s_a <= e_a and s_b <= e_b */
340 	if (e_a < s_b || e_b < s_a)
341 		return 0;
342 
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;
347 
348 	if (s_a < s_b)
349 		return (e_a >= e_b) ? 4 : 3;
350 
351 	/* We already know that s_a <= e_b */
352 	return (e_a <= e_b) ? 2 : 1;
353 }
354 
355 /**
356  * sv_menu_enable_insert:
357  * @sv:
358  * @col:
359  * @row:
360  *
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.
364  */
365 static void
sv_menu_enable_insert(SheetView * sv,gboolean col,gboolean row)366 sv_menu_enable_insert (SheetView *sv, gboolean col, gboolean row)
367 {
368 	int flags = 0;
369 
370 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
371 
372 	if (sv->enable_insert_cols != col) {
373 		flags |= MS_INSERT_COLS;
374 		sv->enable_insert_cols = col;
375 	}
376 	if (sv->enable_insert_rows != row) {
377 		flags |= MS_INSERT_ROWS;
378 		sv->enable_insert_rows = row;
379 	}
380 	if (sv->enable_insert_cells != (col|row)) {
381 		flags |= MS_INSERT_CELLS;
382 		sv->enable_insert_cells = (col|row);
383 	}
384 
385 	/* during initialization it does not matter */
386 	if (!flags || sv->sheet == NULL)
387 		return;
388 
389 	WORKBOOK_VIEW_FOREACH_CONTROL(sv_wbv (sv), wbc,
390 		wb_control_menu_state_update (wbc, flags););
391 }
392 
393 /**
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.
398  *
399  * Returns: (transfer none): the first range, if a control is supplied it
400  * displays an error if there is more than one range.
401  **/
402 GnmRange const *
selection_first_range(SheetView const * sv,GOCmdContext * cc,char const * cmd_name)403 selection_first_range (SheetView const *sv,
404 		       GOCmdContext *cc, char const *cmd_name)
405 {
406 	GnmRange const *r;
407 	GSList *l;
408 
409 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
410 
411 	l = sv->selections;
412 
413 	g_return_val_if_fail (l != NULL && l->data != NULL, NULL);
414 
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;
422 	}
423 
424 	return r;
425 }
426 
427 /**
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)
432  *
433  * This extends the selection to cover col, row and updates the status areas.
434  */
435 void
sv_selection_extend_to(SheetView * sv,int col,int row)436 sv_selection_extend_to (SheetView *sv, int col, int row)
437 {
438 	int base_col, base_row;
439 
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;
450 
451 	/* If nothing was going to change, don't 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;
457 
458 	sv_selection_set (sv, &sv->edit_pos, base_col, base_row, col, row);
459 
460 	/*
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.
465 	 */
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);
470 	});
471 }
472 
473 static void
sheet_selection_set_internal(SheetView * sv,GnmCellPos const * edit,int base_col,int base_row,int move_col,int move_row,gboolean just_add_it)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)
479 {
480 	GSList *list;
481 	GnmRange *ss;
482 	GnmRange old_sel, new_sel;
483 	gboolean do_cols, do_rows;
484 
485 	g_return_if_fail (sv->selections != NULL);
486 
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);
491 
492 	g_return_if_fail (range_is_sane (&new_sel));
493 
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;
499 
500 	sv_selection_simplified_free (sv);
501 
502 	old_sel = *ss;
503 	*ss = new_sel;
504 
505 	/* Set the cursor boundary */
506 	gnm_sheet_view_cursor_set (sv, edit,
507 		base_col, base_row,
508 		move_col, move_row, ss);
509 
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;
514 	}
515 
516 	if (range_overlap (&old_sel, &new_sel)) {
517 		GSList *ranges, *l;
518 		/*
519 		 * Compute the blocks that need to be repainted: those that
520 		 * are in the complement of the intersection.
521 		 */
522 		ranges = range_fragment (&old_sel, &new_sel);
523 
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);
530 	}
531 
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;
541 
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;
550 			}
551 			gnm_sheet_view_redraw_headers (sv, TRUE, FALSE, &tmp);
552 		}
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;
561 			}
562 			gnm_sheet_view_redraw_headers (sv, TRUE, FALSE, &tmp);
563 		}
564 	}
565 
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;
575 
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;
584 			}
585 			gnm_sheet_view_redraw_headers (sv, FALSE, TRUE, &tmp);
586 		}
587 
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;
596 			}
597 			gnm_sheet_view_redraw_headers (sv, FALSE, TRUE, &tmp);
598 		}
599 	}
600 
601 set_menu_flags:
602 	gnm_sheet_view_flag_selection_change (sv);
603 
604 	/*
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.
608 	 */
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;
612 
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;
617 	}
618 	sv_menu_enable_insert (sv, do_cols, do_rows);
619 
620 	/*
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:
625 	 *
626 	 * sheet->priv->enable_showhide_detail = ....
627 	 *
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););
632 	 *      }
633 	 * });
634 	 */
635 }
636 
637 void
sv_selection_set(SheetView * sv,GnmCellPos const * edit,int base_col,int base_row,int move_col,int move_row)638 sv_selection_set (SheetView *sv, GnmCellPos const *edit,
639 		  int base_col, int base_row,
640 		  int move_col, int move_row)
641 {
642 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
643 
644 	sheet_selection_set_internal (sv, edit,
645 		base_col, base_row,
646 		move_col, move_row, FALSE);
647 }
648 
649 void
sv_selection_simplify(SheetView * sv)650 sv_selection_simplify (SheetView *sv)
651 {
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;
662 		}
663 		break;
664 	default:
665 	case GNM_SELECTION_MODE_TOGGLE:
666 		g_warning ("Selection mode %d not implemented!\n", sv->selection_mode);
667 		break;
668 	}
669 	sv->selection_mode = GNM_SELECTION_MODE_ADD;
670 }
671 
672 /**
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.
681  *
682  * Prepends a range to the selection list and sets the edit position.
683  **/
684 void
sv_selection_add_full(SheetView * sv,int edit_col,int edit_row,int base_col,int base_row,int move_col,int move_row,GnmSelectionMode mode)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)
690 {
691 	GnmRange *ss;
692 	GnmCellPos edit;
693 
694 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
695 	sv_selection_simplify (sv);
696 
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);
706 }
707 
708 void
sv_selection_add_range(SheetView * sv,GnmRange const * r)709 sv_selection_add_range (SheetView *sv, GnmRange const *r)
710 {
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);
714 }
715 void
sv_selection_add_pos(SheetView * sv,int col,int row,GnmSelectionMode mode)716 sv_selection_add_pos (SheetView *sv, int col, int row, GnmSelectionMode mode)
717 {
718 	sv_selection_add_full (sv, col, row, col, row, col, row, mode);
719 }
720 
721 /**
722  * sv_selection_free:
723  * @sv: #SheetView
724  *
725  * Releases the selection associated with @sv
726  *
727  * WARNING: This does not set a new selection and leaves the view in an
728  *		INVALID STATE.
729  **/
730 void
sv_selection_free(SheetView * sv)731 sv_selection_free (SheetView *sv)
732 {
733 	g_slist_free_full (sv->selections, g_free);
734 	sv->selections = NULL;
735 	sv->selection_mode = GNM_SELECTION_MODE_ADD;
736 }
737 
738 /**
739  * sv_selection_simplified_free:
740  * @sv: #SheetView
741  *
742  * Releases the simplified selection associated with @sv
743  *
744  **/
745 void
sv_selection_simplified_free(SheetView * sv)746 sv_selection_simplified_free (SheetView *sv)
747 {
748 	g_slist_free_full (sv->selections_simplified, g_free);
749 	sv->selections_simplified = NULL;
750 }
751 
752 /**
753  * sv_selection_reset:
754  * @sv:  The sheet view
755  *
756  * Releases the selection associated with @sv , and forces a redraw of the
757  * previously selected regions and headers.
758  *
759  * WARNING: This does not set a new selection and leaves the view in an
760  *		INVALID STATE.
761  **/
762 void
sv_selection_reset(SheetView * sv)763 sv_selection_reset (SheetView *sv)
764 {
765 	GSList *list, *tmp;
766 
767 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
768 
769 	/* Empty the sheets selection */
770 	list = sv->selections;
771 	sv->selections = NULL;
772 	sv->selection_mode = GNM_SELECTION_MODE_ADD;
773 
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);
780 	}
781 
782 	g_slist_free (list);
783 
784 	/* Make sure we re-enable the insert col/row and cell menu items */
785 	sv_menu_enable_insert (sv, TRUE, TRUE);
786 }
787 
788 /**
789  * selection_get_ranges:
790  * @sv: #SheetView
791  * @allow_intersection: Divide the selection into nonoverlapping subranges.
792  *
793  * Caller is responsible for free the list and the content.
794  * Returns: (element-type GnmRange) (transfer full):
795  **/
796 GSList *
selection_get_ranges(SheetView const * sv,gboolean allow_intersection)797 selection_get_ranges (SheetView const *sv, gboolean allow_intersection)
798 {
799 	GSList  *l;
800 	GSList *proposed = NULL;
801 
802 #undef DEBUG_SELECTION
803 #ifdef DEBUG_SELECTION
804 	g_printerr ("============================\n");
805 #endif
806 
807 	l = sv_selection_calc_simplification (sv);
808 
809 	/*
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.
813 	 */
814 	for (; l != NULL; l = l->next) {
815 		GnmRange const *r = l->data;
816 
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);
821 
822 		if (allow_intersection) {
823 			proposed = g_slist_prepend (proposed, b);
824 			continue;
825 		}
826 
827 		/* run through the proposed regions and handle any that
828 		 * overlap with the current selection region
829 		 */
830 		while (proposed != NULL) {
831 			int row_intersect, col_intersect;
832 
833 			/* pop the 1st element off the list */
834 			GnmRange *a = proposed->data;
835 			proposed = g_slist_remove (proposed, a);
836 
837 			/* The region was already subsumed completely by previous
838 			 * elements */
839 			if (b == NULL) {
840 				clear = g_slist_prepend (clear, a);
841 				continue;
842 			}
843 
844 #ifdef DEBUG_SELECTION
845 			g_printerr ("a = ");
846 			range_dump (a, "; b = ");
847 			range_dump (b, "\n");
848 #endif
849 
850 			col_intersect =
851 				segments_intersect (a->start.col, a->end.col,
852 						    b->start.col, b->end.col);
853 
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
864 
865 			/* No intersection */
866 			if (col_intersect == 0) {
867 				clear = g_slist_prepend (clear, a);
868 				continue;
869 			}
870 
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
884 
885 			/* No intersection */
886 			if (row_intersect == 0) {
887 				clear = g_slist_prepend (clear, a);
888 				continue;
889 			}
890 
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;
904 			}
905 
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 */
912 
913 					/* remove old region */
914 					g_free (b);
915 					b = NULL;
916 					break;
917 
918 				case 3 : /* overlap top */
919 					/* Shrink existing range */
920 					b->start.row = a->end.row + 1;
921 					break;
922 
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;
928 					}
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);
934 					}
935 					/* Shrink existing range */
936 					a->start.col = b->end.col + 1;
937 					break;
938 
939 				case 1 : /* overlap bottom */
940 					/* Shrink existing range */
941 					a->start.row = b->end.row + 1;
942 					break;
943 
944 				default:
945 					g_assert_not_reached ();
946 				}
947 				break;
948 
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;
955 
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);
963 					}
964 					/* fall through */
965 
966 				case 2 : /* b contains a */
967 					/* shrink the left segment */
968 					a->end.col = b->start.col - 1;
969 					break;
970 
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);
978 					}
979 
980 					/* shrink the left segment */
981 					if (b->start.col == 0) {
982 						g_free (a);
983 						a = NULL;
984 						continue;
985 					}
986 					a->end.col = b->start.col - 1;
987 					break;
988 
989 				default:
990 					g_assert_not_reached ();
991 				}
992 				break;
993 
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;
1000 
1001 				case 2 : /* b contains a */
1002 					/* remove the selection */
1003 					g_free (a);
1004 					a = NULL;
1005 					continue;
1006 
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;
1012 					}
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);
1018 					}
1019 					/* fall through */
1020 
1021 				case 1 : /* overlap bottom */
1022 					/* shrink the top segment */
1023 					a->start.row = b->end.row + 1;
1024 					break;
1025 
1026 				default:
1027 					g_assert_not_reached ();
1028 				}
1029 				break;
1030 
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;
1037 
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 */
1045 
1046 				case 2 : /* b contains a */
1047 					/* shrink the right segment */
1048 					a->start.col = b->end.col + 1;
1049 					break;
1050 
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;
1056 
1057 					/* shrink the right segment */
1058 					a->start.col = b->end.col + 1;
1059 					break;
1060 
1061 				default:
1062 					g_assert_not_reached ();
1063 				}
1064 				break;
1065 			}
1066 
1067 			/* WARNING : * Be careful putting code here.
1068 			 * Some of the cases skips this */
1069 
1070 			/* continue checking the new region for intersections */
1071 			clear = g_slist_prepend (clear, a);
1072 		}
1073 		proposed = (b != NULL) ? g_slist_prepend (clear, b) : clear;
1074 	}
1075 
1076 	return proposed;
1077 }
1078 
1079 /**
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.
1085  *
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.
1089  *
1090  */
1091 
1092 void
sv_selection_apply(SheetView * sv,SelectionApplyFunc const func,gboolean allow_intersection,void * closure)1093 sv_selection_apply (SheetView *sv, SelectionApplyFunc const func,
1094 		    gboolean allow_intersection,
1095 		    void * closure)
1096 {
1097 	GSList *l;
1098 	GSList *proposed = NULL;
1099 
1100 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
1101 
1102 	if (allow_intersection) {
1103 		for (l = sv_selection_calc_simplification (sv);
1104 		     l != NULL; l = l->next) {
1105 			GnmRange const *ss = l->data;
1106 
1107 			(*func) (sv, ss, closure);
1108 		}
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);
1115 
1116 #ifdef DEBUG_SELECTION
1117 			range_dump (r, "\n");
1118 #endif
1119 
1120 			(*func) (sv, r, closure);
1121 			g_free (r);
1122 		}
1123 	}
1124 }
1125 
1126 typedef struct {
1127 	GString *str;
1128 	gboolean include_sheet_name_prefix;
1129 } selection_to_string_closure;
1130 
1131 static void
cb_range_to_string(SheetView * sv,GnmRange const * r,void * closure)1132 cb_range_to_string (SheetView *sv, GnmRange const *r, void *closure)
1133 {
1134 	GnmConventionsOut out;
1135 	GnmRangeRef rr;
1136 	GnmParsePos pp;
1137 	selection_to_string_closure *res = closure;
1138 
1139 	if (res->str->len)
1140 		g_string_append_c (res->str, ',');
1141 
1142 	if (res->include_sheet_name_prefix)
1143 		g_string_append_printf (res->str, "%s!", sv->sheet->name_quoted);
1144 
1145 	out.accum = res->str;
1146 	out.pp = parse_pos_init_sheet (&pp, sv->sheet);
1147 	out.convs = sheet_get_conventions (sv->sheet);
1148 
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);
1152 }
1153 
1154 static void
sv_selection_apply_in_order(SheetView * sv,SelectionApplyFunc const func,void * closure)1155 sv_selection_apply_in_order (SheetView *sv, SelectionApplyFunc const func,
1156 			     void * closure)
1157 {
1158 	GSList *l, *reverse;
1159 
1160 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
1161 
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;
1166 
1167 		(*func) (sv, ss, closure);
1168 	}
1169 	g_slist_free (reverse);
1170 }
1171 
1172 
1173 char *
selection_to_string(SheetView * sv,gboolean include_sheet_name_prefix)1174 selection_to_string (SheetView *sv, gboolean include_sheet_name_prefix)
1175 {
1176 	char    *output;
1177 	selection_to_string_closure res;
1178 
1179 	res.str = g_string_new (NULL);
1180 	res.include_sheet_name_prefix = include_sheet_name_prefix;
1181 
1182 	sv_selection_apply_in_order (sv, &cb_range_to_string, &res);
1183 
1184 	output = res.str->str;
1185 	g_string_free (res.str, FALSE);
1186 	return output;
1187 }
1188 
1189 /**
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:
1194  *
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.
1198  */
1199 gboolean
sv_selection_foreach(SheetView * sv,gboolean (* range_cb)(SheetView * sv,GnmRange const * range,gpointer user_data),gpointer user_data)1200 sv_selection_foreach (SheetView *sv,
1201 			 gboolean (*range_cb) (SheetView *sv,
1202 					       GnmRange const *range,
1203 					       gpointer user_data),
1204 			 gpointer user_data)
1205 {
1206 	GSList *l;
1207 
1208 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
1209 
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;
1214 	}
1215 	return TRUE;
1216 }
1217 
1218 /* A protected sheet can limit whether locked and unlocked cells can be
1219  * selected */
1220 gboolean
sheet_selection_is_allowed(Sheet const * sheet,GnmCellPos const * pos)1221 sheet_selection_is_allowed (Sheet const *sheet, GnmCellPos const *pos)
1222 {
1223 	GnmStyle const *style;
1224 
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;
1232 }
1233 
1234 /*
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.
1242  *
1243  * Returns: %TRUE if the cursor leaves the boundary region.
1244  */
1245 static gboolean
walk_boundaries(SheetView const * sv,GnmRange const * const bound,gboolean const forward,gboolean const horizontal,gboolean const smart_merge,GnmCellPos * const res)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)
1249 {
1250 	ColRowInfo const *cri;
1251 	int const step = forward ? 1 : -1;
1252 	GnmCellPos pos = sv->edit_pos_real;
1253 	GnmRange const *merge;
1254 
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;
1288 	}
1289 
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;
1296 
1297 	if (!sheet_selection_is_allowed (sv->sheet, &pos))
1298 		goto loop;
1299 
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;
1315 			}
1316 		}
1317 	}
1318 
1319 	*res = pos;
1320 	return FALSE;
1321 }
1322 
1323 /**
1324  * sv_selection_walk_step:
1325  * @sv: #SheetView
1326  * @forward:
1327  * @horizontal:
1328  *
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).
1335  **/
1336 void
sv_selection_walk_step(SheetView * sv,gboolean forward,gboolean horizontal)1337 sv_selection_walk_step (SheetView *sv, gboolean forward, gboolean horizontal)
1338 {
1339 	int selections_count;
1340 	GnmCellPos destination;
1341 	GnmRange const *ss;
1342 	gboolean is_singleton = FALSE;
1343 	GSList *selections;
1344 
1345 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
1346 	g_return_if_fail (sv->selections != NULL);
1347 
1348 	selections = sv_selection_calc_simplification (sv);
1349 
1350 	ss = selections->data;
1351 	selections_count = g_slist_length (selections);
1352 
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;
1365 		}
1366 	}
1367 
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;
1372 
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);
1386 
1387 		/* Ignore attempts to move outside the boundary region */
1388 		if (!walk_boundaries (sv, &bound, forward, horizontal,
1389 				      FALSE, &destination)) {
1390 
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;
1394 
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;
1402 		}
1403 		return;
1404 	}
1405 
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;
1421 		}
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);
1426 	}
1427 
1428 	gnm_sheet_view_set_edit_pos (sv, &destination);
1429 	gnm_sheet_view_make_cell_visible (sv, destination.col, destination.row, FALSE);
1430 }
1431 
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
characterize_vec(Sheet * sheet,GnmRange * vector,gboolean as_cols,gboolean expand_text)1435 characterize_vec (Sheet *sheet, GnmRange *vector,
1436 		  gboolean as_cols, gboolean expand_text)
1437 {
1438 	GnmCell *cell;
1439 	GnmValue const *v;
1440 	GnmRange tmp;
1441 	int dx = 0, dy = 0;
1442 	gboolean is_string = FALSE;
1443 
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;
1452 
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;
1468 			}
1469 		} else
1470 			return is_string;
1471 	}
1472 
1473 	return is_string; /* NOTREACHED */
1474 }
1475 
1476 void
sv_selection_to_plot(SheetView * sv,GogPlot * go_plot)1477 sv_selection_to_plot (SheetView *sv, GogPlot *go_plot)
1478 {
1479 	GSList *ptr, *sels, *selections;
1480 	GnmRange const *r;
1481 	int num_cols, num_rows;
1482 
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;
1494 
1495 	gboolean default_to_cols;
1496 
1497 	selections = sv_selection_calc_simplification (sv);
1498 
1499 	/* Use the total number of cols vs rows in all of the selected regions.
1500 	 * We cannot 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);
1507 	}
1508 
1509 	/* Excel docs claim that rows == cols uses rows */
1510 	default_to_cols = (!data || data->colrowmode == 0)? (num_cols < num_rows): data->colrowmode == 1;
1511 
1512 	desc = gog_plot_description (plot);
1513 	series = gog_plot_new_series (plot);
1514 
1515 	header.sheet = sheet;
1516 	header.col_relative = header.row_relative = FALSE;
1517 
1518 
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 ++;
1545 			}
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 ++;
1556 			}
1557 		}
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;
1566 	}
1567 
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);
1576 	}
1577 	for (ptr = sels; ptr != NULL; ptr = ptr->next) {
1578 		GnmRange vector = *((GnmRange const *)ptr->data);
1579 
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;
1585 
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;
1596 		}
1597 
1598 		for (i = 0 ; i <= count ; ) {
1599 			if (cur_dim >= desc->series.num_dim) {
1600 				if (num_series >= desc->num_series_max)
1601 					break;
1602 
1603 				series = gog_plot_new_series (plot);
1604 				first_series = FALSE;
1605 				first_value_dim = TRUE;
1606 				cur_dim = 0;
1607 				num_series++;
1608 			}
1609 
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;
1614 
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);
1621 				}
1622 				++cur_dim;
1623 			}
1624 
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;
1629 
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++;
1639 			}
1640 
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);
1651 
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);
1658 			}
1659 
1660 			cur_dim++;
1661 /*skip :*/
1662 
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;
1669 			}
1670 		}
1671 	}
1672 
1673 	g_slist_free (sels);
1674 
1675 #warning TODO If last series is incomplete try to shift data out of optional dimensions.
1676 }
1677