1 /*
2  * sheet-view.c:
3  *
4  * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
24 #include <stdlib.h>
25 #include <gnumeric.h>
26 
27 #include <sheet-view.h>
28 #include <sheet.h>
29 #include <sheet-merge.h>
30 #include <sheet-filter.h>
31 #include <gnm-sheet-slicer.h>
32 #include <sheet-private.h>
33 #include <sheet-control.h>
34 #include <sheet-control-priv.h>
35 #include <workbook-view.h>
36 #include <workbook-control.h>
37 #include <ranges.h>
38 #include <selection.h>
39 #include <application.h>
40 #include <value.h>
41 #include <parse-util.h>
42 #include <expr-name.h>
43 #include <command-context.h>
44 #include <gnumeric-conf.h>
45 #include <sheet-style.h>
46 #include <mstyle.h>
47 #include <gutils.h>
48 
49 #include <gsf/gsf-impl-utils.h>
50 
51 #define GNM_SHEET_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GNM_SHEET_VIEW_TYPE, SheetViewClass))
52 static GObjectClass *parent_class;
53 
54 /*************************************************************************/
55 
56 static void
auto_expr_timer_clear(SheetView * sv)57 auto_expr_timer_clear (SheetView *sv)
58 {
59 	if (sv->auto_expr_timer != 0) {
60 		g_source_remove (sv->auto_expr_timer);
61 		sv->auto_expr_timer = 0;
62 	}
63 }
64 
65 static gboolean
cb_update_auto_expr(gpointer data)66 cb_update_auto_expr (gpointer data)
67 {
68 	SheetView *sv = (SheetView *) data;
69 
70 	if (wb_view_cur_sheet_view (sv->sv_wbv) == sv)
71 		wb_view_auto_expr_recalc (sv->sv_wbv);
72 
73 	sv->auto_expr_timer = 0;
74 	return FALSE;
75 }
76 
77 /*************************************************************************/
78 
79 static void
sv_sheet_name_changed(G_GNUC_UNUSED Sheet * sheet,G_GNUC_UNUSED GParamSpec * pspec,SheetView * sv)80 sv_sheet_name_changed (G_GNUC_UNUSED Sheet *sheet,
81 		       G_GNUC_UNUSED GParamSpec *pspec,
82 		       SheetView *sv)
83 {
84 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
85 	sv->edit_pos_changed.content = TRUE;
86 }
87 
88 static void
sv_sheet_visibility_changed(Sheet * sheet,G_GNUC_UNUSED GParamSpec * pspec,SheetView * sv)89 sv_sheet_visibility_changed (Sheet *sheet,
90 			     G_GNUC_UNUSED GParamSpec *pspec,
91 			     SheetView *sv)
92 {
93 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
94 	/* See bug 366477.  */
95 	if (sheet_is_visible (sheet) && !wb_view_cur_sheet (sv->sv_wbv))
96 		wb_view_sheet_focus (sv->sv_wbv, sheet);
97 }
98 
99 static void
sv_sheet_r1c1_changed(G_GNUC_UNUSED Sheet * sheet,G_GNUC_UNUSED GParamSpec * pspec,SheetView * sv)100 sv_sheet_r1c1_changed (G_GNUC_UNUSED Sheet *sheet,
101 		       G_GNUC_UNUSED GParamSpec *pspec,
102 		       SheetView *sv)
103 {
104 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
105 	sv->edit_pos_changed.location = TRUE;
106 }
107 
108 /**
109  * sv_sheet:
110  * @sv: #SheetView
111  *
112  * Returns: (transfer none): the sheet.
113  **/
114 Sheet *
sv_sheet(SheetView const * sv)115 sv_sheet (SheetView const *sv)
116 {
117 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
118 	return sv->sheet;
119 }
120 
121 /**
122  * sv_wbv:
123  * @sv: #SheetView
124  *
125  * Returns: (transfer none): the workbook view.
126  **/
127 WorkbookView *
sv_wbv(SheetView const * sv)128 sv_wbv (SheetView const *sv)
129 {
130 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
131 	return sv->sv_wbv;
132 }
133 
134 static void
sv_init_sc(SheetView const * sv,SheetControl * sc)135 sv_init_sc (SheetView const *sv, SheetControl *sc)
136 {
137 	GnmCellPos initial;
138 
139 	sc_scale_changed (sc);
140 
141 	/* set_panes will change the initial so cache it */
142 	initial = sv->initial_top_left;
143 	sc_set_panes (sc);
144 
145 	/* And this will restore it */
146 	sc_set_top_left (sc, initial.col, initial.row);
147 	sc_scrollbar_config (sc);
148 
149 	/* Set the visible bound, not the logical bound */
150 	sc_cursor_bound (sc, selection_first_range (sv, NULL, NULL));
151 	sc_ant (sc);
152 }
153 
154 void
gnm_sheet_view_attach_control(SheetView * sv,SheetControl * sc)155 gnm_sheet_view_attach_control (SheetView *sv, SheetControl *sc)
156 {
157 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
158 	g_return_if_fail (GNM_IS_SHEET_CONTROL (sc));
159 	g_return_if_fail (sc->view == NULL);
160 
161 	g_ptr_array_add (sv->controls, sc);
162 	sc->view  = sv;
163 	sv_init_sc (sv, sc);
164 }
165 
166 void
gnm_sheet_view_detach_control(SheetView * sv,SheetControl * sc)167 gnm_sheet_view_detach_control (SheetView *sv, SheetControl *sc)
168 {
169 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
170 	g_return_if_fail (GNM_IS_SHEET_CONTROL (sc));
171 	g_return_if_fail (sv == sc->view);
172 
173 	g_ptr_array_remove (sv->controls, sc);
174 	sc->view = NULL;
175 }
176 
177 static void
sv_weakref_notify(SheetView ** ptr,GObject * sv)178 sv_weakref_notify (SheetView **ptr, GObject *sv)
179 {
180 	g_return_if_fail (ptr != NULL);
181 	g_return_if_fail (*ptr == (SheetView *)sv); /* remember sv is dead */
182 	*ptr = NULL;
183 }
184 
185 void
gnm_sheet_view_weak_ref(SheetView * sv,SheetView ** ptr)186 gnm_sheet_view_weak_ref (SheetView *sv, SheetView **ptr)
187 {
188 	g_return_if_fail (ptr != NULL);
189 
190 	*ptr = sv;
191 	if (sv != NULL)
192 		g_object_weak_ref (G_OBJECT (sv),
193 			(GWeakNotify) sv_weakref_notify,
194 			ptr);
195 }
196 
197 void
gnm_sheet_view_weak_unref(SheetView ** ptr)198 gnm_sheet_view_weak_unref (SheetView **ptr)
199 {
200 	g_return_if_fail (ptr != NULL);
201 
202 	if (*ptr != NULL) {
203 		g_object_weak_unref (G_OBJECT (*ptr),
204 			(GWeakNotify) sv_weakref_notify,
205 			ptr);
206 		*ptr = NULL;
207 	}
208 }
209 
210 static void
sv_finalize(GObject * object)211 sv_finalize (GObject *object)
212 {
213 	SheetView *sv = GNM_SHEET_VIEW (object);
214 	g_ptr_array_free (sv->controls, TRUE);
215 	parent_class->finalize (object);
216 }
217 
218 static void
sv_real_dispose(GObject * object)219 sv_real_dispose (GObject *object)
220 {
221 	SheetView *sv = GNM_SHEET_VIEW (object);
222 
223 	while (sv->controls->len > 0) {
224 		SheetControl *control =
225 			g_ptr_array_index (sv->controls,
226 					   sv->controls->len - 1);
227 		gnm_sheet_view_detach_control (sv, control);
228 		g_object_unref (control);
229 	}
230 
231 	if (sv->sheet) {
232 		Sheet *sheet = sv->sheet;
233 		sv->sheet = NULL;
234 		g_ptr_array_remove (sheet->sheet_views, sv);
235 		g_signal_handlers_disconnect_by_func (sheet, sv_sheet_name_changed, sv);
236 		g_signal_handlers_disconnect_by_func (sheet, sv_sheet_visibility_changed, sv);
237 		g_signal_handlers_disconnect_by_func (sheet, sv_sheet_r1c1_changed, sv);
238 		g_object_unref (sv);
239 		g_object_unref (sheet);
240 	}
241 
242 	gnm_sheet_view_unant (sv);
243 	sv_selection_free (sv);
244 	sv_selection_simplified_free (sv);
245 	auto_expr_timer_clear (sv);
246 
247 	parent_class->dispose (object);
248 }
249 
250 static void
gnm_sheet_view_class_init(GObjectClass * klass)251 gnm_sheet_view_class_init (GObjectClass *klass)
252 {
253 	SheetViewClass *wbc_class = GNM_SHEET_VIEW_CLASS (klass);
254 
255 	g_return_if_fail (wbc_class != NULL);
256 
257 	parent_class = g_type_class_peek_parent (klass);
258 	klass->dispose = sv_real_dispose;
259 	klass->finalize = sv_finalize;
260 }
261 
262 static void
gnm_sheet_view_init(GObject * object)263 gnm_sheet_view_init (GObject *object)
264 {
265 	SheetView *sv = GNM_SHEET_VIEW (object);
266 
267 	sv->controls = g_ptr_array_new ();
268 
269 	/* Init menu states */
270 	sv->enable_insert_rows = TRUE;
271 	sv->enable_insert_cols = TRUE;
272 	sv->enable_insert_cells = TRUE;
273 
274 	sv->edit_pos_changed.location = TRUE;
275 	sv->edit_pos_changed.content  = TRUE;
276 	sv->edit_pos_changed.style    = TRUE;
277 	sv->selection_content_changed = TRUE;
278 	sv->reposition_selection = TRUE;
279 	sv->auto_expr_timer = 0;
280 
281 	sv->frozen_top_left.col = sv->frozen_top_left.row =
282 	sv->unfrozen_top_left.col = sv->unfrozen_top_left.row = -1;
283 	sv->initial_top_left.col = sv->initial_top_left.row = 0;
284 
285 	sv->selections = NULL;
286 	sv->selection_mode = GNM_SELECTION_MODE_ADD;
287 	sv->selections_simplified = NULL;
288 	sv_selection_add_pos (sv, 0, 0, GNM_SELECTION_MODE_ADD);
289 }
290 
GSF_CLASS(SheetView,gnm_sheet_view,gnm_sheet_view_class_init,gnm_sheet_view_init,G_TYPE_OBJECT)291 GSF_CLASS (SheetView, gnm_sheet_view,
292 	   gnm_sheet_view_class_init, gnm_sheet_view_init,
293 	   G_TYPE_OBJECT)
294 
295 SheetView *
296 gnm_sheet_view_new (Sheet *sheet, WorkbookView *wbv)
297 {
298 	SheetView *sv;
299 
300 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
301 
302 	sv = g_object_new (GNM_SHEET_VIEW_TYPE, NULL);
303 	sv->sheet = g_object_ref (sheet);
304 	sv->sv_wbv = wbv;
305 	g_ptr_array_add (sheet->sheet_views, sv);
306 	g_object_ref (sv);
307 
308 	g_signal_connect (G_OBJECT (sheet),
309 			  "notify::name",
310 			  G_CALLBACK (sv_sheet_name_changed),
311 			  sv);
312 
313 	g_signal_connect (G_OBJECT (sheet),
314 			  "notify::visibility",
315 			  G_CALLBACK (sv_sheet_visibility_changed),
316 			  sv);
317 
318 	g_signal_connect (G_OBJECT (sheet),
319 			  "notify::use-r1c1",
320 			  G_CALLBACK (sv_sheet_r1c1_changed),
321 			  sv);
322 
323 	SHEET_VIEW_FOREACH_CONTROL (sv, control,
324 		sv_init_sc (sv, control););
325 	return sv;
326 }
327 
328 void
gnm_sheet_view_dispose(SheetView * sv)329 gnm_sheet_view_dispose (SheetView *sv)
330 {
331 	g_object_run_dispose (G_OBJECT (sv));
332 }
333 
334 void
gnm_sheet_view_unant(SheetView * sv)335 gnm_sheet_view_unant (SheetView *sv)
336 {
337 	GList *ptr;
338 
339 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
340 
341 	if (sv->ants == NULL)
342 		return;
343 	for (ptr = sv->ants; ptr != NULL; ptr = ptr->next)
344 		g_free (ptr->data);
345 	g_list_free (sv->ants);
346 	sv->ants = NULL;
347 
348 	SHEET_VIEW_FOREACH_CONTROL (sv, control,
349 		sc_unant (control););
350 }
351 
352 /**
353  * gnm_sheet_view_ant:
354  * @sv:
355  * @ranges: (element-type GnmRange) (transfer none): The ranges to ant.
356  */
357 void
gnm_sheet_view_ant(SheetView * sv,GList * ranges)358 gnm_sheet_view_ant (SheetView *sv, GList *ranges)
359 {
360 	GList *ptr;
361 
362 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
363 	g_return_if_fail (ranges != NULL);
364 
365 	if (sv->ants != NULL)
366 		gnm_sheet_view_unant (sv);
367 	for (ptr = ranges; ptr != NULL; ptr = ptr->next)
368 		sv->ants = g_list_prepend (sv->ants, gnm_range_dup (ptr->data));
369 	sv->ants = g_list_reverse (sv->ants);
370 
371 	SHEET_VIEW_FOREACH_CONTROL (sv, control,
372 		sc_ant (control););
373 }
374 
375 void
gnm_sheet_view_make_cell_visible(SheetView * sv,int col,int row,gboolean couple_panes)376 gnm_sheet_view_make_cell_visible (SheetView *sv, int col, int row,
377 		      gboolean couple_panes)
378 {
379 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
380 	SHEET_VIEW_FOREACH_CONTROL(sv, control,
381 		sc_make_cell_visible (control, col, row, couple_panes););
382 }
383 
384 void
gnm_sheet_view_redraw_range(SheetView * sv,GnmRange const * r)385 gnm_sheet_view_redraw_range	(SheetView *sv, GnmRange const *r)
386 {
387 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
388 
389 	SHEET_VIEW_FOREACH_CONTROL (sv, sc, sc_redraw_range (sc, r););
390 }
391 
392 void
gnm_sheet_view_redraw_headers(SheetView const * sv,gboolean col,gboolean row,GnmRange const * r)393 gnm_sheet_view_redraw_headers (SheetView const *sv,
394 		   gboolean col, gboolean row,
395 		   GnmRange const* r /* optional == NULL */)
396 {
397 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
398 
399 	SHEET_VIEW_FOREACH_CONTROL (sv, control,
400 		sc_redraw_headers (control, col, row, r););
401 }
402 
403 void
gnm_sheet_view_resize(SheetView * sv,gboolean force_scroll)404 gnm_sheet_view_resize (SheetView *sv, gboolean force_scroll)
405 {
406 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
407 
408 	SHEET_VIEW_FOREACH_CONTROL (sv, control,
409 		sc_resize (control, force_scroll););
410 }
411 
412 
413 gboolean
gnm_sheet_view_selection_copy(SheetView * sv,WorkbookControl * wbc)414 gnm_sheet_view_selection_copy (SheetView *sv, WorkbookControl *wbc)
415 {
416 	GnmRange const *sel;
417 
418 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
419 	if (!(sel = selection_first_range (sv, GO_CMD_CONTEXT (wbc), _("Copy"))))
420 		return FALSE;
421 
422 	gnm_app_clipboard_cut_copy (wbc, FALSE, sv, sel, TRUE);
423 
424 	return TRUE;
425 }
426 
427 gboolean
gnm_sheet_view_selection_cut(SheetView * sv,WorkbookControl * wbc)428 gnm_sheet_view_selection_cut (SheetView *sv, WorkbookControl *wbc)
429 {
430 	GnmRange const *sel;
431 
432 	/* 'cut' is a poor description of what we're
433 	 * doing here.  'move' would be a better
434 	 * approximation.  The key portion of this process is that
435 	 * the range being moved has all
436 	 *	- references to it adjusted to the new site.
437 	 *	- relative references from it adjusted.
438 	 *
439 	 * NOTE : This command DOES NOT MOVE ANYTHING !
440 	 *        We only store the src, paste does the move.
441 	 */
442 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
443 
444 	if (!(sel = selection_first_range (sv, GO_CMD_CONTEXT (wbc), _("Cut"))))
445 		return FALSE;
446 
447 	if (sheet_range_splits_region (sv_sheet (sv), sel, NULL, GO_CMD_CONTEXT (wbc), _("Cut")))
448 		return FALSE;
449 
450 	gnm_app_clipboard_cut_copy (wbc, TRUE, sv, sel, TRUE);
451 
452 	return TRUE;
453 }
454 
455 /**
456  * gnm_sheet_view_cursor_set:
457  * @sv: The sheet
458  * @edit:
459  * @base_col:
460  * @base_row:
461  * @move_col:
462  * @move_row:
463  * @bound: (nullable): A range that should contain all the supplied points
464  **/
465 void
gnm_sheet_view_cursor_set(SheetView * sv,GnmCellPos const * edit,int base_col,int base_row,int move_col,int move_row,GnmRange const * bound)466 gnm_sheet_view_cursor_set (SheetView *sv,
467 	       GnmCellPos const *edit,
468 	       int base_col, int base_row,
469 	       int move_col, int move_row,
470 	       GnmRange const *bound)
471 {
472 	GnmRange r;
473 
474 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
475 
476 	/* Change the edit position */
477 	gnm_sheet_view_set_edit_pos (sv, edit);
478 
479 	sv->cursor.base_corner.col = base_col;
480 	sv->cursor.base_corner.row = base_row;
481 	sv->cursor.move_corner.col = move_col;
482 	sv->cursor.move_corner.row = move_row;
483 
484 	if (bound == NULL) {
485 		if (base_col < move_col) {
486 			r.start.col =  base_col;
487 			r.end.col =  move_col;
488 		} else {
489 			r.end.col =  base_col;
490 			r.start.col =  move_col;
491 		}
492 		if (base_row < move_row) {
493 			r.start.row =  base_row;
494 			r.end.row =  move_row;
495 		} else {
496 			r.end.row =  base_row;
497 			r.start.row =  move_row;
498 		}
499 		bound = &r;
500 	}
501 
502 	g_return_if_fail (range_is_sane	(bound));
503 
504 	SHEET_VIEW_FOREACH_CONTROL(sv, control,
505 		sc_cursor_bound (control, bound););
506 }
507 
508 void
gnm_sheet_view_set_edit_pos(SheetView * sv,GnmCellPos const * pos)509 gnm_sheet_view_set_edit_pos (SheetView *sv, GnmCellPos const *pos)
510 {
511 	GnmCellPos old;
512 	GnmRange const *merged;
513 
514 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
515 	g_return_if_fail (pos != NULL);
516 
517 	old = sv->edit_pos;
518 	sv->first_tab_col = -1; /* invalidate */
519 
520 	if (old.col == pos->col && old.row == pos->row)
521 		return;
522 
523 	g_return_if_fail (IS_SHEET (sv->sheet));
524 	g_return_if_fail (pos->col >= 0);
525 	g_return_if_fail (pos->col < gnm_sheet_get_max_cols (sv->sheet));
526 	g_return_if_fail (pos->row >= 0);
527 	g_return_if_fail (pos->row < gnm_sheet_get_max_rows (sv->sheet));
528 
529 
530 	merged = gnm_sheet_merge_is_corner (sv->sheet, &old);
531 
532 	sv->edit_pos_changed.location =
533 		sv->edit_pos_changed.content =
534 		sv->edit_pos_changed.style  = TRUE;
535 
536 	/* Redraw before change */
537 	if (merged == NULL) {
538 		GnmRange tmp; tmp.start = tmp.end = old;
539 		gnm_sheet_view_redraw_range (sv, &tmp);
540 	} else
541 		gnm_sheet_view_redraw_range (sv, merged);
542 
543 	sv->edit_pos_real = *pos;
544 
545 	/* Redraw after change (handling merged cells) */
546 	merged = gnm_sheet_merge_contains_pos (sv->sheet, &sv->edit_pos_real);
547 	if (merged == NULL) {
548 		GnmRange tmp; tmp.start = tmp.end = *pos;
549 		gnm_sheet_view_redraw_range (sv, &tmp);
550 		sv->edit_pos = sv->edit_pos_real;
551 	} else {
552 		gnm_sheet_view_redraw_range (sv, merged);
553 		sv->edit_pos = merged->start;
554 	}
555 }
556 
557 /**
558  * gnm_sheet_view_flag_status_update_pos:
559  * @sv:
560  * @pos:
561  *
562  *    flag the view as requiring an update to the status display
563  *    if the supplied cell location is the edit cursor, or part of the
564  *    selected region.
565  *
566  * Will cause the format toolbar, the edit area, and the auto expressions to be
567  * updated if appropriate.
568  */
569 void
gnm_sheet_view_flag_status_update_pos(SheetView * sv,GnmCellPos const * pos)570 gnm_sheet_view_flag_status_update_pos (SheetView *sv, GnmCellPos const *pos)
571 {
572 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
573 	g_return_if_fail (pos != NULL);
574 
575 	/* if a part of the selected region changed value update
576 	 * the auto expressions
577 	 */
578 	if (sv_is_pos_selected (sv, pos->col, pos->row))
579 		sv->selection_content_changed = TRUE;
580 
581 	/* If the edit cell changes value update the edit area
582 	 * and the format toolbar
583 	 */
584 	if (pos->col == sv->edit_pos.col && pos->row == sv->edit_pos.row)
585 		sv->edit_pos_changed.content =
586 		sv->edit_pos_changed.style  = TRUE;
587 }
588 
589 /**
590  * gnm_sheet_view_flag_status_update_range:
591  * @sv:
592  * @range: (nullable): If %NULL then force an update.
593  *
594  * flag the sheet as requiring an update to the status display if the supplied
595  * cell location contains the edit cursor, or intersects of the selected region.
596  *
597  * Will cause the format toolbar, the edit area, and the auto expressions to be
598  * updated if appropriate.
599  */
600 void
gnm_sheet_view_flag_status_update_range(SheetView * sv,GnmRange const * range)601 gnm_sheet_view_flag_status_update_range (SheetView *sv, GnmRange const *range)
602 {
603 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
604 
605 	/* Force an update */
606 	if (range == NULL) {
607 		sv->selection_content_changed = TRUE;
608 		sv->edit_pos_changed.location =
609 		sv->edit_pos_changed.content =
610 		sv->edit_pos_changed.style  = TRUE;
611 		return;
612 	}
613 
614 	/* if a part of the selected region changed value update
615 	 * the auto expressions
616 	 */
617 	if (sv_is_range_selected (sv, range))
618 		sv->selection_content_changed = TRUE;
619 
620 	/* If the edit cell changes value update the edit area
621 	 * and the format toolbar
622 	 */
623 	if (range_contains (range, sv->edit_pos.col, sv->edit_pos.row))
624 		sv->edit_pos_changed.content = sv->edit_pos_changed.style  = TRUE;
625 }
626 
627 /**
628  * gnm_sheet_view_flag_style_update_range:
629  * @sv: The sheet being changed
630  * @range: the range that is changing.
631  *
632  * Flag style changes that will require updating the style  indicators.
633  */
634 void
gnm_sheet_view_flag_style_update_range(SheetView * sv,GnmRange const * range)635 gnm_sheet_view_flag_style_update_range (SheetView *sv, GnmRange const *range)
636 {
637 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
638 	g_return_if_fail (range != NULL);
639 	if (range_contains (range, sv->edit_pos.col, sv->edit_pos.row))
640 		sv->edit_pos_changed.style  = TRUE;
641 }
642 
643 /**
644  * gnm_sheet_view_flag_selection_change:
645  * @sv:
646  *
647  * flag the sheet as requiring an update to the status display
648  *
649  * Will cause auto expressions to be updated
650  */
651 void
gnm_sheet_view_flag_selection_change(SheetView * sv)652 gnm_sheet_view_flag_selection_change (SheetView *sv)
653 {
654 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
655 	sv->selection_content_changed = TRUE;
656 }
657 
658 static void
sheet_view_edit_pos_tool_tips(SheetView * sv)659 sheet_view_edit_pos_tool_tips (SheetView *sv)
660 {
661 	GnmStyle const *style;
662 	GnmInputMsg	*im = NULL;
663 
664 	style = sheet_style_get (sv->sheet,
665 				 sv->edit_pos.col,
666 				 sv->edit_pos.row);
667 	if (style != NULL && gnm_style_is_element_set (style, MSTYLE_INPUT_MSG))
668 		im = gnm_style_get_input_msg (style);
669 
670 	/* We need to call these even with im == NULL to remove the old tooltip.*/
671 	SHEET_VIEW_FOREACH_CONTROL (sv, control,
672 				    sc_show_im_tooltip (control, im, &sv->edit_pos););
673 }
674 
675 void
gnm_sheet_view_update(SheetView * sv)676 gnm_sheet_view_update (SheetView *sv)
677 {
678 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
679 
680 	if (sv->edit_pos_changed.content) {
681 		sv->edit_pos_changed.content = FALSE;
682 		if (wb_view_cur_sheet_view (sv->sv_wbv) == sv)
683 			wb_view_edit_line_set (sv->sv_wbv, NULL);
684 	}
685 
686 	if (sv->edit_pos_changed.style ) {
687 		sv->edit_pos_changed.style  = FALSE;
688 		if (wb_view_cur_sheet_view (sv->sv_wbv) == sv)
689 			wb_view_style_feedback (sv->sv_wbv);
690 	}
691 
692 	if (sv->edit_pos_changed.location) {
693 		sv->edit_pos_changed.location = FALSE;
694 		if (wb_view_cur_sheet_view (sv->sv_wbv) == sv) {
695 			wb_view_selection_desc (sv->sv_wbv, TRUE, NULL);
696 			SHEET_VIEW_FOREACH_CONTROL
697 				(sv, sc, wb_control_menu_state_update
698 				 (sc_wbc (sc),
699 				  MS_COMMENT_LINKS | MS_PAGE_BREAKS););
700 			sheet_view_edit_pos_tool_tips (sv);
701 		}
702 	}
703 
704 	if (sv->selection_content_changed) {
705 		int const lag = gnm_conf_get_core_gui_editing_recalclag ();
706 		sv->selection_content_changed = FALSE;
707 		if (sv->auto_expr_timer == 0 || lag < 0) {
708 			auto_expr_timer_clear (sv);
709 			sv->auto_expr_timer = g_timeout_add_full (0, abs (lag), /* seems ok */
710 				cb_update_auto_expr, (gpointer) sv, NULL);
711 		}
712 		SHEET_VIEW_FOREACH_CONTROL (sv, sc,
713 			wb_control_menu_state_update (sc_wbc (sc), MS_ADD_VS_REMOVE_FILTER |
714 						      MS_COMMENT_LINKS_RANGE););
715 	}
716 
717 	SHEET_VIEW_FOREACH_CONTROL (sv, sc,
718 				    wb_control_menu_state_update
719 				    (sc_wbc (sc), MS_SELECT_OBJECT););
720 
721 }
722 
723 /**
724  * gnm_sheet_view_editpos_in_filter:
725  * @sv: #SheetView
726  *
727  * Returns: (nullable): #GnmFilter that overlaps the sv::edit_pos
728  **/
729 GnmFilter *
gnm_sheet_view_editpos_in_filter(SheetView const * sv)730 gnm_sheet_view_editpos_in_filter (SheetView const *sv)
731 {
732 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
733 	return gnm_sheet_filter_at_pos (sv->sheet, &sv->edit_pos);
734 }
735 
736 /**
737  * gnm_sheet_view_selection_intersects_filter_rows:
738  * @sv: #SheetView
739  *
740  * Returns: (nullable): #GnmFilter whose rows intersect the rows
741  *          of the current selection.
742  **/
743 GnmFilter *
gnm_sheet_view_selection_intersects_filter_rows(SheetView const * sv)744 gnm_sheet_view_selection_intersects_filter_rows (SheetView const *sv)
745 {
746 	GnmRange const *r;
747 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
748 	r = selection_first_range (sv, NULL, NULL);
749 
750 	return r ? gnm_sheet_filter_intersect_rows
751 	  (sv->sheet, r->start.row, r->end.row) : NULL;
752 }
753 
754 /**
755  * gnm_sheet_view_selection_extends_filter:
756  * @sv: #SheetView
757  *
758  * Returns: (nullable): #GnmFilter whose rows intersect the rows
759  *          of the current selection range to which the filter can be
760  *          extended.
761  **/
762 GnmRange *
gnm_sheet_view_selection_extends_filter(SheetView const * sv,GnmFilter const * f)763 gnm_sheet_view_selection_extends_filter (SheetView const *sv,
764 					 GnmFilter const *f)
765 {
766 	GnmRange const *r;
767 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
768 	r = selection_first_range (sv, NULL, NULL);
769 
770 	return gnm_sheet_filter_can_be_extended (sv->sheet, f, r);
771 }
772 
773 
774 
775 
776 /**
777  * gnm_sheet_view_editpos_in_slicer:
778  * @sv: #SheetView
779  *
780  * Returns: (transfer none) (nullable): #GnmSheetSlicer that overlaps the
781  * sv::edit_pos
782  **/
783 GnmSheetSlicer *
gnm_sheet_view_editpos_in_slicer(SheetView const * sv)784 gnm_sheet_view_editpos_in_slicer (SheetView const *sv)
785 {
786 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
787 	return gnm_sheet_slicers_at_pos (sv->sheet, &sv->edit_pos);
788 }
789 
790 /**
791  * gnm_sheet_view_freeze_panes:
792  * @sv: the sheet
793  * @frozen_top_left: (nullable): top left corner of the frozen region
794  * @unfrozen_top_left: (nullable): top left corner of the unfrozen region
795  *
796  * By definition the unfrozen region must be below the frozen.
797  * If @frozen_top_left == @unfrozen_top_left or @frozen_top_left == NULL unfreeze
798  **/
799 void
gnm_sheet_view_freeze_panes(SheetView * sv,GnmCellPos const * frozen,GnmCellPos const * unfrozen)800 gnm_sheet_view_freeze_panes (SheetView *sv,
801 			     GnmCellPos const *frozen,
802 			     GnmCellPos const *unfrozen)
803 {
804 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
805 
806 	if (gnm_debug_flag ("frozen-panes")) {
807 		g_printerr ("Frozen: %-10s",
808 			    frozen ? cellpos_as_string (frozen) : "-");
809 		g_printerr ("Unfrozen: %s\n",
810 			    unfrozen ? cellpos_as_string (unfrozen) : "-");
811 	}
812 
813 	if (frozen != NULL) {
814 		g_return_if_fail (unfrozen != NULL);
815 		g_return_if_fail (unfrozen->col >= frozen->col);
816 		g_return_if_fail (unfrozen->row >= frozen->row);
817 
818 		/* Just in case */
819 		if (unfrozen->col != gnm_sheet_get_last_col (sv->sheet) &&
820 		    unfrozen->row != gnm_sheet_get_last_row (sv->sheet) &&
821 		    !gnm_cellpos_equal (frozen, unfrozen)) {
822 			sv->frozen_top_left = *frozen;
823 			sv->unfrozen_top_left = *unfrozen;
824 			if (sv->frozen_top_left.col == sv->unfrozen_top_left.col)
825 				sv->frozen_top_left.col = sv->unfrozen_top_left.col = 0;
826 			if (sv->frozen_top_left.row == sv->unfrozen_top_left.row)
827 				sv->frozen_top_left.row = sv->unfrozen_top_left.row = 0;
828 		} else
829 			frozen = unfrozen = NULL;
830 	}
831 
832 	if (frozen == NULL) {
833 		g_return_if_fail (unfrozen == NULL);
834 
835 		/* no change */
836 		if (sv->frozen_top_left.col < 0 &&
837 		    sv->frozen_top_left.row < 0 &&
838 		    sv->unfrozen_top_left.col < 0 &&
839 		    sv->unfrozen_top_left.row < 0)
840 			return;
841 
842 		sv->initial_top_left = sv->frozen_top_left;
843 		sv->frozen_top_left.col = sv->frozen_top_left.row =
844 		sv->unfrozen_top_left.col = sv->unfrozen_top_left.row = -1;
845 	}
846 
847 	SHEET_VIEW_FOREACH_CONTROL (sv, control,
848 		sv_init_sc (sv, control););
849 
850 	WORKBOOK_VIEW_FOREACH_CONTROL(sv->sv_wbv, wbc,
851 		wb_control_menu_state_update (wbc, MS_FREEZE_VS_THAW););
852 }
853 
854 /**
855  * gnm_sheet_view_panes_insdel_colrow:
856  * @sv:
857  * @is_cols: %TRUE for columns, %FALSE for rows.
858  * @is_insert:
859  * @start:
860  * @count:
861  *
862  * Adjust the positions of frozen panes as necessary to handle col/row
863  * insertions and deletions.  note this assumes that the ins/del operations
864  * have already set the flags that will force a resize.
865  **/
866 void
gnm_sheet_view_panes_insdel_colrow(SheetView * sv,gboolean is_cols,gboolean is_insert,int start,int count)867 gnm_sheet_view_panes_insdel_colrow (SheetView *sv, gboolean is_cols,
868 				    gboolean is_insert, int start, int count)
869 {
870 	GnmCellPos tl;
871 	GnmCellPos br;
872 
873 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
874 
875 	tl = sv->frozen_top_left;	/* _copy_ them */
876 	br = sv->unfrozen_top_left;
877 
878 	if (is_cols) {
879 		/* ignore if not frozen, or acting in unfrozen region */
880 		if (br.col <= tl.col || br.col <= start)
881 			return;
882 		if (is_insert) {
883 			br.col += count;
884 			if (tl.col > start)
885 				tl.col += count;
886 			if (br.col < tl.col || br.col >= gnm_sheet_get_max_cols (sv->sheet))
887 				return;
888 		} else {
889 			if (tl.col >= start)
890 				tl.col -= MIN (count, tl.col - start);
891 			br.col -= count;
892 			if (br.col <= tl.col)
893 				br.col = tl.col + 1;
894 		}
895 	} else {
896 		/* ignore if not frozen, or acting in unfrozen region */
897 		if (br.row <= tl.row || br.row <= start)
898 			return;
899 		if (is_insert) {
900 			br.row += count;
901 			if (tl.row > start)
902 				tl.row += count;
903 			if (br.row < tl.row || br.row >= gnm_sheet_get_max_rows (sv->sheet))
904 				return;
905 		} else {
906 			if (tl.row >= start)
907 				tl.row -= MIN (count, tl.row - start);
908 			br.row -= count;
909 			if (br.row <= tl.row)
910 				br.row = tl.row + 1;
911 		}
912 	}
913 	gnm_sheet_view_freeze_panes (sv, &tl, &br);
914 }
915 
916 gboolean
gnm_sheet_view_is_frozen(SheetView const * sv)917 gnm_sheet_view_is_frozen (SheetView const *sv)
918 {
919 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), FALSE);
920 
921 	/* be flexible, in the future we will support 2 way splits too */
922 	return  sv->unfrozen_top_left.col >= 0 ||
923 		sv->unfrozen_top_left.row >= 0;
924 }
925 
926 /**
927   gnm_sheet_view_set_initial_top_left:
928  * @sv: the sheet view.
929  * @col:
930  * @row:
931  *
932  * Sets the top left cell that a newly created sheet control should display.
933  * This corresponds to the top left cell visible in pane 0 (frozen or not).
934  * NOTE : the unfrozen_top_left != initial_top_left.  Unfrozen is the first
935  * unfrozen cell, and corresponds to the _minimum_ cell in pane 0.  However,
936  * the pane can scroll and may have something else currently visible as the top
937  * left.
938  */
939 void
gnm_sheet_view_set_initial_top_left(SheetView * sv,int col,int row)940 gnm_sheet_view_set_initial_top_left (SheetView *sv, int col, int row)
941 {
942 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
943 	g_return_if_fail (0 <= col && col < gnm_sheet_get_max_cols (sv->sheet));
944 	g_return_if_fail (0 <= row && row < gnm_sheet_get_max_rows (sv->sheet));
945 	g_return_if_fail (!gnm_sheet_view_is_frozen (sv) ||
946 			  (sv->unfrozen_top_left.col <= col &&
947 			   sv->unfrozen_top_left.row <= row));
948 
949 	sv->initial_top_left.col = col;
950 	sv->initial_top_left.row = row;
951 }
952