1 /*
2  * sheet-control-gui.c: Implements a graphic control for a sheet.
3  *
4  * Copyright (C) 2000-2006 Jody Goldberg (jody@gnome.org)
5  * Copyright (C) 1997-1999 Miguel de Icaza (miguel@kernel.org)
6  *
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.
11  *
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.
16  *
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
21  */
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
24 #include <gnumeric.h>
25 #include <sheet-control-gui-priv.h>
26 
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>
59 
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>
66 
67 #include <go-data-slicer-field.h>
68 #include <goffice/goffice.h>
69 
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>
74 
75 #include <string.h>
76 
77 static GObjectClass *scg_parent_class;
78 
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);
82 
83 
84 /**
85  * scg_pane:
86  * @scg: #SheetControlGUI
87  * @pane: the pane index.
88  *
89  * Returns: (transfer none): the pane.
90  **/
91 GnmPane *
scg_pane(SheetControlGUI * scg,int p)92 scg_pane (SheetControlGUI *scg, int p)
93 {
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);
98 
99 	return scg->pane[p];
100 }
101 
102 /**
103  * scg_view:
104  * @scg: #SheetControlGUI
105  *
106  * Returns: (transfer none): the sheet view.
107  **/
108 SheetView *
scg_view(SheetControlGUI const * scg)109 scg_view (SheetControlGUI const *scg)
110 {
111 	g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
112 	return scg->sheet_control.view;
113 }
114 
115 
116 /**
117  * scg_sheet:
118  * @scg: #SheetControlGUI
119  *
120  * Returns: (transfer none): the sheet.
121  **/
122 Sheet *
scg_sheet(SheetControlGUI const * scg)123 scg_sheet (SheetControlGUI const *scg)
124 {
125 	return sc_sheet ((SheetControl *)scg);
126 }
127 
128 
129 /**
130  * scg_wbc:
131  * @scg: #SheetControlGUI
132  *
133  * Returns: (transfer none): the workbook control.
134  **/
135 WorkbookControl *
scg_wbc(SheetControlGUI const * scg)136 scg_wbc (SheetControlGUI const *scg)
137 {
138 	g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
139 	return scg->sheet_control.wbc;
140 }
141 
142 
143 /**
144  * scg_wbcg:
145  * @scg: #SheetControlGUI
146  *
147  * Returns: (transfer none): the #WBCGtk.
148  **/
149 
150 WBCGtk *
scg_wbcg(SheetControlGUI const * scg)151 scg_wbcg (SheetControlGUI const *scg)
152 {
153 	g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
154 	return scg->wbcg;
155 }
156 
157 static void
scg_redraw_all(SheetControl * sc,gboolean headers)158 scg_redraw_all (SheetControl *sc, gboolean headers)
159 {
160 	SheetControlGUI *scg = (SheetControlGUI *)sc;
161 
162 	g_return_if_fail (GNM_IS_SCG (scg));
163 
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);
174 		}
175 	});
176 }
177 
178 static void
scg_redraw_range(SheetControl * sc,GnmRange const * r)179 scg_redraw_range (SheetControl *sc, GnmRange const *r)
180 {
181 	SheetControlGUI *scg = (SheetControlGUI *)sc;
182 	Sheet const *sheet = scg_sheet (scg);
183 	GnmRange visible, area;
184 
185 	/*
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.
189 	 */
190 	if (r->end.row - r->start.row > 500) {
191 		scg_redraw_all (sc, FALSE);
192 		return;
193 	}
194 
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 ();
199 
200 	SCG_FOREACH_PANE (scg, pane, {
201 		visible.start = pane->first;
202 		visible.end = pane->last_visible;
203 
204 		if (range_intersection (&area, r, &visible)) {
205 			sheet_range_bounding_box (sheet, &area);
206 			gnm_pane_redraw_range (pane, &area);
207 		}
208 	};);
209 
210 	gnm_app_recalc_finish ();
211 }
212 
213 static void
scg_redraw_headers(SheetControl * sc,gboolean const col,gboolean const row,GnmRange const * r)214 scg_redraw_headers (SheetControl *sc,
215 		    gboolean const col, gboolean const row,
216 		    GnmRange const * r /* optional == NULL */)
217 {
218 	SheetControlGUI *scg = (SheetControlGUI *)sc;
219 	GnmPane *pane;
220 	int i;
221 	double scale;
222 
223 	/*
224 	 * A rough guess of the trade off point between of redrawing all
225 	 * and calculating the redraw size
226 	 */
227 	const int COL_HEURISTIC = 20;
228 	const int ROW_HEURISTIC = 50;
229 
230 	for (i = scg->active_panes; i-- > 0 ; ) {
231 		if (NULL == (pane = scg->pane[i]))
232 			continue;
233 
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);
238 
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);
248 				}
249 			}
250 			goc_canvas_invalidate (col_canvas,
251 				left / scale, 0, right / scale, G_MAXINT64);
252 		}
253 
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);
266 				}
267 			}
268 			goc_canvas_invalidate (GOC_CANVAS (pane->row.canvas),
269 				0, top / scale, G_MAXINT64, bottom / scale);
270 		}
271 	}
272 }
273 
274 static void
cb_outline_button(GtkWidget * btn,SheetControlGUI * scg)275 cb_outline_button (GtkWidget *btn, SheetControlGUI *scg)
276 {
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;
282 
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;
288 
289 	g_return_if_fail (i < btns->len);
290 
291 	cmd_global_outline_change (wbc, is_cols, i+1);
292 }
293 
294 static void
scg_setup_group_buttons(SheetControlGUI * scg,unsigned max_outline,GnmItemBar const * ib,gboolean is_cols,int w,int h,GPtrArray * btns,GtkWidget * box)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)
298 {
299 	PangoFontDescription *font_desc;
300 	unsigned i;
301 	Sheet const *sheet = scg_sheet (scg);
302 
303 	if (!sheet->display_outlines)
304 		max_outline = 0;
305 	else if (max_outline > 0)
306 		max_outline++;
307 
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));
312 	}
313 
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);
322 
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);
329 
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));
336 	}
337 
338 	font_desc = item_bar_normal_font (ib);
339 
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);
346 	}
347 
348 	pango_font_description_free (font_desc);
349 	gtk_widget_show_all (box);
350 }
351 
352 static void
scg_resize(SheetControlGUI * scg,G_GNUC_UNUSED gboolean force_scroll)353 scg_resize (SheetControlGUI *scg, G_GNUC_UNUSED gboolean force_scroll)
354 {
355 	Sheet const *sheet = scg_sheet (scg);
356 	GnmPane *pane = scg_pane (scg, 0);
357 	int h, w, btn_h, btn_w, tmp;
358 
359 	if (!pane)
360 		return;
361 
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);
367 
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);
376 
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);
385 
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);
400 
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);
409 			}
410 		}
411 
412 		if (scg->pane[1]) {
413 			if (gnm_debug_flag ("frozen-panes"))
414 				g_printerr ("Pane 1: %d\n", r - l);
415 
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.
422 			 */
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);
425 		}
426 
427 		if (scg->pane[3]) {
428 			if (gnm_debug_flag ("frozen-panes"))
429 				g_printerr ("Pane 2: %d\n", b - t);
430 
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);
435 		}
436 
437 		if (scg->pane[2]) {
438 			if (gnm_debug_flag ("frozen-panes"))
439 				g_printerr ("Pane 3: %d %d\n", r - l, b - t);
440 
441 			gtk_widget_set_size_request (GTK_WIDGET (scg->pane[2]), fw, fh);
442 		}
443 	}
444 
445 	SCG_FOREACH_PANE (scg, pane, {
446 			gnm_pane_reposition_cursors (pane);
447 	});
448 }
449 
450 static void
scg_resize_virt(SheetControl * sc,gboolean force_scroll)451 scg_resize_virt (SheetControl *sc, gboolean force_scroll)
452 {
453 	scg_resize ((SheetControlGUI *)sc, force_scroll);
454 }
455 
456 static void
gnm_adjustment_configure(GtkAdjustment * adjustment,gdouble value,gdouble lower,gdouble upper,gdouble step_increment,gdouble page_increment,gdouble page_size)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)
464 {
465 	g_object_freeze_notify (G_OBJECT (adjustment));
466 
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);
473 
474 	g_object_thaw_notify (G_OBJECT (adjustment));
475 
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);
479 
480 }
481 
482 /**
483  * scg_scrollbar_config:
484  * @sc:
485  *
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.
489  *
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.
492  *
493  * 2) It does NOT take into account hidden rows/cols  So a region that contains
494  * a large hidden segment will appear larger.
495  *
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.
498  */
499 static gboolean
scg_scrollbar_config_real(SheetControl const * sc)500 scg_scrollbar_config_real (SheetControl const *sc)
501 {
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;
508 
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;
514 
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,
524 			 1,
525 			 MAX (gtk_adjustment_get_page_size (va) - 3.0, 1.0),
526 			 last_row - pane->first.row + 1);
527 
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,
537 			 1,
538 			 MAX (gtk_adjustment_get_page_size (ha) - 3.0, 1.0),
539 			 last_col - pane->first.col + 1);
540 	}
541 
542 	scg->scroll_bar_timer = 0;
543 	return FALSE;
544 }
545 
546 
547 static void
scg_scrollbar_config(SheetControl * sc)548 scg_scrollbar_config (SheetControl *sc)
549 {
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);
557 }
558 
559 void
scg_colrow_size_set(SheetControlGUI * scg,gboolean is_cols,int index,int new_size_pixels)560 scg_colrow_size_set (SheetControlGUI *scg,
561 		     gboolean is_cols, int index, int new_size_pixels)
562 {
563 	WorkbookControl *wbc = scg_wbc (scg);
564 	SheetView *sv = scg_view (scg);
565 
566 	/* If all cols/rows in the selection are completely selected
567 	 * then resize all of them, otherwise just resize the selected col/row.
568 	 */
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);
575 }
576 
577 void
scg_select_all(SheetControlGUI * scg)578 scg_select_all (SheetControlGUI *scg)
579 {
580 	Sheet *sheet = scg_sheet (scg);
581 	gboolean const rangesel = wbcg_rangesel_possible (scg->wbcg);
582 
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);
590 
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);
598 	}
599 	sheet_update (sheet);
600 }
601 
602 gboolean
scg_colrow_select(SheetControlGUI * scg,gboolean is_cols,int index,int modifiers)603 scg_colrow_select (SheetControlGUI *scg, gboolean is_cols,
604 		   int index, int modifiers)
605 {
606 	SheetView *sv = scg_view (scg);
607 	gboolean const rangesel = wbcg_rangesel_possible (scg->wbcg);
608 
609 	if (!rangesel &&
610 	    !wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
611 		return FALSE;
612 
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);
624 		}
625 	} else {
626 		if (!rangesel && !(modifiers & GDK_CONTROL_MASK))
627 			sv_selection_reset (sv);
628 
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);
652 		}
653 	}
654 
655 	/* The edit pos, and the selection may have changed */
656 	if (!rangesel)
657 		sheet_update (sv->sheet);
658 	return TRUE;
659 }
660 
661 /***************************************************************************/
662 
663 static void
cb_select_all_btn_draw(GtkWidget * widget,cairo_t * cr,SheetControlGUI * scg)664 cb_select_all_btn_draw (GtkWidget *widget, cairo_t *cr, SheetControlGUI *scg)
665 {
666 	int offset = scg_sheet (scg)->text_is_rtl ? -1 : 0;
667 	GtkAllocation a;
668 	GtkStyleContext *ctxt = gtk_widget_get_style_context (widget);
669 
670 	gtk_widget_get_allocation (widget, &a);
671 
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);
678 }
679 
680 static gboolean
cb_select_all_btn_event(G_GNUC_UNUSED GtkWidget * widget,GdkEvent * event,SheetControlGUI * scg)681 cb_select_all_btn_event (G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event, SheetControlGUI *scg)
682 {
683 	if (event->type == GDK_BUTTON_PRESS) {
684 		scg_select_all (scg);
685 		return TRUE;
686 	}
687 
688 	return FALSE;
689 }
690 
691 static void
cb_vscrollbar_value_changed(GtkRange * range,SheetControlGUI * scg)692 cb_vscrollbar_value_changed (GtkRange *range, SheetControlGUI *scg)
693 {
694 	GtkAdjustment *adj = gtk_range_get_adjustment (range);
695 	scg_set_top_row (scg, gtk_adjustment_get_value (adj));
696 }
697 
698 static void
cb_hscrollbar_value_changed(GtkRange * range,SheetControlGUI * scg)699 cb_hscrollbar_value_changed (GtkRange *range, SheetControlGUI *scg)
700 {
701 	GtkAdjustment *adj = gtk_range_get_adjustment (range);
702 	scg_set_left_col (scg, gtk_adjustment_get_value (adj));
703 }
704 
705 static void
cb_hscrollbar_adjust_bounds(GtkRange * range,gdouble new_value,Sheet * sheet)706 cb_hscrollbar_adjust_bounds (GtkRange *range, gdouble new_value, Sheet *sheet)
707 {
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);
717 	}
718 }
719 static void
cb_vscrollbar_adjust_bounds(GtkRange * range,gdouble new_value,Sheet * sheet)720 cb_vscrollbar_adjust_bounds (GtkRange *range, gdouble new_value, Sheet *sheet)
721 {
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);
731 	}
732 }
733 
734 static void
cb_table_destroy(SheetControlGUI * scg)735 cb_table_destroy (SheetControlGUI *scg)
736 {
737 	SheetControl *sc = (SheetControl *) scg;
738 	int i;
739 
740 	g_clear_object (&scg->grid);
741 
742 	scg_mode_edit (scg);	/* finish any object edits */
743 	scg_unant (sc);		/* Make sure that everything is unanted */
744 
745 	if (scg->wbcg) {
746 		GtkWindow *toplevel = wbcg_toplevel (scg->wbcg);
747 
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);
752 	}
753 
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;
758 		}
759 
760 	g_object_unref (scg);
761 }
762 
763 static void
scg_init(SheetControlGUI * scg)764 scg_init (SheetControlGUI *scg)
765 {
766 	scg->comment.selected = NULL;
767 	scg->comment.item = NULL;
768 	scg->comment.timer = 0;
769 
770 	scg->delayedMovement.timer = 0;
771 	scg->delayedMovement.handler = NULL;
772 
773 	scg->grab_stack = 0;
774 	scg->selected_objects = NULL;
775 
776 	scg->im.item = NULL;
777 	scg->im.timer = 0;
778 
779 	// These shouldn't matter and will be overwritten
780 	scg->screen_width = 1920;
781 	scg->screen_height = 1200;
782 }
783 
784 /*************************************************************************/
785 
786 /**
787  * gnm_pane_update_inital_top_left:
788  * A convenience routine to store the new topleft back in the view.
789  */
790 static void
gnm_pane_update_inital_top_left(GnmPane const * pane)791 gnm_pane_update_inital_top_left (GnmPane const *pane)
792 {
793 	if (pane->index == 0) {
794 		SheetView *sv = scg_view (pane->simple.scg);
795 		sv->initial_top_left = pane->first;
796 	}
797 }
798 
799 static gint64
bar_set_left_col(GnmPane * pane,int new_first_col)800 bar_set_left_col (GnmPane *pane, int new_first_col)
801 {
802 	GocCanvas *colc;
803 	gint64 col_offset;
804 
805 
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;
809 
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);
813 
814 	return col_offset;
815 }
816 
817 static void
gnm_pane_set_left_col(GnmPane * pane,int new_first_col)818 gnm_pane_set_left_col (GnmPane *pane, int new_first_col)
819 {
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));
824 
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);
828 
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);
832 	}
833 }
834 
835 void
scg_set_left_col(SheetControlGUI * scg,int col)836 scg_set_left_col (SheetControlGUI *scg, int col)
837 {
838 	Sheet const *sheet;
839 	GnmRange const *bound;
840 
841 	g_return_if_fail (GNM_IS_SCG (scg));
842 
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;
851 
852 	if (scg->pane[1]) {
853 		int right = scg_view (scg)->unfrozen_top_left.col;
854 		if (col < right)
855 			col = right;
856 	}
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);
860 }
861 
862 static gint64
bar_set_top_row(GnmPane * pane,int new_first_row)863 bar_set_top_row (GnmPane *pane, int new_first_row)
864 {
865 	GocCanvas *rowc;
866 	gint64 row_offset;
867 
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;
871 
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);
875 
876 	return row_offset;
877 }
878 
879 static void
gnm_pane_set_top_row(GnmPane * pane,int new_first_row)880 gnm_pane_set_top_row (GnmPane *pane, int new_first_row)
881 {
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));
886 
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;
891 
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);
895 	}
896 }
897 
898 void
scg_set_top_row(SheetControlGUI * scg,int row)899 scg_set_top_row (SheetControlGUI *scg, int row)
900 {
901 	Sheet const *sheet;
902 	GnmRange const *bound;
903 
904 	g_return_if_fail (GNM_IS_SCG (scg));
905 
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;
914 
915 	if (scg->pane[3]) {
916 		int bottom = scg_view (scg)->unfrozen_top_left.row;
917 		if (row < bottom)
918 			row = bottom;
919 	}
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);
923 }
924 
925 static void
gnm_pane_set_top_left(GnmPane * pane,int col,int row,gboolean force_scroll)926 gnm_pane_set_top_left (GnmPane *pane,
927 			 int col, int row, gboolean force_scroll)
928 {
929 	gboolean changed = FALSE;
930 	gint64 col_offset, row_offset;
931 	GocCanvas *canvas;
932 
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)));
937 
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;
943 		}
944 
945 		col_offset = bar_set_left_col (pane, col);
946 		changed = TRUE;
947 	} else {
948 		col_offset = pane->first_offset.x;
949 	}
950 
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;
956 		}
957 		row_offset = bar_set_top_row (pane, row);
958 		changed = TRUE;
959 	} else
960 		row_offset = pane->first_offset.y;
961 
962 	if (!changed)
963 		return;
964 
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);
969 }
970 
971 static void
scg_set_top_left(SheetControl * sc,int col,int row)972 scg_set_top_left (SheetControl *sc, int col, int row)
973 {
974 	SheetControlGUI *scg = (SheetControlGUI *)sc;
975 
976 	g_return_if_fail (GNM_IS_SCG (scg));
977 
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);
983 }
984 
985 static void
gnm_pane_make_cell_visible(GnmPane * pane,int col,int row,gboolean const force_scroll)986 gnm_pane_make_cell_visible (GnmPane *pane, int col, int row,
987 			    gboolean const force_scroll)
988 {
989 	GocCanvas *canvas;
990 	Sheet *sheet;
991 	int   new_first_col, new_first_row;
992 	GnmRange range;
993 	GtkAllocation ca;
994 
995 	g_return_if_fail (GNM_IS_PANE (pane));
996 
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.
1000 	 */
1001 	if (!gtk_widget_get_realized (GTK_WIDGET (pane)))
1002 		return;
1003 
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));
1009 
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);
1014 
1015 	gtk_widget_get_allocation (GTK_WIDGET (canvas), &ca);
1016 
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;
1026 
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;
1033 				}
1034 			}
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;
1042 
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;
1052 
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;
1059 				}
1060 			}
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;
1068 
1069 	gnm_pane_set_top_left (pane, new_first_col, new_first_row,
1070 				 force_scroll);
1071 }
1072 
1073 /**
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.
1081  *
1082  * Ensure that cell (col, row) is visible.
1083  * Sheet is scrolled if cell is outside viewport.
1084  */
1085 void
scg_make_cell_visible(SheetControlGUI * scg,int col,int row,gboolean force_scroll,gboolean couple_panes)1086 scg_make_cell_visible (SheetControlGUI *scg, int col, int row,
1087 		       gboolean force_scroll, gboolean couple_panes)
1088 {
1089 	SheetView const *sv = scg_view (scg);
1090 	GnmCellPos const *tl, *br;
1091 
1092 	g_return_if_fail (GNM_IS_SCG (scg));
1093 
1094 	if (!scg->active_panes)
1095 		return;
1096 
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.
1118 			 */
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);
1124 		}
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);
1148 	}
1149 	if (scg->pane[2])
1150 		gnm_pane_set_top_left (scg->pane[2],
1151 			tl->col, tl->row, force_scroll);
1152 }
1153 
1154 static void
scg_make_cell_visible_virt(SheetControl * sc,int col,int row,gboolean couple_panes)1155 scg_make_cell_visible_virt (SheetControl *sc, int col, int row,
1156 			    gboolean couple_panes)
1157 {
1158 	scg_make_cell_visible ((SheetControlGUI *)sc, col, row,
1159 			       FALSE, couple_panes);
1160 }
1161 
1162 /*************************************************************************/
1163 
1164 static void
scg_set_panes(SheetControl * sc)1165 scg_set_panes (SheetControl *sc)
1166 {
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;
1171 
1172 	g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
1173 
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;
1181 
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));
1185 
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);
1197 			}
1198 			gnm_pane_bound_set (scg->pane[1],
1199 				tl->col, br->row, br->col - 1, gnm_sheet_get_last_row (sv->sheet));
1200 		}
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);
1209 			}
1210 			gnm_pane_bound_set (scg->pane[2],
1211 				tl->col, tl->row, br->col - 1, br->row - 1);
1212 		}
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);
1224 			}
1225 			gnm_pane_bound_set (scg->pane[3],
1226 				br->col, tl->row, gnm_sheet_get_last_col (sv->sheet), br->row - 1);
1227 		}
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;
1234 			}
1235 
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));
1239 	}
1240 
1241 	gtk_widget_show_all (GTK_WIDGET (scg->grid));
1242 
1243 	/* in case headers are hidden */
1244 	scg_adjust_preferences (scg);
1245 	scg_resize (scg, TRUE);
1246 
1247 	if (being_frozen) {
1248 		GnmCellPos const *tl = &sc->view->frozen_top_left;
1249 
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);
1256 	}
1257 	set_resize_pane_pos (scg, scg->vpane);
1258 	set_resize_pane_pos (scg, scg->hpane);
1259 }
1260 
1261 static void
cb_wbc_destroyed(SheetControlGUI * scg)1262 cb_wbc_destroyed (SheetControlGUI *scg)
1263 {
1264 	scg->wbcg = NULL;
1265 	scg->sheet_control.wbc = NULL;
1266 }
1267 
1268 static void
cb_scg_redraw(SheetControlGUI * scg)1269 cb_scg_redraw (SheetControlGUI *scg)
1270 {
1271 	scg_adjust_preferences (scg);
1272 	scg_redraw_all (&scg->sheet_control, TRUE);
1273 }
1274 
1275 static void
cb_scg_redraw_resize(SheetControlGUI * scg)1276 cb_scg_redraw_resize (SheetControlGUI *scg)
1277 {
1278 	cb_scg_redraw (scg);
1279 	scg_resize (scg, FALSE);
1280 }
1281 
1282 static void
cb_scg_sheet_resized(SheetControlGUI * scg)1283 cb_scg_sheet_resized (SheetControlGUI *scg)
1284 {
1285 	cb_scg_redraw_resize (scg);
1286 	sc_set_panes (&scg->sheet_control);
1287 }
1288 
1289 static void
cb_scg_direction_changed(SheetControlGUI * scg)1290 cb_scg_direction_changed (SheetControlGUI *scg)
1291 {
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);
1299 	}
1300 	scg_resize (scg, TRUE);
1301 }
1302 
1303 static GnmPane const *
resize_pane_pos(SheetControlGUI * scg,GtkPaned * p,int * colrow_result,gint64 * guide_pos)1304 resize_pane_pos (SheetControlGUI *scg, GtkPaned *p,
1305 		 int *colrow_result, gint64 *guide_pos)
1306 {
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);
1312 
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;
1320 		}
1321 		if (scg->pane[1]) {
1322 			GtkAllocation pa;
1323 			gtk_widget_get_allocation (GTK_WIDGET (scg->pane[1]),
1324 						   &pa);
1325 
1326 			if (pos < pa.width)
1327 				pane = scg_pane (scg, 1);
1328 			else
1329 				pos -= pa.width;
1330 		}
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;
1339 		}
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;
1348 		}
1349 		pos = MAX (pos, 0);
1350 		pos += pane->first_offset.y;
1351 		colrow = gnm_pane_find_row (pane, pos, guide_pos);
1352 	}
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++;
1357 	}
1358 	if (NULL != colrow_result)
1359 		*colrow_result = colrow;
1360 
1361 	return pane;
1362 }
1363 
1364 static void
scg_gtk_paned_set_position(SheetControlGUI * scg,GtkPaned * p,int pane_pos)1365 scg_gtk_paned_set_position (SheetControlGUI *scg, GtkPaned *p, int pane_pos)
1366 {
1367 	/* A negative position is special to GtkPaned.  */
1368 	pane_pos = MAX (pane_pos, 0);
1369 
1370 	if (p == scg->vpane)
1371 		scg->vpos = pane_pos;
1372 	else
1373 		scg->hpos = pane_pos;
1374 
1375 	gtk_paned_set_position (p, pane_pos);
1376 }
1377 
1378 static void
set_resize_pane_pos(SheetControlGUI * scg,GtkPaned * p)1379 set_resize_pane_pos (SheetControlGUI *scg, GtkPaned *p)
1380 {
1381 	int handle_size, pane_pos, size;
1382 	GnmPane *pane0 = scg->pane[0];
1383 
1384 	if (!pane0)
1385 		return;
1386 
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;
1398 		}
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;
1410 		}
1411 	}
1412 	gtk_widget_style_get (GTK_WIDGET (p), "handle-size", &handle_size, NULL);
1413 	pane_pos -= handle_size / 2;
1414 
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);
1420 }
1421 
1422 static void
1423 cb_check_resize (GtkPaned *p, GtkAllocation *allocation, SheetControlGUI *scg);
1424 
1425 static gboolean
resize_pane_finish(SheetControlGUI * scg,GtkPaned * p)1426 resize_pane_finish (SheetControlGUI *scg, GtkPaned *p)
1427 {
1428 	SheetView *sv =	scg_view (scg);
1429 	GnmCellPos frozen_tl, unfrozen_tl;
1430 	GnmPane const *pane;
1431 	int colrow;
1432 	gint64 guide_pos;
1433 
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);
1440 
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;
1454 	}
1455 	gnm_sheet_view_freeze_panes	(sv, &frozen_tl, &unfrozen_tl);
1456 
1457 	scg->pane_drag_handler = 0;
1458 	scg_size_guide_stop (scg);
1459 
1460 	set_resize_pane_pos (scg, p);
1461 
1462 	g_signal_handlers_unblock_by_func
1463 		(G_OBJECT (p),
1464 		 G_CALLBACK (cb_check_resize), scg);
1465 
1466 	return FALSE;
1467 }
1468 static gboolean
cb_resize_vpane_finish(SheetControlGUI * scg)1469 cb_resize_vpane_finish (SheetControlGUI *scg)
1470 {
1471 	return resize_pane_finish (scg, scg->vpane);
1472 }
1473 static gboolean
cb_resize_hpane_finish(SheetControlGUI * scg)1474 cb_resize_hpane_finish (SheetControlGUI *scg)
1475 {
1476 	return resize_pane_finish (scg, scg->hpane);
1477 }
1478 
1479 static void
cb_resize_pane_motion(GtkPaned * p,G_GNUC_UNUSED GParamSpec * pspec,SheetControlGUI * scg)1480 cb_resize_pane_motion (GtkPaned *p,
1481 		       G_GNUC_UNUSED GParamSpec *pspec,
1482 		       SheetControlGUI *scg)
1483 {
1484 	gboolean const vert = (p == scg->hpane);
1485 	int colrow;
1486 	gint64 guide_pos;
1487 
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);
1499 	}
1500 	if (scg->pane_drag_handler)
1501 		scg_size_guide_motion (scg, vert, guide_pos);
1502 
1503 }
1504 
1505 static void
cb_check_resize(GtkPaned * p,G_GNUC_UNUSED GtkAllocation * allocation,SheetControlGUI * scg)1506 cb_check_resize (GtkPaned *p, G_GNUC_UNUSED GtkAllocation *allocation,
1507 		 SheetControlGUI *scg)
1508 {
1509 	gboolean const vert = (p == scg->vpane);
1510 	gint max, pos = vert ? scg->vpos : scg->hpos;
1511 
1512 	g_object_get (G_OBJECT (p), "max-position", &max, NULL);
1513 	if (pos > max)
1514 		pos = max;
1515 
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);
1524 	}
1525 }
1526 
1527 struct resize_closure {
1528 	GtkPaned *p;
1529 	SheetControlGUI *scg;
1530 };
1531 
1532 static gboolean
idle_resize(struct resize_closure * r)1533 idle_resize (struct resize_closure *r)
1534 {
1535 
1536 	set_resize_pane_pos (r->scg, r->p);
1537 	g_free (r);
1538 	return FALSE;
1539 }
1540 
1541 static void
cb_canvas_resize(GtkWidget * w,G_GNUC_UNUSED GtkAllocation * allocation,SheetControlGUI * scg)1542 cb_canvas_resize (GtkWidget *w, G_GNUC_UNUSED GtkAllocation *allocation,
1543 		 SheetControlGUI *scg)
1544 {
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);
1550 }
1551 
1552 static gboolean
post_create_cb(SheetControlGUI * scg)1553 post_create_cb (SheetControlGUI *scg)
1554 {
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;
1559 }
1560 
1561 static gboolean
sheet_object_key_pressed(G_GNUC_UNUSED GtkWidget * w,GdkEventKey * event,SheetControlGUI * scg)1562 sheet_object_key_pressed (G_GNUC_UNUSED GtkWidget *w, GdkEventKey *event, SheetControlGUI *scg)
1563 {
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;
1574 
1575 				if (old_pos > 0){
1576 					workbook_sheet_move(sheet, -1);
1577 					cmd_reorganize_sheets (wbc, old_state, sheet);
1578 				}
1579 			} else {
1580 				gnm_notebook_prev_page (scg->wbcg->bnotebook);
1581 			}
1582 			return FALSE;
1583 		}
1584 		break;
1585 	case GDK_KEY_KP_Page_Down:
1586 	case GDK_KEY_Page_Down:
1587 
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;
1593 
1594 				if (old_pos < num_sheets - 1){
1595 					workbook_sheet_move(sheet, 1);
1596 					cmd_reorganize_sheets (wbc, old_state, sheet);
1597 				}
1598 			} else {
1599 				gnm_notebook_next_page (scg->wbcg->bnotebook);
1600 			}
1601 			return FALSE;
1602 		}
1603 		break;
1604 	}
1605 	return TRUE;
1606 }
1607 
1608 static void
cb_screen_changed(GtkWidget * widget,G_GNUC_UNUSED GdkScreen * prev,SheetControlGUI * scg)1609 cb_screen_changed (GtkWidget *widget, G_GNUC_UNUSED GdkScreen *prev,
1610 		   SheetControlGUI *scg)
1611 {
1612 	GdkScreen *screen = gtk_widget_get_screen (widget);
1613 
1614 	if (screen) {
1615 		scg->screen_width = gdk_screen_get_width (screen);
1616 		scg->screen_height = gdk_screen_get_height (screen);
1617 	}
1618 }
1619 
1620 SheetControlGUI *
sheet_control_gui_new(SheetView * sv,WBCGtk * wbcg)1621 sheet_control_gui_new (SheetView *sv, WBCGtk *wbcg)
1622 {
1623 	SheetControlGUI *scg;
1624 	Sheet *sheet;
1625 	GocDirection direction;
1626 	GdkRGBA cfore, cback;
1627 
1628 	g_return_val_if_fail (GNM_IS_SHEET_VIEW (sv), NULL);
1629 
1630 	sheet = sv_sheet (sv);
1631 	direction = (sheet->text_is_rtl)? GOC_DIRECTION_RTL: GOC_DIRECTION_LTR;
1632 
1633 	scg = g_object_new (GNM_SCG_TYPE, NULL);
1634 	scg->wbcg = wbcg;
1635 	scg->sheet_control.wbc = GNM_WBC (wbcg);
1636 
1637 	g_object_weak_ref (G_OBJECT (wbcg),
1638 		(GWeakNotify) cb_wbc_destroyed,
1639 		scg);
1640 
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;
1648 
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);
1671 
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);
1678 
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);
1698 
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);
1710 
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);
1721 
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);
1746 
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);
1755 
1756 		gnm_sheet_view_attach_control (sv, GNM_SHEET_CONTROL (scg));
1757 
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);
1790 		}
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);
1799 			}
1800 		}
1801 	}
1802 
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);
1819 
1820 	g_signal_connect (G_OBJECT (scg->grid),
1821 			  "screen-changed",
1822 			  G_CALLBACK (cb_screen_changed),
1823 			  scg);
1824 
1825 	return scg;
1826 }
1827 
1828 static void
scg_comment_timer_clear(SheetControlGUI * scg)1829 scg_comment_timer_clear (SheetControlGUI *scg)
1830 {
1831 	if (scg->comment.timer != 0) {
1832 		g_source_remove (scg->comment.timer);
1833 		scg->comment.timer = 0;
1834 	}
1835 }
1836 
1837 static void
scg_im_destroy(SheetControlGUI * scg)1838 scg_im_destroy (SheetControlGUI *scg) {
1839 	if (scg->im.timer != 0) {
1840 		g_source_remove (scg->im.timer);
1841 		scg->im.timer = 0;
1842 	}
1843 	if (scg->im.item) {
1844 		gtk_widget_destroy (scg->im.item);
1845 		scg->im.item = NULL;
1846 	}
1847 }
1848 
1849 static void
scg_finalize(GObject * object)1850 scg_finalize (GObject *object)
1851 {
1852 	SheetControlGUI *scg = GNM_SCG (object);
1853 	SheetControl	*sc = (SheetControl *) scg;
1854 	Sheet		*sheet = scg_sheet (scg);
1855 	GSList *ptr;
1856 
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));
1864 			);
1865 
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);
1869 	}
1870 
1871 	if (scg->pane_drag_handler) {
1872 		g_source_remove (scg->pane_drag_handler);
1873 		scg->pane_drag_handler = 0;
1874 	}
1875 
1876 	if (scg->scroll_bar_timer) {
1877 		g_source_remove (scg->scroll_bar_timer);
1878 		scg->scroll_bar_timer = 0;
1879 	}
1880 
1881 	scg_comment_timer_clear (scg);
1882 
1883 	if (scg->delayedMovement.timer != 0) {
1884 		g_source_remove (scg->delayedMovement.timer);
1885 		scg->delayedMovement.timer = 0;
1886 	}
1887 	scg_comment_unselect (scg, scg->comment.selected);
1888 
1889 	scg_im_destroy (scg);
1890 
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);
1899 	}
1900 
1901 	if (scg->grid) {
1902 		gtk_widget_destroy (GTK_WIDGET (scg->grid));
1903 		g_object_unref (scg->grid);
1904 		scg->grid = NULL;
1905 	}
1906 
1907 	g_clear_object (&scg->label);
1908 
1909 	if (scg->wbcg != NULL)
1910 		g_object_weak_unref (G_OBJECT (scg->wbcg),
1911 			(GWeakNotify) cb_wbc_destroyed,
1912 			scg);
1913 
1914 	(*scg_parent_class->finalize) (object);
1915 }
1916 
1917 static void
scg_unant(SheetControl * sc)1918 scg_unant (SheetControl *sc)
1919 {
1920 	SheetControlGUI *scg = (SheetControlGUI *)sc;
1921 
1922 	g_return_if_fail (GNM_IS_SCG (scg));
1923 
1924 	/* Always have a pane 0 */
1925 	if (scg->active_panes == 0  || scg->pane[0]->cursor.animated == NULL)
1926 		return;
1927 
1928 	SCG_FOREACH_PANE (scg, pane, {
1929 		GSList *l;
1930 
1931 		for (l = pane->cursor.animated; l; l = l->next) {
1932 			GocItem *item = l->data;
1933 			goc_item_destroy (item);
1934 		}
1935 
1936 		g_slist_free (pane->cursor.animated);
1937 		pane->cursor.animated = NULL;
1938 	});
1939 }
1940 
1941 static void
scg_ant(SheetControl * sc)1942 scg_ant (SheetControl *sc)
1943 {
1944 	SheetControlGUI *scg = (SheetControlGUI *)sc;
1945 	GList *l;
1946 
1947 	g_return_if_fail (GNM_IS_SCG (scg));
1948 
1949 	if (scg->active_panes == 0)
1950 		return;
1951 
1952 	/* Always have a grid 0 */
1953 	if (NULL != scg->pane[0]->cursor.animated)
1954 		scg_unant (sc);
1955 
1956 	for (l = sc->view->ants; l; l = l->next) {
1957 		GnmRange const *r = l->data;
1958 
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);
1969 		});
1970 	}
1971 }
1972 
1973 void
scg_adjust_preferences(SheetControlGUI * scg)1974 scg_adjust_preferences (SheetControlGUI *scg)
1975 {
1976 	Sheet const *sheet = scg_sheet (scg);
1977 
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);
1982 		}
1983 
1984 		if (pane->row.canvas != NULL) {
1985 			gtk_widget_set_visible (GTK_WIDGET (pane->row.canvas),
1986 						!sheet->hide_row_header);
1987 		}
1988 	});
1989 
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);
1996 
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);
2001 
2002 			gtk_widget_set_visible (scg->vs,
2003 						wbv->show_vertical_scrollbar);
2004 		}
2005 	}
2006 }
2007 
2008 /***************************************************************************/
2009 
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 */
2040 };
2041 static void
context_menu_handler(GnmPopupMenuElement const * element,gpointer user_data)2042 context_menu_handler (GnmPopupMenuElement const *element,
2043 		      gpointer user_data)
2044 {
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;
2051 
2052 	g_return_if_fail (element != NULL);
2053 	g_return_if_fail (IS_SHEET (sheet));
2054 
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);
2096 	}
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);
2103 
2104 	}
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;
2143 
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;
2150 
2151 		for (l = scg_view (scg)->selections; l != NULL; l = l->next) {
2152 			GnmRange const *r = l->data;
2153 			GnmStyleList *styles;
2154 
2155 			styles = sheet_style_collect_hlinks (sheet, r);
2156 			n_links += g_slist_length (styles);
2157 			style_list_free (styles);
2158 		}
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;
2165 	}
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;
2172 
2173 	default:
2174 		break;
2175 	}
2176 }
2177 
2178 void
scg_context_menu(SheetControlGUI * scg,GdkEvent * event,gboolean is_col,gboolean is_row)2179 scg_context_menu (SheetControlGUI *scg, GdkEvent *event,
2180 		  gboolean is_col, gboolean is_row)
2181 {
2182 	SheetView *sv	 = scg_view (scg);
2183 	Sheet	  *sheet = sv_sheet (sv);
2184 
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
2198 	};
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
2209 	};
2210 
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
2242 	};
2243 
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 },
2253 
2254 		{ "", NULL, 0, 0, 0, NULL },
2255 
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 },
2278 
2279 		{ N_("Clear Co_ntents"), "edit-clear",
2280 		    0, 0, CONTEXT_CLEAR_CONTENT, NULL },
2281 
2282 		{ "", NULL, CONTEXT_DISPLAY_FOR_CELLS, 0, 0, NULL },
2283 
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 },
2290 
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 },
2300 
2301 		{ "", NULL, 0, 0, 0, NULL },
2302 
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 },
2309 
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 */
2328 
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 */
2341 
2342 
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 */
2350 
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 */
2358 
2359 		{ NULL, NULL, 0, 0, 0, NULL },
2360 	};
2361 
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);
2367 
2368 	/* Paste special only applies to local copies, not cuts, or remote
2369 	 * items
2370 	 */
2371 	int sensitivity_filter =
2372 		(!gnm_app_clipboard_is_empty () &&
2373 		!gnm_app_clipboard_is_cut ())
2374 		? 0 : CONTEXT_DISABLE_PASTE_SPECIAL;
2375 
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;
2383 
2384 	wbcg_edit_finish (scg->wbcg, WBC_EDIT_REJECT, NULL);
2385 
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
2389 	 */
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);
2398 
2399 		n_sel++;
2400 
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);
2409 			}
2410 		}
2411 
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;
2418 
2419 
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;
2426 
2427 		if (!(rfull_h || rfull_v))
2428 			sensitivity_filter |= CONTEXT_DISABLE_FOR_CELLS;
2429 
2430 		full_sheet = full_sheet || (rfull_h && rfull_v);
2431 
2432 		h = range_height (r);
2433 		w = range_width (r);
2434 		n_cols += w;
2435 		n_rows += h;
2436 		n_cells += w * h;
2437 
2438 		styles = sheet_style_collect_hlinks (sheet, r);
2439 		n_links += g_slist_length (styles);
2440 		style_list_free (styles);
2441 
2442 		objs = sheet_objects_get (sheet, r, GNM_CELL_COMMENT_TYPE);
2443 		n_comments += g_slist_length (objs);
2444 		g_slist_free (objs);
2445 	}
2446 
2447 	if (only_merges)
2448 		sensitivity_filter |= CONTEXT_DISABLE_FOR_ONLYMERGES;
2449 	if (no_merges)
2450 		sensitivity_filter |= CONTEXT_DISABLE_FOR_NOMERGES;
2451 
2452 
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;
2458 
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));
2462 
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;
2472 		}
2473 		display_filter |= CONTEXT_DISPLAY_WITH_DATA_SLICER;
2474 		display_filter &= ~CONTEXT_DISPLAY_FOR_CELLS;
2475 	}
2476 
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);
2491 		}
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);
2496 		}
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);
2501 	}
2502 
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);
2513 		}
2514 	}
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);
2521 
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);
2526 		}
2527 	}
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);
2532 	}
2533 
2534 
2535 	gnm_create_popup_menu (popup_elements,
2536 			       &context_menu_handler, scg, NULL,
2537 			       display_filter, sensitivity_filter, event);
2538 }
2539 
2540 static gboolean
cb_redraw_sel(G_GNUC_UNUSED SheetView * sv,GnmRange const * r,gpointer user_data)2541 cb_redraw_sel (G_GNUC_UNUSED SheetView *sv, GnmRange const *r, gpointer user_data)
2542 {
2543 	SheetControl *sc = user_data;
2544 	scg_redraw_range (sc, r);
2545 	scg_redraw_headers (sc, TRUE, TRUE, r);
2546 	return TRUE;
2547 }
2548 
2549 void
scg_cursor_visible(SheetControlGUI * scg,gboolean is_visible)2550 scg_cursor_visible (SheetControlGUI *scg, gboolean is_visible)
2551 {
2552 	SheetControl *sc = (SheetControl *) scg;
2553 
2554 	/* there is always a grid 0 */
2555 	if (NULL == scg->pane[0])
2556 		return;
2557 
2558 	SCG_FOREACH_PANE (scg, pane,
2559 		gnm_item_cursor_set_visibility (pane->cursor.std, is_visible););
2560 
2561 	sv_selection_foreach (sc->view, cb_redraw_sel, sc);
2562 }
2563 
2564 /***************************************************************************/
2565 
2566 /**
2567  * scg_mode_edit:
2568  * @scg:  The sheet control
2569  *
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.
2573  **/
2574 void
scg_mode_edit(SheetControlGUI * scg)2575 scg_mode_edit (SheetControlGUI *scg)
2576 {
2577 	WBCGtk *wbcg;
2578 	g_return_if_fail (GNM_IS_SCG (scg));
2579 
2580 	wbcg = scg->wbcg;
2581 
2582 	if (wbcg != NULL) /* Can be NULL during destruction */
2583 		wbcg_insert_object_clear (wbcg);
2584 
2585 	scg_object_unselect (scg, NULL);
2586 
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);
2594 	}
2595 
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);
2599 
2600 	if (wbcg)
2601 		wb_control_update_action_sensitivity (GNM_WBC (wbcg));
2602 }
2603 
2604 static void
scg_mode_edit_virt(SheetControl * sc)2605 scg_mode_edit_virt (SheetControl *sc)
2606 {
2607 	scg_mode_edit ((SheetControlGUI *)sc);
2608 }
2609 
2610 static int
calc_obj_place(GnmPane * pane,gint64 canvas_coord,gboolean is_col,double * offset)2611 calc_obj_place (GnmPane *pane, gint64 canvas_coord, gboolean is_col,
2612 		double *offset)
2613 {
2614 	gint64 origin;
2615 	int colrow;
2616 	ColRowInfo const *cri;
2617 	Sheet const *sheet = scg_sheet (pane->simple.scg);
2618 
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);
2625 	}
2626 
2627 	/* TODO : handle other anchor types */
2628 	*offset = (canvas_coord - origin) / (double)cri->size_pixels;
2629 	return colrow;
2630 }
2631 
2632 #define SO_CLASS(so) GNM_SO_CLASS (G_OBJECT_GET_CLASS(so))
2633 
2634 /**
2635  * scg_object_select:
2636  * @scg: The #SheetControl to edit in.
2637  * @so: The #SheetObject to select.
2638  *
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.
2641  **/
2642 void
scg_object_select(SheetControlGUI * scg,SheetObject * so)2643 scg_object_select (SheetControlGUI *scg, SheetObject *so)
2644 {
2645 	double *coords;
2646 
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);
2652 
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));
2657 
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);
2665 	}
2666 
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);
2672 
2673 	SCG_FOREACH_PANE (scg, pane,
2674 		gnm_pane_object_update_bbox (pane, so););
2675 }
2676 
2677 static void
cb_scg_object_unselect(SheetObject * so,G_GNUC_UNUSED double * coords,SheetControlGUI * scg)2678 cb_scg_object_unselect (SheetObject *so, G_GNUC_UNUSED double *coords, SheetControlGUI *scg)
2679 {
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);
2683 }
2684 
2685 /**
2686  * scg_object_unselect:
2687  * @scg: #SheetControlGUI
2688  * @so: #SheetObject (optionally NULL)
2689  *
2690  * unselect the supplied object, and drop out of edit mode if this is the last
2691  * one.  If @so == NULL unselect _all_ objects.
2692  **/
2693 void
scg_object_unselect(SheetControlGUI * scg,SheetObject * so)2694 scg_object_unselect (SheetControlGUI *scg, SheetObject *so)
2695 {
2696 	WorkbookControl *wbc = scg_wbc (scg);
2697 
2698 	/* cheesy cycle avoidance */
2699 	if (scg->selected_objects == NULL)
2700 		return;
2701 
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);
2712 
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);
2718 }
2719 
2720 void
scg_object_select_next(SheetControlGUI * scg,gboolean reverse)2721 scg_object_select_next	(SheetControlGUI *scg, gboolean reverse)
2722 {
2723 	Sheet *sheet = scg_sheet (scg);
2724 	GSList *ptr = sheet->sheet_objects;
2725 
2726 	g_return_if_fail (ptr != NULL);
2727 
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;
2749 				}
2750 				if (ptr->data != target) {
2751 					scg_object_unselect (scg, NULL);
2752 					scg_object_select (scg, target);
2753 					return;
2754 				}
2755 			}
2756 	}
2757 }
2758 
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;
2769 
2770 static double
snap_pos_to_grid(ObjDragInfo const * info,gboolean is_col,double pos,gboolean to_min)2771 snap_pos_to_grid (ObjDragInfo const *info, gboolean is_col, double pos,
2772 		  gboolean to_min)
2773 {
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);
2782 
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;
2789 			}
2790 		}
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;
2801 			}
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);
2809 		}
2810 	}
2811 	return/* sign */ pos;
2812 }
2813 
2814 static void
apply_move(SheetObject * so,int x_idx,int y_idx,double * coords,ObjDragInfo * info,gboolean snap_to_grid)2815 apply_move (SheetObject *so, int x_idx, int y_idx, double *coords,
2816 	    ObjDragInfo *info, gboolean snap_to_grid)
2817 {
2818 	gboolean move_x = (x_idx >= 0);
2819 	gboolean move_y = (y_idx >= 0);
2820 	double x, y;
2821 
2822 	x = move_x ? coords[x_idx] + info->dx : 0;
2823 	y = move_y ? coords[y_idx] + info->dy : 0;
2824 
2825 	if (snap_to_grid) {
2826 		g_return_if_fail (info->pane != NULL);
2827 
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];
2835 		}
2836 	}
2837 
2838 	if (move_x) coords[x_idx] = x;
2839 	if (move_y) coords[y_idx] = y;
2840 
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;
2844 	}
2845 }
2846 
2847 static void
drag_object(SheetObject * so,double * coords,ObjDragInfo * info)2848 drag_object (SheetObject *so, double *coords, ObjDragInfo *info)
2849 {
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}
2855 	};
2856 
2857 	g_return_if_fail (info->drag_type <= 8);
2858 
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););
2869 }
2870 
2871 static void
cb_drag_selected_objects(SheetObject * so,double * coords,ObjDragInfo * info)2872 cb_drag_selected_objects (SheetObject *so, double *coords, ObjDragInfo *info)
2873 {
2874 	if (so != info->primary_object)
2875 		drag_object (so, coords, info);
2876 }
2877 
2878 /**
2879  * scg_objects_drag:
2880  * @scg: #SheetControlGUI
2881  * @primary: #SheetObject (optionally NULL)
2882  * @dx:
2883  * @dy:
2884  * @drag_type:
2885  * @symmetric:
2886  *
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.
2890  **/
2891 void
scg_objects_drag(SheetControlGUI * scg,GnmPane * pane,SheetObject * primary,gdouble * dx,gdouble * dy,int drag_type,gboolean symmetric,gboolean snap_to_grid,gboolean is_mouse_move)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)
2898 {
2899 	double *coords;
2900 
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;
2911 
2912 	if (primary != NULL) {
2913 		coords = g_hash_table_lookup (scg->selected_objects, primary);
2914 		drag_object (primary, coords, &info);
2915 	}
2916 
2917 	g_hash_table_foreach (scg->selected_objects,
2918 		(GHFunc) cb_drag_selected_objects, &info);
2919 
2920 	*dx = info.dx;
2921 	*dy = info.dy;
2922 }
2923 
2924 typedef struct {
2925 	SheetControlGUI *scg;
2926 	GSList *objects, *anchors;
2927 } CollectObjectsData;
2928 static void
cb_collect_objects_to_commit(SheetObject * so,double * coords,CollectObjectsData * data)2929 cb_collect_objects_to_commit (SheetObject *so, double *coords, CollectObjectsData *data)
2930 {
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];
2941 	}
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);
2945 
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);
2954 
2955 				g_object_unref (ctrl_pts[9]);
2956 				ctrl_pts[9] = NULL;
2957 
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);
2962 			}
2963 		});
2964 	}
2965 }
2966 
2967 static char *
scg_objects_drag_commit_get_undo_text(int drag_type,int n,gboolean created_objects)2968 scg_objects_drag_commit_get_undo_text (int drag_type, int n,
2969 				       gboolean created_objects)
2970 {
2971 	char const *format;
2972 
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);
2987 	}
2988 
2989 	return g_strdup_printf (format, n);
2990 
2991 }
2992 
2993 void
scg_objects_drag_commit(SheetControlGUI * scg,int drag_type,gboolean created_objects,GOUndo ** pundo,GOUndo ** predo,gchar ** undo_title)2994 scg_objects_drag_commit (SheetControlGUI *scg, int drag_type,
2995 			 gboolean created_objects,
2996 			 GOUndo **pundo, GOUndo **predo, gchar **undo_title)
2997 {
2998 	CollectObjectsData data;
2999 	char *text = NULL;
3000 	GOUndo *undo = NULL;
3001 	GOUndo *redo = NULL;
3002 
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);
3007 
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);
3012 
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);
3022 	}
3023 	g_slist_free (data.objects);
3024 	g_slist_free_full (data.anchors, g_free);
3025 }
3026 
3027 void
scg_objects_nudge(SheetControlGUI * scg,GnmPane * pane,int drag_type,double dx,double dy,gboolean symmetric,gboolean snap_to_grid)3028 scg_objects_nudge (SheetControlGUI *scg, GnmPane *pane,
3029 		   int drag_type, double dx, double dy, gboolean symmetric, gboolean snap_to_grid)
3030 {
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);
3035 	}
3036 }
3037 
3038 void
scg_object_coords_to_anchor(SheetControlGUI const * scg,double const * coords,SheetObjectAnchor * in_out)3039 scg_object_coords_to_anchor (SheetControlGUI const *scg,
3040 			     double const *coords, SheetObjectAnchor *in_out)
3041 {
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);
3048 
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;
3057 	}
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;
3065 	}
3066 
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;
3097 	}
3098 	}
3099 }
3100 
3101 static double
cell_offset_calc_pixel(Sheet const * sheet,int i,gboolean is_col,double offset)3102 cell_offset_calc_pixel (Sheet const *sheet, int i, gboolean is_col,
3103 			double offset)
3104 {
3105 	ColRowInfo const *cri = sheet_colrow_get_info (sheet, i, is_col);
3106 	return offset * cri->size_pixels;
3107 }
3108 
3109 void
scg_object_anchor_to_coords(SheetControlGUI const * scg,SheetObjectAnchor const * anchor,double * coords)3110 scg_object_anchor_to_coords (SheetControlGUI const *scg,
3111 			     SheetObjectAnchor const *anchor, double *coords)
3112 {
3113 	Sheet *sheet = scg_sheet (scg);
3114 	GODrawingAnchorDir direction;
3115 	gint64 pixels[4];
3116 	GnmRange const *r;
3117 
3118 	g_return_if_fail (GNM_IS_SCG (scg));
3119 	g_return_if_fail (anchor != NULL);
3120 	g_return_if_fail (coords != NULL);
3121 
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);
3148 		}
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);
3157 	}
3158 
3159 	direction = anchor->base.direction;
3160 	if (direction == GOD_ANCHOR_DIR_UNKNOWN)
3161 		direction = GOD_ANCHOR_DIR_DOWN_RIGHT;
3162 
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];
3167 }
3168 
3169 /***************************************************************************/
3170 
3171 static gboolean
scg_comment_display_filter_cb(PangoAttribute * attribute,gboolean * state)3172 scg_comment_display_filter_cb (PangoAttribute *attribute, gboolean *state)
3173 {
3174 	if (attribute->klass->type == PANGO_ATTR_FOREGROUND &&
3175 	    attribute->start_index != attribute->end_index)
3176 		*state = TRUE;
3177 	return FALSE;
3178 }
3179 
3180 /**
3181  * scg_comment_display:
3182  * @scg: The SheetControl
3183  * @cc: A cell comment
3184  *
3185  */
3186 void
scg_comment_display(SheetControlGUI * scg,GnmComment * cc,int x,int y)3187 scg_comment_display (SheetControlGUI *scg, GnmComment *cc,
3188 		     int x, int y)
3189 {
3190 	g_return_if_fail (GNM_IS_SCG (scg));
3191 
3192 	scg_comment_timer_clear (scg);
3193 
3194 	/* If someone clicked and dragged the comment marker this may be NULL */
3195 	if (scg->comment.selected == NULL)
3196 		return;
3197 
3198 	if (cc == NULL)
3199 		cc = scg->comment.selected;
3200 	else if (scg->comment.selected != cc)
3201 		scg_comment_unselect (scg, scg->comment.selected);
3202 
3203 	g_return_if_fail (GNM_IS_CELL_COMMENT (cc));
3204 
3205 	if (scg->comment.item == NULL) {
3206 		GtkWidget *label, *box;
3207 		char *comment_text;
3208 		PangoAttrList *comment_markup;
3209 		char const *comment_author;
3210 
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);
3216 
3217 		box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
3218 
3219 		if (comment_author != NULL) {
3220 			char *text;
3221 			PangoAttrList *attrs;
3222 			PangoAttribute *attr;
3223 
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);
3228 
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);
3236 
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);
3240 		}
3241 
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);
3261 			}
3262 			gtk_label_set_attributes (GTK_LABEL (label), comment_markup);
3263 		}
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);
3267 
3268 		gnm_convert_to_tooltip (GTK_WIDGET (scg->grid), box);
3269 
3270 		scg->comment.item = gtk_widget_get_toplevel (box);
3271 		gtk_window_move (GTK_WINDOW (scg->comment.item),
3272 				 x + 10, y + 10);
3273 
3274 		gtk_widget_show_all (scg->comment.item);
3275 	}
3276 }
3277 
3278 static gint
cb_cell_comment_timer(SheetControlGUI * scg)3279 cb_cell_comment_timer (SheetControlGUI *scg)
3280 {
3281 	g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3282 	g_return_val_if_fail (scg->comment.timer != 0, FALSE);
3283 
3284 	scg->comment.timer = 0;
3285 	scg_comment_display (scg, scg->comment.selected,
3286 			     scg->comment.x, scg->comment.y);
3287 	return FALSE;
3288 }
3289 
3290 /**
3291  * scg_comment_select:
3292  * @scg: The SheetControl
3293  * @cc: A cell comment
3294  *
3295  * Prepare @cc for display.
3296  */
3297 void
scg_comment_select(SheetControlGUI * scg,GnmComment * cc,int x,int y)3298 scg_comment_select (SheetControlGUI *scg, GnmComment *cc, int x, int y)
3299 {
3300 	g_return_if_fail (GNM_IS_SCG (scg));
3301 
3302 	if (scg->comment.selected != NULL)
3303 		scg_comment_unselect (scg, scg->comment.selected);
3304 
3305 	g_return_if_fail (scg->comment.timer == 0);
3306 
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;
3312 }
3313 
3314 /**
3315  * scg_comment_unselect:
3316  * @scg: The SheetControl
3317  * @cc: A cell comment
3318  *
3319  * If @cc is the current cell comment being edited/displayed shutdown the
3320  * display mechanism.
3321  */
3322 void
scg_comment_unselect(SheetControlGUI * scg,GnmComment * cc)3323 scg_comment_unselect (SheetControlGUI *scg, GnmComment *cc)
3324 {
3325 	g_return_if_fail (GNM_IS_SCG (scg));
3326 
3327 	if (cc == scg->comment.selected) {
3328 		scg->comment.selected = NULL;
3329 		scg_comment_timer_clear (scg);
3330 
3331 		if (scg->comment.item != NULL) {
3332 			gtk_widget_destroy (scg->comment.item);
3333 			scg->comment.item = NULL;
3334 		}
3335 	}
3336 }
3337 
3338 /************************************************************************/
3339 /* Col/Row size support routines.  */
3340 
3341 gint64
scg_colrow_distance_get(SheetControlGUI const * scg,gboolean is_cols,int from,int to)3342 scg_colrow_distance_get (SheetControlGUI const *scg, gboolean is_cols,
3343 			 int from, int to)
3344 {
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;
3351 
3352 	g_return_val_if_fail (GNM_IS_SCG (scg), 1);
3353 
3354 	if (from > to) {
3355 		int const tmp = to;
3356 		to = from;
3357 		from = tmp;
3358 		sign = -1;
3359 	}
3360 
3361 	g_return_val_if_fail (from >= 0, 1);
3362 
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;
3369 	}
3370 
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.
3374 	 */
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);
3379 
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;
3392 		}
3393 	}
3394 
3395 	return pixels*sign;
3396 }
3397 
3398 /*************************************************************************/
3399 
3400 static void
scg_cursor_bound(SheetControl * sc,GnmRange const * r)3401 scg_cursor_bound (SheetControl *sc, GnmRange const *r)
3402 {
3403 	SheetControlGUI *scg = (SheetControlGUI *) sc;
3404 	SCG_FOREACH_PANE (scg, pane, gnm_pane_cursor_bound_set (pane, r););
3405 }
3406 
3407 static void
scg_recompute_visible_region(SheetControl * sc,gboolean full_recompute)3408 scg_recompute_visible_region (SheetControl *sc, gboolean full_recompute)
3409 {
3410 	SheetControlGUI *scg = (SheetControlGUI *) sc;
3411 
3412 	SCG_FOREACH_PANE (scg, pane,
3413 		gnm_pane_compute_visible_region (pane, full_recompute););
3414 }
3415 
3416 void
scg_edit_start(SheetControlGUI * scg)3417 scg_edit_start (SheetControlGUI *scg)
3418 {
3419 	g_return_if_fail (GNM_IS_SCG (scg));
3420 
3421 	SCG_FOREACH_PANE (scg, pane, gnm_pane_edit_start (pane););
3422 }
3423 
3424 void
scg_edit_stop(SheetControlGUI * scg)3425 scg_edit_stop (SheetControlGUI *scg)
3426 {
3427 	g_return_if_fail (GNM_IS_SCG (scg));
3428 
3429 	scg_rangesel_stop (scg, FALSE);
3430 	SCG_FOREACH_PANE (scg, pane, gnm_pane_edit_stop (pane););
3431 }
3432 
3433 /**
3434  * scg_rangesel_changed:
3435  * @scg:   The scg
3436  *
3437  * Notify expr_entry that the expression range has changed.
3438  **/
3439 static void
scg_rangesel_changed(SheetControlGUI * scg,int base_col,int base_row,int move_col,int move_row)3440 scg_rangesel_changed (SheetControlGUI *scg,
3441 		      int base_col, int base_row,
3442 		      int move_col, int move_row)
3443 {
3444 	GnmExprEntry *expr_entry;
3445 	gboolean ic_changed;
3446 	GnmRange *r, last_r;
3447 	Sheet *sheet;
3448 
3449 	g_return_if_fail (GNM_IS_SCG (scg));
3450 
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;
3455 
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;
3463 	}
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;
3470 	}
3471 
3472 	sheet = scg_sheet (scg);
3473 	expr_entry = wbcg_get_entry_logical (scg->wbcg);
3474 
3475 	gnm_expr_entry_freeze (expr_entry);
3476 	/* The order here is tricky.
3477 	 * 1) Assign the range to the expr entry.
3478 	 */
3479 	ic_changed = gnm_expr_entry_load_from_range (
3480 		expr_entry, sheet, r);
3481 
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);
3485 
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);
3491 
3492 	gnm_expr_entry_thaw (expr_entry);
3493 
3494 	SCG_FOREACH_PANE (scg, pane, gnm_pane_rangesel_bound_set (pane, r););
3495 }
3496 
3497 void
scg_rangesel_start(SheetControlGUI * scg,int base_col,int base_row,int move_col,int move_row)3498 scg_rangesel_start (SheetControlGUI *scg,
3499 		    int base_col, int base_row,
3500 		    int move_col, int move_row)
3501 {
3502 	GnmRange r;
3503 
3504 	g_return_if_fail (GNM_IS_SCG (scg));
3505 
3506 	if (scg->rangesel.active)
3507 		return;
3508 
3509 	if (scg->wbcg->rangesel != NULL)
3510 		g_warning ("misconfiged rangesel");
3511 
3512 	scg->wbcg->rangesel = scg;
3513 	scg->rangesel.active = TRUE;
3514 
3515 	gnm_expr_entry_find_range (wbcg_get_entry_logical (scg->wbcg));
3516 
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);
3520 }
3521 
3522 void
scg_rangesel_stop(SheetControlGUI * scg,gboolean clear_string)3523 scg_rangesel_stop (SheetControlGUI *scg, gboolean clear_string)
3524 {
3525 	g_return_if_fail (GNM_IS_SCG (scg));
3526 
3527 	if (!scg->rangesel.active)
3528 		return;
3529 	if (scg->wbcg->rangesel != scg)
3530 		g_warning ("misconfiged rangesel");
3531 
3532 	scg->wbcg->rangesel = NULL;
3533 	scg->rangesel.active = FALSE;
3534 	SCG_FOREACH_PANE (scg, pane, gnm_pane_rangesel_stop (pane););
3535 
3536 	gnm_expr_entry_rangesel_stop (wbcg_get_entry_logical (scg->wbcg),
3537 		clear_string);
3538 }
3539 
3540 /**
3541  * scg_set_display_cursor:
3542  * @scg:
3543  *
3544  * Set the displayed cursor type.
3545  */
3546 void
scg_set_display_cursor(SheetControlGUI * scg)3547 scg_set_display_cursor (SheetControlGUI *scg)
3548 {
3549 	GdkCursorType cursor = GDK_CURSOR_IS_PIXMAP;
3550 
3551 	g_return_if_fail (GNM_IS_SCG (scg));
3552 
3553 	if (scg->wbcg->new_object != NULL)
3554 		cursor = GDK_CROSSHAIR;
3555 
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);
3563 		}
3564 	});
3565 }
3566 
3567 void
scg_rangesel_extend_to(SheetControlGUI * scg,int col,int row)3568 scg_rangesel_extend_to (SheetControlGUI *scg, int col, int row)
3569 {
3570 	int base_col, base_row;
3571 
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;
3582 
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);
3587 }
3588 
3589 void
scg_rangesel_bound(SheetControlGUI * scg,int base_col,int base_row,int move_col,int move_row)3590 scg_rangesel_bound (SheetControlGUI *scg,
3591 		    int base_col, int base_row,
3592 		    int move_col, int move_row)
3593 {
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);
3598 }
3599 
3600 void
scg_rangesel_move(SheetControlGUI * scg,int n,gboolean jump_to_bound,gboolean horiz)3601 scg_rangesel_move (SheetControlGUI *scg, int n, gboolean jump_to_bound,
3602 		   gboolean horiz)
3603 {
3604 	SheetView *sv = scg_view (scg);
3605 	GnmCellPos tmp;
3606 
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;
3612 
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);
3619 
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);
3627 }
3628 
3629 void
scg_rangesel_extend(SheetControlGUI * scg,int n,gboolean jump_to_bound,gboolean horiz)3630 scg_rangesel_extend (SheetControlGUI *scg, int n,
3631 		     gboolean jump_to_bound, gboolean horiz)
3632 {
3633 	Sheet *sheet = scg_sheet (scg);
3634 
3635 	if (scg->rangesel.active) {
3636 		GnmCellPos tmp = scg->rangesel.move_corner;
3637 
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);
3646 
3647 		scg_rangesel_changed (scg,
3648 			scg->rangesel.base_corner.col,
3649 			scg->rangesel.base_corner.row, tmp.col, tmp.row);
3650 
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);
3658 }
3659 
3660 /**
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
3667  *
3668  * Moves the cursor count rows
3669  */
3670 void
scg_cursor_move(SheetControlGUI * scg,int n,gboolean jump_to_bound,gboolean horiz)3671 scg_cursor_move (SheetControlGUI *scg, int n,
3672 		 gboolean jump_to_bound, gboolean horiz)
3673 {
3674 	SheetView *sv = scg_view (scg);
3675 	GnmCellPos tmp = sv->edit_pos_real;
3676 	int step = (n>0) ? 1 : -1;
3677 
3678 	if (!wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
3679 		return;
3680 
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);
3691 
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);
3697 }
3698 
3699 /**
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.
3706  */
3707 void
scg_cursor_extend(SheetControlGUI * scg,int n,gboolean jump_to_bound,gboolean horiz)3708 scg_cursor_extend (SheetControlGUI *scg, int n,
3709 		   gboolean jump_to_bound, gboolean horiz)
3710 {
3711 	SheetView *sv = scg_view (scg);
3712 	GnmCellPos move = sv->cursor.move_corner;
3713 	GnmCellPos visible = scg->pane[0]->first;
3714 
3715 	if (!wbcg_edit_finish (scg->wbcg, WBC_EDIT_ACCEPT, NULL))
3716 		return;
3717 
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);
3726 
3727 	sv_selection_extend_to (sv, move.col, move.row);
3728 	gnm_sheet_view_make_cell_visible (sv, visible.col, visible.row, FALSE);
3729 }
3730 
3731 void
scg_take_focus(SheetControlGUI * scg)3732 scg_take_focus (SheetControlGUI *scg)
3733 {
3734 	g_return_if_fail (GNM_IS_SCG (scg));
3735 
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)));
3741 }
3742 
3743 /*********************************************************************************/
3744 void
scg_size_guide_start(SheetControlGUI * scg,gboolean vert,int colrow,gboolean is_colrow_resize)3745 scg_size_guide_start (SheetControlGUI *scg,
3746 		      gboolean vert, int colrow, gboolean is_colrow_resize)
3747 {
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););
3751 }
3752 void
scg_size_guide_motion(SheetControlGUI * scg,gboolean vert,gint64 guide_pos)3753 scg_size_guide_motion (SheetControlGUI *scg, gboolean vert, gint64 guide_pos)
3754 {
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););
3758 }
3759 void
scg_size_guide_stop(SheetControlGUI * scg)3760 scg_size_guide_stop (SheetControlGUI *scg)
3761 {
3762 	g_return_if_fail (GNM_IS_SCG (scg));
3763 	SCG_FOREACH_PANE (scg, pane,
3764 		gnm_pane_size_guide_stop (pane););
3765 }
3766 /*********************************************************************************/
3767 
3768 void
scg_special_cursor_start(SheetControlGUI * scg,int style,int button)3769 scg_special_cursor_start (SheetControlGUI *scg, int style, int button)
3770 {
3771 	g_return_if_fail (GNM_IS_SCG (scg));
3772 
3773 	SCG_FOREACH_PANE (scg, pane,
3774 		gnm_pane_special_cursor_start (pane, style, button););
3775 }
3776 
3777 void
scg_special_cursor_stop(SheetControlGUI * scg)3778 scg_special_cursor_stop (SheetControlGUI *scg)
3779 {
3780 	g_return_if_fail (GNM_IS_SCG (scg));
3781 
3782 	SCG_FOREACH_PANE (scg, pane,
3783 		gnm_pane_special_cursor_stop (pane););
3784 }
3785 
3786 gboolean
scg_special_cursor_bound_set(SheetControlGUI * scg,GnmRange const * r)3787 scg_special_cursor_bound_set (SheetControlGUI *scg, GnmRange const *r)
3788 {
3789 	gboolean changed = FALSE;
3790 
3791 	g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3792 
3793 	SCG_FOREACH_PANE (scg, pane,
3794 		changed |= gnm_pane_special_cursor_bound_set (pane, r););
3795 	return changed;
3796 }
3797 
3798 static void
scg_object_create_view(SheetControl * sc,SheetObject * so)3799 scg_object_create_view	(SheetControl *sc, SheetObject *so)
3800 {
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);
3807 }
3808 
3809 static void
scg_scale_changed(SheetControl * sc)3810 scg_scale_changed (SheetControl *sc)
3811 {
3812 	SheetControlGUI *scg = (SheetControlGUI *)sc;
3813 	Sheet *sheet = scg_sheet (scg);
3814 	double z;
3815 	GSList *ptr;
3816 
3817 	g_return_if_fail (GNM_IS_SCG (scg));
3818 
3819 	z = sheet->last_zoom_factor_used;
3820 
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);
3827 	});
3828 
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);
3835 }
3836 
3837 static gboolean
cb_cell_im_timer(SheetControlGUI * scg)3838 cb_cell_im_timer (SheetControlGUI *scg)
3839 {
3840 	g_return_val_if_fail (GNM_IS_SCG (scg), FALSE);
3841 	g_return_val_if_fail (scg->im.timer != 0, FALSE);
3842 
3843 	scg->im.timer = 0;
3844 	scg_im_destroy (scg);
3845 	return FALSE;
3846 }
3847 
3848 static GnmPane *
scg_find_pane(SheetControlGUI * scg,GnmCellPos * pos)3849 scg_find_pane (SheetControlGUI *scg, GnmCellPos *pos)
3850 {
3851 	int i;
3852 
3853 	for (i = 0; i < scg->active_panes; i++) {
3854 		GnmPane *pane = scg->pane[i];
3855 
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;
3862 	}
3863 	return NULL;
3864 }
3865 
3866 static void
scg_show_im_tooltip(SheetControl * sc,GnmInputMsg * im,GnmCellPos * pos)3867 scg_show_im_tooltip (SheetControl *sc, GnmInputMsg *im, GnmCellPos *pos)
3868 {
3869 	SheetControlGUI *scg = (SheetControlGUI *)sc;
3870 	GnmPane *pane;
3871 
3872 	g_return_if_fail (GNM_IS_SCG (scg));
3873 
3874 	scg_im_destroy (scg);
3875 
3876 	pane = scg_find_pane (scg, pos);
3877 
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;
3886 
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);
3891 
3892 		if ((len_text == 0) && (len_title == 0))
3893 			return;
3894 
3895 		box = gtk_box_new (GTK_ORIENTATION_VERTICAL, FALSE);
3896 
3897 		if (len_title > 0) {
3898 			PangoAttrList *attrs;
3899 			PangoAttribute *attr;
3900 
3901 			label = gtk_label_new (title);
3902 
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);
3910 
3911 			gtk_widget_set_halign (label, GTK_ALIGN_START);
3912 			gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
3913 		}
3914 		if (len_text > 0) {
3915 			label = gtk_label_new (text);
3916 
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);
3921 		}
3922 		gnm_convert_to_tooltip (GTK_WIDGET (scg->grid), box);
3923 		scg->im.item = gtk_widget_get_toplevel (box);
3924 
3925 		x = sheet_col_get_distance_pixels
3926 			(sheet, pane->first.col, pos->col + (rtl ? 1 : 0));
3927 
3928 		y = sheet_row_get_distance_pixels
3929 			(sheet, pane->first.row, pos->row + 1);
3930 
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;
3936 
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;
3942 
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);
3946 	}
3947 }
3948 
3949 static void
scg_freeze_object_view(SheetControl * sc,gboolean freeze)3950 scg_freeze_object_view (SheetControl *sc, gboolean freeze)
3951 {
3952 	SCG_FOREACH_PANE
3953 		(GNM_SCG(sc), pane,
3954 		 GocGroup *g = pane->object_views;
3955 		 goc_group_freeze (g, freeze););
3956 }
3957 
3958 static void
scg_class_init(GObjectClass * object_class)3959 scg_class_init (GObjectClass *object_class)
3960 {
3961 	SheetControlClass *sc_class = SHEET_CONTROL_CLASS (object_class);
3962 
3963 	g_return_if_fail (sc_class != NULL);
3964 
3965 	scg_parent_class = g_type_class_peek_parent (object_class);
3966 
3967 	object_class->finalize = scg_finalize;
3968 
3969 	sc_class->resize                   = scg_resize_virt;
3970 	sc_class->redraw_all               = scg_redraw_all;
3971 	sc_class->redraw_range		   = scg_redraw_range;
3972 	sc_class->redraw_headers           = scg_redraw_headers;
3973 	sc_class->ant                      = scg_ant;
3974 	sc_class->unant                    = scg_unant;
3975 	sc_class->scrollbar_config         = scg_scrollbar_config;
3976 	sc_class->mode_edit                = scg_mode_edit_virt;
3977 	sc_class->set_top_left		   = scg_set_top_left;
3978 	sc_class->recompute_visible_region = scg_recompute_visible_region;
3979 	sc_class->make_cell_visible        = scg_make_cell_visible_virt;
3980 	sc_class->cursor_bound             = scg_cursor_bound;
3981 	sc_class->set_panes		   = scg_set_panes;
3982 	sc_class->object_create_view	   = scg_object_create_view;
3983 	sc_class->scale_changed		   = scg_scale_changed;
3984 	sc_class->show_im_tooltip	   = scg_show_im_tooltip;
3985 	sc_class->freeze_object_view	   = scg_freeze_object_view;
3986 }
3987 
GSF_CLASS(SheetControlGUI,sheet_control_gui,scg_class_init,scg_init,GNM_SHEET_CONTROL_TYPE)3988 GSF_CLASS (SheetControlGUI, sheet_control_gui,
3989 	   scg_class_init, scg_init, GNM_SHEET_CONTROL_TYPE)
3990 
3991 static gint
3992 cb_scg_queued_movement (SheetControlGUI *scg)
3993 {
3994 	Sheet const *sheet = scg_sheet (scg);
3995 	scg->delayedMovement.timer = 0;
3996 	(*scg->delayedMovement.handler) (scg,
3997 		scg->delayedMovement.n, FALSE,
3998 		scg->delayedMovement.horiz);
3999 	if (wbcg_is_editing (scg->wbcg))
4000 		sheet_update_only_grid (sheet);
4001 	else
4002 		sheet_update (sheet);
4003 	return FALSE;
4004 }
4005 
4006 /**
4007  * scg_queue_movement:
4008  * @scg:
4009  * @handler: (scope async): The movement handler
4010  * @n:		how far
4011  * @jump:	TRUE jump to bound
4012  * @horiz:	TRUE move by cols
4013  *
4014  * Do motion compression when possible to avoid redrawing an area that will
4015  * disappear when we scroll again.
4016  **/
4017 void
scg_queue_movement(SheetControlGUI * scg,SCGUIMoveFunc handler,int n,gboolean jump,gboolean horiz)4018 scg_queue_movement (SheetControlGUI	*scg,
4019 		    SCGUIMoveFunc	 handler,
4020 		    int n, gboolean jump, gboolean horiz)
4021 {
4022 	g_return_if_fail (GNM_IS_SCG (scg));
4023 
4024 	/* do we need to flush a pending movement */
4025 	if (scg->delayedMovement.timer != 0) {
4026 		if (jump ||
4027 		    /* do not skip more than 3 requests at a time */
4028 		    scg->delayedMovement.counter > 3 ||
4029 		    scg->delayedMovement.handler != handler ||
4030 		    scg->delayedMovement.horiz != horiz) {
4031 			g_source_remove (scg->delayedMovement.timer);
4032 			(*scg->delayedMovement.handler) (scg,
4033 				scg->delayedMovement.n, FALSE,
4034 				scg->delayedMovement.horiz);
4035 			scg->delayedMovement.handler = NULL;
4036 			scg->delayedMovement.timer = 0;
4037 		} else {
4038 			scg->delayedMovement.counter++;
4039 			scg->delayedMovement.n += n;
4040 			return;
4041 		}
4042 	}
4043 
4044 	/* jumps are always immediate */
4045 	if (jump) {
4046 		Sheet const *sheet = scg_sheet (scg);
4047 		(*handler) (scg, n, TRUE, horiz);
4048 		if (wbcg_is_editing (scg->wbcg))
4049 			sheet_update_only_grid (sheet);
4050 		else
4051 			sheet_update (sheet);
4052 		return;
4053 	}
4054 
4055 	scg->delayedMovement.counter = 1;
4056 	scg->delayedMovement.handler = handler;
4057 	scg->delayedMovement.horiz   = horiz;
4058 	scg->delayedMovement.n	     = n;
4059 	scg->delayedMovement.timer   = g_timeout_add (10,
4060 		(GSourceFunc)cb_scg_queued_movement, scg);
4061 }
4062 
4063 static void
scg_image_create(SheetControlGUI * scg,SheetObjectAnchor * anchor,guint8 const * data,unsigned len)4064 scg_image_create (SheetControlGUI *scg, SheetObjectAnchor *anchor,
4065 		  guint8 const *data, unsigned len)
4066 {
4067 	SheetObjectImage *soi;
4068 	SheetObject *so;
4069 	double w, h;
4070 
4071 	/* ensure that we are not editing anything else */
4072 	scg_mode_edit (scg);
4073 
4074 	soi = g_object_new (GNM_SO_IMAGE_TYPE, NULL);
4075 	sheet_object_image_set_image (soi, "", data, len);
4076 
4077 	so = GNM_SO (soi);
4078 	sheet_object_set_anchor (so, anchor);
4079 	sheet_object_set_sheet (so, scg_sheet (scg));
4080 	scg_object_select (scg, so);
4081 	sheet_object_default_size (so, &w, &h);
4082 	scg_objects_drag (scg, NULL, NULL, &w, &h, 7, FALSE, FALSE, FALSE);
4083 	scg_objects_drag_commit	(scg, 7, TRUE, NULL, NULL, NULL);
4084 }
4085 
4086 void
scg_paste_image(SheetControlGUI * scg,GnmRange * where,guint8 const * data,unsigned len)4087 scg_paste_image (SheetControlGUI *scg, GnmRange *where,
4088 		 guint8 const *data, unsigned len)
4089 {
4090 	SheetObjectAnchor anchor;
4091 
4092 	sheet_object_anchor_init (&anchor, where, NULL,
4093 		GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4094 	scg_image_create (scg, &anchor, data, len);
4095 }
4096 
4097 static void
scg_drag_receive_img_data(SheetControlGUI * scg,double x,double y,guint8 const * data,unsigned len)4098 scg_drag_receive_img_data (SheetControlGUI *scg, double x, double y,
4099 			   guint8 const *data, unsigned len)
4100 {
4101 	double coords[4];
4102 	SheetObjectAnchor anchor;
4103 
4104 	sheet_object_anchor_init (&anchor, NULL, NULL,
4105 		GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4106 	coords[0] = coords[2] = x;
4107 	coords[1] = coords[3] = y;
4108 	scg_object_coords_to_anchor (scg, coords, &anchor);
4109 	scg_image_create (scg, &anchor, data, len);
4110 }
4111 
4112 static void
scg_drag_receive_img_uri(SheetControlGUI * scg,double x,double y,const gchar * uri)4113 scg_drag_receive_img_uri (SheetControlGUI *scg, double x, double y, const gchar *uri)
4114 {
4115 	GError *err = NULL;
4116 	GsfInput *input = go_file_open (uri, &err);
4117 	GOIOContext *ioc = go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4118 
4119 	if (input != NULL) {
4120 		unsigned len = gsf_input_size (input);
4121 		guint8 const *data = gsf_input_read (input, len, NULL);
4122 
4123 		scg_drag_receive_img_data (scg, x, y, data, len);
4124 		g_object_unref (input);
4125 	} else
4126 		go_cmd_context_error (GO_CMD_CONTEXT (ioc), err);
4127 
4128 	if (go_io_error_occurred (ioc) ||
4129 	    go_io_warning_occurred (ioc)) {
4130 		go_io_error_display (ioc);
4131 		go_io_error_clear (ioc);
4132 	}
4133 	g_object_unref (ioc);
4134 }
4135 
4136 static void
scg_drag_receive_spreadsheet(SheetControlGUI * scg,const gchar * uri)4137 scg_drag_receive_spreadsheet (SheetControlGUI *scg, const gchar *uri)
4138 {
4139 	GError *err = NULL;
4140 	GsfInput *input = go_file_open (uri, &err);
4141 	GOIOContext *ioc = go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4142 
4143 	if (input != NULL) {
4144 		WorkbookView *wbv;
4145 
4146 		wbv = workbook_view_new_from_input (input, uri, NULL, ioc, NULL);
4147 		if (wbv != NULL)
4148 			gui_wb_view_show (scg->wbcg,
4149 					  wbv);
4150 
4151 	} else
4152 		go_cmd_context_error (GO_CMD_CONTEXT (ioc), err);
4153 
4154 	if (go_io_error_occurred (ioc) ||
4155 	    go_io_warning_occurred (ioc)) {
4156 		go_io_error_display (ioc);
4157 		go_io_error_clear (ioc);
4158 	}
4159 	g_object_unref (ioc);
4160 }
4161 
4162 static void
scg_paste_cellregion(SheetControlGUI * scg,double x,double y,GnmCellRegion * content)4163 scg_paste_cellregion (SheetControlGUI *scg, double x, double y,
4164 		      GnmCellRegion *content)
4165 {
4166 	WorkbookControl	*wbc  = scg_wbc (scg);
4167 	Sheet *sheet = scg_sheet (scg) ;
4168 	GnmPasteTarget pt;
4169 	SheetObjectAnchor anchor;
4170 	double coords[4];
4171 
4172 	sheet_object_anchor_init (&anchor, NULL, NULL,
4173 		GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
4174 	coords[0] = coords[2] = x;
4175 	coords[1] = coords[3] = y;
4176 	scg_object_coords_to_anchor (scg, coords, &anchor);
4177 	paste_target_init (&pt, sheet, &anchor.cell_bound, PASTE_ALL_SHEET);
4178 	if (content && ((content->cols > 0 && content->rows > 0) ||
4179 			content->objects != NULL))
4180 		cmd_paste_copy (wbc, &pt, content);
4181 }
4182 
4183 static void
scg_drag_receive_cellregion(SheetControlGUI * scg,double x,double y,const char * data,unsigned len)4184 scg_drag_receive_cellregion (SheetControlGUI *scg, double x, double y,
4185 			     const char *data, unsigned len)
4186 {
4187 	GnmCellRegion *content;
4188 	GOIOContext *io_context =
4189 		go_io_context_new (GO_CMD_CONTEXT (scg->wbcg));
4190 
4191 	content = gnm_xml_cellregion_read (scg_wbc (scg), io_context,
4192 				       scg_sheet (scg), data, len);
4193 	g_object_unref (io_context);
4194 	if (content != NULL) {
4195 		scg_paste_cellregion (scg, x, y, content);
4196 		cellregion_unref (content);
4197 	}
4198 }
4199 
4200 static void
scg_drag_receive_uri_list(SheetControlGUI * scg,double x,double y,const char * data,unsigned len)4201 scg_drag_receive_uri_list (SheetControlGUI *scg, double x, double y,
4202 			   const char *data, unsigned len)
4203 {
4204 	char *cdata = g_strndup (data, len);
4205 	GSList *urls = go_file_split_urls (cdata);
4206 	GSList *l;
4207 
4208 	g_free (cdata);
4209 	for (l = urls; l; l = l-> next) {
4210 		char const *uri_str = l->data;
4211 		gchar *mime = go_get_mime_type (uri_str);
4212 		/* Note that we have imperfect detection of mime-type with some
4213 		 * platforms, e.g. Win32. In the worst case if
4214 		 * go_get_mime_type() doesn't return "application/x-gnumeric"
4215 		 * (registry corruption?) it will give "text/plain" and a
4216 		 * spreadsheet file is assumed. */
4217 		if (!mime)
4218 			continue;
4219 
4220 		if (!strncmp (mime, "image/", 6))
4221 			scg_drag_receive_img_uri (scg, x, y, uri_str);
4222 		else if (!strcmp (mime, "application/x-gnumeric") ||
4223 			 !strcmp (mime, "application/vnd.ms-excel") ||
4224 			 !strcmp (mime, "application/vnd.sun.xml.calc") ||
4225 			 !strcmp (mime, "application/vnd.oasis.opendocument.spreadsheet") ||
4226 			 !strcmp (mime, "application/vnd.lotus-1-2-3") ||
4227 			 !strcmp (mime, "application/x-applix-spreadsheet") ||
4228 			 !strcmp (mime, "application/x-dbase") ||
4229 			 !strcmp (mime, "application/x-oleo") ||
4230 			 !strcmp (mime, "application/x-quattropro") ||
4231 			 !strcmp (mime, "application/x-sc") ||
4232 			 /* !strcmp (mime, "application/xhtml+xml") || */
4233 			 !strcmp (mime, "text/spreadsheet") ||
4234 			 !strcmp (mime, "text/tab-separated-values") ||
4235 			 !strcmp (mime, "text/x-comma-separated-values") ||
4236 			 !strcmp (mime, "text/html") ||
4237 			 !strcmp (mime, "text/plain")) {
4238 			scg_drag_receive_spreadsheet (scg, uri_str);
4239 		} else {
4240 			g_printerr ("Received URI %s with mime type %s.\n", uri_str, mime);
4241 			g_printerr ("I have no idea what to do with that.\n");
4242 		}
4243 		g_free (mime);
4244 	}
4245 	g_slist_free_full (urls, (GDestroyNotify) g_free);
4246 }
4247 
4248 static void
scg_drag_receive_same_process(SheetControlGUI * scg,GtkWidget * source_widget,double x,double y)4249 scg_drag_receive_same_process (SheetControlGUI *scg, GtkWidget *source_widget,
4250 			       double x, double y)
4251 {
4252 	SheetControlGUI *source_scg = NULL;
4253 	GnmPane *pane;
4254 
4255 	g_return_if_fail (source_widget != NULL);
4256 	g_return_if_fail (GNM_IS_PANE (source_widget));
4257 
4258 	pane = GNM_PANE (source_widget);
4259 	x *= goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
4260 	y *= goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
4261 	source_scg = pane->simple.scg;
4262 	if (source_scg == scg) {
4263 		GdkWindow *window;
4264 		GdkModifierType mask;
4265 		gint64 xx = x, yy = y;
4266 		gint64 origin_x = 0, origin_y = 0;
4267 		gboolean make_dup;
4268 		GOUndo *undo = NULL;
4269 		GOUndo *redo = NULL;
4270 		gchar *title = NULL;
4271 
4272 		window = gtk_widget_get_parent_window (GTK_WIDGET (pane));
4273 		gdk_window_get_device_position (window,
4274 				gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (gdk_window_get_display (window))),
4275 				NULL, NULL, &mask);
4276 
4277 		make_dup = ((mask & GDK_CONTROL_MASK) != 0);
4278 
4279 		/* When copying objects, we have to create a copy of current selection.
4280 		 * Since new objects are on top of canvas, we have to move current selection
4281 		 * back to original position, create a copy of selected objects, make them
4282 		 * the current selection, then move these objects to drop location. */
4283 
4284 		if (make_dup) {
4285 			xx = origin_x = pane->drag.origin_x;
4286 			yy = origin_y = pane->drag.origin_y;
4287 		}
4288 
4289 		gnm_pane_objects_drag (pane, NULL, xx, yy, 8, FALSE,
4290 				       (mask & GDK_SHIFT_MASK) != 0);
4291 		pane->drag.origin_x = pane->drag.last_x;
4292 		pane->drag.origin_y = pane->drag.last_y;
4293 
4294 		if (make_dup) {
4295 			GSList *ptr, *objs = go_hash_keys (scg->selected_objects);
4296 			GOUndo *nudge_undo = NULL;
4297 			GOUndo *nudge_redo = NULL;
4298 			double dx, dy;
4299 
4300 			for (ptr = objs ; ptr != NULL ; ptr = ptr->next) {
4301 				SheetObject *dup_obj = sheet_object_dup (ptr->data);
4302 				if (dup_obj != NULL) {
4303 					sheet_object_set_sheet (dup_obj, scg_sheet (scg));
4304 					scg_object_select (scg, dup_obj);
4305 					g_object_unref (dup_obj);
4306 					scg_object_unselect (scg, ptr->data);
4307 				}
4308 			}
4309 			g_slist_free (objs);
4310 			scg_objects_drag_commit	(scg, 8, TRUE, &undo, &redo, &title);
4311 			dx = x - origin_x;
4312 			dy = y - origin_y;
4313 			scg_objects_drag (scg, pane, NULL, &dx, &dy, 8, FALSE, FALSE, FALSE);
4314 			scg_objects_drag_commit (scg, 8, FALSE, &nudge_undo, &nudge_redo, NULL);
4315 			undo = go_undo_combine (undo, nudge_undo);
4316 			redo = go_undo_combine (nudge_redo, redo);
4317 		} else
4318 			scg_objects_drag_commit	(scg, 8, FALSE, &undo, &redo, &title);
4319 		cmd_generic (GNM_WBC (scg_wbcg (scg)), title, undo, redo);
4320 		g_free (title);
4321 	} else {
4322 		GnmCellRegion *content;
4323 		GSList *objects;
4324 
4325 		g_return_if_fail (GNM_IS_SCG (source_scg));
4326 
4327 		objects = go_hash_keys (source_scg->selected_objects);
4328 		content = clipboard_copy_obj (scg_sheet (source_scg),
4329 					      objects);
4330 		if (content != NULL) {
4331 			scg_paste_cellregion (scg, x, y, content);
4332 			cellregion_unref (content);
4333 		}
4334 		g_slist_free (objects);
4335 	}
4336 }
4337 
4338 /*  Keep in sync with gtk_selection_data_targets_include_text() */
4339 static gboolean
is_text_target(gchar * target_type)4340 is_text_target (gchar *target_type)
4341 {
4342 	const gchar *charset;
4343 	gchar       *text_plain_locale;
4344 	gboolean ret;
4345 
4346 	g_get_charset (&charset);
4347 	text_plain_locale = g_strdup_printf ("text/plain;charset=%s", charset);
4348 	ret = !strcmp (target_type, "UTF8_STRING") ||
4349 	      !strcmp (target_type, "COMPOUND_TEXT") ||
4350 	      !strcmp (target_type, "TEXT") ||
4351 	      !strcmp (target_type, "STRING") ||
4352 	      !strcmp (target_type, "text/plain;charset=utf-8") ||
4353 	      !strcmp (target_type, text_plain_locale) ||
4354 	      !strcmp (target_type, "text/plain");
4355 	g_free (text_plain_locale);
4356 	return ret;
4357 }
4358 
4359 void
scg_drag_data_received(SheetControlGUI * scg,GtkWidget * source_widget,double x,double y,GtkSelectionData * selection_data)4360 scg_drag_data_received (SheetControlGUI *scg, GtkWidget *source_widget,
4361 			double x, double y, GtkSelectionData *selection_data)
4362 {
4363 	gchar *target_type = gdk_atom_name (gtk_selection_data_get_target (selection_data));
4364 	const char *sel_data = (const char *)gtk_selection_data_get_data (selection_data);
4365 	gsize sel_len = gtk_selection_data_get_length (selection_data);
4366 
4367 	if (!strcmp (target_type, "text/uri-list")) {
4368 		scg_drag_receive_uri_list (scg, x, y, sel_data, sel_len);
4369 
4370 	} else if (!strncmp (target_type, "image/", 6)) {
4371 		scg_drag_receive_img_data (scg, x, y, sel_data, sel_len);
4372 	} else if (!strcmp (target_type, "GNUMERIC_SAME_PROC")) {
4373 		scg_drag_receive_same_process (scg, source_widget, x, y);
4374 	} else if (!strcmp (target_type, "application/x-gnumeric")) {
4375 		scg_drag_receive_cellregion (scg, x, y, sel_data, sel_len);
4376 	} else
4377 		g_warning ("Unknown target type '%s'!", target_type);
4378 
4379 	if (gnm_debug_flag ("dnd")) {
4380 		if (!strcmp (target_type, "x-special/gnome-copied-files")) {
4381 			char *cdata = g_strndup (sel_data, sel_len);
4382 			g_print ("data length: %d, data: %s\n",
4383 				 (int)sel_len, cdata);
4384 			g_free (cdata);
4385 		} else if (!strcmp (target_type, "_NETSCAPE_URL")) {
4386 			char *cdata = g_strndup (sel_data, sel_len);
4387 			g_print ("data length: %d, data: %s\n",
4388 				 (int)sel_len, cdata);
4389 			g_free (cdata);
4390 		} else if (is_text_target (target_type)) {
4391 			char *cdata = g_strndup (sel_data, sel_len);
4392 			g_print ("data length: %d, data: %s\n",
4393 				 (int)sel_len, cdata);
4394 			g_free (cdata);
4395 		} else if (!strcmp (target_type, "text/html")) {
4396 			char *cdata = g_strndup (sel_data, sel_len);
4397 			/* For mozilla, need to convert the encoding */
4398 			g_print ("data length: %d, data: %s\n", (int)sel_len, cdata);
4399 			g_free (cdata);
4400 		}
4401 	}
4402 
4403 	g_free (target_type);
4404 }
4405 
4406 static void
scg_drag_send_image(G_GNUC_UNUSED SheetControlGUI * scg,GtkSelectionData * selection_data,GSList * objects,gchar const * mime_type)4407 scg_drag_send_image (G_GNUC_UNUSED SheetControlGUI *scg,
4408 		     GtkSelectionData *selection_data,
4409 		     GSList *objects,
4410 		     gchar const *mime_type)
4411 {
4412 	SheetObject *so = NULL;
4413 	GsfOutput *output;
4414 	GsfOutputMemory *omem;
4415 	gsf_off_t osize;
4416 	char *format;
4417 	GSList *ptr;
4418 
4419 	for (ptr = objects; ptr != NULL; ptr = ptr->next) {
4420 		if (GNM_IS_SO_IMAGEABLE (GNM_SO (ptr->data))) {
4421 			so = GNM_SO (ptr->data);
4422 			break;
4423 		}
4424 	}
4425 	if (so == NULL) {
4426 		g_warning ("non imageable object requested as image\n");
4427 		return;
4428 	}
4429 
4430 	format = go_mime_to_image_format (mime_type);
4431 	if (!format) {
4432 		g_warning ("No image format for %s\n", mime_type);
4433 		g_free (format);
4434 		return;
4435 	}
4436 
4437 	output = gsf_output_memory_new ();
4438 	omem = GSF_OUTPUT_MEMORY (output);
4439 	sheet_object_write_image (so, format, -1.0, output, NULL);
4440 	osize = gsf_output_size (output);
4441 
4442 	gtk_selection_data_set
4443 		(selection_data,
4444 		 gtk_selection_data_get_target (selection_data),
4445 		 8, gsf_output_memory_get_bytes (omem), osize);
4446 	gsf_output_close (output);
4447 	g_object_unref (output);
4448 	g_free (format);
4449 }
4450 
4451 static void
scg_drag_send_graph(G_GNUC_UNUSED SheetControlGUI * scg,GtkSelectionData * selection_data,GSList * objects,gchar const * mime_type)4452 scg_drag_send_graph (G_GNUC_UNUSED SheetControlGUI *scg,
4453 		     GtkSelectionData *selection_data,
4454 		     GSList *objects,
4455 		     gchar const *mime_type)
4456 {
4457 	SheetObject *so = NULL;
4458 	GsfOutput *output;
4459 	GsfOutputMemory *omem;
4460 	gsf_off_t osize;
4461 	GSList *ptr;
4462 
4463 	for (ptr = objects; ptr != NULL; ptr = ptr->next)
4464 		if (GNM_IS_SO_EXPORTABLE (GNM_SO (ptr->data))) {
4465 			so = GNM_SO (ptr->data);
4466 			break;
4467 		}
4468 
4469 	if (so == NULL) {
4470 		g_warning ("non exportable object requested\n");
4471 		return;
4472 	}
4473 
4474 	output = gsf_output_memory_new ();
4475 	omem = GSF_OUTPUT_MEMORY (output);
4476 	sheet_object_write_object (so, mime_type, output, NULL,
4477 				   gnm_conventions_default);
4478 	osize = gsf_output_size (output);
4479 
4480 	gtk_selection_data_set
4481 		(selection_data,
4482 		 gtk_selection_data_get_target (selection_data),
4483 		 8, gsf_output_memory_get_bytes (omem), osize);
4484 	gsf_output_close (output);
4485 	g_object_unref (output);
4486 }
4487 
4488 static void
scg_drag_send_clipboard_objects(SheetControl * sc,GtkSelectionData * selection_data,GSList * objects)4489 scg_drag_send_clipboard_objects (SheetControl *sc,
4490 				 GtkSelectionData *selection_data,
4491 				 GSList *objects)
4492 {
4493 	GnmCellRegion	*content = clipboard_copy_obj (sc_sheet (sc), objects);
4494 	GsfOutputMemory *output;
4495 
4496 	if (content == NULL)
4497 		return;
4498 
4499 	output  = gnm_cellregion_to_xml (content);
4500 	gtk_selection_data_set
4501 		(selection_data,
4502 		 gtk_selection_data_get_target (selection_data),
4503 		 8,
4504 		 gsf_output_memory_get_bytes (output),
4505 		 gsf_output_size (GSF_OUTPUT (output)));
4506 	g_object_unref (output);
4507 	cellregion_unref (content);
4508 }
4509 
4510 static void
scg_drag_send_text(SheetControlGUI * scg,GtkSelectionData * sd)4511 scg_drag_send_text (SheetControlGUI *scg, GtkSelectionData *sd)
4512 {
4513 	Sheet *sheet = scg_sheet (scg);
4514 	GnmRange range = sheet_get_extent (sheet, TRUE, TRUE);
4515 	GnmCellRegion *reg = clipboard_copy_range (sheet, &range);
4516 	GString *s = cellregion_to_string (reg, TRUE, sheet_date_conv (sheet));
4517 
4518 	cellregion_unref (reg);
4519 	if (!s)
4520 		return;
4521 	gtk_selection_data_set (sd, gtk_selection_data_get_target (sd),
4522 				8, s->str, s->len);
4523 	g_string_free (s, TRUE);
4524 }
4525 
4526 void
scg_drag_data_get(SheetControlGUI * scg,GtkSelectionData * selection_data)4527 scg_drag_data_get (SheetControlGUI *scg, GtkSelectionData *selection_data)
4528 {
4529 	GdkAtom target = gtk_selection_data_get_target (selection_data);
4530 	gchar *target_name = gdk_atom_name (target);
4531 	GSList *objects = scg->selected_objects
4532 		? go_hash_keys (scg->selected_objects)
4533 		: NULL;
4534 
4535 	if (strcmp (target_name, "GNUMERIC_SAME_PROC") == 0)
4536 		/* Set dummy selection for process internal dnd */
4537 		gtk_selection_data_set (selection_data, target,
4538 					8, (const guint8 *)"", 1);
4539 	else if (strcmp (target_name, "GNUMERIC_SHEET") == 0)
4540 		gtk_selection_data_set (selection_data, target,
4541 					8, (void *)scg, sizeof (scg));
4542 	else if (strcmp (target_name, "application/x-gnumeric") == 0)
4543 		scg_drag_send_clipboard_objects (GNM_SHEET_CONTROL (scg),
4544 			selection_data, objects);
4545 	else if (strcmp (target_name, "application/x-goffice-graph") == 0)
4546 		scg_drag_send_graph (scg, selection_data, objects, target_name);
4547 	else if (strncmp (target_name, "image/", 6) == 0)
4548 		scg_drag_send_image (scg, selection_data, objects, target_name);
4549 	else if (strcmp (target_name, "UTF8_STRING") == 0)
4550 		scg_drag_send_text (scg, selection_data);
4551 
4552 	g_free (target_name);
4553 	g_slist_free (objects);
4554 }
4555 
4556 void
scg_delete_sheet_if_possible(SheetControlGUI * scg)4557 scg_delete_sheet_if_possible (SheetControlGUI *scg)
4558 {
4559 	SheetControl *sc = (SheetControl *) scg;
4560 	Sheet *sheet = scg_sheet (scg);
4561 	Workbook *wb = sheet->workbook;
4562 
4563 	/* If this is the last sheet left, ignore the request */
4564 	if (workbook_sheet_count (wb) != 1) {
4565 		WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
4566 		WorkbookControl *wbc = sc->wbc;
4567 		workbook_sheet_delete (sheet);
4568 		/* Careful: sc just ceased to be valid.  */
4569 		cmd_reorganize_sheets (wbc, old_state, sheet);
4570 	}
4571 }
4572 
4573 void
scg_reload_item_edits(SheetControlGUI * scg)4574 scg_reload_item_edits (SheetControlGUI *scg)
4575 {
4576 	SCG_FOREACH_PANE (scg, pane, {
4577 			if (pane->editor != NULL)
4578 				goc_item_bounds_changed
4579 					(GOC_ITEM (pane->editor));
4580 	});
4581 }
4582