Introspection fixes.
[gnumeric.git] / src / sheet-control-gui.c
blobcd467d43f92c3a3ed836bebc768149413ab5220c
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * sheet-control-gui.c: Implements a graphic control for a sheet.
5 * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
6 * Copyright (C) 1997-1999 Miguel de Icaza (miguel@kernel.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
23 #include <gnumeric-config.h>
24 #include <glib/gi18n-lib.h>
25 #include "gnumeric.h"
26 #include "sheet-control-gui-priv.h"
28 #include "sheet.h"
29 #include "sheet-private.h"
30 #include "sheet-view.h"
31 #include "sheet-merge.h"
32 #include "workbook.h"
33 #include "workbook-view.h"
34 #include "workbook-cmd-format.h"
35 #include "wbc-gtk-impl.h"
36 #include "cell.h"
37 #include "selection.h"
38 #include "style.h"
39 #include "sheet-style.h"
40 #include "sheet-object-impl.h"
41 #include "sheet-object-cell-comment.h"
42 #include "sheet-object-image.h"
43 #include "gui-util.h"
44 #include "gutils.h"
45 #include "parse-util.h"
46 #include "selection.h"
47 #include "application.h"
48 #include "cellspan.h"
49 #include "cmd-edit.h"
50 #include "commands.h"
51 #include "gnm-commands-slicer.h"
52 #include "clipboard.h"
53 #include "dialogs.h"
54 #include "gui-file.h"
55 #include "sheet-merge.h"
56 #include "ranges.h"
57 #include "xml-sax.h"
58 #include "style-color.h"
59 #include "gnumeric-conf.h"
61 #include "gnm-pane-impl.h"
62 #include "item-bar.h"
63 #include "item-cursor.h"
64 #include "widgets/gnumeric-expr-entry.h"
65 #include "gnm-sheet-slicer.h"
66 #include "input-msg.h"
68 #include <go-data-slicer-field.h>
69 #include <goffice/goffice.h>
71 #include <gdk/gdkkeysyms.h>
72 #include <gsf/gsf-impl-utils.h>
73 #include <gsf/gsf-input.h>
74 #include <gsf/gsf-output-memory.h>
75 #include <gtk/gtk.h>
77 #include <string.h>
79 static GObjectClass *scg_parent_class;
81 static void scg_unant (SheetControl *sc);
82 static void set_resize_pane_pos (SheetControlGUI *scg, GtkPaned *p);
83 static void cb_resize_pane_motion (GtkPaned *p, GParamSpec *pspec, SheetControlGUI *scg);
86 /**
87 * scg_pane:
88 * @scg: #SheetControlGUI
89 * @pane: the pane index.
91 * Returns: (transfer none): the pane.
92 **/
93 GnmPane *
94 scg_pane (SheetControlGUI *scg, int p)
96 /* it is ok to request a pane when we are not frozen */
97 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
98 g_return_val_if_fail (p >= 0, NULL);
99 g_return_val_if_fail (p < 4, NULL);
101 return scg->pane[p];
105 * scg_view:
106 * @scg: #SheetControlGUI
108 * Returns: (transfer none): the sheet view.
110 SheetView *
111 scg_view (SheetControlGUI const *scg)
113 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
114 return scg->sheet_control.view;
119 * scg_sheet:
120 * @scg: #SheetControlGUI
122 * Returns: (transfer none): the sheet.
124 Sheet *
125 scg_sheet (SheetControlGUI const *scg)
127 return sc_sheet ((SheetControl *)scg);
132 * scg_wbc:
133 * @scg: #SheetControlGUI
135 * Returns: (transfer none): the workbook control.
137 WorkbookControl *
138 scg_wbc (SheetControlGUI const *scg)
140 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
141 return scg->sheet_control.wbc;
146 * scg_wbcg:
147 * @scg: #SheetControlGUI
149 * Returns: (transfer none): the #WBCGtk.
152 WBCGtk *
153 scg_wbcg (SheetControlGUI const *scg)
155 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
156 return scg->wbcg;
159 static void
160 scg_redraw_all (SheetControl *sc, gboolean headers)
162 SheetControlGUI *scg = (SheetControlGUI *)sc;
164 g_return_if_fail (GNM_IS_SCG (scg));
166 SCG_FOREACH_PANE (scg, pane, {
167 goc_canvas_invalidate (GOC_CANVAS (pane),
168 G_MININT64, 0, G_MAXINT64, G_MAXINT64);
169 if (headers) {
170 if (NULL != pane->col.canvas)
171 goc_canvas_invalidate (pane->col.canvas,
172 0, 0, G_MAXINT64, G_MAXINT64);
173 if (NULL != pane->row.canvas)
174 goc_canvas_invalidate (pane->row.canvas,
175 0, 0, G_MAXINT64, G_MAXINT64);
180 static void
181 scg_redraw_range (SheetControl *sc, GnmRange const *r)
183 SheetControlGUI *scg = (SheetControlGUI *)sc;
184 Sheet const *sheet = scg_sheet (scg);
185 GnmRange visible, area;
188 * Getting the bounding box causes row respans to be done if
189 * needed. That can be expensive, so just redraw the whole
190 * sheet if the row count is too big.
192 if (r->end.row - r->start.row > 500) {
193 scg_redraw_all (sc, FALSE);
194 return;
197 /* We potentially do a lot of recalcs as part of this, so make sure
198 stuff that caches sub-computations see the whole thing instead
199 of clearing between cells. */
200 gnm_app_recalc_start ();
202 SCG_FOREACH_PANE (scg, pane, {
203 visible.start = pane->first;
204 visible.end = pane->last_visible;
206 if (range_intersection (&area, r, &visible)) {
207 sheet_range_bounding_box (sheet, &area);
208 gnm_pane_redraw_range (pane, &area);
210 };);
212 gnm_app_recalc_finish ();
215 static void
216 scg_redraw_headers (SheetControl *sc,
217 gboolean const col, gboolean const row,
218 GnmRange const * r /* optional == NULL */)
220 SheetControlGUI *scg = (SheetControlGUI *)sc;
221 GnmPane *pane;
222 int i;
223 double scale;
226 * A rough guess of the trade off point between of redrawing all
227 * and calculating the redraw size
229 const int COL_HEURISTIC = 20;
230 const int ROW_HEURISTIC = 50;
232 for (i = scg->active_panes; i-- > 0 ; ) {
233 if (NULL == (pane = scg->pane[i]))
234 continue;
236 if (col && pane->col.canvas != NULL) {
237 int left = 0, right = G_MAXINT - 1;
238 GocCanvas * const col_canvas = GOC_CANVAS (pane->col.canvas);
239 scale = goc_canvas_get_pixels_per_unit (col_canvas);
241 if (r != NULL) {
242 int const size = r->end.col - r->start.col;
243 if (-COL_HEURISTIC < size && size < COL_HEURISTIC) {
244 left = pane->first_offset.x +
245 scg_colrow_distance_get (scg, TRUE,
246 pane->first.col, r->start.col);
247 right = left +
248 scg_colrow_distance_get (scg, TRUE,
249 r->start.col, r->end.col+1);
252 goc_canvas_invalidate (col_canvas,
253 left / scale, 0, right / scale, G_MAXINT64);
256 if (row && pane->row.canvas != NULL) {
257 gint64 top = 0, bottom = G_MAXINT64 - 1;
258 scale = goc_canvas_get_pixels_per_unit (pane->row.canvas);
259 if (r != NULL) {
260 int const size = r->end.row - r->start.row;
261 if (-ROW_HEURISTIC < size && size < ROW_HEURISTIC) {
262 top = pane->first_offset.y +
263 scg_colrow_distance_get (scg, FALSE,
264 pane->first.row, r->start.row);
265 bottom = top +
266 scg_colrow_distance_get (scg, FALSE,
267 r->start.row, r->end.row+1);
270 goc_canvas_invalidate (GOC_CANVAS (pane->row.canvas),
271 0, top / scale, G_MAXINT64, bottom / scale);
276 static void
277 cb_outline_button (GtkWidget *btn, SheetControlGUI *scg)
279 SheetControl *sc = (SheetControl *) scg;
280 WorkbookControl *wbc = sc->wbc;
281 GPtrArray const *btns;
282 unsigned i = 0;
283 gboolean is_cols = g_object_get_data (G_OBJECT (btn), "is_cols") != NULL;
285 /* which button */
286 btns = is_cols ? scg->col_group.buttons : scg->row_group.buttons;
287 for (i = 0; i < btns->len; i++)
288 if (g_ptr_array_index (btns, i) == btn)
289 break;
291 g_return_if_fail (i < btns->len);
293 cmd_global_outline_change (wbc, is_cols, i+1);
296 static void
297 scg_setup_group_buttons (SheetControlGUI *scg, unsigned max_outline,
298 GnmItemBar const *ib, gboolean is_cols, int w, int h,
299 GPtrArray *btns, GtkWidget *box)
301 PangoFontDescription *font_desc;
302 unsigned i;
303 Sheet const *sheet = scg_sheet (scg);
305 if (!sheet->display_outlines)
306 max_outline = 0;
307 else if (max_outline > 0)
308 max_outline++;
310 while (btns->len > max_outline) {
311 GtkWidget *w = g_ptr_array_remove_index_fast (btns, btns->len - 1);
312 gtk_container_remove (GTK_CONTAINER (box),
313 gtk_widget_get_parent (w));
316 while (btns->len < max_outline) {
317 GtkWidget *out = gtk_alignment_new (.5, .5, 1., 1.);
318 GtkWidget *in = gtk_alignment_new (.5, .5, 0., 0.);
319 GtkWidget *btn = gtk_button_new ();
320 char *tmp = g_strdup_printf ("<small>%d</small>", btns->len+1);
321 GtkWidget *label = gtk_label_new (NULL);
322 gtk_label_set_markup (GTK_LABEL (label), tmp);
323 g_free (tmp);
325 gtk_widget_set_can_focus (btn, FALSE);
326 gtk_container_add (GTK_CONTAINER (in), label);
327 gtk_container_add (GTK_CONTAINER (btn), in);
328 gtk_container_add (GTK_CONTAINER (out), btn);
329 gtk_box_pack_start (GTK_BOX (box), out, TRUE, TRUE, 0);
330 g_ptr_array_add (btns, btn);
332 g_signal_connect (G_OBJECT (btn),
333 "clicked",
334 G_CALLBACK (cb_outline_button), scg);
335 if (is_cols)
336 g_object_set_data (G_OBJECT (btn),
337 "is_cols", GINT_TO_POINTER (1));
340 font_desc = item_bar_normal_font (ib);
342 /* size all of the button so things work after a zoom */
343 for (i = 0 ; i < btns->len ; i++) {
344 GtkWidget *btn = g_ptr_array_index (btns, i);
345 GtkWidget *label = gtk_bin_get_child (GTK_BIN (gtk_bin_get_child (GTK_BIN (btn))));
346 gtk_widget_set_size_request (GTK_WIDGET (btn), w, h);
347 gtk_widget_override_font (label, font_desc);
350 pango_font_description_free (font_desc);
351 gtk_widget_show_all (box);
354 static void
355 scg_resize (SheetControlGUI *scg, G_GNUC_UNUSED gboolean force_scroll)
357 Sheet const *sheet = scg_sheet (scg);
358 GnmPane *pane = scg_pane (scg, 0);
359 int h, w, btn_h, btn_w, tmp;
361 if (!pane)
362 return;
364 /* Recalibrate the starting offsets */
365 pane->first_offset.x = scg_colrow_distance_get (scg,
366 TRUE, 0, pane->first.col);
367 pane->first_offset.y = scg_colrow_distance_get (scg,
368 FALSE, 0, pane->first.row);
370 /* resize Pane[0] headers */
371 h = gnm_item_bar_calc_size (scg->pane[0]->col.item);
372 btn_h = h - gnm_item_bar_indent (scg->pane[0]->col.item);
373 w = gnm_item_bar_calc_size (scg->pane[0]->row.item);
374 btn_w = w - gnm_item_bar_indent (scg->pane[0]->row.item);
375 gtk_widget_set_size_request (scg->select_all_btn, btn_w, btn_h);
376 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[0]->col.canvas), -1, h);
377 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[0]->row.canvas), w, -1);
379 tmp = gnm_item_bar_group_size (scg->pane[0]->col.item,
380 sheet->cols.max_outline_level);
381 scg_setup_group_buttons (scg, sheet->cols.max_outline_level,
382 scg->pane[0]->col.item, TRUE,
383 tmp, tmp, scg->col_group.buttons, scg->col_group.button_box);
384 scg_setup_group_buttons (scg, sheet->rows.max_outline_level,
385 scg->pane[0]->row.item, FALSE,
386 -1, btn_h, scg->row_group.buttons, scg->row_group.button_box);
388 if (scg->active_panes != 1 && sv_is_frozen (scg_view (scg))) {
389 GnmCellPos const *tl = &scg_view (scg)->frozen_top_left;
390 GnmCellPos const *br = &scg_view (scg)->unfrozen_top_left;
391 int const l = scg_colrow_distance_get (scg, TRUE,
392 0, tl->col);
393 int const r = scg_colrow_distance_get (scg, TRUE,
394 tl->col, br->col) + l;
395 int const t = scg_colrow_distance_get (scg, FALSE,
396 0, tl->row);
397 int const b = scg_colrow_distance_get (scg, FALSE,
398 tl->row, br->row) + t;
399 int i;
400 int fw = MIN (scg->screen_width, r - l);
401 int fh = MIN (scg->screen_height, b - t);
403 /* pane 0 has already been done */
404 for (i = scg->active_panes; i-- > 1 ; ) {
405 GnmPane *pane = scg->pane[i];
406 if (NULL != pane) {
407 pane->first_offset.x = scg_colrow_distance_get (
408 scg, TRUE, 0, pane->first.col);
409 pane->first_offset.y = scg_colrow_distance_get (
410 scg, FALSE, 0, pane->first.row);
414 if (scg->pane[1]) {
415 if (gnm_debug_flag ("frozen-panes"))
416 g_printerr ("Pane 1: %d\n", r - l);
418 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[1]), fw, -1);
419 /* The item_bar_calcs should be equal */
420 /* FIXME : The canvas gets confused when the initial scroll
421 * region is set too early in its life cycle.
422 * It likes it to be at the origin, we can live with that for now.
423 * However, we really should track the bug eventually.
425 h = gnm_item_bar_calc_size (scg->pane[1]->col.item);
426 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[1]->col.canvas), fw, h);
429 if (scg->pane[3]) {
430 if (gnm_debug_flag ("frozen-panes"))
431 g_printerr ("Pane 2: %d\n", b - t);
433 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[3]), -1, fh);
434 /* The item_bar_calcs should be equal */
435 w = gnm_item_bar_calc_size (scg->pane[3]->row.item);
436 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[3]->row.canvas), w, fh);
439 if (scg->pane[2]) {
440 if (gnm_debug_flag ("frozen-panes"))
441 g_printerr ("Pane 3: %d %d\n", r - l, b - t);
443 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[2]), fw, fh);
447 SCG_FOREACH_PANE (scg, pane, {
448 gnm_pane_reposition_cursors (pane);
452 static void
453 scg_resize_virt (SheetControl *sc, gboolean force_scroll)
455 scg_resize ((SheetControlGUI *)sc, force_scroll);
458 static void
459 gnm_adjustment_configure (GtkAdjustment *adjustment,
460 gdouble value,
461 gdouble lower,
462 gdouble upper,
463 gdouble step_increment,
464 gdouble page_increment,
465 gdouble page_size)
467 g_object_freeze_notify (G_OBJECT (adjustment));
469 // These do nothing if value isn't changed
470 gtk_adjustment_set_lower (adjustment, lower);
471 gtk_adjustment_set_upper (adjustment, upper);
472 gtk_adjustment_set_step_increment (adjustment, step_increment);
473 gtk_adjustment_set_page_increment (adjustment, page_increment);
474 gtk_adjustment_set_page_size (adjustment, page_size);
476 g_object_thaw_notify (G_OBJECT (adjustment));
478 // These fire signals if nothing changes, so check by hand
479 if (!(gtk_adjustment_get_value (adjustment) == value))
480 gtk_adjustment_set_value (adjustment, value);
485 * scg_scrollbar_config:
486 * @sc:
488 * Manages the scrollbar dimensions and paging parameters.
489 * Currently sizes things based on the cols/rows visible in pane-0. This has
490 * several subtleties.
492 * 1) Using cols/rows instead of pixels means that the scrollbar changes size
493 * as it passes through regions with different sized cols/rows.
495 * 2) It does NOT take into account hidden rows/cols So a region that contains
496 * a large hidden segment will appear larger.
498 * 3) It only uses pane-0 because that is the only one that can scroll in both
499 * dimensions. The others are of fixed size.
501 static gboolean
502 scg_scrollbar_config_real (SheetControl const *sc)
504 SheetControlGUI *scg = GNM_SCG (sc);
505 GtkAdjustment *va = scg->va;
506 GtkAdjustment *ha = scg->ha;
507 GnmPane *pane = scg_pane (scg, 0);
508 SheetView const *sv = sc->view;
509 Sheet const *sheet = sv->sheet;
511 if (pane) {
512 int const last_col = pane->last_full.col;
513 int const last_row = pane->last_full.row;
514 int max_col = last_col;
515 int max_row = last_row;
517 if (max_row < sheet->rows.max_used)
518 max_row = sheet->rows.max_used;
519 if (max_row < sheet->max_object_extent.row)
520 max_row = sheet->max_object_extent.row;
521 gnm_adjustment_configure
522 (va,
523 pane->first.row,
524 sv_is_frozen (sv) ? sv->unfrozen_top_left.row : 0,
525 max_row + 1,
527 MAX (gtk_adjustment_get_page_size (va) - 3.0, 1.0),
528 last_row - pane->first.row + 1);
530 if (max_col < sheet->cols.max_used)
531 max_col = sheet->cols.max_used;
532 if (max_col < sheet->max_object_extent.col)
533 max_col = sheet->max_object_extent.col;
534 gnm_adjustment_configure
535 (ha,
536 pane->first.col,
537 sv_is_frozen (sv) ? sv->unfrozen_top_left.col : 0,
538 max_col + 1,
540 MAX (gtk_adjustment_get_page_size (ha) - 3.0, 1.0),
541 last_col - pane->first.col + 1);
544 scg->scroll_bar_timer = 0;
545 return FALSE;
549 static void
550 scg_scrollbar_config (SheetControl *sc)
552 SheetControlGUI *scg = GNM_SCG (sc);
553 /* See bug 789412 */
554 if (!scg->scroll_bar_timer)
555 scg->scroll_bar_timer =
556 g_timeout_add (1,
557 (GSourceFunc) scg_scrollbar_config_real,
558 scg);
561 void
562 scg_colrow_size_set (SheetControlGUI *scg,
563 gboolean is_cols, int index, int new_size_pixels)
565 WorkbookControl *wbc = scg_wbc (scg);
566 SheetView *sv = scg_view (scg);
568 /* If all cols/rows in the selection are completely selected
569 * then resize all of them, otherwise just resize the selected col/row.
571 if (!sv_is_full_colrow_selected (sv, is_cols, index))
572 cmd_resize_colrow (wbc, sv->sheet, is_cols,
573 colrow_get_index_list (index, index, NULL), new_size_pixels);
574 else
575 workbook_cmd_resize_selected_colrow (wbc, sv->sheet, is_cols,
576 new_size_pixels);
579 void
580 scg_select_all (SheetControlGUI *scg)
582 Sheet *sheet = scg_sheet (scg);
583 gboolean const rangesel = wbcg_rangesel_possible (scg->wbcg);
585 if (rangesel) {
586 scg_rangesel_bound (scg,
587 0, 0, gnm_sheet_get_last_col (sheet), gnm_sheet_get_last_row (sheet));
588 gnm_expr_entry_signal_update (
589 wbcg_get_entry_logical (scg->wbcg), TRUE);
590 } else if (wbc_gtk_get_guru (scg->wbcg) == NULL) {
591 SheetView *sv = scg_view (scg);
593 scg_mode_edit (scg);
594 wbcg_edit_finish (scg->wbcg, WBC_EDIT_REJECT, NULL);
595 sv_selection_reset (sv);
596 sv_selection_add_full (sv, sv->edit_pos.col, sv->edit_pos.row,
597 0, 0, gnm_sheet_get_last_col (sheet),
598 gnm_sheet_get_last_row (sheet),
599 GNM_SELECTION_MODE_ADD);
601 sheet_update (sheet);
604 gboolean
605 scg_colrow_select (SheetControlGUI *scg, gboolean is_cols,
606 int index, int modifiers)
608 SheetView *sv = scg_view (scg);
609 gboolean const rangesel = wbcg_rangesel_possible (scg->wbcg);
611 if (!rangesel &&
612 !wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
613 return FALSE;
615 if (modifiers & GDK_SHIFT_MASK) {
616 if (rangesel) {
617 if (is_cols)
618 scg_rangesel_extend_to (scg, index, -1);
619 else
620 scg_rangesel_extend_to (scg, -1, index);
621 } else {
622 if (is_cols)
623 sv_selection_extend_to (sv, index, -1);
624 else
625 sv_selection_extend_to (sv, -1, index);
627 } else {
628 if (!rangesel && !(modifiers & GDK_CONTROL_MASK))
629 sv_selection_reset (sv);
631 if (rangesel) {
632 if (is_cols)
633 scg_rangesel_bound (scg,
634 index, 0, index, gnm_sheet_get_last_row (sv->sheet));
635 else
636 scg_rangesel_bound (scg,
637 0, index, gnm_sheet_get_last_col (sv->sheet), index);
638 } else if (is_cols) {
639 GnmPane *pane =
640 scg_pane (scg, scg->pane[3] ? 3 : 0);
641 sv_selection_add_full (sv,
642 index, pane->first.row,
643 index, 0,
644 index, gnm_sheet_get_last_row (sv->sheet),
645 GNM_SELECTION_MODE_ADD);
646 } else {
647 GnmPane *pane =
648 scg_pane (scg, scg->pane[1] ? 1 : 0);
649 sv_selection_add_full (sv,
650 pane->first.col, index,
651 0, index,
652 gnm_sheet_get_last_col (sv->sheet), index,
653 GNM_SELECTION_MODE_ADD);
657 /* The edit pos, and the selection may have changed */
658 if (!rangesel)
659 sheet_update (sv->sheet);
660 return TRUE;
663 /***************************************************************************/
665 static void
666 cb_select_all_btn_draw (GtkWidget *widget, cairo_t *cr, SheetControlGUI *scg)
668 int offset = scg_sheet (scg)->text_is_rtl ? -1 : 0;
669 GtkAllocation a;
670 GtkStyleContext *ctxt = gtk_widget_get_style_context (widget);
672 gtk_widget_get_allocation (widget, &a);
674 gtk_style_context_save (ctxt);
675 gtk_style_context_set_state (ctxt, GTK_STATE_FLAG_NORMAL);
676 gtk_render_background (ctxt, cr, offset + 1, 1,
677 a.width - 1, a.height - 1);
678 gtk_render_frame (ctxt, cr, offset, 0, a.width + 1, a.height + 1);
679 gtk_style_context_restore (ctxt);
682 static gboolean
683 cb_select_all_btn_event (G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event, SheetControlGUI *scg)
685 if (event->type == GDK_BUTTON_PRESS) {
686 scg_select_all (scg);
687 return TRUE;
690 return FALSE;
693 static void
694 cb_vscrollbar_value_changed (GtkRange *range, SheetControlGUI *scg)
696 GtkAdjustment *adj = gtk_range_get_adjustment (range);
697 scg_set_top_row (scg, gtk_adjustment_get_value (adj));
700 static void
701 cb_hscrollbar_value_changed (GtkRange *range, SheetControlGUI *scg)
703 GtkAdjustment *adj = gtk_range_get_adjustment (range);
704 scg_set_left_col (scg, gtk_adjustment_get_value (adj));
707 static void
708 cb_hscrollbar_adjust_bounds (GtkRange *range, gdouble new_value, Sheet *sheet)
710 GtkAdjustment *adj = gtk_range_get_adjustment (range);
711 double upper = gtk_adjustment_get_upper (adj);
712 double page_size = gtk_adjustment_get_page_size (adj);
713 gdouble limit = upper - page_size;
714 if (upper < gnm_sheet_get_max_cols (sheet) && new_value >= limit) {
715 upper = new_value + page_size + 1;
716 if (upper > gnm_sheet_get_max_cols (sheet))
717 upper = gnm_sheet_get_max_cols (sheet);
718 gtk_adjustment_set_upper (adj, upper);
721 static void
722 cb_vscrollbar_adjust_bounds (GtkRange *range, gdouble new_value, Sheet *sheet)
724 GtkAdjustment *adj = gtk_range_get_adjustment (range);
725 double upper = gtk_adjustment_get_upper (adj);
726 double page_size = gtk_adjustment_get_page_size (adj);
727 gdouble limit = upper - page_size;
728 if (upper < gnm_sheet_get_max_rows (sheet) && new_value >= limit) {
729 upper = new_value + page_size + 1;
730 if (upper > gnm_sheet_get_max_rows (sheet))
731 upper = gnm_sheet_get_max_rows (sheet);
732 gtk_adjustment_set_upper (adj, upper);
736 static void
737 cb_table_destroy (SheetControlGUI *scg)
739 SheetControl *sc = (SheetControl *) scg;
740 int i;
742 g_clear_object (&scg->grid);
744 scg_mode_edit (scg); /* finish any object edits */
745 scg_unant (sc); /* Make sure that everything is unanted */
747 if (scg->wbcg) {
748 GtkWindow *toplevel = wbcg_toplevel (scg->wbcg);
750 /* Only pane-0 ever gets focus */
751 if (NULL != toplevel &&
752 gtk_window_get_focus (toplevel) == GTK_WIDGET (scg_pane (scg, 0)))
753 gtk_window_set_focus (toplevel, NULL);
756 for (i = scg->active_panes; i-- > 0 ; )
757 if (NULL != scg->pane[i]) {
758 gtk_widget_destroy (GTK_WIDGET (scg->pane[i]));
759 scg->pane[i] = NULL;
762 g_object_unref (scg);
765 static void
766 scg_init (SheetControlGUI *scg)
768 scg->comment.selected = NULL;
769 scg->comment.item = NULL;
770 scg->comment.timer = 0;
772 scg->delayedMovement.timer = 0;
773 scg->delayedMovement.handler = NULL;
775 scg->grab_stack = 0;
776 scg->selected_objects = NULL;
778 scg->im.item = NULL;
779 scg->im.timer = 0;
781 // These shouldn't matter and will be overwritten
782 scg->screen_width = 1920;
783 scg->screen_height = 1200;
786 /*************************************************************************/
789 * gnm_pane_update_inital_top_left:
790 * A convenience routine to store the new topleft back in the view.
792 static void
793 gnm_pane_update_inital_top_left (GnmPane const *pane)
795 if (pane->index == 0) {
796 SheetView *sv = scg_view (pane->simple.scg);
797 sv->initial_top_left = pane->first;
801 static gint64
802 bar_set_left_col (GnmPane *pane, int new_first_col)
804 GocCanvas *colc;
805 gint64 col_offset;
808 col_offset = pane->first_offset.x +=
809 scg_colrow_distance_get (pane->simple.scg, TRUE, pane->first.col, new_first_col);
810 pane->first.col = new_first_col;
812 /* Scroll the column headers */
813 if (NULL != (colc = pane->col.canvas))
814 goc_canvas_scroll_to (colc, col_offset / colc->pixels_per_unit, 0);
816 return col_offset;
819 static void
820 gnm_pane_set_left_col (GnmPane *pane, int new_first_col)
822 Sheet *sheet;
823 g_return_if_fail (pane != NULL);
824 sheet = scg_sheet (pane->simple.scg);
825 g_return_if_fail (0 <= new_first_col && new_first_col < gnm_sheet_get_max_cols (sheet));
827 if (pane->first.col != new_first_col) {
828 GocCanvas * const canvas = GOC_CANVAS (pane);
829 gint64 const col_offset = bar_set_left_col (pane, new_first_col);
831 gnm_pane_compute_visible_region (pane, FALSE);
832 goc_canvas_scroll_to (canvas, col_offset / canvas->pixels_per_unit, pane->first_offset.y / canvas->pixels_per_unit);
833 gnm_pane_update_inital_top_left (pane);
837 void
838 scg_set_left_col (SheetControlGUI *scg, int col)
840 Sheet const *sheet;
841 GnmRange const *bound;
843 g_return_if_fail (GNM_IS_SCG (scg));
845 sheet = scg_sheet (scg);
846 bound = &sheet->priv->unhidden_region;
847 if (col < bound->start.col)
848 col = bound->start.col;
849 else if (col >= gnm_sheet_get_max_cols (sheet))
850 col = gnm_sheet_get_last_col (sheet);
851 else if (col > bound->end.col)
852 col = bound->end.col;
854 if (scg->pane[1]) {
855 int right = scg_view (scg)->unfrozen_top_left.col;
856 if (col < right)
857 col = right;
859 if (scg->pane[3])
860 gnm_pane_set_left_col (scg_pane (scg, 3), col);
861 gnm_pane_set_left_col (scg_pane (scg, 0), col);
864 static gint64
865 bar_set_top_row (GnmPane *pane, int new_first_row)
867 GocCanvas *rowc;
868 gint64 row_offset;
870 row_offset = pane->first_offset.y +=
871 scg_colrow_distance_get (pane->simple.scg, FALSE, pane->first.row, new_first_row);
872 pane->first.row = new_first_row;
874 /* Scroll the row headers */
875 if (NULL != (rowc = pane->row.canvas))
876 goc_canvas_scroll_to (rowc, 0, row_offset / rowc->pixels_per_unit);
878 return row_offset;
881 static void
882 gnm_pane_set_top_row (GnmPane *pane, int new_first_row)
884 Sheet *sheet;
885 g_return_if_fail (pane != NULL);
886 sheet = scg_sheet (pane->simple.scg);
887 g_return_if_fail (0 <= new_first_row && new_first_row < gnm_sheet_get_max_rows (sheet));
889 if (pane->first.row != new_first_row) {
890 GocCanvas * const canvas = GOC_CANVAS(pane);
891 gint64 const row_offset = bar_set_top_row (pane, new_first_row);
892 gint64 col_offset = pane->first_offset.x;
894 gnm_pane_compute_visible_region (pane, FALSE);
895 goc_canvas_scroll_to (canvas, col_offset / canvas->pixels_per_unit, row_offset / canvas->pixels_per_unit);
896 gnm_pane_update_inital_top_left (pane);
900 void
901 scg_set_top_row (SheetControlGUI *scg, int row)
903 Sheet const *sheet;
904 GnmRange const *bound;
906 g_return_if_fail (GNM_IS_SCG (scg));
908 sheet = scg_sheet (scg);
909 bound = &sheet->priv->unhidden_region;
910 if (row < bound->start.row)
911 row = bound->start.row;
912 else if (row >= gnm_sheet_get_max_rows (sheet))
913 row = gnm_sheet_get_last_row (sheet);
914 else if (row > bound->end.row)
915 row = bound->end.row;
917 if (scg->pane[3]) {
918 int bottom = scg_view (scg)->unfrozen_top_left.row;
919 if (row < bottom)
920 row = bottom;
922 if (scg->pane[1])
923 gnm_pane_set_top_row (scg_pane (scg, 1), row);
924 gnm_pane_set_top_row (scg_pane (scg, 0), row);
927 static void
928 gnm_pane_set_top_left (GnmPane *pane,
929 int col, int row, gboolean force_scroll)
931 gboolean changed = FALSE;
932 gint64 col_offset, row_offset;
933 GocCanvas *canvas;
935 g_return_if_fail (0 <= col &&
936 col < gnm_sheet_get_max_cols (scg_sheet (pane->simple.scg)));
937 g_return_if_fail (0 <= row &&
938 row < gnm_sheet_get_max_rows (scg_sheet (pane->simple.scg)));
940 if (pane->first.col != col || force_scroll) {
941 if (force_scroll) {
942 /* Clear the offsets in case col/row size changed */
943 pane->first_offset.x = 0;
944 pane->first.col = 0;
947 col_offset = bar_set_left_col (pane, col);
948 changed = TRUE;
949 } else {
950 col_offset = pane->first_offset.x;
953 if (pane->first.row != row || force_scroll) {
954 if (force_scroll) {
955 /* Clear the offsets in case col/row size changed */
956 pane->first_offset.y = 0;
957 pane->first.row = 0;
959 row_offset = bar_set_top_row (pane, row);
960 changed = TRUE;
961 } else
962 row_offset = pane->first_offset.y;
964 if (!changed)
965 return;
967 gnm_pane_compute_visible_region (pane, force_scroll);
968 canvas = GOC_CANVAS (pane);
969 goc_canvas_scroll_to (canvas, col_offset / canvas->pixels_per_unit, row_offset / canvas->pixels_per_unit);
970 gnm_pane_update_inital_top_left (pane);
973 static void
974 scg_set_top_left (SheetControl *sc, int col, int row)
976 SheetControlGUI *scg = (SheetControlGUI *)sc;
978 g_return_if_fail (GNM_IS_SCG (scg));
980 if (!scg->pane[0])
981 return;
982 /* We could be faster if necessary */
983 scg_set_left_col (scg, col);
984 scg_set_top_row (scg, row);
987 static void
988 gnm_pane_make_cell_visible (GnmPane *pane, int col, int row,
989 gboolean const force_scroll)
991 GocCanvas *canvas;
992 Sheet *sheet;
993 int new_first_col, new_first_row;
994 GnmRange range;
995 GtkAllocation ca;
997 g_return_if_fail (GNM_IS_PANE (pane));
999 /* Avoid calling this before the canvas is realized: We do not know the
1000 * visible area, and would unconditionally scroll the cell to the top
1001 * left of the viewport.
1003 if (!gtk_widget_get_realized (GTK_WIDGET (pane)))
1004 return;
1006 sheet = scg_sheet (pane->simple.scg);
1007 g_return_if_fail (col >= 0);
1008 g_return_if_fail (row >= 0);
1009 g_return_if_fail (col < gnm_sheet_get_max_cols (sheet));
1010 g_return_if_fail (row < gnm_sheet_get_max_rows (sheet));
1012 canvas = GOC_CANVAS (pane);
1013 range.start.col = range.end.col = col;
1014 range.start.row = range.end.row = row;
1015 gnm_sheet_merge_find_bounding_box (sheet, &range);
1017 gtk_widget_get_allocation (GTK_WIDGET (canvas), &ca);
1019 /* Find the new pane->first.col */
1020 if (range.start.col < pane->first.col) {
1021 new_first_col = range.start.col;
1022 } else if (range.end.col > pane->last_full.col) {
1023 int width = ca.width;
1024 ColRowInfo const * const ci = sheet_col_get_info (sheet, range.end.col);
1025 if (ci->size_pixels < width) {
1026 int first_col = (pane->last_visible.col == pane->first.col)
1027 ? pane->first.col : range.end.col;
1029 for (; first_col > 0; --first_col) {
1030 ColRowInfo const * const ci = sheet_col_get_info (sheet, first_col);
1031 if (ci->visible) {
1032 width -= ci->size_pixels;
1033 if (width < 0)
1034 break;
1037 new_first_col = first_col+1;
1038 if (new_first_col > range.start.col)
1039 new_first_col = range.start.col;
1040 } else
1041 new_first_col = col;
1042 } else
1043 new_first_col = pane->first.col;
1045 /* Find the new pane->first.row */
1046 if (range.start.row < pane->first.row) {
1047 new_first_row = range.start.row;
1048 } else if (range.end.row > pane->last_full.row) {
1049 int height = ca.height;
1050 ColRowInfo const * const ri = sheet_row_get_info (sheet, range.end.row);
1051 if (ri->size_pixels < height) {
1052 int first_row = (pane->last_visible.row == pane->first.row)
1053 ? pane->first.row : range.end.row;
1055 for (; first_row > 0; --first_row) {
1056 ColRowInfo const * const ri = sheet_row_get_info (sheet, first_row);
1057 if (ri->visible) {
1058 height -= ri->size_pixels;
1059 if (height < 0)
1060 break;
1063 new_first_row = first_row+1;
1064 if (new_first_row > range.start.row)
1065 new_first_row = range.start.row;
1066 } else
1067 new_first_row = row;
1068 } else
1069 new_first_row = pane->first.row;
1071 gnm_pane_set_top_left (pane, new_first_col, new_first_row,
1072 force_scroll);
1076 * scg_make_cell_visible:
1077 * @scg: The gui control
1078 * @col:
1079 * @row:
1080 * @force_scroll: Completely recalibrate the offsets to the new position
1081 * @couple_panes: Scroll scroll dynamic panes back to bounds if target
1082 * is in frozen segment.
1084 * Ensure that cell (col, row) is visible.
1085 * Sheet is scrolled if cell is outside viewport.
1087 void
1088 scg_make_cell_visible (SheetControlGUI *scg, int col, int row,
1089 gboolean force_scroll, gboolean couple_panes)
1091 SheetView const *sv = scg_view (scg);
1092 GnmCellPos const *tl, *br;
1094 g_return_if_fail (GNM_IS_SCG (scg));
1096 if (!scg->active_panes)
1097 return;
1099 tl = &sv->frozen_top_left;
1100 br = &sv->unfrozen_top_left;
1101 if (col < br->col) {
1102 if (row >= br->row) { /* pane 1 */
1103 if (col < tl->col)
1104 col = tl->col;
1105 gnm_pane_make_cell_visible (scg->pane[1],
1106 col, row, force_scroll);
1107 gnm_pane_set_top_left (scg->pane[0],
1108 couple_panes ? br->col : scg->pane[0]->first.col,
1109 scg->pane[1]->first.row,
1110 force_scroll);
1111 if (couple_panes && scg->pane[3])
1112 gnm_pane_set_left_col (scg->pane[3], br->col);
1113 } else if (couple_panes) { /* pane 2 */
1114 /* FIXME : We may need to change the way this routine
1115 * is used to fix this. Because we only know what the
1116 * target cell is we cannot absolutely differentiate
1117 * between col & row scrolling. For now use the
1118 * heuristic that if the col was visible this is a
1119 * vertical jump.
1121 if (scg->pane[2]->first.col <= col &&
1122 scg->pane[2]->last_visible.col >= col) {
1123 scg_set_top_row (scg, row);
1124 } else
1125 scg_set_left_col (scg, col);
1127 } else if (row < br->row) { /* pane 3 */
1128 if (row < tl->row)
1129 row = tl->row;
1130 gnm_pane_make_cell_visible (scg->pane[3],
1131 col, row, force_scroll);
1132 gnm_pane_set_top_left (scg->pane[0],
1133 scg->pane[3]->first.col,
1134 couple_panes
1135 ? br->row
1136 : scg->pane[0]->first.row,
1137 force_scroll);
1138 if (couple_panes && scg->pane[1])
1139 gnm_pane_set_top_row (scg->pane[1],
1140 br->row);
1141 } else { /* pane 0 */
1142 gnm_pane_make_cell_visible (scg->pane[0],
1143 col, row, force_scroll);
1144 if (scg->pane[1])
1145 gnm_pane_set_top_left (scg->pane[1],
1146 tl->col, scg->pane[0]->first.row, force_scroll);
1147 if (scg->pane[3])
1148 gnm_pane_set_top_left (scg->pane[3],
1149 scg->pane[0]->first.col, tl->row, force_scroll);
1151 if (scg->pane[2])
1152 gnm_pane_set_top_left (scg->pane[2],
1153 tl->col, tl->row, force_scroll);
1156 static void
1157 scg_make_cell_visible_virt (SheetControl *sc, int col, int row,
1158 gboolean couple_panes)
1160 scg_make_cell_visible ((SheetControlGUI *)sc, col, row,
1161 FALSE, couple_panes);
1164 /*************************************************************************/
1166 static void
1167 scg_set_panes (SheetControl *sc)
1169 SheetControlGUI *scg = (SheetControlGUI *) sc;
1170 SheetView *sv = sc->view;
1171 gboolean const being_frozen = sv_is_frozen (sv);
1172 GocDirection direction = (sv_sheet (sv)->text_is_rtl)? GOC_DIRECTION_RTL: GOC_DIRECTION_LTR;
1174 g_return_if_fail (GNM_IS_SV (sv));
1176 if (!scg->pane[0])
1177 return;
1178 if (being_frozen) {
1179 GnmCellPos const *tl = &sv->frozen_top_left;
1180 GnmCellPos const *br = &sv->unfrozen_top_left;
1181 gboolean const freeze_h = br->col > tl->col;
1182 gboolean const freeze_v = br->row > tl->row;
1184 gnm_pane_bound_set (scg->pane[0],
1185 br->col, br->row,
1186 gnm_sheet_get_last_col (sv->sheet), gnm_sheet_get_last_row (sv->sheet));
1188 if (freeze_h) {
1189 scg->active_panes = 2;
1190 if (!scg->pane[1]) {
1191 scg->pane[1] = gnm_pane_new (scg, TRUE, FALSE, 1);
1192 gnm_pane_set_direction (scg->pane[1], direction);
1193 gtk_grid_attach (scg->grid,
1194 GTK_WIDGET (scg->pane[1]),
1195 2, 3, 1, 1);
1196 gtk_grid_attach (scg->grid,
1197 GTK_WIDGET (scg->pane[1]->col.canvas),
1198 2, 0, 1, 2);
1200 gnm_pane_bound_set (scg->pane[1],
1201 tl->col, br->row, br->col - 1, gnm_sheet_get_last_row (sv->sheet));
1203 if (freeze_h && freeze_v) {
1204 scg->active_panes = 4;
1205 if (!scg->pane[2]) {
1206 scg->pane[2] = gnm_pane_new (scg, FALSE, FALSE, 2);
1207 gnm_pane_set_direction (scg->pane[2], direction);
1208 gtk_grid_attach (scg->grid,
1209 GTK_WIDGET (scg->pane[2]),
1210 2, 2, 1, 1);
1212 gnm_pane_bound_set (scg->pane[2],
1213 tl->col, tl->row, br->col - 1, br->row - 1);
1215 if (freeze_v) {
1216 scg->active_panes = 4;
1217 if (!scg->pane[3]) {
1218 scg->pane[3] = gnm_pane_new (scg, FALSE, TRUE, 3);
1219 gnm_pane_set_direction (scg->pane[3], direction);
1220 gtk_grid_attach (scg->grid,
1221 GTK_WIDGET (scg->pane[3]),
1222 3, 2, 1, 1);
1223 gtk_grid_attach (scg->grid,
1224 GTK_WIDGET (scg->pane[3]->row.canvas),
1225 0, 2, 2, 1);
1227 gnm_pane_bound_set (scg->pane[3],
1228 br->col, tl->row, gnm_sheet_get_last_col (sv->sheet), br->row - 1);
1230 } else {
1231 int i;
1232 for (i = 1 ; i <= 3 ; i++)
1233 if (scg->pane[i]) {
1234 gtk_widget_destroy (GTK_WIDGET (scg->pane[i]));
1235 scg->pane[i] = NULL;
1238 scg->active_panes = 1;
1239 gnm_pane_bound_set (scg->pane[0],
1240 0, 0, gnm_sheet_get_last_col (sv->sheet), gnm_sheet_get_last_row (sv->sheet));
1243 gtk_widget_show_all (GTK_WIDGET (scg->grid));
1245 /* in case headers are hidden */
1246 scg_adjust_preferences (scg);
1247 scg_resize (scg, TRUE);
1249 if (being_frozen) {
1250 GnmCellPos const *tl = &sc->view->frozen_top_left;
1252 if (scg->pane[1])
1253 gnm_pane_set_left_col (scg->pane[1], tl->col);
1254 if (scg->pane[2])
1255 gnm_pane_set_top_left (scg->pane[2], tl->col, tl->row, TRUE);
1256 if (scg->pane[3])
1257 gnm_pane_set_top_row (scg->pane[3], tl->row);
1259 set_resize_pane_pos (scg, scg->vpane);
1260 set_resize_pane_pos (scg, scg->hpane);
1263 static void
1264 cb_wbc_destroyed (SheetControlGUI *scg)
1266 scg->wbcg = NULL;
1267 scg->sheet_control.wbc = NULL;
1270 static void
1271 cb_scg_redraw (SheetControlGUI *scg)
1273 scg_adjust_preferences (scg);
1274 scg_redraw_all (&scg->sheet_control, TRUE);
1277 static void
1278 cb_scg_redraw_resize (SheetControlGUI *scg)
1280 cb_scg_redraw (scg);
1281 scg_resize (scg, FALSE);
1284 static void
1285 cb_scg_sheet_resized (SheetControlGUI *scg)
1287 cb_scg_redraw_resize (scg);
1288 sc_set_panes (&scg->sheet_control);
1291 static void
1292 cb_scg_direction_changed (SheetControlGUI *scg)
1294 /* set direction in the canvas */
1295 int i = scg->active_panes;
1296 while (i-- > 0) {
1297 GnmPane *pane = scg->pane[i];
1298 if (NULL != pane)
1299 gnm_pane_set_direction (scg->pane[i],
1300 scg_sheet (scg)->text_is_rtl? GOC_DIRECTION_RTL: GOC_DIRECTION_LTR);
1302 scg_resize (scg, TRUE);
1305 static GnmPane const *
1306 resize_pane_pos (SheetControlGUI *scg, GtkPaned *p,
1307 int *colrow_result, gint64 *guide_pos)
1309 ColRowInfo const *cri;
1310 GnmPane const *pane = scg_pane (scg, 0);
1311 gboolean const vert = (p == scg->hpane);
1312 int colrow, handle;
1313 gint64 pos = gtk_paned_get_position (p);
1315 gtk_widget_style_get (GTK_WIDGET (p), "handle-size", &handle, NULL);
1316 pos += handle / 2;
1317 if (vert) {
1318 if (gtk_widget_get_visible (GTK_WIDGET (pane->row.canvas))) {
1319 GtkAllocation ca;
1320 gtk_widget_get_allocation (GTK_WIDGET (pane->row.canvas), &ca);
1321 pos -= ca.width;
1323 if (scg->pane[1]) {
1324 GtkAllocation pa;
1325 gtk_widget_get_allocation (GTK_WIDGET (scg->pane[1]),
1326 &pa);
1328 if (pos < pa.width)
1329 pane = scg_pane (scg, 1);
1330 else
1331 pos -= pa.width;
1333 pos = MAX (pos, 0);
1334 pos += pane->first_offset.x;
1335 colrow = gnm_pane_find_col (pane, pos, guide_pos);
1336 } else {
1337 if (gtk_widget_get_visible (GTK_WIDGET (pane->col.canvas))) {
1338 GtkAllocation ca;
1339 gtk_widget_get_allocation (GTK_WIDGET (pane->col.canvas), &ca);
1340 pos -= ca.height;
1342 if (scg->pane[3]) {
1343 GtkAllocation pa;
1344 gtk_widget_get_allocation (GTK_WIDGET (scg->pane[3]),
1345 &pa);
1346 if (pos < pa.height)
1347 pane = scg_pane (scg, 3);
1348 else
1349 pos -= pa.height;
1351 pos = MAX (pos, 0);
1352 pos += pane->first_offset.y;
1353 colrow = gnm_pane_find_row (pane, pos, guide_pos);
1355 cri = sheet_colrow_get_info (scg_sheet (scg), colrow, vert);
1356 if (pos >= (*guide_pos + cri->size_pixels / 2)) {
1357 *guide_pos += cri->size_pixels;
1358 colrow++;
1360 if (NULL != colrow_result)
1361 *colrow_result = colrow;
1363 return pane;
1366 static void
1367 scg_gtk_paned_set_position (SheetControlGUI *scg, GtkPaned *p, int pane_pos)
1369 /* A negative position is special to GtkPaned. */
1370 pane_pos = MAX (pane_pos, 0);
1372 if (p == scg->vpane)
1373 scg->vpos = pane_pos;
1374 else
1375 scg->hpos = pane_pos;
1377 gtk_paned_set_position (p, pane_pos);
1380 static void
1381 set_resize_pane_pos (SheetControlGUI *scg, GtkPaned *p)
1383 int handle_size, pane_pos, size;
1384 GnmPane *pane0 = scg->pane[0];
1386 if (!pane0)
1387 return;
1389 if (p == scg->vpane) {
1390 if (gtk_widget_get_visible (GTK_WIDGET (pane0->col.canvas))) {
1391 GtkAllocation alloc;
1392 gtk_widget_get_allocation (GTK_WIDGET (pane0->col.canvas), &alloc);
1393 pane_pos = alloc.height;
1394 } else
1395 pane_pos = 0;
1396 if (scg->pane[3]) {
1397 gtk_widget_get_size_request (
1398 GTK_WIDGET (scg->pane[3]), NULL, &size);
1399 pane_pos += size;
1401 } else {
1402 if (gtk_widget_get_visible (GTK_WIDGET (pane0->row.canvas))) {
1403 GtkAllocation alloc;
1404 gtk_widget_get_allocation (GTK_WIDGET (pane0->row.canvas), &alloc);
1405 pane_pos = alloc.width;
1406 } else
1407 pane_pos = 0;
1408 if (scg->pane[1]) {
1409 gtk_widget_get_size_request (
1410 GTK_WIDGET (scg->pane[1]), &size, NULL);
1411 pane_pos += size;
1414 gtk_widget_style_get (GTK_WIDGET (p), "handle-size", &handle_size, NULL);
1415 pane_pos -= handle_size / 2;
1417 g_signal_handlers_block_by_func (G_OBJECT (p),
1418 G_CALLBACK (cb_resize_pane_motion), scg);
1419 scg_gtk_paned_set_position (scg, p, pane_pos);
1420 g_signal_handlers_unblock_by_func (G_OBJECT (p),
1421 G_CALLBACK (cb_resize_pane_motion), scg);
1424 static void
1425 cb_check_resize (GtkPaned *p, GtkAllocation *allocation, SheetControlGUI *scg);
1427 static gboolean
1428 resize_pane_finish (SheetControlGUI *scg, GtkPaned *p)
1430 SheetView *sv = scg_view (scg);
1431 GnmCellPos frozen_tl, unfrozen_tl;
1432 GnmPane const *pane;
1433 int colrow;
1434 gint64 guide_pos;
1436 #warning GTK3: replace this?
1437 #if 0
1438 if (p->in_drag)
1439 return TRUE;
1440 #endif
1441 pane = resize_pane_pos (scg, p, &colrow, &guide_pos);
1443 if (sv_is_frozen (sv)) {
1444 frozen_tl = sv->frozen_top_left;
1445 unfrozen_tl = sv->unfrozen_top_left;
1446 } else
1447 frozen_tl = pane->first;
1448 if (p == scg->hpane) {
1449 unfrozen_tl.col = colrow;
1450 if (!sv_is_frozen (sv))
1451 unfrozen_tl.row = frozen_tl.row = 0;
1452 } else {
1453 unfrozen_tl.row = colrow;
1454 if (!sv_is_frozen (sv))
1455 unfrozen_tl.col = frozen_tl.col = 0;
1457 sv_freeze_panes (sv, &frozen_tl, &unfrozen_tl);
1459 scg->pane_drag_handler = 0;
1460 scg_size_guide_stop (scg);
1462 set_resize_pane_pos (scg, p);
1464 g_signal_handlers_unblock_by_func
1465 (G_OBJECT (p),
1466 G_CALLBACK (cb_check_resize), scg);
1468 return FALSE;
1470 static gboolean
1471 cb_resize_vpane_finish (SheetControlGUI *scg)
1473 return resize_pane_finish (scg, scg->vpane);
1475 static gboolean
1476 cb_resize_hpane_finish (SheetControlGUI *scg)
1478 return resize_pane_finish (scg, scg->hpane);
1481 static void
1482 cb_resize_pane_motion (GtkPaned *p,
1483 G_GNUC_UNUSED GParamSpec *pspec,
1484 SheetControlGUI *scg)
1486 gboolean const vert = (p == scg->hpane);
1487 int colrow;
1488 gint64 guide_pos;
1490 resize_pane_pos (scg, p, &colrow, &guide_pos);
1491 #warning GTK3: what replaces p->in_drag?
1492 if (scg->pane_drag_handler == 0/* && p->in_drag*/) {
1493 g_signal_handlers_block_by_func
1494 (G_OBJECT (p),
1495 G_CALLBACK (cb_check_resize), scg);
1496 scg_size_guide_start (scg, vert, colrow, FALSE);
1497 scg->pane_drag_handler = g_timeout_add (250,
1498 vert ? (GSourceFunc) cb_resize_hpane_finish
1499 : (GSourceFunc) cb_resize_vpane_finish,
1500 (gpointer) scg);
1502 if (scg->pane_drag_handler)
1503 scg_size_guide_motion (scg, vert, guide_pos);
1507 static void
1508 cb_check_resize (GtkPaned *p, G_GNUC_UNUSED GtkAllocation *allocation,
1509 SheetControlGUI *scg)
1511 gboolean const vert = (p == scg->vpane);
1512 gint max, pos = vert ? scg->vpos : scg->hpos;
1514 g_object_get (G_OBJECT (p), "max-position", &max, NULL);
1515 if (pos > max)
1516 pos = max;
1518 if (gtk_paned_get_position (p) != pos) {
1519 g_signal_handlers_block_by_func
1520 (G_OBJECT (p),
1521 G_CALLBACK (cb_resize_pane_motion), scg);
1522 gtk_paned_set_position (p, pos);
1523 g_signal_handlers_unblock_by_func
1524 (G_OBJECT (p),
1525 G_CALLBACK (cb_resize_pane_motion), scg);
1529 struct resize_closure {
1530 GtkPaned *p;
1531 SheetControlGUI *scg;
1534 static gboolean
1535 idle_resize (struct resize_closure *r)
1538 set_resize_pane_pos (r->scg, r->p);
1539 g_free (r);
1540 return FALSE;
1543 static void
1544 cb_canvas_resize (GtkWidget *w, G_GNUC_UNUSED GtkAllocation *allocation,
1545 SheetControlGUI *scg)
1547 struct resize_closure *r = g_new (struct resize_closure, 1);
1548 r->scg = scg;
1549 r->p = (w == GTK_WIDGET (scg->pane[0]->col.canvas))? scg->hpane: scg->vpane;
1550 /* The allocation is not correct at this point, weird */
1551 g_idle_add ((GSourceFunc) idle_resize, r);
1554 static gboolean
1555 post_create_cb (SheetControlGUI *scg)
1557 Sheet *sheet = sc_sheet (GNM_SC (scg));
1558 if (sheet->sheet_objects)
1559 scg_object_select (scg, (SheetObject *) sheet->sheet_objects->data);
1560 return FALSE;
1563 static gboolean
1564 sheet_object_key_pressed (G_GNUC_UNUSED GtkWidget *w, GdkEventKey *event, SheetControlGUI *scg)
1566 Sheet *sheet = scg_sheet (scg);
1567 WorkbookControl * wbc = scg_wbc (scg);
1568 Workbook * wb = wb_control_get_workbook (wbc);
1569 switch (event->keyval) {
1570 case GDK_KEY_KP_Page_Up:
1571 case GDK_KEY_Page_Up:
1572 if ((event->state & GDK_CONTROL_MASK) != 0){
1573 if ((event->state & GDK_SHIFT_MASK) != 0){
1574 WorkbookSheetState * old_state = workbook_sheet_state_new(wb);
1575 int old_pos = sheet->index_in_wb;
1577 if (old_pos > 0){
1578 workbook_sheet_move(sheet, -1);
1579 cmd_reorganize_sheets (wbc, old_state, sheet);
1581 } else {
1582 gnm_notebook_prev_page (scg->wbcg->bnotebook);
1584 return FALSE;
1586 break;
1587 case GDK_KEY_KP_Page_Down:
1588 case GDK_KEY_Page_Down:
1590 if ((event->state & GDK_CONTROL_MASK) != 0){
1591 if ((event->state & GDK_SHIFT_MASK) != 0){
1592 WorkbookSheetState * old_state = workbook_sheet_state_new(wb);
1593 int num_sheets = workbook_sheet_count(wb);
1594 gint old_pos = sheet->index_in_wb;
1596 if (old_pos < num_sheets - 1){
1597 workbook_sheet_move(sheet, 1);
1598 cmd_reorganize_sheets (wbc, old_state, sheet);
1600 } else {
1601 gnm_notebook_next_page (scg->wbcg->bnotebook);
1603 return FALSE;
1605 break;
1607 return TRUE;
1610 static void
1611 cb_screen_changed (GtkWidget *widget, G_GNUC_UNUSED GdkScreen *prev,
1612 SheetControlGUI *scg)
1614 GdkScreen *screen = gtk_widget_get_screen (widget);
1616 if (screen) {
1617 scg->screen_width = gdk_screen_get_width (screen);
1618 scg->screen_height = gdk_screen_get_height (screen);
1622 SheetControlGUI *
1623 sheet_control_gui_new (SheetView *sv, WBCGtk *wbcg)
1625 SheetControlGUI *scg;
1626 Sheet *sheet;
1627 GocDirection direction;
1628 GdkRGBA cfore, cback;
1630 g_return_val_if_fail (GNM_IS_SV (sv), NULL);
1632 sheet = sv_sheet (sv);
1633 direction = (sheet->text_is_rtl)? GOC_DIRECTION_RTL: GOC_DIRECTION_LTR;
1635 scg = g_object_new (GNM_SCG_TYPE, NULL);
1636 scg->wbcg = wbcg;
1637 scg->sheet_control.wbc = GNM_WBC (wbcg);
1639 g_object_weak_ref (G_OBJECT (wbcg),
1640 (GWeakNotify) cb_wbc_destroyed,
1641 scg);
1643 if (sheet->sheet_type == GNM_SHEET_DATA) {
1644 scg->active_panes = 1;
1645 scg->pane[0] = NULL;
1646 scg->pane[1] = NULL;
1647 scg->pane[2] = NULL;
1648 scg->pane[3] = NULL;
1649 scg->pane_drag_handler = 0;
1651 scg->col_group.buttons = g_ptr_array_new ();
1652 scg->row_group.buttons = g_ptr_array_new ();
1653 scg->col_group.button_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1654 g_object_set (scg->col_group.button_box,
1655 "halign", GTK_ALIGN_CENTER,
1656 "homogeneous", TRUE,
1657 NULL);
1658 scg->row_group.button_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1659 g_object_set (scg->row_group.button_box,
1660 "valign", GTK_ALIGN_CENTER,
1661 "homogeneous", TRUE,
1662 NULL);
1663 scg->select_all_btn = gtk_drawing_area_new ();
1664 gtk_style_context_add_class (gtk_widget_get_style_context (scg->select_all_btn),
1665 GTK_STYLE_CLASS_BUTTON);
1666 gtk_style_context_add_class (gtk_widget_get_style_context (scg->select_all_btn),
1667 "all");
1668 gtk_widget_add_events (scg->select_all_btn, GDK_BUTTON_PRESS_MASK);
1669 g_signal_connect (G_OBJECT (scg->select_all_btn), "draw",
1670 G_CALLBACK (cb_select_all_btn_draw), scg);
1671 g_signal_connect (G_OBJECT (scg->select_all_btn), "event",
1672 G_CALLBACK (cb_select_all_btn_event), scg);
1674 scg->grid = GTK_GRID (gtk_grid_new ());
1675 gtk_grid_attach (scg->grid, scg->col_group.button_box,
1676 1, 0, 1, 1);
1677 gtk_grid_attach (scg->grid, scg->row_group.button_box,
1678 0, 1, 1, 1);
1679 gtk_grid_attach (scg->grid, scg->select_all_btn, 1, 1, 1, 1);
1681 scg->pane[1] = scg->pane[2] = scg->pane[3] = NULL;
1682 scg->pane[0] = gnm_pane_new (scg, TRUE, TRUE, 0);
1683 gnm_pane_set_direction (scg->pane[0], direction);
1684 gtk_grid_attach (scg->grid,
1685 GTK_WIDGET (scg->pane[0]->col.canvas),
1686 3, 0, 1, 2);
1687 gtk_grid_attach (scg->grid,
1688 GTK_WIDGET (scg->pane[0]->row.canvas),
1689 0, 3, 2, 1);
1690 g_object_set (scg->pane[0],
1691 "hexpand", TRUE,
1692 "vexpand", TRUE,
1693 NULL);
1694 gtk_grid_attach (scg->grid, GTK_WIDGET (scg->pane[0]),
1695 3, 3, 1, 1);
1696 g_signal_connect_after (G_OBJECT (scg->pane[0]->col.canvas), "size-allocate",
1697 G_CALLBACK (cb_canvas_resize), scg);
1698 g_signal_connect_after (G_OBJECT (scg->pane[0]->row.canvas), "size-allocate",
1699 G_CALLBACK (cb_canvas_resize), scg);
1701 scg->va = (GtkAdjustment *)gtk_adjustment_new (0., 0., 1, 1., 1., 1.);
1702 scg->vs = g_object_new (GTK_TYPE_SCROLLBAR,
1703 "orientation", GTK_ORIENTATION_VERTICAL,
1704 "adjustment", scg->va,
1705 NULL);
1706 g_signal_connect (G_OBJECT (scg->vs),
1707 "value_changed",
1708 G_CALLBACK (cb_vscrollbar_value_changed), scg);
1709 g_signal_connect (G_OBJECT (scg->vs),
1710 "adjust_bounds",
1711 G_CALLBACK (cb_vscrollbar_adjust_bounds), sheet);
1713 scg->ha = (GtkAdjustment *)gtk_adjustment_new (0., 0., 1, 1., 1., 1.);
1714 scg->hs = g_object_new (GTK_TYPE_SCROLLBAR,
1715 "adjustment", scg->ha,
1716 NULL);
1717 g_signal_connect (G_OBJECT (scg->hs),
1718 "value_changed",
1719 G_CALLBACK (cb_hscrollbar_value_changed), scg);
1720 g_signal_connect (G_OBJECT (scg->hs),
1721 "adjust_bounds",
1722 G_CALLBACK (cb_hscrollbar_adjust_bounds), sheet);
1724 g_object_ref (scg->grid);
1725 scg->vpane = g_object_new (GTK_TYPE_PANED, "orientation", GTK_ORIENTATION_VERTICAL, NULL);
1726 gtk_paned_add1 (scg->vpane, gtk_label_new (NULL)); /* use a spacer */
1727 gtk_paned_add2 (scg->vpane, scg->vs);
1728 scg_gtk_paned_set_position (scg, scg->vpane, 0);
1729 gtk_widget_set_vexpand (GTK_WIDGET (scg->vpane), TRUE);
1730 gtk_grid_attach (scg->grid,
1731 GTK_WIDGET (scg->vpane), 4, 0, 1, 4);
1732 scg->hpane = g_object_new (GTK_TYPE_PANED, NULL);
1733 gtk_paned_add1 (scg->hpane, gtk_label_new (NULL)); /* use a spacer */
1734 gtk_paned_add2 (scg->hpane, scg->hs);
1735 scg_gtk_paned_set_position (scg, scg->hpane, 0);
1736 gtk_widget_set_hexpand (GTK_WIDGET (scg->hpane), TRUE);
1737 gtk_grid_attach (scg->grid,
1738 GTK_WIDGET (scg->hpane), 0, 4, 4, 1);
1739 /* do not connect until after setting position */
1740 g_signal_connect (G_OBJECT (scg->vpane), "notify::position",
1741 G_CALLBACK (cb_resize_pane_motion), scg);
1742 g_signal_connect (G_OBJECT (scg->hpane), "notify::position",
1743 G_CALLBACK (cb_resize_pane_motion), scg);
1744 g_signal_connect_after (G_OBJECT (scg->vpane), "size-allocate",
1745 G_CALLBACK (cb_check_resize), scg);
1746 g_signal_connect_after (G_OBJECT (scg->hpane), "size-allocate",
1747 G_CALLBACK (cb_check_resize), scg);
1749 g_signal_connect_data (G_OBJECT (scg->grid),
1750 "size-allocate",
1751 G_CALLBACK (scg_scrollbar_config), scg, NULL,
1752 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1753 g_signal_connect_object (G_OBJECT (scg->grid),
1754 "destroy",
1755 G_CALLBACK (cb_table_destroy), G_OBJECT (scg),
1756 G_CONNECT_SWAPPED);
1758 sv_attach_control (sv, GNM_SC (scg));
1760 g_object_connect (G_OBJECT (sheet),
1761 "swapped_signal::notify::text-is-rtl", cb_scg_direction_changed, scg,
1762 "swapped_signal::notify::display-formulas", cb_scg_redraw, scg,
1763 "swapped_signal::notify::display-zeros", cb_scg_redraw, scg,
1764 "swapped_signal::notify::display-grid", cb_scg_redraw, scg,
1765 "swapped_signal::notify::display-column-header", scg_adjust_preferences, scg,
1766 "swapped_signal::notify::display-row-header", scg_adjust_preferences, scg,
1767 "swapped_signal::notify::use-r1c1", cb_scg_redraw, scg,
1768 "swapped_signal::notify::display-outlines", cb_scg_redraw_resize, scg,
1769 "swapped_signal::notify::display-outlines-below", cb_scg_redraw_resize, scg,
1770 "swapped_signal::notify::display-outlines-right", cb_scg_redraw_resize, scg,
1771 "swapped_signal::notify::columns", cb_scg_sheet_resized, scg,
1772 "swapped_signal::notify::rows", cb_scg_sheet_resized, scg,
1773 NULL);
1774 } else {
1775 scg->active_panes = 0;
1776 scg->grid = GTK_GRID (gtk_grid_new ());
1777 g_object_ref (scg->grid);
1778 sheet->hide_col_header = sheet->hide_row_header = FALSE;
1779 if (sheet->sheet_type == GNM_SHEET_OBJECT) {
1780 /* WHY store this in ->vs? */
1781 scg->vs = g_object_new (GOC_TYPE_CANVAS,
1782 "hexpand", TRUE,
1783 "vexpand", TRUE,
1784 NULL);
1785 gtk_style_context_add_class (gtk_widget_get_style_context (scg->vs),
1786 "full-sheet");
1787 gtk_grid_attach (scg->grid, scg->vs, 0, 0, 1, 1);
1788 gtk_widget_set_can_focus (scg->vs, TRUE);
1789 gtk_widget_set_can_default (scg->vs, TRUE);
1790 g_signal_connect (G_OBJECT (scg->vs), "key-press-event",
1791 G_CALLBACK (sheet_object_key_pressed), scg);
1793 sv_attach_control (sv, GNM_SC (scg));
1794 if (scg->vs) {
1795 g_object_set_data (G_OBJECT (scg->vs), "sheet-control", scg);
1796 if (sheet->sheet_objects) {
1797 /* we need an idle function because not everything is initialized at this point */
1798 sheet_object_new_view ((SheetObject *) sheet->sheet_objects->data,
1799 (SheetObjectViewContainer*) scg->vs);
1800 g_idle_add ((GSourceFunc) post_create_cb, scg);
1805 scg->label = g_object_new
1806 (GNM_NOTEBOOK_BUTTON_TYPE,
1807 "label", sheet->name_unquoted,
1808 //"valign", GTK_ALIGN_START,
1809 "background-color",
1810 (sheet->tab_color
1811 ? go_color_to_gdk_rgba (sheet->tab_color->go_color,
1812 &cback)
1813 : NULL),
1814 "text-color",
1815 (sheet->tab_text_color
1816 ? go_color_to_gdk_rgba (sheet->tab_text_color->go_color,
1817 &cfore)
1818 : NULL),
1819 NULL);
1820 g_object_ref (scg->label);
1822 g_signal_connect (G_OBJECT (scg->grid),
1823 "screen-changed",
1824 G_CALLBACK (cb_screen_changed),
1825 scg);
1827 return scg;
1830 static void
1831 scg_comment_timer_clear (SheetControlGUI *scg)
1833 if (scg->comment.timer != 0) {
1834 g_source_remove (scg->comment.timer);
1835 scg->comment.timer = 0;
1839 static void
1840 scg_im_destroy (SheetControlGUI *scg) {
1841 if (scg->im.timer != 0) {
1842 g_source_remove (scg->im.timer);
1843 scg->im.timer = 0;
1845 if (scg->im.item) {
1846 gtk_widget_destroy (scg->im.item);
1847 scg->im.item = NULL;
1851 static void
1852 scg_finalize (GObject *object)
1854 SheetControlGUI *scg = GNM_SCG (object);
1855 SheetControl *sc = (SheetControl *) scg;
1856 Sheet *sheet = scg_sheet (scg);
1857 GSList *ptr;
1859 /* remove the object view before we disappear */
1860 scg_object_unselect (scg, NULL);
1861 if (*scg->pane)
1862 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1863 SCG_FOREACH_PANE (scg, pane,
1864 g_object_unref (
1865 sheet_object_get_view (ptr->data, (SheetObjectViewContainer *)pane));
1868 if (scg->col_group.buttons) {
1869 g_ptr_array_free (scg->col_group.buttons, TRUE);
1870 g_ptr_array_free (scg->row_group.buttons, TRUE);
1873 if (scg->pane_drag_handler) {
1874 g_source_remove (scg->pane_drag_handler);
1875 scg->pane_drag_handler = 0;
1878 if (scg->scroll_bar_timer) {
1879 g_source_remove (scg->scroll_bar_timer);
1880 scg->scroll_bar_timer = 0;
1883 scg_comment_timer_clear (scg);
1885 if (scg->delayedMovement.timer != 0) {
1886 g_source_remove (scg->delayedMovement.timer);
1887 scg->delayedMovement.timer = 0;
1889 scg_comment_unselect (scg, scg->comment.selected);
1891 scg_im_destroy (scg);
1893 if (sc->view) {
1894 Sheet *sheet = sv_sheet (sc->view);
1895 g_signal_handlers_disconnect_by_func (sheet, scg_adjust_preferences, scg);
1896 g_signal_handlers_disconnect_by_func (sheet, cb_scg_redraw, scg);
1897 g_signal_handlers_disconnect_by_func (sheet, cb_scg_redraw_resize, scg);
1898 g_signal_handlers_disconnect_by_func (sheet, cb_scg_sheet_resized, scg);
1899 g_signal_handlers_disconnect_by_func (sheet, cb_scg_direction_changed, scg);
1900 sv_detach_control (sc);
1903 if (scg->grid) {
1904 gtk_widget_destroy (GTK_WIDGET (scg->grid));
1905 g_object_unref (scg->grid);
1906 scg->grid = NULL;
1909 g_clear_object (&scg->label);
1911 if (scg->wbcg != NULL)
1912 g_object_weak_unref (G_OBJECT (scg->wbcg),
1913 (GWeakNotify) cb_wbc_destroyed,
1914 scg);
1916 (*scg_parent_class->finalize) (object);
1919 static void
1920 scg_unant (SheetControl *sc)
1922 SheetControlGUI *scg = (SheetControlGUI *)sc;
1924 g_return_if_fail (GNM_IS_SCG (scg));
1926 /* Always have a pane 0 */
1927 if (scg->active_panes == 0 || scg->pane[0]->cursor.animated == NULL)
1928 return;
1930 SCG_FOREACH_PANE (scg, pane, {
1931 GSList *l;
1933 for (l = pane->cursor.animated; l; l = l->next) {
1934 GocItem *item = l->data;
1935 goc_item_destroy (item);
1938 g_slist_free (pane->cursor.animated);
1939 pane->cursor.animated = NULL;
1943 static void
1944 scg_ant (SheetControl *sc)
1946 SheetControlGUI *scg = (SheetControlGUI *)sc;
1947 GList *l;
1949 g_return_if_fail (GNM_IS_SCG (scg));
1951 if (scg->active_panes == 0)
1952 return;
1954 /* Always have a grid 0 */
1955 if (NULL != scg->pane[0]->cursor.animated)
1956 scg_unant (sc);
1958 for (l = sc->view->ants; l; l = l->next) {
1959 GnmRange const *r = l->data;
1961 SCG_FOREACH_PANE (scg, pane, {
1962 GnmItemCursor *ic = GNM_ITEM_CURSOR (goc_item_new (
1963 pane->grid_items,
1964 gnm_item_cursor_get_type (),
1965 "SheetControlGUI", scg,
1966 "style", GNM_ITEM_CURSOR_ANTED,
1967 NULL));
1968 gnm_item_cursor_bound_set (ic, r);
1969 pane->cursor.animated =
1970 g_slist_prepend (pane->cursor.animated, ic);
1975 void
1976 scg_adjust_preferences (SheetControlGUI *scg)
1978 Sheet const *sheet = scg_sheet (scg);
1980 SCG_FOREACH_PANE (scg, pane, {
1981 if (pane->col.canvas != NULL) {
1982 gtk_widget_set_visible (GTK_WIDGET (pane->col.canvas),
1983 !sheet->hide_col_header);
1986 if (pane->row.canvas != NULL) {
1987 gtk_widget_set_visible (GTK_WIDGET (pane->row.canvas),
1988 !sheet->hide_row_header);
1992 if (scg->select_all_btn) {
1993 /* we used to test for the corner table existence, why??? */
1994 gboolean visible = !(sheet->hide_col_header || sheet->hide_row_header);
1995 gtk_widget_set_visible (scg->select_all_btn, visible);
1996 gtk_widget_set_visible (scg->row_group.button_box, visible);
1997 gtk_widget_set_visible (scg->col_group.button_box, visible);
1999 if (scg_wbc (scg) != NULL) {
2000 WorkbookView *wbv = wb_control_view (scg_wbc (scg));
2001 gtk_widget_set_visible (scg->hs,
2002 wbv->show_horizontal_scrollbar);
2004 gtk_widget_set_visible (scg->vs,
2005 wbv->show_vertical_scrollbar);
2010 /***************************************************************************/
2012 enum {
2013 CONTEXT_CUT = 1,
2014 CONTEXT_COPY,
2015 CONTEXT_PASTE,
2016 CONTEXT_PASTE_SPECIAL,
2017 CONTEXT_INSERT,
2018 CONTEXT_DELETE,
2019 CONTEXT_CLEAR_CONTENT,
2020 CONTEXT_FORMAT_CELL,
2021 CONTEXT_FORMAT_CELL_COND,
2022 CONTEXT_CELL_AUTOFIT_WIDTH,
2023 CONTEXT_CELL_AUTOFIT_HEIGHT,
2024 CONTEXT_CELL_MERGE,
2025 CONTEXT_CELL_UNMERGE,
2026 CONTEXT_COL_WIDTH,
2027 CONTEXT_COL_HIDE,
2028 CONTEXT_COL_UNHIDE,
2029 CONTEXT_COL_AUTOFIT,
2030 CONTEXT_ROW_HEIGHT,
2031 CONTEXT_ROW_HIDE,
2032 CONTEXT_ROW_UNHIDE,
2033 CONTEXT_ROW_AUTOFIT,
2034 CONTEXT_COMMENT_EDIT,
2035 CONTEXT_COMMENT_ADD,
2036 CONTEXT_COMMENT_REMOVE,
2037 CONTEXT_HYPERLINK_EDIT,
2038 CONTEXT_HYPERLINK_ADD,
2039 CONTEXT_HYPERLINK_REMOVE,
2040 CONTEXT_DATA_SLICER_REFRESH, /* refresh and redraw */
2041 CONTEXT_DATA_SLICER_EDIT /* prop dialog */
2043 static void
2044 context_menu_handler (GnmPopupMenuElement const *element,
2045 gpointer user_data)
2047 SheetControlGUI *scg = user_data;
2048 SheetControl *sc = (SheetControl *) scg;
2049 SheetView *sv = sc->view;
2050 Sheet *sheet = sv->sheet;
2051 WBCGtk *wbcg = scg->wbcg;
2052 WorkbookControl *wbc = sc->wbc;
2054 g_return_if_fail (element != NULL);
2055 g_return_if_fail (IS_SHEET (sheet));
2057 switch (element->index) {
2058 case CONTEXT_CUT:
2059 sv_selection_cut (sv, wbc);
2060 break;
2061 case CONTEXT_COPY:
2062 sv_selection_copy (sv, wbc);
2063 break;
2064 case CONTEXT_PASTE:
2065 cmd_paste_to_selection (wbc, sv, PASTE_DEFAULT);
2066 break;
2067 case CONTEXT_PASTE_SPECIAL:
2068 dialog_paste_special (wbcg);
2069 break;
2070 case CONTEXT_INSERT:
2071 dialog_insert_cells (wbcg);
2072 break;
2073 case CONTEXT_DELETE:
2074 dialog_delete_cells (wbcg);
2075 break;
2076 case CONTEXT_CLEAR_CONTENT:
2077 cmd_selection_clear (wbc, CLEAR_VALUES);
2078 break;
2079 case CONTEXT_FORMAT_CELL:
2080 dialog_cell_format (wbcg, FD_CURRENT, 0);
2081 break;
2082 case CONTEXT_FORMAT_CELL_COND:
2083 dialog_cell_format_cond (wbcg);
2084 break;
2085 case CONTEXT_CELL_AUTOFIT_HEIGHT:
2086 workbook_cmd_autofit_selection
2087 (wbc, wb_control_cur_sheet (wbc), FALSE);
2088 break;
2089 case CONTEXT_CELL_AUTOFIT_WIDTH:
2090 workbook_cmd_autofit_selection
2091 (wbc, wb_control_cur_sheet (wbc), TRUE);
2092 break;
2093 case CONTEXT_CELL_MERGE : {
2094 GSList *range_list = selection_get_ranges
2095 (wb_control_cur_sheet_view (wbc), FALSE);
2096 cmd_merge_cells (wbc, wb_control_cur_sheet (wbc), range_list, FALSE);
2097 range_fragment_free (range_list);
2099 break;
2100 case CONTEXT_CELL_UNMERGE : {
2101 GSList *range_list = selection_get_ranges
2102 (wb_control_cur_sheet_view (wbc), FALSE);
2103 cmd_unmerge_cells (wbc, wb_control_cur_sheet (wbc), range_list);
2104 range_fragment_free (range_list);
2107 break;
2108 case CONTEXT_COL_WIDTH:
2109 dialog_col_width (wbcg, FALSE);
2110 break;
2111 case CONTEXT_COL_AUTOFIT:
2112 workbook_cmd_resize_selected_colrow
2113 (wbc, wb_control_cur_sheet (wbc), TRUE, -1);
2114 break;
2115 case CONTEXT_COL_HIDE:
2116 cmd_selection_colrow_hide (wbc, TRUE, FALSE);
2117 break;
2118 case CONTEXT_COL_UNHIDE:
2119 cmd_selection_colrow_hide (wbc, TRUE, TRUE);
2120 break;
2121 case CONTEXT_ROW_HEIGHT:
2122 dialog_row_height (wbcg, FALSE);
2123 break;
2124 case CONTEXT_ROW_AUTOFIT:
2125 workbook_cmd_resize_selected_colrow
2126 (wbc, wb_control_cur_sheet (wbc), FALSE, -1);
2127 break;
2128 case CONTEXT_ROW_HIDE:
2129 cmd_selection_colrow_hide (wbc, FALSE, FALSE);
2130 break;
2131 case CONTEXT_ROW_UNHIDE:
2132 cmd_selection_colrow_hide (wbc, FALSE, TRUE);
2133 break;
2134 case CONTEXT_COMMENT_EDIT:
2135 case CONTEXT_COMMENT_ADD:
2136 dialog_cell_comment (wbcg, sheet, &sv->edit_pos);
2137 break;
2138 case CONTEXT_COMMENT_REMOVE:
2139 cmd_selection_clear (GNM_WBC (wbcg), CLEAR_COMMENTS);
2140 break;
2141 case CONTEXT_HYPERLINK_EDIT:
2142 case CONTEXT_HYPERLINK_ADD:
2143 dialog_hyperlink (wbcg, sc);
2144 break;
2146 case CONTEXT_HYPERLINK_REMOVE: {
2147 GnmStyle *style = gnm_style_new ();
2148 GSList *l;
2149 int n_links = 0;
2150 gchar const *format;
2151 gchar *name;
2153 for (l = scg_view (scg)->selections; l != NULL; l = l->next) {
2154 GnmRange const *r = l->data;
2155 GnmStyleList *styles;
2157 styles = sheet_style_collect_hlinks (sheet, r);
2158 n_links += g_slist_length (styles);
2159 style_list_free (styles);
2161 format = ngettext ("Remove %d Link", "Remove %d Links", n_links);
2162 name = g_strdup_printf (format, n_links);
2163 gnm_style_set_hlink (style, NULL);
2164 cmd_selection_format (wbc, style, NULL, name);
2165 g_free (name);
2166 break;
2168 case CONTEXT_DATA_SLICER_REFRESH:
2169 cmd_slicer_refresh (wbc);
2170 break;
2171 case CONTEXT_DATA_SLICER_EDIT:
2172 dialog_data_slicer (wbcg, FALSE);
2173 break;
2175 default:
2176 break;
2180 void
2181 scg_context_menu (SheetControlGUI *scg, GdkEvent *event,
2182 gboolean is_col, gboolean is_row)
2184 SheetView *sv = scg_view (scg);
2185 Sheet *sheet = sv_sheet (sv);
2187 enum {
2188 CONTEXT_DISPLAY_FOR_CELLS = 1 << 0,
2189 CONTEXT_DISPLAY_FOR_ROWS = 1 << 1,
2190 CONTEXT_DISPLAY_FOR_COLS = 1 << 2,
2191 CONTEXT_DISPLAY_WITH_HYPERLINK = 1 << 3,
2192 CONTEXT_DISPLAY_WITHOUT_HYPERLINK = 1 << 4,
2193 CONTEXT_DISPLAY_WITH_HYPERLINK_IN_RANGE = 1 << 5,
2194 CONTEXT_DISPLAY_WITH_DATA_SLICER = 1 << 6,
2195 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW = 1 << 7,
2196 CONTEXT_DISPLAY_WITH_DATA_SLICER_COL = 1 << 8,
2197 CONTEXT_DISPLAY_WITH_COMMENT = 1 << 9,
2198 CONTEXT_DISPLAY_WITHOUT_COMMENT = 1 << 10,
2199 CONTEXT_DISPLAY_WITH_COMMENT_IN_RANGE = 1 << 11
2201 enum {
2202 CONTEXT_DISABLE_PASTE_SPECIAL = 1 << 0,
2203 CONTEXT_DISABLE_FOR_ROWS = 1 << 1,
2204 CONTEXT_DISABLE_FOR_COLS = 1 << 2,
2205 CONTEXT_DISABLE_FOR_CELLS = 1 << 3,
2206 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION = 1 << 4,
2207 CONTEXT_DISABLE_FOR_ALL_COLS = 1 << 5,
2208 CONTEXT_DISABLE_FOR_ALL_ROWS = 1 << 6,
2209 CONTEXT_DISABLE_FOR_NOMERGES = 1 << 7,
2210 CONTEXT_DISABLE_FOR_ONLYMERGES = 1 << 8
2213 /* Note: keep the following two in sync!*/
2214 enum {
2215 POPUPITEM_CUT = 0,
2216 POPUPITEM_COPY,
2217 POPUPITEM_PASTE,
2218 POPUPITEM_PASTESPECIAL,
2219 POPUPITEM_SEP1,
2220 POPUPITEM_INSERT_CELL,
2221 POPUPITEM_DELETE_CELL,
2222 POPUPITEM_INSERT_COLUMN,
2223 POPUPITEM_DELETE_COLUMN,
2224 POPUPITEM_INSERT_ROW,
2225 POPUPITEM_DELETE_ROW,
2226 POPUPITEM_CLEAR_CONTENTS,
2227 POPUPITEM_SEP2,
2228 POPUPITEM_COMMENT_ADD,
2229 POPUPITEM_COMMENT_EDIT,
2230 POPUPITEM_COMMENT_REMOVE,
2231 POPUPITEM_LINK_ADD,
2232 POPUPITEM_LINK_EDIT,
2233 POPUPITEM_LINK_REMOVE,
2234 POPUPITEM_SEP3,
2235 POPUPITEM_DATASLICER_EDIT,
2236 POPUPITEM_DATASLICER_REFRESH,
2237 POPUPITEM_DATASLICER_FIELD_ORDER,
2238 POPUPITEM_DATASLICER_LEFT,
2239 POPUPITEM_DATASLICER_RIGHT,
2240 POPUPITEM_DATASLICER_UP,
2241 POPUPITEM_DATASLICER_DOWN,
2242 POPUPITEM_DATASLICER_SUBMENU,
2243 POPUPITEM_FORMAT
2246 static GnmPopupMenuElement popup_elements[] = {
2247 { N_("Cu_t"), "edit-cut",
2248 0, 0, CONTEXT_CUT, NULL },
2249 { N_("_Copy"), "edit-copy",
2250 0, 0, CONTEXT_COPY, NULL },
2251 { N_("_Paste"), "edit-paste",
2252 0, 0, CONTEXT_PASTE, NULL },
2253 { N_("Paste _Special"), "edit-paste",
2254 0, CONTEXT_DISABLE_PASTE_SPECIAL, CONTEXT_PASTE_SPECIAL, NULL },
2256 { "", NULL, 0, 0, 0, NULL },
2258 { N_("_Insert Cells..."), NULL,
2259 CONTEXT_DISPLAY_FOR_CELLS,
2260 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION, CONTEXT_INSERT, NULL },
2261 { N_("_Delete Cells..."), "edit-delete",
2262 CONTEXT_DISPLAY_FOR_CELLS,
2263 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION, CONTEXT_DELETE, NULL },
2264 { N_("_Insert Column(s)"), "gnumeric-column-add",
2265 CONTEXT_DISPLAY_FOR_COLS,
2266 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION,
2267 CONTEXT_INSERT, NULL },
2268 { N_("_Delete Column(s)"), "gnumeric-column-delete",
2269 CONTEXT_DISPLAY_FOR_COLS,
2270 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION,
2271 CONTEXT_DELETE, NULL },
2272 { N_("_Insert Row(s)"), "gnumeric-row-add",
2273 CONTEXT_DISPLAY_FOR_ROWS,
2274 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION,
2275 CONTEXT_INSERT, NULL },
2276 { N_("_Delete Row(s)"), "gnumeric-row-delete",
2277 CONTEXT_DISPLAY_FOR_ROWS,
2278 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION,
2279 CONTEXT_DELETE, NULL },
2281 { N_("Clear Co_ntents"), "edit-clear",
2282 0, 0, CONTEXT_CLEAR_CONTENT, NULL },
2284 { "", NULL, CONTEXT_DISPLAY_FOR_CELLS, 0, 0, NULL },
2286 { N_("Add _Comment..."), "gnumeric-comment-add",
2287 CONTEXT_DISPLAY_WITHOUT_COMMENT, 0, CONTEXT_COMMENT_ADD, NULL },
2288 { N_("Edit Co_mment..."),"gnumeric-comment-edit",
2289 CONTEXT_DISPLAY_WITH_COMMENT, 0, CONTEXT_COMMENT_EDIT, NULL },
2290 { N_("_Remove Comments"), "gnumeric-comment-delete",
2291 CONTEXT_DISPLAY_WITH_COMMENT_IN_RANGE, 0, CONTEXT_COMMENT_REMOVE, NULL },
2293 { N_("Add _Hyperlink..."), "gnumeric-link-add",
2294 CONTEXT_DISPLAY_WITHOUT_HYPERLINK, 0,
2295 CONTEXT_HYPERLINK_ADD, NULL },
2296 { N_("Edit _Hyperlink..."), "gnumeric-link-edit",
2297 CONTEXT_DISPLAY_WITH_HYPERLINK, 0,
2298 CONTEXT_HYPERLINK_EDIT, NULL },
2299 { N_("_Remove Hyperlink"), "gnumeric-link-delete",
2300 CONTEXT_DISPLAY_WITH_HYPERLINK_IN_RANGE, 0,
2301 CONTEXT_HYPERLINK_REMOVE, NULL },
2303 { "", NULL, 0, 0, 0, NULL },
2305 { N_("_Edit DataSlicer"), NULL,
2306 CONTEXT_DISPLAY_WITH_DATA_SLICER, 0,
2307 CONTEXT_DATA_SLICER_EDIT, NULL },
2308 { N_("_Refresh DataSlicer"), NULL,
2309 CONTEXT_DISPLAY_WITH_DATA_SLICER, 0,
2310 CONTEXT_DATA_SLICER_REFRESH, NULL },
2312 { N_("DataSlicer Field _Order "), NULL,
2313 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW | CONTEXT_DISPLAY_WITH_DATA_SLICER_COL, 0,
2314 -1, NULL }, /* start sub menu */
2315 { N_("Left"), "go-previous",
2316 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW, 0,
2317 CONTEXT_DATA_SLICER_REFRESH, NULL },
2318 { N_("Right"), "go-next",
2319 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW, 0,
2320 CONTEXT_DATA_SLICER_REFRESH, NULL },
2321 { N_("Up"), "go-up",
2322 CONTEXT_DISPLAY_WITH_DATA_SLICER_COL, 0,
2323 CONTEXT_DATA_SLICER_REFRESH, NULL },
2324 { N_("Down"), "go-down",
2325 CONTEXT_DISPLAY_WITH_DATA_SLICER_COL, 0,
2326 CONTEXT_DATA_SLICER_REFRESH, NULL },
2327 { "", NULL,
2328 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW | CONTEXT_DISPLAY_WITH_DATA_SLICER_COL, 0,
2329 -1, NULL }, /* end sub menu */
2331 { N_("_Format All Cells..."), GTK_STOCK_PROPERTIES,
2332 0, 0, CONTEXT_FORMAT_CELL, NULL },
2333 { N_("C_onditional Formatting..."), GTK_STOCK_PROPERTIES,
2334 0, 0, CONTEXT_FORMAT_CELL_COND, NULL },
2335 { N_("Cell"), NULL, 0, 0, -1, NULL},/* start sub menu */
2336 { N_("_Merge"), "gnumeric-cells-merge", 0,
2337 CONTEXT_DISABLE_FOR_ONLYMERGES, CONTEXT_CELL_MERGE, NULL },
2338 { N_("_Unmerge"), "gnumeric-cells-split", 0,
2339 CONTEXT_DISABLE_FOR_NOMERGES, CONTEXT_CELL_UNMERGE, NULL },
2340 { N_("Auto Fit _Width"), "gnumeric-column-size", 0, 0, CONTEXT_CELL_AUTOFIT_WIDTH, NULL },
2341 { N_("Auto Fit _Height"), "gnumeric-row-size", 0, 0, CONTEXT_CELL_AUTOFIT_HEIGHT, NULL },
2342 { "", NULL, 0, 0, -1, NULL},/* end sub menu */
2345 /* Column specific (Note some labels duplicate row labels) */
2346 { N_("Column"), NULL, 0, 0, -1, NULL},/* start sub menu */
2347 { N_("_Width..."), "gnumeric-column-size", 0, 0, CONTEXT_COL_WIDTH, NULL },
2348 { N_("_Auto Fit Width"), "gnumeric-column-size", 0, 0, CONTEXT_COL_AUTOFIT, NULL },
2349 { N_("_Hide"), "gnumeric-column-hide", 0, CONTEXT_DISABLE_FOR_ALL_COLS, CONTEXT_COL_HIDE, NULL },
2350 { N_("_Unhide"), "gnumeric-column-unhide", 0, 0, CONTEXT_COL_UNHIDE, NULL },
2351 { "", NULL, 0, 0, -1, NULL},/* end sub menu */
2353 /* Row specific (Note some labels duplicate col labels) */
2354 { N_("Row"), NULL, 0, 0, -1, NULL},/* start sub menu */
2355 { N_("Hei_ght..."), "gnumeric-row-size", 0, 0, CONTEXT_ROW_HEIGHT, NULL },
2356 { N_("_Auto Fit Height"), "gnumeric-row-size", 0, 0, CONTEXT_ROW_AUTOFIT, NULL },
2357 { N_("_Hide"), "gnumeric-row-hide", 0, CONTEXT_DISABLE_FOR_ALL_ROWS, CONTEXT_ROW_HIDE, NULL },
2358 { N_("_Unhide"), "gnumeric-row-unhide", 0, 0, CONTEXT_ROW_UNHIDE, NULL },
2359 { "", NULL, 0, 0, -1, NULL},/* end sub menu */
2361 { NULL, NULL, 0, 0, 0, NULL },
2364 /* row and column specific operations */
2365 int display_filter =
2366 ((!is_col && !is_row) ? CONTEXT_DISPLAY_FOR_CELLS : 0) |
2367 (is_col ? CONTEXT_DISPLAY_FOR_COLS : 0) |
2368 (is_row ? CONTEXT_DISPLAY_FOR_ROWS : 0);
2370 /* Paste special only applies to local copies, not cuts, or remote
2371 * items
2373 int sensitivity_filter =
2374 (!gnm_app_clipboard_is_empty () &&
2375 !gnm_app_clipboard_is_cut ())
2376 ? 0 : CONTEXT_DISABLE_PASTE_SPECIAL;
2378 GSList *l;
2379 gboolean has_link = FALSE, has_comment = FALSE;
2380 int n_comments = 0, n_links = 0, n_cols = 0, n_rows = 0, n_cells = 0;
2381 GnmSheetSlicer *slicer;
2382 GnmRange rge;
2383 int n_sel = 0;
2384 gboolean full_sheet = FALSE, only_merges = TRUE, no_merges = TRUE;
2386 wbcg_edit_finish (scg->wbcg, WBC_EDIT_REJECT, NULL);
2388 /* Now see if there is some selection which selects a whole row or a
2389 * whole column and disable the insert/delete col/row menu items
2390 * accordingly
2392 for (l = scg_view (scg)->selections; l != NULL; l = l->next) {
2393 GnmRange const *r = l->data;
2394 GnmRange const *merge;
2395 GSList *objs, *merges;
2396 GnmStyleList *styles;
2397 int h, w;
2398 gboolean rfull_h = range_is_full (r, sheet, TRUE);
2399 gboolean rfull_v = range_is_full (r, sheet, FALSE);
2401 n_sel++;
2403 if (!range_is_singleton (r)) {
2404 merge = gnm_sheet_merge_is_corner (sheet, &(r->start));
2405 if (NULL == merge || !range_equal (merge, r))
2406 only_merges = FALSE;
2407 merges = gnm_sheet_merge_get_overlap (sheet, r);
2408 if (merges != NULL) {
2409 no_merges = FALSE;
2410 g_slist_free (merges);
2414 if (rfull_v) {
2415 display_filter |= CONTEXT_DISPLAY_FOR_COLS;
2416 display_filter &= ~CONTEXT_DISPLAY_FOR_CELLS;
2417 sensitivity_filter |= CONTEXT_DISABLE_FOR_ALL_ROWS;
2418 } else
2419 sensitivity_filter |= CONTEXT_DISABLE_FOR_ROWS;
2422 if (rfull_h) {
2423 display_filter |= CONTEXT_DISPLAY_FOR_ROWS;
2424 display_filter &= ~CONTEXT_DISPLAY_FOR_CELLS;
2425 sensitivity_filter |= CONTEXT_DISABLE_FOR_ALL_COLS;
2426 } else
2427 sensitivity_filter |= CONTEXT_DISABLE_FOR_COLS;
2429 if (!(rfull_h || rfull_v))
2430 sensitivity_filter |= CONTEXT_DISABLE_FOR_CELLS;
2432 full_sheet = full_sheet || (rfull_h && rfull_v);
2434 h = range_height (r);
2435 w = range_width (r);
2436 n_cols += w;
2437 n_rows += h;
2438 n_cells += w * h;
2440 styles = sheet_style_collect_hlinks (sheet, r);
2441 n_links += g_slist_length (styles);
2442 style_list_free (styles);
2444 objs = sheet_objects_get (sheet, r, GNM_CELL_COMMENT_TYPE);
2445 n_comments += g_slist_length (objs);
2446 g_slist_free (objs);
2449 if (only_merges)
2450 sensitivity_filter |= CONTEXT_DISABLE_FOR_ONLYMERGES;
2451 if (no_merges)
2452 sensitivity_filter |= CONTEXT_DISABLE_FOR_NOMERGES;
2455 if ((display_filter & CONTEXT_DISPLAY_FOR_COLS) &&
2456 (display_filter & CONTEXT_DISPLAY_FOR_ROWS))
2457 display_filter = 0;
2458 if (n_sel > 1)
2459 sensitivity_filter |= CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION;
2461 has_comment = (sheet_get_comment (sheet, &sv->edit_pos) != NULL);
2462 range_init_cellpos (&rge, &sv->edit_pos);
2463 has_link = (NULL != sheet_style_region_contains_link (sheet, &rge));
2465 slicer = sv_editpos_in_slicer (scg_view (scg));
2466 /* FIXME: disabled for now */
2467 if (0 && slicer) {
2468 GODataSlicerField *dsf = gnm_sheet_slicer_field_header_at_pos (slicer, &sv->edit_pos);
2469 if (NULL != dsf) {
2470 if (go_data_slicer_field_get_field_type_pos (dsf, GDS_FIELD_TYPE_COL) >= 0)
2471 display_filter |= CONTEXT_DISPLAY_WITH_DATA_SLICER_COL;
2472 if (go_data_slicer_field_get_field_type_pos (dsf, GDS_FIELD_TYPE_ROW) >= 0)
2473 display_filter |= CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW;
2475 display_filter |= CONTEXT_DISPLAY_WITH_DATA_SLICER;
2476 display_filter &= ~CONTEXT_DISPLAY_FOR_CELLS;
2479 if (display_filter & CONTEXT_DISPLAY_FOR_CELLS) {
2480 char const *format;
2481 display_filter |= ((has_link) ?
2482 CONTEXT_DISPLAY_WITH_HYPERLINK : CONTEXT_DISPLAY_WITHOUT_HYPERLINK);
2483 display_filter |= ((n_links > 0) ?
2484 CONTEXT_DISPLAY_WITH_HYPERLINK_IN_RANGE : CONTEXT_DISPLAY_WITHOUT_HYPERLINK);
2485 display_filter |= ((has_comment) ?
2486 CONTEXT_DISPLAY_WITH_COMMENT : CONTEXT_DISPLAY_WITHOUT_COMMENT);
2487 display_filter |= ((n_comments > 0) ?
2488 CONTEXT_DISPLAY_WITH_COMMENT_IN_RANGE : CONTEXT_DISPLAY_WITHOUT_COMMENT);
2489 if (n_links > 0) {
2490 /* xgettext : %d gives the number of links. This is input to ngettext. */
2491 format = ngettext ("_Remove %d Link", "_Remove %d Links", n_links);
2492 popup_elements[POPUPITEM_LINK_REMOVE].allocated_name = g_strdup_printf (format, n_links);
2494 if (n_comments > 0) {
2495 /* xgettext : %d gives the number of comments. This is input to ngettext. */
2496 format = ngettext ("_Remove %d Comment", "_Remove %d Comments", n_comments);
2497 popup_elements[POPUPITEM_COMMENT_REMOVE].allocated_name = g_strdup_printf (format, n_comments);
2499 format = ngettext ("_Insert %d Cell...", "_Insert %d Cells...", n_cells);
2500 popup_elements[POPUPITEM_INSERT_CELL].allocated_name = g_strdup_printf (format, n_cells);
2501 format = ngettext ("_Delete %d Cell...", "_Delete %d Cells...", n_cells);
2502 popup_elements[POPUPITEM_DELETE_CELL].allocated_name = g_strdup_printf (format, n_cells);
2505 if (display_filter & CONTEXT_DISPLAY_FOR_COLS) {
2506 char const *format;
2507 format = ngettext ("_Insert %d Column", "_Insert %d Columns", n_cols);
2508 popup_elements[POPUPITEM_INSERT_COLUMN].allocated_name = g_strdup_printf (format, n_cols);
2509 format = ngettext ("_Delete %d Column", "_Delete %d Columns", n_cols);
2510 popup_elements[POPUPITEM_DELETE_COLUMN].allocated_name = g_strdup_printf (format, n_cols);
2511 if (!(sensitivity_filter & (CONTEXT_DISABLE_FOR_CELLS | CONTEXT_DISABLE_FOR_ROWS))) {
2512 format = ngettext ("_Format %d Column", "_Format %d Columns", n_cols);
2513 popup_elements[POPUPITEM_FORMAT].allocated_name
2514 = g_strdup_printf (format, n_cols);
2517 if (display_filter & CONTEXT_DISPLAY_FOR_ROWS) {
2518 char const *format;
2519 format = ngettext ("_Insert %d Row", "_Insert %d Rows", n_rows);
2520 popup_elements[POPUPITEM_INSERT_ROW].allocated_name = g_strdup_printf (format, n_rows);
2521 format = ngettext ("_Delete %d Row", "_Delete %d Rows", n_rows);
2522 popup_elements[POPUPITEM_DELETE_ROW].allocated_name = g_strdup_printf (format, n_rows);
2524 if (!(sensitivity_filter & (CONTEXT_DISABLE_FOR_CELLS | CONTEXT_DISABLE_FOR_COLS))) {
2525 format = ngettext ("_Format %d Row", "_Format %d Rows", n_rows);
2526 popup_elements[POPUPITEM_FORMAT].allocated_name
2527 = g_strdup_printf (format, n_rows);
2530 if (!popup_elements[POPUPITEM_FORMAT].allocated_name && !full_sheet) {
2531 char const *format;
2532 format = ngettext ("_Format %d Cell...", "_Format %d Cells", n_cells);
2533 popup_elements[POPUPITEM_FORMAT].allocated_name = g_strdup_printf (format, n_cells);
2537 gnm_create_popup_menu (popup_elements, &context_menu_handler,
2538 scg, display_filter,
2539 sensitivity_filter, event);
2542 static gboolean
2543 cb_redraw_sel (G_GNUC_UNUSED SheetView *sv, GnmRange const *r, gpointer user_data)
2545 SheetControl *sc = user_data;
2546 scg_redraw_range (sc, r);
2547 scg_redraw_headers (sc, TRUE, TRUE, r);
2548 return TRUE;
2551 void
2552 scg_cursor_visible (SheetControlGUI *scg, gboolean is_visible)
2554 SheetControl *sc = (SheetControl *) scg;
2556 /* there is always a grid 0 */
2557 if (NULL == scg->pane[0])
2558 return;
2560 SCG_FOREACH_PANE (scg, pane,
2561 gnm_item_cursor_set_visibility (pane->cursor.std, is_visible););
2563 sv_selection_foreach (sc->view, cb_redraw_sel, sc);
2566 /***************************************************************************/
2569 * scg_mode_edit:
2570 * @scg: The sheet control
2572 * Put @sheet into the standard state 'edit mode'. This shuts down
2573 * any object editing and frees any objects that are created but not
2574 * realized.
2576 void
2577 scg_mode_edit (SheetControlGUI *scg)
2579 WBCGtk *wbcg;
2580 g_return_if_fail (GNM_IS_SCG (scg));
2582 wbcg = scg->wbcg;
2584 if (wbcg != NULL) /* Can be NULL during destruction */
2585 wbcg_insert_object_clear (wbcg);
2587 scg_object_unselect (scg, NULL);
2589 /* During destruction we have already been disconnected
2590 * so don't bother changing the cursor */
2591 if (scg->grid != NULL &&
2592 scg_sheet (scg) != NULL &&
2593 scg_view (scg) != NULL) {
2594 scg_set_display_cursor (scg);
2595 scg_cursor_visible (scg, TRUE);
2598 if (wbcg != NULL && wbc_gtk_get_guru (wbcg) != NULL &&
2599 scg == wbcg_cur_scg (wbcg))
2600 wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
2602 if (wbcg)
2603 wb_control_update_action_sensitivity (GNM_WBC (wbcg));
2606 static void
2607 scg_mode_edit_virt (SheetControl *sc)
2609 scg_mode_edit ((SheetControlGUI *)sc);
2612 static int
2613 calc_obj_place (GnmPane *pane, gint64 canvas_coord, gboolean is_col,
2614 double *offset)
2616 gint64 origin;
2617 int colrow;
2618 ColRowInfo const *cri;
2619 Sheet const *sheet = scg_sheet (pane->simple.scg);
2621 if (is_col) {
2622 colrow = gnm_pane_find_col (pane, canvas_coord, &origin);
2623 cri = sheet_col_get_info (sheet, colrow);
2624 } else {
2625 colrow = gnm_pane_find_row (pane, canvas_coord, &origin);
2626 cri = sheet_row_get_info (sheet, colrow);
2629 /* TODO : handle other anchor types */
2630 *offset = (canvas_coord - origin) / (double)cri->size_pixels;
2631 return colrow;
2634 #define SO_CLASS(so) GNM_SO_CLASS (G_OBJECT_GET_CLASS(so))
2637 * scg_object_select:
2638 * @scg: The #SheetControl to edit in.
2639 * @so: The #SheetObject to select.
2641 * Adds @so to the set of selected objects and prepares it for user editing.
2642 * Adds a reference to @ref if it is selected.
2644 void
2645 scg_object_select (SheetControlGUI *scg, SheetObject *so)
2647 double *coords;
2649 if (scg->selected_objects == NULL) {
2650 if (wb_view_is_protected (sv_wbv (scg_view (scg)), TRUE) ||
2651 !wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
2652 return;
2653 g_object_ref (so);
2655 wbcg_insert_object_clear (scg->wbcg);
2656 scg_cursor_visible (scg, FALSE);
2657 scg_set_display_cursor (scg);
2658 scg_unant (GNM_SC (scg));
2660 scg->selected_objects = g_hash_table_new_full (
2661 g_direct_hash, g_direct_equal,
2662 (GDestroyNotify) g_object_unref, (GDestroyNotify) g_free);
2663 wb_control_update_action_sensitivity (scg_wbc (scg));
2664 } else {
2665 g_return_if_fail (g_hash_table_lookup (scg->selected_objects, so) == NULL);
2666 g_object_ref (so);
2669 coords = g_new (double, 4);
2670 scg_object_anchor_to_coords (scg, sheet_object_get_anchor (so), coords);
2671 g_hash_table_insert (scg->selected_objects, so, coords);
2672 g_signal_connect_object (so, "unrealized",
2673 G_CALLBACK (scg_mode_edit), scg, G_CONNECT_SWAPPED);
2675 SCG_FOREACH_PANE (scg, pane,
2676 gnm_pane_object_update_bbox (pane, so););
2679 static void
2680 cb_scg_object_unselect (SheetObject *so, G_GNUC_UNUSED double *coords, SheetControlGUI *scg)
2682 SCG_FOREACH_PANE (scg, pane, gnm_pane_object_unselect (pane, so););
2683 g_signal_handlers_disconnect_by_func (so,
2684 scg_mode_edit, scg);
2688 * scg_object_unselect:
2689 * @scg: #SheetControlGUI
2690 * @so: #SheetObject (optionally NULL)
2692 * unselect the supplied object, and drop out of edit mode if this is the last
2693 * one. If @so == NULL unselect _all_ objects.
2695 void
2696 scg_object_unselect (SheetControlGUI *scg, SheetObject *so)
2698 WorkbookControl *wbc = scg_wbc (scg);
2700 /* cheesy cycle avoidance */
2701 if (scg->selected_objects == NULL)
2702 return;
2704 if (so != NULL) {
2705 double *pts = g_hash_table_lookup (scg->selected_objects, so);
2706 g_return_if_fail (pts != NULL);
2707 cb_scg_object_unselect (so, pts, scg);
2708 g_hash_table_remove (scg->selected_objects, so);
2709 if (g_hash_table_size (scg->selected_objects) > 0)
2710 return;
2711 } else
2712 g_hash_table_foreach (scg->selected_objects,
2713 (GHFunc) cb_scg_object_unselect, scg);
2715 g_hash_table_destroy (scg->selected_objects);
2716 scg->selected_objects = NULL;
2717 scg_mode_edit (scg);
2718 if (wbc)
2719 wb_control_update_action_sensitivity (wbc);
2722 void
2723 scg_object_select_next (SheetControlGUI *scg, gboolean reverse)
2725 Sheet *sheet = scg_sheet (scg);
2726 GSList *ptr = sheet->sheet_objects;
2728 g_return_if_fail (ptr != NULL);
2730 if ((scg->selected_objects == NULL) ||
2731 (g_hash_table_size (scg->selected_objects) == 0)) {
2732 scg_object_select (scg, ptr->data);
2733 return;
2734 } else {
2735 GSList *prev = NULL;
2736 for (; ptr != NULL ; prev = ptr, ptr = ptr->next)
2737 if (NULL != g_hash_table_lookup
2738 (scg->selected_objects, ptr->data)) {
2739 SheetObject *target;
2740 if (reverse) {
2741 if (ptr->next == NULL)
2742 target = sheet->sheet_objects->data;
2743 else
2744 target = ptr->next->data;
2745 } else {
2746 if (NULL == prev) {
2747 GSList *last = g_slist_last (ptr);
2748 target = last->data;
2749 } else
2750 target = prev->data;
2752 if (ptr->data != target) {
2753 scg_object_unselect (scg, NULL);
2754 scg_object_select (scg, target);
2755 return;
2761 typedef struct {
2762 SheetControlGUI *scg;
2763 GnmPane *pane;
2764 SheetObject *primary_object;
2765 int drag_type;
2766 double dx, dy;
2767 gboolean symmetric;
2768 gboolean snap_to_grid;
2769 gboolean is_mouse_move;
2770 } ObjDragInfo;
2772 static double
2773 snap_pos_to_grid (ObjDragInfo const *info, gboolean is_col, double pos,
2774 gboolean to_min)
2776 GnmPane const *pane = info->pane;
2777 Sheet const *sheet = scg_sheet (info->scg);
2778 int cell = is_col ? pane->first.col : pane->first.row;
2779 gint64 pixel = is_col ? pane->first_offset.x : pane->first_offset.y;
2780 gboolean snap = FALSE;
2781 int length = 0;
2782 ColRowInfo const *cr_info;
2783 int sheet_max = colrow_max (is_col, sheet);
2785 if (pos < pixel) {
2786 while (cell > 0 && pos < pixel) {
2787 cr_info = sheet_colrow_get_info (sheet, --cell, is_col);
2788 if (cr_info->visible) {
2789 length = cr_info->size_pixels;
2790 pixel -= length;
2793 if (pos < pixel)
2794 pos = pixel;
2795 } else {
2796 do {
2797 cr_info = sheet_colrow_get_info (sheet, cell, is_col);
2798 if (cr_info->visible) {
2799 length = cr_info->size_pixels;
2800 if (pixel <= pos && pos <= pixel + length)
2801 snap = TRUE;
2802 pixel += length;
2804 } while (++cell < sheet_max && !snap);
2805 pixel -= length;
2806 if (snap) {
2807 if (info->is_mouse_move)
2808 pos = (fabs (pos - pixel) < fabs (pos - pixel - length)) ? pixel : pixel + length;
2809 else
2810 pos = (pixel == pos) ? pixel : (to_min ? pixel : pixel + length);
2813 return/* sign */ pos;
2816 static void
2817 apply_move (SheetObject *so, int x_idx, int y_idx, double *coords,
2818 ObjDragInfo *info, gboolean snap_to_grid)
2820 gboolean move_x = (x_idx >= 0);
2821 gboolean move_y = (y_idx >= 0);
2822 double x, y;
2824 x = move_x ? coords[x_idx] + info->dx : 0;
2825 y = move_y ? coords[y_idx] + info->dy : 0;
2827 if (snap_to_grid) {
2828 g_return_if_fail (info->pane != NULL);
2830 if (move_x)
2831 x = snap_pos_to_grid (info, TRUE, x, info->dx < 0.);
2832 if (move_y)
2833 y = snap_pos_to_grid (info, FALSE, y, info->dy < 0.);
2834 if (info->primary_object == so || NULL == info->primary_object) {
2835 if (move_x) info->dx = x - coords[x_idx];
2836 if (move_y) info->dy = y - coords[y_idx];
2840 if (move_x) coords[x_idx] = x;
2841 if (move_y) coords[y_idx] = y;
2843 if (info->symmetric && !snap_to_grid) {
2844 if (move_x) coords[x_idx == 0 ? 2 : 0] -= info->dx;
2845 if (move_y) coords[y_idx == 1 ? 3 : 1] -= info->dy;
2849 static void
2850 drag_object (SheetObject *so, double *coords, ObjDragInfo *info)
2852 static struct {
2853 int x_idx, y_idx;
2854 } const idx_info[8] = {
2855 { 0, 1}, {-1, 1}, { 2, 1}, { 0,-1},
2856 { 2,-1}, { 0, 3}, {-1, 3}, { 2, 3}
2859 g_return_if_fail (info->drag_type <= 8);
2861 if (info->drag_type == 8) {
2862 apply_move (so, 0, 1, coords, info, info->snap_to_grid);
2863 apply_move (so, 2, 3, coords, info, FALSE);
2864 } else
2865 apply_move (so,
2866 idx_info[info->drag_type].x_idx,
2867 idx_info[info->drag_type].y_idx,
2868 coords, info, info->snap_to_grid);
2869 SCG_FOREACH_PANE (info->scg, pane,
2870 gnm_pane_object_update_bbox (pane, so););
2873 static void
2874 cb_drag_selected_objects (SheetObject *so, double *coords, ObjDragInfo *info)
2876 if (so != info->primary_object)
2877 drag_object (so, coords, info);
2881 * scg_objects_drag:
2882 * @scg: #SheetControlGUI
2883 * @primary: #SheetObject (optionally NULL)
2884 * @dx:
2885 * @dy:
2886 * @drag_type:
2887 * @symmetric:
2889 * Move the control points and drag views of the currently selected objects to
2890 * a new position. This movement is only made in @scg not in the actual
2891 * objects.
2893 void
2894 scg_objects_drag (SheetControlGUI *scg, GnmPane *pane,
2895 SheetObject *primary,
2896 gdouble *dx, gdouble *dy,
2897 int drag_type, gboolean symmetric,
2898 gboolean snap_to_grid,
2899 gboolean is_mouse_move)
2901 double *coords;
2903 ObjDragInfo info;
2904 info.scg = scg;
2905 info.pane = pane;
2906 info.primary_object = primary;
2907 info.dx = *dx;
2908 info.dy = *dy;
2909 info.symmetric = symmetric;
2910 info.drag_type = drag_type;
2911 info.snap_to_grid = snap_to_grid;
2912 info.is_mouse_move = is_mouse_move;
2914 if (primary != NULL) {
2915 coords = g_hash_table_lookup (scg->selected_objects, primary);
2916 drag_object (primary, coords, &info);
2919 g_hash_table_foreach (scg->selected_objects,
2920 (GHFunc) cb_drag_selected_objects, &info);
2922 *dx = info.dx;
2923 *dy = info.dy;
2926 typedef struct {
2927 SheetControlGUI *scg;
2928 GSList *objects, *anchors;
2929 } CollectObjectsData;
2930 static void
2931 cb_collect_objects_to_commit (SheetObject *so, double *coords, CollectObjectsData *data)
2933 SheetObjectAnchor *anchor = sheet_object_anchor_dup (
2934 sheet_object_get_anchor (so));
2935 if (!sheet_object_can_resize (so)) {
2936 /* FIXME: that code should be invalid */
2937 double scale = goc_canvas_get_pixels_per_unit (GOC_CANVAS (data->scg->pane[0])) / 72.;
2938 sheet_object_default_size (so, coords + 2, coords + 3);
2939 coords[2] *= gnm_app_display_dpi_get (TRUE) * scale;
2940 coords[3] *= gnm_app_display_dpi_get (FALSE) * scale;
2941 coords[2] += coords[0];
2942 coords[3] += coords[1];
2944 scg_object_coords_to_anchor (data->scg, coords, anchor);
2945 data->objects = g_slist_prepend (data->objects, so);
2946 data->anchors = g_slist_prepend (data->anchors, anchor);
2948 if (!sheet_object_rubber_band_directly (so)) {
2949 SCG_FOREACH_PANE (data->scg, pane, {
2950 GocItem **ctrl_pts = g_hash_table_lookup (pane->drag.ctrl_pts, so);
2951 if (NULL != ctrl_pts[9]) {
2952 double const *pts = g_hash_table_lookup (
2953 pane->simple.scg->selected_objects, so);
2954 SheetObjectView *sov = sheet_object_get_view (so,
2955 (SheetObjectViewContainer *)pane);
2957 g_object_unref (ctrl_pts[9]);
2958 ctrl_pts[9] = NULL;
2960 if (NULL == sov)
2961 sov = sheet_object_new_view (so, (SheetObjectViewContainer *) pane);
2962 if (NULL != sov)
2963 sheet_object_view_set_bounds (sov, pts, TRUE);
2969 static char *
2970 scg_objects_drag_commit_get_undo_text (int drag_type, int n,
2971 gboolean created_objects)
2973 char const *format;
2975 if (created_objects) {
2976 if (drag_type == 8)
2977 /* xgettext : %d gives the number of objects. This is input to ngettext. */
2978 format = ngettext ("Duplicate %d Object", "Duplicate %d Objects", n);
2979 else
2980 /* xgettext : %d gives the number of objects. This is input to ngettext. */
2981 format = ngettext ("Insert %d Object", "Insert %d Objects", n);
2982 } else {
2983 if (drag_type == 8)
2984 /* xgettext : %d gives the number of objects. This is input to ngettext. */
2985 format = ngettext ("Move %d Object", "Move %d Objects", n);
2986 else
2987 /* xgettext : %d gives the number of objects. This is input to ngettext. */
2988 format = ngettext ("Resize %d Object", "Resize %d Objects", n);
2991 return g_strdup_printf (format, n);
2995 void
2996 scg_objects_drag_commit (SheetControlGUI *scg, int drag_type,
2997 gboolean created_objects,
2998 GOUndo **pundo, GOUndo **predo, gchar **undo_title)
3000 CollectObjectsData data;
3001 char *text = NULL;
3002 GOUndo *undo = NULL;
3003 GOUndo *redo = NULL;
3005 data.objects = data.anchors = NULL;
3006 data.scg = scg;
3007 g_hash_table_foreach (scg->selected_objects,
3008 (GHFunc) cb_collect_objects_to_commit, &data);
3010 undo = sheet_object_move_undo (data.objects, created_objects);
3011 redo = sheet_object_move_do (data.objects, data.anchors, created_objects);
3012 text = scg_objects_drag_commit_get_undo_text
3013 (drag_type, g_slist_length (data.objects), created_objects);
3015 if (pundo && predo) {
3016 *pundo = undo;
3017 *predo = redo;
3018 if (undo_title)
3019 *undo_title = text;
3020 } else {
3021 cmd_generic (GNM_WBC (scg_wbcg (scg)),
3022 text, undo, redo);
3023 g_free (text);
3025 g_slist_free (data.objects);
3026 g_slist_free_full (data.anchors, g_free);
3029 void
3030 scg_objects_nudge (SheetControlGUI *scg, GnmPane *pane,
3031 int drag_type, double dx, double dy, gboolean symmetric, gboolean snap_to_grid)
3033 /* no nudging if we are creating an object */
3034 if (!scg->wbcg->new_object) {
3035 scg_objects_drag (scg, pane, NULL, &dx, &dy, drag_type, symmetric, snap_to_grid, FALSE);
3036 scg_objects_drag_commit (scg, drag_type, FALSE, NULL, NULL, NULL);
3040 void
3041 scg_object_coords_to_anchor (SheetControlGUI const *scg,
3042 double const *coords, SheetObjectAnchor *in_out)
3044 Sheet *sheet = scg_sheet (scg);
3045 /* pane 0 always exists and the others are always use the same basis */
3046 GnmPane *pane = scg_pane ((SheetControlGUI *)scg, 0);
3047 double tmp[4];
3048 g_return_if_fail (GNM_IS_SCG (scg));
3049 g_return_if_fail (coords != NULL);
3051 in_out->base.direction = GOD_ANCHOR_DIR_NONE_MASK;
3052 if (coords[0] > coords[2]) {
3053 tmp[0] = coords[2];
3054 tmp[2] = coords[0];
3055 } else {
3056 tmp[0] = coords[0];
3057 tmp[2] = coords[2];
3058 in_out->base.direction = GOD_ANCHOR_DIR_RIGHT;
3060 if (coords[1] > coords[3]) {
3061 tmp[1] = coords[3];
3062 tmp[3] = coords[1];
3063 } else {
3064 tmp[1] = coords[1];
3065 tmp[3] = coords[3];
3066 in_out->base.direction |= GOD_ANCHOR_DIR_DOWN;
3069 switch (in_out->mode) {
3070 case GNM_SO_ANCHOR_TWO_CELLS:
3071 in_out->cell_bound.start.col = calc_obj_place (pane, tmp[0], TRUE,
3072 in_out->offset + 0);
3073 in_out->cell_bound.start.row = calc_obj_place (pane, tmp[1], FALSE,
3074 in_out->offset + 1);
3075 in_out->cell_bound.end.col = calc_obj_place (pane, tmp[2], TRUE,
3076 in_out->offset + 2);
3077 in_out->cell_bound.end.row = calc_obj_place (pane, tmp[3], FALSE,
3078 in_out->offset + 3);
3079 break;
3080 case GNM_SO_ANCHOR_ONE_CELL:
3081 in_out->cell_bound.start.col = calc_obj_place (pane, tmp[0], TRUE,
3082 in_out->offset + 0);
3083 in_out->cell_bound.start.row = calc_obj_place (pane, tmp[1], FALSE,
3084 in_out->offset + 1);
3085 in_out->cell_bound.end = in_out->cell_bound.start;
3086 in_out->offset[2] = (tmp[2] - tmp[0]) / colrow_compute_pixel_scale (sheet, TRUE);
3087 in_out->offset[3] = (tmp[3] - tmp[1]) / colrow_compute_pixel_scale (sheet, FALSE);
3088 break;
3089 case GNM_SO_ANCHOR_ABSOLUTE: {
3090 double h, v;
3091 range_init (&in_out->cell_bound, 0, 0, 0, 0);
3092 h = colrow_compute_pixel_scale (sheet, TRUE);
3093 v = colrow_compute_pixel_scale (sheet, FALSE);
3094 in_out->offset[0] = tmp[0] / h;
3095 in_out->offset[1] = tmp[1] / v;
3096 in_out->offset[2] = (tmp[2] - tmp[0]) / h;
3097 in_out->offset[3] = (tmp[3] - tmp[1]) / v;
3098 break;
3103 static double
3104 cell_offset_calc_pixel (Sheet const *sheet, int i, gboolean is_col,
3105 double offset)
3107 ColRowInfo const *cri = sheet_colrow_get_info (sheet, i, is_col);
3108 return offset * cri->size_pixels;
3111 void
3112 scg_object_anchor_to_coords (SheetControlGUI const *scg,
3113 SheetObjectAnchor const *anchor, double *coords)
3115 Sheet *sheet = scg_sheet (scg);
3116 GODrawingAnchorDir direction;
3117 gint64 pixels[4];
3118 GnmRange const *r;
3120 g_return_if_fail (GNM_IS_SCG (scg));
3121 g_return_if_fail (anchor != NULL);
3122 g_return_if_fail (coords != NULL);
3124 r = &anchor->cell_bound;
3125 if (anchor->mode != GNM_SO_ANCHOR_ABSOLUTE) {
3126 pixels[0] = scg_colrow_distance_get (scg, TRUE, 0, r->start.col);
3127 pixels[1] = scg_colrow_distance_get (scg, FALSE, 0, r->start.row);
3128 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
3129 pixels[2] = pixels[0] + scg_colrow_distance_get (scg, TRUE,
3130 r->start.col, r->end.col);
3131 pixels[3] = pixels[1] + scg_colrow_distance_get (scg, FALSE,
3132 r->start.row, r->end.row);
3133 /* add .5 to offsets so that the rounding is optimal */
3134 pixels[0] += cell_offset_calc_pixel (sheet, r->start.col,
3135 TRUE, anchor->offset[0]) + .5;
3136 pixels[1] += cell_offset_calc_pixel (sheet, r->start.row,
3137 FALSE, anchor->offset[1]) + .5;
3138 pixels[2] += cell_offset_calc_pixel (sheet, r->end.col,
3139 TRUE, anchor->offset[2]) + .5;
3140 pixels[3] += cell_offset_calc_pixel (sheet, r->end.row,
3141 FALSE, anchor->offset[3]) + .5;
3142 } else {
3143 /* add .5 to offsets so that the rounding is optimal */
3144 pixels[0] += cell_offset_calc_pixel (sheet, r->start.col,
3145 TRUE, anchor->offset[0]) + .5;
3146 pixels[1] += cell_offset_calc_pixel (sheet, r->start.row,
3147 FALSE, anchor->offset[1]) + .5;
3148 pixels[2] = pixels[0] + go_fake_floor (anchor->offset[2] * colrow_compute_pixel_scale (sheet, TRUE) + .5);
3149 pixels[3] = pixels[1] + go_fake_floor (anchor->offset[3] * colrow_compute_pixel_scale (sheet, TRUE) + .5);
3151 } else {
3152 double h, v;
3153 h = colrow_compute_pixel_scale (sheet, TRUE);
3154 v = colrow_compute_pixel_scale (sheet, FALSE);
3155 pixels[0] = go_fake_floor (anchor->offset[0] * h);
3156 pixels[1] = go_fake_floor (anchor->offset[1] * v);
3157 pixels[2] = go_fake_floor ((anchor->offset[0] + anchor->offset[2]) * h);
3158 pixels[3] = go_fake_floor ((anchor->offset[1] + anchor->offset[3]) * v);
3161 direction = anchor->base.direction;
3162 if (direction == GOD_ANCHOR_DIR_UNKNOWN)
3163 direction = GOD_ANCHOR_DIR_DOWN_RIGHT;
3165 coords[0] = pixels[direction & GOD_ANCHOR_DIR_H_MASK ? 0 : 2];
3166 coords[1] = pixels[direction & GOD_ANCHOR_DIR_V_MASK ? 1 : 3];
3167 coords[2] = pixels[direction & GOD_ANCHOR_DIR_H_MASK ? 2 : 0];
3168 coords[3] = pixels[direction & GOD_ANCHOR_DIR_V_MASK ? 3 : 1];
3171 /***************************************************************************/
3173 static gboolean
3174 scg_comment_display_filter_cb (PangoAttribute *attribute, gboolean *state)
3176 if (attribute->klass->type == PANGO_ATTR_FOREGROUND &&
3177 attribute->start_index != attribute->end_index)
3178 *state = TRUE;
3179 return FALSE;
3183 * scg_comment_display:
3184 * @scg: The SheetControl
3185 * @cc: A cell comment
3188 void
3189 scg_comment_display (SheetControlGUI *scg, GnmComment *cc,
3190 int x, int y)
3192 g_return_if_fail (GNM_IS_SCG (scg));
3194 scg_comment_timer_clear (scg);
3196 /* If someone clicked and dragged the comment marker this may be NULL */
3197 if (scg->comment.selected == NULL)
3198 return;
3200 if (cc == NULL)
3201 cc = scg->comment.selected;
3202 else if (scg->comment.selected != cc)
3203 scg_comment_unselect (scg, scg->comment.selected);
3205 g_return_if_fail (GNM_IS_CELL_COMMENT (cc));
3207 if (scg->comment.item == NULL) {
3208 GtkWidget *label, *box;
3209 char *comment_text;
3210 PangoAttrList *comment_markup;
3211 char const *comment_author;
3213 g_object_get (G_OBJECT (cc),
3214 "text", &comment_text,
3215 "markup", &comment_markup,
3216 NULL);
3217 comment_author = cell_comment_author_get (cc);
3219 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
3221 if (comment_author != NULL) {
3222 char *text;
3223 PangoAttrList *attrs;
3224 PangoAttribute *attr;
3226 /* xgettext: this is a by-line for cell comments */
3227 text = g_strdup_printf (_("By %s:"), comment_author);
3228 label = gtk_label_new (text);
3229 g_free (text);
3231 attrs = pango_attr_list_new ();
3232 attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
3233 attr->start_index = 0;
3234 attr->end_index = G_MAXINT;
3235 pango_attr_list_insert (attrs, attr);
3236 gtk_label_set_attributes (GTK_LABEL (label), attrs);
3237 pango_attr_list_unref (attrs);
3239 gtk_widget_set_halign (label, GTK_ALIGN_START);
3240 gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
3241 gtk_box_set_spacing (GTK_BOX (box), 10);
3244 label = gtk_label_new (comment_text);
3245 if (comment_markup) {
3246 gboolean font_colour_set = FALSE;
3247 pango_attr_list_filter
3248 (comment_markup,
3249 (PangoAttrFilterFunc) scg_comment_display_filter_cb,
3250 &font_colour_set);
3251 if (font_colour_set) {
3252 /* Imported comments may have a font colour set. */
3253 /* If that is the case, we set a background colour. */
3254 guint length = strlen (comment_text);
3255 PangoAttribute *attr = pango_attr_foreground_new (0,0,0);
3256 attr->start_index = 0;
3257 attr->end_index = length;
3258 pango_attr_list_insert_before (comment_markup, attr);
3259 attr = pango_attr_background_new (255*255, 255*255, 224*255 );
3260 attr->start_index = 0;
3261 attr->end_index = length;
3262 pango_attr_list_insert_before (comment_markup, attr);
3264 gtk_label_set_attributes (GTK_LABEL (label), comment_markup);
3266 g_free (comment_text);
3267 gtk_widget_set_halign (label, GTK_ALIGN_START);
3268 gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
3270 gnm_convert_to_tooltip (GTK_WIDGET (scg->grid), box);
3272 scg->comment.item = gtk_widget_get_toplevel (box);
3273 gtk_window_move (GTK_WINDOW (scg->comment.item),
3274 x + 10, y + 10);
3276 gtk_widget_show_all (scg->comment.item);
3280 static gint
3281 cb_cell_comment_timer (SheetControlGUI *scg)
3283 g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3284 g_return_val_if_fail (scg->comment.timer != 0, FALSE);
3286 scg->comment.timer = 0;
3287 scg_comment_display (scg, scg->comment.selected,
3288 scg->comment.x, scg->comment.y);
3289 return FALSE;
3293 * scg_comment_select:
3294 * @scg: The SheetControl
3295 * @cc: A cell comment
3297 * Prepare @cc for display.
3299 void
3300 scg_comment_select (SheetControlGUI *scg, GnmComment *cc, int x, int y)
3302 g_return_if_fail (GNM_IS_SCG (scg));
3304 if (scg->comment.selected != NULL)
3305 scg_comment_unselect (scg, scg->comment.selected);
3307 g_return_if_fail (scg->comment.timer == 0);
3309 scg->comment.selected = cc;
3310 scg->comment.timer = g_timeout_add (1000,
3311 (GSourceFunc)cb_cell_comment_timer, scg);
3312 scg->comment.x = x;
3313 scg->comment.y = y;
3317 * scg_comment_unselect:
3318 * @scg: The SheetControl
3319 * @cc: A cell comment
3321 * If @cc is the current cell comment being edited/displayed shutdown the
3322 * display mechanism.
3324 void
3325 scg_comment_unselect (SheetControlGUI *scg, GnmComment *cc)
3327 g_return_if_fail (GNM_IS_SCG (scg));
3329 if (cc == scg->comment.selected) {
3330 scg->comment.selected = NULL;
3331 scg_comment_timer_clear (scg);
3333 if (scg->comment.item != NULL) {
3334 gtk_widget_destroy (scg->comment.item);
3335 scg->comment.item = NULL;
3340 /************************************************************************/
3341 /* Col/Row size support routines. */
3343 gint64
3344 scg_colrow_distance_get (SheetControlGUI const *scg, gboolean is_cols,
3345 int from, int to)
3347 Sheet *sheet = scg_sheet (scg);
3348 ColRowCollection const *collection;
3349 int default_size;
3350 int i;
3351 gint64 pixels = 0;
3352 int sign = 1;
3354 g_return_val_if_fail (GNM_IS_SCG (scg), 1);
3356 if (from > to) {
3357 int const tmp = to;
3358 to = from;
3359 from = tmp;
3360 sign = -1;
3363 g_return_val_if_fail (from >= 0, 1);
3365 if (is_cols) {
3366 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1);
3367 collection = &sheet->cols;
3368 } else {
3369 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
3370 collection = &sheet->rows;
3373 /* Do not use col_row_foreach, it ignores empties.
3374 * Optimize this so that long jumps are not quite so horrific
3375 * for performance.
3377 default_size = collection->default_style.size_pixels;
3378 for (i = from ; i < to ; ++i) {
3379 ColRowSegment const *segment =
3380 COLROW_GET_SEGMENT(collection, i);
3382 if (segment != NULL) {
3383 ColRowInfo const *cri = segment->info[COLROW_SUB_INDEX (i)];
3384 if (cri == NULL)
3385 pixels += default_size;
3386 else if (cri->visible)
3387 pixels += cri->size_pixels;
3388 } else {
3389 int segment_end = COLROW_SEGMENT_END (i)+1;
3390 if (segment_end > to)
3391 segment_end = to;
3392 pixels += default_size * (segment_end - i);
3393 i = segment_end - 1;
3397 return pixels*sign;
3400 /*************************************************************************/
3402 static void
3403 scg_cursor_bound (SheetControl *sc, GnmRange const *r)
3405 SheetControlGUI *scg = (SheetControlGUI *) sc;
3406 SCG_FOREACH_PANE (scg, pane, gnm_pane_cursor_bound_set (pane, r););
3409 static void
3410 scg_recompute_visible_region (SheetControl *sc, gboolean full_recompute)
3412 SheetControlGUI *scg = (SheetControlGUI *) sc;
3414 SCG_FOREACH_PANE (scg, pane,
3415 gnm_pane_compute_visible_region (pane, full_recompute););
3418 void
3419 scg_edit_start (SheetControlGUI *scg)
3421 g_return_if_fail (GNM_IS_SCG (scg));
3423 SCG_FOREACH_PANE (scg, pane, gnm_pane_edit_start (pane););
3426 void
3427 scg_edit_stop (SheetControlGUI *scg)
3429 g_return_if_fail (GNM_IS_SCG (scg));
3431 scg_rangesel_stop (scg, FALSE);
3432 SCG_FOREACH_PANE (scg, pane, gnm_pane_edit_stop (pane););
3436 * scg_rangesel_changed:
3437 * @scg: The scg
3439 * Notify expr_entry that the expression range has changed.
3441 static void
3442 scg_rangesel_changed (SheetControlGUI *scg,
3443 int base_col, int base_row,
3444 int move_col, int move_row)
3446 GnmExprEntry *expr_entry;
3447 gboolean ic_changed;
3448 GnmRange *r, last_r;
3449 Sheet *sheet;
3451 g_return_if_fail (GNM_IS_SCG (scg));
3453 scg->rangesel.base_corner.col = base_col;
3454 scg->rangesel.base_corner.row = base_row;
3455 scg->rangesel.move_corner.col = move_col;
3456 scg->rangesel.move_corner.row = move_row;
3458 r = &scg->rangesel.displayed;
3459 if (base_col < move_col) {
3460 r->start.col = base_col;
3461 r->end.col = move_col;
3462 } else {
3463 r->end.col = base_col;
3464 r->start.col = move_col;
3466 if (base_row < move_row) {
3467 r->start.row = base_row;
3468 r->end.row = move_row;
3469 } else {
3470 r->end.row = base_row;
3471 r->start.row = move_row;
3474 sheet = scg_sheet (scg);
3475 expr_entry = wbcg_get_entry_logical (scg->wbcg);
3477 gnm_expr_entry_freeze (expr_entry);
3478 /* The order here is tricky.
3479 * 1) Assign the range to the expr entry.
3481 ic_changed = gnm_expr_entry_load_from_range (
3482 expr_entry, sheet, r);
3484 /* 2) if the expr entry changed the region get the new region */
3485 if (ic_changed)
3486 gnm_expr_entry_get_rangesel (expr_entry, r, NULL);
3488 /* 3) now double check that all merged regions are fully contained */
3489 last_r = *r;
3490 gnm_sheet_merge_find_bounding_box (sheet, r);
3491 if (!range_equal (&last_r, r))
3492 gnm_expr_entry_load_from_range (expr_entry, sheet, r);
3494 gnm_expr_entry_thaw (expr_entry);
3496 SCG_FOREACH_PANE (scg, pane, gnm_pane_rangesel_bound_set (pane, r););
3499 void
3500 scg_rangesel_start (SheetControlGUI *scg,
3501 int base_col, int base_row,
3502 int move_col, int move_row)
3504 GnmRange r;
3506 g_return_if_fail (GNM_IS_SCG (scg));
3508 if (scg->rangesel.active)
3509 return;
3511 if (scg->wbcg->rangesel != NULL)
3512 g_warning ("misconfiged rangesel");
3514 scg->wbcg->rangesel = scg;
3515 scg->rangesel.active = TRUE;
3517 gnm_expr_entry_find_range (wbcg_get_entry_logical (scg->wbcg));
3519 range_init (&r, base_col, base_row, move_col, move_row);
3520 SCG_FOREACH_PANE (scg, pane, gnm_pane_rangesel_start (pane, &r););
3521 scg_rangesel_changed (scg, base_col, base_row, move_col, move_row);
3524 void
3525 scg_rangesel_stop (SheetControlGUI *scg, gboolean clear_string)
3527 g_return_if_fail (GNM_IS_SCG (scg));
3529 if (!scg->rangesel.active)
3530 return;
3531 if (scg->wbcg->rangesel != scg)
3532 g_warning ("misconfiged rangesel");
3534 scg->wbcg->rangesel = NULL;
3535 scg->rangesel.active = FALSE;
3536 SCG_FOREACH_PANE (scg, pane, gnm_pane_rangesel_stop (pane););
3538 gnm_expr_entry_rangesel_stop (wbcg_get_entry_logical (scg->wbcg),
3539 clear_string);
3543 * scg_set_display_cursor:
3544 * @scg:
3546 * Set the displayed cursor type.
3548 void
3549 scg_set_display_cursor (SheetControlGUI *scg)
3551 GdkCursorType cursor = GDK_CURSOR_IS_PIXMAP;
3553 g_return_if_fail (GNM_IS_SCG (scg));
3555 if (scg->wbcg->new_object != NULL)
3556 cursor = GDK_CROSSHAIR;
3558 SCG_FOREACH_PANE (scg, pane, {
3559 GtkWidget *w = GTK_WIDGET (pane);
3560 if (gtk_widget_get_window (w)) {
3561 if (cursor == GDK_CURSOR_IS_PIXMAP)
3562 gnm_widget_set_cursor (w, pane->mouse_cursor);
3563 else
3564 gnm_widget_set_cursor_type (w, cursor);
3569 void
3570 scg_rangesel_extend_to (SheetControlGUI *scg, int col, int row)
3572 int base_col, base_row;
3574 if (col < 0) {
3575 base_col = 0;
3576 col = gnm_sheet_get_last_col (scg_sheet (scg));
3577 } else
3578 base_col = scg->rangesel.base_corner.col;
3579 if (row < 0) {
3580 base_row = 0;
3581 row = gnm_sheet_get_last_row (scg_sheet (scg));
3582 } else
3583 base_row = scg->rangesel.base_corner.row;
3585 if (scg->rangesel.active)
3586 scg_rangesel_changed (scg, base_col, base_row, col, row);
3587 else
3588 scg_rangesel_start (scg, base_col, base_row, col, row);
3591 void
3592 scg_rangesel_bound (SheetControlGUI *scg,
3593 int base_col, int base_row,
3594 int move_col, int move_row)
3596 if (scg->rangesel.active)
3597 scg_rangesel_changed (scg, base_col, base_row, move_col, move_row);
3598 else
3599 scg_rangesel_start (scg, base_col, base_row, move_col, move_row);
3602 void
3603 scg_rangesel_move (SheetControlGUI *scg, int n, gboolean jump_to_bound,
3604 gboolean horiz)
3606 SheetView *sv = scg_view (scg);
3607 GnmCellPos tmp;
3609 if (!scg->rangesel.active) {
3610 tmp.col = sv->edit_pos_real.col;
3611 tmp.row = sv->edit_pos_real.row;
3612 } else
3613 tmp = scg->rangesel.base_corner;
3615 if (horiz)
3616 tmp.col = sheet_find_boundary_horizontal (
3617 sv_sheet (sv), tmp.col, tmp.row, tmp.row, n, jump_to_bound);
3618 else
3619 tmp.row = sheet_find_boundary_vertical (
3620 sv_sheet (sv), tmp.col, tmp.row, tmp.col, n, jump_to_bound);
3622 if (scg->rangesel.active)
3623 scg_rangesel_changed (scg, tmp.col, tmp.row, tmp.col, tmp.row);
3624 else
3625 scg_rangesel_start (scg, tmp.col, tmp.row, tmp.col, tmp.row);
3626 scg_make_cell_visible (scg, tmp.col, tmp.row, FALSE, FALSE);
3627 gnm_expr_entry_signal_update (
3628 wbcg_get_entry_logical (scg->wbcg), FALSE);
3631 void
3632 scg_rangesel_extend (SheetControlGUI *scg, int n,
3633 gboolean jump_to_bound, gboolean horiz)
3635 Sheet *sheet = scg_sheet (scg);
3637 if (scg->rangesel.active) {
3638 GnmCellPos tmp = scg->rangesel.move_corner;
3640 if (horiz)
3641 tmp.col = sheet_find_boundary_horizontal (sheet,
3642 tmp.col, tmp.row, scg->rangesel.base_corner.row,
3643 n, jump_to_bound);
3644 else
3645 tmp.row = sheet_find_boundary_vertical (sheet,
3646 tmp.col, tmp.row, scg->rangesel.base_corner.col,
3647 n, jump_to_bound);
3649 scg_rangesel_changed (scg,
3650 scg->rangesel.base_corner.col,
3651 scg->rangesel.base_corner.row, tmp.col, tmp.row);
3653 scg_make_cell_visible (scg,
3654 scg->rangesel.move_corner.col,
3655 scg->rangesel.move_corner.row, FALSE, TRUE);
3656 gnm_expr_entry_signal_update (
3657 wbcg_get_entry_logical (scg->wbcg), FALSE);
3658 } else
3659 scg_rangesel_move (scg, n, jump_to_bound, horiz);
3663 * scg_cursor_move:
3664 * @scg: The scg
3665 * @dir: Number of units to move the cursor
3666 * @jump_to_bound: skip from the start to the end of ranges
3667 * of filled or unfilled cells.
3668 * @horiz: is the movement horizontal or vertical
3670 * Moves the cursor count rows
3672 void
3673 scg_cursor_move (SheetControlGUI *scg, int n,
3674 gboolean jump_to_bound, gboolean horiz)
3676 SheetView *sv = scg_view (scg);
3677 GnmCellPos tmp = sv->edit_pos_real;
3678 int step = (n>0) ? 1 : -1;
3680 if (!wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
3681 return;
3683 if (horiz)
3684 tmp.col = sheet_find_boundary_horizontal (sv->sheet,
3685 tmp.col + n - step, tmp.row, tmp.row,
3686 step, jump_to_bound);
3687 else
3688 tmp.row = sheet_find_boundary_vertical
3689 (sv->sheet,
3690 tmp.col, tmp.row + n - step,
3691 tmp.col,
3692 step, jump_to_bound);
3694 sv_selection_reset (sv);
3695 sv_cursor_set (sv, &tmp,
3696 tmp.col, tmp.row, tmp.col, tmp.row, NULL);
3697 sv_make_cell_visible (sv, tmp.col, tmp.row, FALSE);
3698 sv_selection_add_pos (sv, tmp.col, tmp.row, GNM_SELECTION_MODE_ADD);
3702 * scg_cursor_extend:
3703 * @scg: The scg
3704 * @n: Units to extend the selection
3705 * @jump_to_bound: Move to transitions between cells and blanks,
3706 * or move in single steps.
3707 * @horiz: extend vertically or horizontally.
3709 void
3710 scg_cursor_extend (SheetControlGUI *scg, int n,
3711 gboolean jump_to_bound, gboolean horiz)
3713 SheetView *sv = scg_view (scg);
3714 GnmCellPos move = sv->cursor.move_corner;
3715 GnmCellPos visible = scg->pane[0]->first;
3717 if (!wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
3718 return;
3720 if (horiz)
3721 visible.col = move.col = sheet_find_boundary_horizontal (sv->sheet,
3722 move.col, move.row, sv->cursor.base_corner.row,
3723 n, jump_to_bound);
3724 else
3725 visible.row = move.row = sheet_find_boundary_vertical (sv->sheet,
3726 move.col, move.row, sv->cursor.base_corner.col,
3727 n, jump_to_bound);
3729 sv_selection_extend_to (sv, move.col, move.row);
3730 sv_make_cell_visible (sv, visible.col, visible.row, FALSE);
3733 void
3734 scg_take_focus (SheetControlGUI *scg)
3736 g_return_if_fail (GNM_IS_SCG (scg));
3738 /* FIXME: Slightly hackish. */
3739 if (wbcg_toplevel (scg->wbcg))
3740 gtk_window_set_focus (wbcg_toplevel (scg->wbcg),
3741 (scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT)?
3742 GTK_WIDGET (scg->vs): GTK_WIDGET (scg_pane (scg, 0)));
3745 /*********************************************************************************/
3746 void
3747 scg_size_guide_start (SheetControlGUI *scg,
3748 gboolean vert, int colrow, gboolean is_colrow_resize)
3750 g_return_if_fail (GNM_IS_SCG (scg));
3751 SCG_FOREACH_PANE (scg, pane,
3752 gnm_pane_size_guide_start (pane, vert, colrow, is_colrow_resize););
3754 void
3755 scg_size_guide_motion (SheetControlGUI *scg, gboolean vert, gint64 guide_pos)
3757 g_return_if_fail (GNM_IS_SCG (scg));
3758 SCG_FOREACH_PANE (scg, pane,
3759 gnm_pane_size_guide_motion (pane, vert, guide_pos););
3761 void
3762 scg_size_guide_stop (SheetControlGUI *scg)
3764 g_return_if_fail (GNM_IS_SCG (scg));
3765 SCG_FOREACH_PANE (scg, pane,
3766 gnm_pane_size_guide_stop (pane););
3768 /*********************************************************************************/
3770 void
3771 scg_special_cursor_start (SheetControlGUI *scg, int style, int button)
3773 g_return_if_fail (GNM_IS_SCG (scg));
3775 SCG_FOREACH_PANE (scg, pane,
3776 gnm_pane_special_cursor_start (pane, style, button););
3779 void
3780 scg_special_cursor_stop (SheetControlGUI *scg)
3782 g_return_if_fail (GNM_IS_SCG (scg));
3784 SCG_FOREACH_PANE (scg, pane,
3785 gnm_pane_special_cursor_stop (pane););
3788 gboolean
3789 scg_special_cursor_bound_set (SheetControlGUI *scg, GnmRange const *r)
3791 gboolean changed = FALSE;
3793 g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3795 SCG_FOREACH_PANE (scg, pane,
3796 changed |= gnm_pane_special_cursor_bound_set (pane, r););
3797 return changed;
3800 static void
3801 scg_object_create_view (SheetControl *sc, SheetObject *so)
3803 SheetControlGUI *scg = GNM_SCG (sc);
3804 if (scg->active_panes)
3805 SCG_FOREACH_PANE (scg, pane,
3806 sheet_object_new_view (so, (SheetObjectViewContainer *)pane););
3807 else
3808 sheet_object_new_view (so, (SheetObjectViewContainer *)scg->vs);
3811 static void
3812 scg_scale_changed (SheetControl *sc)
3814 SheetControlGUI *scg = (SheetControlGUI *)sc;
3815 Sheet *sheet = scg_sheet (scg);
3816 double z;
3817 GSList *ptr;
3819 g_return_if_fail (GNM_IS_SCG (scg));
3821 z = sheet->last_zoom_factor_used;
3823 SCG_FOREACH_PANE (scg, pane, {
3824 if (pane->col.canvas != NULL)
3825 goc_canvas_set_pixels_per_unit (pane->col.canvas, z);
3826 if (pane->row.canvas != NULL)
3827 goc_canvas_set_pixels_per_unit (pane->row.canvas, z);
3828 goc_canvas_set_pixels_per_unit (GOC_CANVAS (pane), z);
3831 scg_resize (scg, TRUE);
3832 set_resize_pane_pos (scg, scg->vpane);
3833 set_resize_pane_pos (scg, scg->hpane);
3834 /* now, update sheet objects positions and sizes */
3835 for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next)
3836 sheet_object_update_bounds (GNM_SO (ptr->data), NULL);
3839 static gboolean
3840 cb_cell_im_timer (SheetControlGUI *scg)
3842 g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3843 g_return_val_if_fail (scg->im.timer != 0, FALSE);
3845 scg->im.timer = 0;
3846 scg_im_destroy (scg);
3847 return FALSE;
3850 static GnmPane *
3851 scg_find_pane (SheetControlGUI *scg, GnmCellPos *pos)
3853 int i;
3855 for (i = 0; i < scg->active_panes; i++) {
3856 GnmPane *pane = scg->pane[i];
3858 if (pane &&
3859 pane->first.col <= pos->col &&
3860 pane->first.row <= pos->row &&
3861 pane->last_visible.col >= pos->col &&
3862 pane->last_visible.row >= pos->row)
3863 return pane;
3865 return NULL;
3868 static void
3869 scg_show_im_tooltip (SheetControl *sc, GnmInputMsg *im, GnmCellPos *pos)
3871 SheetControlGUI *scg = (SheetControlGUI *)sc;
3872 GnmPane *pane;
3874 g_return_if_fail (GNM_IS_SCG (scg));
3876 scg_im_destroy (scg);
3878 pane = scg_find_pane (scg, pos);
3880 if (im && pane) {
3881 GtkWidget *label, *box;
3882 char const *text, *title;
3883 int len_text, len_title;
3884 int x, y, x_origin, y_origin;
3885 GtkAllocation allocation;
3886 Sheet *sheet = scg_sheet (scg);
3887 gboolean rtl = sheet->text_is_rtl;
3889 text = gnm_input_msg_get_msg (im);
3890 title = gnm_input_msg_get_title (im);
3891 len_text = (text == NULL) ? 0 : strlen (text);
3892 len_title = (title == NULL) ? 0 : strlen (title);
3894 if ((len_text == 0) && (len_title == 0))
3895 return;
3897 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
3899 if (len_title > 0) {
3900 PangoAttrList *attrs;
3901 PangoAttribute *attr;
3903 label = gtk_label_new (title);
3905 attrs = pango_attr_list_new ();
3906 attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
3907 attr->start_index = 0;
3908 attr->end_index = G_MAXINT;
3909 pango_attr_list_insert (attrs, attr);
3910 gtk_label_set_attributes (GTK_LABEL (label), attrs);
3911 pango_attr_list_unref (attrs);
3913 gtk_widget_set_halign (label, GTK_ALIGN_START);
3914 gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
3916 if (len_text > 0) {
3917 label = gtk_label_new (text);
3919 gtk_widget_set_halign (label, GTK_ALIGN_START);
3920 gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
3921 if (len_title > 0)
3922 gtk_box_set_spacing (GTK_BOX (box), 10);
3924 gnm_convert_to_tooltip (GTK_WIDGET (scg->grid), box);
3925 scg->im.item = gtk_widget_get_toplevel (box);
3927 x = sheet_col_get_distance_pixels
3928 (sheet, pane->first.col, pos->col + (rtl ? 1 : 0));
3930 y = sheet_row_get_distance_pixels
3931 (sheet, pane->first.row, pos->row + 1);
3933 gtk_widget_get_allocation (GTK_WIDGET (pane), &allocation);
3934 if (rtl)
3935 x = allocation.width - x;
3936 x += allocation.x;
3937 y += allocation.y;
3939 gdk_window_get_position
3940 (gtk_widget_get_parent_window (GTK_WIDGET (pane)),
3941 &x_origin, &y_origin);
3942 x += x_origin;
3943 y += y_origin;
3945 gtk_window_move (GTK_WINDOW (scg->im.item), x + 10, y + 10);
3946 gtk_widget_show_all (scg->im.item);
3947 scg->im.timer = g_timeout_add (1500, (GSourceFunc)cb_cell_im_timer, scg);
3952 static void
3953 scg_class_init (GObjectClass *object_class)
3955 SheetControlClass *sc_class = SHEET_CONTROL_CLASS (object_class);
3957 g_return_if_fail (sc_class != NULL);
3959 scg_parent_class = g_type_class_peek_parent (object_class);
3961 object_class->finalize = scg_finalize;
3963 sc_class->resize = scg_resize_virt;
3964 sc_class->redraw_all = scg_redraw_all;
3965 sc_class->redraw_range = scg_redraw_range;
3966 sc_class->redraw_headers = scg_redraw_headers;
3967 sc_class->ant = scg_ant;
3968 sc_class->unant = scg_unant;
3969 sc_class->scrollbar_config = scg_scrollbar_config;
3970 sc_class->mode_edit = scg_mode_edit_virt;
3971 sc_class->set_top_left = scg_set_top_left;
3972 sc_class->recompute_visible_region = scg_recompute_visible_region;
3973 sc_class->make_cell_visible = scg_make_cell_visible_virt;
3974 sc_class->cursor_bound = scg_cursor_bound;
3975 sc_class->set_panes = scg_set_panes;
3976 sc_class->object_create_view = scg_object_create_view;
3977 sc_class->scale_changed = scg_scale_changed;
3978 sc_class->show_im_tooltip = scg_show_im_tooltip;
3981 GSF_CLASS (SheetControlGUI, sheet_control_gui,
3982 scg_class_init, scg_init, GNM_SC_TYPE)
3984 static gint
3985 cb_scg_queued_movement (SheetControlGUI *scg)
3987 Sheet const *sheet = scg_sheet (scg);
3988 scg->delayedMovement.timer = 0;
3989 (*scg->delayedMovement.handler) (scg,
3990 scg->delayedMovement.n, FALSE,
3991 scg->delayedMovement.horiz);
3992 if (wbcg_is_editing (scg->wbcg))
3993 sheet_update_only_grid (sheet);
3994 else
3995 sheet_update (sheet);
3996 return FALSE;
4000 * scg_queue_movement:
4001 * @scg:
4002 * @handler: (scope async): The movement handler
4003 * @n: how far
4004 * @jump: TRUE jump to bound
4005 * @horiz: TRUE move by cols
4007 * Do motion compression when possible to avoid redrawing an area that will
4008 * disappear when we scroll again.
4010 void
4011 scg_queue_movement (SheetControlGUI *scg,
4012 SCGUIMoveFunc handler,
4013 int n, gboolean jump, gboolean horiz)
4015 g_return_if_fail (GNM_IS_SCG (scg));
4017 /* do we need to flush a pending movement */
4018 if (scg->delayedMovement.timer != 0) {
4019 if (jump ||
4020 /* do not skip more than 3 requests at a time */
4021 scg->delayedMovement.counter > 3 ||
4022 scg->delayedMovement.handler != handler ||
4023 scg->delayedMovement.horiz != horiz) {
4024 g_source_remove (scg->delayedMovement.timer);
4025 (*scg->delayedMovement.handler) (scg,
4026 scg->delayedMovement.n, FALSE,
4027 scg->delayedMovement.horiz);
4028 scg->delayedMovement.handler = NULL;
4029 scg->delayedMovement.timer = 0;
4030 } else {
4031 scg->delayedMovement.counter++;
4032 scg->delayedMovement.n += n;
4033 return;
4037 /* jumps are always immediate */
4038 if (jump) {
4039 Sheet const *sheet = scg_sheet (scg);
4040 (*handler) (scg, n, TRUE, horiz);
4041 if (wbcg_is_editing (scg->wbcg))
4042 sheet_update_only_grid (sheet);
4043 else
4044 sheet_update (sheet);
4045 return;
4048 scg->delayedMovement.counter = 1;
4049 scg->delayedMovement.handler = handler;
4050 scg->delayedMovement.horiz = horiz;
4051 scg->delayedMovement.n = n;
4052 scg->delayedMovement.timer = g_timeout_add (10,
4053 (GSourceFunc)cb_scg_queued_movement, scg);
4056 static void
4057 scg_image_create (SheetControlGUI *scg, SheetObjectAnchor *anchor,
4058 guint8 const *data, unsigned len)
4060 SheetObjectImage *soi;
4061 SheetObject *so;
4062 double w, h;
4064 /* ensure that we are not editing anything else */
4065 scg_mode_edit (scg);
4067 soi = g_object_new (GNM_SO_IMAGE_TYPE, NULL);
4068 sheet_object_image_set_image (soi, "", data, len);
4070 so = GNM_SO (soi);
4071 sheet_object_set_anchor (so, anchor);
4072 sheet_object_set_sheet (so, scg_sheet (scg));
4073 scg_object_select (scg, so);
4074 sheet_object_default_size (so, &w, &h);
4075 scg_objects_drag (scg, NULL, NULL, &w, &h, 7, FALSE, FALSE, FALSE);
4076 scg_objects_drag_commit (scg, 7, TRUE, NULL, NULL, NULL);
4079 void
4080 scg_paste_image (SheetControlGUI *scg, GnmRange *where,
4081 guint8 const *data, unsigned len)
4083 SheetObjectAnchor anchor;
4085 sheet_object_anchor_init (&anchor, where, NULL,
4086 GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4087 scg_image_create (scg, &anchor, data, len);
4090 static void
4091 scg_drag_receive_img_data (SheetControlGUI *scg, double x, double y,
4092 guint8 const *data, unsigned len)
4094 double coords[4];
4095 SheetObjectAnchor anchor;
4097 sheet_object_anchor_init (&anchor, NULL, NULL,
4098 GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4099 coords[0] = coords[2] = x;
4100 coords[1] = coords[3] = y;
4101 scg_object_coords_to_anchor (scg, coords, &anchor);
4102 scg_image_create (scg, &anchor, data, len);
4105 static void
4106 scg_drag_receive_img_uri (SheetControlGUI *scg, double x, double y, const gchar *uri)
4108 GError *err = NULL;
4109 GsfInput *input = go_file_open (uri, &err);
4110 GOIOContext *ioc = go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4112 if (input != NULL) {
4113 unsigned len = gsf_input_size (input);
4114 guint8 const *data = gsf_input_read (input, len, NULL);
4116 scg_drag_receive_img_data (scg, x, y, data, len);
4117 g_object_unref (input);
4118 } else
4119 go_cmd_context_error (GO_CMD_CONTEXT (ioc), err);
4121 if (go_io_error_occurred (ioc) ||
4122 go_io_warning_occurred (ioc)) {
4123 go_io_error_display (ioc);
4124 go_io_error_clear (ioc);
4126 g_object_unref (ioc);
4129 static void
4130 scg_drag_receive_spreadsheet (SheetControlGUI *scg, const gchar *uri)
4132 GError *err = NULL;
4133 GsfInput *input = go_file_open (uri, &err);
4134 GOIOContext *ioc = go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4136 if (input != NULL) {
4137 WorkbookView *wbv;
4139 wbv = workbook_view_new_from_input (input, uri, NULL, ioc, NULL);
4140 if (wbv != NULL)
4141 gui_wb_view_show (scg->wbcg,
4142 wbv);
4144 } else
4145 go_cmd_context_error (GO_CMD_CONTEXT (ioc), err);
4147 if (go_io_error_occurred (ioc) ||
4148 go_io_warning_occurred (ioc)) {
4149 go_io_error_display (ioc);
4150 go_io_error_clear (ioc);
4152 g_object_unref (ioc);
4155 static void
4156 scg_paste_cellregion (SheetControlGUI *scg, double x, double y,
4157 GnmCellRegion *content)
4159 WorkbookControl *wbc = scg_wbc (scg);
4160 Sheet *sheet = scg_sheet (scg) ;
4161 GnmPasteTarget pt;
4162 SheetObjectAnchor anchor;
4163 double coords[4];
4165 sheet_object_anchor_init (&anchor, NULL, NULL,
4166 GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4167 coords[0] = coords[2] = x;
4168 coords[1] = coords[3] = y;
4169 scg_object_coords_to_anchor (scg, coords, &anchor);
4170 paste_target_init (&pt, sheet, &anchor.cell_bound, PASTE_ALL_SHEET);
4171 if (content && ((content->cols > 0 && content->rows > 0) ||
4172 content->objects != NULL))
4173 cmd_paste_copy (wbc, &pt, content);
4176 static void
4177 scg_drag_receive_cellregion (SheetControlGUI *scg, double x, double y,
4178 const char *data, unsigned len)
4180 GnmCellRegion *content;
4181 GOIOContext *io_context =
4182 go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4184 content = gnm_xml_cellregion_read (scg_wbc (scg), io_context,
4185 scg_sheet (scg), data, len);
4186 g_object_unref (io_context);
4187 if (content != NULL) {
4188 scg_paste_cellregion (scg, x, y, content);
4189 cellregion_unref (content);
4193 static void
4194 scg_drag_receive_uri_list (SheetControlGUI *scg, double x, double y,
4195 const char *data, unsigned len)
4197 char *cdata = g_strndup (data, len);
4198 GSList *urls = go_file_split_urls (cdata);
4199 GSList *l;
4201 g_free (cdata);
4202 for (l = urls; l; l = l-> next) {
4203 char const *uri_str = l->data;
4204 gchar *mime = go_get_mime_type (uri_str);
4205 /* Note that we have imperfect detection of mime-type with some
4206 * platforms, e.g. Win32. In the worst case if
4207 * go_get_mime_type() doesn't return "application/x-gnumeric"
4208 * (registry corruption?) it will give "text/plain" and a
4209 * spreadsheet file is assumed. */
4210 if (!mime)
4211 continue;
4213 if (!strncmp (mime, "image/", 6))
4214 scg_drag_receive_img_uri (scg, x, y, uri_str);
4215 else if (!strcmp (mime, "application/x-gnumeric") ||
4216 !strcmp (mime, "application/vnd.ms-excel") ||
4217 !strcmp (mime, "application/vnd.sun.xml.calc") ||
4218 !strcmp (mime, "application/vnd.oasis.opendocument.spreadsheet") ||
4219 !strcmp (mime, "application/vnd.lotus-1-2-3") ||
4220 !strcmp (mime, "application/x-applix-spreadsheet") ||
4221 !strcmp (mime, "application/x-dbase") ||
4222 !strcmp (mime, "application/x-oleo") ||
4223 !strcmp (mime, "application/x-quattropro") ||
4224 !strcmp (mime, "application/x-sc") ||
4225 /* !strcmp (mime, "application/xhtml+xml") || */
4226 !strcmp (mime, "text/spreadsheet") ||
4227 !strcmp (mime, "text/tab-separated-values") ||
4228 !strcmp (mime, "text/x-comma-separated-values") ||
4229 !strcmp (mime, "text/html") ||
4230 !strcmp (mime, "text/plain")) {
4231 scg_drag_receive_spreadsheet (scg, uri_str);
4232 } else {
4233 g_printerr ("Received URI %s with mime type %s.\n", uri_str, mime);
4234 g_printerr ("I have no idea what to do with that.\n");
4236 g_free (mime);
4238 g_slist_free_full (urls, (GDestroyNotify) g_free);
4241 static void
4242 scg_drag_receive_same_process (SheetControlGUI *scg, GtkWidget *source_widget,
4243 double x, double y)
4245 SheetControlGUI *source_scg = NULL;
4246 GnmPane *pane;
4248 g_return_if_fail (source_widget != NULL);
4249 g_return_if_fail (GNM_IS_PANE (source_widget));
4251 pane = GNM_PANE (source_widget);
4252 x *= goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
4253 y *= goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
4254 source_scg = pane->simple.scg;
4255 if (source_scg == scg) {
4256 GdkWindow *window;
4257 GdkModifierType mask;
4258 gint64 xx = x, yy = y;
4259 gint64 origin_x = 0, origin_y = 0;
4260 gboolean make_dup;
4261 GOUndo *undo = NULL;
4262 GOUndo *redo = NULL;
4263 gchar *title = NULL;
4265 window = gtk_widget_get_parent_window (GTK_WIDGET (pane));
4266 gdk_window_get_device_position (window,
4267 gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (gdk_window_get_display (window))),
4268 NULL, NULL, &mask);
4270 make_dup = ((mask & GDK_CONTROL_MASK) != 0);
4272 /* When copying objects, we have to create a copy of current selection.
4273 * Since new objects are on top of canvas, we have to move current selection
4274 * back to original position, create a copy of selected objects, make them
4275 * the current selection, then move these objects to drop location. */
4277 if (make_dup) {
4278 xx = origin_x = pane->drag.origin_x;
4279 yy = origin_y = pane->drag.origin_y;
4282 gnm_pane_objects_drag (pane, NULL, xx, yy, 8, FALSE,
4283 (mask & GDK_SHIFT_MASK) != 0);
4284 pane->drag.origin_x = pane->drag.last_x;
4285 pane->drag.origin_y = pane->drag.last_y;
4287 if (make_dup) {
4288 GSList *ptr, *objs = go_hash_keys (scg->selected_objects);
4289 GOUndo *nudge_undo = NULL;
4290 GOUndo *nudge_redo = NULL;
4291 double dx, dy;
4293 for (ptr = objs ; ptr != NULL ; ptr = ptr->next) {
4294 SheetObject *dup_obj = sheet_object_dup (ptr->data);
4295 if (dup_obj != NULL) {
4296 sheet_object_set_sheet (dup_obj, scg_sheet (scg));
4297 scg_object_select (scg, dup_obj);
4298 g_object_unref (dup_obj);
4299 scg_object_unselect (scg, ptr->data);
4302 g_slist_free (objs);
4303 scg_objects_drag_commit (scg, 8, TRUE, &undo, &redo, &title);
4304 dx = x - origin_x;
4305 dy = y - origin_y;
4306 scg_objects_drag (scg, pane, NULL, &dx, &dy, 8, FALSE, FALSE, FALSE);
4307 scg_objects_drag_commit (scg, 8, FALSE, &nudge_undo, &nudge_redo, NULL);
4308 undo = go_undo_combine (undo, nudge_undo);
4309 redo = go_undo_combine (nudge_redo, redo);
4310 } else
4311 scg_objects_drag_commit (scg, 8, FALSE, &undo, &redo, &title);
4312 cmd_generic (GNM_WBC (scg_wbcg (scg)), title, undo, redo);
4313 g_free (title);
4314 } else {
4315 GnmCellRegion *content;
4316 GSList *objects;
4318 g_return_if_fail (GNM_IS_SCG (source_scg));
4320 objects = go_hash_keys (source_scg->selected_objects);
4321 content = clipboard_copy_obj (scg_sheet (source_scg),
4322 objects);
4323 if (content != NULL) {
4324 scg_paste_cellregion (scg, x, y, content);
4325 cellregion_unref (content);
4327 g_slist_free (objects);
4331 /* Keep in sync with gtk_selection_data_targets_include_text() */
4332 static gboolean
4333 is_text_target (gchar *target_type)
4335 const gchar *charset;
4336 gchar *text_plain_locale;
4337 gboolean ret;
4339 g_get_charset (&charset);
4340 text_plain_locale = g_strdup_printf ("text/plain;charset=%s", charset);
4341 ret = !strcmp (target_type, "UTF8_STRING") ||
4342 !strcmp (target_type, "COMPOUND_TEXT") ||
4343 !strcmp (target_type, "TEXT") ||
4344 !strcmp (target_type, "STRING") ||
4345 !strcmp (target_type, "text/plain;charset=utf-8") ||
4346 !strcmp (target_type, text_plain_locale) ||
4347 !strcmp (target_type, "text/plain");
4348 g_free (text_plain_locale);
4349 return ret;
4352 void
4353 scg_drag_data_received (SheetControlGUI *scg, GtkWidget *source_widget,
4354 double x, double y, GtkSelectionData *selection_data)
4356 gchar *target_type = gdk_atom_name (gtk_selection_data_get_target (selection_data));
4357 const char *sel_data = (const char *)gtk_selection_data_get_data (selection_data);
4358 gsize sel_len = gtk_selection_data_get_length (selection_data);
4360 if (!strcmp (target_type, "text/uri-list")) {
4361 scg_drag_receive_uri_list (scg, x, y, sel_data, sel_len);
4363 } else if (!strncmp (target_type, "image/", 6)) {
4364 scg_drag_receive_img_data (scg, x, y, sel_data, sel_len);
4365 } else if (!strcmp (target_type, "GNUMERIC_SAME_PROC")) {
4366 scg_drag_receive_same_process (scg, source_widget, x, y);
4367 } else if (!strcmp (target_type, "application/x-gnumeric")) {
4368 scg_drag_receive_cellregion (scg, x, y, sel_data, sel_len);
4369 } else
4370 g_warning ("Unknown target type '%s'!", target_type);
4372 if (gnm_debug_flag ("dnd")) {
4373 if (!strcmp (target_type, "x-special/gnome-copied-files")) {
4374 char *cdata = g_strndup (sel_data, sel_len);
4375 g_print ("data length: %d, data: %s\n",
4376 (int)sel_len, cdata);
4377 g_free (cdata);
4378 } else if (!strcmp (target_type, "_NETSCAPE_URL")) {
4379 char *cdata = g_strndup (sel_data, sel_len);
4380 g_print ("data length: %d, data: %s\n",
4381 (int)sel_len, cdata);
4382 g_free (cdata);
4383 } else if (is_text_target (target_type)) {
4384 char *cdata = g_strndup (sel_data, sel_len);
4385 g_print ("data length: %d, data: %s\n",
4386 (int)sel_len, cdata);
4387 g_free (cdata);
4388 } else if (!strcmp (target_type, "text/html")) {
4389 char *cdata = g_strndup (sel_data, sel_len);
4390 /* For mozilla, need to convert the encoding */
4391 g_print ("data length: %d, data: %s\n", (int)sel_len, cdata);
4392 g_free (cdata);
4396 g_free (target_type);
4399 static void
4400 scg_drag_send_image (G_GNUC_UNUSED SheetControlGUI *scg,
4401 GtkSelectionData *selection_data,
4402 GSList *objects,
4403 gchar const *mime_type)
4405 SheetObject *so = NULL;
4406 GsfOutput *output;
4407 GsfOutputMemory *omem;
4408 gsf_off_t osize;
4409 char *format;
4410 GSList *ptr;
4412 for (ptr = objects; ptr != NULL; ptr = ptr->next) {
4413 if (GNM_IS_SO_IMAGEABLE (GNM_SO (ptr->data))) {
4414 so = GNM_SO (ptr->data);
4415 break;
4418 if (so == NULL) {
4419 g_warning ("non imageable object requested as image\n");
4420 return;
4423 format = go_mime_to_image_format (mime_type);
4424 if (!format) {
4425 g_warning ("No image format for %s\n", mime_type);
4426 g_free (format);
4427 return;
4430 output = gsf_output_memory_new ();
4431 omem = GSF_OUTPUT_MEMORY (output);
4432 sheet_object_write_image (so, format, -1.0, output, NULL);
4433 osize = gsf_output_size (output);
4435 gtk_selection_data_set
4436 (selection_data,
4437 gtk_selection_data_get_target (selection_data),
4438 8, gsf_output_memory_get_bytes (omem), osize);
4439 gsf_output_close (output);
4440 g_object_unref (output);
4441 g_free (format);
4444 static void
4445 scg_drag_send_graph (G_GNUC_UNUSED SheetControlGUI *scg,
4446 GtkSelectionData *selection_data,
4447 GSList *objects,
4448 gchar const *mime_type)
4450 SheetObject *so = NULL;
4451 GsfOutput *output;
4452 GsfOutputMemory *omem;
4453 gsf_off_t osize;
4454 GSList *ptr;
4456 for (ptr = objects; ptr != NULL; ptr = ptr->next)
4457 if (GNM_IS_SO_EXPORTABLE (GNM_SO (ptr->data))) {
4458 so = GNM_SO (ptr->data);
4459 break;
4462 if (so == NULL) {
4463 g_warning ("non exportable object requested\n");
4464 return;
4467 output = gsf_output_memory_new ();
4468 omem = GSF_OUTPUT_MEMORY (output);
4469 sheet_object_write_object (so, mime_type, output, NULL,
4470 gnm_conventions_default);
4471 osize = gsf_output_size (output);
4473 gtk_selection_data_set
4474 (selection_data,
4475 gtk_selection_data_get_target (selection_data),
4476 8, gsf_output_memory_get_bytes (omem), osize);
4477 gsf_output_close (output);
4478 g_object_unref (output);
4481 static void
4482 scg_drag_send_clipboard_objects (SheetControl *sc,
4483 GtkSelectionData *selection_data,
4484 GSList *objects)
4486 GnmCellRegion *content = clipboard_copy_obj (sc_sheet (sc), objects);
4487 GsfOutputMemory *output;
4489 if (content == NULL)
4490 return;
4492 output = gnm_cellregion_to_xml (content);
4493 gtk_selection_data_set
4494 (selection_data,
4495 gtk_selection_data_get_target (selection_data),
4497 gsf_output_memory_get_bytes (output),
4498 gsf_output_size (GSF_OUTPUT (output)));
4499 g_object_unref (output);
4500 cellregion_unref (content);
4503 static void
4504 scg_drag_send_text (SheetControlGUI *scg, GtkSelectionData *sd)
4506 Sheet *sheet = scg_sheet (scg);
4507 Workbook *wb = sheet->workbook;
4508 GnmRange range = sheet_get_extent (sheet, TRUE, TRUE);
4509 GnmCellRegion *reg = clipboard_copy_range (sheet, &range);
4510 GString *s = cellregion_to_string (reg, TRUE, workbook_date_conv (wb));
4512 cellregion_unref (reg);
4513 if (!s)
4514 return;
4515 gtk_selection_data_set (sd, gtk_selection_data_get_target (sd),
4516 8, s->str, s->len);
4517 g_string_free (s, TRUE);
4520 void
4521 scg_drag_data_get (SheetControlGUI *scg, GtkSelectionData *selection_data)
4523 GdkAtom target = gtk_selection_data_get_target (selection_data);
4524 gchar *target_name = gdk_atom_name (target);
4525 GSList *objects = scg->selected_objects
4526 ? go_hash_keys (scg->selected_objects)
4527 : NULL;
4529 if (strcmp (target_name, "GNUMERIC_SAME_PROC") == 0)
4530 /* Set dummy selection for process internal dnd */
4531 gtk_selection_data_set (selection_data, target,
4532 8, (const guint8 *)"", 1);
4533 else if (strcmp (target_name, "GNUMERIC_SHEET") == 0)
4534 gtk_selection_data_set (selection_data, target,
4535 8, (void *)scg, sizeof (scg));
4536 else if (strcmp (target_name, "application/x-gnumeric") == 0)
4537 scg_drag_send_clipboard_objects (GNM_SC (scg),
4538 selection_data, objects);
4539 else if (strcmp (target_name, "application/x-goffice-graph") == 0)
4540 scg_drag_send_graph (scg, selection_data, objects, target_name);
4541 else if (strncmp (target_name, "image/", 6) == 0)
4542 scg_drag_send_image (scg, selection_data, objects, target_name);
4543 else if (strcmp (target_name, "UTF8_STRING") == 0)
4544 scg_drag_send_text (scg, selection_data);
4546 g_free (target_name);
4547 g_slist_free (objects);
4550 void
4551 scg_delete_sheet_if_possible (SheetControlGUI *scg)
4553 SheetControl *sc = (SheetControl *) scg;
4554 Sheet *sheet = scg_sheet (scg);
4555 Workbook *wb = sheet->workbook;
4557 /* If this is the last sheet left, ignore the request */
4558 if (workbook_sheet_count (wb) != 1) {
4559 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
4560 WorkbookControl *wbc = sc->wbc;
4561 workbook_sheet_delete (sheet);
4562 /* Careful: sc just ceased to be valid. */
4563 cmd_reorganize_sheets (wbc, old_state, sheet);
4567 void
4568 scg_reload_item_edits (SheetControlGUI *scg)
4570 SCG_FOREACH_PANE (scg, pane, {
4571 if (pane->editor != NULL)
4572 goc_item_bounds_changed
4573 (GOC_ITEM (pane->editor));