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, >k_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