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