1 /*
2  * Gnumeric's extended canvas used to display a pane
3  *
4  * Author:
5  *     Miguel de Icaza (miguel@kernel.org)
6  *     Jody Goldberg (jody@gnome.org)
7  */
8 #include <gnumeric-config.h>
9 #include <gnm-i18n.h>
10 #include <gnumeric.h>
11 #include <gnm-pane-impl.h>
12 #include <gnm-pane.h>
13 
14 #include <sheet-control-gui-priv.h>
15 #include <gui-util.h>
16 #include <gutils.h>
17 #include <mstyle.h>
18 #include <selection.h>
19 #include <parse-util.h>
20 #include <ranges.h>
21 #include <sheet.h>
22 #include <sheet-view.h>
23 #include <application.h>
24 #include <workbook-view.h>
25 #include <wbc-gtk-impl.h>
26 #include <workbook.h>
27 #include <workbook-cmd-format.h>
28 #include <commands.h>
29 #include <cmd-edit.h>
30 #include <clipboard.h>
31 #include <sheet-filter-combo.h>
32 #include <widgets/gnm-cell-combo-view.h>
33 #include <item-bar.h>
34 #include <item-cursor.h>
35 #include <item-edit.h>
36 #include <item-grid.h>
37 #include <gnumeric-conf.h>
38 
39 #include <gsf/gsf-impl-utils.h>
40 
41 #include <gdk/gdkkeysyms.h>
42 
43 #include <string.h>
44 
45 #define SCROLL_LOCK_MASK GDK_MOD5_MASK
46 
47 typedef GocCanvasClass GnmPaneClass;
48 static GocCanvasClass *parent_klass;
49 
50 static void cb_pane_popup_menu (GnmPane *pane);
51 static void gnm_pane_clear_obj_size_tip (GnmPane *pane);
52 static void gnm_pane_display_obj_size_tip (GnmPane *pane, GocItem *ctrl_pt);
53 
54 /*
55  * For now, application/x-gnumeric is disabled. It handles neither
56  * images nor graphs correctly.
57  */
58 static GtkTargetEntry const drag_types_in[] = {
59 	{(char *) "GNUMERIC_SAME_PROC", GTK_TARGET_SAME_APP, 0},
60 	/* {(char *) "application/x-gnumeric", 0, 0}, */
61 };
62 
63 static GtkTargetEntry const drag_types_out[] = {
64 	{(char *) "GNUMERIC_SAME_PROC", GTK_TARGET_SAME_APP, 0},
65 	{(char *) "application/x-gnumeric", 0, 0},
66 };
67 
68 static gboolean
gnm_pane_guru_key(WBCGtk const * wbcg,GdkEvent * event)69 gnm_pane_guru_key (WBCGtk const *wbcg, GdkEvent *event)
70 {
71 	GtkWidget *entry, *guru = wbc_gtk_get_guru (wbcg);
72 
73 	if (guru == NULL)
74 		return FALSE;
75 
76 	entry = wbcg_get_entry_underlying (wbcg);
77 	gtk_widget_event (entry ? entry : guru, event);
78 	return TRUE;
79 }
80 
81 static gboolean
gnm_pane_object_key_press(GnmPane * pane,GdkEventKey * ev)82 gnm_pane_object_key_press (GnmPane *pane, GdkEventKey *ev)
83 {
84 	SheetControlGUI *scg = pane->simple.scg;
85 	SheetControl    *sc = GNM_SHEET_CONTROL (scg);
86 	gboolean const shift	= 0 != (ev->state & GDK_SHIFT_MASK);
87 	gboolean const control	= 0 != (ev->state & GDK_CONTROL_MASK);
88 	gboolean const alt	= 0 != (ev->state & GDK_MOD1_MASK);
89 	gboolean const symmetric = control && alt;
90 	double   const delta = 1.0 / GOC_CANVAS (pane)->pixels_per_unit;
91 
92 	switch (ev->keyval) {
93 	case GDK_KEY_Escape:
94 		scg_mode_edit (scg);
95 		gnm_app_clipboard_unant ();
96 		return TRUE;
97 
98 	case GDK_KEY_BackSpace: /* Ick! */
99 	case GDK_KEY_KP_Delete:
100 	case GDK_KEY_Delete:
101 		if (scg->selected_objects != NULL) {
102 			cmd_objects_delete (sc->wbc,
103 					    go_hash_keys (scg->selected_objects), NULL);
104 			return TRUE;
105 		}
106 		sc_mode_edit (sc);
107 		break;
108 
109 	case GDK_KEY_Tab:
110 	case GDK_KEY_ISO_Left_Tab:
111 	case GDK_KEY_KP_Tab:
112 		if ((scg_sheet (scg))->sheet_objects != NULL) {
113 			scg_object_select_next (scg, (ev->state & GDK_SHIFT_MASK) != 0);
114 			return TRUE;
115 		}
116 		break;
117 
118 	case GDK_KEY_KP_Left: case GDK_KEY_Left:
119 		scg_objects_nudge (scg, pane, (alt ? 4 : (control ? 3 : 8)), -delta , 0, symmetric, shift);
120 		gnm_pane_display_obj_size_tip (pane, NULL);
121 		return TRUE;
122 	case GDK_KEY_KP_Right: case GDK_KEY_Right:
123 		scg_objects_nudge (scg, pane, (alt ? 4 : (control ? 3 : 8)), delta, 0, symmetric, shift);
124 		gnm_pane_display_obj_size_tip (pane, NULL);
125 		return TRUE;
126 	case GDK_KEY_KP_Up: case GDK_KEY_Up:
127 		scg_objects_nudge (scg, pane, (alt ? 6 : (control ? 1 : 8)), 0, -delta, symmetric, shift);
128 		gnm_pane_display_obj_size_tip (pane, NULL);
129 		return TRUE;
130 	case GDK_KEY_KP_Down: case GDK_KEY_Down:
131 		scg_objects_nudge (scg, pane, (alt ? 6 : (control ? 1 : 8)), 0, delta, symmetric, shift);
132 		gnm_pane_display_obj_size_tip (pane, NULL);
133 		return TRUE;
134 
135 	default:
136 		break;
137 	}
138 	return FALSE;
139 }
140 
141 static gboolean
gnm_pane_key_mode_sheet(GnmPane * pane,GdkEventKey * kevent,gboolean allow_rangesel)142 gnm_pane_key_mode_sheet (GnmPane *pane, GdkEventKey *kevent,
143 			 gboolean allow_rangesel)
144 {
145 	GdkEvent *event = (GdkEvent *)kevent;
146 	SheetControlGUI *scg = pane->simple.scg;
147 	SheetControl *sc = (SheetControl *) scg;
148 	SheetView *sv = sc->view;
149 	Sheet *sheet = sv->sheet;
150 	WBCGtk *wbcg = scg->wbcg;
151 	WorkbookControl * wbc = scg_wbc(scg);
152 	Workbook * wb = wb_control_get_workbook(wbc);
153 	gboolean delayed_movement = FALSE;
154 	gboolean jump_to_bounds;
155 	gboolean is_enter = FALSE;
156 	int first_tab_col;
157 	int state;
158 	void (*movefn) (SheetControlGUI *, int n, gboolean jump, gboolean horiz);
159 	gboolean transition_keys = gnm_conf_get_core_gui_editing_transitionkeys ();
160 	gboolean const end_mode = wbcg->last_key_was_end;
161 	GdkModifierType event_state;
162 
163 	(void)gdk_event_get_state (event, &event_state);
164 	state = gnm_filter_modifiers (event_state);
165 	jump_to_bounds = (event_state & GDK_CONTROL_MASK) != 0;
166 
167 	/* Update end-mode for magic end key stuff. */
168 	if (kevent->keyval != GDK_KEY_End && kevent->keyval != GDK_KEY_KP_End)
169 		wbcg_set_end_mode (wbcg, FALSE);
170 
171 	if (allow_rangesel)
172 		movefn = (event_state & GDK_SHIFT_MASK)
173 			? scg_rangesel_extend
174 			: scg_rangesel_move;
175 	else
176 		movefn = (event_state & GDK_SHIFT_MASK)
177 			? scg_cursor_extend
178 			: scg_cursor_move;
179 
180 	switch (kevent->keyval) {
181 	case GDK_KEY_a:
182 		scg_select_all (scg);
183 		break;
184 	case GDK_KEY_KP_Left:
185 	case GDK_KEY_Left:
186 		if (event_state & GDK_MOD1_MASK)
187 			return TRUE; /* Alt is used for accelerators */
188 
189 		if (event_state & SCROLL_LOCK_MASK)
190 			scg_set_left_col (scg, pane->first.col - 1);
191 		else if (transition_keys && jump_to_bounds) {
192 			delayed_movement = TRUE;
193 			scg_queue_movement (scg, movefn,
194 				-(pane->last_visible.col - pane->first.col),
195 				FALSE, TRUE);
196 		} else
197 			(*movefn) (scg, sheet->text_is_rtl ? 1 : -1,
198 				   jump_to_bounds || end_mode, TRUE);
199 		break;
200 
201 	case GDK_KEY_KP_Right:
202 	case GDK_KEY_Right:
203 		if (event_state & GDK_MOD1_MASK)
204 			return TRUE; /* Alt is used for accelerators */
205 
206 		if (event_state & SCROLL_LOCK_MASK)
207 			scg_set_left_col (scg, pane->first.col + 1);
208 		else if (transition_keys && jump_to_bounds) {
209 			delayed_movement = TRUE;
210 			scg_queue_movement (scg, movefn,
211 				pane->last_visible.col - pane->first.col,
212 				FALSE, TRUE);
213 		} else
214 			(*movefn) (scg, sheet->text_is_rtl ? -1 : 1,
215 				   jump_to_bounds || end_mode, TRUE);
216 		break;
217 
218 	case GDK_KEY_KP_Up:
219 	case GDK_KEY_Up:
220 		if (event_state & SCROLL_LOCK_MASK)
221 			scg_set_top_row (scg, pane->first.row - 1);
222 		else if (transition_keys && jump_to_bounds) {
223 			delayed_movement = TRUE;
224 			scg_queue_movement (scg, movefn,
225 				-(pane->last_visible.row - pane->first.row),
226 				FALSE, FALSE);
227 		} else
228 			(*movefn) (scg, -1, jump_to_bounds || end_mode, FALSE);
229 		break;
230 
231 	case GDK_KEY_KP_Down:
232 	case GDK_KEY_Down:
233 		if (gnm_filter_modifiers (event_state) == GDK_MOD1_MASK) {
234 			/* 1) Any in cell combos ? */
235 			SheetObject *so = sv_wbv (sv)->in_cell_combo;
236 
237 			/* 2) How about any autofilters ? */
238 			if (NULL == so) {
239 				GnmRange r;
240 				GSList *objs = sheet_objects_get (sheet,
241 					range_init_cellpos (&r, &sv->edit_pos),
242 					GNM_FILTER_COMBO_TYPE);
243 				if (objs != NULL)
244 					so = objs->data, g_slist_free (objs);
245 			}
246 
247 			if (NULL != so) {
248 				SheetObjectView	*sov = sheet_object_get_view (so,
249 					(SheetObjectViewContainer *)pane);
250 				gnm_cell_combo_view_popdown
251 					(sov,
252 					 gdk_event_get_time (event));
253 				break;
254 			}
255 		}
256 
257 		if (event_state & SCROLL_LOCK_MASK)
258 			scg_set_top_row (scg, pane->first.row + 1);
259 		else if (transition_keys && jump_to_bounds) {
260 			delayed_movement = TRUE;
261 			scg_queue_movement (scg, movefn,
262 					    pane->last_visible.row - pane->first.row,
263 					    FALSE, FALSE);
264 		} else
265 			(*movefn) (scg, 1, jump_to_bounds || end_mode, FALSE);
266 		break;
267 
268 	case GDK_KEY_KP_Page_Up:
269 	case GDK_KEY_Page_Up:
270 		if (event_state & GDK_CONTROL_MASK) {
271 			if (event_state & GDK_SHIFT_MASK) {
272 				int old_pos = sheet->index_in_wb;
273 				if (old_pos > 0) {
274 					WorkbookSheetState * old_state = workbook_sheet_state_new (wb);
275 					workbook_sheet_move (sheet, -1);
276 					cmd_reorganize_sheets (wbc, old_state, sheet);
277 				}
278 			} else {
279 				gnm_notebook_prev_page (wbcg->bnotebook);
280 			}
281 		} else if ((event_state & GDK_MOD1_MASK) == 0) {
282 			delayed_movement = TRUE;
283 			scg_queue_movement (scg, movefn,
284 					    -(pane->last_visible.row - pane->first.row),
285 					    FALSE, FALSE);
286 		} else {
287 			delayed_movement = TRUE;
288 			scg_queue_movement (scg, movefn,
289 					    -(pane->last_visible.col - pane->first.col),
290 					    FALSE, TRUE);
291 		}
292 		break;
293 
294 	case GDK_KEY_KP_Page_Down:
295 	case GDK_KEY_Page_Down:
296 		if ((event_state & GDK_CONTROL_MASK) != 0){
297 			if ((event_state & GDK_SHIFT_MASK) != 0){
298 				int num_sheets = workbook_sheet_count (wb);
299 				gint old_pos = sheet->index_in_wb;
300 				if (old_pos < num_sheets - 1) {
301 					WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
302 					workbook_sheet_move (sheet, 1);
303 					cmd_reorganize_sheets (wbc, old_state, sheet);
304 				}
305 			} else {
306 				gnm_notebook_next_page (wbcg->bnotebook);
307 			}
308 		} else if ((event_state & GDK_MOD1_MASK) == 0) {
309 			delayed_movement = TRUE;
310 			scg_queue_movement (scg, movefn,
311 					    pane->last_visible.row - pane->first.row,
312 					    FALSE, FALSE);
313 		} else {
314 			delayed_movement = TRUE;
315 			scg_queue_movement (scg, movefn,
316 					    pane->last_visible.col - pane->first.col,
317 					    FALSE, TRUE);
318 		}
319 		break;
320 
321 	case GDK_KEY_KP_Home:
322 	case GDK_KEY_Home:
323 		if (event_state & SCROLL_LOCK_MASK) {
324 			scg_set_left_col (scg, sv->edit_pos.col);
325 			scg_set_top_row (scg, sv->edit_pos.row);
326 		} else if (end_mode) {
327 			/* Same as ctrl-end.  */
328 			GnmRange r = sheet_get_extent (sheet, FALSE, TRUE);
329 			(*movefn) (scg, r.end.col - sv->edit_pos.col, FALSE, TRUE);
330 			(*movefn)(scg, r.end.row - sv->edit_pos.row, FALSE, FALSE);
331 		} else {
332 			/* do the ctrl-home jump to A1 in 2 steps */
333 			(*movefn)(scg, -gnm_sheet_get_max_cols (sheet), FALSE, TRUE);
334 			if ((event_state & GDK_CONTROL_MASK) || transition_keys)
335 				(*movefn)(scg, -gnm_sheet_get_max_rows (sheet), FALSE, FALSE);
336 		}
337 		break;
338 
339 	case GDK_KEY_KP_End:
340 	case GDK_KEY_End:
341 		if (event_state & SCROLL_LOCK_MASK) {
342 			int new_col = sv->edit_pos.col - (pane->last_full.col - pane->first.col);
343 			int new_row = sv->edit_pos.row - (pane->last_full.row - pane->first.row);
344 			scg_set_left_col (scg, new_col);
345 			scg_set_top_row (scg, new_row);
346 		} else if ((event_state & GDK_CONTROL_MASK)) {
347 			GnmRange r = sheet_get_extent (sheet, FALSE, TRUE);
348 
349 			/* do the ctrl-end jump to the extent in 2 steps */
350 			(*movefn)(scg, r.end.col - sv->edit_pos.col, FALSE, TRUE);
351 			(*movefn)(scg, r.end.row - sv->edit_pos.row, FALSE, FALSE);
352 		} else  /* toggle end mode */
353 			wbcg_set_end_mode (wbcg, !end_mode);
354 		break;
355 
356 	case GDK_KEY_KP_Insert:
357 	case GDK_KEY_Insert:
358 		if (gnm_pane_guru_key (wbcg, event))
359 			break;
360 		if (state == GDK_CONTROL_MASK)
361 			gnm_sheet_view_selection_copy (sv, GNM_WBC (wbcg));
362 		else if (state == GDK_SHIFT_MASK)
363 			cmd_paste_to_selection (GNM_WBC (wbcg), sv, PASTE_DEFAULT);
364 		break;
365 
366 	case GDK_KEY_BackSpace:
367 		if (wbcg_is_editing (wbcg))
368 			goto forward;
369 		else if (!wbcg_is_editing (wbcg) && (event_state & GDK_CONTROL_MASK) != 0) {
370 			/* Re-center the view on the active cell */
371 			scg_make_cell_visible (scg, sv->edit_pos.col,
372 					       sv->edit_pos.row, FALSE, TRUE);
373 			break;
374 		}
375 		/* Fall through */
376 
377 	case GDK_KEY_KP_Delete:
378 	case GDK_KEY_Delete:
379 		if (wbcg_is_editing (wbcg)) {
380 			/* stop auto-completion. then do a quick and cheesy update */
381 			wbcg_auto_complete_destroy (wbcg);
382 			SCG_FOREACH_PANE (scg, pane, {
383 				if (pane->editor)
384 					goc_item_invalidate (GOC_ITEM (pane->editor));
385 			});
386 			return TRUE;
387 		}
388 		if (gnm_pane_guru_key (wbcg, event))
389 			break;
390 		if (state == GDK_SHIFT_MASK) {
391 			scg_mode_edit (scg);
392 			gnm_sheet_view_selection_cut (sv, GNM_WBC (wbcg));
393 		} else
394 			cmd_selection_clear (GNM_WBC (wbcg), CLEAR_VALUES);
395 		break;
396 
397 	/*
398 	 * NOTE : Keep these in sync with the condition
399 	 *        for tabs.
400 	 */
401 	case GDK_KEY_KP_Enter:
402 	case GDK_KEY_Return:
403 		if (wbcg_is_editing (wbcg) &&
404 		    (state == GDK_CONTROL_MASK ||
405 		     state == (GDK_CONTROL_MASK|GDK_SHIFT_MASK) ||
406 		     gnm_filter_modifiers (event_state) == GDK_MOD1_MASK))
407 			/* Forward the keystroke to the input line */
408 			return gtk_widget_event (
409 				wbcg_get_entry_underlying (wbcg), (GdkEvent *) event);
410 		is_enter = TRUE;
411 		/* fall down */
412 
413 	case GDK_KEY_Tab:
414 	case GDK_KEY_ISO_Left_Tab:
415 	case GDK_KEY_KP_Tab:
416 		if (gnm_pane_guru_key (wbcg, event))
417 			break;
418 
419 		/* Be careful to restore the editing sheet if we are editing */
420 		if (wbcg_is_editing (wbcg))
421 			sheet = wbcg->editing_sheet;
422 
423 	       	/* registering the cmd clears it,  restore it afterwards */
424 		first_tab_col = sv->first_tab_col;
425 
426 		if (wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL)) {
427 			GODirection dir = gnm_conf_get_core_gui_editing_enter_moves_dir ();
428 
429 			sv->first_tab_col = first_tab_col;
430 
431 			if ((event_state & GDK_MOD1_MASK) &&
432 			    (event_state & GDK_CONTROL_MASK) &&
433 			    !is_enter) {
434 				if (event_state & GDK_SHIFT_MASK)
435 					workbook_cmd_dec_indent (sc->wbc);
436 				else
437 					workbook_cmd_inc_indent	(sc->wbc);
438 			} else if (!is_enter || dir != GO_DIRECTION_NONE) {
439 				gboolean forward = TRUE;
440 				gboolean horizontal = TRUE;
441 				if (is_enter) {
442 					horizontal = go_direction_is_horizontal (dir);
443 					forward = go_direction_is_forward (dir);
444 				} else if ((event_state & GDK_CONTROL_MASK) &&
445 					   ((sc_sheet (sc))->sheet_objects != NULL)) {
446 					scg_object_select_next
447 						(scg, (event_state & GDK_SHIFT_MASK) != 0);
448 					break;
449 				}
450 
451 				if (event_state & GDK_SHIFT_MASK)
452 					forward = !forward;
453 
454 				sv_selection_walk_step (sv, forward, horizontal);
455 
456 				/* invalidate, in case Enter direction changes */
457 				if (is_enter)
458 					sv->first_tab_col = -1;
459 			}
460 		}
461 		break;
462 
463 	case GDK_KEY_Escape:
464 		wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
465 		gnm_app_clipboard_unant ();
466 		break;
467 
468 	case GDK_KEY_F4:
469 		if (wbcg_is_editing (wbcg))
470 			return gtk_widget_event (
471 				wbcg_get_entry_underlying (wbcg), (GdkEvent *) event);
472 		return TRUE;
473 
474 	case GDK_KEY_F2:
475 		if (gnm_pane_guru_key (wbcg, event))
476 			break;
477 
478 		if (wbcg_is_editing (wbcg)) {
479 			GtkWidget *entry = (GtkWidget *) wbcg_get_entry (wbcg);
480 			GtkWindow *top   = wbcg_toplevel (wbcg);
481 			if (entry != gtk_window_get_focus (top)) {
482 				gtk_window_set_focus (top, entry);
483 				return TRUE;
484 			}
485 		}
486 		if (!wbcg_edit_start (wbcg, FALSE, FALSE))
487 			return FALSE; /* attempt to edit failed */
488 		/* fall through */
489 
490 	default:
491 		if (!wbcg_is_editing (wbcg)) {
492 			if ((event_state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0)
493 				return FALSE;
494 
495 			/* If the character is not printable do not start editing */
496 			if (kevent->length == 0)
497 				return FALSE;
498 
499 			if (!wbcg_edit_start (wbcg, TRUE, TRUE))
500 				return FALSE; /* attempt to edit failed */
501 		}
502 		scg_rangesel_stop (scg, FALSE);
503 
504 	forward:
505 		/* Forward the keystroke to the input line */
506 		return gtk_widget_event (wbcg_get_entry_underlying (wbcg),
507 					 (GdkEvent *) event);
508 	}
509 
510 	if (!delayed_movement) {
511 		if (wbcg_is_editing (wbcg))
512 			sheet_update_only_grid (sheet);
513 		else
514 			sheet_update (sheet);
515 	}
516 
517 	return TRUE;
518 }
519 
520 static gboolean
gnm_pane_colrow_key_press(SheetControlGUI * scg,GdkEventKey * event,gboolean allow_rangesel)521 gnm_pane_colrow_key_press (SheetControlGUI *scg, GdkEventKey *event,
522 			   gboolean allow_rangesel)
523 {
524 	SheetControl *sc = (SheetControl *) scg;
525 	SheetView *sv = sc->view;
526 	GnmRange target;
527 
528 	if (allow_rangesel) {
529 		if (scg->rangesel.active)
530 			target = scg->rangesel.displayed;
531 		else
532 			target.start = target.end = sv->edit_pos_real;
533 	} else {
534 		GnmRange const *r = selection_first_range (sv, NULL, NULL);
535 		if (NULL == r)
536 			return FALSE;
537 		target = *r;
538 	}
539 
540 	if (event->state & GDK_SHIFT_MASK) {
541 		if (event->state & GDK_CONTROL_MASK)	/* full sheet */
542 			/* TODO : How to handle ctrl-A too ? */
543 			range_init_full_sheet (&target, sv->sheet);
544 		else {					/* full row */
545 			target.start.col = 0;
546 			target.end.col = gnm_sheet_get_last_col (sv->sheet);
547 		}
548 	} else if (event->state & GDK_CONTROL_MASK) {	/* full col */
549 		target.start.row = 0;
550 		target.end.row = gnm_sheet_get_last_row (sv->sheet);
551 	} else
552 		return FALSE;
553 
554 	/* Accept during rangesel */
555 	if (allow_rangesel)
556 		scg_rangesel_bound (scg,
557 				    target.start.col, target.start.row,
558 				    target.end.col, target.end.row);
559 	/* actually want the ctrl/shift space keys handled by the input module
560 	 * filters during an edit */
561 	else if (!wbcg_is_editing (scg->wbcg))
562 		sv_selection_set (sv, &sv->edit_pos,
563 				  target.start.col, target.start.row,
564 				  target.end.col, target.end.row);
565 	else
566 		return FALSE;
567 
568 	return TRUE;
569 }
570 
571 static gint
gnm_pane_key_press(GtkWidget * widget,GdkEventKey * event)572 gnm_pane_key_press (GtkWidget *widget, GdkEventKey *event)
573 {
574 	GnmPane	*pane = GNM_PANE (widget);
575 	SheetControlGUI *scg = pane->simple.scg;
576 	gboolean	 allow_rangesel;
577 
578 	switch (event->keyval) {
579 	case GDK_KEY_Shift_L:   case GDK_KEY_Shift_R:
580 	case GDK_KEY_Alt_L:     case GDK_KEY_Alt_R:
581 	case GDK_KEY_Control_L: case GDK_KEY_Control_R:
582 	return (*GTK_WIDGET_CLASS (parent_klass)->key_press_event) (widget, event);
583 	}
584 
585 	/* Object manipulation */
586 	if (scg->selected_objects != NULL ||
587 	     scg->wbcg->new_object != NULL) {
588 		if (wbc_gtk_get_guru (scg->wbcg) == NULL &&
589 		    gnm_pane_object_key_press (pane, event))
590 			return TRUE;
591 	}
592 
593 	/* handle grabs after object keys to allow Esc to cancel, and arrows to
594 	 * fine tune position even while dragging */
595 	if (scg->grab_stack > 0)
596 		return TRUE;
597 
598 	allow_rangesel = wbcg_rangesel_possible (scg->wbcg);
599 
600 	/* handle ctrl/shift space before input-method filter steals it */
601 	if (event->keyval == GDK_KEY_space &&
602 	    gnm_pane_colrow_key_press (scg, event, allow_rangesel))
603 		return TRUE;
604 
605 	pane->insert_decimal =
606 		event->keyval == GDK_KEY_KP_Decimal ||
607 		event->keyval == GDK_KEY_KP_Separator;
608 
609 	if (gtk_im_context_filter_keypress (pane->im_context, event))
610 		return TRUE;
611 
612 	gtk_im_context_reset (pane->im_context);
613 
614 	if (gnm_pane_key_mode_sheet (pane, event, allow_rangesel))
615 		return TRUE;
616 
617 	return (*GTK_WIDGET_CLASS (parent_klass)->key_press_event) (widget, event);
618 }
619 
620 static gint
gnm_pane_key_release(GtkWidget * widget,GdkEventKey * event)621 gnm_pane_key_release (GtkWidget *widget, GdkEventKey *event)
622 {
623 	GnmPane *pane = GNM_PANE (widget);
624 	SheetControl *sc = (SheetControl *) pane->simple.scg;
625 
626 	if (pane->simple.scg->grab_stack > 0)
627 		return TRUE;
628 
629 	if (gtk_im_context_filter_keypress (pane->im_context, event))
630 		return TRUE;
631 	/*
632 	 * The status_region normally displays the current edit_pos
633 	 * When we extend the selection it changes to displaying the size of
634 	 * the selected region while we are selecting.  When the shift key
635 	 * is released, or the mouse button is release we need to reset
636 	 * to displaying the edit pos.
637 	 */
638 	if (pane->simple.scg->selected_objects == NULL &&
639 	    (event->keyval == GDK_KEY_Shift_L || event->keyval == GDK_KEY_Shift_R))
640 		wb_view_selection_desc (wb_control_view (sc->wbc), TRUE, NULL);
641 
642 	return (*GTK_WIDGET_CLASS (parent_klass)->key_release_event) (widget, event);
643 }
644 
645 static gint
gnm_pane_focus_in(GtkWidget * widget,GdkEventFocus * event)646 gnm_pane_focus_in (GtkWidget *widget, GdkEventFocus *event)
647 {
648 	GnmPane *pane = GNM_PANE (widget);
649 	gtk_im_context_focus_in (pane->im_context);
650 	return (*GTK_WIDGET_CLASS (parent_klass)->focus_in_event) (widget, event);
651 }
652 
653 static gint
gnm_pane_focus_out(GtkWidget * widget,GdkEventFocus * event)654 gnm_pane_focus_out (GtkWidget *widget, GdkEventFocus *event)
655 {
656 	gnm_pane_clear_obj_size_tip (GNM_PANE (widget));
657 	gtk_im_context_focus_out (GNM_PANE (widget)->im_context);
658 	return (*GTK_WIDGET_CLASS (parent_klass)->focus_out_event) (widget, event);
659 }
660 
661 static void
gnm_pane_realize(GtkWidget * w)662 gnm_pane_realize (GtkWidget *w)
663 {
664 	if (GTK_WIDGET_CLASS (parent_klass)->realize)
665 		(*GTK_WIDGET_CLASS (parent_klass)->realize) (w);
666 
667 	gtk_im_context_set_client_window
668 		(GNM_PANE (w)->im_context,
669 		 gtk_widget_get_window (gtk_widget_get_toplevel (w)));
670 }
671 
672 static void
gnm_pane_unrealize(GtkWidget * widget)673 gnm_pane_unrealize (GtkWidget *widget)
674 {
675 	GnmPane *pane;
676 
677 	pane = GNM_PANE (widget);
678 	g_return_if_fail (pane != NULL);
679 
680 	if (pane->im_context) {
681 		gtk_im_context_set_client_window (pane->im_context, NULL);
682 	}
683 
684 	(*GTK_WIDGET_CLASS (parent_klass)->unrealize)(widget);
685 }
686 
687 static void
gnm_pane_size_allocate(GtkWidget * w,GtkAllocation * allocation)688 gnm_pane_size_allocate (GtkWidget *w, GtkAllocation *allocation)
689 {
690 	GnmPane *pane = GNM_PANE (w);
691 	(*GTK_WIDGET_CLASS (parent_klass)->size_allocate) (w, allocation);
692 	gnm_pane_compute_visible_region (pane, TRUE);
693 }
694 
695 static GtkEditable *
gnm_pane_get_editable(GnmPane const * pane)696 gnm_pane_get_editable (GnmPane const *pane)
697 {
698 	GnmExprEntry *gee = wbcg_get_entry_logical (pane->simple.scg->wbcg);
699 	GtkEntry *entry = gnm_expr_entry_get_entry (gee);
700 	return GTK_EDITABLE (entry);
701 }
702 
703 static void
cb_gnm_pane_commit(GtkIMContext * context,char const * str,GnmPane * pane)704 cb_gnm_pane_commit (GtkIMContext *context, char const *str, GnmPane *pane)
705 {
706 	gint tmp_pos, length;
707 	WBCGtk *wbcg = pane->simple.scg->wbcg;
708 	GtkEditable *editable = gnm_pane_get_editable (pane);
709 
710 	if (!wbcg_is_editing (wbcg) && !wbcg_edit_start (wbcg, TRUE, TRUE))
711 		return;
712 
713 	if (pane->insert_decimal) {
714 		GString const *s = go_locale_get_decimal ();
715 		str = s->str;
716 		length = s->len;
717 	} else
718 		length = strlen (str);
719 
720 	if (gtk_editable_get_selection_bounds (editable, NULL, NULL))
721 		gtk_editable_delete_selection (editable);
722 	else {
723 		tmp_pos = gtk_editable_get_position (editable);
724 		if (gtk_entry_get_overwrite_mode (GTK_ENTRY (editable)))
725 			gtk_editable_delete_text (editable,tmp_pos,tmp_pos+1);
726 	}
727 
728 	tmp_pos = gtk_editable_get_position (editable);
729 	gtk_editable_insert_text (editable, str, length, &tmp_pos);
730 	gtk_editable_set_position (editable, tmp_pos);
731 }
732 
733 static void
cb_gnm_pane_preedit_start(GtkIMContext * context,GnmPane * pane)734 cb_gnm_pane_preedit_start (GtkIMContext *context, GnmPane *pane)
735 {
736 	WBCGtk *wbcg = pane->simple.scg->wbcg;
737 	pane->im_preedit_started = TRUE;
738 	if (!wbcg_is_editing (wbcg))
739 		wbcg_edit_start (wbcg, TRUE, TRUE);
740 }
741 
742 static void
cb_gnm_pane_preedit_changed(GtkIMContext * context,GnmPane * pane)743 cb_gnm_pane_preedit_changed (GtkIMContext *context, GnmPane *pane)
744 {
745 	gchar *preedit_string;
746 	int tmp_pos;
747 	int cursor_pos;
748 	WBCGtk *wbcg = pane->simple.scg->wbcg;
749 	GtkEditable *editable = gnm_pane_get_editable (pane);
750 	if (!pane->im_preedit_started)
751 		return;
752 
753 	tmp_pos = gtk_editable_get_position (editable);
754 	if (pane->preedit_attrs)
755 		pango_attr_list_unref (pane->preedit_attrs);
756 	gtk_im_context_get_preedit_string (pane->im_context, &preedit_string, &pane->preedit_attrs, &cursor_pos);
757 
758 	if (!wbcg_is_editing (wbcg) && !wbcg_edit_start (wbcg, FALSE, TRUE)) {
759 		gtk_im_context_reset (pane->im_context);
760 		pane->preedit_length = 0;
761 		if (pane->preedit_attrs)
762 			pango_attr_list_unref (pane->preedit_attrs);
763 		pane->preedit_attrs = NULL;
764 		g_free (preedit_string);
765 		return;
766 	}
767 
768 	if (pane->preedit_length)
769 		gtk_editable_delete_text (editable,tmp_pos,tmp_pos+pane->preedit_length);
770 	pane->preedit_length = strlen (preedit_string);
771 
772 	if (pane->preedit_length)
773 		gtk_editable_insert_text (editable, preedit_string, pane->preedit_length, &tmp_pos);
774 	g_free (preedit_string);
775 }
776 
777 static void
cb_gnm_pane_preedit_end(GtkIMContext * context,GnmPane * pane)778 cb_gnm_pane_preedit_end (GtkIMContext *context, GnmPane *pane)
779 {
780 	pane->im_preedit_started = FALSE;
781 }
782 
783 static gboolean
cb_gnm_pane_retrieve_surrounding(GtkIMContext * context,GnmPane * pane)784 cb_gnm_pane_retrieve_surrounding (GtkIMContext *context, GnmPane *pane)
785 {
786 	GtkEditable *editable = gnm_pane_get_editable (pane);
787 	gchar *surrounding = gtk_editable_get_chars (editable, 0, -1);
788 	gint   cur_pos     = gtk_editable_get_position (editable);
789 
790 	gtk_im_context_set_surrounding (context,
791 					surrounding, strlen (surrounding),
792 					g_utf8_offset_to_pointer (surrounding, cur_pos) - surrounding);
793 
794 	g_free (surrounding);
795 	return TRUE;
796 }
797 
798 static gboolean
cb_gnm_pane_delete_surrounding(GtkIMContext * context,gint offset,gint n_chars,GnmPane * pane)799 cb_gnm_pane_delete_surrounding (GtkIMContext *context,
800 				gint         offset,
801 				gint         n_chars,
802 				GnmPane    *pane)
803 {
804 	GtkEditable *editable = gnm_pane_get_editable (pane);
805 	gint cur_pos = gtk_editable_get_position (editable);
806 	gtk_editable_delete_text (editable,
807 				  cur_pos + offset,
808 				  cur_pos + offset + n_chars);
809 
810 	return TRUE;
811 }
812 
813 /* create views for the sheet objects now that we exist */
814 static void
cb_pane_init_objs(GnmPane * pane)815 cb_pane_init_objs (GnmPane *pane)
816 {
817 	Sheet *sheet = scg_sheet (pane->simple.scg);
818 	GSList *ptr, *list;
819 
820 	if (sheet != NULL) {
821 		/* List is stored in reverse stacking order.  Top of stack is
822 		 * first.  On creation new foocanvas item get added to
823 		 * the front, so we need to create the views in reverse order */
824 		list = g_slist_reverse (g_slist_copy (sheet->sheet_objects));
825 		for (ptr = list; ptr != NULL ; ptr = ptr->next)
826 			sheet_object_new_view (ptr->data,
827 				(SheetObjectViewContainer *)pane);
828 		g_slist_free (list);
829 	}
830 }
831 
832 static void
cb_ctrl_pts_free(GocItem ** ctrl_pts)833 cb_ctrl_pts_free (GocItem **ctrl_pts)
834 {
835 	int i = 10;
836 	while (i-- > 0)
837 		if (ctrl_pts[i] != NULL)
838 			g_object_unref (ctrl_pts[i]);
839 	g_free (ctrl_pts);
840 }
841 
842 static void
gnm_pane_dispose(GObject * obj)843 gnm_pane_dispose (GObject *obj)
844 {
845 	GnmPane *pane = GNM_PANE (obj);
846 
847 	if (pane->col.canvas != NULL) {
848 		gtk_widget_destroy (GTK_WIDGET (pane->col.canvas));
849 		g_object_unref (pane->col.canvas);
850 		pane->col.canvas = NULL;
851 	}
852 
853 	if (pane->row.canvas != NULL) {
854 		gtk_widget_destroy (GTK_WIDGET (pane->row.canvas));
855 		g_object_unref (pane->row.canvas);
856 		pane->row.canvas = NULL;
857 	}
858 
859 	if (pane->im_context) {
860 		GtkIMContext *imc = pane->im_context;
861 
862 		pane->im_context = NULL;
863 		g_signal_handlers_disconnect_by_func
864 			(imc, cb_gnm_pane_commit, pane);
865 		g_signal_handlers_disconnect_by_func
866 			(imc, cb_gnm_pane_preedit_start, pane);
867 		g_signal_handlers_disconnect_by_func
868 			(imc, cb_gnm_pane_preedit_changed, pane);
869 		g_signal_handlers_disconnect_by_func
870 			(imc, cb_gnm_pane_preedit_end, pane);
871 		g_signal_handlers_disconnect_by_func
872 			(imc, cb_gnm_pane_retrieve_surrounding, pane);
873 		g_signal_handlers_disconnect_by_func
874 			(imc, cb_gnm_pane_delete_surrounding, pane);
875 		gtk_im_context_set_client_window (imc, NULL);
876 		g_object_unref (imc);
877 	}
878 
879 	g_slist_free (pane->cursor.animated);
880 	pane->cursor.animated = NULL;
881 	g_slist_free_full (pane->cursor.expr_range, g_object_unref);
882 	pane->cursor.expr_range = NULL;
883 
884 	g_clear_object (&pane->mouse_cursor);
885 	gnm_pane_clear_obj_size_tip (pane);
886 
887 	if (pane->drag.ctrl_pts) {
888 		g_hash_table_destroy (pane->drag.ctrl_pts);
889 		pane->drag.ctrl_pts = NULL;
890 	}
891 
892 	/* Be anal just in case we somehow manage to remove a pane
893 	 * unexpectedly.  */
894 	pane->grid = NULL;
895 	pane->editor = NULL;
896 	pane->cursor.std = pane->cursor.rangesel = pane->cursor.special = NULL;
897 	pane->size_guide.guide = NULL;
898 	pane->size_guide.start = NULL;
899 	pane->size_guide.points = NULL;
900 
901 	G_OBJECT_CLASS (parent_klass)->dispose (obj);
902 }
903 
904 static void
gnm_pane_init(GnmPane * pane)905 gnm_pane_init (GnmPane *pane)
906 {
907 	GocCanvas	*canvas = GOC_CANVAS (pane);
908 	GocGroup	*root_group = goc_canvas_get_root (canvas);
909 
910 	pane->grid_items   = goc_group_new (root_group);
911 	pane->object_views = goc_group_new (root_group);
912 	pane->action_items = goc_group_new (root_group);
913 
914 	pane->first.col = pane->last_full.col = pane->last_visible.col = 0;
915 	pane->first.row = pane->last_full.row = pane->last_visible.row = 0;
916 	pane->first_offset.x = 0;
917 	pane->first_offset.y = 0;
918 
919 	pane->editor = NULL;
920 	pane->mouse_cursor = NULL;
921 	pane->cursor.rangesel = NULL;
922 	pane->cursor.special = NULL;
923 	pane->cursor.expr_range = NULL;
924 	pane->cursor.animated = NULL;
925 	pane->size_tip = NULL;
926 
927 	pane->slide_handler = NULL;
928 	pane->slide_data = NULL;
929 	pane->sliding_timer = 0;
930 	pane->sliding_x  = pane->sliding_dx = -1;
931 	pane->sliding_y  = pane->sliding_dy = -1;
932 	pane->sliding_adjacent_h = pane->sliding_adjacent_v = FALSE;
933 
934 	pane->drag.button = 0;
935 	pane->drag.ctrl_pts = g_hash_table_new_full (g_direct_hash, g_direct_equal,
936 		NULL, (GDestroyNotify) cb_ctrl_pts_free);
937 
938 	pane->im_context = gtk_im_multicontext_new ();
939 	pane->preedit_length = 0;
940 	pane->preedit_attrs    = NULL;
941 	pane->im_preedit_started = FALSE;
942 
943 	gtk_widget_set_can_focus (GTK_WIDGET (canvas), TRUE);
944 	gtk_widget_set_can_default (GTK_WIDGET (canvas), TRUE);
945 
946 	g_signal_connect (G_OBJECT (pane->im_context), "commit",
947 			  G_CALLBACK (cb_gnm_pane_commit), pane);
948 	g_signal_connect (G_OBJECT (pane->im_context), "preedit_start",
949 			  G_CALLBACK (cb_gnm_pane_preedit_start), pane);
950 	g_signal_connect (G_OBJECT (pane->im_context), "preedit_changed",
951 			  G_CALLBACK (cb_gnm_pane_preedit_changed), pane);
952 	g_signal_connect (G_OBJECT (pane->im_context), "preedit_end",
953 			  G_CALLBACK (cb_gnm_pane_preedit_end), pane);
954 	g_signal_connect (G_OBJECT (pane->im_context), "retrieve_surrounding",
955 			  G_CALLBACK (cb_gnm_pane_retrieve_surrounding),
956 			  pane);
957 	g_signal_connect (G_OBJECT (pane->im_context), "delete_surrounding",
958 			  G_CALLBACK (cb_gnm_pane_delete_surrounding),
959 			  pane);
960 }
961 
962 static void
gnm_pane_class_init(GnmPaneClass * klass)963 gnm_pane_class_init (GnmPaneClass *klass)
964 {
965 	GObjectClass   *gobject_class = G_OBJECT_CLASS (klass);
966 	GtkWidgetClass *widget_class  = (GtkWidgetClass *) klass;
967 
968 	parent_klass = g_type_class_peek_parent (klass);
969 
970 	gobject_class->dispose  = gnm_pane_dispose;
971 
972 	widget_class->realize		   = gnm_pane_realize;
973 	widget_class->unrealize		   = gnm_pane_unrealize;
974 	widget_class->size_allocate	   = gnm_pane_size_allocate;
975 	widget_class->key_press_event	   = gnm_pane_key_press;
976 	widget_class->key_release_event	   = gnm_pane_key_release;
977 	widget_class->focus_in_event	   = gnm_pane_focus_in;
978 	widget_class->focus_out_event	   = gnm_pane_focus_out;
979 #ifdef HAVE_GTK_WIDGET_CLASS_SET_CSS_NAME
980 	gtk_widget_class_set_css_name (widget_class, "pane");
981 #endif
982 
983 	gtk_widget_class_install_style_property
984 		(widget_class,
985 		 g_param_spec_int ("function-indicator-size",
986 				   P_("Function Indicator Size"),
987 				   P_("Size of function indicator"),
988 				   0,
989 				   G_MAXINT,
990 				   10,
991 				   G_PARAM_READABLE));
992 
993 	gtk_widget_class_install_style_property
994 		(widget_class,
995 		 g_param_spec_int ("comment-indicator-size",
996 				   P_("comment Indicator Size"),
997 				   P_("Size of comment indicator"),
998 				   0,
999 				   G_MAXINT,
1000 				   6,
1001 				   G_PARAM_READABLE));
1002 
1003 	gtk_widget_class_install_style_property
1004 		(widget_class,
1005 		 g_param_spec_int ("resize-guide-width",
1006 				   P_("Resize Guide Width"),
1007 				   P_("With of the guides used for resizing columns and rows"),
1008 				   0,
1009 				   G_MAXINT,
1010 				   1,
1011 				   G_PARAM_READABLE));
1012 
1013 	gtk_widget_class_install_style_property
1014 		(widget_class,
1015 		 g_param_spec_int ("pane-resize-guide-width",
1016 				   P_("Pane Resize Guide Width"),
1017 				   P_("With of the guides used for resizing panes"),
1018 				   0,
1019 				   G_MAXINT,
1020 				   7,
1021 				   G_PARAM_READABLE));
1022 
1023 	gtk_widget_class_install_style_property
1024 		(widget_class,
1025 		 g_param_spec_int ("control-circle-size",
1026 				   P_("Control Circle Size"),
1027 				   P_("Size of control circle for sizing sheet objects"),
1028 				   0,
1029 				   G_MAXINT,
1030 				   4,
1031 				   G_PARAM_READABLE));
1032 
1033 	gtk_widget_class_install_style_property
1034 		(widget_class,
1035 		 g_param_spec_int ("control-circle-outline",
1036 				   P_("Control Circle Outline"),
1037 				   P_("Width of outline of control circle for sizing sheet objects"),
1038 				   0,
1039 				   G_MAXINT,
1040 				   1,
1041 				   G_PARAM_READABLE));
1042 }
1043 
GSF_CLASS(GnmPane,gnm_pane,gnm_pane_class_init,gnm_pane_init,GNM_SIMPLE_CANVAS_TYPE)1044 GSF_CLASS (GnmPane, gnm_pane,
1045 	   gnm_pane_class_init, gnm_pane_init,
1046 	   GNM_SIMPLE_CANVAS_TYPE)
1047 #if 0
1048 ;
1049 #endif
1050 
1051 static void
1052 gnm_pane_header_init (GnmPane *pane, SheetControlGUI *scg,
1053 		      gboolean is_col_header)
1054 {
1055 	Sheet *sheet = scg_sheet (scg);
1056 	GocCanvas *canvas = gnm_simple_canvas_new (scg);
1057 	GocGroup *group = goc_canvas_get_root (canvas);
1058 	GocItem *item = goc_item_new (group,
1059 		gnm_item_bar_get_type (),
1060 		"pane",	pane,
1061 		"IsColHeader", is_col_header,
1062 		NULL);
1063 
1064 	/* give a non-constraining default in case something scrolls before we
1065 	 * are realized */
1066 	if (is_col_header) {
1067 		if (sheet && sheet->text_is_rtl)
1068 			goc_canvas_set_direction (canvas, GOC_DIRECTION_RTL);
1069 		pane->col.canvas = g_object_ref_sink (canvas);
1070 		pane->col.item = GNM_ITEM_BAR (item);
1071 	} else {
1072 		pane->row.canvas = g_object_ref_sink (canvas);
1073 		pane->row.item = GNM_ITEM_BAR (item);
1074 	}
1075 
1076 	pane->size_guide.points = NULL;
1077 	pane->size_guide.start  = NULL;
1078 	pane->size_guide.guide  = NULL;
1079 
1080 	if (NULL != scg &&
1081 	    NULL != sheet &&
1082 	    fabs (1. - sheet->last_zoom_factor_used) > 1e-6)
1083 		goc_canvas_set_pixels_per_unit (canvas, sheet->last_zoom_factor_used);
1084 }
1085 
1086 static void
cb_pane_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time,GnmPane * pane)1087 cb_pane_drag_data_received (GtkWidget *widget, GdkDragContext *context,
1088 			    gint x, gint y, GtkSelectionData *selection_data,
1089 			    guint info, guint time, GnmPane *pane)
1090 {
1091 	double wx, wy;
1092 
1093 	if (gnm_debug_flag ("dnd")) {
1094 		gchar *target_name = gdk_atom_name (gtk_selection_data_get_target (selection_data));
1095 		g_printerr ("drag-data-received - %s\n", target_name);
1096 		g_free (target_name);
1097 	}
1098 
1099 	goc_canvas_w2c (GOC_CANVAS (pane), x, y, &wx, &wy);
1100 	scg_drag_data_received (pane->simple.scg,
1101 		gtk_drag_get_source_widget (context),
1102 		wx, wy, selection_data);
1103 }
1104 
1105 static void
cb_pane_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time,SheetControlGUI * scg)1106 cb_pane_drag_data_get (GtkWidget *widget, GdkDragContext *context,
1107 		       GtkSelectionData *selection_data,
1108 		       guint info, guint time,
1109 		       SheetControlGUI *scg)
1110 {
1111 	if (gnm_debug_flag ("dnd")) {
1112 		gchar *target_name = gdk_atom_name (gtk_selection_data_get_target (selection_data));
1113 		g_printerr ("drag-data-get - %s \n", target_name);
1114 		g_free (target_name);
1115 	}
1116 
1117 	scg_drag_data_get (scg, selection_data);
1118 }
1119 
1120 /* Move the rubber bands if we are the source */
1121 static gboolean
cb_pane_drag_motion(GtkWidget * widget,GdkDragContext * context,int x,int y,guint32 time,GnmPane * pane)1122 cb_pane_drag_motion (GtkWidget *widget, GdkDragContext *context,
1123 		     int x, int y, guint32 time, GnmPane *pane)
1124 {
1125 	GtkWidget *source_widget = gtk_drag_get_source_widget (context);
1126 	SheetControlGUI *scg = GNM_PANE (widget)->simple.scg;
1127 
1128 	if ((GNM_IS_PANE (source_widget) &&
1129 	     GNM_PANE (source_widget)->simple.scg == scg)) {
1130 		/* same scg */
1131 		GocCanvas *canvas = GOC_CANVAS (widget);
1132 		GdkModifierType mask;
1133 		GdkWindow *window = gtk_widget_get_parent_window (source_widget);
1134 		double wx, wy;
1135 
1136 		g_object_set_data (G_OBJECT (context),
1137 			"wbcg", scg_wbcg (scg));
1138 		goc_canvas_w2c (canvas, x, y, &wx, &wy);
1139 		wx *= goc_canvas_get_pixels_per_unit (canvas);
1140 		wy *= goc_canvas_get_pixels_per_unit (canvas);
1141 
1142 		gdk_window_get_device_position (window,
1143 			gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (gdk_window_get_display (window))),
1144 			NULL, NULL, &mask);
1145 		gnm_pane_objects_drag (GNM_PANE (source_widget), NULL,
1146 			wx, wy, 8, FALSE, (mask & GDK_SHIFT_MASK) != 0);
1147 		gdk_drag_status (context,
1148 				 (mask & GDK_CONTROL_MASK) != 0 ? GDK_ACTION_COPY : GDK_ACTION_MOVE,
1149 				 time);
1150 	}
1151 	return TRUE;
1152 }
1153 
1154 static void
cb_pane_drag_end(GtkWidget * widget,GdkDragContext * context,GnmPane * source_pane)1155 cb_pane_drag_end (GtkWidget *widget, GdkDragContext *context,
1156 		  GnmPane *source_pane)
1157 {
1158 	/* ungrab any grabbed item */
1159 	GocItem *item = goc_canvas_get_grabbed_item (GOC_CANVAS (source_pane));
1160 	if (item)
1161 		gnm_simple_canvas_ungrab (item);
1162 	/* sync the ctrl-pts with the object in case the drag was canceled. */
1163 	gnm_pane_objects_drag (source_pane, NULL,
1164 		source_pane->drag.origin_x,
1165 		source_pane->drag.origin_y,
1166 		8, FALSE, FALSE);
1167 	source_pane->drag.had_motion = FALSE;
1168 	source_pane->drag.button = 0;
1169 }
1170 
1171 /*
1172  * Move the rubber bands back to original position when cursor leaves
1173  * the scg, but not when it moves to another pane. We use object data,
1174  * and rely on gtk sending drag_move to the new widget before sending
1175  * drag_leave to the old one.
1176  */
1177 static void
cb_pane_drag_leave(GtkWidget * widget,GdkDragContext * context,guint32 time,GnmPane * pane)1178 cb_pane_drag_leave (GtkWidget *widget, GdkDragContext *context,
1179 		    guint32 time, GnmPane *pane)
1180 {
1181 	GtkWidget *source_widget = gtk_drag_get_source_widget (context);
1182 	GnmPane *source_pane;
1183 	WBCGtk *wbcg;
1184 
1185 	if (!source_widget || !GNM_IS_PANE (source_widget)) return;
1186 
1187 	source_pane = GNM_PANE (source_widget);
1188 
1189 	wbcg = scg_wbcg (source_pane->simple.scg);
1190 	if (wbcg == g_object_get_data (G_OBJECT (context), "wbcg"))
1191 		return;
1192 
1193 	gnm_pane_objects_drag (source_pane, NULL,
1194 		source_pane->drag.origin_x,
1195 		source_pane->drag.origin_y,
1196 		8, FALSE, FALSE);
1197 	source_pane->drag.had_motion = FALSE;
1198 }
1199 
1200 static void
gnm_pane_drag_dest_init(GnmPane * pane,SheetControlGUI * scg)1201 gnm_pane_drag_dest_init (GnmPane *pane, SheetControlGUI *scg)
1202 {
1203 	GtkWidget *widget = GTK_WIDGET (pane);
1204 
1205 	gtk_drag_dest_set (widget, GTK_DEST_DEFAULT_ALL,
1206 			   drag_types_in, G_N_ELEMENTS (drag_types_in),
1207 			   GDK_ACTION_COPY | GDK_ACTION_MOVE);
1208 	gtk_drag_dest_add_uri_targets (widget);
1209 	gtk_drag_dest_add_image_targets (widget);
1210 	gtk_drag_dest_add_text_targets (widget);
1211 
1212 	g_object_connect (G_OBJECT (widget),
1213 		"signal::drag-data-received",	G_CALLBACK (cb_pane_drag_data_received), pane,
1214 		"signal::drag-data-get",	G_CALLBACK (cb_pane_drag_data_get),	scg,
1215 		"signal::drag-motion",		G_CALLBACK (cb_pane_drag_motion),	pane,
1216 		"signal::drag-leave",		G_CALLBACK (cb_pane_drag_leave),	pane,
1217 		"signal::drag-end",		G_CALLBACK (cb_pane_drag_end),		pane,
1218 		NULL);
1219 }
1220 
1221 GnmPane *
gnm_pane_new(SheetControlGUI * scg,gboolean col_headers,gboolean row_headers,int index)1222 gnm_pane_new (SheetControlGUI *scg,
1223 	      gboolean col_headers, gboolean row_headers, int index)
1224 {
1225 	GocItem	*item;
1226 	GnmPane	*pane;
1227 	Sheet   *sheet;
1228 
1229 	g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
1230 
1231 	pane = g_object_new (GNM_PANE_TYPE, NULL);
1232 	pane->index      = index;
1233 	pane->simple.scg = scg;
1234 
1235 	goc_canvas_set_document (GOC_CANVAS (pane), wb_control_get_doc (scg_wbc (scg)));
1236 	if (NULL != (sheet = scg_sheet (scg)) &&
1237 	    fabs (1. - sheet->last_zoom_factor_used) > 1e-6)
1238 		goc_canvas_set_pixels_per_unit (GOC_CANVAS (pane),
1239 			sheet->last_zoom_factor_used);
1240 
1241 	gnm_pane_drag_dest_init (pane, scg);
1242 
1243 	item = goc_item_new (pane->grid_items,
1244 		gnm_item_grid_get_type (),
1245 		"SheetControlGUI", scg,
1246 		NULL);
1247 	pane->grid = GNM_ITEM_GRID (item);
1248 
1249 	item = goc_item_new (pane->grid_items,
1250 		gnm_item_cursor_get_type (),
1251 		"SheetControlGUI", scg,
1252 		NULL);
1253 	pane->cursor.std = GNM_ITEM_CURSOR (item);
1254 	if (col_headers)
1255 		gnm_pane_header_init (pane, scg, TRUE);
1256 	else
1257 		pane->col.canvas = NULL;
1258 	if (row_headers)
1259 		gnm_pane_header_init (pane, scg, FALSE);
1260 	else
1261 		pane->row.canvas = NULL;
1262 
1263 	g_signal_connect_swapped (pane, "popup-menu",
1264 		G_CALLBACK (cb_pane_popup_menu), pane);
1265 	g_signal_connect_swapped (G_OBJECT (pane), "realize",
1266 		G_CALLBACK (cb_pane_init_objs), pane);
1267 
1268 	return pane;
1269 }
1270 
1271 /**
1272  * gnm_pane_find_col:
1273  * @pane:
1274  * @x: In canvas coords
1275  * @col_origin: optionally return the canvas coord of the col
1276  *
1277  * Returns the column containing canvas coord @x
1278  **/
1279 int
gnm_pane_find_col(GnmPane const * pane,gint64 x,gint64 * col_origin)1280 gnm_pane_find_col (GnmPane const *pane, gint64 x, gint64 *col_origin)
1281 {
1282 	Sheet const *sheet = scg_sheet (pane->simple.scg);
1283 	int col   = pane->first.col;
1284 	gint64 pixel = pane->first_offset.x;
1285 
1286 	if (x < pixel) {
1287 		while (col > 0) {
1288 			ColRowInfo const *ci = sheet_col_get_info (sheet, --col);
1289 			if (ci->visible) {
1290 				pixel -= ci->size_pixels;
1291 				if (x >= pixel) {
1292 					if (col_origin)
1293 						*col_origin = pixel;
1294 					return col;
1295 				}
1296 			}
1297 		}
1298 		if (col_origin)
1299 			*col_origin = 0;
1300 		return 0;
1301 	}
1302 
1303 	do {
1304 		ColRowInfo const *ci = sheet_col_get_info (sheet, col);
1305 		if (ci->visible) {
1306 			int const tmp = ci->size_pixels;
1307 			if (x <= pixel + tmp) {
1308 				if (col_origin)
1309 					*col_origin = pixel;
1310 				return col;
1311 			}
1312 			pixel += tmp;
1313 		}
1314 	} while (++col < gnm_sheet_get_last_col (sheet));
1315 
1316 	if (col_origin)
1317 		*col_origin = pixel;
1318 	return gnm_sheet_get_last_col (sheet);
1319 }
1320 
1321 /**
1322  * gnm_pane_find_row:
1323  * @pane:
1324  * @y: In canvas coords
1325  * @row_origin: optionally return the canvas coord of the row
1326  *
1327  * Returns the column containing canvas coord @y
1328  **/
1329 int
gnm_pane_find_row(GnmPane const * pane,gint64 y,gint64 * row_origin)1330 gnm_pane_find_row (GnmPane const *pane, gint64 y, gint64 *row_origin)
1331 {
1332 	Sheet const *sheet = scg_sheet (pane->simple.scg);
1333 	int row   = pane->first.row;
1334 	gint64 pixel = pane->first_offset.y;
1335 
1336 	if (y < pixel) {
1337 		while (row > 0) {
1338 			ColRowInfo const *ri = sheet_row_get_info (sheet, --row);
1339 			if (ri->visible) {
1340 				pixel -= ri->size_pixels;
1341 				if (y >= pixel) {
1342 					if (row_origin)
1343 						*row_origin = pixel;
1344 					return row;
1345 				}
1346 			}
1347 		}
1348 		if (row_origin)
1349 			*row_origin = 0;
1350 		return 0;
1351 	}
1352 
1353 	do {
1354 		ColRowInfo const *ri = sheet_row_get_info (sheet, row);
1355 		if (ri->visible) {
1356 			int const tmp = ri->size_pixels;
1357 			if (pixel <= y && y <= pixel + tmp) {
1358 				if (row_origin)
1359 					*row_origin = pixel;
1360 				return row;
1361 			}
1362 			pixel += tmp;
1363 		}
1364 	} while (++row < gnm_sheet_get_last_row (sheet));
1365 	if (row_origin)
1366 		*row_origin = pixel;
1367 	return gnm_sheet_get_last_row (sheet);
1368 }
1369 
1370 /*
1371  * gnm_pane_compute_visible_region : Keeps the top left col/row the same and
1372  *     recalculates the visible boundaries.
1373  *
1374  * @full_recompute:
1375  *       if %TRUE recompute the pixel offsets of the top left row/col
1376  *       else assumes that the pixel offsets of the top left have not changed.
1377  */
1378 void
gnm_pane_compute_visible_region(GnmPane * pane,gboolean const full_recompute)1379 gnm_pane_compute_visible_region (GnmPane *pane,
1380 				 gboolean const full_recompute)
1381 {
1382 	SheetControlGUI const * const scg = pane->simple.scg;
1383 	Sheet const *sheet = scg_sheet (scg);
1384 	GocCanvas   *canvas = GOC_CANVAS (pane);
1385 	gint64 pixels;
1386 	int col, row, width, height;
1387 	GtkAllocation ca;
1388 
1389 	gtk_widget_get_allocation (GTK_WIDGET (canvas), &ca);
1390 
1391 	/* When col/row sizes change we need to do a full recompute */
1392 	if (full_recompute) {
1393 		gint64 col_offset = pane->first_offset.x = scg_colrow_distance_get (scg,
1394 										   TRUE, 0, pane->first.col);
1395 		if (NULL != pane->col.canvas)
1396 			goc_canvas_scroll_to (pane->col.canvas, col_offset / canvas->pixels_per_unit, 0);
1397 
1398 		pane->first_offset.y = scg_colrow_distance_get (scg,
1399 								  FALSE, 0, pane->first.row);
1400 		if (NULL != pane->row.canvas)
1401 			goc_canvas_scroll_to (pane->row.canvas,
1402 					      0, pane->first_offset.y / canvas->pixels_per_unit);
1403 
1404 		goc_canvas_scroll_to (GOC_CANVAS (pane),
1405 				      col_offset / canvas->pixels_per_unit, pane->first_offset.y / canvas->pixels_per_unit);
1406 	}
1407 
1408 	/* Find out the last visible col and the last full visible column */
1409 	pixels = 0;
1410 	col = pane->first.col;
1411 	width = ca.width;
1412 
1413 	do {
1414 		ColRowInfo const * const ci = sheet_col_get_info (sheet, col);
1415 		if (ci->visible) {
1416 			int const bound = pixels + ci->size_pixels;
1417 
1418 			if (bound == width) {
1419 				pane->last_visible.col = col;
1420 				pane->last_full.col = col;
1421 				break;
1422 			}
1423 			if (bound > width) {
1424 				pane->last_visible.col = col;
1425 				if (col == pane->first.col)
1426 					pane->last_full.col = pane->first.col;
1427 				else
1428 					pane->last_full.col = col - 1;
1429 				break;
1430 			}
1431 			pixels = bound;
1432 		}
1433 		++col;
1434 	} while (pixels < width && col < gnm_sheet_get_max_cols (sheet));
1435 
1436 	if (col >= gnm_sheet_get_max_cols (sheet)) {
1437 		pane->last_visible.col = gnm_sheet_get_last_col (sheet);
1438 		pane->last_full.col = gnm_sheet_get_last_col (sheet);
1439 	}
1440 
1441 	/* Find out the last visible row and the last fully visible row */
1442 	pixels = 0;
1443 	row = pane->first.row;
1444 	height = ca.height;
1445 	do {
1446 		ColRowInfo const * const ri = sheet_row_get_info (sheet, row);
1447 		if (ri->visible) {
1448 			int const bound = pixels + ri->size_pixels;
1449 
1450 			if (bound == height) {
1451 				pane->last_visible.row = row;
1452 				pane->last_full.row = row;
1453 				break;
1454 			}
1455 			if (bound > height) {
1456 				pane->last_visible.row = row;
1457 				if (row == pane->first.row)
1458 					pane->last_full.row = pane->first.row;
1459 				else
1460 					pane->last_full.row = row - 1;
1461 				break;
1462 			}
1463 			pixels = bound;
1464 		}
1465 		++row;
1466 	} while (pixels < height && row < gnm_sheet_get_max_rows (sheet));
1467 
1468 	if (row >= gnm_sheet_get_max_rows (sheet)) {
1469 		pane->last_visible.row = gnm_sheet_get_last_row (sheet);
1470 		pane->last_full.row = gnm_sheet_get_last_row (sheet);
1471 	}
1472 
1473 	/* Update the scrollbar sizes for the primary pane */
1474 	if (pane->index == 0)
1475 		sc_scrollbar_config (GNM_SHEET_CONTROL (scg));
1476 
1477 	/* Force the cursor to update its bounds relative to the new visible region */
1478 	gnm_pane_reposition_cursors (pane);
1479 }
1480 
1481 void
gnm_pane_redraw_range(GnmPane * pane,GnmRange const * r)1482 gnm_pane_redraw_range (GnmPane *pane, GnmRange const *r)
1483 {
1484 	SheetControlGUI *scg;
1485 	gint64 x1, y1, x2, y2;
1486 	GnmRange tmp;
1487 	Sheet *sheet;
1488 	double scale = goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
1489 
1490 	g_return_if_fail (GNM_IS_PANE (pane));
1491 
1492 	scg = pane->simple.scg;
1493 	sheet = scg_sheet (scg);
1494 
1495 	if ((r->end.col < pane->first.col) ||
1496 	    (r->end.row < pane->first.row) ||
1497 	    (r->start.col > pane->last_visible.col) ||
1498 	    (r->start.row > pane->last_visible.row))
1499 		return;
1500 
1501 	/* Only draw those regions that are visible */
1502 	tmp.start.col = MAX (pane->first.col, r->start.col);
1503 	tmp.start.row = MAX (pane->first.row, r->start.row);
1504 	tmp.end.col =  MIN (pane->last_visible.col, r->end.col);
1505 	tmp.end.row =  MIN (pane->last_visible.row, r->end.row);
1506 
1507 	/* redraw a border of 2 pixels around the region to handle thick borders
1508 	 * NOTE the 2nd coordinates are excluded so add 1 extra (+2border +1include)
1509 	 */
1510 	x1 = scg_colrow_distance_get (scg, TRUE, pane->first.col, tmp.start.col) +
1511 		pane->first_offset.x;
1512 	y1 = scg_colrow_distance_get (scg, FALSE, pane->first.row, tmp.start.row) +
1513 		pane->first_offset.y;
1514 	x2 = (tmp.end.col < gnm_sheet_get_last_col (sheet))
1515 		? 4 + 1 + x1 + scg_colrow_distance_get (scg, TRUE,
1516 							tmp.start.col, tmp.end.col+1)
1517 		: G_MAXINT64;
1518 	y2 = (tmp.end.row < gnm_sheet_get_last_row (sheet))
1519 		? 4 + 1 + y1 + scg_colrow_distance_get (scg, FALSE,
1520 							tmp.start.row, tmp.end.row+1)
1521 		: G_MAXINT64;
1522 
1523 	goc_canvas_invalidate (&pane->simple.canvas, (x1-2) / scale, (y1-2) / scale, x2 / scale, y2 / scale);
1524 }
1525 
1526 /*****************************************************************************/
1527 
1528 void
gnm_pane_slide_stop(GnmPane * pane)1529 gnm_pane_slide_stop (GnmPane *pane)
1530 {
1531 	if (pane->sliding_timer == 0)
1532 		return;
1533 
1534 	g_source_remove (pane->sliding_timer);
1535 	pane->slide_handler = NULL;
1536 	pane->slide_data = NULL;
1537 	pane->sliding_timer = 0;
1538 }
1539 
1540 static int
col_scroll_step(int dx,Sheet * sheet)1541 col_scroll_step (int dx, Sheet *sheet)
1542 {
1543 	/* FIXME: get from gdk.  */
1544 	int dpi_x_this_screen = 90;
1545 	int start_x = dpi_x_this_screen / 3;
1546 	double double_dx = dpi_x_this_screen / 3.0;
1547 	double step = pow (2.0, (dx - start_x) / double_dx);
1548 
1549 	return (int) (CLAMP (step, 1.0, gnm_sheet_get_max_cols (sheet) / 15.0));
1550 }
1551 
1552 static int
row_scroll_step(int dy,Sheet * sheet)1553 row_scroll_step (int dy, Sheet *sheet)
1554 {
1555 	/* FIXME: get from gdk.  */
1556 	int dpi_y_this_screen = 90;
1557 	int start_y = dpi_y_this_screen / 4;
1558 	double double_dy = dpi_y_this_screen / 8.0;
1559 	double step = pow (2.0, (dy - start_y) / double_dy);
1560 
1561 	return (int) (CLAMP (step, 1.0, gnm_sheet_get_max_rows (sheet) / 15.0));
1562 }
1563 
1564 static gint
cb_pane_sliding(GnmPane * pane)1565 cb_pane_sliding (GnmPane *pane)
1566 {
1567 	int const pane_index = pane->index;
1568 	GnmPane *pane0 = scg_pane (pane->simple.scg, 0);
1569 	GnmPane *pane1 = scg_pane (pane->simple.scg, 1);
1570 	GnmPane *pane3 = scg_pane (pane->simple.scg, 3);
1571 	gboolean slide_x = FALSE, slide_y = FALSE;
1572 	int col = -1, row = -1;
1573 	Sheet *sheet = scg_sheet (pane->simple.scg);
1574 	GnmPaneSlideInfo info;
1575 	GtkAllocation pa;
1576 
1577 	gtk_widget_get_allocation (GTK_WIDGET (pane), &pa);
1578 
1579 	if (pane->sliding_dx > 0) {
1580 		GnmPane *target_pane = pane;
1581 
1582 		slide_x = TRUE;
1583 		if (pane_index == 1 || pane_index == 2) {
1584 			if (!pane->sliding_adjacent_h) {
1585 				int width = pa.width;
1586 				int x = pane->first_offset.x + width + pane->sliding_dx;
1587 
1588 				/* in case pane is narrow */
1589 				col = gnm_pane_find_col (pane, x, NULL);
1590 				if (col > pane0->last_full.col) {
1591 					pane->sliding_adjacent_h = TRUE;
1592 					pane->sliding_dx = 1; /* good enough */
1593 				} else
1594 					slide_x = FALSE;
1595 			} else
1596 				target_pane = pane0;
1597 		} else
1598 			pane->sliding_adjacent_h = FALSE;
1599 
1600 		if (slide_x) {
1601 			col = target_pane->last_full.col +
1602 				col_scroll_step (pane->sliding_dx, sheet);
1603 			if (col >= gnm_sheet_get_last_col (sheet)) {
1604 				col = gnm_sheet_get_last_col (sheet);
1605 				slide_x = FALSE;
1606 			}
1607 		}
1608 	} else if (pane->sliding_dx < 0) {
1609 		slide_x = TRUE;
1610 		col = pane0->first.col - col_scroll_step (-pane->sliding_dx, sheet);
1611 
1612 		if (pane1 != NULL) {
1613 			if (pane_index == 0 || pane_index == 3) {
1614 				GtkAllocation p1a;
1615 				int width;
1616 
1617 				gtk_widget_get_allocation (GTK_WIDGET (pane1),
1618 							   &p1a);
1619 
1620 				width = p1a.width;
1621 				if (pane->sliding_dx > (-width) &&
1622 				    col <= pane1->last_visible.col) {
1623 					int x = pane1->first_offset.x + width + pane->sliding_dx;
1624 					col = gnm_pane_find_col (pane, x, NULL);
1625 					slide_x = FALSE;
1626 				}
1627 			}
1628 
1629 			if (col <= pane1->first.col) {
1630 				col = pane1->first.col;
1631 				slide_x = FALSE;
1632 			}
1633 		} else if (col <= 0) {
1634 			col = 0;
1635 			slide_x = FALSE;
1636 		}
1637 	}
1638 
1639 	if (pane->sliding_dy > 0) {
1640 		GnmPane *target_pane = pane;
1641 
1642 		slide_y = TRUE;
1643 		if (pane_index == 3 || pane_index == 2) {
1644 			if (!pane->sliding_adjacent_v) {
1645 				int height = pa.height;
1646 				int y = pane->first_offset.y + height + pane->sliding_dy;
1647 
1648 				/* in case pane is short */
1649 				row = gnm_pane_find_row (pane, y, NULL);
1650 				if (row > pane0->last_full.row) {
1651 					pane->sliding_adjacent_v = TRUE;
1652 					pane->sliding_dy = 1; /* good enough */
1653 				} else
1654 					slide_y = FALSE;
1655 			} else
1656 				target_pane = pane0;
1657 		} else
1658 			pane->sliding_adjacent_v = FALSE;
1659 
1660 		if (slide_y) {
1661 			row = target_pane->last_full.row +
1662 				row_scroll_step (pane->sliding_dy, sheet);
1663 			if (row >= gnm_sheet_get_last_row (sheet)) {
1664 				row = gnm_sheet_get_last_row (sheet);
1665 				slide_y = FALSE;
1666 			}
1667 		}
1668 	} else if (pane->sliding_dy < 0) {
1669 		slide_y = TRUE;
1670 		row = pane0->first.row - row_scroll_step (-pane->sliding_dy, sheet);
1671 
1672 		if (pane3 != NULL) {
1673 			if (pane_index == 0 || pane_index == 1) {
1674 				GtkAllocation p3a;
1675 				int height;
1676 
1677 				gtk_widget_get_allocation (GTK_WIDGET (pane3),
1678 							   &p3a);
1679 
1680 				height = p3a.height;
1681 				if (pane->sliding_dy > (-height) &&
1682 				    row <= pane3->last_visible.row) {
1683 					int y = pane3->first_offset.y + height + pane->sliding_dy;
1684 					row = gnm_pane_find_row (pane3, y, NULL);
1685 					slide_y = FALSE;
1686 				}
1687 			}
1688 
1689 			if (row <= pane3->first.row) {
1690 				row = pane3->first.row;
1691 				slide_y = FALSE;
1692 			}
1693 		} else if (row <= 0) {
1694 			row = 0;
1695 			slide_y = FALSE;
1696 		}
1697 	}
1698 
1699 	if (col < 0 && row < 0) {
1700 		gnm_pane_slide_stop (pane);
1701 		return TRUE;
1702 	}
1703 
1704 	if (col < 0) {
1705 		col = gnm_pane_find_col (pane, pane->sliding_x, NULL);
1706 	} else if (row < 0)
1707 		row = gnm_pane_find_row (pane, pane->sliding_y, NULL);
1708 
1709 	info.col = col;
1710 	info.row = row;
1711 	info.user_data = pane->slide_data;
1712 	if (pane->slide_handler == NULL ||
1713 	    (*pane->slide_handler) (pane, &info))
1714 		scg_make_cell_visible (pane->simple.scg, col, row, FALSE, TRUE);
1715 
1716 	if (!slide_x && !slide_y)
1717 		gnm_pane_slide_stop (pane);
1718 	else if (pane->sliding_timer == 0)
1719 		pane->sliding_timer = g_timeout_add (300, (GSourceFunc)cb_pane_sliding, pane);
1720 
1721 	return TRUE;
1722 }
1723 
1724 /**
1725  * gnm_pane_handle_motion:
1726  * @pane: The GnmPane managing the scroll
1727  * @canvas: The Canvas the event comes from
1728  * @slide_flags:
1729  * @handler: (scope async): The handler when sliding
1730  * @user_data: closure data
1731  *
1732  * Handle a motion event from a @canvas and scroll the @pane
1733  * depending on how far outside the bounds of @pane the @event is.
1734  * Usually @canvas == @pane however as long as the canvases share a basis
1735  * space they can be different.
1736  **/
1737 gboolean
gnm_pane_handle_motion(GnmPane * pane,GocCanvas * canvas,gint64 x,gint64 y,GnmPaneSlideFlags slide_flags,GnmPaneSlideHandler slide_handler,gpointer user_data)1738 gnm_pane_handle_motion (GnmPane *pane,
1739 			GocCanvas *canvas, gint64 x, gint64 y,
1740 			GnmPaneSlideFlags slide_flags,
1741 			GnmPaneSlideHandler slide_handler,
1742 			gpointer user_data)
1743 {
1744 	GnmPane *pane0, *pane1, *pane3;
1745 	int pindex, width, height;
1746 	gint64 dx = 0, dy = 0, left, top;
1747 	GtkAllocation pa, p0a, p1a, p3a;
1748 
1749 	g_return_val_if_fail (GNM_IS_PANE (pane), FALSE);
1750 	g_return_val_if_fail (GOC_IS_CANVAS (canvas), FALSE);
1751 	g_return_val_if_fail (slide_handler != NULL, FALSE);
1752 
1753 	pindex = pane->index;
1754 	left = pane->first_offset.x;
1755 	top = pane->first_offset.y;
1756 	gtk_widget_get_allocation (GTK_WIDGET (pane), &pa);
1757 	width = pa.width;
1758 	height = pa.height;
1759 
1760 	pane0 = scg_pane (pane->simple.scg, 0);
1761 	gtk_widget_get_allocation (GTK_WIDGET (pane0), &p0a);
1762 
1763 	pane1 = scg_pane (pane->simple.scg, 1);
1764 	if (pane1) gtk_widget_get_allocation (GTK_WIDGET (pane1), &p1a);
1765 
1766 	pane3 = scg_pane (pane->simple.scg, 3);
1767 	if (pane3) gtk_widget_get_allocation (GTK_WIDGET (pane3), &p3a);
1768 
1769 	if (slide_flags & GNM_PANE_SLIDE_X) {
1770 		if (x < left)
1771 			dx = x - left;
1772 		else if (x >= left + width)
1773 			dx = x - width - left;
1774 	}
1775 
1776 	if (slide_flags & GNM_PANE_SLIDE_Y) {
1777 		if (y < top)
1778 			dy = y - top;
1779 		else if (y >= top + height)
1780 			dy = y - height - top;
1781 	}
1782 
1783 	if (pane->sliding_adjacent_h) {
1784 		if (pindex == 0 || pindex == 3) {
1785 			if (dx < 0) {
1786 				x = pane1->first_offset.x;
1787 				dx += p1a.width;
1788 				if (dx > 0)
1789 					x += dx;
1790 				dx = 0;
1791 			} else
1792 				pane->sliding_adjacent_h = FALSE;
1793 		} else {
1794 			if (dx > 0) {
1795 				x = pane0->first_offset.x + dx;
1796 				dx -= p0a.width;
1797 				if (dx < 0)
1798 					dx = 0;
1799 			} else if (dx == 0) {
1800 				/* initiate a reverse scroll of panes 0,3 */
1801 				if ((pane1->last_visible.col+1) != pane0->first.col)
1802 					dx = x - (left + width);
1803 			} else
1804 				dx = 0;
1805 		}
1806 	}
1807 
1808 	if (pane->sliding_adjacent_v) {
1809 		if (pindex == 0 || pindex == 1) {
1810 			if (dy < 0) {
1811 				y = pane3->first_offset.y;
1812 				dy += p3a.height;
1813 				if (dy > 0)
1814 					y += dy;
1815 				dy = 0;
1816 			} else
1817 				pane->sliding_adjacent_v = FALSE;
1818 		} else {
1819 			if (dy > 0) {
1820 				y = pane0->first_offset.y + dy;
1821 				dy -= p0a.height;
1822 				if (dy < 0)
1823 					dy = 0;
1824 			} else if (dy == 0) {
1825 				/* initiate a reverse scroll of panes 0,1 */
1826 				if ((pane3->last_visible.row+1) != pane0->first.row)
1827 					dy = y - (top + height);
1828 			} else
1829 				dy = 0;
1830 		}
1831 	}
1832 
1833 	/* Movement is inside the visible region */
1834 	if (dx == 0 && dy == 0) {
1835 		if (!(slide_flags & GNM_PANE_SLIDE_EXTERIOR_ONLY)) {
1836 			GnmPaneSlideInfo info;
1837 			info.row = gnm_pane_find_row (pane, y, NULL);
1838 			info.col = gnm_pane_find_col (pane, x, NULL);
1839 			info.user_data = user_data;
1840 			(*slide_handler) (pane, &info);
1841 		}
1842 		gnm_pane_slide_stop (pane);
1843 		return TRUE;
1844 	}
1845 
1846 	pane->sliding_x  = x;
1847 	pane->sliding_dx = dx;
1848 	pane->sliding_y  = y;
1849 	pane->sliding_dy = dy;
1850 	pane->slide_handler = slide_handler;
1851 	pane->slide_data = user_data;
1852 
1853 	if (pane->sliding_timer == 0)
1854 		cb_pane_sliding (pane);
1855 	return FALSE;
1856 }
1857 
1858 /* TODO : All the slide_* members of GnmPane really ought to be in
1859  * SheetControlGUI, most of these routines also belong there.  However, since
1860  * the primary point of access is via GnmPane and SCG is very large
1861  * already I'm leaving them here for now.  Move them when we return to
1862  * investigate how to do reverse scrolling for pseudo-adjacent panes.
1863  */
1864 void
gnm_pane_slide_init(GnmPane * pane)1865 gnm_pane_slide_init (GnmPane *pane)
1866 {
1867 	GnmPane *pane0, *pane1, *pane3;
1868 
1869 	g_return_if_fail (GNM_IS_PANE (pane));
1870 
1871 	pane0 = scg_pane (pane->simple.scg, 0);
1872 	pane1 = scg_pane (pane->simple.scg, 1);
1873 	pane3 = scg_pane (pane->simple.scg, 3);
1874 
1875 	pane->sliding_adjacent_h = (pane1 != NULL)
1876 		? (pane1->last_full.col == (pane0->first.col - 1))
1877 		: FALSE;
1878 	pane->sliding_adjacent_v = (pane3 != NULL)
1879 		? (pane3->last_full.row == (pane0->first.row - 1))
1880 		: FALSE;
1881 }
1882 
1883 static gboolean
cb_obj_autoscroll(GnmPane * pane,GnmPaneSlideInfo const * info)1884 cb_obj_autoscroll (GnmPane *pane, GnmPaneSlideInfo const *info)
1885 {
1886 	SheetControlGUI *scg = pane->simple.scg;
1887 	GdkModifierType mask;
1888 	GdkWindow *window = gtk_widget_get_parent_window (GTK_WIDGET (pane));
1889 
1890 	/* Cheesy hack calculate distance we move the screen, this loses the
1891 	 * mouse position */
1892 	double dx = pane->first_offset.x;
1893 	double dy = pane->first_offset.y;
1894 	scg_make_cell_visible (scg, info->col, info->row, FALSE, TRUE);
1895 	dx = pane->first_offset.x - dx;
1896 	dy = pane->first_offset.y - dy;
1897 
1898 	pane->drag.had_motion = TRUE;
1899 	gdk_window_get_device_position (window,
1900 		gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (gdk_window_get_display (window))),
1901 		NULL, NULL, &mask);
1902 	scg_objects_drag (pane->simple.scg, pane,
1903 			  NULL, &dx, &dy, 8, FALSE, (mask & GDK_SHIFT_MASK) != 0, TRUE);
1904 
1905 	pane->drag.last_x += dx;
1906 	pane->drag.last_y += dy;
1907 	return FALSE;
1908 }
1909 
1910 void
gnm_pane_object_autoscroll(GnmPane * pane,GdkDragContext * context,gint x,gint y,guint time)1911 gnm_pane_object_autoscroll (GnmPane *pane, GdkDragContext *context,
1912 			    gint x, gint y, guint time)
1913 {
1914 	int const pane_index = pane->index;
1915 	SheetControlGUI *scg = pane->simple.scg;
1916 	GnmPane *pane0 = scg_pane (scg, 0);
1917 	GnmPane *pane1 = scg_pane (scg, 1);
1918 	GnmPane *pane3 = scg_pane (scg, 3);
1919 	GtkWidget *w = GTK_WIDGET (pane);
1920 	GtkAllocation wa;
1921 	gint dx, dy;
1922 
1923 	gtk_widget_get_allocation (w, &wa);
1924 
1925 	if (y < wa.y) {
1926 		if (pane_index < 2 && pane3 != NULL) {
1927 			w = GTK_WIDGET (pane3);
1928 			gtk_widget_get_allocation (w, &wa);
1929 		}
1930 		dy = y - wa.y;
1931 		g_return_if_fail (dy <= 0);
1932 	} else if (y >= (wa.y + wa.height)) {
1933 		if (pane_index >= 2) {
1934 			w = GTK_WIDGET (pane0);
1935 			gtk_widget_get_allocation (w, &wa);
1936 		}
1937 		dy = y - (wa.y + wa.height);
1938 		g_return_if_fail (dy >= 0);
1939 	} else
1940 		dy = 0;
1941 	if (x < wa.x) {
1942 		if ((pane_index == 0 || pane_index == 3) && pane1 != NULL) {
1943 			w = GTK_WIDGET (pane1);
1944 			gtk_widget_get_allocation (w, &wa);
1945 		}
1946 		dx = x - wa.x;
1947 		g_return_if_fail (dx <= 0);
1948 	} else if (x >= (wa.x + wa.width)) {
1949 		if (pane_index >= 2) {
1950 			w = GTK_WIDGET (pane0);
1951 			gtk_widget_get_allocation (w, &wa);
1952 		}
1953 		dx = x - (wa.x + wa.width);
1954 		g_return_if_fail (dx >= 0);
1955 	} else
1956 		dx = 0;
1957 
1958 	g_object_set_data (G_OBJECT (context),
1959 			   "wbcg", scg_wbcg (scg));
1960 	pane->sliding_dx    = dx;
1961 	pane->sliding_dy    = dy;
1962 	pane->slide_handler = &cb_obj_autoscroll;
1963 	pane->slide_data    = NULL;
1964 	pane->sliding_x     = x;
1965 	pane->sliding_y     = y;
1966 	if (pane->sliding_timer == 0)
1967 		cb_pane_sliding (pane);
1968 }
1969 
1970 /**
1971  * gnm_pane_object_group:
1972  * @pane: #GnmPane
1973  *
1974  * Returns: (transfer none): the #GocGroup including all #SheetObjectView
1975  * instances in @pane.
1976  **/
1977 GocGroup *
gnm_pane_object_group(GnmPane * pane)1978 gnm_pane_object_group (GnmPane *pane)
1979 {
1980 	return pane->object_views;
1981 }
1982 
1983 static void
gnm_pane_clear_obj_size_tip(GnmPane * pane)1984 gnm_pane_clear_obj_size_tip (GnmPane *pane)
1985 {
1986 	if (pane->size_tip) {
1987 		gtk_widget_destroy (gtk_widget_get_toplevel (pane->size_tip));
1988 		pane->size_tip = NULL;
1989 	}
1990 }
1991 
1992 static void
gnm_pane_display_obj_size_tip(GnmPane * pane,GocItem * ctrl_pt)1993 gnm_pane_display_obj_size_tip (GnmPane *pane, GocItem *ctrl_pt)
1994 {
1995 	SheetControlGUI *scg = pane->simple.scg;
1996 	double const *coords;
1997 	double pts[4];
1998 	char *msg;
1999 	SheetObjectAnchor anchor;
2000 
2001 	if (pane->size_tip == NULL) {
2002 		GtkWidget *cw = GTK_WIDGET (pane);
2003 		GtkWidget *top;
2004 		int x, y;
2005 
2006 		if (ctrl_pt == NULL) {
2007 			/*
2008 			 * Keyboard navigation when we are not displaying
2009 			 * a tooltip already.
2010 			 */
2011 			return;
2012 		}
2013 
2014 		pane->size_tip = gnm_create_tooltip (cw);
2015 		top = gtk_widget_get_toplevel (pane->size_tip);
2016 
2017 		gnm_canvas_get_screen_position (ctrl_pt->canvas,
2018 						ctrl_pt->x1, ctrl_pt->y1,
2019 						&x, &y);
2020 		gtk_window_move (GTK_WINDOW (top), x + 10, y + 10);
2021 		gtk_widget_show_all (top);
2022 	}
2023 
2024 	g_return_if_fail (pane->cur_object != NULL);
2025 	g_return_if_fail (pane->size_tip != NULL);
2026 
2027 	coords = g_hash_table_lookup (scg->selected_objects, pane->cur_object);
2028 	anchor = *sheet_object_get_anchor (pane->cur_object);
2029 	scg_object_coords_to_anchor (scg, coords, &anchor);
2030 	sheet_object_anchor_to_pts (&anchor, scg_sheet (scg), pts);
2031 	msg = g_strdup_printf (_("%.1f x %.1f pts\n%d x %d pixels"),
2032 		MAX (fabs (pts[2] - pts[0]), 0),
2033 		MAX (fabs (pts[3] - pts[1]), 0),
2034 		MAX ((int)floor (fabs (coords [2] - coords [0]) + 0.5), 0),
2035 		MAX ((int)floor (fabs (coords [3] - coords [1]) + 0.5), 0));
2036 	gtk_label_set_text (GTK_LABEL (pane->size_tip), msg);
2037 	g_free (msg);
2038 }
2039 
2040 void
gnm_pane_bound_set(GnmPane * pane,int start_col,int start_row,int end_col,int end_row)2041 gnm_pane_bound_set (GnmPane *pane,
2042 		    int start_col, int start_row,
2043 		    int end_col, int end_row)
2044 {
2045 	GnmRange r;
2046 
2047 	g_return_if_fail (pane != NULL);
2048 
2049 	range_init (&r, start_col, start_row, end_col, end_row);
2050 	goc_item_set (GOC_ITEM (pane->grid),
2051 			     "bound", &r,
2052 			     NULL);
2053 }
2054 
2055 /****************************************************************************/
2056 
2057 void
gnm_pane_size_guide_start(GnmPane * pane,gboolean vert,int colrow,gboolean is_colrow_resize)2058 gnm_pane_size_guide_start (GnmPane *pane,
2059 			   gboolean vert, int colrow, gboolean is_colrow_resize)
2060 {
2061 	SheetControlGUI const *scg;
2062 	double x0, y0, x1, y1, pos;
2063 	double zoom;
2064 	GOStyle *style;
2065 	GdkRGBA rgba;
2066 	GtkStyleContext *context;
2067 	const char *guide_class = is_colrow_resize ? "resize-guide" : "pane-resize-guide";
2068 	const char *colrow_class = vert ? "col" : "row";
2069 	const char *width_prop_name = is_colrow_resize ? "resize-guide-width" : "pane-resize-guide-width";
2070 	int width;
2071 
2072 	g_return_if_fail (pane != NULL);
2073 	g_return_if_fail (pane->size_guide.guide  == NULL);
2074 	g_return_if_fail (pane->size_guide.start  == NULL);
2075 	g_return_if_fail (pane->size_guide.points == NULL);
2076 
2077 	zoom = GOC_CANVAS (pane)->pixels_per_unit;
2078 	scg = pane->simple.scg;
2079 
2080 	pos = scg_colrow_distance_get (scg, vert, 0, colrow) / zoom;
2081 	if (vert) {
2082 		x0 = pos;
2083 		y0 = scg_colrow_distance_get (scg, FALSE,
2084 					      0, pane->first.row) / zoom;
2085 		x1 = pos;
2086 		y1 = scg_colrow_distance_get (scg, FALSE,
2087 					      0, pane->last_visible.row+1) / zoom;
2088 	} else {
2089 		x0 = scg_colrow_distance_get (scg, TRUE,
2090 					      0, pane->first.col) / zoom;
2091 		y0 = pos;
2092 		x1 = scg_colrow_distance_get (scg, TRUE,
2093 					      0, pane->last_visible.col+1) / zoom;
2094 		y1 = pos;
2095 	}
2096 
2097 	gtk_widget_style_get (GTK_WIDGET (pane), width_prop_name, &width, NULL);
2098 
2099 	/* Guideline positioning is done in gnm_pane_size_guide_motion */
2100 	pane->size_guide.guide = goc_item_new (pane->action_items,
2101 		GOC_TYPE_LINE,
2102 		"x0", x0, "y0", y0,
2103 		"x1", x1, "y1", y1,
2104 		NULL);
2105 	style = go_styled_object_get_style (GO_STYLED_OBJECT (pane->size_guide.guide));
2106 	style->line.width = width;
2107 	context = goc_item_get_style_context (pane->size_guide.guide);
2108 	gtk_style_context_add_class (context, guide_class);
2109 	gtk_style_context_add_class (context, colrow_class);
2110 	if (is_colrow_resize)
2111 		gtk_style_context_add_class (context, "end");
2112 	gnm_style_context_get_color (context, GTK_STATE_FLAG_SELECTED, &rgba);
2113 	if (gnm_debug_flag ("css")) {
2114 		char *name = g_strconcat ("pane.", guide_class, ".", colrow_class,
2115 					  (is_colrow_resize ? ".resize" : ""),
2116 					  ".color", NULL);
2117 		gnm_css_debug_color (name, &rgba);
2118 		g_free (name);
2119 	}
2120 	go_color_from_gdk_rgba (&rgba, &style->line.color);
2121 
2122 	if (is_colrow_resize) {
2123 		pane->size_guide.start = goc_item_new (pane->action_items,
2124 			GOC_TYPE_LINE,
2125 			"x0", x0, "y0", y0,
2126 			"x1", x1, "y1", y1,
2127 			NULL);
2128 		style = go_styled_object_get_style (GO_STYLED_OBJECT (pane->size_guide.start));
2129 		context = goc_item_get_style_context (pane->size_guide.start);
2130 		gtk_style_context_add_class (context, guide_class);
2131 		gtk_style_context_add_class (context, colrow_class);
2132 		gtk_style_context_add_class (context, "start");
2133 		gnm_style_context_get_color (context, GTK_STATE_FLAG_SELECTED, &rgba);
2134 		go_color_from_gdk_rgba (&rgba, &style->line.color);
2135 		style->line.width = width;
2136 	}
2137 }
2138 
2139 void
gnm_pane_size_guide_stop(GnmPane * pane)2140 gnm_pane_size_guide_stop (GnmPane *pane)
2141 {
2142 	g_return_if_fail (pane != NULL);
2143 
2144 	g_clear_object (&pane->size_guide.start);
2145 	g_clear_object (&pane->size_guide.guide);
2146 }
2147 
2148 /**
2149  * gnm_pane_size_guide_motion:
2150  * @p: #GnmPane
2151  * @vert: %TRUE for a vertical guide, %FALSE for horizontal
2152  * @guide_pos: in unscaled sheet pixel coords
2153  *
2154  * Moves the guide line to @guide_pos.
2155  * NOTE : gnm_pane_size_guide_start must be called before any calls to
2156  *	gnm_pane_size_guide_motion
2157  **/
2158 void
gnm_pane_size_guide_motion(GnmPane * pane,gboolean vert,gint64 guide_pos)2159 gnm_pane_size_guide_motion (GnmPane *pane, gboolean vert, gint64 guide_pos)
2160 {
2161 	GocItem *resize_guide = GOC_ITEM (pane->size_guide.guide);
2162 	double const	 scale	    = 1. / resize_guide->canvas->pixels_per_unit;
2163 	double x;
2164 
2165 	x = scale * (guide_pos - .5);
2166 	if (vert)
2167 		goc_item_set (resize_guide, "x0", x, "x1", x, NULL);
2168 	else
2169 		goc_item_set (resize_guide, "y0", x, "y1", x, NULL);
2170 }
2171 
2172 /****************************************************************************/
2173 
2174 static void
cb_update_ctrl_pts(SheetObject * so,GocItem ** ctrl_pts,GnmPane * pane)2175 cb_update_ctrl_pts (SheetObject *so, GocItem **ctrl_pts, GnmPane *pane)
2176 {
2177 	double *coords = g_hash_table_lookup (
2178 		pane->simple.scg->selected_objects, so);
2179 	scg_object_anchor_to_coords (pane->simple.scg, sheet_object_get_anchor (so), coords);
2180 	gnm_pane_object_update_bbox (pane, so);
2181 }
2182 
2183 /* Called when the zoom changes */
2184 void
gnm_pane_reposition_cursors(GnmPane * pane)2185 gnm_pane_reposition_cursors (GnmPane *pane)
2186 {
2187 	GSList *l;
2188 
2189 	gnm_item_cursor_reposition (pane->cursor.std);
2190 	if (NULL != pane->cursor.rangesel)
2191 		gnm_item_cursor_reposition (pane->cursor.rangesel);
2192 	if (NULL != pane->cursor.special)
2193 		gnm_item_cursor_reposition (pane->cursor.special);
2194 	for (l = pane->cursor.expr_range; l; l = l->next)
2195 		gnm_item_cursor_reposition (GNM_ITEM_CURSOR (l->data));
2196 	for (l = pane->cursor.animated; l; l = l->next)
2197 		gnm_item_cursor_reposition (GNM_ITEM_CURSOR (l->data));
2198 
2199 	/* ctrl pts do not scale with the zoom, compensate */
2200 	if (pane->drag.ctrl_pts != NULL)
2201 		g_hash_table_foreach (pane->drag.ctrl_pts,
2202 			(GHFunc) cb_update_ctrl_pts, pane);
2203 }
2204 
2205 gboolean
gnm_pane_cursor_bound_set(GnmPane * pane,GnmRange const * r)2206 gnm_pane_cursor_bound_set (GnmPane *pane, GnmRange const *r)
2207 {
2208 	return gnm_item_cursor_bound_set (pane->cursor.std, r);
2209 }
2210 
2211 /****************************************************************************/
2212 
2213 gboolean
gnm_pane_rangesel_bound_set(GnmPane * pane,GnmRange const * r)2214 gnm_pane_rangesel_bound_set (GnmPane *pane, GnmRange const *r)
2215 {
2216 	return gnm_item_cursor_bound_set (pane->cursor.rangesel, r);
2217 }
2218 void
gnm_pane_rangesel_start(GnmPane * pane,GnmRange const * r)2219 gnm_pane_rangesel_start (GnmPane *pane, GnmRange const *r)
2220 {
2221 	GocItem *item;
2222 	SheetControlGUI *scg = pane->simple.scg;
2223 
2224 	g_return_if_fail (pane->cursor.rangesel == NULL);
2225 
2226 	/* Hide the primary cursor while the range selection cursor is visible
2227 	 * and we are selecting on a different sheet than the expr being edited */
2228 	if (scg_sheet (scg) != wb_control_cur_sheet (scg_wbc (scg)))
2229 		gnm_item_cursor_set_visibility (pane->cursor.std, FALSE);
2230 	item = goc_item_new (pane->grid_items,
2231 		gnm_item_cursor_get_type (),
2232 		"SheetControlGUI", scg,
2233 		"style",	GNM_ITEM_CURSOR_ANTED,
2234 		NULL);
2235 	pane->cursor.rangesel = GNM_ITEM_CURSOR (item);
2236 	gnm_item_cursor_bound_set (pane->cursor.rangesel, r);
2237 }
2238 
2239 void
gnm_pane_rangesel_stop(GnmPane * pane)2240 gnm_pane_rangesel_stop (GnmPane *pane)
2241 {
2242 	g_return_if_fail (pane->cursor.rangesel != NULL);
2243 
2244 	g_clear_object (&pane->cursor.rangesel);
2245 
2246 	/* Make the primary cursor visible again */
2247 	gnm_item_cursor_set_visibility (pane->cursor.std, TRUE);
2248 
2249 	gnm_pane_slide_stop (pane);
2250 }
2251 
2252 /****************************************************************************/
2253 
2254 gboolean
gnm_pane_special_cursor_bound_set(GnmPane * pane,GnmRange const * r)2255 gnm_pane_special_cursor_bound_set (GnmPane *pane, GnmRange const *r)
2256 {
2257 	return gnm_item_cursor_bound_set (pane->cursor.special, r);
2258 }
2259 
2260 void
gnm_pane_special_cursor_start(GnmPane * pane,int style,int button)2261 gnm_pane_special_cursor_start (GnmPane *pane, int style, int button)
2262 {
2263 	GocItem *item;
2264 	GocCanvas *canvas = GOC_CANVAS (pane);
2265 
2266 	g_return_if_fail (pane->cursor.special == NULL);
2267 	item = goc_item_new (
2268 		GOC_GROUP (canvas->root),
2269 		gnm_item_cursor_get_type (),
2270 		"SheetControlGUI", pane->simple.scg,
2271 		"style",	   style,
2272 		"button",	   button,
2273 		NULL);
2274 	pane->cursor.special = GNM_ITEM_CURSOR (item);
2275 }
2276 
2277 void
gnm_pane_special_cursor_stop(GnmPane * pane)2278 gnm_pane_special_cursor_stop (GnmPane *pane)
2279 {
2280 	g_return_if_fail (pane->cursor.special != NULL);
2281 
2282 	g_clear_object (&pane->cursor.special);
2283 }
2284 
2285 void
gnm_pane_mouse_cursor_set(GnmPane * pane,GdkCursor * c)2286 gnm_pane_mouse_cursor_set (GnmPane *pane, GdkCursor *c)
2287 {
2288 	g_object_ref (c);
2289 	if (pane->mouse_cursor)
2290 		g_object_unref (pane->mouse_cursor);
2291 	pane->mouse_cursor = c;
2292 }
2293 
2294 /****************************************************************************/
2295 
2296 
2297 void
gnm_pane_expr_cursor_bound_set(GnmPane * pane,GnmRange const * r,GOColor color)2298 gnm_pane_expr_cursor_bound_set (GnmPane *pane, GnmRange const *r,
2299 				GOColor color)
2300 {
2301 	GnmItemCursor *cursor;
2302 
2303 	cursor = (GnmItemCursor *) goc_item_new
2304 		(GOC_GROUP (GOC_CANVAS (pane)->root),
2305 		 gnm_item_cursor_get_type (),
2306 		 "SheetControlGUI",	pane->simple.scg,
2307 		 "style",		GNM_ITEM_CURSOR_EXPR_RANGE,
2308 		 "color",		color,
2309 		 NULL);
2310 
2311 	gnm_item_cursor_bound_set (cursor, r);
2312 	pane->cursor.expr_range = g_slist_prepend
2313 		(pane->cursor.expr_range, cursor);
2314 }
2315 
2316 void
gnm_pane_expr_cursor_stop(GnmPane * pane)2317 gnm_pane_expr_cursor_stop (GnmPane *pane)
2318 {
2319 	g_slist_free_full (pane->cursor.expr_range, g_object_unref);
2320 	pane->cursor.expr_range = NULL;
2321 }
2322 
2323 /****************************************************************************/
2324 
2325 void
gnm_pane_edit_start(GnmPane * pane)2326 gnm_pane_edit_start (GnmPane *pane)
2327 {
2328 	GocCanvas *canvas = GOC_CANVAS (pane);
2329 
2330 	g_return_if_fail (pane->editor == NULL);
2331 
2332 	/* edit item handles visibility checks */
2333 	pane->editor = (GnmItemEdit *) goc_item_new (
2334 		GOC_GROUP (canvas->root),
2335 		gnm_item_edit_get_type (),
2336 		"SheetControlGUI",	pane->simple.scg,
2337 		NULL);
2338 }
2339 
2340 void
gnm_pane_edit_stop(GnmPane * pane)2341 gnm_pane_edit_stop (GnmPane *pane)
2342 {
2343 	g_clear_object (&pane->editor);
2344 }
2345 
2346 void
gnm_pane_objects_drag(GnmPane * pane,SheetObject * so,gdouble new_x,gdouble new_y,int drag_type,gboolean symmetric,gboolean snap_to_grid)2347 gnm_pane_objects_drag (GnmPane *pane, SheetObject *so,
2348 		       gdouble new_x, gdouble new_y, int drag_type,
2349 		       gboolean symmetric,gboolean snap_to_grid)
2350 {
2351 	double dx, dy;
2352 	dx = new_x - pane->drag.last_x;
2353 	dy = new_y - pane->drag.last_y;
2354 	pane->drag.had_motion = TRUE;
2355 	scg_objects_drag (pane->simple.scg, pane,
2356 		so, &dx, &dy, drag_type, symmetric, snap_to_grid, TRUE);
2357 
2358 	pane->drag.last_x += dx;
2359 	pane->drag.last_y += dy;
2360 }
2361 
2362 /* new_x and new_y are in world coords */
2363 static void
gnm_pane_object_move(GnmPane * pane,GObject * ctrl_pt,gdouble new_x,gdouble new_y,gboolean symmetric,gboolean snap_to_grid)2364 gnm_pane_object_move (GnmPane *pane, GObject *ctrl_pt,
2365 		      gdouble new_x, gdouble new_y,
2366 		      gboolean symmetric,
2367 		      gboolean snap_to_grid)
2368 {
2369 	int const idx = GPOINTER_TO_INT (g_object_get_data (ctrl_pt, "index"));
2370 	pane->cur_object  = g_object_get_data (G_OBJECT (ctrl_pt), "so");
2371 
2372 	gnm_pane_objects_drag (pane, pane->cur_object, new_x, new_y, idx,
2373 			       symmetric, snap_to_grid);
2374 	if (idx != 8)
2375 		gnm_pane_display_obj_size_tip (pane, GOC_ITEM (ctrl_pt));
2376 }
2377 
2378 static gboolean
cb_slide_handler(GnmPane * pane,GnmPaneSlideInfo const * info)2379 cb_slide_handler (GnmPane *pane, GnmPaneSlideInfo const *info)
2380 {
2381 	guint64 x, y;
2382 	SheetControlGUI const *scg = pane->simple.scg;
2383 	double const scale = 1. / GOC_CANVAS (pane)->pixels_per_unit;
2384 
2385 	x = scg_colrow_distance_get (scg, TRUE, pane->first.col, info->col);
2386 	x += pane->first_offset.x;
2387 	y = scg_colrow_distance_get (scg, FALSE, pane->first.row, info->row);
2388 	y += pane->first_offset.y;
2389 
2390 	gnm_pane_object_move (pane, info->user_data,
2391 		x * scale, y * scale, FALSE, FALSE);
2392 
2393 	return TRUE;
2394 }
2395 
2396 static void
cb_ptr_array_free(GPtrArray * actions)2397 cb_ptr_array_free (GPtrArray *actions)
2398 {
2399 	g_ptr_array_free (actions, TRUE);
2400 }
2401 
2402 /* event and so can be NULL */
2403 void
gnm_pane_display_object_menu(GnmPane * pane,SheetObject * so,GdkEvent * event)2404 gnm_pane_display_object_menu (GnmPane *pane, SheetObject *so, GdkEvent *event)
2405 {
2406 	SheetControlGUI *scg = pane->simple.scg;
2407 	GPtrArray *actions = g_ptr_array_new ();
2408 	GtkWidget *menu;
2409 	unsigned i = 0;
2410 
2411 	if (NULL != so && (!scg->selected_objects ||
2412 	    NULL == g_hash_table_lookup (scg->selected_objects, so)))
2413 		scg_object_select (scg, so);
2414 
2415 	sheet_object_populate_menu (so, actions);
2416 
2417 	if (actions->len == 0) {
2418 		g_ptr_array_free (actions, TRUE);
2419 		return;
2420 	}
2421 
2422 	menu = sheet_object_build_menu
2423 		(sheet_object_get_view (so, (SheetObjectViewContainer *) pane),
2424 		 actions, &i);
2425 	g_object_set_data_full (G_OBJECT (menu), "actions", actions,
2426 		(GDestroyNotify)cb_ptr_array_free);
2427 	gtk_widget_show_all (menu);
2428 	gnumeric_popup_menu (GTK_MENU (menu), event);
2429 }
2430 
2431 static void
cb_collect_selected_objs(SheetObject * so,double * coords,GSList ** accum)2432 cb_collect_selected_objs (SheetObject *so, double *coords, GSList **accum)
2433 {
2434 	*accum = g_slist_prepend (*accum, so);
2435 }
2436 
2437 static void
cb_pane_popup_menu(GnmPane * pane)2438 cb_pane_popup_menu (GnmPane *pane)
2439 {
2440 	SheetControlGUI *scg = pane->simple.scg;
2441 
2442 	/* ignore new_object, it is not visible, and should not create a
2443 	 * context menu */
2444 	if (NULL != scg->selected_objects) {
2445 		GSList *accum = NULL;
2446 		g_hash_table_foreach (scg->selected_objects,
2447 			(GHFunc) cb_collect_selected_objs, &accum);
2448 		if (NULL != accum && NULL == accum->next)
2449 			gnm_pane_display_object_menu (pane, accum->data, NULL);
2450 		g_slist_free (accum);
2451 	} else {
2452 		/* the popup-menu signal is a binding. the grid almost always
2453 		 * has focus we need to cheat to find out if the user
2454 		 * realllllly wants a col/row header menu */
2455 		gboolean is_col = FALSE;
2456 		gboolean is_row = FALSE;
2457 		GdkWindow *gdk_win = gdk_device_get_window_at_position (
2458 			gtk_get_current_event_device (),
2459 			NULL, NULL);
2460 
2461 		if (gdk_win != NULL) {
2462 			gpointer gtk_win_void = NULL;
2463 			GtkWindow *gtk_win = NULL;
2464 			gdk_window_get_user_data (gdk_win, &gtk_win_void);
2465 			gtk_win = gtk_win_void;
2466 			if (gtk_win != NULL) {
2467 				if (gtk_win == (GtkWindow *)pane->col.canvas)
2468 					is_col = TRUE;
2469 				else if (gtk_win == (GtkWindow *)pane->row.canvas)
2470 					is_row = TRUE;
2471 			}
2472 		}
2473 
2474 		scg_context_menu (scg, NULL, is_col, is_row);
2475 	}
2476 }
2477 
2478 static void
control_point_set_cursor(SheetControlGUI const * scg,GocItem * ctrl_pt)2479 control_point_set_cursor (SheetControlGUI const *scg, GocItem *ctrl_pt)
2480 {
2481 	SheetObject *so = g_object_get_data (G_OBJECT (ctrl_pt), "so");
2482 	int idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (ctrl_pt), "index"));
2483 	double const *coords = g_hash_table_lookup (scg->selected_objects, so);
2484 	gboolean invert_h = coords [0] > coords [2];
2485 	gboolean invert_v = coords [1] > coords [3];
2486 	GdkCursorType cursor;
2487 
2488 	if (goc_canvas_get_direction (ctrl_pt->canvas) == GOC_DIRECTION_RTL)
2489 		invert_h = !invert_h;
2490 
2491 	switch (idx) {
2492 	case 1: invert_v = !invert_v;
2493 		/* fallthrough */
2494 	case 6: cursor = invert_v ? GDK_TOP_SIDE : GDK_BOTTOM_SIDE;
2495 		break;
2496 
2497 	case 3: invert_h = !invert_h;
2498 		/* fallthrough */
2499 	case 4: cursor = invert_h ? GDK_LEFT_SIDE  : GDK_RIGHT_SIDE;
2500 		break;
2501 
2502 	case 2: invert_h = !invert_h;
2503 		/* fallthrough */
2504 	case 0: cursor = invert_v
2505 			? (invert_h ? GDK_BOTTOM_RIGHT_CORNER : GDK_BOTTOM_LEFT_CORNER)
2506 			: (invert_h ? GDK_TOP_RIGHT_CORNER : GDK_TOP_LEFT_CORNER);
2507 		break;
2508 
2509 	case 7: invert_h = !invert_h;
2510 		/* fallthrough */
2511 	case 5: cursor = invert_v
2512 			? (invert_h ? GDK_TOP_RIGHT_CORNER : GDK_TOP_LEFT_CORNER)
2513 			: (invert_h ? GDK_BOTTOM_RIGHT_CORNER : GDK_BOTTOM_LEFT_CORNER);
2514 		break;
2515 
2516 	case 8:
2517 	default:
2518 		cursor = GDK_FLEUR;
2519 	}
2520 	gnm_widget_set_cursor_type (GTK_WIDGET (ctrl_pt->canvas), cursor);
2521 }
2522 
2523 static void
target_list_add_list(GtkTargetList * targets,GtkTargetList * added_targets)2524 target_list_add_list (GtkTargetList *targets, GtkTargetList *added_targets)
2525 {
2526 	unsigned n;
2527 	GtkTargetEntry *gte;
2528 
2529 	g_return_if_fail (targets != NULL);
2530 
2531 	if (added_targets == NULL)
2532 		return;
2533 
2534 	gte = gtk_target_table_new_from_list (added_targets, &n);
2535 	gtk_target_list_add_table (targets, gte, n);
2536 	gtk_target_table_free (gte, n);
2537 }
2538 
2539 /*
2540  * Drag one or more sheet objects using GTK drag and drop, to the same
2541  * sheet, another workbook, another gnumeric or a different application.
2542  */
2543 static void
gnm_pane_drag_begin(GnmPane * pane,SheetObject * so,GdkEvent * event)2544 gnm_pane_drag_begin (GnmPane *pane, SheetObject *so, GdkEvent *event)
2545 {
2546 	GtkTargetList *targets, *im_targets;
2547 	GocCanvas *canvas    = GOC_CANVAS (pane);
2548 	SheetControlGUI *scg = pane->simple.scg;
2549 	GSList *objects;
2550 	SheetObject *imageable = NULL, *exportable = NULL;
2551 	GSList *ptr;
2552 	SheetObject *candidate;
2553 
2554 	targets = gtk_target_list_new (drag_types_out,
2555 				  G_N_ELEMENTS (drag_types_out));
2556 	objects = go_hash_keys (scg->selected_objects);
2557 	for (ptr = objects; ptr != NULL; ptr = ptr->next) {
2558 		candidate = GNM_SO (ptr->data);
2559 
2560 		if (exportable == NULL &&
2561 		    GNM_IS_SO_EXPORTABLE (candidate))
2562 			exportable = candidate;
2563 		if (imageable == NULL &&
2564 		    GNM_IS_SO_IMAGEABLE (candidate))
2565 			imageable = candidate;
2566 	}
2567 
2568 	if (exportable) {
2569 		im_targets = sheet_object_exportable_get_target_list (exportable);
2570 		if (im_targets != NULL) {
2571 			target_list_add_list (targets, im_targets);
2572 			gtk_target_list_unref (im_targets);
2573 		}
2574 	}
2575 	if (imageable) {
2576 		im_targets = sheet_object_get_target_list (imageable);
2577 		if (im_targets != NULL) {
2578 			target_list_add_list (targets, im_targets);
2579 			gtk_target_list_unref (im_targets);
2580 		}
2581 	}
2582 
2583 
2584 	if (gnm_debug_flag ("dnd")) {
2585 		unsigned i, n;
2586 		GtkTargetEntry *gte = gtk_target_table_new_from_list (targets, &n);
2587 		g_printerr ("%u offered formats:\n", n);
2588 		for (i = 0; i < n; i++)
2589 			g_printerr ("%s\n", gte[n].target);
2590 		gtk_target_table_free (gte, n);
2591 	}
2592 
2593 	gtk_drag_begin (GTK_WIDGET (canvas), targets,
2594 			GDK_ACTION_COPY | GDK_ACTION_MOVE,
2595 			pane->drag.button, event);
2596 	gtk_target_list_unref (targets);
2597 	g_slist_free (objects);
2598 }
2599 
2600 void
gnm_pane_object_start_resize(GnmPane * pane,int button,guint64 x,gint64 y,SheetObject * so,int drag_type,gboolean is_creation)2601 gnm_pane_object_start_resize (GnmPane *pane, int button, guint64 x, gint64 y,
2602 			      SheetObject *so, int drag_type, gboolean is_creation)
2603 {
2604 	GocItem **ctrl_pts;
2605 
2606 	g_return_if_fail (GNM_IS_SO (so));
2607 	g_return_if_fail (0 <= drag_type);
2608 	g_return_if_fail (drag_type < 9);
2609 
2610 	ctrl_pts = g_hash_table_lookup (pane->drag.ctrl_pts, so);
2611 
2612 	g_return_if_fail (NULL != ctrl_pts);
2613 
2614 	if (is_creation && !sheet_object_can_resize (so)) {
2615 		scg_objects_drag_commit (pane->simple.scg, 9, TRUE,
2616 					 NULL, NULL, NULL);
2617 		return;
2618 	}
2619 	gnm_simple_canvas_grab (ctrl_pts[drag_type]);
2620 	pane->drag.created_objects = is_creation;
2621 	pane->drag.button = button;
2622 	pane->drag.last_x = pane->drag.origin_x = x;
2623 	pane->drag.last_y = pane->drag.origin_y = y;
2624 	pane->drag.had_motion = FALSE;
2625 	gnm_pane_slide_init (pane);
2626 	gnm_widget_set_cursor_type (GTK_WIDGET (pane), GDK_HAND2);
2627 }
2628 
2629 /*
2630  GnmControlCircleItem
2631  */
2632 typedef GocCircle GnmControlCircle;
2633 typedef GocCircleClass GnmControlCircleClass;
2634 
2635 #define CONTROL_TYPE_CIRCLE	(control_circle_get_type ())
2636 #define CONTROL_CIRCLE(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), CONTROL_TYPE_CIRCLE, GnmControlCircle))
2637 #define CONTROL_IS_CIRCLE(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), CONTROL_TYPE_CIRCLE))
2638 
2639 static GType control_circle_get_type (void);
2640 
2641 static gboolean
control_point_button_pressed(GocItem * item,int button,double x,double y)2642 control_point_button_pressed (GocItem *item, int button, double x, double y)
2643 {
2644 	GnmPane *pane = GNM_PANE (item->canvas);
2645 	GdkEventButton *event = (GdkEventButton *) goc_canvas_get_cur_event (item->canvas);
2646 	SheetObject *so;
2647 	int idx;
2648 
2649 	if (0 != pane->drag.button)
2650 		return TRUE;
2651 
2652 	x *= goc_canvas_get_pixels_per_unit (item->canvas);
2653 	y *= goc_canvas_get_pixels_per_unit (item->canvas);
2654 	so  = g_object_get_data (G_OBJECT (item), "so");
2655 	idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2656 	switch (event->button) {
2657 	case 1:
2658 	case 2: gnm_pane_object_start_resize (pane, button, x, y, so,  idx, FALSE);
2659 		break;
2660 	case 3: gnm_pane_display_object_menu (pane, so, (GdkEvent *) event);
2661 		break;
2662 	default: /* Ignore mouse wheel events */
2663 		return FALSE;
2664 	}
2665 	return TRUE;
2666 }
2667 
2668 static gboolean
control_point_button_released(GocItem * item,int button,G_GNUC_UNUSED double x,G_GNUC_UNUSED double y)2669 control_point_button_released (GocItem *item, int button, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
2670 {
2671 	GnmPane *pane = GNM_PANE (item->canvas);
2672 	SheetControlGUI *scg = pane->simple.scg;
2673 	SheetObject *so;
2674 	int idx;
2675 
2676 	if (pane->drag.button != button)
2677 		return TRUE;
2678 	so  = g_object_get_data (G_OBJECT (item), "so");
2679 	idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2680 	pane->drag.button = 0;
2681 	gnm_simple_canvas_ungrab (item);
2682 	gnm_pane_slide_stop (pane);
2683 	control_point_set_cursor (scg, item);
2684 	if (idx == 8)
2685 		; /* ignore fake event generated by the dnd code */
2686 	else if (pane->drag.had_motion)
2687 		scg_objects_drag_commit	(scg, idx,
2688 					 pane->drag.created_objects,
2689 					 NULL, NULL, NULL);
2690 	else if (pane->drag.created_objects && idx == 7) {
2691 		double w, h;
2692 		sheet_object_default_size (so, &w, &h);
2693 		scg_objects_drag (scg, NULL, NULL, &w, &h, 7, FALSE, FALSE, FALSE);
2694 		scg_objects_drag_commit	(scg, 7, TRUE,
2695 					 NULL, NULL, NULL);
2696 	}
2697 	gnm_pane_clear_obj_size_tip (pane);
2698 	return TRUE;
2699 }
2700 
2701 static gboolean
control_point_motion(GocItem * item,double x,double y)2702 control_point_motion (GocItem *item, double x, double y)
2703 {
2704 	GnmPane *pane = GNM_PANE (item->canvas);
2705 	GdkEventMotion *event = (GdkEventMotion *) goc_canvas_get_cur_event (item->canvas);
2706 	SheetObject *so;
2707 	int idx;
2708 
2709 	if (0 == pane->drag.button)
2710 		return TRUE;
2711 
2712 	x *= goc_canvas_get_pixels_per_unit (item->canvas);
2713 	y *= goc_canvas_get_pixels_per_unit (item->canvas);
2714 	/* TODO : motion is still too granular along the internal axis
2715 	 * when the other axis is external.
2716 	 * eg  drag from middle of sheet down.  The x axis is still internal
2717 	 * onlt the y is external, however, since we are autoscrolling
2718 	 * we are limited to moving with col/row coords, not x,y.
2719 	 * Possible solution would be to change the EXTERIOR_ONLY flag
2720 	 * to something like USE_PIXELS_INSTEAD_OF_COLROW and change
2721 	 * the semantics of the col,row args in the callback.  However,
2722 	 * that is more work than I want to do right now.
2723 	 */
2724 	so  = g_object_get_data (G_OBJECT (item), "so");
2725 	idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2726 	if (idx == 8)
2727 		gnm_pane_drag_begin (pane, so, (GdkEvent *) event);
2728 	else if (gnm_pane_handle_motion (pane,
2729 					   item->canvas, x, y,
2730 					   GNM_PANE_SLIDE_X | GNM_PANE_SLIDE_Y |
2731 					   GNM_PANE_SLIDE_EXTERIOR_ONLY,
2732 					   cb_slide_handler, item))
2733 		gnm_pane_object_move (pane, G_OBJECT (item),
2734 				      x, y,
2735 				      (event->state & GDK_CONTROL_MASK) != 0,
2736 				      (event->state & GDK_SHIFT_MASK) != 0);
2737 	return TRUE;
2738 }
2739 
2740 static gboolean
control_point_button2_pressed(GocItem * item,int button,G_GNUC_UNUSED double x,G_GNUC_UNUSED double y)2741 control_point_button2_pressed (GocItem *item, int button, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
2742 {
2743 	GnmPane *pane = GNM_PANE (item->canvas);
2744 	SheetControlGUI *scg = pane->simple.scg;
2745 	SheetObject *so;
2746 
2747 	so  = g_object_get_data (G_OBJECT (item), "so");
2748 	if (pane->drag.button == 1)
2749 		sheet_object_get_editor (so, GNM_SHEET_CONTROL (scg));
2750 	return TRUE;
2751 }
2752 
2753 static void
update_control_point_colors(GocItem * item,GtkStateFlags flags)2754 update_control_point_colors (GocItem *item, GtkStateFlags flags)
2755 {
2756 	GtkStyleContext *context = goc_item_get_style_context (item);
2757 	GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (item));
2758 	GdkRGBA *fore, *back;
2759 
2760 	gtk_style_context_get (context, flags,
2761 			       "color", &fore,
2762 			       "background-color", &back,
2763 			       NULL);
2764 	go_color_from_gdk_rgba (fore, &style->line.color);
2765 	go_color_from_gdk_rgba (back, &style->fill.pattern.back);
2766 	gdk_rgba_free (fore);
2767 	gdk_rgba_free (back);
2768 	goc_item_invalidate (item);
2769 }
2770 
2771 static gboolean
control_point_enter_notify(GocItem * item,G_GNUC_UNUSED double x,G_GNUC_UNUSED double y)2772 control_point_enter_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
2773 {
2774 	GnmPane *pane = GNM_PANE (item->canvas);
2775 	SheetControlGUI *scg = pane->simple.scg;
2776 	int idx;
2777 
2778 	control_point_set_cursor (scg, item);
2779 
2780 	pane->cur_object  = g_object_get_data (G_OBJECT (item), "so");
2781 	idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2782 	if (idx != 8) {
2783 		update_control_point_colors (item, GTK_STATE_FLAG_PRELIGHT);
2784 		gnm_pane_display_obj_size_tip (pane, item);
2785 	}
2786 	return TRUE;
2787 }
2788 
2789 static gboolean
control_point_leave_notify(GocItem * item,G_GNUC_UNUSED double x,G_GNUC_UNUSED double y)2790 control_point_leave_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
2791 {
2792 	GnmPane *pane = GNM_PANE (item->canvas);
2793 	SheetControlGUI *scg = pane->simple.scg;
2794 	int idx;
2795 
2796 	control_point_set_cursor (scg, item);
2797 
2798 	idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2799 	if (idx != 8) {
2800 		update_control_point_colors (item, GTK_STATE_FLAG_NORMAL);
2801 		gnm_pane_clear_obj_size_tip (pane);
2802 	}
2803 	pane->cur_object = NULL;
2804 	return TRUE;
2805 }
2806 
2807 static void
control_circle_class_init(GocItemClass * item_klass)2808 control_circle_class_init (GocItemClass *item_klass)
2809 {
2810 	item_klass->button_pressed = control_point_button_pressed;
2811 	item_klass->button_released = control_point_button_released;
2812 	item_klass->motion = control_point_motion;
2813 	item_klass->button2_pressed = control_point_button2_pressed;
2814 	item_klass->enter_notify = control_point_enter_notify;
2815 	item_klass->leave_notify = control_point_leave_notify;
2816 }
2817 
2818 static GSF_CLASS (GnmControlCircle, control_circle,
2819 		  control_circle_class_init, NULL,
2820 		  GOC_TYPE_CIRCLE)
2821 
2822 #define GNM_ITEM_ACETATE(obj)          (G_TYPE_CHECK_INSTANCE_CAST((obj), item_acetate_get_type (), ItemAcetate))
2823 #define GNM_IS_ITEM_ACETATE(o)         (G_TYPE_CHECK_INSTANCE_TYPE((o), item_acetate_get_type ()))
2824 
2825 #define MARGIN	10
2826 
2827 static GType item_acetate_get_type (void);
2828 
2829 typedef GocRectangle		ItemAcetate;
2830 typedef GocRectangleClass	ItemAcetateClass;
2831 
2832 static double
item_acetate_distance(GocItem * item,double x,double y,GocItem ** actual_item)2833 item_acetate_distance (GocItem *item, double x, double y, GocItem **actual_item)
2834 {
2835 	if (x < (item->x0 - MARGIN) ||
2836 	    x > (item->x1 + MARGIN) ||
2837 	    y < (item->y0 - MARGIN) ||
2838 		y > (item->y1 + MARGIN))
2839 		return DBL_MAX;
2840 	*actual_item = item;
2841 	return 0.;
2842 }
2843 
2844 static void
item_acetate_class_init(GocItemClass * item_class)2845 item_acetate_class_init (GocItemClass *item_class)
2846 {
2847 	item_class->distance = item_acetate_distance;
2848 	item_class->button_pressed = control_point_button_pressed;
2849 	item_class->button_released = control_point_button_released;
2850 	item_class->motion = control_point_motion;
2851 	item_class->button2_pressed = control_point_button2_pressed;
2852 	item_class->enter_notify = control_point_enter_notify;
2853 	item_class->leave_notify = control_point_leave_notify;
2854 }
2855 
GSF_CLASS(ItemAcetate,item_acetate,item_acetate_class_init,NULL,GOC_TYPE_RECTANGLE)2856 static GSF_CLASS (ItemAcetate, item_acetate,
2857 		  item_acetate_class_init, NULL,
2858 		  GOC_TYPE_RECTANGLE)
2859 
2860 /**
2861  * new_control_point:
2862  * @pane: #GnmPane
2863  * @idx:    control point index to be created
2864  * @x:      x coordinate of control point
2865  * @y:      y coordinate of control point
2866  *
2867  * This is used to create a number of control points in a sheet
2868  * object, the meaning of them is used in other parts of the code
2869  * to belong to the following locations:
2870  *
2871  *     0 -------- 1 -------- 2
2872  *     |                     |
2873  *     3                     4
2874  *     |                     |
2875  *     5 -------- 6 -------- 7
2876  *
2877  *     8 == a clear overlay that extends slightly beyond the region
2878  *     9 == an optional stippled rectangle for moving/resizing expensive
2879  *         objects
2880  **/
2881 static GocItem *
2882 new_control_point (GnmPane *pane, SheetObject *so, int idx, double x, double y)
2883 {
2884 	GOStyle *style;
2885 	GocItem *item;
2886 	int radius, outline;
2887 	double scale = GOC_CANVAS (pane)->pixels_per_unit;
2888 
2889 	gtk_widget_style_get (GTK_WIDGET (pane),
2890 			      "control-circle-size", &radius,
2891 			      "control-circle-outline", &outline,
2892 			      NULL);
2893 
2894 	style = go_style_new ();
2895 	style->line.width = outline;
2896 	style->line.auto_color = FALSE;
2897 	style->line.dash_type = GO_LINE_SOLID; /* anything but 0 */
2898 	style->line.pattern = GO_PATTERN_SOLID;
2899 	item = goc_item_new (
2900 		pane->action_items,
2901 		CONTROL_TYPE_CIRCLE,
2902 		"x", x,
2903 		"y", y,
2904 		"radius", radius / scale,
2905 		NULL);
2906 	g_object_unref (style);
2907 
2908 	update_control_point_colors (item, GTK_STATE_FLAG_NORMAL);
2909 
2910 	g_object_set_data (G_OBJECT (item), "index",  GINT_TO_POINTER (idx));
2911 	g_object_set_data (G_OBJECT (item), "so",  so);
2912 
2913 	return item;
2914 }
2915 
2916 /**
2917  * set_item_x_y:
2918  * Changes the x and y position of the idx-th control point,
2919  * creating the control point if necessary.
2920  **/
2921 static void
set_item_x_y(GnmPane * pane,SheetObject * so,GocItem ** ctrl_pts,int idx,double x,double y,gboolean visible)2922 set_item_x_y (GnmPane *pane, SheetObject *so, GocItem **ctrl_pts,
2923 	      int idx, double x, double y, gboolean visible)
2924 {
2925 	double scale = GOC_CANVAS (pane)->pixels_per_unit;
2926 	if (ctrl_pts[idx] == NULL)
2927 		ctrl_pts[idx] = new_control_point (pane, so, idx, x / scale, y / scale);
2928 	else
2929 		goc_item_set (ctrl_pts[idx], "x", x / scale, "y", y / scale, NULL);
2930 	if (visible)
2931 		goc_item_show (ctrl_pts[idx]);
2932 	else
2933 		goc_item_hide (ctrl_pts[idx]);
2934 }
2935 
2936 #define normalize_high_low(d1,d2) if (d1<d2) { double tmp=d1; d1=d2; d2=tmp;}
2937 
2938 static void
set_acetate_coords(GnmPane * pane,SheetObject * so,GocItem ** ctrl_pts,double l,double t,double r,double b)2939 set_acetate_coords (GnmPane *pane, SheetObject *so, GocItem **ctrl_pts,
2940 		    double l, double t, double r, double b)
2941 {
2942 	double scale = goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
2943 	int radius, outline;
2944 
2945 	if (!sheet_object_rubber_band_directly (so)) {
2946 		if (NULL == ctrl_pts[9]) {
2947 			GOStyle *style = go_style_new ();
2948 			GtkStyleContext *context;
2949 			GdkRGBA rgba;
2950 			GocItem *item;
2951 
2952 			ctrl_pts[9] = item = goc_item_new (pane->action_items,
2953 				GOC_TYPE_RECTANGLE,
2954 				NULL);
2955 			context = goc_item_get_style_context (item);
2956 			gtk_style_context_add_class (context, "object-size");
2957 			gtk_style_context_add_class (context, "rubber-band");
2958 
2959 			style->fill.auto_type = FALSE;
2960 			style->fill.type  = GO_STYLE_FILL_PATTERN;
2961 			style->fill.auto_back = FALSE;
2962 			style->fill.pattern.back = 0;
2963 			style->fill.auto_fore = FALSE;
2964 			style->fill.pattern.fore = 0;
2965 			style->line.pattern = GO_PATTERN_FOREGROUND_SOLID;
2966 			style->line.width = 0.;
2967 			style->line.auto_color = FALSE;
2968 			style->line.color = 0;
2969 			gnm_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &rgba);
2970 			go_color_from_gdk_rgba (&rgba, &style->line.fore);
2971 			go_styled_object_set_style (GO_STYLED_OBJECT (item),
2972 						    style);
2973 			g_object_unref (style);
2974 			goc_item_lower_to_bottom (item);
2975 		}
2976 		normalize_high_low (r, l);
2977 		normalize_high_low (b, t);
2978 		goc_item_set (ctrl_pts[9],
2979 		       "x", l / scale, "y", t / scale,
2980 		       "width", (r - l) / scale, "height", (b - t) / scale,
2981 		       NULL);
2982 	} else {
2983 		double coords[4];
2984 		SheetObjectView *sov = sheet_object_get_view (so, (SheetObjectViewContainer *)pane);
2985 		if (NULL == sov)
2986 			sov = sheet_object_new_view (so, (SheetObjectViewContainer *)pane);
2987 
2988 		coords [0] = l; coords [2] = r; coords [1] = t; coords [3] = b;
2989 		if (NULL != sov)
2990 			sheet_object_view_set_bounds (sov, coords, TRUE);
2991 		normalize_high_low (r, l);
2992 		normalize_high_low (b, t);
2993 	}
2994 
2995 	gtk_widget_style_get (GTK_WIDGET (pane),
2996 			      "control-circle-size", &radius,
2997 			      "control-circle-outline", &outline,
2998 			      NULL);
2999 
3000 	l -= (radius + outline) / 2 - 1;
3001 	r += (radius + outline) / 2;
3002 	t -= (radius + outline) / 2 - 1;
3003 	b += (radius + outline) / 2;
3004 
3005 	if (NULL == ctrl_pts[8]) {
3006 		GOStyle *style = go_style_new ();
3007 		GocItem *item;
3008 
3009 		style->fill.auto_type = FALSE;
3010 		style->fill.type  = GO_STYLE_FILL_PATTERN;
3011 		style->fill.auto_back = FALSE;
3012 		go_pattern_set_solid (&style->fill.pattern, 0);
3013 		style->line.auto_dash = FALSE;
3014 		style->line.dash_type = GO_LINE_NONE;
3015 		/* work around the screwup in shapes that adds a large
3016 		 * border to anything that uses miter (is this required for
3017 		 * a rectangle in goc-canvas? */
3018 		style->line.join = CAIRO_LINE_JOIN_ROUND;
3019 		item = goc_item_new (
3020 			pane->action_items,
3021 			item_acetate_get_type (),
3022 			"style", style,
3023 		        NULL);
3024 		g_object_unref (style);
3025 		g_object_set_data (G_OBJECT (item), "index",
3026 			GINT_TO_POINTER (8));
3027 		g_object_set_data (G_OBJECT (item), "so", so);
3028 
3029 		ctrl_pts[8] = item;
3030 	}
3031 	goc_item_set (ctrl_pts[8],
3032 	       "x", l / scale,
3033 	       "y", t / scale,
3034 	       "width", (r - l) / scale,
3035 	       "height", (b - t) / scale,
3036 	       NULL);
3037 }
3038 
3039 void
gnm_pane_object_unselect(GnmPane * pane,SheetObject * so)3040 gnm_pane_object_unselect (GnmPane *pane, SheetObject *so)
3041 {
3042 	gnm_pane_clear_obj_size_tip (pane);
3043 	g_hash_table_remove (pane->drag.ctrl_pts, so);
3044 }
3045 
3046 /**
3047  * gnm_pane_object_update_bbox:
3048  * @pane: #GnmPane
3049  * @so: #SheetObject
3050  *
3051  * Updates the position and potentially creates control points
3052  * for manipulating the size/position of @so.
3053  **/
3054 void
gnm_pane_object_update_bbox(GnmPane * pane,SheetObject * so)3055 gnm_pane_object_update_bbox (GnmPane *pane, SheetObject *so)
3056 {
3057 	GocItem **ctrl_pts = g_hash_table_lookup (pane->drag.ctrl_pts, so);
3058 	double const *pts = g_hash_table_lookup (
3059 		pane->simple.scg->selected_objects, so);
3060 	int radius, outline, total_size;
3061 
3062 	if (ctrl_pts == NULL) {
3063 		ctrl_pts = g_new0 (GocItem *, 10);
3064 		g_hash_table_insert (pane->drag.ctrl_pts, so, ctrl_pts);
3065 	}
3066 
3067 	g_return_if_fail (ctrl_pts != NULL);
3068 
3069 	gtk_widget_style_get (GTK_WIDGET (pane),
3070 			      "control-circle-size", &radius,
3071 			      "control-circle-outline", &outline,
3072 			      NULL);
3073 	/* space for 2 halves and a full */
3074 	total_size = radius * 4 + outline * 2;
3075 
3076 	/* set the acetate 1st so that the other points will override it */
3077 	set_acetate_coords (pane, so, ctrl_pts, pts[0], pts[1], pts[2], pts[3]);
3078 	if (sheet_object_can_resize (so)) {
3079 		set_item_x_y (pane, so, ctrl_pts, 0, pts[0], pts[1], TRUE);
3080 		set_item_x_y (pane, so, ctrl_pts, 1, (pts[0] + pts[2]) / 2, pts[1],
3081 			      fabs (pts[2]-pts[0]) >= total_size);
3082 		set_item_x_y (pane, so, ctrl_pts, 2, pts[2], pts[1], TRUE);
3083 		set_item_x_y (pane, so, ctrl_pts, 3, pts[0], (pts[1] + pts[3]) / 2,
3084 			      fabs (pts[3]-pts[1]) >= total_size);
3085 		set_item_x_y (pane, so, ctrl_pts, 4, pts[2], (pts[1] + pts[3]) / 2,
3086 			      fabs (pts[3]-pts[1]) >= total_size);
3087 		set_item_x_y (pane, so, ctrl_pts, 5, pts[0], pts[3], TRUE);
3088 		set_item_x_y (pane, so, ctrl_pts, 6, (pts[0] + pts[2]) / 2, pts[3],
3089 			      fabs (pts[2]-pts[0]) >= total_size);
3090 		set_item_x_y (pane, so, ctrl_pts, 7, pts[2], pts[3], TRUE);
3091 	}
3092 }
3093 
3094 static void
cb_bounds_changed(SheetObject * so,GocItem * sov)3095 cb_bounds_changed (SheetObject *so, GocItem *sov)
3096 {
3097 	double coords[4], *cur;
3098 	SheetControlGUI *scg = GNM_SIMPLE_CANVAS (sov->canvas)->scg;
3099 	if (GNM_PANE (sov->canvas)->drag.button != 0)
3100 		return; /* do not reset bounds during drag */
3101 
3102 	scg_object_anchor_to_coords (scg, sheet_object_get_anchor (so), coords);
3103 	if (NULL != scg->selected_objects &&
3104 	    NULL != (cur = g_hash_table_lookup (scg->selected_objects, so))) {
3105 		int i;
3106 		for (i = 4; i-- > 0 ;) cur[i] = coords[i];
3107 		gnm_pane_object_update_bbox (GNM_PANE (sov->canvas), so);
3108 	}
3109 
3110 	sheet_object_view_set_bounds (GNM_SO_VIEW (sov),
3111 		coords, so->flags & SHEET_OBJECT_IS_VISIBLE);
3112 }
3113 
3114 /**
3115  * gnm_pane_object_register:
3116  * @so: A sheet object
3117  * @view: A canvas item acting as a view for @so
3118  * @selectable: Add handlers for selecting and editing the object
3119  *
3120  * Setup some standard callbacks for manipulating a view of a sheet object.
3121  * Returns: (transfer none): @view set to a #SheetObjectView.
3122  **/
3123 SheetObjectView *
gnm_pane_object_register(SheetObject * so,GocItem * view,gboolean selectable)3124 gnm_pane_object_register (SheetObject *so, GocItem *view, gboolean selectable)
3125 {
3126 	g_signal_connect_object (so, "bounds-changed",
3127 		G_CALLBACK (cb_bounds_changed), view, 0);
3128 	return GNM_SO_VIEW (view);
3129 }
3130 
3131 /**
3132  * gnm_pane_widget_register:
3133  * @so: A sheet object
3134  * @w: The widget for the sheet object view
3135  * @view: A canvas item acting as a view for @so
3136  *
3137  * Setup some standard callbacks for manipulating widgets as views of sheet
3138  * objects.
3139  **/
3140 void
gnm_pane_widget_register(SheetObject * so,GtkWidget * w,GocItem * view)3141 gnm_pane_widget_register (SheetObject *so, GtkWidget *w, GocItem *view)
3142 {
3143 	// There was a time when this did something.  Right now it does
3144 	// not, so don't walk the tree.
3145 #if 0
3146 	if (GTK_IS_CONTAINER (w)) {
3147 		GList *ptr, *children = gtk_container_get_children (GTK_CONTAINER (w));
3148 		for (ptr = children ; ptr != NULL; ptr = ptr->next)
3149 			gnm_pane_widget_register (so, ptr->data, view);
3150 		g_list_free (children);
3151 	}
3152 #endif
3153 }
3154 
3155 void
gnm_pane_set_direction(GnmPane * pane,GocDirection direction)3156 gnm_pane_set_direction (GnmPane *pane, GocDirection direction)
3157 {
3158 	goc_canvas_set_direction (GOC_CANVAS (pane), direction);
3159 	if (pane->col.canvas != NULL)
3160 		goc_canvas_set_direction (pane->col.canvas, direction);
3161 }
3162