Update Spanish translation
[gnumeric.git] / src / sheet-control-gui.c
bloba29f6c1bb71078231db559794cdd06cd450913cd
1 /*
2 * sheet-control-gui.c: Implements a graphic control for a sheet.
4 * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
5 * Copyright (C) 1997-1999 Miguel de Icaza (miguel@kernel.org)
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
24 #include <gnumeric.h>
25 #include <sheet-control-gui-priv.h>
27 #include <sheet.h>
28 #include <sheet-private.h>
29 #include <sheet-view.h>
30 #include <sheet-merge.h>
31 #include <workbook.h>
32 #include <workbook-view.h>
33 #include <workbook-cmd-format.h>
34 #include <wbc-gtk-impl.h>
35 #include <cell.h>
36 #include <selection.h>
37 #include <style.h>
38 #include <sheet-style.h>
39 #include <sheet-object-impl.h>
40 #include <sheet-object-cell-comment.h>
41 #include <sheet-object-image.h>
42 #include <gui-util.h>
43 #include <gutils.h>
44 #include <parse-util.h>
45 #include <selection.h>
46 #include <application.h>
47 #include <cellspan.h>
48 #include <cmd-edit.h>
49 #include <commands.h>
50 #include <gnm-commands-slicer.h>
51 #include <clipboard.h>
52 #include <dialogs/dialogs.h>
53 #include <gui-file.h>
54 #include <sheet-merge.h>
55 #include <ranges.h>
56 #include <xml-sax.h>
57 #include <style-color.h>
58 #include <gnumeric-conf.h>
60 #include <gnm-pane-impl.h>
61 #include <item-bar.h>
62 #include <item-cursor.h>
63 #include <widgets/gnm-expr-entry.h>
64 #include <gnm-sheet-slicer.h>
65 #include <input-msg.h>
67 #include <go-data-slicer-field.h>
68 #include <goffice/goffice.h>
70 #include <gdk/gdkkeysyms.h>
71 #include <gsf/gsf-impl-utils.h>
72 #include <gsf/gsf-input.h>
73 #include <gsf/gsf-output-memory.h>
75 #include <string.h>
77 static GObjectClass *scg_parent_class;
79 static void scg_unant (SheetControl *sc);
80 static void set_resize_pane_pos (SheetControlGUI *scg, GtkPaned *p);
81 static void cb_resize_pane_motion (GtkPaned *p, GParamSpec *pspec, SheetControlGUI *scg);
84 /**
85 * scg_pane:
86 * @scg: #SheetControlGUI
87 * @pane: the pane index.
89 * Returns: (transfer none): the pane.
90 **/
91 GnmPane *
92 scg_pane (SheetControlGUI *scg, int p)
94 /* it is ok to request a pane when we are not frozen */
95 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
96 g_return_val_if_fail (p >= 0, NULL);
97 g_return_val_if_fail (p < 4, NULL);
99 return scg->pane[p];
103 * scg_view:
104 * @scg: #SheetControlGUI
106 * Returns: (transfer none): the sheet view.
108 SheetView *
109 scg_view (SheetControlGUI const *scg)
111 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
112 return scg->sheet_control.view;
117 * scg_sheet:
118 * @scg: #SheetControlGUI
120 * Returns: (transfer none): the sheet.
122 Sheet *
123 scg_sheet (SheetControlGUI const *scg)
125 return sc_sheet ((SheetControl *)scg);
130 * scg_wbc:
131 * @scg: #SheetControlGUI
133 * Returns: (transfer none): the workbook control.
135 WorkbookControl *
136 scg_wbc (SheetControlGUI const *scg)
138 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
139 return scg->sheet_control.wbc;
144 * scg_wbcg:
145 * @scg: #SheetControlGUI
147 * Returns: (transfer none): the #WBCGtk.
150 WBCGtk *
151 scg_wbcg (SheetControlGUI const *scg)
153 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
154 return scg->wbcg;
157 static void
158 scg_redraw_all (SheetControl *sc, gboolean headers)
160 SheetControlGUI *scg = (SheetControlGUI *)sc;
162 g_return_if_fail (GNM_IS_SCG (scg));
164 SCG_FOREACH_PANE (scg, pane, {
165 goc_canvas_invalidate (GOC_CANVAS (pane),
166 G_MININT64, 0, G_MAXINT64, G_MAXINT64);
167 if (headers) {
168 if (NULL != pane->col.canvas)
169 goc_canvas_invalidate (pane->col.canvas,
170 0, 0, G_MAXINT64, G_MAXINT64);
171 if (NULL != pane->row.canvas)
172 goc_canvas_invalidate (pane->row.canvas,
173 0, 0, G_MAXINT64, G_MAXINT64);
178 static void
179 scg_redraw_range (SheetControl *sc, GnmRange const *r)
181 SheetControlGUI *scg = (SheetControlGUI *)sc;
182 Sheet const *sheet = scg_sheet (scg);
183 GnmRange visible, area;
186 * Getting the bounding box causes row respans to be done if
187 * needed. That can be expensive, so just redraw the whole
188 * sheet if the row count is too big.
190 if (r->end.row - r->start.row > 500) {
191 scg_redraw_all (sc, FALSE);
192 return;
195 /* We potentially do a lot of recalcs as part of this, so make sure
196 stuff that caches sub-computations see the whole thing instead
197 of clearing between cells. */
198 gnm_app_recalc_start ();
200 SCG_FOREACH_PANE (scg, pane, {
201 visible.start = pane->first;
202 visible.end = pane->last_visible;
204 if (range_intersection (&area, r, &visible)) {
205 sheet_range_bounding_box (sheet, &area);
206 gnm_pane_redraw_range (pane, &area);
208 };);
210 gnm_app_recalc_finish ();
213 static void
214 scg_redraw_headers (SheetControl *sc,
215 gboolean const col, gboolean const row,
216 GnmRange const * r /* optional == NULL */)
218 SheetControlGUI *scg = (SheetControlGUI *)sc;
219 GnmPane *pane;
220 int i;
221 double scale;
224 * A rough guess of the trade off point between of redrawing all
225 * and calculating the redraw size
227 const int COL_HEURISTIC = 20;
228 const int ROW_HEURISTIC = 50;
230 for (i = scg->active_panes; i-- > 0 ; ) {
231 if (NULL == (pane = scg->pane[i]))
232 continue;
234 if (col && pane->col.canvas != NULL) {
235 int left = 0, right = G_MAXINT - 1;
236 GocCanvas * const col_canvas = GOC_CANVAS (pane->col.canvas);
237 scale = goc_canvas_get_pixels_per_unit (col_canvas);
239 if (r != NULL) {
240 int const size = r->end.col - r->start.col;
241 if (-COL_HEURISTIC < size && size < COL_HEURISTIC) {
242 left = pane->first_offset.x +
243 scg_colrow_distance_get (scg, TRUE,
244 pane->first.col, r->start.col);
245 right = left +
246 scg_colrow_distance_get (scg, TRUE,
247 r->start.col, r->end.col+1);
250 goc_canvas_invalidate (col_canvas,
251 left / scale, 0, right / scale, G_MAXINT64);
254 if (row && pane->row.canvas != NULL) {
255 gint64 top = 0, bottom = G_MAXINT64 - 1;
256 scale = goc_canvas_get_pixels_per_unit (pane->row.canvas);
257 if (r != NULL) {
258 int const size = r->end.row - r->start.row;
259 if (-ROW_HEURISTIC < size && size < ROW_HEURISTIC) {
260 top = pane->first_offset.y +
261 scg_colrow_distance_get (scg, FALSE,
262 pane->first.row, r->start.row);
263 bottom = top +
264 scg_colrow_distance_get (scg, FALSE,
265 r->start.row, r->end.row+1);
268 goc_canvas_invalidate (GOC_CANVAS (pane->row.canvas),
269 0, top / scale, G_MAXINT64, bottom / scale);
274 static void
275 cb_outline_button (GtkWidget *btn, SheetControlGUI *scg)
277 SheetControl *sc = (SheetControl *) scg;
278 WorkbookControl *wbc = sc->wbc;
279 GPtrArray const *btns;
280 unsigned i = 0;
281 gboolean is_cols = g_object_get_data (G_OBJECT (btn), "is_cols") != NULL;
283 /* which button */
284 btns = is_cols ? scg->col_group.buttons : scg->row_group.buttons;
285 for (i = 0; i < btns->len; i++)
286 if (g_ptr_array_index (btns, i) == btn)
287 break;
289 g_return_if_fail (i < btns->len);
291 cmd_global_outline_change (wbc, is_cols, i+1);
294 static void
295 scg_setup_group_buttons (SheetControlGUI *scg, unsigned max_outline,
296 GnmItemBar const *ib, gboolean is_cols, int w, int h,
297 GPtrArray *btns, GtkWidget *box)
299 PangoFontDescription *font_desc;
300 unsigned i;
301 Sheet const *sheet = scg_sheet (scg);
303 if (!sheet->display_outlines)
304 max_outline = 0;
305 else if (max_outline > 0)
306 max_outline++;
308 while (btns->len > max_outline) {
309 GtkWidget *w = g_ptr_array_remove_index_fast (btns, btns->len - 1);
310 gtk_container_remove (GTK_CONTAINER (box),
311 gtk_widget_get_parent (w));
314 while (btns->len < max_outline) {
315 GtkWidget *out = gtk_alignment_new (.5, .5, 1., 1.);
316 GtkWidget *in = gtk_alignment_new (.5, .5, 0., 0.);
317 GtkWidget *btn = gtk_button_new ();
318 char *tmp = g_strdup_printf ("<small>%d</small>", btns->len+1);
319 GtkWidget *label = gtk_label_new (NULL);
320 gtk_label_set_markup (GTK_LABEL (label), tmp);
321 g_free (tmp);
323 gtk_widget_set_can_focus (btn, FALSE);
324 gtk_container_add (GTK_CONTAINER (in), label);
325 gtk_container_add (GTK_CONTAINER (btn), in);
326 gtk_container_add (GTK_CONTAINER (out), btn);
327 gtk_box_pack_start (GTK_BOX (box), out, TRUE, TRUE, 0);
328 g_ptr_array_add (btns, btn);
330 g_signal_connect (G_OBJECT (btn),
331 "clicked",
332 G_CALLBACK (cb_outline_button), scg);
333 if (is_cols)
334 g_object_set_data (G_OBJECT (btn),
335 "is_cols", GINT_TO_POINTER (1));
338 font_desc = item_bar_normal_font (ib);
340 /* size all of the button so things work after a zoom */
341 for (i = 0 ; i < btns->len ; i++) {
342 GtkWidget *btn = g_ptr_array_index (btns, i);
343 GtkWidget *label = gtk_bin_get_child (GTK_BIN (gtk_bin_get_child (GTK_BIN (btn))));
344 gtk_widget_set_size_request (GTK_WIDGET (btn), w, h);
345 gtk_widget_override_font (label, font_desc);
348 pango_font_description_free (font_desc);
349 gtk_widget_show_all (box);
352 static void
353 scg_resize (SheetControlGUI *scg, G_GNUC_UNUSED gboolean force_scroll)
355 Sheet const *sheet = scg_sheet (scg);
356 GnmPane *pane = scg_pane (scg, 0);
357 int h, w, btn_h, btn_w, tmp;
359 if (!pane)
360 return;
362 /* Recalibrate the starting offsets */
363 pane->first_offset.x = scg_colrow_distance_get (scg,
364 TRUE, 0, pane->first.col);
365 pane->first_offset.y = scg_colrow_distance_get (scg,
366 FALSE, 0, pane->first.row);
368 /* resize Pane[0] headers */
369 h = gnm_item_bar_calc_size (scg->pane[0]->col.item);
370 btn_h = h - gnm_item_bar_indent (scg->pane[0]->col.item);
371 w = gnm_item_bar_calc_size (scg->pane[0]->row.item);
372 btn_w = w - gnm_item_bar_indent (scg->pane[0]->row.item);
373 gtk_widget_set_size_request (scg->select_all_btn, btn_w, btn_h);
374 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[0]->col.canvas), -1, h);
375 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[0]->row.canvas), w, -1);
377 tmp = gnm_item_bar_group_size (scg->pane[0]->col.item,
378 sheet->cols.max_outline_level);
379 scg_setup_group_buttons (scg, sheet->cols.max_outline_level,
380 scg->pane[0]->col.item, TRUE,
381 tmp, tmp, scg->col_group.buttons, scg->col_group.button_box);
382 scg_setup_group_buttons (scg, sheet->rows.max_outline_level,
383 scg->pane[0]->row.item, FALSE,
384 -1, btn_h, scg->row_group.buttons, scg->row_group.button_box);
386 if (scg->active_panes != 1 && gnm_sheet_view_is_frozen (scg_view (scg))) {
387 GnmCellPos const *tl = &scg_view (scg)->frozen_top_left;
388 GnmCellPos const *br = &scg_view (scg)->unfrozen_top_left;
389 int const l = scg_colrow_distance_get (scg, TRUE,
390 0, tl->col);
391 int const r = scg_colrow_distance_get (scg, TRUE,
392 tl->col, br->col) + l;
393 int const t = scg_colrow_distance_get (scg, FALSE,
394 0, tl->row);
395 int const b = scg_colrow_distance_get (scg, FALSE,
396 tl->row, br->row) + t;
397 int i;
398 int fw = MIN (scg->screen_width, r - l);
399 int fh = MIN (scg->screen_height, b - t);
401 /* pane 0 has already been done */
402 for (i = scg->active_panes; i-- > 1 ; ) {
403 GnmPane *pane = scg->pane[i];
404 if (NULL != pane) {
405 pane->first_offset.x = scg_colrow_distance_get (
406 scg, TRUE, 0, pane->first.col);
407 pane->first_offset.y = scg_colrow_distance_get (
408 scg, FALSE, 0, pane->first.row);
412 if (scg->pane[1]) {
413 if (gnm_debug_flag ("frozen-panes"))
414 g_printerr ("Pane 1: %d\n", r - l);
416 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[1]), fw, -1);
417 /* The item_bar_calcs should be equal */
418 /* FIXME : The canvas gets confused when the initial scroll
419 * region is set too early in its life cycle.
420 * It likes it to be at the origin, we can live with that for now.
421 * However, we really should track the bug eventually.
423 h = gnm_item_bar_calc_size (scg->pane[1]->col.item);
424 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[1]->col.canvas), fw, h);
427 if (scg->pane[3]) {
428 if (gnm_debug_flag ("frozen-panes"))
429 g_printerr ("Pane 2: %d\n", b - t);
431 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[3]), -1, fh);
432 /* The item_bar_calcs should be equal */
433 w = gnm_item_bar_calc_size (scg->pane[3]->row.item);
434 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[3]->row.canvas), w, fh);
437 if (scg->pane[2]) {
438 if (gnm_debug_flag ("frozen-panes"))
439 g_printerr ("Pane 3: %d %d\n", r - l, b - t);
441 gtk_widget_set_size_request (GTK_WIDGET (scg->pane[2]), fw, fh);
445 SCG_FOREACH_PANE (scg, pane, {
446 gnm_pane_reposition_cursors (pane);
450 static void
451 scg_resize_virt (SheetControl *sc, gboolean force_scroll)
453 scg_resize ((SheetControlGUI *)sc, force_scroll);
456 static void
457 gnm_adjustment_configure (GtkAdjustment *adjustment,
458 gdouble value,
459 gdouble lower,
460 gdouble upper,
461 gdouble step_increment,
462 gdouble page_increment,
463 gdouble page_size)
465 g_object_freeze_notify (G_OBJECT (adjustment));
467 // These do nothing if value isn't changed
468 gtk_adjustment_set_lower (adjustment, lower);
469 gtk_adjustment_set_upper (adjustment, upper);
470 gtk_adjustment_set_step_increment (adjustment, step_increment);
471 gtk_adjustment_set_page_increment (adjustment, page_increment);
472 gtk_adjustment_set_page_size (adjustment, page_size);
474 g_object_thaw_notify (G_OBJECT (adjustment));
476 // These fire signals if nothing changes, so check by hand
477 if (!(gtk_adjustment_get_value (adjustment) == value))
478 gtk_adjustment_set_value (adjustment, value);
483 * scg_scrollbar_config:
484 * @sc:
486 * Manages the scrollbar dimensions and paging parameters.
487 * Currently sizes things based on the cols/rows visible in pane-0. This has
488 * several subtleties.
490 * 1) Using cols/rows instead of pixels means that the scrollbar changes size
491 * as it passes through regions with different sized cols/rows.
493 * 2) It does NOT take into account hidden rows/cols So a region that contains
494 * a large hidden segment will appear larger.
496 * 3) It only uses pane-0 because that is the only one that can scroll in both
497 * dimensions. The others are of fixed size.
499 static gboolean
500 scg_scrollbar_config_real (SheetControl const *sc)
502 SheetControlGUI *scg = GNM_SCG (sc);
503 GtkAdjustment *va = scg->va;
504 GtkAdjustment *ha = scg->ha;
505 GnmPane *pane = scg_pane (scg, 0);
506 SheetView const *sv = sc->view;
507 Sheet const *sheet = sv->sheet;
509 if (pane) {
510 int const last_col = pane->last_full.col;
511 int const last_row = pane->last_full.row;
512 int max_col = last_col;
513 int max_row = last_row;
515 if (max_row < sheet->rows.max_used)
516 max_row = sheet->rows.max_used;
517 if (max_row < sheet->max_object_extent.row)
518 max_row = sheet->max_object_extent.row;
519 gnm_adjustment_configure
520 (va,
521 pane->first.row,
522 gnm_sheet_view_is_frozen (sv) ? sv->unfrozen_top_left.row : 0,
523 max_row + 1,
525 MAX (gtk_adjustment_get_page_size (va) - 3.0, 1.0),
526 last_row - pane->first.row + 1);
528 if (max_col < sheet->cols.max_used)
529 max_col = sheet->cols.max_used;
530 if (max_col < sheet->max_object_extent.col)
531 max_col = sheet->max_object_extent.col;
532 gnm_adjustment_configure
533 (ha,
534 pane->first.col,
535 gnm_sheet_view_is_frozen (sv) ? sv->unfrozen_top_left.col : 0,
536 max_col + 1,
538 MAX (gtk_adjustment_get_page_size (ha) - 3.0, 1.0),
539 last_col - pane->first.col + 1);
542 scg->scroll_bar_timer = 0;
543 return FALSE;
547 static void
548 scg_scrollbar_config (SheetControl *sc)
550 SheetControlGUI *scg = GNM_SCG (sc);
551 /* See bug 789412 */
552 if (!scg->scroll_bar_timer)
553 scg->scroll_bar_timer =
554 g_timeout_add (1,
555 (GSourceFunc) scg_scrollbar_config_real,
556 scg);
559 void
560 scg_colrow_size_set (SheetControlGUI *scg,
561 gboolean is_cols, int index, int new_size_pixels)
563 WorkbookControl *wbc = scg_wbc (scg);
564 SheetView *sv = scg_view (scg);
566 /* If all cols/rows in the selection are completely selected
567 * then resize all of them, otherwise just resize the selected col/row.
569 if (!sv_is_full_colrow_selected (sv, is_cols, index))
570 cmd_resize_colrow (wbc, sv->sheet, is_cols,
571 colrow_get_index_list (index, index, NULL), new_size_pixels);
572 else
573 workbook_cmd_resize_selected_colrow (wbc, sv->sheet, is_cols,
574 new_size_pixels);
577 void
578 scg_select_all (SheetControlGUI *scg)
580 Sheet *sheet = scg_sheet (scg);
581 gboolean const rangesel = wbcg_rangesel_possible (scg->wbcg);
583 if (rangesel) {
584 scg_rangesel_bound (scg,
585 0, 0, gnm_sheet_get_last_col (sheet), gnm_sheet_get_last_row (sheet));
586 gnm_expr_entry_signal_update (
587 wbcg_get_entry_logical (scg->wbcg), TRUE);
588 } else if (wbc_gtk_get_guru (scg->wbcg) == NULL) {
589 SheetView *sv = scg_view (scg);
591 scg_mode_edit (scg);
592 wbcg_edit_finish (scg->wbcg, WBC_EDIT_REJECT, NULL);
593 sv_selection_reset (sv);
594 sv_selection_add_full (sv, sv->edit_pos.col, sv->edit_pos.row,
595 0, 0, gnm_sheet_get_last_col (sheet),
596 gnm_sheet_get_last_row (sheet),
597 GNM_SELECTION_MODE_ADD);
599 sheet_update (sheet);
602 gboolean
603 scg_colrow_select (SheetControlGUI *scg, gboolean is_cols,
604 int index, int modifiers)
606 SheetView *sv = scg_view (scg);
607 gboolean const rangesel = wbcg_rangesel_possible (scg->wbcg);
609 if (!rangesel &&
610 !wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
611 return FALSE;
613 if (modifiers & GDK_SHIFT_MASK) {
614 if (rangesel) {
615 if (is_cols)
616 scg_rangesel_extend_to (scg, index, -1);
617 else
618 scg_rangesel_extend_to (scg, -1, index);
619 } else {
620 if (is_cols)
621 sv_selection_extend_to (sv, index, -1);
622 else
623 sv_selection_extend_to (sv, -1, index);
625 } else {
626 if (!rangesel && !(modifiers & GDK_CONTROL_MASK))
627 sv_selection_reset (sv);
629 if (rangesel) {
630 if (is_cols)
631 scg_rangesel_bound (scg,
632 index, 0, index, gnm_sheet_get_last_row (sv->sheet));
633 else
634 scg_rangesel_bound (scg,
635 0, index, gnm_sheet_get_last_col (sv->sheet), index);
636 } else if (is_cols) {
637 GnmPane *pane =
638 scg_pane (scg, scg->pane[3] ? 3 : 0);
639 sv_selection_add_full (sv,
640 index, pane->first.row,
641 index, 0,
642 index, gnm_sheet_get_last_row (sv->sheet),
643 GNM_SELECTION_MODE_ADD);
644 } else {
645 GnmPane *pane =
646 scg_pane (scg, scg->pane[1] ? 1 : 0);
647 sv_selection_add_full (sv,
648 pane->first.col, index,
649 0, index,
650 gnm_sheet_get_last_col (sv->sheet), index,
651 GNM_SELECTION_MODE_ADD);
655 /* The edit pos, and the selection may have changed */
656 if (!rangesel)
657 sheet_update (sv->sheet);
658 return TRUE;
661 /***************************************************************************/
663 static void
664 cb_select_all_btn_draw (GtkWidget *widget, cairo_t *cr, SheetControlGUI *scg)
666 int offset = scg_sheet (scg)->text_is_rtl ? -1 : 0;
667 GtkAllocation a;
668 GtkStyleContext *ctxt = gtk_widget_get_style_context (widget);
670 gtk_widget_get_allocation (widget, &a);
672 gtk_style_context_save (ctxt);
673 gtk_style_context_set_state (ctxt, GTK_STATE_FLAG_NORMAL);
674 gtk_render_background (ctxt, cr, offset + 1, 1,
675 a.width - 1, a.height - 1);
676 gtk_render_frame (ctxt, cr, offset, 0, a.width + 1, a.height + 1);
677 gtk_style_context_restore (ctxt);
680 static gboolean
681 cb_select_all_btn_event (G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event, SheetControlGUI *scg)
683 if (event->type == GDK_BUTTON_PRESS) {
684 scg_select_all (scg);
685 return TRUE;
688 return FALSE;
691 static void
692 cb_vscrollbar_value_changed (GtkRange *range, SheetControlGUI *scg)
694 GtkAdjustment *adj = gtk_range_get_adjustment (range);
695 scg_set_top_row (scg, gtk_adjustment_get_value (adj));
698 static void
699 cb_hscrollbar_value_changed (GtkRange *range, SheetControlGUI *scg)
701 GtkAdjustment *adj = gtk_range_get_adjustment (range);
702 scg_set_left_col (scg, gtk_adjustment_get_value (adj));
705 static void
706 cb_hscrollbar_adjust_bounds (GtkRange *range, gdouble new_value, Sheet *sheet)
708 GtkAdjustment *adj = gtk_range_get_adjustment (range);
709 double upper = gtk_adjustment_get_upper (adj);
710 double page_size = gtk_adjustment_get_page_size (adj);
711 gdouble limit = upper - page_size;
712 if (upper < gnm_sheet_get_max_cols (sheet) && new_value >= limit) {
713 upper = new_value + page_size + 1;
714 if (upper > gnm_sheet_get_max_cols (sheet))
715 upper = gnm_sheet_get_max_cols (sheet);
716 gtk_adjustment_set_upper (adj, upper);
719 static void
720 cb_vscrollbar_adjust_bounds (GtkRange *range, gdouble new_value, Sheet *sheet)
722 GtkAdjustment *adj = gtk_range_get_adjustment (range);
723 double upper = gtk_adjustment_get_upper (adj);
724 double page_size = gtk_adjustment_get_page_size (adj);
725 gdouble limit = upper - page_size;
726 if (upper < gnm_sheet_get_max_rows (sheet) && new_value >= limit) {
727 upper = new_value + page_size + 1;
728 if (upper > gnm_sheet_get_max_rows (sheet))
729 upper = gnm_sheet_get_max_rows (sheet);
730 gtk_adjustment_set_upper (adj, upper);
734 static void
735 cb_table_destroy (SheetControlGUI *scg)
737 SheetControl *sc = (SheetControl *) scg;
738 int i;
740 g_clear_object (&scg->grid);
742 scg_mode_edit (scg); /* finish any object edits */
743 scg_unant (sc); /* Make sure that everything is unanted */
745 if (scg->wbcg) {
746 GtkWindow *toplevel = wbcg_toplevel (scg->wbcg);
748 /* Only pane-0 ever gets focus */
749 if (NULL != toplevel &&
750 gtk_window_get_focus (toplevel) == GTK_WIDGET (scg_pane (scg, 0)))
751 gtk_window_set_focus (toplevel, NULL);
754 for (i = scg->active_panes; i-- > 0 ; )
755 if (NULL != scg->pane[i]) {
756 gtk_widget_destroy (GTK_WIDGET (scg->pane[i]));
757 scg->pane[i] = NULL;
760 g_object_unref (scg);
763 static void
764 scg_init (SheetControlGUI *scg)
766 scg->comment.selected = NULL;
767 scg->comment.item = NULL;
768 scg->comment.timer = 0;
770 scg->delayedMovement.timer = 0;
771 scg->delayedMovement.handler = NULL;
773 scg->grab_stack = 0;
774 scg->selected_objects = NULL;
776 scg->im.item = NULL;
777 scg->im.timer = 0;
779 // These shouldn't matter and will be overwritten
780 scg->screen_width = 1920;
781 scg->screen_height = 1200;
784 /*************************************************************************/
787 * gnm_pane_update_inital_top_left:
788 * A convenience routine to store the new topleft back in the view.
790 static void
791 gnm_pane_update_inital_top_left (GnmPane const *pane)
793 if (pane->index == 0) {
794 SheetView *sv = scg_view (pane->simple.scg);
795 sv->initial_top_left = pane->first;
799 static gint64
800 bar_set_left_col (GnmPane *pane, int new_first_col)
802 GocCanvas *colc;
803 gint64 col_offset;
806 col_offset = pane->first_offset.x +=
807 scg_colrow_distance_get (pane->simple.scg, TRUE, pane->first.col, new_first_col);
808 pane->first.col = new_first_col;
810 /* Scroll the column headers */
811 if (NULL != (colc = pane->col.canvas))
812 goc_canvas_scroll_to (colc, col_offset / colc->pixels_per_unit, 0);
814 return col_offset;
817 static void
818 gnm_pane_set_left_col (GnmPane *pane, int new_first_col)
820 Sheet *sheet;
821 g_return_if_fail (pane != NULL);
822 sheet = scg_sheet (pane->simple.scg);
823 g_return_if_fail (0 <= new_first_col && new_first_col < gnm_sheet_get_max_cols (sheet));
825 if (pane->first.col != new_first_col) {
826 GocCanvas * const canvas = GOC_CANVAS (pane);
827 gint64 const col_offset = bar_set_left_col (pane, new_first_col);
829 gnm_pane_compute_visible_region (pane, FALSE);
830 goc_canvas_scroll_to (canvas, col_offset / canvas->pixels_per_unit, pane->first_offset.y / canvas->pixels_per_unit);
831 gnm_pane_update_inital_top_left (pane);
835 void
836 scg_set_left_col (SheetControlGUI *scg, int col)
838 Sheet const *sheet;
839 GnmRange const *bound;
841 g_return_if_fail (GNM_IS_SCG (scg));
843 sheet = scg_sheet (scg);
844 bound = &sheet->priv->unhidden_region;
845 if (col < bound->start.col)
846 col = bound->start.col;
847 else if (col >= gnm_sheet_get_max_cols (sheet))
848 col = gnm_sheet_get_last_col (sheet);
849 else if (col > bound->end.col)
850 col = bound->end.col;
852 if (scg->pane[1]) {
853 int right = scg_view (scg)->unfrozen_top_left.col;
854 if (col < right)
855 col = right;
857 if (scg->pane[3])
858 gnm_pane_set_left_col (scg_pane (scg, 3), col);
859 gnm_pane_set_left_col (scg_pane (scg, 0), col);
862 static gint64
863 bar_set_top_row (GnmPane *pane, int new_first_row)
865 GocCanvas *rowc;
866 gint64 row_offset;
868 row_offset = pane->first_offset.y +=
869 scg_colrow_distance_get (pane->simple.scg, FALSE, pane->first.row, new_first_row);
870 pane->first.row = new_first_row;
872 /* Scroll the row headers */
873 if (NULL != (rowc = pane->row.canvas))
874 goc_canvas_scroll_to (rowc, 0, row_offset / rowc->pixels_per_unit);
876 return row_offset;
879 static void
880 gnm_pane_set_top_row (GnmPane *pane, int new_first_row)
882 Sheet *sheet;
883 g_return_if_fail (pane != NULL);
884 sheet = scg_sheet (pane->simple.scg);
885 g_return_if_fail (0 <= new_first_row && new_first_row < gnm_sheet_get_max_rows (sheet));
887 if (pane->first.row != new_first_row) {
888 GocCanvas * const canvas = GOC_CANVAS(pane);
889 gint64 const row_offset = bar_set_top_row (pane, new_first_row);
890 gint64 col_offset = pane->first_offset.x;
892 gnm_pane_compute_visible_region (pane, FALSE);
893 goc_canvas_scroll_to (canvas, col_offset / canvas->pixels_per_unit, row_offset / canvas->pixels_per_unit);
894 gnm_pane_update_inital_top_left (pane);
898 void
899 scg_set_top_row (SheetControlGUI *scg, int row)
901 Sheet const *sheet;
902 GnmRange const *bound;
904 g_return_if_fail (GNM_IS_SCG (scg));
906 sheet = scg_sheet (scg);
907 bound = &sheet->priv->unhidden_region;
908 if (row < bound->start.row)
909 row = bound->start.row;
910 else if (row >= gnm_sheet_get_max_rows (sheet))
911 row = gnm_sheet_get_last_row (sheet);
912 else if (row > bound->end.row)
913 row = bound->end.row;
915 if (scg->pane[3]) {
916 int bottom = scg_view (scg)->unfrozen_top_left.row;
917 if (row < bottom)
918 row = bottom;
920 if (scg->pane[1])
921 gnm_pane_set_top_row (scg_pane (scg, 1), row);
922 gnm_pane_set_top_row (scg_pane (scg, 0), row);
925 static void
926 gnm_pane_set_top_left (GnmPane *pane,
927 int col, int row, gboolean force_scroll)
929 gboolean changed = FALSE;
930 gint64 col_offset, row_offset;
931 GocCanvas *canvas;
933 g_return_if_fail (0 <= col &&
934 col < gnm_sheet_get_max_cols (scg_sheet (pane->simple.scg)));
935 g_return_if_fail (0 <= row &&
936 row < gnm_sheet_get_max_rows (scg_sheet (pane->simple.scg)));
938 if (pane->first.col != col || force_scroll) {
939 if (force_scroll) {
940 /* Clear the offsets in case col/row size changed */
941 pane->first_offset.x = 0;
942 pane->first.col = 0;
945 col_offset = bar_set_left_col (pane, col);
946 changed = TRUE;
947 } else {
948 col_offset = pane->first_offset.x;
951 if (pane->first.row != row || force_scroll) {
952 if (force_scroll) {
953 /* Clear the offsets in case col/row size changed */
954 pane->first_offset.y = 0;
955 pane->first.row = 0;
957 row_offset = bar_set_top_row (pane, row);
958 changed = TRUE;
959 } else
960 row_offset = pane->first_offset.y;
962 if (!changed)
963 return;
965 gnm_pane_compute_visible_region (pane, force_scroll);
966 canvas = GOC_CANVAS (pane);
967 goc_canvas_scroll_to (canvas, col_offset / canvas->pixels_per_unit, row_offset / canvas->pixels_per_unit);
968 gnm_pane_update_inital_top_left (pane);
971 static void
972 scg_set_top_left (SheetControl *sc, int col, int row)
974 SheetControlGUI *scg = (SheetControlGUI *)sc;
976 g_return_if_fail (GNM_IS_SCG (scg));
978 if (!scg->pane[0])
979 return;
980 /* We could be faster if necessary */
981 scg_set_left_col (scg, col);
982 scg_set_top_row (scg, row);
985 static void
986 gnm_pane_make_cell_visible (GnmPane *pane, int col, int row,
987 gboolean const force_scroll)
989 GocCanvas *canvas;
990 Sheet *sheet;
991 int new_first_col, new_first_row;
992 GnmRange range;
993 GtkAllocation ca;
995 g_return_if_fail (GNM_IS_PANE (pane));
997 /* Avoid calling this before the canvas is realized: We do not know the
998 * visible area, and would unconditionally scroll the cell to the top
999 * left of the viewport.
1001 if (!gtk_widget_get_realized (GTK_WIDGET (pane)))
1002 return;
1004 sheet = scg_sheet (pane->simple.scg);
1005 g_return_if_fail (col >= 0);
1006 g_return_if_fail (row >= 0);
1007 g_return_if_fail (col < gnm_sheet_get_max_cols (sheet));
1008 g_return_if_fail (row < gnm_sheet_get_max_rows (sheet));
1010 canvas = GOC_CANVAS (pane);
1011 range.start.col = range.end.col = col;
1012 range.start.row = range.end.row = row;
1013 gnm_sheet_merge_find_bounding_box (sheet, &range);
1015 gtk_widget_get_allocation (GTK_WIDGET (canvas), &ca);
1017 /* Find the new pane->first.col */
1018 if (range.start.col < pane->first.col) {
1019 new_first_col = range.start.col;
1020 } else if (range.end.col > pane->last_full.col) {
1021 int width = ca.width;
1022 ColRowInfo const * const ci = sheet_col_get_info (sheet, range.end.col);
1023 if (ci->size_pixels < width) {
1024 int first_col = (pane->last_visible.col == pane->first.col)
1025 ? pane->first.col : range.end.col;
1027 for (; first_col > 0; --first_col) {
1028 ColRowInfo const * const ci = sheet_col_get_info (sheet, first_col);
1029 if (ci->visible) {
1030 width -= ci->size_pixels;
1031 if (width < 0)
1032 break;
1035 new_first_col = first_col+1;
1036 if (new_first_col > range.start.col)
1037 new_first_col = range.start.col;
1038 } else
1039 new_first_col = col;
1040 } else
1041 new_first_col = pane->first.col;
1043 /* Find the new pane->first.row */
1044 if (range.start.row < pane->first.row) {
1045 new_first_row = range.start.row;
1046 } else if (range.end.row > pane->last_full.row) {
1047 int height = ca.height;
1048 ColRowInfo const * const ri = sheet_row_get_info (sheet, range.end.row);
1049 if (ri->size_pixels < height) {
1050 int first_row = (pane->last_visible.row == pane->first.row)
1051 ? pane->first.row : range.end.row;
1053 for (; first_row > 0; --first_row) {
1054 ColRowInfo const * const ri = sheet_row_get_info (sheet, first_row);
1055 if (ri->visible) {
1056 height -= ri->size_pixels;
1057 if (height < 0)
1058 break;
1061 new_first_row = first_row+1;
1062 if (new_first_row > range.start.row)
1063 new_first_row = range.start.row;
1064 } else
1065 new_first_row = row;
1066 } else
1067 new_first_row = pane->first.row;
1069 gnm_pane_set_top_left (pane, new_first_col, new_first_row,
1070 force_scroll);
1074 * scg_make_cell_visible:
1075 * @scg: The gui control
1076 * @col:
1077 * @row:
1078 * @force_scroll: Completely recalibrate the offsets to the new position
1079 * @couple_panes: Scroll scroll dynamic panes back to bounds if target
1080 * is in frozen segment.
1082 * Ensure that cell (col, row) is visible.
1083 * Sheet is scrolled if cell is outside viewport.
1085 void
1086 scg_make_cell_visible (SheetControlGUI *scg, int col, int row,
1087 gboolean force_scroll, gboolean couple_panes)
1089 SheetView const *sv = scg_view (scg);
1090 GnmCellPos const *tl, *br;
1092 g_return_if_fail (GNM_IS_SCG (scg));
1094 if (!scg->active_panes)
1095 return;
1097 tl = &sv->frozen_top_left;
1098 br = &sv->unfrozen_top_left;
1099 if (col < br->col) {
1100 if (row >= br->row) { /* pane 1 */
1101 if (col < tl->col)
1102 col = tl->col;
1103 gnm_pane_make_cell_visible (scg->pane[1],
1104 col, row, force_scroll);
1105 gnm_pane_set_top_left (scg->pane[0],
1106 couple_panes ? br->col : scg->pane[0]->first.col,
1107 scg->pane[1]->first.row,
1108 force_scroll);
1109 if (couple_panes && scg->pane[3])
1110 gnm_pane_set_left_col (scg->pane[3], br->col);
1111 } else if (couple_panes) { /* pane 2 */
1112 /* FIXME : We may need to change the way this routine
1113 * is used to fix this. Because we only know what the
1114 * target cell is we cannot absolutely differentiate
1115 * between col & row scrolling. For now use the
1116 * heuristic that if the col was visible this is a
1117 * vertical jump.
1119 if (scg->pane[2]->first.col <= col &&
1120 scg->pane[2]->last_visible.col >= col) {
1121 scg_set_top_row (scg, row);
1122 } else
1123 scg_set_left_col (scg, col);
1125 } else if (row < br->row) { /* pane 3 */
1126 if (row < tl->row)
1127 row = tl->row;
1128 gnm_pane_make_cell_visible (scg->pane[3],
1129 col, row, force_scroll);
1130 gnm_pane_set_top_left (scg->pane[0],
1131 scg->pane[3]->first.col,
1132 couple_panes
1133 ? br->row
1134 : scg->pane[0]->first.row,
1135 force_scroll);
1136 if (couple_panes && scg->pane[1])
1137 gnm_pane_set_top_row (scg->pane[1],
1138 br->row);
1139 } else { /* pane 0 */
1140 gnm_pane_make_cell_visible (scg->pane[0],
1141 col, row, force_scroll);
1142 if (scg->pane[1])
1143 gnm_pane_set_top_left (scg->pane[1],
1144 tl->col, scg->pane[0]->first.row, force_scroll);
1145 if (scg->pane[3])
1146 gnm_pane_set_top_left (scg->pane[3],
1147 scg->pane[0]->first.col, tl->row, force_scroll);
1149 if (scg->pane[2])
1150 gnm_pane_set_top_left (scg->pane[2],
1151 tl->col, tl->row, force_scroll);
1154 static void
1155 scg_make_cell_visible_virt (SheetControl *sc, int col, int row,
1156 gboolean couple_panes)
1158 scg_make_cell_visible ((SheetControlGUI *)sc, col, row,
1159 FALSE, couple_panes);
1162 /*************************************************************************/
1164 static void
1165 scg_set_panes (SheetControl *sc)
1167 SheetControlGUI *scg = (SheetControlGUI *) sc;
1168 SheetView *sv = sc->view;
1169 gboolean const being_frozen = gnm_sheet_view_is_frozen (sv);
1170 GocDirection direction = (sv_sheet (sv)->text_is_rtl)? GOC_DIRECTION_RTL: GOC_DIRECTION_LTR;
1172 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
1174 if (!scg->pane[0])
1175 return;
1176 if (being_frozen) {
1177 GnmCellPos const *tl = &sv->frozen_top_left;
1178 GnmCellPos const *br = &sv->unfrozen_top_left;
1179 gboolean const freeze_h = br->col > tl->col;
1180 gboolean const freeze_v = br->row > tl->row;
1182 gnm_pane_bound_set (scg->pane[0],
1183 br->col, br->row,
1184 gnm_sheet_get_last_col (sv->sheet), gnm_sheet_get_last_row (sv->sheet));
1186 if (freeze_h) {
1187 scg->active_panes = 2;
1188 if (!scg->pane[1]) {
1189 scg->pane[1] = gnm_pane_new (scg, TRUE, FALSE, 1);
1190 gnm_pane_set_direction (scg->pane[1], direction);
1191 gtk_grid_attach (scg->grid,
1192 GTK_WIDGET (scg->pane[1]),
1193 2, 3, 1, 1);
1194 gtk_grid_attach (scg->grid,
1195 GTK_WIDGET (scg->pane[1]->col.canvas),
1196 2, 0, 1, 2);
1198 gnm_pane_bound_set (scg->pane[1],
1199 tl->col, br->row, br->col - 1, gnm_sheet_get_last_row (sv->sheet));
1201 if (freeze_h && freeze_v) {
1202 scg->active_panes = 4;
1203 if (!scg->pane[2]) {
1204 scg->pane[2] = gnm_pane_new (scg, FALSE, FALSE, 2);
1205 gnm_pane_set_direction (scg->pane[2], direction);
1206 gtk_grid_attach (scg->grid,
1207 GTK_WIDGET (scg->pane[2]),
1208 2, 2, 1, 1);
1210 gnm_pane_bound_set (scg->pane[2],
1211 tl->col, tl->row, br->col - 1, br->row - 1);
1213 if (freeze_v) {
1214 scg->active_panes = 4;
1215 if (!scg->pane[3]) {
1216 scg->pane[3] = gnm_pane_new (scg, FALSE, TRUE, 3);
1217 gnm_pane_set_direction (scg->pane[3], direction);
1218 gtk_grid_attach (scg->grid,
1219 GTK_WIDGET (scg->pane[3]),
1220 3, 2, 1, 1);
1221 gtk_grid_attach (scg->grid,
1222 GTK_WIDGET (scg->pane[3]->row.canvas),
1223 0, 2, 2, 1);
1225 gnm_pane_bound_set (scg->pane[3],
1226 br->col, tl->row, gnm_sheet_get_last_col (sv->sheet), br->row - 1);
1228 } else {
1229 int i;
1230 for (i = 1 ; i <= 3 ; i++)
1231 if (scg->pane[i]) {
1232 gtk_widget_destroy (GTK_WIDGET (scg->pane[i]));
1233 scg->pane[i] = NULL;
1236 scg->active_panes = 1;
1237 gnm_pane_bound_set (scg->pane[0],
1238 0, 0, gnm_sheet_get_last_col (sv->sheet), gnm_sheet_get_last_row (sv->sheet));
1241 gtk_widget_show_all (GTK_WIDGET (scg->grid));
1243 /* in case headers are hidden */
1244 scg_adjust_preferences (scg);
1245 scg_resize (scg, TRUE);
1247 if (being_frozen) {
1248 GnmCellPos const *tl = &sc->view->frozen_top_left;
1250 if (scg->pane[1])
1251 gnm_pane_set_left_col (scg->pane[1], tl->col);
1252 if (scg->pane[2])
1253 gnm_pane_set_top_left (scg->pane[2], tl->col, tl->row, TRUE);
1254 if (scg->pane[3])
1255 gnm_pane_set_top_row (scg->pane[3], tl->row);
1257 set_resize_pane_pos (scg, scg->vpane);
1258 set_resize_pane_pos (scg, scg->hpane);
1261 static void
1262 cb_wbc_destroyed (SheetControlGUI *scg)
1264 scg->wbcg = NULL;
1265 scg->sheet_control.wbc = NULL;
1268 static void
1269 cb_scg_redraw (SheetControlGUI *scg)
1271 scg_adjust_preferences (scg);
1272 scg_redraw_all (&scg->sheet_control, TRUE);
1275 static void
1276 cb_scg_redraw_resize (SheetControlGUI *scg)
1278 cb_scg_redraw (scg);
1279 scg_resize (scg, FALSE);
1282 static void
1283 cb_scg_sheet_resized (SheetControlGUI *scg)
1285 cb_scg_redraw_resize (scg);
1286 sc_set_panes (&scg->sheet_control);
1289 static void
1290 cb_scg_direction_changed (SheetControlGUI *scg)
1292 /* set direction in the canvas */
1293 int i = scg->active_panes;
1294 while (i-- > 0) {
1295 GnmPane *pane = scg->pane[i];
1296 if (NULL != pane)
1297 gnm_pane_set_direction (scg->pane[i],
1298 scg_sheet (scg)->text_is_rtl? GOC_DIRECTION_RTL: GOC_DIRECTION_LTR);
1300 scg_resize (scg, TRUE);
1303 static GnmPane const *
1304 resize_pane_pos (SheetControlGUI *scg, GtkPaned *p,
1305 int *colrow_result, gint64 *guide_pos)
1307 ColRowInfo const *cri;
1308 GnmPane const *pane = scg_pane (scg, 0);
1309 gboolean const vert = (p == scg->hpane);
1310 int colrow, handle;
1311 gint64 pos = gtk_paned_get_position (p);
1313 gtk_widget_style_get (GTK_WIDGET (p), "handle-size", &handle, NULL);
1314 pos += handle / 2;
1315 if (vert) {
1316 if (gtk_widget_get_visible (GTK_WIDGET (pane->row.canvas))) {
1317 GtkAllocation ca;
1318 gtk_widget_get_allocation (GTK_WIDGET (pane->row.canvas), &ca);
1319 pos -= ca.width;
1321 if (scg->pane[1]) {
1322 GtkAllocation pa;
1323 gtk_widget_get_allocation (GTK_WIDGET (scg->pane[1]),
1324 &pa);
1326 if (pos < pa.width)
1327 pane = scg_pane (scg, 1);
1328 else
1329 pos -= pa.width;
1331 pos = MAX (pos, 0);
1332 pos += pane->first_offset.x;
1333 colrow = gnm_pane_find_col (pane, pos, guide_pos);
1334 } else {
1335 if (gtk_widget_get_visible (GTK_WIDGET (pane->col.canvas))) {
1336 GtkAllocation ca;
1337 gtk_widget_get_allocation (GTK_WIDGET (pane->col.canvas), &ca);
1338 pos -= ca.height;
1340 if (scg->pane[3]) {
1341 GtkAllocation pa;
1342 gtk_widget_get_allocation (GTK_WIDGET (scg->pane[3]),
1343 &pa);
1344 if (pos < pa.height)
1345 pane = scg_pane (scg, 3);
1346 else
1347 pos -= pa.height;
1349 pos = MAX (pos, 0);
1350 pos += pane->first_offset.y;
1351 colrow = gnm_pane_find_row (pane, pos, guide_pos);
1353 cri = sheet_colrow_get_info (scg_sheet (scg), colrow, vert);
1354 if (pos >= (*guide_pos + cri->size_pixels / 2)) {
1355 *guide_pos += cri->size_pixels;
1356 colrow++;
1358 if (NULL != colrow_result)
1359 *colrow_result = colrow;
1361 return pane;
1364 static void
1365 scg_gtk_paned_set_position (SheetControlGUI *scg, GtkPaned *p, int pane_pos)
1367 /* A negative position is special to GtkPaned. */
1368 pane_pos = MAX (pane_pos, 0);
1370 if (p == scg->vpane)
1371 scg->vpos = pane_pos;
1372 else
1373 scg->hpos = pane_pos;
1375 gtk_paned_set_position (p, pane_pos);
1378 static void
1379 set_resize_pane_pos (SheetControlGUI *scg, GtkPaned *p)
1381 int handle_size, pane_pos, size;
1382 GnmPane *pane0 = scg->pane[0];
1384 if (!pane0)
1385 return;
1387 if (p == scg->vpane) {
1388 if (gtk_widget_get_visible (GTK_WIDGET (pane0->col.canvas))) {
1389 GtkAllocation alloc;
1390 gtk_widget_get_allocation (GTK_WIDGET (pane0->col.canvas), &alloc);
1391 pane_pos = alloc.height;
1392 } else
1393 pane_pos = 0;
1394 if (scg->pane[3]) {
1395 gtk_widget_get_size_request (
1396 GTK_WIDGET (scg->pane[3]), NULL, &size);
1397 pane_pos += size;
1399 } else {
1400 if (gtk_widget_get_visible (GTK_WIDGET (pane0->row.canvas))) {
1401 GtkAllocation alloc;
1402 gtk_widget_get_allocation (GTK_WIDGET (pane0->row.canvas), &alloc);
1403 pane_pos = alloc.width;
1404 } else
1405 pane_pos = 0;
1406 if (scg->pane[1]) {
1407 gtk_widget_get_size_request (
1408 GTK_WIDGET (scg->pane[1]), &size, NULL);
1409 pane_pos += size;
1412 gtk_widget_style_get (GTK_WIDGET (p), "handle-size", &handle_size, NULL);
1413 pane_pos -= handle_size / 2;
1415 g_signal_handlers_block_by_func (G_OBJECT (p),
1416 G_CALLBACK (cb_resize_pane_motion), scg);
1417 scg_gtk_paned_set_position (scg, p, pane_pos);
1418 g_signal_handlers_unblock_by_func (G_OBJECT (p),
1419 G_CALLBACK (cb_resize_pane_motion), scg);
1422 static void
1423 cb_check_resize (GtkPaned *p, GtkAllocation *allocation, SheetControlGUI *scg);
1425 static gboolean
1426 resize_pane_finish (SheetControlGUI *scg, GtkPaned *p)
1428 SheetView *sv = scg_view (scg);
1429 GnmCellPos frozen_tl, unfrozen_tl;
1430 GnmPane const *pane;
1431 int colrow;
1432 gint64 guide_pos;
1434 #warning GTK3: replace this?
1435 #if 0
1436 if (p->in_drag)
1437 return TRUE;
1438 #endif
1439 pane = resize_pane_pos (scg, p, &colrow, &guide_pos);
1441 if (gnm_sheet_view_is_frozen (sv)) {
1442 frozen_tl = sv->frozen_top_left;
1443 unfrozen_tl = sv->unfrozen_top_left;
1444 } else
1445 frozen_tl = pane->first;
1446 if (p == scg->hpane) {
1447 unfrozen_tl.col = colrow;
1448 if (!gnm_sheet_view_is_frozen (sv))
1449 unfrozen_tl.row = frozen_tl.row = 0;
1450 } else {
1451 unfrozen_tl.row = colrow;
1452 if (!gnm_sheet_view_is_frozen (sv))
1453 unfrozen_tl.col = frozen_tl.col = 0;
1455 gnm_sheet_view_freeze_panes (sv, &frozen_tl, &unfrozen_tl);
1457 scg->pane_drag_handler = 0;
1458 scg_size_guide_stop (scg);
1460 set_resize_pane_pos (scg, p);
1462 g_signal_handlers_unblock_by_func
1463 (G_OBJECT (p),
1464 G_CALLBACK (cb_check_resize), scg);
1466 return FALSE;
1468 static gboolean
1469 cb_resize_vpane_finish (SheetControlGUI *scg)
1471 return resize_pane_finish (scg, scg->vpane);
1473 static gboolean
1474 cb_resize_hpane_finish (SheetControlGUI *scg)
1476 return resize_pane_finish (scg, scg->hpane);
1479 static void
1480 cb_resize_pane_motion (GtkPaned *p,
1481 G_GNUC_UNUSED GParamSpec *pspec,
1482 SheetControlGUI *scg)
1484 gboolean const vert = (p == scg->hpane);
1485 int colrow;
1486 gint64 guide_pos;
1488 resize_pane_pos (scg, p, &colrow, &guide_pos);
1489 #warning GTK3: what replaces p->in_drag?
1490 if (scg->pane_drag_handler == 0/* && p->in_drag*/) {
1491 g_signal_handlers_block_by_func
1492 (G_OBJECT (p),
1493 G_CALLBACK (cb_check_resize), scg);
1494 scg_size_guide_start (scg, vert, colrow, FALSE);
1495 scg->pane_drag_handler = g_timeout_add (250,
1496 vert ? (GSourceFunc) cb_resize_hpane_finish
1497 : (GSourceFunc) cb_resize_vpane_finish,
1498 (gpointer) scg);
1500 if (scg->pane_drag_handler)
1501 scg_size_guide_motion (scg, vert, guide_pos);
1505 static void
1506 cb_check_resize (GtkPaned *p, G_GNUC_UNUSED GtkAllocation *allocation,
1507 SheetControlGUI *scg)
1509 gboolean const vert = (p == scg->vpane);
1510 gint max, pos = vert ? scg->vpos : scg->hpos;
1512 g_object_get (G_OBJECT (p), "max-position", &max, NULL);
1513 if (pos > max)
1514 pos = max;
1516 if (gtk_paned_get_position (p) != pos) {
1517 g_signal_handlers_block_by_func
1518 (G_OBJECT (p),
1519 G_CALLBACK (cb_resize_pane_motion), scg);
1520 gtk_paned_set_position (p, pos);
1521 g_signal_handlers_unblock_by_func
1522 (G_OBJECT (p),
1523 G_CALLBACK (cb_resize_pane_motion), scg);
1527 struct resize_closure {
1528 GtkPaned *p;
1529 SheetControlGUI *scg;
1532 static gboolean
1533 idle_resize (struct resize_closure *r)
1536 set_resize_pane_pos (r->scg, r->p);
1537 g_free (r);
1538 return FALSE;
1541 static void
1542 cb_canvas_resize (GtkWidget *w, G_GNUC_UNUSED GtkAllocation *allocation,
1543 SheetControlGUI *scg)
1545 struct resize_closure *r = g_new (struct resize_closure, 1);
1546 r->scg = scg;
1547 r->p = (w == GTK_WIDGET (scg->pane[0]->col.canvas))? scg->hpane: scg->vpane;
1548 /* The allocation is not correct at this point, weird */
1549 g_idle_add ((GSourceFunc) idle_resize, r);
1552 static gboolean
1553 post_create_cb (SheetControlGUI *scg)
1555 Sheet *sheet = sc_sheet (GNM_SHEET_CONTROL (scg));
1556 if (sheet->sheet_objects)
1557 scg_object_select (scg, (SheetObject *) sheet->sheet_objects->data);
1558 return FALSE;
1561 static gboolean
1562 sheet_object_key_pressed (G_GNUC_UNUSED GtkWidget *w, GdkEventKey *event, SheetControlGUI *scg)
1564 Sheet *sheet = scg_sheet (scg);
1565 WorkbookControl * wbc = scg_wbc (scg);
1566 Workbook * wb = wb_control_get_workbook (wbc);
1567 switch (event->keyval) {
1568 case GDK_KEY_KP_Page_Up:
1569 case GDK_KEY_Page_Up:
1570 if ((event->state & GDK_CONTROL_MASK) != 0){
1571 if ((event->state & GDK_SHIFT_MASK) != 0){
1572 WorkbookSheetState * old_state = workbook_sheet_state_new(wb);
1573 int old_pos = sheet->index_in_wb;
1575 if (old_pos > 0){
1576 workbook_sheet_move(sheet, -1);
1577 cmd_reorganize_sheets (wbc, old_state, sheet);
1579 } else {
1580 gnm_notebook_prev_page (scg->wbcg->bnotebook);
1582 return FALSE;
1584 break;
1585 case GDK_KEY_KP_Page_Down:
1586 case GDK_KEY_Page_Down:
1588 if ((event->state & GDK_CONTROL_MASK) != 0){
1589 if ((event->state & GDK_SHIFT_MASK) != 0){
1590 WorkbookSheetState * old_state = workbook_sheet_state_new(wb);
1591 int num_sheets = workbook_sheet_count(wb);
1592 gint old_pos = sheet->index_in_wb;
1594 if (old_pos < num_sheets - 1){
1595 workbook_sheet_move(sheet, 1);
1596 cmd_reorganize_sheets (wbc, old_state, sheet);
1598 } else {
1599 gnm_notebook_next_page (scg->wbcg->bnotebook);
1601 return FALSE;
1603 break;
1605 return TRUE;
1608 static void
1609 cb_screen_changed (GtkWidget *widget, G_GNUC_UNUSED GdkScreen *prev,
1610 SheetControlGUI *scg)
1612 GdkScreen *screen = gtk_widget_get_screen (widget);
1614 if (screen) {
1615 scg->screen_width = gdk_screen_get_width (screen);
1616 scg->screen_height = gdk_screen_get_height (screen);
1620 SheetControlGUI *
1621 sheet_control_gui_new (SheetView *sv, WBCGtk *wbcg)
1623 SheetControlGUI *scg;
1624 Sheet *sheet;
1625 GocDirection direction;
1626 GdkRGBA cfore, cback;
1628 g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
1630 sheet = sv_sheet (sv);
1631 direction = (sheet->text_is_rtl)? GOC_DIRECTION_RTL: GOC_DIRECTION_LTR;
1633 scg = g_object_new (GNM_SCG_TYPE, NULL);
1634 scg->wbcg = wbcg;
1635 scg->sheet_control.wbc = GNM_WBC (wbcg);
1637 g_object_weak_ref (G_OBJECT (wbcg),
1638 (GWeakNotify) cb_wbc_destroyed,
1639 scg);
1641 if (sheet->sheet_type == GNM_SHEET_DATA) {
1642 scg->active_panes = 1;
1643 scg->pane[0] = NULL;
1644 scg->pane[1] = NULL;
1645 scg->pane[2] = NULL;
1646 scg->pane[3] = NULL;
1647 scg->pane_drag_handler = 0;
1649 scg->col_group.buttons = g_ptr_array_new ();
1650 scg->row_group.buttons = g_ptr_array_new ();
1651 scg->col_group.button_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1652 g_object_set (scg->col_group.button_box,
1653 "halign", GTK_ALIGN_CENTER,
1654 "homogeneous", TRUE,
1655 NULL);
1656 scg->row_group.button_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1657 g_object_set (scg->row_group.button_box,
1658 "valign", GTK_ALIGN_CENTER,
1659 "homogeneous", TRUE,
1660 NULL);
1661 scg->select_all_btn = gtk_drawing_area_new ();
1662 gtk_style_context_add_class (gtk_widget_get_style_context (scg->select_all_btn),
1663 GTK_STYLE_CLASS_BUTTON);
1664 gtk_style_context_add_class (gtk_widget_get_style_context (scg->select_all_btn),
1665 "all");
1666 gtk_widget_add_events (scg->select_all_btn, GDK_BUTTON_PRESS_MASK);
1667 g_signal_connect (G_OBJECT (scg->select_all_btn), "draw",
1668 G_CALLBACK (cb_select_all_btn_draw), scg);
1669 g_signal_connect (G_OBJECT (scg->select_all_btn), "event",
1670 G_CALLBACK (cb_select_all_btn_event), scg);
1672 scg->grid = GTK_GRID (gtk_grid_new ());
1673 gtk_grid_attach (scg->grid, scg->col_group.button_box,
1674 1, 0, 1, 1);
1675 gtk_grid_attach (scg->grid, scg->row_group.button_box,
1676 0, 1, 1, 1);
1677 gtk_grid_attach (scg->grid, scg->select_all_btn, 1, 1, 1, 1);
1679 scg->pane[1] = scg->pane[2] = scg->pane[3] = NULL;
1680 scg->pane[0] = gnm_pane_new (scg, TRUE, TRUE, 0);
1681 gnm_pane_set_direction (scg->pane[0], direction);
1682 gtk_grid_attach (scg->grid,
1683 GTK_WIDGET (scg->pane[0]->col.canvas),
1684 3, 0, 1, 2);
1685 gtk_grid_attach (scg->grid,
1686 GTK_WIDGET (scg->pane[0]->row.canvas),
1687 0, 3, 2, 1);
1688 g_object_set (scg->pane[0],
1689 "hexpand", TRUE,
1690 "vexpand", TRUE,
1691 NULL);
1692 gtk_grid_attach (scg->grid, GTK_WIDGET (scg->pane[0]),
1693 3, 3, 1, 1);
1694 g_signal_connect_after (G_OBJECT (scg->pane[0]->col.canvas), "size-allocate",
1695 G_CALLBACK (cb_canvas_resize), scg);
1696 g_signal_connect_after (G_OBJECT (scg->pane[0]->row.canvas), "size-allocate",
1697 G_CALLBACK (cb_canvas_resize), scg);
1699 scg->va = (GtkAdjustment *)gtk_adjustment_new (0., 0., 1, 1., 1., 1.);
1700 scg->vs = g_object_new (GTK_TYPE_SCROLLBAR,
1701 "orientation", GTK_ORIENTATION_VERTICAL,
1702 "adjustment", scg->va,
1703 NULL);
1704 g_signal_connect (G_OBJECT (scg->vs),
1705 "value_changed",
1706 G_CALLBACK (cb_vscrollbar_value_changed), scg);
1707 g_signal_connect (G_OBJECT (scg->vs),
1708 "adjust_bounds",
1709 G_CALLBACK (cb_vscrollbar_adjust_bounds), sheet);
1711 scg->ha = (GtkAdjustment *)gtk_adjustment_new (0., 0., 1, 1., 1., 1.);
1712 scg->hs = g_object_new (GTK_TYPE_SCROLLBAR,
1713 "adjustment", scg->ha,
1714 NULL);
1715 g_signal_connect (G_OBJECT (scg->hs),
1716 "value_changed",
1717 G_CALLBACK (cb_hscrollbar_value_changed), scg);
1718 g_signal_connect (G_OBJECT (scg->hs),
1719 "adjust_bounds",
1720 G_CALLBACK (cb_hscrollbar_adjust_bounds), sheet);
1722 g_object_ref (scg->grid);
1723 scg->vpane = g_object_new (GTK_TYPE_PANED, "orientation", GTK_ORIENTATION_VERTICAL, NULL);
1724 gtk_paned_add1 (scg->vpane, gtk_label_new (NULL)); /* use a spacer */
1725 gtk_paned_add2 (scg->vpane, scg->vs);
1726 scg_gtk_paned_set_position (scg, scg->vpane, 0);
1727 gtk_widget_set_vexpand (GTK_WIDGET (scg->vpane), TRUE);
1728 gtk_grid_attach (scg->grid,
1729 GTK_WIDGET (scg->vpane), 4, 0, 1, 4);
1730 scg->hpane = g_object_new (GTK_TYPE_PANED, NULL);
1731 gtk_paned_add1 (scg->hpane, gtk_label_new (NULL)); /* use a spacer */
1732 gtk_paned_add2 (scg->hpane, scg->hs);
1733 scg_gtk_paned_set_position (scg, scg->hpane, 0);
1734 gtk_widget_set_hexpand (GTK_WIDGET (scg->hpane), TRUE);
1735 gtk_grid_attach (scg->grid,
1736 GTK_WIDGET (scg->hpane), 0, 4, 4, 1);
1737 /* do not connect until after setting position */
1738 g_signal_connect (G_OBJECT (scg->vpane), "notify::position",
1739 G_CALLBACK (cb_resize_pane_motion), scg);
1740 g_signal_connect (G_OBJECT (scg->hpane), "notify::position",
1741 G_CALLBACK (cb_resize_pane_motion), scg);
1742 g_signal_connect_after (G_OBJECT (scg->vpane), "size-allocate",
1743 G_CALLBACK (cb_check_resize), scg);
1744 g_signal_connect_after (G_OBJECT (scg->hpane), "size-allocate",
1745 G_CALLBACK (cb_check_resize), scg);
1747 g_signal_connect_data (G_OBJECT (scg->grid),
1748 "size-allocate",
1749 G_CALLBACK (scg_scrollbar_config), scg, NULL,
1750 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
1751 g_signal_connect_object (G_OBJECT (scg->grid),
1752 "destroy",
1753 G_CALLBACK (cb_table_destroy), G_OBJECT (scg),
1754 G_CONNECT_SWAPPED);
1756 gnm_sheet_view_attach_control (sv, GNM_SHEET_CONTROL (scg));
1758 g_object_connect (G_OBJECT (sheet),
1759 "swapped_signal::notify::text-is-rtl", cb_scg_direction_changed, scg,
1760 "swapped_signal::notify::display-formulas", cb_scg_redraw, scg,
1761 "swapped_signal::notify::display-zeros", cb_scg_redraw, scg,
1762 "swapped_signal::notify::display-grid", cb_scg_redraw, scg,
1763 "swapped_signal::notify::display-column-header", scg_adjust_preferences, scg,
1764 "swapped_signal::notify::display-row-header", scg_adjust_preferences, scg,
1765 "swapped_signal::notify::use-r1c1", cb_scg_redraw, scg,
1766 "swapped_signal::notify::display-outlines", cb_scg_redraw_resize, scg,
1767 "swapped_signal::notify::display-outlines-below", cb_scg_redraw_resize, scg,
1768 "swapped_signal::notify::display-outlines-right", cb_scg_redraw_resize, scg,
1769 "swapped_signal::notify::columns", cb_scg_sheet_resized, scg,
1770 "swapped_signal::notify::rows", cb_scg_sheet_resized, scg,
1771 NULL);
1772 } else {
1773 scg->active_panes = 0;
1774 scg->grid = GTK_GRID (gtk_grid_new ());
1775 g_object_ref (scg->grid);
1776 sheet->hide_col_header = sheet->hide_row_header = FALSE;
1777 if (sheet->sheet_type == GNM_SHEET_OBJECT) {
1778 /* WHY store this in ->vs? */
1779 scg->vs = g_object_new (GOC_TYPE_CANVAS,
1780 "hexpand", TRUE,
1781 "vexpand", TRUE,
1782 NULL);
1783 gtk_style_context_add_class (gtk_widget_get_style_context (scg->vs),
1784 "full-sheet");
1785 gtk_grid_attach (scg->grid, scg->vs, 0, 0, 1, 1);
1786 gtk_widget_set_can_focus (scg->vs, TRUE);
1787 gtk_widget_set_can_default (scg->vs, TRUE);
1788 g_signal_connect (G_OBJECT (scg->vs), "key-press-event",
1789 G_CALLBACK (sheet_object_key_pressed), scg);
1791 gnm_sheet_view_attach_control (sv, GNM_SHEET_CONTROL (scg));
1792 if (scg->vs) {
1793 g_object_set_data (G_OBJECT (scg->vs), "sheet-control", scg);
1794 if (sheet->sheet_objects) {
1795 /* we need an idle function because not everything is initialized at this point */
1796 sheet_object_new_view ((SheetObject *) sheet->sheet_objects->data,
1797 (SheetObjectViewContainer*) scg->vs);
1798 g_idle_add ((GSourceFunc) post_create_cb, scg);
1803 scg->label = g_object_new
1804 (GNM_NOTEBOOK_BUTTON_TYPE,
1805 "label", sheet->name_unquoted,
1806 //"valign", GTK_ALIGN_START,
1807 "background-color",
1808 (sheet->tab_color
1809 ? go_color_to_gdk_rgba (sheet->tab_color->go_color,
1810 &cback)
1811 : NULL),
1812 "text-color",
1813 (sheet->tab_text_color
1814 ? go_color_to_gdk_rgba (sheet->tab_text_color->go_color,
1815 &cfore)
1816 : NULL),
1817 NULL);
1818 g_object_ref (scg->label);
1820 g_signal_connect (G_OBJECT (scg->grid),
1821 "screen-changed",
1822 G_CALLBACK (cb_screen_changed),
1823 scg);
1825 return scg;
1828 static void
1829 scg_comment_timer_clear (SheetControlGUI *scg)
1831 if (scg->comment.timer != 0) {
1832 g_source_remove (scg->comment.timer);
1833 scg->comment.timer = 0;
1837 static void
1838 scg_im_destroy (SheetControlGUI *scg) {
1839 if (scg->im.timer != 0) {
1840 g_source_remove (scg->im.timer);
1841 scg->im.timer = 0;
1843 if (scg->im.item) {
1844 gtk_widget_destroy (scg->im.item);
1845 scg->im.item = NULL;
1849 static void
1850 scg_finalize (GObject *object)
1852 SheetControlGUI *scg = GNM_SCG (object);
1853 SheetControl *sc = (SheetControl *) scg;
1854 Sheet *sheet = scg_sheet (scg);
1855 GSList *ptr;
1857 /* remove the object view before we disappear */
1858 scg_object_unselect (scg, NULL);
1859 if (*scg->pane)
1860 for (ptr = sheet->sheet_objects; ptr != NULL ; ptr = ptr->next )
1861 SCG_FOREACH_PANE (scg, pane,
1862 g_object_unref (
1863 sheet_object_get_view (ptr->data, (SheetObjectViewContainer *)pane));
1866 if (scg->col_group.buttons) {
1867 g_ptr_array_free (scg->col_group.buttons, TRUE);
1868 g_ptr_array_free (scg->row_group.buttons, TRUE);
1871 if (scg->pane_drag_handler) {
1872 g_source_remove (scg->pane_drag_handler);
1873 scg->pane_drag_handler = 0;
1876 if (scg->scroll_bar_timer) {
1877 g_source_remove (scg->scroll_bar_timer);
1878 scg->scroll_bar_timer = 0;
1881 scg_comment_timer_clear (scg);
1883 if (scg->delayedMovement.timer != 0) {
1884 g_source_remove (scg->delayedMovement.timer);
1885 scg->delayedMovement.timer = 0;
1887 scg_comment_unselect (scg, scg->comment.selected);
1889 scg_im_destroy (scg);
1891 if (sc->view) {
1892 Sheet *sheet = sv_sheet (sc->view);
1893 g_signal_handlers_disconnect_by_func (sheet, scg_adjust_preferences, scg);
1894 g_signal_handlers_disconnect_by_func (sheet, cb_scg_redraw, scg);
1895 g_signal_handlers_disconnect_by_func (sheet, cb_scg_redraw_resize, scg);
1896 g_signal_handlers_disconnect_by_func (sheet, cb_scg_sheet_resized, scg);
1897 g_signal_handlers_disconnect_by_func (sheet, cb_scg_direction_changed, scg);
1898 gnm_sheet_view_detach_control (sc->view, sc);
1901 if (scg->grid) {
1902 gtk_widget_destroy (GTK_WIDGET (scg->grid));
1903 g_object_unref (scg->grid);
1904 scg->grid = NULL;
1907 g_clear_object (&scg->label);
1909 if (scg->wbcg != NULL)
1910 g_object_weak_unref (G_OBJECT (scg->wbcg),
1911 (GWeakNotify) cb_wbc_destroyed,
1912 scg);
1914 (*scg_parent_class->finalize) (object);
1917 static void
1918 scg_unant (SheetControl *sc)
1920 SheetControlGUI *scg = (SheetControlGUI *)sc;
1922 g_return_if_fail (GNM_IS_SCG (scg));
1924 /* Always have a pane 0 */
1925 if (scg->active_panes == 0 || scg->pane[0]->cursor.animated == NULL)
1926 return;
1928 SCG_FOREACH_PANE (scg, pane, {
1929 GSList *l;
1931 for (l = pane->cursor.animated; l; l = l->next) {
1932 GocItem *item = l->data;
1933 goc_item_destroy (item);
1936 g_slist_free (pane->cursor.animated);
1937 pane->cursor.animated = NULL;
1941 static void
1942 scg_ant (SheetControl *sc)
1944 SheetControlGUI *scg = (SheetControlGUI *)sc;
1945 GList *l;
1947 g_return_if_fail (GNM_IS_SCG (scg));
1949 if (scg->active_panes == 0)
1950 return;
1952 /* Always have a grid 0 */
1953 if (NULL != scg->pane[0]->cursor.animated)
1954 scg_unant (sc);
1956 for (l = sc->view->ants; l; l = l->next) {
1957 GnmRange const *r = l->data;
1959 SCG_FOREACH_PANE (scg, pane, {
1960 GnmItemCursor *ic = GNM_ITEM_CURSOR (goc_item_new (
1961 pane->grid_items,
1962 gnm_item_cursor_get_type (),
1963 "SheetControlGUI", scg,
1964 "style", GNM_ITEM_CURSOR_ANTED,
1965 NULL));
1966 gnm_item_cursor_bound_set (ic, r);
1967 pane->cursor.animated =
1968 g_slist_prepend (pane->cursor.animated, ic);
1973 void
1974 scg_adjust_preferences (SheetControlGUI *scg)
1976 Sheet const *sheet = scg_sheet (scg);
1978 SCG_FOREACH_PANE (scg, pane, {
1979 if (pane->col.canvas != NULL) {
1980 gtk_widget_set_visible (GTK_WIDGET (pane->col.canvas),
1981 !sheet->hide_col_header);
1984 if (pane->row.canvas != NULL) {
1985 gtk_widget_set_visible (GTK_WIDGET (pane->row.canvas),
1986 !sheet->hide_row_header);
1990 if (scg->select_all_btn) {
1991 /* we used to test for the corner table existence, why??? */
1992 gboolean visible = !(sheet->hide_col_header || sheet->hide_row_header);
1993 gtk_widget_set_visible (scg->select_all_btn, visible);
1994 gtk_widget_set_visible (scg->row_group.button_box, visible);
1995 gtk_widget_set_visible (scg->col_group.button_box, visible);
1997 if (scg_wbc (scg) != NULL) {
1998 WorkbookView *wbv = wb_control_view (scg_wbc (scg));
1999 gtk_widget_set_visible (scg->hs,
2000 wbv->show_horizontal_scrollbar);
2002 gtk_widget_set_visible (scg->vs,
2003 wbv->show_vertical_scrollbar);
2008 /***************************************************************************/
2010 enum {
2011 CONTEXT_CUT = 1,
2012 CONTEXT_COPY,
2013 CONTEXT_PASTE,
2014 CONTEXT_PASTE_SPECIAL,
2015 CONTEXT_INSERT,
2016 CONTEXT_DELETE,
2017 CONTEXT_CLEAR_CONTENT,
2018 CONTEXT_FORMAT_CELL,
2019 CONTEXT_FORMAT_CELL_COND,
2020 CONTEXT_CELL_AUTOFIT_WIDTH,
2021 CONTEXT_CELL_AUTOFIT_HEIGHT,
2022 CONTEXT_CELL_MERGE,
2023 CONTEXT_CELL_UNMERGE,
2024 CONTEXT_COL_WIDTH,
2025 CONTEXT_COL_HIDE,
2026 CONTEXT_COL_UNHIDE,
2027 CONTEXT_COL_AUTOFIT,
2028 CONTEXT_ROW_HEIGHT,
2029 CONTEXT_ROW_HIDE,
2030 CONTEXT_ROW_UNHIDE,
2031 CONTEXT_ROW_AUTOFIT,
2032 CONTEXT_COMMENT_EDIT,
2033 CONTEXT_COMMENT_ADD,
2034 CONTEXT_COMMENT_REMOVE,
2035 CONTEXT_HYPERLINK_EDIT,
2036 CONTEXT_HYPERLINK_ADD,
2037 CONTEXT_HYPERLINK_REMOVE,
2038 CONTEXT_DATA_SLICER_REFRESH, /* refresh and redraw */
2039 CONTEXT_DATA_SLICER_EDIT /* prop dialog */
2041 static void
2042 context_menu_handler (GnmPopupMenuElement const *element,
2043 gpointer user_data)
2045 SheetControlGUI *scg = user_data;
2046 SheetControl *sc = (SheetControl *) scg;
2047 SheetView *sv = sc->view;
2048 Sheet *sheet = sv->sheet;
2049 WBCGtk *wbcg = scg->wbcg;
2050 WorkbookControl *wbc = sc->wbc;
2052 g_return_if_fail (element != NULL);
2053 g_return_if_fail (IS_SHEET (sheet));
2055 switch (element->index) {
2056 case CONTEXT_CUT:
2057 gnm_sheet_view_selection_cut (sv, wbc);
2058 break;
2059 case CONTEXT_COPY:
2060 gnm_sheet_view_selection_copy (sv, wbc);
2061 break;
2062 case CONTEXT_PASTE:
2063 cmd_paste_to_selection (wbc, sv, PASTE_DEFAULT);
2064 break;
2065 case CONTEXT_PASTE_SPECIAL:
2066 dialog_paste_special (wbcg);
2067 break;
2068 case CONTEXT_INSERT:
2069 dialog_insert_cells (wbcg);
2070 break;
2071 case CONTEXT_DELETE:
2072 dialog_delete_cells (wbcg);
2073 break;
2074 case CONTEXT_CLEAR_CONTENT:
2075 cmd_selection_clear (wbc, CLEAR_VALUES);
2076 break;
2077 case CONTEXT_FORMAT_CELL:
2078 dialog_cell_format (wbcg, FD_CURRENT, 0);
2079 break;
2080 case CONTEXT_FORMAT_CELL_COND:
2081 dialog_cell_format_cond (wbcg);
2082 break;
2083 case CONTEXT_CELL_AUTOFIT_HEIGHT:
2084 workbook_cmd_autofit_selection
2085 (wbc, wb_control_cur_sheet (wbc), FALSE);
2086 break;
2087 case CONTEXT_CELL_AUTOFIT_WIDTH:
2088 workbook_cmd_autofit_selection
2089 (wbc, wb_control_cur_sheet (wbc), TRUE);
2090 break;
2091 case CONTEXT_CELL_MERGE : {
2092 GSList *range_list = selection_get_ranges
2093 (wb_control_cur_sheet_view (wbc), FALSE);
2094 cmd_merge_cells (wbc, wb_control_cur_sheet (wbc), range_list, FALSE);
2095 range_fragment_free (range_list);
2097 break;
2098 case CONTEXT_CELL_UNMERGE : {
2099 GSList *range_list = selection_get_ranges
2100 (wb_control_cur_sheet_view (wbc), FALSE);
2101 cmd_unmerge_cells (wbc, wb_control_cur_sheet (wbc), range_list);
2102 range_fragment_free (range_list);
2105 break;
2106 case CONTEXT_COL_WIDTH:
2107 dialog_col_width (wbcg, FALSE);
2108 break;
2109 case CONTEXT_COL_AUTOFIT:
2110 workbook_cmd_resize_selected_colrow
2111 (wbc, wb_control_cur_sheet (wbc), TRUE, -1);
2112 break;
2113 case CONTEXT_COL_HIDE:
2114 cmd_selection_colrow_hide (wbc, TRUE, FALSE);
2115 break;
2116 case CONTEXT_COL_UNHIDE:
2117 cmd_selection_colrow_hide (wbc, TRUE, TRUE);
2118 break;
2119 case CONTEXT_ROW_HEIGHT:
2120 dialog_row_height (wbcg, FALSE);
2121 break;
2122 case CONTEXT_ROW_AUTOFIT:
2123 workbook_cmd_resize_selected_colrow
2124 (wbc, wb_control_cur_sheet (wbc), FALSE, -1);
2125 break;
2126 case CONTEXT_ROW_HIDE:
2127 cmd_selection_colrow_hide (wbc, FALSE, FALSE);
2128 break;
2129 case CONTEXT_ROW_UNHIDE:
2130 cmd_selection_colrow_hide (wbc, FALSE, TRUE);
2131 break;
2132 case CONTEXT_COMMENT_EDIT:
2133 case CONTEXT_COMMENT_ADD:
2134 dialog_cell_comment (wbcg, sheet, &sv->edit_pos);
2135 break;
2136 case CONTEXT_COMMENT_REMOVE:
2137 cmd_selection_clear (GNM_WBC (wbcg), CLEAR_COMMENTS);
2138 break;
2139 case CONTEXT_HYPERLINK_EDIT:
2140 case CONTEXT_HYPERLINK_ADD:
2141 dialog_hyperlink (wbcg, sc);
2142 break;
2144 case CONTEXT_HYPERLINK_REMOVE: {
2145 GnmStyle *style = gnm_style_new ();
2146 GSList *l;
2147 int n_links = 0;
2148 gchar const *format;
2149 gchar *name;
2151 for (l = scg_view (scg)->selections; l != NULL; l = l->next) {
2152 GnmRange const *r = l->data;
2153 GnmStyleList *styles;
2155 styles = sheet_style_collect_hlinks (sheet, r);
2156 n_links += g_slist_length (styles);
2157 style_list_free (styles);
2159 format = ngettext ("Remove %d Link", "Remove %d Links", n_links);
2160 name = g_strdup_printf (format, n_links);
2161 gnm_style_set_hlink (style, NULL);
2162 cmd_selection_format (wbc, style, NULL, name);
2163 g_free (name);
2164 break;
2166 case CONTEXT_DATA_SLICER_REFRESH:
2167 cmd_slicer_refresh (wbc);
2168 break;
2169 case CONTEXT_DATA_SLICER_EDIT:
2170 dialog_data_slicer (wbcg, FALSE);
2171 break;
2173 default:
2174 break;
2178 void
2179 scg_context_menu (SheetControlGUI *scg, GdkEvent *event,
2180 gboolean is_col, gboolean is_row)
2182 SheetView *sv = scg_view (scg);
2183 Sheet *sheet = sv_sheet (sv);
2185 enum {
2186 CONTEXT_DISPLAY_FOR_CELLS = 1 << 0,
2187 CONTEXT_DISPLAY_FOR_ROWS = 1 << 1,
2188 CONTEXT_DISPLAY_FOR_COLS = 1 << 2,
2189 CONTEXT_DISPLAY_WITH_HYPERLINK = 1 << 3,
2190 CONTEXT_DISPLAY_WITHOUT_HYPERLINK = 1 << 4,
2191 CONTEXT_DISPLAY_WITH_HYPERLINK_IN_RANGE = 1 << 5,
2192 CONTEXT_DISPLAY_WITH_DATA_SLICER = 1 << 6,
2193 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW = 1 << 7,
2194 CONTEXT_DISPLAY_WITH_DATA_SLICER_COL = 1 << 8,
2195 CONTEXT_DISPLAY_WITH_COMMENT = 1 << 9,
2196 CONTEXT_DISPLAY_WITHOUT_COMMENT = 1 << 10,
2197 CONTEXT_DISPLAY_WITH_COMMENT_IN_RANGE = 1 << 11
2199 enum {
2200 CONTEXT_DISABLE_PASTE_SPECIAL = 1 << 0,
2201 CONTEXT_DISABLE_FOR_ROWS = 1 << 1,
2202 CONTEXT_DISABLE_FOR_COLS = 1 << 2,
2203 CONTEXT_DISABLE_FOR_CELLS = 1 << 3,
2204 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION = 1 << 4,
2205 CONTEXT_DISABLE_FOR_ALL_COLS = 1 << 5,
2206 CONTEXT_DISABLE_FOR_ALL_ROWS = 1 << 6,
2207 CONTEXT_DISABLE_FOR_NOMERGES = 1 << 7,
2208 CONTEXT_DISABLE_FOR_ONLYMERGES = 1 << 8
2211 /* Note: keep the following two in sync!*/
2212 enum {
2213 POPUPITEM_CUT = 0,
2214 POPUPITEM_COPY,
2215 POPUPITEM_PASTE,
2216 POPUPITEM_PASTESPECIAL,
2217 POPUPITEM_SEP1,
2218 POPUPITEM_INSERT_CELL,
2219 POPUPITEM_DELETE_CELL,
2220 POPUPITEM_INSERT_COLUMN,
2221 POPUPITEM_DELETE_COLUMN,
2222 POPUPITEM_INSERT_ROW,
2223 POPUPITEM_DELETE_ROW,
2224 POPUPITEM_CLEAR_CONTENTS,
2225 POPUPITEM_SEP2,
2226 POPUPITEM_COMMENT_ADD,
2227 POPUPITEM_COMMENT_EDIT,
2228 POPUPITEM_COMMENT_REMOVE,
2229 POPUPITEM_LINK_ADD,
2230 POPUPITEM_LINK_EDIT,
2231 POPUPITEM_LINK_REMOVE,
2232 POPUPITEM_SEP3,
2233 POPUPITEM_DATASLICER_EDIT,
2234 POPUPITEM_DATASLICER_REFRESH,
2235 POPUPITEM_DATASLICER_FIELD_ORDER,
2236 POPUPITEM_DATASLICER_LEFT,
2237 POPUPITEM_DATASLICER_RIGHT,
2238 POPUPITEM_DATASLICER_UP,
2239 POPUPITEM_DATASLICER_DOWN,
2240 POPUPITEM_DATASLICER_SUBMENU,
2241 POPUPITEM_FORMAT
2244 static GnmPopupMenuElement popup_elements[] = {
2245 { N_("Cu_t"), "edit-cut",
2246 0, 0, CONTEXT_CUT, NULL },
2247 { N_("_Copy"), "edit-copy",
2248 0, 0, CONTEXT_COPY, NULL },
2249 { N_("_Paste"), "edit-paste",
2250 0, 0, CONTEXT_PASTE, NULL },
2251 { N_("Paste _Special"), "edit-paste",
2252 0, CONTEXT_DISABLE_PASTE_SPECIAL, CONTEXT_PASTE_SPECIAL, NULL },
2254 { "", NULL, 0, 0, 0, NULL },
2256 { N_("_Insert Cells..."), NULL,
2257 CONTEXT_DISPLAY_FOR_CELLS,
2258 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION, CONTEXT_INSERT, NULL },
2259 { N_("_Delete Cells..."), "edit-delete",
2260 CONTEXT_DISPLAY_FOR_CELLS,
2261 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION, CONTEXT_DELETE, NULL },
2262 { N_("_Insert Column(s)"), "gnumeric-column-add",
2263 CONTEXT_DISPLAY_FOR_COLS,
2264 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION,
2265 CONTEXT_INSERT, NULL },
2266 { N_("_Delete Column(s)"), "gnumeric-column-delete",
2267 CONTEXT_DISPLAY_FOR_COLS,
2268 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION,
2269 CONTEXT_DELETE, NULL },
2270 { N_("_Insert Row(s)"), "gnumeric-row-add",
2271 CONTEXT_DISPLAY_FOR_ROWS,
2272 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION,
2273 CONTEXT_INSERT, NULL },
2274 { N_("_Delete Row(s)"), "gnumeric-row-delete",
2275 CONTEXT_DISPLAY_FOR_ROWS,
2276 CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION,
2277 CONTEXT_DELETE, NULL },
2279 { N_("Clear Co_ntents"), "edit-clear",
2280 0, 0, CONTEXT_CLEAR_CONTENT, NULL },
2282 { "", NULL, CONTEXT_DISPLAY_FOR_CELLS, 0, 0, NULL },
2284 { N_("Add _Comment..."), "gnumeric-comment-add",
2285 CONTEXT_DISPLAY_WITHOUT_COMMENT, 0, CONTEXT_COMMENT_ADD, NULL },
2286 { N_("Edit Co_mment..."),"gnumeric-comment-edit",
2287 CONTEXT_DISPLAY_WITH_COMMENT, 0, CONTEXT_COMMENT_EDIT, NULL },
2288 { N_("_Remove Comments"), "gnumeric-comment-delete",
2289 CONTEXT_DISPLAY_WITH_COMMENT_IN_RANGE, 0, CONTEXT_COMMENT_REMOVE, NULL },
2291 { N_("Add _Hyperlink..."), "gnumeric-link-add",
2292 CONTEXT_DISPLAY_WITHOUT_HYPERLINK, 0,
2293 CONTEXT_HYPERLINK_ADD, NULL },
2294 { N_("Edit _Hyperlink..."), "gnumeric-link-edit",
2295 CONTEXT_DISPLAY_WITH_HYPERLINK, 0,
2296 CONTEXT_HYPERLINK_EDIT, NULL },
2297 { N_("_Remove Hyperlink"), "gnumeric-link-delete",
2298 CONTEXT_DISPLAY_WITH_HYPERLINK_IN_RANGE, 0,
2299 CONTEXT_HYPERLINK_REMOVE, NULL },
2301 { "", NULL, 0, 0, 0, NULL },
2303 { N_("_Edit DataSlicer"), NULL,
2304 CONTEXT_DISPLAY_WITH_DATA_SLICER, 0,
2305 CONTEXT_DATA_SLICER_EDIT, NULL },
2306 { N_("_Refresh DataSlicer"), NULL,
2307 CONTEXT_DISPLAY_WITH_DATA_SLICER, 0,
2308 CONTEXT_DATA_SLICER_REFRESH, NULL },
2310 { N_("DataSlicer Field _Order "), NULL,
2311 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW | CONTEXT_DISPLAY_WITH_DATA_SLICER_COL, 0,
2312 -1, NULL }, /* start sub menu */
2313 { N_("Left"), "go-previous",
2314 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW, 0,
2315 CONTEXT_DATA_SLICER_REFRESH, NULL },
2316 { N_("Right"), "go-next",
2317 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW, 0,
2318 CONTEXT_DATA_SLICER_REFRESH, NULL },
2319 { N_("Up"), "go-up",
2320 CONTEXT_DISPLAY_WITH_DATA_SLICER_COL, 0,
2321 CONTEXT_DATA_SLICER_REFRESH, NULL },
2322 { N_("Down"), "go-down",
2323 CONTEXT_DISPLAY_WITH_DATA_SLICER_COL, 0,
2324 CONTEXT_DATA_SLICER_REFRESH, NULL },
2325 { "", NULL,
2326 CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW | CONTEXT_DISPLAY_WITH_DATA_SLICER_COL, 0,
2327 -1, NULL }, /* end sub menu */
2329 { N_("_Format All Cells..."), GTK_STOCK_PROPERTIES,
2330 0, 0, CONTEXT_FORMAT_CELL, NULL },
2331 { N_("C_onditional Formatting..."), GTK_STOCK_PROPERTIES,
2332 0, 0, CONTEXT_FORMAT_CELL_COND, NULL },
2333 { N_("Cell"), NULL, 0, 0, -1, NULL},/* start sub menu */
2334 { N_("_Merge"), "gnumeric-cells-merge", 0,
2335 CONTEXT_DISABLE_FOR_ONLYMERGES, CONTEXT_CELL_MERGE, NULL },
2336 { N_("_Unmerge"), "gnumeric-cells-split", 0,
2337 CONTEXT_DISABLE_FOR_NOMERGES, CONTEXT_CELL_UNMERGE, NULL },
2338 { N_("Auto Fit _Width"), "gnumeric-column-size", 0, 0, CONTEXT_CELL_AUTOFIT_WIDTH, NULL },
2339 { N_("Auto Fit _Height"), "gnumeric-row-size", 0, 0, CONTEXT_CELL_AUTOFIT_HEIGHT, NULL },
2340 { "", NULL, 0, 0, -1, NULL},/* end sub menu */
2343 /* Column specific (Note some labels duplicate row labels) */
2344 { N_("Column"), NULL, 0, 0, -1, NULL},/* start sub menu */
2345 { N_("_Width..."), "gnumeric-column-size", 0, 0, CONTEXT_COL_WIDTH, NULL },
2346 { N_("_Auto Fit Width"), "gnumeric-column-size", 0, 0, CONTEXT_COL_AUTOFIT, NULL },
2347 { N_("_Hide"), "gnumeric-column-hide", 0, CONTEXT_DISABLE_FOR_ALL_COLS, CONTEXT_COL_HIDE, NULL },
2348 { N_("_Unhide"), "gnumeric-column-unhide", 0, 0, CONTEXT_COL_UNHIDE, NULL },
2349 { "", NULL, 0, 0, -1, NULL},/* end sub menu */
2351 /* Row specific (Note some labels duplicate col labels) */
2352 { N_("Row"), NULL, 0, 0, -1, NULL},/* start sub menu */
2353 { N_("Hei_ght..."), "gnumeric-row-size", 0, 0, CONTEXT_ROW_HEIGHT, NULL },
2354 { N_("_Auto Fit Height"), "gnumeric-row-size", 0, 0, CONTEXT_ROW_AUTOFIT, NULL },
2355 { N_("_Hide"), "gnumeric-row-hide", 0, CONTEXT_DISABLE_FOR_ALL_ROWS, CONTEXT_ROW_HIDE, NULL },
2356 { N_("_Unhide"), "gnumeric-row-unhide", 0, 0, CONTEXT_ROW_UNHIDE, NULL },
2357 { "", NULL, 0, 0, -1, NULL},/* end sub menu */
2359 { NULL, NULL, 0, 0, 0, NULL },
2362 /* row and column specific operations */
2363 int display_filter =
2364 ((!is_col && !is_row) ? CONTEXT_DISPLAY_FOR_CELLS : 0) |
2365 (is_col ? CONTEXT_DISPLAY_FOR_COLS : 0) |
2366 (is_row ? CONTEXT_DISPLAY_FOR_ROWS : 0);
2368 /* Paste special only applies to local copies, not cuts, or remote
2369 * items
2371 int sensitivity_filter =
2372 (!gnm_app_clipboard_is_empty () &&
2373 !gnm_app_clipboard_is_cut ())
2374 ? 0 : CONTEXT_DISABLE_PASTE_SPECIAL;
2376 GSList *l;
2377 gboolean has_link = FALSE, has_comment = FALSE;
2378 int n_comments = 0, n_links = 0, n_cols = 0, n_rows = 0, n_cells = 0;
2379 GnmSheetSlicer *slicer;
2380 GnmRange rge;
2381 int n_sel = 0;
2382 gboolean full_sheet = FALSE, only_merges = TRUE, no_merges = TRUE;
2384 wbcg_edit_finish (scg->wbcg, WBC_EDIT_REJECT, NULL);
2386 /* Now see if there is some selection which selects a whole row or a
2387 * whole column and disable the insert/delete col/row menu items
2388 * accordingly
2390 for (l = scg_view (scg)->selections; l != NULL; l = l->next) {
2391 GnmRange const *r = l->data;
2392 GnmRange const *merge;
2393 GSList *objs, *merges;
2394 GnmStyleList *styles;
2395 int h, w;
2396 gboolean rfull_h = range_is_full (r, sheet, TRUE);
2397 gboolean rfull_v = range_is_full (r, sheet, FALSE);
2399 n_sel++;
2401 if (!range_is_singleton (r)) {
2402 merge = gnm_sheet_merge_is_corner (sheet, &(r->start));
2403 if (NULL == merge || !range_equal (merge, r))
2404 only_merges = FALSE;
2405 merges = gnm_sheet_merge_get_overlap (sheet, r);
2406 if (merges != NULL) {
2407 no_merges = FALSE;
2408 g_slist_free (merges);
2412 if (rfull_v) {
2413 display_filter |= CONTEXT_DISPLAY_FOR_COLS;
2414 display_filter &= ~CONTEXT_DISPLAY_FOR_CELLS;
2415 sensitivity_filter |= CONTEXT_DISABLE_FOR_ALL_ROWS;
2416 } else
2417 sensitivity_filter |= CONTEXT_DISABLE_FOR_ROWS;
2420 if (rfull_h) {
2421 display_filter |= CONTEXT_DISPLAY_FOR_ROWS;
2422 display_filter &= ~CONTEXT_DISPLAY_FOR_CELLS;
2423 sensitivity_filter |= CONTEXT_DISABLE_FOR_ALL_COLS;
2424 } else
2425 sensitivity_filter |= CONTEXT_DISABLE_FOR_COLS;
2427 if (!(rfull_h || rfull_v))
2428 sensitivity_filter |= CONTEXT_DISABLE_FOR_CELLS;
2430 full_sheet = full_sheet || (rfull_h && rfull_v);
2432 h = range_height (r);
2433 w = range_width (r);
2434 n_cols += w;
2435 n_rows += h;
2436 n_cells += w * h;
2438 styles = sheet_style_collect_hlinks (sheet, r);
2439 n_links += g_slist_length (styles);
2440 style_list_free (styles);
2442 objs = sheet_objects_get (sheet, r, GNM_CELL_COMMENT_TYPE);
2443 n_comments += g_slist_length (objs);
2444 g_slist_free (objs);
2447 if (only_merges)
2448 sensitivity_filter |= CONTEXT_DISABLE_FOR_ONLYMERGES;
2449 if (no_merges)
2450 sensitivity_filter |= CONTEXT_DISABLE_FOR_NOMERGES;
2453 if ((display_filter & CONTEXT_DISPLAY_FOR_COLS) &&
2454 (display_filter & CONTEXT_DISPLAY_FOR_ROWS))
2455 display_filter = 0;
2456 if (n_sel > 1)
2457 sensitivity_filter |= CONTEXT_DISABLE_FOR_DISCONTIGUOUS_SELECTION;
2459 has_comment = (sheet_get_comment (sheet, &sv->edit_pos) != NULL);
2460 range_init_cellpos (&rge, &sv->edit_pos);
2461 has_link = (NULL != sheet_style_region_contains_link (sheet, &rge));
2463 slicer = gnm_sheet_view_editpos_in_slicer (scg_view (scg));
2464 /* FIXME: disabled for now */
2465 if (0 && slicer) {
2466 GODataSlicerField *dsf = gnm_sheet_slicer_field_header_at_pos (slicer, &sv->edit_pos);
2467 if (NULL != dsf) {
2468 if (go_data_slicer_field_get_field_type_pos (dsf, GDS_FIELD_TYPE_COL) >= 0)
2469 display_filter |= CONTEXT_DISPLAY_WITH_DATA_SLICER_COL;
2470 if (go_data_slicer_field_get_field_type_pos (dsf, GDS_FIELD_TYPE_ROW) >= 0)
2471 display_filter |= CONTEXT_DISPLAY_WITH_DATA_SLICER_ROW;
2473 display_filter |= CONTEXT_DISPLAY_WITH_DATA_SLICER;
2474 display_filter &= ~CONTEXT_DISPLAY_FOR_CELLS;
2477 if (display_filter & CONTEXT_DISPLAY_FOR_CELLS) {
2478 char const *format;
2479 display_filter |= ((has_link) ?
2480 CONTEXT_DISPLAY_WITH_HYPERLINK : CONTEXT_DISPLAY_WITHOUT_HYPERLINK);
2481 display_filter |= ((n_links > 0) ?
2482 CONTEXT_DISPLAY_WITH_HYPERLINK_IN_RANGE : CONTEXT_DISPLAY_WITHOUT_HYPERLINK);
2483 display_filter |= ((has_comment) ?
2484 CONTEXT_DISPLAY_WITH_COMMENT : CONTEXT_DISPLAY_WITHOUT_COMMENT);
2485 display_filter |= ((n_comments > 0) ?
2486 CONTEXT_DISPLAY_WITH_COMMENT_IN_RANGE : CONTEXT_DISPLAY_WITHOUT_COMMENT);
2487 if (n_links > 0) {
2488 /* xgettext : %d gives the number of links. This is input to ngettext. */
2489 format = ngettext ("_Remove %d Link", "_Remove %d Links", n_links);
2490 popup_elements[POPUPITEM_LINK_REMOVE].allocated_name = g_strdup_printf (format, n_links);
2492 if (n_comments > 0) {
2493 /* xgettext : %d gives the number of comments. This is input to ngettext. */
2494 format = ngettext ("_Remove %d Comment", "_Remove %d Comments", n_comments);
2495 popup_elements[POPUPITEM_COMMENT_REMOVE].allocated_name = g_strdup_printf (format, n_comments);
2497 format = ngettext ("_Insert %d Cell...", "_Insert %d Cells...", n_cells);
2498 popup_elements[POPUPITEM_INSERT_CELL].allocated_name = g_strdup_printf (format, n_cells);
2499 format = ngettext ("_Delete %d Cell...", "_Delete %d Cells...", n_cells);
2500 popup_elements[POPUPITEM_DELETE_CELL].allocated_name = g_strdup_printf (format, n_cells);
2503 if (display_filter & CONTEXT_DISPLAY_FOR_COLS) {
2504 char const *format;
2505 format = ngettext ("_Insert %d Column", "_Insert %d Columns", n_cols);
2506 popup_elements[POPUPITEM_INSERT_COLUMN].allocated_name = g_strdup_printf (format, n_cols);
2507 format = ngettext ("_Delete %d Column", "_Delete %d Columns", n_cols);
2508 popup_elements[POPUPITEM_DELETE_COLUMN].allocated_name = g_strdup_printf (format, n_cols);
2509 if (!(sensitivity_filter & (CONTEXT_DISABLE_FOR_CELLS | CONTEXT_DISABLE_FOR_ROWS))) {
2510 format = ngettext ("_Format %d Column", "_Format %d Columns", n_cols);
2511 popup_elements[POPUPITEM_FORMAT].allocated_name
2512 = g_strdup_printf (format, n_cols);
2515 if (display_filter & CONTEXT_DISPLAY_FOR_ROWS) {
2516 char const *format;
2517 format = ngettext ("_Insert %d Row", "_Insert %d Rows", n_rows);
2518 popup_elements[POPUPITEM_INSERT_ROW].allocated_name = g_strdup_printf (format, n_rows);
2519 format = ngettext ("_Delete %d Row", "_Delete %d Rows", n_rows);
2520 popup_elements[POPUPITEM_DELETE_ROW].allocated_name = g_strdup_printf (format, n_rows);
2522 if (!(sensitivity_filter & (CONTEXT_DISABLE_FOR_CELLS | CONTEXT_DISABLE_FOR_COLS))) {
2523 format = ngettext ("_Format %d Row", "_Format %d Rows", n_rows);
2524 popup_elements[POPUPITEM_FORMAT].allocated_name
2525 = g_strdup_printf (format, n_rows);
2528 if (!popup_elements[POPUPITEM_FORMAT].allocated_name && !full_sheet) {
2529 char const *format;
2530 format = ngettext ("_Format %d Cell...", "_Format %d Cells", n_cells);
2531 popup_elements[POPUPITEM_FORMAT].allocated_name = g_strdup_printf (format, n_cells);
2535 gnm_create_popup_menu (popup_elements,
2536 &context_menu_handler, scg, NULL,
2537 display_filter, sensitivity_filter, event);
2540 static gboolean
2541 cb_redraw_sel (G_GNUC_UNUSED SheetView *sv, GnmRange const *r, gpointer user_data)
2543 SheetControl *sc = user_data;
2544 scg_redraw_range (sc, r);
2545 scg_redraw_headers (sc, TRUE, TRUE, r);
2546 return TRUE;
2549 void
2550 scg_cursor_visible (SheetControlGUI *scg, gboolean is_visible)
2552 SheetControl *sc = (SheetControl *) scg;
2554 /* there is always a grid 0 */
2555 if (NULL == scg->pane[0])
2556 return;
2558 SCG_FOREACH_PANE (scg, pane,
2559 gnm_item_cursor_set_visibility (pane->cursor.std, is_visible););
2561 sv_selection_foreach (sc->view, cb_redraw_sel, sc);
2564 /***************************************************************************/
2567 * scg_mode_edit:
2568 * @scg: The sheet control
2570 * Put @sheet into the standard state 'edit mode'. This shuts down
2571 * any object editing and frees any objects that are created but not
2572 * realized.
2574 void
2575 scg_mode_edit (SheetControlGUI *scg)
2577 WBCGtk *wbcg;
2578 g_return_if_fail (GNM_IS_SCG (scg));
2580 wbcg = scg->wbcg;
2582 if (wbcg != NULL) /* Can be NULL during destruction */
2583 wbcg_insert_object_clear (wbcg);
2585 scg_object_unselect (scg, NULL);
2587 /* During destruction we have already been disconnected
2588 * so don't bother changing the cursor */
2589 if (scg->grid != NULL &&
2590 scg_sheet (scg) != NULL &&
2591 scg_view (scg) != NULL) {
2592 scg_set_display_cursor (scg);
2593 scg_cursor_visible (scg, TRUE);
2596 if (wbcg != NULL && wbc_gtk_get_guru (wbcg) != NULL &&
2597 scg == wbcg_cur_scg (wbcg))
2598 wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
2600 if (wbcg)
2601 wb_control_update_action_sensitivity (GNM_WBC (wbcg));
2604 static void
2605 scg_mode_edit_virt (SheetControl *sc)
2607 scg_mode_edit ((SheetControlGUI *)sc);
2610 static int
2611 calc_obj_place (GnmPane *pane, gint64 canvas_coord, gboolean is_col,
2612 double *offset)
2614 gint64 origin;
2615 int colrow;
2616 ColRowInfo const *cri;
2617 Sheet const *sheet = scg_sheet (pane->simple.scg);
2619 if (is_col) {
2620 colrow = gnm_pane_find_col (pane, canvas_coord, &origin);
2621 cri = sheet_col_get_info (sheet, colrow);
2622 } else {
2623 colrow = gnm_pane_find_row (pane, canvas_coord, &origin);
2624 cri = sheet_row_get_info (sheet, colrow);
2627 /* TODO : handle other anchor types */
2628 *offset = (canvas_coord - origin) / (double)cri->size_pixels;
2629 return colrow;
2632 #define SO_CLASS(so) GNM_SO_CLASS (G_OBJECT_GET_CLASS(so))
2635 * scg_object_select:
2636 * @scg: The #SheetControl to edit in.
2637 * @so: The #SheetObject to select.
2639 * Adds @so to the set of selected objects and prepares it for user editing.
2640 * Adds a reference to @ref if it is selected.
2642 void
2643 scg_object_select (SheetControlGUI *scg, SheetObject *so)
2645 double *coords;
2647 if (scg->selected_objects == NULL) {
2648 if (wb_view_is_protected (sv_wbv (scg_view (scg)), TRUE) ||
2649 !wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
2650 return;
2651 g_object_ref (so);
2653 wbcg_insert_object_clear (scg->wbcg);
2654 scg_cursor_visible (scg, FALSE);
2655 scg_set_display_cursor (scg);
2656 scg_unant (GNM_SHEET_CONTROL (scg));
2658 scg->selected_objects = g_hash_table_new_full (
2659 g_direct_hash, g_direct_equal,
2660 (GDestroyNotify) g_object_unref, (GDestroyNotify) g_free);
2661 wb_control_update_action_sensitivity (scg_wbc (scg));
2662 } else {
2663 g_return_if_fail (g_hash_table_lookup (scg->selected_objects, so) == NULL);
2664 g_object_ref (so);
2667 coords = g_new (double, 4);
2668 scg_object_anchor_to_coords (scg, sheet_object_get_anchor (so), coords);
2669 g_hash_table_insert (scg->selected_objects, so, coords);
2670 g_signal_connect_object (so, "unrealized",
2671 G_CALLBACK (scg_mode_edit), scg, G_CONNECT_SWAPPED);
2673 SCG_FOREACH_PANE (scg, pane,
2674 gnm_pane_object_update_bbox (pane, so););
2677 static void
2678 cb_scg_object_unselect (SheetObject *so, G_GNUC_UNUSED double *coords, SheetControlGUI *scg)
2680 SCG_FOREACH_PANE (scg, pane, gnm_pane_object_unselect (pane, so););
2681 g_signal_handlers_disconnect_by_func (so,
2682 scg_mode_edit, scg);
2686 * scg_object_unselect:
2687 * @scg: #SheetControlGUI
2688 * @so: #SheetObject (optionally NULL)
2690 * unselect the supplied object, and drop out of edit mode if this is the last
2691 * one. If @so == NULL unselect _all_ objects.
2693 void
2694 scg_object_unselect (SheetControlGUI *scg, SheetObject *so)
2696 WorkbookControl *wbc = scg_wbc (scg);
2698 /* cheesy cycle avoidance */
2699 if (scg->selected_objects == NULL)
2700 return;
2702 if (so != NULL) {
2703 double *pts = g_hash_table_lookup (scg->selected_objects, so);
2704 g_return_if_fail (pts != NULL);
2705 cb_scg_object_unselect (so, pts, scg);
2706 g_hash_table_remove (scg->selected_objects, so);
2707 if (g_hash_table_size (scg->selected_objects) > 0)
2708 return;
2709 } else
2710 g_hash_table_foreach (scg->selected_objects,
2711 (GHFunc) cb_scg_object_unselect, scg);
2713 g_hash_table_destroy (scg->selected_objects);
2714 scg->selected_objects = NULL;
2715 scg_mode_edit (scg);
2716 if (wbc)
2717 wb_control_update_action_sensitivity (wbc);
2720 void
2721 scg_object_select_next (SheetControlGUI *scg, gboolean reverse)
2723 Sheet *sheet = scg_sheet (scg);
2724 GSList *ptr = sheet->sheet_objects;
2726 g_return_if_fail (ptr != NULL);
2728 if ((scg->selected_objects == NULL) ||
2729 (g_hash_table_size (scg->selected_objects) == 0)) {
2730 scg_object_select (scg, ptr->data);
2731 return;
2732 } else {
2733 GSList *prev = NULL;
2734 for (; ptr != NULL ; prev = ptr, ptr = ptr->next)
2735 if (NULL != g_hash_table_lookup
2736 (scg->selected_objects, ptr->data)) {
2737 SheetObject *target;
2738 if (reverse) {
2739 if (ptr->next == NULL)
2740 target = sheet->sheet_objects->data;
2741 else
2742 target = ptr->next->data;
2743 } else {
2744 if (NULL == prev) {
2745 GSList *last = g_slist_last (ptr);
2746 target = last->data;
2747 } else
2748 target = prev->data;
2750 if (ptr->data != target) {
2751 scg_object_unselect (scg, NULL);
2752 scg_object_select (scg, target);
2753 return;
2759 typedef struct {
2760 SheetControlGUI *scg;
2761 GnmPane *pane;
2762 SheetObject *primary_object;
2763 int drag_type;
2764 double dx, dy;
2765 gboolean symmetric;
2766 gboolean snap_to_grid;
2767 gboolean is_mouse_move;
2768 } ObjDragInfo;
2770 static double
2771 snap_pos_to_grid (ObjDragInfo const *info, gboolean is_col, double pos,
2772 gboolean to_min)
2774 GnmPane const *pane = info->pane;
2775 Sheet const *sheet = scg_sheet (info->scg);
2776 int cell = is_col ? pane->first.col : pane->first.row;
2777 gint64 pixel = is_col ? pane->first_offset.x : pane->first_offset.y;
2778 gboolean snap = FALSE;
2779 int length = 0;
2780 ColRowInfo const *cr_info;
2781 int sheet_max = colrow_max (is_col, sheet);
2783 if (pos < pixel) {
2784 while (cell > 0 && pos < pixel) {
2785 cr_info = sheet_colrow_get_info (sheet, --cell, is_col);
2786 if (cr_info->visible) {
2787 length = cr_info->size_pixels;
2788 pixel -= length;
2791 if (pos < pixel)
2792 pos = pixel;
2793 } else {
2794 do {
2795 cr_info = sheet_colrow_get_info (sheet, cell, is_col);
2796 if (cr_info->visible) {
2797 length = cr_info->size_pixels;
2798 if (pixel <= pos && pos <= pixel + length)
2799 snap = TRUE;
2800 pixel += length;
2802 } while (++cell < sheet_max && !snap);
2803 pixel -= length;
2804 if (snap) {
2805 if (info->is_mouse_move)
2806 pos = (fabs (pos - pixel) < fabs (pos - pixel - length)) ? pixel : pixel + length;
2807 else
2808 pos = (pixel == pos) ? pixel : (to_min ? pixel : pixel + length);
2811 return/* sign */ pos;
2814 static void
2815 apply_move (SheetObject *so, int x_idx, int y_idx, double *coords,
2816 ObjDragInfo *info, gboolean snap_to_grid)
2818 gboolean move_x = (x_idx >= 0);
2819 gboolean move_y = (y_idx >= 0);
2820 double x, y;
2822 x = move_x ? coords[x_idx] + info->dx : 0;
2823 y = move_y ? coords[y_idx] + info->dy : 0;
2825 if (snap_to_grid) {
2826 g_return_if_fail (info->pane != NULL);
2828 if (move_x)
2829 x = snap_pos_to_grid (info, TRUE, x, info->dx < 0.);
2830 if (move_y)
2831 y = snap_pos_to_grid (info, FALSE, y, info->dy < 0.);
2832 if (info->primary_object == so || NULL == info->primary_object) {
2833 if (move_x) info->dx = x - coords[x_idx];
2834 if (move_y) info->dy = y - coords[y_idx];
2838 if (move_x) coords[x_idx] = x;
2839 if (move_y) coords[y_idx] = y;
2841 if (info->symmetric && !snap_to_grid) {
2842 if (move_x) coords[x_idx == 0 ? 2 : 0] -= info->dx;
2843 if (move_y) coords[y_idx == 1 ? 3 : 1] -= info->dy;
2847 static void
2848 drag_object (SheetObject *so, double *coords, ObjDragInfo *info)
2850 static struct {
2851 int x_idx, y_idx;
2852 } const idx_info[8] = {
2853 { 0, 1}, {-1, 1}, { 2, 1}, { 0,-1},
2854 { 2,-1}, { 0, 3}, {-1, 3}, { 2, 3}
2857 g_return_if_fail (info->drag_type <= 8);
2859 if (info->drag_type == 8) {
2860 apply_move (so, 0, 1, coords, info, info->snap_to_grid);
2861 apply_move (so, 2, 3, coords, info, FALSE);
2862 } else
2863 apply_move (so,
2864 idx_info[info->drag_type].x_idx,
2865 idx_info[info->drag_type].y_idx,
2866 coords, info, info->snap_to_grid);
2867 SCG_FOREACH_PANE (info->scg, pane,
2868 gnm_pane_object_update_bbox (pane, so););
2871 static void
2872 cb_drag_selected_objects (SheetObject *so, double *coords, ObjDragInfo *info)
2874 if (so != info->primary_object)
2875 drag_object (so, coords, info);
2879 * scg_objects_drag:
2880 * @scg: #SheetControlGUI
2881 * @primary: #SheetObject (optionally NULL)
2882 * @dx:
2883 * @dy:
2884 * @drag_type:
2885 * @symmetric:
2887 * Move the control points and drag views of the currently selected objects to
2888 * a new position. This movement is only made in @scg not in the actual
2889 * objects.
2891 void
2892 scg_objects_drag (SheetControlGUI *scg, GnmPane *pane,
2893 SheetObject *primary,
2894 gdouble *dx, gdouble *dy,
2895 int drag_type, gboolean symmetric,
2896 gboolean snap_to_grid,
2897 gboolean is_mouse_move)
2899 double *coords;
2901 ObjDragInfo info;
2902 info.scg = scg;
2903 info.pane = pane;
2904 info.primary_object = primary;
2905 info.dx = *dx;
2906 info.dy = *dy;
2907 info.symmetric = symmetric;
2908 info.drag_type = drag_type;
2909 info.snap_to_grid = snap_to_grid;
2910 info.is_mouse_move = is_mouse_move;
2912 if (primary != NULL) {
2913 coords = g_hash_table_lookup (scg->selected_objects, primary);
2914 drag_object (primary, coords, &info);
2917 g_hash_table_foreach (scg->selected_objects,
2918 (GHFunc) cb_drag_selected_objects, &info);
2920 *dx = info.dx;
2921 *dy = info.dy;
2924 typedef struct {
2925 SheetControlGUI *scg;
2926 GSList *objects, *anchors;
2927 } CollectObjectsData;
2928 static void
2929 cb_collect_objects_to_commit (SheetObject *so, double *coords, CollectObjectsData *data)
2931 SheetObjectAnchor *anchor = sheet_object_anchor_dup (
2932 sheet_object_get_anchor (so));
2933 if (!sheet_object_can_resize (so)) {
2934 /* FIXME: that code should be invalid */
2935 double scale = goc_canvas_get_pixels_per_unit (GOC_CANVAS (data->scg->pane[0])) / 72.;
2936 sheet_object_default_size (so, coords + 2, coords + 3);
2937 coords[2] *= gnm_app_display_dpi_get (TRUE) * scale;
2938 coords[3] *= gnm_app_display_dpi_get (FALSE) * scale;
2939 coords[2] += coords[0];
2940 coords[3] += coords[1];
2942 scg_object_coords_to_anchor (data->scg, coords, anchor);
2943 data->objects = g_slist_prepend (data->objects, so);
2944 data->anchors = g_slist_prepend (data->anchors, anchor);
2946 if (!sheet_object_rubber_band_directly (so)) {
2947 SCG_FOREACH_PANE (data->scg, pane, {
2948 GocItem **ctrl_pts = g_hash_table_lookup (pane->drag.ctrl_pts, so);
2949 if (NULL != ctrl_pts[9]) {
2950 double const *pts = g_hash_table_lookup (
2951 pane->simple.scg->selected_objects, so);
2952 SheetObjectView *sov = sheet_object_get_view (so,
2953 (SheetObjectViewContainer *)pane);
2955 g_object_unref (ctrl_pts[9]);
2956 ctrl_pts[9] = NULL;
2958 if (NULL == sov)
2959 sov = sheet_object_new_view (so, (SheetObjectViewContainer *) pane);
2960 if (NULL != sov)
2961 sheet_object_view_set_bounds (sov, pts, TRUE);
2967 static char *
2968 scg_objects_drag_commit_get_undo_text (int drag_type, int n,
2969 gboolean created_objects)
2971 char const *format;
2973 if (created_objects) {
2974 if (drag_type == 8)
2975 /* xgettext : %d gives the number of objects. This is input to ngettext. */
2976 format = ngettext ("Duplicate %d Object", "Duplicate %d Objects", n);
2977 else
2978 /* xgettext : %d gives the number of objects. This is input to ngettext. */
2979 format = ngettext ("Insert %d Object", "Insert %d Objects", n);
2980 } else {
2981 if (drag_type == 8)
2982 /* xgettext : %d gives the number of objects. This is input to ngettext. */
2983 format = ngettext ("Move %d Object", "Move %d Objects", n);
2984 else
2985 /* xgettext : %d gives the number of objects. This is input to ngettext. */
2986 format = ngettext ("Resize %d Object", "Resize %d Objects", n);
2989 return g_strdup_printf (format, n);
2993 void
2994 scg_objects_drag_commit (SheetControlGUI *scg, int drag_type,
2995 gboolean created_objects,
2996 GOUndo **pundo, GOUndo **predo, gchar **undo_title)
2998 CollectObjectsData data;
2999 char *text = NULL;
3000 GOUndo *undo = NULL;
3001 GOUndo *redo = NULL;
3003 data.objects = data.anchors = NULL;
3004 data.scg = scg;
3005 g_hash_table_foreach (scg->selected_objects,
3006 (GHFunc) cb_collect_objects_to_commit, &data);
3008 undo = sheet_object_move_undo (data.objects, created_objects);
3009 redo = sheet_object_move_do (data.objects, data.anchors, created_objects);
3010 text = scg_objects_drag_commit_get_undo_text
3011 (drag_type, g_slist_length (data.objects), created_objects);
3013 if (pundo && predo) {
3014 *pundo = undo;
3015 *predo = redo;
3016 if (undo_title)
3017 *undo_title = text;
3018 } else {
3019 cmd_generic (GNM_WBC (scg_wbcg (scg)),
3020 text, undo, redo);
3021 g_free (text);
3023 g_slist_free (data.objects);
3024 g_slist_free_full (data.anchors, g_free);
3027 void
3028 scg_objects_nudge (SheetControlGUI *scg, GnmPane *pane,
3029 int drag_type, double dx, double dy, gboolean symmetric, gboolean snap_to_grid)
3031 /* no nudging if we are creating an object */
3032 if (!scg->wbcg->new_object) {
3033 scg_objects_drag (scg, pane, NULL, &dx, &dy, drag_type, symmetric, snap_to_grid, FALSE);
3034 scg_objects_drag_commit (scg, drag_type, FALSE, NULL, NULL, NULL);
3038 void
3039 scg_object_coords_to_anchor (SheetControlGUI const *scg,
3040 double const *coords, SheetObjectAnchor *in_out)
3042 Sheet *sheet = scg_sheet (scg);
3043 /* pane 0 always exists and the others are always use the same basis */
3044 GnmPane *pane = scg_pane ((SheetControlGUI *)scg, 0);
3045 double tmp[4];
3046 g_return_if_fail (GNM_IS_SCG (scg));
3047 g_return_if_fail (coords != NULL);
3049 in_out->base.direction = GOD_ANCHOR_DIR_NONE_MASK;
3050 if (coords[0] > coords[2]) {
3051 tmp[0] = coords[2];
3052 tmp[2] = coords[0];
3053 } else {
3054 tmp[0] = coords[0];
3055 tmp[2] = coords[2];
3056 in_out->base.direction = GOD_ANCHOR_DIR_RIGHT;
3058 if (coords[1] > coords[3]) {
3059 tmp[1] = coords[3];
3060 tmp[3] = coords[1];
3061 } else {
3062 tmp[1] = coords[1];
3063 tmp[3] = coords[3];
3064 in_out->base.direction |= GOD_ANCHOR_DIR_DOWN;
3067 switch (in_out->mode) {
3068 case GNM_SO_ANCHOR_TWO_CELLS:
3069 in_out->cell_bound.start.col = calc_obj_place (pane, tmp[0], TRUE,
3070 in_out->offset + 0);
3071 in_out->cell_bound.start.row = calc_obj_place (pane, tmp[1], FALSE,
3072 in_out->offset + 1);
3073 in_out->cell_bound.end.col = calc_obj_place (pane, tmp[2], TRUE,
3074 in_out->offset + 2);
3075 in_out->cell_bound.end.row = calc_obj_place (pane, tmp[3], FALSE,
3076 in_out->offset + 3);
3077 break;
3078 case GNM_SO_ANCHOR_ONE_CELL:
3079 in_out->cell_bound.start.col = calc_obj_place (pane, tmp[0], TRUE,
3080 in_out->offset + 0);
3081 in_out->cell_bound.start.row = calc_obj_place (pane, tmp[1], FALSE,
3082 in_out->offset + 1);
3083 in_out->cell_bound.end = in_out->cell_bound.start;
3084 in_out->offset[2] = (tmp[2] - tmp[0]) / colrow_compute_pixel_scale (sheet, TRUE);
3085 in_out->offset[3] = (tmp[3] - tmp[1]) / colrow_compute_pixel_scale (sheet, FALSE);
3086 break;
3087 case GNM_SO_ANCHOR_ABSOLUTE: {
3088 double h, v;
3089 range_init (&in_out->cell_bound, 0, 0, 0, 0);
3090 h = colrow_compute_pixel_scale (sheet, TRUE);
3091 v = colrow_compute_pixel_scale (sheet, FALSE);
3092 in_out->offset[0] = tmp[0] / h;
3093 in_out->offset[1] = tmp[1] / v;
3094 in_out->offset[2] = (tmp[2] - tmp[0]) / h;
3095 in_out->offset[3] = (tmp[3] - tmp[1]) / v;
3096 break;
3101 static double
3102 cell_offset_calc_pixel (Sheet const *sheet, int i, gboolean is_col,
3103 double offset)
3105 ColRowInfo const *cri = sheet_colrow_get_info (sheet, i, is_col);
3106 return offset * cri->size_pixels;
3109 void
3110 scg_object_anchor_to_coords (SheetControlGUI const *scg,
3111 SheetObjectAnchor const *anchor, double *coords)
3113 Sheet *sheet = scg_sheet (scg);
3114 GODrawingAnchorDir direction;
3115 gint64 pixels[4];
3116 GnmRange const *r;
3118 g_return_if_fail (GNM_IS_SCG (scg));
3119 g_return_if_fail (anchor != NULL);
3120 g_return_if_fail (coords != NULL);
3122 r = &anchor->cell_bound;
3123 if (anchor->mode != GNM_SO_ANCHOR_ABSOLUTE) {
3124 pixels[0] = scg_colrow_distance_get (scg, TRUE, 0, r->start.col);
3125 pixels[1] = scg_colrow_distance_get (scg, FALSE, 0, r->start.row);
3126 if (anchor->mode == GNM_SO_ANCHOR_TWO_CELLS) {
3127 pixels[2] = pixels[0] + scg_colrow_distance_get (scg, TRUE,
3128 r->start.col, r->end.col);
3129 pixels[3] = pixels[1] + scg_colrow_distance_get (scg, FALSE,
3130 r->start.row, r->end.row);
3131 /* add .5 to offsets so that the rounding is optimal */
3132 pixels[0] += cell_offset_calc_pixel (sheet, r->start.col,
3133 TRUE, anchor->offset[0]) + .5;
3134 pixels[1] += cell_offset_calc_pixel (sheet, r->start.row,
3135 FALSE, anchor->offset[1]) + .5;
3136 pixels[2] += cell_offset_calc_pixel (sheet, r->end.col,
3137 TRUE, anchor->offset[2]) + .5;
3138 pixels[3] += cell_offset_calc_pixel (sheet, r->end.row,
3139 FALSE, anchor->offset[3]) + .5;
3140 } else {
3141 /* add .5 to offsets so that the rounding is optimal */
3142 pixels[0] += cell_offset_calc_pixel (sheet, r->start.col,
3143 TRUE, anchor->offset[0]) + .5;
3144 pixels[1] += cell_offset_calc_pixel (sheet, r->start.row,
3145 FALSE, anchor->offset[1]) + .5;
3146 pixels[2] = pixels[0] + go_fake_floor (anchor->offset[2] * colrow_compute_pixel_scale (sheet, TRUE) + .5);
3147 pixels[3] = pixels[1] + go_fake_floor (anchor->offset[3] * colrow_compute_pixel_scale (sheet, TRUE) + .5);
3149 } else {
3150 double h, v;
3151 h = colrow_compute_pixel_scale (sheet, TRUE);
3152 v = colrow_compute_pixel_scale (sheet, FALSE);
3153 pixels[0] = go_fake_floor (anchor->offset[0] * h);
3154 pixels[1] = go_fake_floor (anchor->offset[1] * v);
3155 pixels[2] = go_fake_floor ((anchor->offset[0] + anchor->offset[2]) * h);
3156 pixels[3] = go_fake_floor ((anchor->offset[1] + anchor->offset[3]) * v);
3159 direction = anchor->base.direction;
3160 if (direction == GOD_ANCHOR_DIR_UNKNOWN)
3161 direction = GOD_ANCHOR_DIR_DOWN_RIGHT;
3163 coords[0] = pixels[direction & GOD_ANCHOR_DIR_H_MASK ? 0 : 2];
3164 coords[1] = pixels[direction & GOD_ANCHOR_DIR_V_MASK ? 1 : 3];
3165 coords[2] = pixels[direction & GOD_ANCHOR_DIR_H_MASK ? 2 : 0];
3166 coords[3] = pixels[direction & GOD_ANCHOR_DIR_V_MASK ? 3 : 1];
3169 /***************************************************************************/
3171 static gboolean
3172 scg_comment_display_filter_cb (PangoAttribute *attribute, gboolean *state)
3174 if (attribute->klass->type == PANGO_ATTR_FOREGROUND &&
3175 attribute->start_index != attribute->end_index)
3176 *state = TRUE;
3177 return FALSE;
3181 * scg_comment_display:
3182 * @scg: The SheetControl
3183 * @cc: A cell comment
3186 void
3187 scg_comment_display (SheetControlGUI *scg, GnmComment *cc,
3188 int x, int y)
3190 g_return_if_fail (GNM_IS_SCG (scg));
3192 scg_comment_timer_clear (scg);
3194 /* If someone clicked and dragged the comment marker this may be NULL */
3195 if (scg->comment.selected == NULL)
3196 return;
3198 if (cc == NULL)
3199 cc = scg->comment.selected;
3200 else if (scg->comment.selected != cc)
3201 scg_comment_unselect (scg, scg->comment.selected);
3203 g_return_if_fail (GNM_IS_CELL_COMMENT (cc));
3205 if (scg->comment.item == NULL) {
3206 GtkWidget *label, *box;
3207 char *comment_text;
3208 PangoAttrList *comment_markup;
3209 char const *comment_author;
3211 g_object_get (G_OBJECT (cc),
3212 "text", &comment_text,
3213 "markup", &comment_markup,
3214 NULL);
3215 comment_author = cell_comment_author_get (cc);
3217 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
3219 if (comment_author != NULL) {
3220 char *text;
3221 PangoAttrList *attrs;
3222 PangoAttribute *attr;
3224 /* xgettext: this is a by-line for cell comments */
3225 text = g_strdup_printf (_("By %s:"), comment_author);
3226 label = gtk_label_new (text);
3227 g_free (text);
3229 attrs = pango_attr_list_new ();
3230 attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
3231 attr->start_index = 0;
3232 attr->end_index = G_MAXINT;
3233 pango_attr_list_insert (attrs, attr);
3234 gtk_label_set_attributes (GTK_LABEL (label), attrs);
3235 pango_attr_list_unref (attrs);
3237 gtk_widget_set_halign (label, GTK_ALIGN_START);
3238 gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
3239 gtk_box_set_spacing (GTK_BOX (box), 10);
3242 label = gtk_label_new (comment_text);
3243 if (comment_markup) {
3244 gboolean font_colour_set = FALSE;
3245 pango_attr_list_filter
3246 (comment_markup,
3247 (PangoAttrFilterFunc) scg_comment_display_filter_cb,
3248 &font_colour_set);
3249 if (font_colour_set) {
3250 /* Imported comments may have a font colour set. */
3251 /* If that is the case, we set a background colour. */
3252 guint length = strlen (comment_text);
3253 PangoAttribute *attr = pango_attr_foreground_new (0,0,0);
3254 attr->start_index = 0;
3255 attr->end_index = length;
3256 pango_attr_list_insert_before (comment_markup, attr);
3257 attr = pango_attr_background_new (255*255, 255*255, 224*255 );
3258 attr->start_index = 0;
3259 attr->end_index = length;
3260 pango_attr_list_insert_before (comment_markup, attr);
3262 gtk_label_set_attributes (GTK_LABEL (label), comment_markup);
3264 g_free (comment_text);
3265 gtk_widget_set_halign (label, GTK_ALIGN_START);
3266 gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
3268 gnm_convert_to_tooltip (GTK_WIDGET (scg->grid), box);
3270 scg->comment.item = gtk_widget_get_toplevel (box);
3271 gtk_window_move (GTK_WINDOW (scg->comment.item),
3272 x + 10, y + 10);
3274 gtk_widget_show_all (scg->comment.item);
3278 static gint
3279 cb_cell_comment_timer (SheetControlGUI *scg)
3281 g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3282 g_return_val_if_fail (scg->comment.timer != 0, FALSE);
3284 scg->comment.timer = 0;
3285 scg_comment_display (scg, scg->comment.selected,
3286 scg->comment.x, scg->comment.y);
3287 return FALSE;
3291 * scg_comment_select:
3292 * @scg: The SheetControl
3293 * @cc: A cell comment
3295 * Prepare @cc for display.
3297 void
3298 scg_comment_select (SheetControlGUI *scg, GnmComment *cc, int x, int y)
3300 g_return_if_fail (GNM_IS_SCG (scg));
3302 if (scg->comment.selected != NULL)
3303 scg_comment_unselect (scg, scg->comment.selected);
3305 g_return_if_fail (scg->comment.timer == 0);
3307 scg->comment.selected = cc;
3308 scg->comment.timer = g_timeout_add (1000,
3309 (GSourceFunc)cb_cell_comment_timer, scg);
3310 scg->comment.x = x;
3311 scg->comment.y = y;
3315 * scg_comment_unselect:
3316 * @scg: The SheetControl
3317 * @cc: A cell comment
3319 * If @cc is the current cell comment being edited/displayed shutdown the
3320 * display mechanism.
3322 void
3323 scg_comment_unselect (SheetControlGUI *scg, GnmComment *cc)
3325 g_return_if_fail (GNM_IS_SCG (scg));
3327 if (cc == scg->comment.selected) {
3328 scg->comment.selected = NULL;
3329 scg_comment_timer_clear (scg);
3331 if (scg->comment.item != NULL) {
3332 gtk_widget_destroy (scg->comment.item);
3333 scg->comment.item = NULL;
3338 /************************************************************************/
3339 /* Col/Row size support routines. */
3341 gint64
3342 scg_colrow_distance_get (SheetControlGUI const *scg, gboolean is_cols,
3343 int from, int to)
3345 Sheet *sheet = scg_sheet (scg);
3346 ColRowCollection const *collection;
3347 int default_size;
3348 int i;
3349 gint64 pixels = 0;
3350 int sign = 1;
3352 g_return_val_if_fail (GNM_IS_SCG (scg), 1);
3354 if (from > to) {
3355 int const tmp = to;
3356 to = from;
3357 from = tmp;
3358 sign = -1;
3361 g_return_val_if_fail (from >= 0, 1);
3363 if (is_cols) {
3364 g_return_val_if_fail (to <= gnm_sheet_get_max_cols (sheet), 1);
3365 collection = &sheet->cols;
3366 } else {
3367 g_return_val_if_fail (to <= gnm_sheet_get_max_rows (sheet), 1);
3368 collection = &sheet->rows;
3371 /* Do not use col_row_foreach, it ignores empties.
3372 * Optimize this so that long jumps are not quite so horrific
3373 * for performance.
3375 default_size = collection->default_style.size_pixels;
3376 for (i = from ; i < to ; ++i) {
3377 ColRowSegment const *segment =
3378 COLROW_GET_SEGMENT(collection, i);
3380 if (segment != NULL) {
3381 ColRowInfo const *cri = segment->info[COLROW_SUB_INDEX (i)];
3382 if (cri == NULL)
3383 pixels += default_size;
3384 else if (cri->visible)
3385 pixels += cri->size_pixels;
3386 } else {
3387 int segment_end = COLROW_SEGMENT_END (i)+1;
3388 if (segment_end > to)
3389 segment_end = to;
3390 pixels += default_size * (segment_end - i);
3391 i = segment_end - 1;
3395 return pixels*sign;
3398 /*************************************************************************/
3400 static void
3401 scg_cursor_bound (SheetControl *sc, GnmRange const *r)
3403 SheetControlGUI *scg = (SheetControlGUI *) sc;
3404 SCG_FOREACH_PANE (scg, pane, gnm_pane_cursor_bound_set (pane, r););
3407 static void
3408 scg_recompute_visible_region (SheetControl *sc, gboolean full_recompute)
3410 SheetControlGUI *scg = (SheetControlGUI *) sc;
3412 SCG_FOREACH_PANE (scg, pane,
3413 gnm_pane_compute_visible_region (pane, full_recompute););
3416 void
3417 scg_edit_start (SheetControlGUI *scg)
3419 g_return_if_fail (GNM_IS_SCG (scg));
3421 SCG_FOREACH_PANE (scg, pane, gnm_pane_edit_start (pane););
3424 void
3425 scg_edit_stop (SheetControlGUI *scg)
3427 g_return_if_fail (GNM_IS_SCG (scg));
3429 scg_rangesel_stop (scg, FALSE);
3430 SCG_FOREACH_PANE (scg, pane, gnm_pane_edit_stop (pane););
3434 * scg_rangesel_changed:
3435 * @scg: The scg
3437 * Notify expr_entry that the expression range has changed.
3439 static void
3440 scg_rangesel_changed (SheetControlGUI *scg,
3441 int base_col, int base_row,
3442 int move_col, int move_row)
3444 GnmExprEntry *expr_entry;
3445 gboolean ic_changed;
3446 GnmRange *r, last_r;
3447 Sheet *sheet;
3449 g_return_if_fail (GNM_IS_SCG (scg));
3451 scg->rangesel.base_corner.col = base_col;
3452 scg->rangesel.base_corner.row = base_row;
3453 scg->rangesel.move_corner.col = move_col;
3454 scg->rangesel.move_corner.row = move_row;
3456 r = &scg->rangesel.displayed;
3457 if (base_col < move_col) {
3458 r->start.col = base_col;
3459 r->end.col = move_col;
3460 } else {
3461 r->end.col = base_col;
3462 r->start.col = move_col;
3464 if (base_row < move_row) {
3465 r->start.row = base_row;
3466 r->end.row = move_row;
3467 } else {
3468 r->end.row = base_row;
3469 r->start.row = move_row;
3472 sheet = scg_sheet (scg);
3473 expr_entry = wbcg_get_entry_logical (scg->wbcg);
3475 gnm_expr_entry_freeze (expr_entry);
3476 /* The order here is tricky.
3477 * 1) Assign the range to the expr entry.
3479 ic_changed = gnm_expr_entry_load_from_range (
3480 expr_entry, sheet, r);
3482 /* 2) if the expr entry changed the region get the new region */
3483 if (ic_changed)
3484 gnm_expr_entry_get_rangesel (expr_entry, r, NULL);
3486 /* 3) now double check that all merged regions are fully contained */
3487 last_r = *r;
3488 gnm_sheet_merge_find_bounding_box (sheet, r);
3489 if (!range_equal (&last_r, r))
3490 gnm_expr_entry_load_from_range (expr_entry, sheet, r);
3492 gnm_expr_entry_thaw (expr_entry);
3494 SCG_FOREACH_PANE (scg, pane, gnm_pane_rangesel_bound_set (pane, r););
3497 void
3498 scg_rangesel_start (SheetControlGUI *scg,
3499 int base_col, int base_row,
3500 int move_col, int move_row)
3502 GnmRange r;
3504 g_return_if_fail (GNM_IS_SCG (scg));
3506 if (scg->rangesel.active)
3507 return;
3509 if (scg->wbcg->rangesel != NULL)
3510 g_warning ("misconfiged rangesel");
3512 scg->wbcg->rangesel = scg;
3513 scg->rangesel.active = TRUE;
3515 gnm_expr_entry_find_range (wbcg_get_entry_logical (scg->wbcg));
3517 range_init (&r, base_col, base_row, move_col, move_row);
3518 SCG_FOREACH_PANE (scg, pane, gnm_pane_rangesel_start (pane, &r););
3519 scg_rangesel_changed (scg, base_col, base_row, move_col, move_row);
3522 void
3523 scg_rangesel_stop (SheetControlGUI *scg, gboolean clear_string)
3525 g_return_if_fail (GNM_IS_SCG (scg));
3527 if (!scg->rangesel.active)
3528 return;
3529 if (scg->wbcg->rangesel != scg)
3530 g_warning ("misconfiged rangesel");
3532 scg->wbcg->rangesel = NULL;
3533 scg->rangesel.active = FALSE;
3534 SCG_FOREACH_PANE (scg, pane, gnm_pane_rangesel_stop (pane););
3536 gnm_expr_entry_rangesel_stop (wbcg_get_entry_logical (scg->wbcg),
3537 clear_string);
3541 * scg_set_display_cursor:
3542 * @scg:
3544 * Set the displayed cursor type.
3546 void
3547 scg_set_display_cursor (SheetControlGUI *scg)
3549 GdkCursorType cursor = GDK_CURSOR_IS_PIXMAP;
3551 g_return_if_fail (GNM_IS_SCG (scg));
3553 if (scg->wbcg->new_object != NULL)
3554 cursor = GDK_CROSSHAIR;
3556 SCG_FOREACH_PANE (scg, pane, {
3557 GtkWidget *w = GTK_WIDGET (pane);
3558 if (gtk_widget_get_window (w)) {
3559 if (cursor == GDK_CURSOR_IS_PIXMAP)
3560 gnm_widget_set_cursor (w, pane->mouse_cursor);
3561 else
3562 gnm_widget_set_cursor_type (w, cursor);
3567 void
3568 scg_rangesel_extend_to (SheetControlGUI *scg, int col, int row)
3570 int base_col, base_row;
3572 if (col < 0) {
3573 base_col = 0;
3574 col = gnm_sheet_get_last_col (scg_sheet (scg));
3575 } else
3576 base_col = scg->rangesel.base_corner.col;
3577 if (row < 0) {
3578 base_row = 0;
3579 row = gnm_sheet_get_last_row (scg_sheet (scg));
3580 } else
3581 base_row = scg->rangesel.base_corner.row;
3583 if (scg->rangesel.active)
3584 scg_rangesel_changed (scg, base_col, base_row, col, row);
3585 else
3586 scg_rangesel_start (scg, base_col, base_row, col, row);
3589 void
3590 scg_rangesel_bound (SheetControlGUI *scg,
3591 int base_col, int base_row,
3592 int move_col, int move_row)
3594 if (scg->rangesel.active)
3595 scg_rangesel_changed (scg, base_col, base_row, move_col, move_row);
3596 else
3597 scg_rangesel_start (scg, base_col, base_row, move_col, move_row);
3600 void
3601 scg_rangesel_move (SheetControlGUI *scg, int n, gboolean jump_to_bound,
3602 gboolean horiz)
3604 SheetView *sv = scg_view (scg);
3605 GnmCellPos tmp;
3607 if (!scg->rangesel.active) {
3608 tmp.col = sv->edit_pos_real.col;
3609 tmp.row = sv->edit_pos_real.row;
3610 } else
3611 tmp = scg->rangesel.base_corner;
3613 if (horiz)
3614 tmp.col = sheet_find_boundary_horizontal (
3615 sv_sheet (sv), tmp.col, tmp.row, tmp.row, n, jump_to_bound);
3616 else
3617 tmp.row = sheet_find_boundary_vertical (
3618 sv_sheet (sv), tmp.col, tmp.row, tmp.col, n, jump_to_bound);
3620 if (scg->rangesel.active)
3621 scg_rangesel_changed (scg, tmp.col, tmp.row, tmp.col, tmp.row);
3622 else
3623 scg_rangesel_start (scg, tmp.col, tmp.row, tmp.col, tmp.row);
3624 scg_make_cell_visible (scg, tmp.col, tmp.row, FALSE, FALSE);
3625 gnm_expr_entry_signal_update (
3626 wbcg_get_entry_logical (scg->wbcg), FALSE);
3629 void
3630 scg_rangesel_extend (SheetControlGUI *scg, int n,
3631 gboolean jump_to_bound, gboolean horiz)
3633 Sheet *sheet = scg_sheet (scg);
3635 if (scg->rangesel.active) {
3636 GnmCellPos tmp = scg->rangesel.move_corner;
3638 if (horiz)
3639 tmp.col = sheet_find_boundary_horizontal (sheet,
3640 tmp.col, tmp.row, scg->rangesel.base_corner.row,
3641 n, jump_to_bound);
3642 else
3643 tmp.row = sheet_find_boundary_vertical (sheet,
3644 tmp.col, tmp.row, scg->rangesel.base_corner.col,
3645 n, jump_to_bound);
3647 scg_rangesel_changed (scg,
3648 scg->rangesel.base_corner.col,
3649 scg->rangesel.base_corner.row, tmp.col, tmp.row);
3651 scg_make_cell_visible (scg,
3652 scg->rangesel.move_corner.col,
3653 scg->rangesel.move_corner.row, FALSE, TRUE);
3654 gnm_expr_entry_signal_update (
3655 wbcg_get_entry_logical (scg->wbcg), FALSE);
3656 } else
3657 scg_rangesel_move (scg, n, jump_to_bound, horiz);
3661 * scg_cursor_move:
3662 * @scg: The scg
3663 * @dir: Number of units to move the cursor
3664 * @jump_to_bound: skip from the start to the end of ranges
3665 * of filled or unfilled cells.
3666 * @horiz: is the movement horizontal or vertical
3668 * Moves the cursor count rows
3670 void
3671 scg_cursor_move (SheetControlGUI *scg, int n,
3672 gboolean jump_to_bound, gboolean horiz)
3674 SheetView *sv = scg_view (scg);
3675 GnmCellPos tmp = sv->edit_pos_real;
3676 int step = (n>0) ? 1 : -1;
3678 if (!wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
3679 return;
3681 if (horiz)
3682 tmp.col = sheet_find_boundary_horizontal (sv->sheet,
3683 tmp.col + n - step, tmp.row, tmp.row,
3684 step, jump_to_bound);
3685 else
3686 tmp.row = sheet_find_boundary_vertical
3687 (sv->sheet,
3688 tmp.col, tmp.row + n - step,
3689 tmp.col,
3690 step, jump_to_bound);
3692 sv_selection_reset (sv);
3693 gnm_sheet_view_cursor_set (sv, &tmp,
3694 tmp.col, tmp.row, tmp.col, tmp.row, NULL);
3695 gnm_sheet_view_make_cell_visible (sv, tmp.col, tmp.row, FALSE);
3696 sv_selection_add_pos (sv, tmp.col, tmp.row, GNM_SELECTION_MODE_ADD);
3700 * scg_cursor_extend:
3701 * @scg: The scg
3702 * @n: Units to extend the selection
3703 * @jump_to_bound: Move to transitions between cells and blanks,
3704 * or move in single steps.
3705 * @horiz: extend vertically or horizontally.
3707 void
3708 scg_cursor_extend (SheetControlGUI *scg, int n,
3709 gboolean jump_to_bound, gboolean horiz)
3711 SheetView *sv = scg_view (scg);
3712 GnmCellPos move = sv->cursor.move_corner;
3713 GnmCellPos visible = scg->pane[0]->first;
3715 if (!wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
3716 return;
3718 if (horiz)
3719 visible.col = move.col = sheet_find_boundary_horizontal (sv->sheet,
3720 move.col, move.row, sv->cursor.base_corner.row,
3721 n, jump_to_bound);
3722 else
3723 visible.row = move.row = sheet_find_boundary_vertical (sv->sheet,
3724 move.col, move.row, sv->cursor.base_corner.col,
3725 n, jump_to_bound);
3727 sv_selection_extend_to (sv, move.col, move.row);
3728 gnm_sheet_view_make_cell_visible (sv, visible.col, visible.row, FALSE);
3731 void
3732 scg_take_focus (SheetControlGUI *scg)
3734 g_return_if_fail (GNM_IS_SCG (scg));
3736 /* FIXME: Slightly hackish. */
3737 if (wbcg_toplevel (scg->wbcg))
3738 gtk_window_set_focus (wbcg_toplevel (scg->wbcg),
3739 (scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT)?
3740 GTK_WIDGET (scg->vs): GTK_WIDGET (scg_pane (scg, 0)));
3743 /*********************************************************************************/
3744 void
3745 scg_size_guide_start (SheetControlGUI *scg,
3746 gboolean vert, int colrow, gboolean is_colrow_resize)
3748 g_return_if_fail (GNM_IS_SCG (scg));
3749 SCG_FOREACH_PANE (scg, pane,
3750 gnm_pane_size_guide_start (pane, vert, colrow, is_colrow_resize););
3752 void
3753 scg_size_guide_motion (SheetControlGUI *scg, gboolean vert, gint64 guide_pos)
3755 g_return_if_fail (GNM_IS_SCG (scg));
3756 SCG_FOREACH_PANE (scg, pane,
3757 gnm_pane_size_guide_motion (pane, vert, guide_pos););
3759 void
3760 scg_size_guide_stop (SheetControlGUI *scg)
3762 g_return_if_fail (GNM_IS_SCG (scg));
3763 SCG_FOREACH_PANE (scg, pane,
3764 gnm_pane_size_guide_stop (pane););
3766 /*********************************************************************************/
3768 void
3769 scg_special_cursor_start (SheetControlGUI *scg, int style, int button)
3771 g_return_if_fail (GNM_IS_SCG (scg));
3773 SCG_FOREACH_PANE (scg, pane,
3774 gnm_pane_special_cursor_start (pane, style, button););
3777 void
3778 scg_special_cursor_stop (SheetControlGUI *scg)
3780 g_return_if_fail (GNM_IS_SCG (scg));
3782 SCG_FOREACH_PANE (scg, pane,
3783 gnm_pane_special_cursor_stop (pane););
3786 gboolean
3787 scg_special_cursor_bound_set (SheetControlGUI *scg, GnmRange const *r)
3789 gboolean changed = FALSE;
3791 g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3793 SCG_FOREACH_PANE (scg, pane,
3794 changed |= gnm_pane_special_cursor_bound_set (pane, r););
3795 return changed;
3798 static void
3799 scg_object_create_view (SheetControl *sc, SheetObject *so)
3801 SheetControlGUI *scg = GNM_SCG (sc);
3802 if (scg->active_panes)
3803 SCG_FOREACH_PANE (scg, pane,
3804 sheet_object_new_view (so, (SheetObjectViewContainer *)pane););
3805 else
3806 sheet_object_new_view (so, (SheetObjectViewContainer *)scg->vs);
3809 static void
3810 scg_scale_changed (SheetControl *sc)
3812 SheetControlGUI *scg = (SheetControlGUI *)sc;
3813 Sheet *sheet = scg_sheet (scg);
3814 double z;
3815 GSList *ptr;
3817 g_return_if_fail (GNM_IS_SCG (scg));
3819 z = sheet->last_zoom_factor_used;
3821 SCG_FOREACH_PANE (scg, pane, {
3822 if (pane->col.canvas != NULL)
3823 goc_canvas_set_pixels_per_unit (pane->col.canvas, z);
3824 if (pane->row.canvas != NULL)
3825 goc_canvas_set_pixels_per_unit (pane->row.canvas, z);
3826 goc_canvas_set_pixels_per_unit (GOC_CANVAS (pane), z);
3829 scg_resize (scg, TRUE);
3830 set_resize_pane_pos (scg, scg->vpane);
3831 set_resize_pane_pos (scg, scg->hpane);
3832 /* now, update sheet objects positions and sizes */
3833 for (ptr = sheet->sheet_objects; ptr; ptr = ptr->next)
3834 sheet_object_update_bounds (GNM_SO (ptr->data), NULL);
3837 static gboolean
3838 cb_cell_im_timer (SheetControlGUI *scg)
3840 g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3841 g_return_val_if_fail (scg->im.timer != 0, FALSE);
3843 scg->im.timer = 0;
3844 scg_im_destroy (scg);
3845 return FALSE;
3848 static GnmPane *
3849 scg_find_pane (SheetControlGUI *scg, GnmCellPos *pos)
3851 int i;
3853 for (i = 0; i < scg->active_panes; i++) {
3854 GnmPane *pane = scg->pane[i];
3856 if (pane &&
3857 pane->first.col <= pos->col &&
3858 pane->first.row <= pos->row &&
3859 pane->last_visible.col >= pos->col &&
3860 pane->last_visible.row >= pos->row)
3861 return pane;
3863 return NULL;
3866 static void
3867 scg_show_im_tooltip (SheetControl *sc, GnmInputMsg *im, GnmCellPos *pos)
3869 SheetControlGUI *scg = (SheetControlGUI *)sc;
3870 GnmPane *pane;
3872 g_return_if_fail (GNM_IS_SCG (scg));
3874 scg_im_destroy (scg);
3876 pane = scg_find_pane (scg, pos);
3878 if (im && pane) {
3879 GtkWidget *label, *box;
3880 char const *text, *title;
3881 int len_text, len_title;
3882 int x, y, x_origin, y_origin;
3883 GtkAllocation allocation;
3884 Sheet *sheet = scg_sheet (scg);
3885 gboolean rtl = sheet->text_is_rtl;
3887 text = gnm_input_msg_get_msg (im);
3888 title = gnm_input_msg_get_title (im);
3889 len_text = (text == NULL) ? 0 : strlen (text);
3890 len_title = (title == NULL) ? 0 : strlen (title);
3892 if ((len_text == 0) && (len_title == 0))
3893 return;
3895 box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
3897 if (len_title > 0) {
3898 PangoAttrList *attrs;
3899 PangoAttribute *attr;
3901 label = gtk_label_new (title);
3903 attrs = pango_attr_list_new ();
3904 attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
3905 attr->start_index = 0;
3906 attr->end_index = G_MAXINT;
3907 pango_attr_list_insert (attrs, attr);
3908 gtk_label_set_attributes (GTK_LABEL (label), attrs);
3909 pango_attr_list_unref (attrs);
3911 gtk_widget_set_halign (label, GTK_ALIGN_START);
3912 gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
3914 if (len_text > 0) {
3915 label = gtk_label_new (text);
3917 gtk_widget_set_halign (label, GTK_ALIGN_START);
3918 gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
3919 if (len_title > 0)
3920 gtk_box_set_spacing (GTK_BOX (box), 10);
3922 gnm_convert_to_tooltip (GTK_WIDGET (scg->grid), box);
3923 scg->im.item = gtk_widget_get_toplevel (box);
3925 x = sheet_col_get_distance_pixels
3926 (sheet, pane->first.col, pos->col + (rtl ? 1 : 0));
3928 y = sheet_row_get_distance_pixels
3929 (sheet, pane->first.row, pos->row + 1);
3931 gtk_widget_get_allocation (GTK_WIDGET (pane), &allocation);
3932 if (rtl)
3933 x = allocation.width - x;
3934 x += allocation.x;
3935 y += allocation.y;
3937 gdk_window_get_position
3938 (gtk_widget_get_parent_window (GTK_WIDGET (pane)),
3939 &x_origin, &y_origin);
3940 x += x_origin;
3941 y += y_origin;
3943 gtk_window_move (GTK_WINDOW (scg->im.item), x + 10, y + 10);
3944 gtk_widget_show_all (scg->im.item);
3945 scg->im.timer = g_timeout_add (1500, (GSourceFunc)cb_cell_im_timer, scg);
3950 static void
3951 scg_class_init (GObjectClass *object_class)
3953 SheetControlClass *sc_class = SHEET_CONTROL_CLASS (object_class);
3955 g_return_if_fail (sc_class != NULL);
3957 scg_parent_class = g_type_class_peek_parent (object_class);
3959 object_class->finalize = scg_finalize;
3961 sc_class->resize = scg_resize_virt;
3962 sc_class->redraw_all = scg_redraw_all;
3963 sc_class->redraw_range = scg_redraw_range;
3964 sc_class->redraw_headers = scg_redraw_headers;
3965 sc_class->ant = scg_ant;
3966 sc_class->unant = scg_unant;
3967 sc_class->scrollbar_config = scg_scrollbar_config;
3968 sc_class->mode_edit = scg_mode_edit_virt;
3969 sc_class->set_top_left = scg_set_top_left;
3970 sc_class->recompute_visible_region = scg_recompute_visible_region;
3971 sc_class->make_cell_visible = scg_make_cell_visible_virt;
3972 sc_class->cursor_bound = scg_cursor_bound;
3973 sc_class->set_panes = scg_set_panes;
3974 sc_class->object_create_view = scg_object_create_view;
3975 sc_class->scale_changed = scg_scale_changed;
3976 sc_class->show_im_tooltip = scg_show_im_tooltip;
3979 GSF_CLASS (SheetControlGUI, sheet_control_gui,
3980 scg_class_init, scg_init, GNM_SHEET_CONTROL_TYPE)
3982 static gint
3983 cb_scg_queued_movement (SheetControlGUI *scg)
3985 Sheet const *sheet = scg_sheet (scg);
3986 scg->delayedMovement.timer = 0;
3987 (*scg->delayedMovement.handler) (scg,
3988 scg->delayedMovement.n, FALSE,
3989 scg->delayedMovement.horiz);
3990 if (wbcg_is_editing (scg->wbcg))
3991 sheet_update_only_grid (sheet);
3992 else
3993 sheet_update (sheet);
3994 return FALSE;
3998 * scg_queue_movement:
3999 * @scg:
4000 * @handler: (scope async): The movement handler
4001 * @n: how far
4002 * @jump: TRUE jump to bound
4003 * @horiz: TRUE move by cols
4005 * Do motion compression when possible to avoid redrawing an area that will
4006 * disappear when we scroll again.
4008 void
4009 scg_queue_movement (SheetControlGUI *scg,
4010 SCGUIMoveFunc handler,
4011 int n, gboolean jump, gboolean horiz)
4013 g_return_if_fail (GNM_IS_SCG (scg));
4015 /* do we need to flush a pending movement */
4016 if (scg->delayedMovement.timer != 0) {
4017 if (jump ||
4018 /* do not skip more than 3 requests at a time */
4019 scg->delayedMovement.counter > 3 ||
4020 scg->delayedMovement.handler != handler ||
4021 scg->delayedMovement.horiz != horiz) {
4022 g_source_remove (scg->delayedMovement.timer);
4023 (*scg->delayedMovement.handler) (scg,
4024 scg->delayedMovement.n, FALSE,
4025 scg->delayedMovement.horiz);
4026 scg->delayedMovement.handler = NULL;
4027 scg->delayedMovement.timer = 0;
4028 } else {
4029 scg->delayedMovement.counter++;
4030 scg->delayedMovement.n += n;
4031 return;
4035 /* jumps are always immediate */
4036 if (jump) {
4037 Sheet const *sheet = scg_sheet (scg);
4038 (*handler) (scg, n, TRUE, horiz);
4039 if (wbcg_is_editing (scg->wbcg))
4040 sheet_update_only_grid (sheet);
4041 else
4042 sheet_update (sheet);
4043 return;
4046 scg->delayedMovement.counter = 1;
4047 scg->delayedMovement.handler = handler;
4048 scg->delayedMovement.horiz = horiz;
4049 scg->delayedMovement.n = n;
4050 scg->delayedMovement.timer = g_timeout_add (10,
4051 (GSourceFunc)cb_scg_queued_movement, scg);
4054 static void
4055 scg_image_create (SheetControlGUI *scg, SheetObjectAnchor *anchor,
4056 guint8 const *data, unsigned len)
4058 SheetObjectImage *soi;
4059 SheetObject *so;
4060 double w, h;
4062 /* ensure that we are not editing anything else */
4063 scg_mode_edit (scg);
4065 soi = g_object_new (GNM_SO_IMAGE_TYPE, NULL);
4066 sheet_object_image_set_image (soi, "", data, len);
4068 so = GNM_SO (soi);
4069 sheet_object_set_anchor (so, anchor);
4070 sheet_object_set_sheet (so, scg_sheet (scg));
4071 scg_object_select (scg, so);
4072 sheet_object_default_size (so, &w, &h);
4073 scg_objects_drag (scg, NULL, NULL, &w, &h, 7, FALSE, FALSE, FALSE);
4074 scg_objects_drag_commit (scg, 7, TRUE, NULL, NULL, NULL);
4077 void
4078 scg_paste_image (SheetControlGUI *scg, GnmRange *where,
4079 guint8 const *data, unsigned len)
4081 SheetObjectAnchor anchor;
4083 sheet_object_anchor_init (&anchor, where, NULL,
4084 GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4085 scg_image_create (scg, &anchor, data, len);
4088 static void
4089 scg_drag_receive_img_data (SheetControlGUI *scg, double x, double y,
4090 guint8 const *data, unsigned len)
4092 double coords[4];
4093 SheetObjectAnchor anchor;
4095 sheet_object_anchor_init (&anchor, NULL, NULL,
4096 GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4097 coords[0] = coords[2] = x;
4098 coords[1] = coords[3] = y;
4099 scg_object_coords_to_anchor (scg, coords, &anchor);
4100 scg_image_create (scg, &anchor, data, len);
4103 static void
4104 scg_drag_receive_img_uri (SheetControlGUI *scg, double x, double y, const gchar *uri)
4106 GError *err = NULL;
4107 GsfInput *input = go_file_open (uri, &err);
4108 GOIOContext *ioc = go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4110 if (input != NULL) {
4111 unsigned len = gsf_input_size (input);
4112 guint8 const *data = gsf_input_read (input, len, NULL);
4114 scg_drag_receive_img_data (scg, x, y, data, len);
4115 g_object_unref (input);
4116 } else
4117 go_cmd_context_error (GO_CMD_CONTEXT (ioc), err);
4119 if (go_io_error_occurred (ioc) ||
4120 go_io_warning_occurred (ioc)) {
4121 go_io_error_display (ioc);
4122 go_io_error_clear (ioc);
4124 g_object_unref (ioc);
4127 static void
4128 scg_drag_receive_spreadsheet (SheetControlGUI *scg, const gchar *uri)
4130 GError *err = NULL;
4131 GsfInput *input = go_file_open (uri, &err);
4132 GOIOContext *ioc = go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4134 if (input != NULL) {
4135 WorkbookView *wbv;
4137 wbv = workbook_view_new_from_input (input, uri, NULL, ioc, NULL);
4138 if (wbv != NULL)
4139 gui_wb_view_show (scg->wbcg,
4140 wbv);
4142 } else
4143 go_cmd_context_error (GO_CMD_CONTEXT (ioc), err);
4145 if (go_io_error_occurred (ioc) ||
4146 go_io_warning_occurred (ioc)) {
4147 go_io_error_display (ioc);
4148 go_io_error_clear (ioc);
4150 g_object_unref (ioc);
4153 static void
4154 scg_paste_cellregion (SheetControlGUI *scg, double x, double y,
4155 GnmCellRegion *content)
4157 WorkbookControl *wbc = scg_wbc (scg);
4158 Sheet *sheet = scg_sheet (scg) ;
4159 GnmPasteTarget pt;
4160 SheetObjectAnchor anchor;
4161 double coords[4];
4163 sheet_object_anchor_init (&anchor, NULL, NULL,
4164 GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4165 coords[0] = coords[2] = x;
4166 coords[1] = coords[3] = y;
4167 scg_object_coords_to_anchor (scg, coords, &anchor);
4168 paste_target_init (&pt, sheet, &anchor.cell_bound, PASTE_ALL_SHEET);
4169 if (content && ((content->cols > 0 && content->rows > 0) ||
4170 content->objects != NULL))
4171 cmd_paste_copy (wbc, &pt, content);
4174 static void
4175 scg_drag_receive_cellregion (SheetControlGUI *scg, double x, double y,
4176 const char *data, unsigned len)
4178 GnmCellRegion *content;
4179 GOIOContext *io_context =
4180 go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4182 content = gnm_xml_cellregion_read (scg_wbc (scg), io_context,
4183 scg_sheet (scg), data, len);
4184 g_object_unref (io_context);
4185 if (content != NULL) {
4186 scg_paste_cellregion (scg, x, y, content);
4187 cellregion_unref (content);
4191 static void
4192 scg_drag_receive_uri_list (SheetControlGUI *scg, double x, double y,
4193 const char *data, unsigned len)
4195 char *cdata = g_strndup (data, len);
4196 GSList *urls = go_file_split_urls (cdata);
4197 GSList *l;
4199 g_free (cdata);
4200 for (l = urls; l; l = l-> next) {
4201 char const *uri_str = l->data;
4202 gchar *mime = go_get_mime_type (uri_str);
4203 /* Note that we have imperfect detection of mime-type with some
4204 * platforms, e.g. Win32. In the worst case if
4205 * go_get_mime_type() doesn't return "application/x-gnumeric"
4206 * (registry corruption?) it will give "text/plain" and a
4207 * spreadsheet file is assumed. */
4208 if (!mime)
4209 continue;
4211 if (!strncmp (mime, "image/", 6))
4212 scg_drag_receive_img_uri (scg, x, y, uri_str);
4213 else if (!strcmp (mime, "application/x-gnumeric") ||
4214 !strcmp (mime, "application/vnd.ms-excel") ||
4215 !strcmp (mime, "application/vnd.sun.xml.calc") ||
4216 !strcmp (mime, "application/vnd.oasis.opendocument.spreadsheet") ||
4217 !strcmp (mime, "application/vnd.lotus-1-2-3") ||
4218 !strcmp (mime, "application/x-applix-spreadsheet") ||
4219 !strcmp (mime, "application/x-dbase") ||
4220 !strcmp (mime, "application/x-oleo") ||
4221 !strcmp (mime, "application/x-quattropro") ||
4222 !strcmp (mime, "application/x-sc") ||
4223 /* !strcmp (mime, "application/xhtml+xml") || */
4224 !strcmp (mime, "text/spreadsheet") ||
4225 !strcmp (mime, "text/tab-separated-values") ||
4226 !strcmp (mime, "text/x-comma-separated-values") ||
4227 !strcmp (mime, "text/html") ||
4228 !strcmp (mime, "text/plain")) {
4229 scg_drag_receive_spreadsheet (scg, uri_str);
4230 } else {
4231 g_printerr ("Received URI %s with mime type %s.\n", uri_str, mime);
4232 g_printerr ("I have no idea what to do with that.\n");
4234 g_free (mime);
4236 g_slist_free_full (urls, (GDestroyNotify) g_free);
4239 static void
4240 scg_drag_receive_same_process (SheetControlGUI *scg, GtkWidget *source_widget,
4241 double x, double y)
4243 SheetControlGUI *source_scg = NULL;
4244 GnmPane *pane;
4246 g_return_if_fail (source_widget != NULL);
4247 g_return_if_fail (GNM_IS_PANE (source_widget));
4249 pane = GNM_PANE (source_widget);
4250 x *= goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
4251 y *= goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
4252 source_scg = pane->simple.scg;
4253 if (source_scg == scg) {
4254 GdkWindow *window;
4255 GdkModifierType mask;
4256 gint64 xx = x, yy = y;
4257 gint64 origin_x = 0, origin_y = 0;
4258 gboolean make_dup;
4259 GOUndo *undo = NULL;
4260 GOUndo *redo = NULL;
4261 gchar *title = NULL;
4263 window = gtk_widget_get_parent_window (GTK_WIDGET (pane));
4264 gdk_window_get_device_position (window,
4265 gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (gdk_window_get_display (window))),
4266 NULL, NULL, &mask);
4268 make_dup = ((mask & GDK_CONTROL_MASK) != 0);
4270 /* When copying objects, we have to create a copy of current selection.
4271 * Since new objects are on top of canvas, we have to move current selection
4272 * back to original position, create a copy of selected objects, make them
4273 * the current selection, then move these objects to drop location. */
4275 if (make_dup) {
4276 xx = origin_x = pane->drag.origin_x;
4277 yy = origin_y = pane->drag.origin_y;
4280 gnm_pane_objects_drag (pane, NULL, xx, yy, 8, FALSE,
4281 (mask & GDK_SHIFT_MASK) != 0);
4282 pane->drag.origin_x = pane->drag.last_x;
4283 pane->drag.origin_y = pane->drag.last_y;
4285 if (make_dup) {
4286 GSList *ptr, *objs = go_hash_keys (scg->selected_objects);
4287 GOUndo *nudge_undo = NULL;
4288 GOUndo *nudge_redo = NULL;
4289 double dx, dy;
4291 for (ptr = objs ; ptr != NULL ; ptr = ptr->next) {
4292 SheetObject *dup_obj = sheet_object_dup (ptr->data);
4293 if (dup_obj != NULL) {
4294 sheet_object_set_sheet (dup_obj, scg_sheet (scg));
4295 scg_object_select (scg, dup_obj);
4296 g_object_unref (dup_obj);
4297 scg_object_unselect (scg, ptr->data);
4300 g_slist_free (objs);
4301 scg_objects_drag_commit (scg, 8, TRUE, &undo, &redo, &title);
4302 dx = x - origin_x;
4303 dy = y - origin_y;
4304 scg_objects_drag (scg, pane, NULL, &dx, &dy, 8, FALSE, FALSE, FALSE);
4305 scg_objects_drag_commit (scg, 8, FALSE, &nudge_undo, &nudge_redo, NULL);
4306 undo = go_undo_combine (undo, nudge_undo);
4307 redo = go_undo_combine (nudge_redo, redo);
4308 } else
4309 scg_objects_drag_commit (scg, 8, FALSE, &undo, &redo, &title);
4310 cmd_generic (GNM_WBC (scg_wbcg (scg)), title, undo, redo);
4311 g_free (title);
4312 } else {
4313 GnmCellRegion *content;
4314 GSList *objects;
4316 g_return_if_fail (GNM_IS_SCG (source_scg));
4318 objects = go_hash_keys (source_scg->selected_objects);
4319 content = clipboard_copy_obj (scg_sheet (source_scg),
4320 objects);
4321 if (content != NULL) {
4322 scg_paste_cellregion (scg, x, y, content);
4323 cellregion_unref (content);
4325 g_slist_free (objects);
4329 /* Keep in sync with gtk_selection_data_targets_include_text() */
4330 static gboolean
4331 is_text_target (gchar *target_type)
4333 const gchar *charset;
4334 gchar *text_plain_locale;
4335 gboolean ret;
4337 g_get_charset (&charset);
4338 text_plain_locale = g_strdup_printf ("text/plain;charset=%s", charset);
4339 ret = !strcmp (target_type, "UTF8_STRING") ||
4340 !strcmp (target_type, "COMPOUND_TEXT") ||
4341 !strcmp (target_type, "TEXT") ||
4342 !strcmp (target_type, "STRING") ||
4343 !strcmp (target_type, "text/plain;charset=utf-8") ||
4344 !strcmp (target_type, text_plain_locale) ||
4345 !strcmp (target_type, "text/plain");
4346 g_free (text_plain_locale);
4347 return ret;
4350 void
4351 scg_drag_data_received (SheetControlGUI *scg, GtkWidget *source_widget,
4352 double x, double y, GtkSelectionData *selection_data)
4354 gchar *target_type = gdk_atom_name (gtk_selection_data_get_target (selection_data));
4355 const char *sel_data = (const char *)gtk_selection_data_get_data (selection_data);
4356 gsize sel_len = gtk_selection_data_get_length (selection_data);
4358 if (!strcmp (target_type, "text/uri-list")) {
4359 scg_drag_receive_uri_list (scg, x, y, sel_data, sel_len);
4361 } else if (!strncmp (target_type, "image/", 6)) {
4362 scg_drag_receive_img_data (scg, x, y, sel_data, sel_len);
4363 } else if (!strcmp (target_type, "GNUMERIC_SAME_PROC")) {
4364 scg_drag_receive_same_process (scg, source_widget, x, y);
4365 } else if (!strcmp (target_type, "application/x-gnumeric")) {
4366 scg_drag_receive_cellregion (scg, x, y, sel_data, sel_len);
4367 } else
4368 g_warning ("Unknown target type '%s'!", target_type);
4370 if (gnm_debug_flag ("dnd")) {
4371 if (!strcmp (target_type, "x-special/gnome-copied-files")) {
4372 char *cdata = g_strndup (sel_data, sel_len);
4373 g_print ("data length: %d, data: %s\n",
4374 (int)sel_len, cdata);
4375 g_free (cdata);
4376 } else if (!strcmp (target_type, "_NETSCAPE_URL")) {
4377 char *cdata = g_strndup (sel_data, sel_len);
4378 g_print ("data length: %d, data: %s\n",
4379 (int)sel_len, cdata);
4380 g_free (cdata);
4381 } else if (is_text_target (target_type)) {
4382 char *cdata = g_strndup (sel_data, sel_len);
4383 g_print ("data length: %d, data: %s\n",
4384 (int)sel_len, cdata);
4385 g_free (cdata);
4386 } else if (!strcmp (target_type, "text/html")) {
4387 char *cdata = g_strndup (sel_data, sel_len);
4388 /* For mozilla, need to convert the encoding */
4389 g_print ("data length: %d, data: %s\n", (int)sel_len, cdata);
4390 g_free (cdata);
4394 g_free (target_type);
4397 static void
4398 scg_drag_send_image (G_GNUC_UNUSED SheetControlGUI *scg,
4399 GtkSelectionData *selection_data,
4400 GSList *objects,
4401 gchar const *mime_type)
4403 SheetObject *so = NULL;
4404 GsfOutput *output;
4405 GsfOutputMemory *omem;
4406 gsf_off_t osize;
4407 char *format;
4408 GSList *ptr;
4410 for (ptr = objects; ptr != NULL; ptr = ptr->next) {
4411 if (GNM_IS_SO_IMAGEABLE (GNM_SO (ptr->data))) {
4412 so = GNM_SO (ptr->data);
4413 break;
4416 if (so == NULL) {
4417 g_warning ("non imageable object requested as image\n");
4418 return;
4421 format = go_mime_to_image_format (mime_type);
4422 if (!format) {
4423 g_warning ("No image format for %s\n", mime_type);
4424 g_free (format);
4425 return;
4428 output = gsf_output_memory_new ();
4429 omem = GSF_OUTPUT_MEMORY (output);
4430 sheet_object_write_image (so, format, -1.0, output, NULL);
4431 osize = gsf_output_size (output);
4433 gtk_selection_data_set
4434 (selection_data,
4435 gtk_selection_data_get_target (selection_data),
4436 8, gsf_output_memory_get_bytes (omem), osize);
4437 gsf_output_close (output);
4438 g_object_unref (output);
4439 g_free (format);
4442 static void
4443 scg_drag_send_graph (G_GNUC_UNUSED SheetControlGUI *scg,
4444 GtkSelectionData *selection_data,
4445 GSList *objects,
4446 gchar const *mime_type)
4448 SheetObject *so = NULL;
4449 GsfOutput *output;
4450 GsfOutputMemory *omem;
4451 gsf_off_t osize;
4452 GSList *ptr;
4454 for (ptr = objects; ptr != NULL; ptr = ptr->next)
4455 if (GNM_IS_SO_EXPORTABLE (GNM_SO (ptr->data))) {
4456 so = GNM_SO (ptr->data);
4457 break;
4460 if (so == NULL) {
4461 g_warning ("non exportable object requested\n");
4462 return;
4465 output = gsf_output_memory_new ();
4466 omem = GSF_OUTPUT_MEMORY (output);
4467 sheet_object_write_object (so, mime_type, output, NULL,
4468 gnm_conventions_default);
4469 osize = gsf_output_size (output);
4471 gtk_selection_data_set
4472 (selection_data,
4473 gtk_selection_data_get_target (selection_data),
4474 8, gsf_output_memory_get_bytes (omem), osize);
4475 gsf_output_close (output);
4476 g_object_unref (output);
4479 static void
4480 scg_drag_send_clipboard_objects (SheetControl *sc,
4481 GtkSelectionData *selection_data,
4482 GSList *objects)
4484 GnmCellRegion *content = clipboard_copy_obj (sc_sheet (sc), objects);
4485 GsfOutputMemory *output;
4487 if (content == NULL)
4488 return;
4490 output = gnm_cellregion_to_xml (content);
4491 gtk_selection_data_set
4492 (selection_data,
4493 gtk_selection_data_get_target (selection_data),
4495 gsf_output_memory_get_bytes (output),
4496 gsf_output_size (GSF_OUTPUT (output)));
4497 g_object_unref (output);
4498 cellregion_unref (content);
4501 static void
4502 scg_drag_send_text (SheetControlGUI *scg, GtkSelectionData *sd)
4504 Sheet *sheet = scg_sheet (scg);
4505 GnmRange range = sheet_get_extent (sheet, TRUE, TRUE);
4506 GnmCellRegion *reg = clipboard_copy_range (sheet, &range);
4507 GString *s = cellregion_to_string (reg, TRUE, sheet_date_conv (sheet));
4509 cellregion_unref (reg);
4510 if (!s)
4511 return;
4512 gtk_selection_data_set (sd, gtk_selection_data_get_target (sd),
4513 8, s->str, s->len);
4514 g_string_free (s, TRUE);
4517 void
4518 scg_drag_data_get (SheetControlGUI *scg, GtkSelectionData *selection_data)
4520 GdkAtom target = gtk_selection_data_get_target (selection_data);
4521 gchar *target_name = gdk_atom_name (target);
4522 GSList *objects = scg->selected_objects
4523 ? go_hash_keys (scg->selected_objects)
4524 : NULL;
4526 if (strcmp (target_name, "GNUMERIC_SAME_PROC") == 0)
4527 /* Set dummy selection for process internal dnd */
4528 gtk_selection_data_set (selection_data, target,
4529 8, (const guint8 *)"", 1);
4530 else if (strcmp (target_name, "GNUMERIC_SHEET") == 0)
4531 gtk_selection_data_set (selection_data, target,
4532 8, (void *)scg, sizeof (scg));
4533 else if (strcmp (target_name, "application/x-gnumeric") == 0)
4534 scg_drag_send_clipboard_objects (GNM_SHEET_CONTROL (scg),
4535 selection_data, objects);
4536 else if (strcmp (target_name, "application/x-goffice-graph") == 0)
4537 scg_drag_send_graph (scg, selection_data, objects, target_name);
4538 else if (strncmp (target_name, "image/", 6) == 0)
4539 scg_drag_send_image (scg, selection_data, objects, target_name);
4540 else if (strcmp (target_name, "UTF8_STRING") == 0)
4541 scg_drag_send_text (scg, selection_data);
4543 g_free (target_name);
4544 g_slist_free (objects);
4547 void
4548 scg_delete_sheet_if_possible (SheetControlGUI *scg)
4550 SheetControl *sc = (SheetControl *) scg;
4551 Sheet *sheet = scg_sheet (scg);
4552 Workbook *wb = sheet->workbook;
4554 /* If this is the last sheet left, ignore the request */
4555 if (workbook_sheet_count (wb) != 1) {
4556 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
4557 WorkbookControl *wbc = sc->wbc;
4558 workbook_sheet_delete (sheet);
4559 /* Careful: sc just ceased to be valid. */
4560 cmd_reorganize_sheets (wbc, old_state, sheet);
4564 void
4565 scg_reload_item_edits (SheetControlGUI *scg)
4567 SCG_FOREACH_PANE (scg, pane, {
4568 if (pane->editor != NULL)
4569 goc_item_bounds_changed
4570 (GOC_ITEM (pane->editor));