Update Spanish translation
[gnumeric.git] / src / gnm-pane.c
blob999ffa4551354cedeb68c738300b92227ec795f3
1 /*
2 * Gnumeric's extended canvas used to display a pane
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>
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>
39 #include <gsf/gsf-impl-utils.h>
41 #include <gdk/gdkkeysyms.h>
43 #include <string.h>
45 #define SCROLL_LOCK_MASK GDK_MOD5_MASK
47 typedef GocCanvasClass GnmPaneClass;
48 static GocCanvasClass *parent_klass;
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);
55 * For now, application/x-gnumeric is disabled. It handles neither
56 * images nor graphs correctly.
58 static GtkTargetEntry const drag_types_in[] = {
59 {(char *) "GNUMERIC_SAME_PROC", GTK_TARGET_SAME_APP, 0},
60 /* {(char *) "application/x-gnumeric", 0, 0}, */
63 static GtkTargetEntry const drag_types_out[] = {
64 {(char *) "GNUMERIC_SAME_PROC", GTK_TARGET_SAME_APP, 0},
65 {(char *) "application/x-gnumeric", 0, 0},
68 static gboolean
69 gnm_pane_guru_key (WBCGtk const *wbcg, GdkEvent *event)
71 GtkWidget *entry, *guru = wbc_gtk_get_guru (wbcg);
73 if (guru == NULL)
74 return FALSE;
76 entry = wbcg_get_entry_underlying (wbcg);
77 gtk_widget_event (entry ? entry : guru, event);
78 return TRUE;
81 static gboolean
82 gnm_pane_object_key_press (GnmPane *pane, GdkEventKey *ev)
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;
92 switch (ev->keyval) {
93 case GDK_KEY_Escape:
94 scg_mode_edit (scg);
95 gnm_app_clipboard_unant ();
96 return TRUE;
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;
106 sc_mode_edit (sc);
107 break;
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;
116 break;
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;
135 default:
136 break;
138 return FALSE;
141 static gboolean
142 gnm_pane_key_mode_sheet (GnmPane *pane, GdkEventKey *kevent,
143 gboolean allow_rangesel)
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;
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;
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);
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;
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 */
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;
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 */
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;
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;
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;
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);
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;
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;
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);
278 } else {
279 gnm_notebook_prev_page (wbcg->bnotebook);
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);
292 break;
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);
305 } else {
306 gnm_notebook_next_page (wbcg->bnotebook);
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);
319 break;
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);
337 break;
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);
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;
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;
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;
375 /* Fall through */
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));
386 return TRUE;
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;
398 * NOTE : Keep these in sync with the condition
399 * for tabs.
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 */
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;
419 /* Be careful to restore the editing sheet if we are editing */
420 if (wbcg_is_editing (wbcg))
421 sheet = wbcg->editing_sheet;
423 /* registering the cmd clears it, restore it afterwards */
424 first_tab_col = sv->first_tab_col;
426 if (wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL)) {
427 GODirection dir = gnm_conf_get_core_gui_editing_enter_moves_dir ();
429 sv->first_tab_col = first_tab_col;
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;
451 if (event_state & GDK_SHIFT_MASK)
452 forward = !forward;
454 sv_selection_walk_step (sv, forward, horizontal);
456 /* invalidate, in case Enter direction changes */
457 if (is_enter)
458 sv->first_tab_col = -1;
461 break;
463 case GDK_KEY_Escape:
464 wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
465 gnm_app_clipboard_unant ();
466 break;
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;
474 case GDK_KEY_F2:
475 if (gnm_pane_guru_key (wbcg, event))
476 break;
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;
486 if (!wbcg_edit_start (wbcg, FALSE, FALSE))
487 return FALSE; /* attempt to edit failed */
488 /* fall through */
490 default:
491 if (!wbcg_is_editing (wbcg)) {
492 if ((event_state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0)
493 return FALSE;
495 /* If the character is not printable do not start editing */
496 if (kevent->length == 0)
497 return FALSE;
499 if (!wbcg_edit_start (wbcg, TRUE, TRUE))
500 return FALSE; /* attempt to edit failed */
502 scg_rangesel_stop (scg, FALSE);
504 forward:
505 /* Forward the keystroke to the input line */
506 return gtk_widget_event (wbcg_get_entry_underlying (wbcg),
507 (GdkEvent *) event);
510 if (!delayed_movement) {
511 if (wbcg_is_editing (wbcg))
512 sheet_update_only_grid (sheet);
513 else
514 sheet_update (sheet);
517 return TRUE;
520 static gboolean
521 gnm_pane_colrow_key_press (SheetControlGUI *scg, GdkEventKey *event,
522 gboolean allow_rangesel)
524 SheetControl *sc = (SheetControl *) scg;
525 SheetView *sv = sc->view;
526 GnmRange target;
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;
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);
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;
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;
568 return TRUE;
571 static gint
572 gnm_pane_key_press (GtkWidget *widget, GdkEventKey *event)
574 GnmPane *pane = GNM_PANE (widget);
575 SheetControlGUI *scg = pane->simple.scg;
576 gboolean allow_rangesel;
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);
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;
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;
598 allow_rangesel = wbcg_rangesel_possible (scg->wbcg);
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;
605 pane->insert_decimal =
606 event->keyval == GDK_KEY_KP_Decimal ||
607 event->keyval == GDK_KEY_KP_Separator;
609 if (gtk_im_context_filter_keypress (pane->im_context, event))
610 return TRUE;
612 gtk_im_context_reset (pane->im_context);
614 if (gnm_pane_key_mode_sheet (pane, event, allow_rangesel))
615 return TRUE;
617 return (*GTK_WIDGET_CLASS (parent_klass)->key_press_event) (widget, event);
620 static gint
621 gnm_pane_key_release (GtkWidget *widget, GdkEventKey *event)
623 GnmPane *pane = GNM_PANE (widget);
624 SheetControl *sc = (SheetControl *) pane->simple.scg;
626 if (pane->simple.scg->grab_stack > 0)
627 return TRUE;
629 if (gtk_im_context_filter_keypress (pane->im_context, event))
630 return TRUE;
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.
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);
642 return (*GTK_WIDGET_CLASS (parent_klass)->key_release_event) (widget, event);
645 static gint
646 gnm_pane_focus_in (GtkWidget *widget, GdkEventFocus *event)
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);
653 static gint
654 gnm_pane_focus_out (GtkWidget *widget, GdkEventFocus *event)
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);
661 static void
662 gnm_pane_realize (GtkWidget *w)
664 if (GTK_WIDGET_CLASS (parent_klass)->realize)
665 (*GTK_WIDGET_CLASS (parent_klass)->realize) (w);
667 gtk_im_context_set_client_window
668 (GNM_PANE (w)->im_context,
669 gtk_widget_get_window (gtk_widget_get_toplevel (w)));
672 static void
673 gnm_pane_unrealize (GtkWidget *widget)
675 GnmPane *pane;
677 pane = GNM_PANE (widget);
678 g_return_if_fail (pane != NULL);
680 if (pane->im_context) {
681 gtk_im_context_set_client_window (pane->im_context, NULL);
684 (*GTK_WIDGET_CLASS (parent_klass)->unrealize)(widget);
687 static void
688 gnm_pane_size_allocate (GtkWidget *w, GtkAllocation *allocation)
690 GnmPane *pane = GNM_PANE (w);
691 (*GTK_WIDGET_CLASS (parent_klass)->size_allocate) (w, allocation);
692 gnm_pane_compute_visible_region (pane, TRUE);
695 static GtkEditable *
696 gnm_pane_get_editable (GnmPane const *pane)
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);
703 static void
704 cb_gnm_pane_commit (GtkIMContext *context, char const *str, GnmPane *pane)
706 gint tmp_pos, length;
707 WBCGtk *wbcg = pane->simple.scg->wbcg;
708 GtkEditable *editable = gnm_pane_get_editable (pane);
710 if (!wbcg_is_editing (wbcg) && !wbcg_edit_start (wbcg, TRUE, TRUE))
711 return;
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);
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);
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);
733 static void
734 cb_gnm_pane_preedit_start (GtkIMContext *context, GnmPane *pane)
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);
742 static void
743 cb_gnm_pane_preedit_changed (GtkIMContext *context, GnmPane *pane)
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;
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);
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;
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);
772 if (pane->preedit_length)
773 gtk_editable_insert_text (editable, preedit_string, pane->preedit_length, &tmp_pos);
774 g_free (preedit_string);
777 static void
778 cb_gnm_pane_preedit_end (GtkIMContext *context, GnmPane *pane)
780 pane->im_preedit_started = FALSE;
783 static gboolean
784 cb_gnm_pane_retrieve_surrounding (GtkIMContext *context, GnmPane *pane)
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);
790 gtk_im_context_set_surrounding (context,
791 surrounding, strlen (surrounding),
792 g_utf8_offset_to_pointer (surrounding, cur_pos) - surrounding);
794 g_free (surrounding);
795 return TRUE;
798 static gboolean
799 cb_gnm_pane_delete_surrounding (GtkIMContext *context,
800 gint offset,
801 gint n_chars,
802 GnmPane *pane)
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);
810 return TRUE;
813 /* create views for the sheet objects now that we exist */
814 static void
815 cb_pane_init_objs (GnmPane *pane)
817 Sheet *sheet = scg_sheet (pane->simple.scg);
818 GSList *ptr, *list;
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);
832 static void
833 cb_ctrl_pts_free (GocItem **ctrl_pts)
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);
842 static void
843 gnm_pane_dispose (GObject *obj)
845 GnmPane *pane = GNM_PANE (obj);
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;
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;
859 if (pane->im_context) {
860 GtkIMContext *imc = pane->im_context;
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);
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;
884 g_clear_object (&pane->mouse_cursor);
885 gnm_pane_clear_obj_size_tip (pane);
887 if (pane->drag.ctrl_pts) {
888 g_hash_table_destroy (pane->drag.ctrl_pts);
889 pane->drag.ctrl_pts = NULL;
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;
901 G_OBJECT_CLASS (parent_klass)->dispose (obj);
904 static void
905 gnm_pane_init (GnmPane *pane)
907 GocCanvas *canvas = GOC_CANVAS (pane);
908 GocGroup *root_group = goc_canvas_get_root (canvas);
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);
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;
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;
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;
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);
938 pane->im_context = gtk_im_multicontext_new ();
939 pane->preedit_length = 0;
940 pane->preedit_attrs = NULL;
941 pane->im_preedit_started = FALSE;
943 gtk_widget_set_can_focus (GTK_WIDGET (canvas), TRUE);
944 gtk_widget_set_can_default (GTK_WIDGET (canvas), TRUE);
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);
962 static void
963 gnm_pane_class_init (GnmPaneClass *klass)
965 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
966 GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
968 parent_klass = g_type_class_peek_parent (klass);
970 gobject_class->dispose = gnm_pane_dispose;
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;
980 gtk_widget_class_install_style_property
981 (widget_class,
982 g_param_spec_int ("function-indicator-size",
983 P_("Function Indicator Size"),
984 P_("Size of function indicator"),
986 G_MAXINT,
988 G_PARAM_READABLE));
990 gtk_widget_class_install_style_property
991 (widget_class,
992 g_param_spec_int ("comment-indicator-size",
993 P_("comment Indicator Size"),
994 P_("Size of comment indicator"),
996 G_MAXINT,
998 G_PARAM_READABLE));
1000 gtk_widget_class_install_style_property
1001 (widget_class,
1002 g_param_spec_int ("resize-guide-width",
1003 P_("Resize Guide Width"),
1004 P_("With of the guides used for resizing columns and rows"),
1006 G_MAXINT,
1008 G_PARAM_READABLE));
1010 gtk_widget_class_install_style_property
1011 (widget_class,
1012 g_param_spec_int ("pane-resize-guide-width",
1013 P_("Pane Resize Guide Width"),
1014 P_("With of the guides used for resizing panes"),
1016 G_MAXINT,
1018 G_PARAM_READABLE));
1020 gtk_widget_class_install_style_property
1021 (widget_class,
1022 g_param_spec_int ("control-circle-size",
1023 P_("Control Circle Size"),
1024 P_("Size of control circle for sizing sheet objects"),
1026 G_MAXINT,
1028 G_PARAM_READABLE));
1030 gtk_widget_class_install_style_property
1031 (widget_class,
1032 g_param_spec_int ("control-circle-outline",
1033 P_("Control Circle Outline"),
1034 P_("Width of outline of control circle for sizing sheet objects"),
1036 G_MAXINT,
1038 G_PARAM_READABLE));
1041 GSF_CLASS (GnmPane, gnm_pane,
1042 gnm_pane_class_init, gnm_pane_init,
1043 GNM_SIMPLE_CANVAS_TYPE)
1044 #if 0
1046 #endif
1048 static void
1049 gnm_pane_header_init (GnmPane *pane, SheetControlGUI *scg,
1050 gboolean is_col_header)
1052 Sheet *sheet = scg_sheet (scg);
1053 GocCanvas *canvas = gnm_simple_canvas_new (scg);
1054 GocGroup *group = goc_canvas_get_root (canvas);
1055 GocItem *item = goc_item_new (group,
1056 gnm_item_bar_get_type (),
1057 "pane", pane,
1058 "IsColHeader", is_col_header,
1059 NULL);
1061 /* give a non-constraining default in case something scrolls before we
1062 * are realized */
1063 if (is_col_header) {
1064 if (sheet && sheet->text_is_rtl)
1065 goc_canvas_set_direction (canvas, GOC_DIRECTION_RTL);
1066 pane->col.canvas = g_object_ref_sink (canvas);
1067 pane->col.item = GNM_ITEM_BAR (item);
1068 } else {
1069 pane->row.canvas = g_object_ref_sink (canvas);
1070 pane->row.item = GNM_ITEM_BAR (item);
1073 pane->size_guide.points = NULL;
1074 pane->size_guide.start = NULL;
1075 pane->size_guide.guide = NULL;
1077 if (NULL != scg &&
1078 NULL != sheet &&
1079 fabs (1. - sheet->last_zoom_factor_used) > 1e-6)
1080 goc_canvas_set_pixels_per_unit (canvas, sheet->last_zoom_factor_used);
1083 static void
1084 cb_pane_drag_data_received (GtkWidget *widget, GdkDragContext *context,
1085 gint x, gint y, GtkSelectionData *selection_data,
1086 guint info, guint time, GnmPane *pane)
1088 double wx, wy;
1090 if (gnm_debug_flag ("dnd")) {
1091 gchar *target_name = gdk_atom_name (gtk_selection_data_get_target (selection_data));
1092 g_printerr ("drag-data-received - %s\n", target_name);
1093 g_free (target_name);
1096 goc_canvas_w2c (GOC_CANVAS (pane), x, y, &wx, &wy);
1097 scg_drag_data_received (pane->simple.scg,
1098 gtk_drag_get_source_widget (context),
1099 wx, wy, selection_data);
1102 static void
1103 cb_pane_drag_data_get (GtkWidget *widget, GdkDragContext *context,
1104 GtkSelectionData *selection_data,
1105 guint info, guint time,
1106 SheetControlGUI *scg)
1108 if (gnm_debug_flag ("dnd")) {
1109 gchar *target_name = gdk_atom_name (gtk_selection_data_get_target (selection_data));
1110 g_printerr ("drag-data-get - %s \n", target_name);
1111 g_free (target_name);
1114 scg_drag_data_get (scg, selection_data);
1117 /* Move the rubber bands if we are the source */
1118 static gboolean
1119 cb_pane_drag_motion (GtkWidget *widget, GdkDragContext *context,
1120 int x, int y, guint32 time, GnmPane *pane)
1122 GtkWidget *source_widget = gtk_drag_get_source_widget (context);
1123 SheetControlGUI *scg = GNM_PANE (widget)->simple.scg;
1125 if ((GNM_IS_PANE (source_widget) &&
1126 GNM_PANE (source_widget)->simple.scg == scg)) {
1127 /* same scg */
1128 GocCanvas *canvas = GOC_CANVAS (widget);
1129 GdkModifierType mask;
1130 GdkWindow *window = gtk_widget_get_parent_window (source_widget);
1131 double wx, wy;
1133 g_object_set_data (G_OBJECT (context),
1134 "wbcg", scg_wbcg (scg));
1135 goc_canvas_w2c (canvas, x, y, &wx, &wy);
1136 wx *= goc_canvas_get_pixels_per_unit (canvas);
1137 wy *= goc_canvas_get_pixels_per_unit (canvas);
1139 gdk_window_get_device_position (window,
1140 gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (gdk_window_get_display (window))),
1141 NULL, NULL, &mask);
1142 gnm_pane_objects_drag (GNM_PANE (source_widget), NULL,
1143 wx, wy, 8, FALSE, (mask & GDK_SHIFT_MASK) != 0);
1144 gdk_drag_status (context,
1145 (mask & GDK_CONTROL_MASK) != 0 ? GDK_ACTION_COPY : GDK_ACTION_MOVE,
1146 time);
1148 return TRUE;
1151 static void
1152 cb_pane_drag_end (GtkWidget *widget, GdkDragContext *context,
1153 GnmPane *source_pane)
1155 /* ungrab any grabbed item */
1156 GocItem *item = goc_canvas_get_grabbed_item (GOC_CANVAS (source_pane));
1157 if (item)
1158 gnm_simple_canvas_ungrab (item);
1159 /* sync the ctrl-pts with the object in case the drag was canceled. */
1160 gnm_pane_objects_drag (source_pane, NULL,
1161 source_pane->drag.origin_x,
1162 source_pane->drag.origin_y,
1163 8, FALSE, FALSE);
1164 source_pane->drag.had_motion = FALSE;
1165 source_pane->drag.button = 0;
1169 * Move the rubber bands back to original position when curser leaves
1170 * the scg, but not when it moves to another pane. We use object data,
1171 * and rely on gtk sending drag_move to the new widget before sending
1172 * drag_leave to the old one.
1174 static void
1175 cb_pane_drag_leave (GtkWidget *widget, GdkDragContext *context,
1176 guint32 time, GnmPane *pane)
1178 GtkWidget *source_widget = gtk_drag_get_source_widget (context);
1179 GnmPane *source_pane;
1180 WBCGtk *wbcg;
1182 if (!source_widget || !GNM_IS_PANE (source_widget)) return;
1184 source_pane = GNM_PANE (source_widget);
1186 wbcg = scg_wbcg (source_pane->simple.scg);
1187 if (wbcg == g_object_get_data (G_OBJECT (context), "wbcg"))
1188 return;
1190 gnm_pane_objects_drag (source_pane, NULL,
1191 source_pane->drag.origin_x,
1192 source_pane->drag.origin_y,
1193 8, FALSE, FALSE);
1194 source_pane->drag.had_motion = FALSE;
1197 static void
1198 gnm_pane_drag_dest_init (GnmPane *pane, SheetControlGUI *scg)
1200 GtkWidget *widget = GTK_WIDGET (pane);
1202 gtk_drag_dest_set (widget, GTK_DEST_DEFAULT_ALL,
1203 drag_types_in, G_N_ELEMENTS (drag_types_in),
1204 GDK_ACTION_COPY | GDK_ACTION_MOVE);
1205 gtk_drag_dest_add_uri_targets (widget);
1206 gtk_drag_dest_add_image_targets (widget);
1207 gtk_drag_dest_add_text_targets (widget);
1209 g_object_connect (G_OBJECT (widget),
1210 "signal::drag-data-received", G_CALLBACK (cb_pane_drag_data_received), pane,
1211 "signal::drag-data-get", G_CALLBACK (cb_pane_drag_data_get), scg,
1212 "signal::drag-motion", G_CALLBACK (cb_pane_drag_motion), pane,
1213 "signal::drag-leave", G_CALLBACK (cb_pane_drag_leave), pane,
1214 "signal::drag-end", G_CALLBACK (cb_pane_drag_end), pane,
1215 NULL);
1218 GnmPane *
1219 gnm_pane_new (SheetControlGUI *scg,
1220 gboolean col_headers, gboolean row_headers, int index)
1222 GocItem *item;
1223 GnmPane *pane;
1224 Sheet *sheet;
1226 g_return_val_if_fail (GNM_IS_SCG (scg), NULL);
1228 pane = g_object_new (GNM_PANE_TYPE, NULL);
1229 pane->index = index;
1230 pane->simple.scg = scg;
1232 goc_canvas_set_document (GOC_CANVAS (pane), wb_control_get_doc (scg_wbc (scg)));
1233 if (NULL != (sheet = scg_sheet (scg)) &&
1234 fabs (1. - sheet->last_zoom_factor_used) > 1e-6)
1235 goc_canvas_set_pixels_per_unit (GOC_CANVAS (pane),
1236 sheet->last_zoom_factor_used);
1238 gnm_pane_drag_dest_init (pane, scg);
1240 item = goc_item_new (pane->grid_items,
1241 gnm_item_grid_get_type (),
1242 "SheetControlGUI", scg,
1243 NULL);
1244 pane->grid = GNM_ITEM_GRID (item);
1246 item = goc_item_new (pane->grid_items,
1247 gnm_item_cursor_get_type (),
1248 "SheetControlGUI", scg,
1249 NULL);
1250 pane->cursor.std = GNM_ITEM_CURSOR (item);
1251 if (col_headers)
1252 gnm_pane_header_init (pane, scg, TRUE);
1253 else
1254 pane->col.canvas = NULL;
1255 if (row_headers)
1256 gnm_pane_header_init (pane, scg, FALSE);
1257 else
1258 pane->row.canvas = NULL;
1260 g_signal_connect_swapped (pane, "popup-menu",
1261 G_CALLBACK (cb_pane_popup_menu), pane);
1262 g_signal_connect_swapped (G_OBJECT (pane), "realize",
1263 G_CALLBACK (cb_pane_init_objs), pane);
1265 return pane;
1269 * gnm_pane_find_col:
1270 * @pane:
1271 * @x: In canvas coords
1272 * @col_origin: optionally return the canvas coord of the col
1274 * Returns the column containing canvas coord @x
1277 gnm_pane_find_col (GnmPane const *pane, gint64 x, gint64 *col_origin)
1279 Sheet const *sheet = scg_sheet (pane->simple.scg);
1280 int col = pane->first.col;
1281 gint64 pixel = pane->first_offset.x;
1283 if (x < pixel) {
1284 while (col > 0) {
1285 ColRowInfo const *ci = sheet_col_get_info (sheet, --col);
1286 if (ci->visible) {
1287 pixel -= ci->size_pixels;
1288 if (x >= pixel) {
1289 if (col_origin)
1290 *col_origin = pixel;
1291 return col;
1295 if (col_origin)
1296 *col_origin = 0;
1297 return 0;
1300 do {
1301 ColRowInfo const *ci = sheet_col_get_info (sheet, col);
1302 if (ci->visible) {
1303 int const tmp = ci->size_pixels;
1304 if (x <= pixel + tmp) {
1305 if (col_origin)
1306 *col_origin = pixel;
1307 return col;
1309 pixel += tmp;
1311 } while (++col < gnm_sheet_get_last_col (sheet));
1313 if (col_origin)
1314 *col_origin = pixel;
1315 return gnm_sheet_get_last_col (sheet);
1319 * gnm_pane_find_row:
1320 * @pane:
1321 * @y: In canvas coords
1322 * @row_origin: optionally return the canvas coord of the row
1324 * Returns the column containing canvas coord @y
1327 gnm_pane_find_row (GnmPane const *pane, gint64 y, gint64 *row_origin)
1329 Sheet const *sheet = scg_sheet (pane->simple.scg);
1330 int row = pane->first.row;
1331 gint64 pixel = pane->first_offset.y;
1333 if (y < pixel) {
1334 while (row > 0) {
1335 ColRowInfo const *ri = sheet_row_get_info (sheet, --row);
1336 if (ri->visible) {
1337 pixel -= ri->size_pixels;
1338 if (y >= pixel) {
1339 if (row_origin)
1340 *row_origin = pixel;
1341 return row;
1345 if (row_origin)
1346 *row_origin = 0;
1347 return 0;
1350 do {
1351 ColRowInfo const *ri = sheet_row_get_info (sheet, row);
1352 if (ri->visible) {
1353 int const tmp = ri->size_pixels;
1354 if (pixel <= y && y <= pixel + tmp) {
1355 if (row_origin)
1356 *row_origin = pixel;
1357 return row;
1359 pixel += tmp;
1361 } while (++row < gnm_sheet_get_last_row (sheet));
1362 if (row_origin)
1363 *row_origin = pixel;
1364 return gnm_sheet_get_last_row (sheet);
1368 * gnm_pane_compute_visible_region : Keeps the top left col/row the same and
1369 * recalculates the visible boundaries.
1371 * @full_recompute:
1372 * if %TRUE recompute the pixel offsets of the top left row/col
1373 * else assumes that the pixel offsets of the top left have not changed.
1375 void
1376 gnm_pane_compute_visible_region (GnmPane *pane,
1377 gboolean const full_recompute)
1379 SheetControlGUI const * const scg = pane->simple.scg;
1380 Sheet const *sheet = scg_sheet (scg);
1381 GocCanvas *canvas = GOC_CANVAS (pane);
1382 gint64 pixels;
1383 int col, row, width, height;
1384 GtkAllocation ca;
1386 gtk_widget_get_allocation (GTK_WIDGET (canvas), &ca);
1388 /* When col/row sizes change we need to do a full recompute */
1389 if (full_recompute) {
1390 gint64 col_offset = pane->first_offset.x = scg_colrow_distance_get (scg,
1391 TRUE, 0, pane->first.col);
1392 if (NULL != pane->col.canvas)
1393 goc_canvas_scroll_to (pane->col.canvas, col_offset / canvas->pixels_per_unit, 0);
1395 pane->first_offset.y = scg_colrow_distance_get (scg,
1396 FALSE, 0, pane->first.row);
1397 if (NULL != pane->row.canvas)
1398 goc_canvas_scroll_to (pane->row.canvas,
1399 0, pane->first_offset.y / canvas->pixels_per_unit);
1401 goc_canvas_scroll_to (GOC_CANVAS (pane),
1402 col_offset / canvas->pixels_per_unit, pane->first_offset.y / canvas->pixels_per_unit);
1405 /* Find out the last visible col and the last full visible column */
1406 pixels = 0;
1407 col = pane->first.col;
1408 width = ca.width;
1410 do {
1411 ColRowInfo const * const ci = sheet_col_get_info (sheet, col);
1412 if (ci->visible) {
1413 int const bound = pixels + ci->size_pixels;
1415 if (bound == width) {
1416 pane->last_visible.col = col;
1417 pane->last_full.col = col;
1418 break;
1420 if (bound > width) {
1421 pane->last_visible.col = col;
1422 if (col == pane->first.col)
1423 pane->last_full.col = pane->first.col;
1424 else
1425 pane->last_full.col = col - 1;
1426 break;
1428 pixels = bound;
1430 ++col;
1431 } while (pixels < width && col < gnm_sheet_get_max_cols (sheet));
1433 if (col >= gnm_sheet_get_max_cols (sheet)) {
1434 pane->last_visible.col = gnm_sheet_get_last_col (sheet);
1435 pane->last_full.col = gnm_sheet_get_last_col (sheet);
1438 /* Find out the last visible row and the last fully visible row */
1439 pixels = 0;
1440 row = pane->first.row;
1441 height = ca.height;
1442 do {
1443 ColRowInfo const * const ri = sheet_row_get_info (sheet, row);
1444 if (ri->visible) {
1445 int const bound = pixels + ri->size_pixels;
1447 if (bound == height) {
1448 pane->last_visible.row = row;
1449 pane->last_full.row = row;
1450 break;
1452 if (bound > height) {
1453 pane->last_visible.row = row;
1454 if (row == pane->first.row)
1455 pane->last_full.row = pane->first.row;
1456 else
1457 pane->last_full.row = row - 1;
1458 break;
1460 pixels = bound;
1462 ++row;
1463 } while (pixels < height && row < gnm_sheet_get_max_rows (sheet));
1465 if (row >= gnm_sheet_get_max_rows (sheet)) {
1466 pane->last_visible.row = gnm_sheet_get_last_row (sheet);
1467 pane->last_full.row = gnm_sheet_get_last_row (sheet);
1470 /* Update the scrollbar sizes for the primary pane */
1471 if (pane->index == 0)
1472 sc_scrollbar_config (GNM_SHEET_CONTROL (scg));
1474 /* Force the cursor to update its bounds relative to the new visible region */
1475 gnm_pane_reposition_cursors (pane);
1478 void
1479 gnm_pane_redraw_range (GnmPane *pane, GnmRange const *r)
1481 SheetControlGUI *scg;
1482 gint64 x1, y1, x2, y2;
1483 GnmRange tmp;
1484 Sheet *sheet;
1485 double scale = goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
1487 g_return_if_fail (GNM_IS_PANE (pane));
1489 scg = pane->simple.scg;
1490 sheet = scg_sheet (scg);
1492 if ((r->end.col < pane->first.col) ||
1493 (r->end.row < pane->first.row) ||
1494 (r->start.col > pane->last_visible.col) ||
1495 (r->start.row > pane->last_visible.row))
1496 return;
1498 /* Only draw those regions that are visible */
1499 tmp.start.col = MAX (pane->first.col, r->start.col);
1500 tmp.start.row = MAX (pane->first.row, r->start.row);
1501 tmp.end.col = MIN (pane->last_visible.col, r->end.col);
1502 tmp.end.row = MIN (pane->last_visible.row, r->end.row);
1504 /* redraw a border of 2 pixels around the region to handle thick borders
1505 * NOTE the 2nd coordinates are excluded so add 1 extra (+2border +1include)
1507 x1 = scg_colrow_distance_get (scg, TRUE, pane->first.col, tmp.start.col) +
1508 pane->first_offset.x;
1509 y1 = scg_colrow_distance_get (scg, FALSE, pane->first.row, tmp.start.row) +
1510 pane->first_offset.y;
1511 x2 = (tmp.end.col < gnm_sheet_get_last_col (sheet))
1512 ? 4 + 1 + x1 + scg_colrow_distance_get (scg, TRUE,
1513 tmp.start.col, tmp.end.col+1)
1514 : G_MAXINT64;
1515 y2 = (tmp.end.row < gnm_sheet_get_last_row (sheet))
1516 ? 4 + 1 + y1 + scg_colrow_distance_get (scg, FALSE,
1517 tmp.start.row, tmp.end.row+1)
1518 : G_MAXINT64;
1520 goc_canvas_invalidate (&pane->simple.canvas, (x1-2) / scale, (y1-2) / scale, x2 / scale, y2 / scale);
1523 /*****************************************************************************/
1525 void
1526 gnm_pane_slide_stop (GnmPane *pane)
1528 if (pane->sliding_timer == 0)
1529 return;
1531 g_source_remove (pane->sliding_timer);
1532 pane->slide_handler = NULL;
1533 pane->slide_data = NULL;
1534 pane->sliding_timer = 0;
1537 static int
1538 col_scroll_step (int dx, Sheet *sheet)
1540 /* FIXME: get from gdk. */
1541 int dpi_x_this_screen = 90;
1542 int start_x = dpi_x_this_screen / 3;
1543 double double_dx = dpi_x_this_screen / 3.0;
1544 double step = pow (2.0, (dx - start_x) / double_dx);
1546 return (int) (CLAMP (step, 1.0, gnm_sheet_get_max_cols (sheet) / 15.0));
1549 static int
1550 row_scroll_step (int dy, Sheet *sheet)
1552 /* FIXME: get from gdk. */
1553 int dpi_y_this_screen = 90;
1554 int start_y = dpi_y_this_screen / 4;
1555 double double_dy = dpi_y_this_screen / 8.0;
1556 double step = pow (2.0, (dy - start_y) / double_dy);
1558 return (int) (CLAMP (step, 1.0, gnm_sheet_get_max_rows (sheet) / 15.0));
1561 static gint
1562 cb_pane_sliding (GnmPane *pane)
1564 int const pane_index = pane->index;
1565 GnmPane *pane0 = scg_pane (pane->simple.scg, 0);
1566 GnmPane *pane1 = scg_pane (pane->simple.scg, 1);
1567 GnmPane *pane3 = scg_pane (pane->simple.scg, 3);
1568 gboolean slide_x = FALSE, slide_y = FALSE;
1569 int col = -1, row = -1;
1570 Sheet *sheet = scg_sheet (pane->simple.scg);
1571 GnmPaneSlideInfo info;
1572 GtkAllocation pa;
1574 gtk_widget_get_allocation (GTK_WIDGET (pane), &pa);
1576 if (pane->sliding_dx > 0) {
1577 GnmPane *target_pane = pane;
1579 slide_x = TRUE;
1580 if (pane_index == 1 || pane_index == 2) {
1581 if (!pane->sliding_adjacent_h) {
1582 int width = pa.width;
1583 int x = pane->first_offset.x + width + pane->sliding_dx;
1585 /* in case pane is narrow */
1586 col = gnm_pane_find_col (pane, x, NULL);
1587 if (col > pane0->last_full.col) {
1588 pane->sliding_adjacent_h = TRUE;
1589 pane->sliding_dx = 1; /* good enough */
1590 } else
1591 slide_x = FALSE;
1592 } else
1593 target_pane = pane0;
1594 } else
1595 pane->sliding_adjacent_h = FALSE;
1597 if (slide_x) {
1598 col = target_pane->last_full.col +
1599 col_scroll_step (pane->sliding_dx, sheet);
1600 if (col >= gnm_sheet_get_last_col (sheet)) {
1601 col = gnm_sheet_get_last_col (sheet);
1602 slide_x = FALSE;
1605 } else if (pane->sliding_dx < 0) {
1606 slide_x = TRUE;
1607 col = pane0->first.col - col_scroll_step (-pane->sliding_dx, sheet);
1609 if (pane1 != NULL) {
1610 if (pane_index == 0 || pane_index == 3) {
1611 GtkAllocation p1a;
1612 int width;
1614 gtk_widget_get_allocation (GTK_WIDGET (pane1),
1615 &p1a);
1617 width = p1a.width;
1618 if (pane->sliding_dx > (-width) &&
1619 col <= pane1->last_visible.col) {
1620 int x = pane1->first_offset.x + width + pane->sliding_dx;
1621 col = gnm_pane_find_col (pane, x, NULL);
1622 slide_x = FALSE;
1626 if (col <= pane1->first.col) {
1627 col = pane1->first.col;
1628 slide_x = FALSE;
1630 } else if (col <= 0) {
1631 col = 0;
1632 slide_x = FALSE;
1636 if (pane->sliding_dy > 0) {
1637 GnmPane *target_pane = pane;
1639 slide_y = TRUE;
1640 if (pane_index == 3 || pane_index == 2) {
1641 if (!pane->sliding_adjacent_v) {
1642 int height = pa.height;
1643 int y = pane->first_offset.y + height + pane->sliding_dy;
1645 /* in case pane is short */
1646 row = gnm_pane_find_row (pane, y, NULL);
1647 if (row > pane0->last_full.row) {
1648 pane->sliding_adjacent_v = TRUE;
1649 pane->sliding_dy = 1; /* good enough */
1650 } else
1651 slide_y = FALSE;
1652 } else
1653 target_pane = pane0;
1654 } else
1655 pane->sliding_adjacent_v = FALSE;
1657 if (slide_y) {
1658 row = target_pane->last_full.row +
1659 row_scroll_step (pane->sliding_dy, sheet);
1660 if (row >= gnm_sheet_get_last_row (sheet)) {
1661 row = gnm_sheet_get_last_row (sheet);
1662 slide_y = FALSE;
1665 } else if (pane->sliding_dy < 0) {
1666 slide_y = TRUE;
1667 row = pane0->first.row - row_scroll_step (-pane->sliding_dy, sheet);
1669 if (pane3 != NULL) {
1670 if (pane_index == 0 || pane_index == 1) {
1671 GtkAllocation p3a;
1672 int height;
1674 gtk_widget_get_allocation (GTK_WIDGET (pane3),
1675 &p3a);
1677 height = p3a.height;
1678 if (pane->sliding_dy > (-height) &&
1679 row <= pane3->last_visible.row) {
1680 int y = pane3->first_offset.y + height + pane->sliding_dy;
1681 row = gnm_pane_find_row (pane3, y, NULL);
1682 slide_y = FALSE;
1686 if (row <= pane3->first.row) {
1687 row = pane3->first.row;
1688 slide_y = FALSE;
1690 } else if (row <= 0) {
1691 row = 0;
1692 slide_y = FALSE;
1696 if (col < 0 && row < 0) {
1697 gnm_pane_slide_stop (pane);
1698 return TRUE;
1701 if (col < 0) {
1702 col = gnm_pane_find_col (pane, pane->sliding_x, NULL);
1703 } else if (row < 0)
1704 row = gnm_pane_find_row (pane, pane->sliding_y, NULL);
1706 info.col = col;
1707 info.row = row;
1708 info.user_data = pane->slide_data;
1709 if (pane->slide_handler == NULL ||
1710 (*pane->slide_handler) (pane, &info))
1711 scg_make_cell_visible (pane->simple.scg, col, row, FALSE, TRUE);
1713 if (!slide_x && !slide_y)
1714 gnm_pane_slide_stop (pane);
1715 else if (pane->sliding_timer == 0)
1716 pane->sliding_timer = g_timeout_add (300, (GSourceFunc)cb_pane_sliding, pane);
1718 return TRUE;
1722 * gnm_pane_handle_motion:
1723 * @pane: The GnmPane managing the scroll
1724 * @canvas: The Canvas the event comes from
1725 * @slide_flags:
1726 * @handler: (scope async): The handler when sliding
1727 * @user_data: closure data
1729 * Handle a motion event from a @canvas and scroll the @pane
1730 * depending on how far outside the bounds of @pane the @event is.
1731 * Usually @canvas == @pane however as long as the canvases share a basis
1732 * space they can be different.
1734 gboolean
1735 gnm_pane_handle_motion (GnmPane *pane,
1736 GocCanvas *canvas, gint64 x, gint64 y,
1737 GnmPaneSlideFlags slide_flags,
1738 GnmPaneSlideHandler slide_handler,
1739 gpointer user_data)
1741 GnmPane *pane0, *pane1, *pane3;
1742 int pindex, width, height;
1743 gint64 dx = 0, dy = 0, left, top;
1744 GtkAllocation pa, p0a, p1a, p3a;
1746 g_return_val_if_fail (GNM_IS_PANE (pane), FALSE);
1747 g_return_val_if_fail (GOC_IS_CANVAS (canvas), FALSE);
1748 g_return_val_if_fail (slide_handler != NULL, FALSE);
1750 pindex = pane->index;
1751 left = pane->first_offset.x;
1752 top = pane->first_offset.y;
1753 gtk_widget_get_allocation (GTK_WIDGET (pane), &pa);
1754 width = pa.width;
1755 height = pa.height;
1757 pane0 = scg_pane (pane->simple.scg, 0);
1758 gtk_widget_get_allocation (GTK_WIDGET (pane0), &p0a);
1760 pane1 = scg_pane (pane->simple.scg, 1);
1761 if (pane1) gtk_widget_get_allocation (GTK_WIDGET (pane1), &p1a);
1763 pane3 = scg_pane (pane->simple.scg, 3);
1764 if (pane3) gtk_widget_get_allocation (GTK_WIDGET (pane3), &p3a);
1766 if (slide_flags & GNM_PANE_SLIDE_X) {
1767 if (x < left)
1768 dx = x - left;
1769 else if (x >= left + width)
1770 dx = x - width - left;
1773 if (slide_flags & GNM_PANE_SLIDE_Y) {
1774 if (y < top)
1775 dy = y - top;
1776 else if (y >= top + height)
1777 dy = y - height - top;
1780 if (pane->sliding_adjacent_h) {
1781 if (pindex == 0 || pindex == 3) {
1782 if (dx < 0) {
1783 x = pane1->first_offset.x;
1784 dx += p1a.width;
1785 if (dx > 0)
1786 x += dx;
1787 dx = 0;
1788 } else
1789 pane->sliding_adjacent_h = FALSE;
1790 } else {
1791 if (dx > 0) {
1792 x = pane0->first_offset.x + dx;
1793 dx -= p0a.width;
1794 if (dx < 0)
1795 dx = 0;
1796 } else if (dx == 0) {
1797 /* initiate a reverse scroll of panes 0,3 */
1798 if ((pane1->last_visible.col+1) != pane0->first.col)
1799 dx = x - (left + width);
1800 } else
1801 dx = 0;
1805 if (pane->sliding_adjacent_v) {
1806 if (pindex == 0 || pindex == 1) {
1807 if (dy < 0) {
1808 y = pane3->first_offset.y;
1809 dy += p3a.height;
1810 if (dy > 0)
1811 y += dy;
1812 dy = 0;
1813 } else
1814 pane->sliding_adjacent_v = FALSE;
1815 } else {
1816 if (dy > 0) {
1817 y = pane0->first_offset.y + dy;
1818 dy -= p0a.height;
1819 if (dy < 0)
1820 dy = 0;
1821 } else if (dy == 0) {
1822 /* initiate a reverse scroll of panes 0,1 */
1823 if ((pane3->last_visible.row+1) != pane0->first.row)
1824 dy = y - (top + height);
1825 } else
1826 dy = 0;
1830 /* Movement is inside the visible region */
1831 if (dx == 0 && dy == 0) {
1832 if (!(slide_flags & GNM_PANE_SLIDE_EXTERIOR_ONLY)) {
1833 GnmPaneSlideInfo info;
1834 info.row = gnm_pane_find_row (pane, y, NULL);
1835 info.col = gnm_pane_find_col (pane, x, NULL);
1836 info.user_data = user_data;
1837 (*slide_handler) (pane, &info);
1839 gnm_pane_slide_stop (pane);
1840 return TRUE;
1843 pane->sliding_x = x;
1844 pane->sliding_dx = dx;
1845 pane->sliding_y = y;
1846 pane->sliding_dy = dy;
1847 pane->slide_handler = slide_handler;
1848 pane->slide_data = user_data;
1850 if (pane->sliding_timer == 0)
1851 cb_pane_sliding (pane);
1852 return FALSE;
1855 /* TODO : All the slide_* members of GnmPane really ought to be in
1856 * SheetControlGUI, most of these routines also belong there. However, since
1857 * the primary point of access is via GnmPane and SCG is very large
1858 * already I'm leaving them here for now. Move them when we return to
1859 * investigate how to do reverse scrolling for pseudo-adjacent panes.
1861 void
1862 gnm_pane_slide_init (GnmPane *pane)
1864 GnmPane *pane0, *pane1, *pane3;
1866 g_return_if_fail (GNM_IS_PANE (pane));
1868 pane0 = scg_pane (pane->simple.scg, 0);
1869 pane1 = scg_pane (pane->simple.scg, 1);
1870 pane3 = scg_pane (pane->simple.scg, 3);
1872 pane->sliding_adjacent_h = (pane1 != NULL)
1873 ? (pane1->last_full.col == (pane0->first.col - 1))
1874 : FALSE;
1875 pane->sliding_adjacent_v = (pane3 != NULL)
1876 ? (pane3->last_full.row == (pane0->first.row - 1))
1877 : FALSE;
1880 static gboolean
1881 cb_obj_autoscroll (GnmPane *pane, GnmPaneSlideInfo const *info)
1883 SheetControlGUI *scg = pane->simple.scg;
1884 GdkModifierType mask;
1885 GdkWindow *window = gtk_widget_get_parent_window (GTK_WIDGET (pane));
1887 /* Cheesy hack calculate distance we move the screen, this loses the
1888 * mouse position */
1889 double dx = pane->first_offset.x;
1890 double dy = pane->first_offset.y;
1891 scg_make_cell_visible (scg, info->col, info->row, FALSE, TRUE);
1892 dx = pane->first_offset.x - dx;
1893 dy = pane->first_offset.y - dy;
1895 pane->drag.had_motion = TRUE;
1896 gdk_window_get_device_position (window,
1897 gdk_device_manager_get_client_pointer (gdk_display_get_device_manager (gdk_window_get_display (window))),
1898 NULL, NULL, &mask);
1899 scg_objects_drag (pane->simple.scg, pane,
1900 NULL, &dx, &dy, 8, FALSE, (mask & GDK_SHIFT_MASK) != 0, TRUE);
1902 pane->drag.last_x += dx;
1903 pane->drag.last_y += dy;
1904 return FALSE;
1907 void
1908 gnm_pane_object_autoscroll (GnmPane *pane, GdkDragContext *context,
1909 gint x, gint y, guint time)
1911 int const pane_index = pane->index;
1912 SheetControlGUI *scg = pane->simple.scg;
1913 GnmPane *pane0 = scg_pane (scg, 0);
1914 GnmPane *pane1 = scg_pane (scg, 1);
1915 GnmPane *pane3 = scg_pane (scg, 3);
1916 GtkWidget *w = GTK_WIDGET (pane);
1917 GtkAllocation wa;
1918 gint dx, dy;
1920 gtk_widget_get_allocation (w, &wa);
1922 if (y < wa.y) {
1923 if (pane_index < 2 && pane3 != NULL) {
1924 w = GTK_WIDGET (pane3);
1925 gtk_widget_get_allocation (w, &wa);
1927 dy = y - wa.y;
1928 g_return_if_fail (dy <= 0);
1929 } else if (y >= (wa.y + wa.height)) {
1930 if (pane_index >= 2) {
1931 w = GTK_WIDGET (pane0);
1932 gtk_widget_get_allocation (w, &wa);
1934 dy = y - (wa.y + wa.height);
1935 g_return_if_fail (dy >= 0);
1936 } else
1937 dy = 0;
1938 if (x < wa.x) {
1939 if ((pane_index == 0 || pane_index == 3) && pane1 != NULL) {
1940 w = GTK_WIDGET (pane1);
1941 gtk_widget_get_allocation (w, &wa);
1943 dx = x - wa.x;
1944 g_return_if_fail (dx <= 0);
1945 } else if (x >= (wa.x + wa.width)) {
1946 if (pane_index >= 2) {
1947 w = GTK_WIDGET (pane0);
1948 gtk_widget_get_allocation (w, &wa);
1950 dx = x - (wa.x + wa.width);
1951 g_return_if_fail (dx >= 0);
1952 } else
1953 dx = 0;
1955 g_object_set_data (G_OBJECT (context),
1956 "wbcg", scg_wbcg (scg));
1957 pane->sliding_dx = dx;
1958 pane->sliding_dy = dy;
1959 pane->slide_handler = &cb_obj_autoscroll;
1960 pane->slide_data = NULL;
1961 pane->sliding_x = x;
1962 pane->sliding_y = y;
1963 if (pane->sliding_timer == 0)
1964 cb_pane_sliding (pane);
1968 * gnm_pane_object_group:
1969 * @pane: #GnmPane
1971 * Returns: (transfer none): the #GocGroup including all #SheetObjectView
1972 * instances in @pane.
1974 GocGroup *
1975 gnm_pane_object_group (GnmPane *pane)
1977 return pane->object_views;
1980 static void
1981 gnm_pane_clear_obj_size_tip (GnmPane *pane)
1983 if (pane->size_tip) {
1984 gtk_widget_destroy (gtk_widget_get_toplevel (pane->size_tip));
1985 pane->size_tip = NULL;
1989 static void
1990 gnm_pane_display_obj_size_tip (GnmPane *pane, GocItem *ctrl_pt)
1992 SheetControlGUI *scg = pane->simple.scg;
1993 double const *coords;
1994 double pts[4];
1995 char *msg;
1996 SheetObjectAnchor anchor;
1998 if (pane->size_tip == NULL) {
1999 GtkWidget *cw = GTK_WIDGET (pane);
2000 GtkWidget *top;
2001 int x, y;
2003 if (ctrl_pt == NULL) {
2005 * Keyboard navigation when we are not displaying
2006 * a tooltip already.
2008 return;
2011 pane->size_tip = gnm_create_tooltip (cw);
2012 top = gtk_widget_get_toplevel (pane->size_tip);
2014 gnm_canvas_get_screen_position (ctrl_pt->canvas,
2015 ctrl_pt->x1, ctrl_pt->y1,
2016 &x, &y);
2017 gtk_window_move (GTK_WINDOW (top), x + 10, y + 10);
2018 gtk_widget_show_all (top);
2021 g_return_if_fail (pane->cur_object != NULL);
2022 g_return_if_fail (pane->size_tip != NULL);
2024 coords = g_hash_table_lookup (scg->selected_objects, pane->cur_object);
2025 anchor = *sheet_object_get_anchor (pane->cur_object);
2026 scg_object_coords_to_anchor (scg, coords, &anchor);
2027 sheet_object_anchor_to_pts (&anchor, scg_sheet (scg), pts);
2028 msg = g_strdup_printf (_("%.1f x %.1f pts\n%d x %d pixels"),
2029 MAX (fabs (pts[2] - pts[0]), 0),
2030 MAX (fabs (pts[3] - pts[1]), 0),
2031 MAX ((int)floor (fabs (coords [2] - coords [0]) + 0.5), 0),
2032 MAX ((int)floor (fabs (coords [3] - coords [1]) + 0.5), 0));
2033 gtk_label_set_text (GTK_LABEL (pane->size_tip), msg);
2034 g_free (msg);
2037 void
2038 gnm_pane_bound_set (GnmPane *pane,
2039 int start_col, int start_row,
2040 int end_col, int end_row)
2042 GnmRange r;
2044 g_return_if_fail (pane != NULL);
2046 range_init (&r, start_col, start_row, end_col, end_row);
2047 goc_item_set (GOC_ITEM (pane->grid),
2048 "bound", &r,
2049 NULL);
2052 /****************************************************************************/
2054 void
2055 gnm_pane_size_guide_start (GnmPane *pane,
2056 gboolean vert, int colrow, gboolean is_colrow_resize)
2058 SheetControlGUI const *scg;
2059 double x0, y0, x1, y1, pos;
2060 double zoom;
2061 GOStyle *style;
2062 GdkRGBA rgba;
2063 GtkStyleContext *context;
2064 const char *guide_class = is_colrow_resize ? "resize-guide" : "pane-resize-guide";
2065 const char *colrow_class = vert ? "col" : "row";
2066 const char *width_prop_name = is_colrow_resize ? "resize-guide-width" : "pane-resize-guide-width";
2067 int width;
2069 g_return_if_fail (pane != NULL);
2070 g_return_if_fail (pane->size_guide.guide == NULL);
2071 g_return_if_fail (pane->size_guide.start == NULL);
2072 g_return_if_fail (pane->size_guide.points == NULL);
2074 zoom = GOC_CANVAS (pane)->pixels_per_unit;
2075 scg = pane->simple.scg;
2077 pos = scg_colrow_distance_get (scg, vert, 0, colrow) / zoom;
2078 if (vert) {
2079 x0 = pos;
2080 y0 = scg_colrow_distance_get (scg, FALSE,
2081 0, pane->first.row) / zoom;
2082 x1 = pos;
2083 y1 = scg_colrow_distance_get (scg, FALSE,
2084 0, pane->last_visible.row+1) / zoom;
2085 } else {
2086 x0 = scg_colrow_distance_get (scg, TRUE,
2087 0, pane->first.col) / zoom;
2088 y0 = pos;
2089 x1 = scg_colrow_distance_get (scg, TRUE,
2090 0, pane->last_visible.col+1) / zoom;
2091 y1 = pos;
2094 gtk_widget_style_get (GTK_WIDGET (pane), width_prop_name, &width, NULL);
2096 /* Guideline positioning is done in gnm_pane_size_guide_motion */
2097 pane->size_guide.guide = goc_item_new (pane->action_items,
2098 GOC_TYPE_LINE,
2099 "x0", x0, "y0", y0,
2100 "x1", x1, "y1", y1,
2101 NULL);
2102 style = go_styled_object_get_style (GO_STYLED_OBJECT (pane->size_guide.guide));
2103 style->line.width = width;
2104 context = goc_item_get_style_context (pane->size_guide.guide);
2105 gtk_style_context_add_class (context, guide_class);
2106 gtk_style_context_add_class (context, colrow_class);
2107 if (is_colrow_resize)
2108 gtk_style_context_add_class (context, "end");
2109 gnm_style_context_get_color (context, GTK_STATE_FLAG_SELECTED, &rgba);
2110 go_color_from_gdk_rgba (&rgba, &style->line.color);
2112 if (is_colrow_resize) {
2113 pane->size_guide.start = goc_item_new (pane->action_items,
2114 GOC_TYPE_LINE,
2115 "x0", x0, "y0", y0,
2116 "x1", x1, "y1", y1,
2117 NULL);
2118 style = go_styled_object_get_style (GO_STYLED_OBJECT (pane->size_guide.start));
2119 context = goc_item_get_style_context (pane->size_guide.start);
2120 gtk_style_context_add_class (context, guide_class);
2121 gtk_style_context_add_class (context, colrow_class);
2122 gtk_style_context_add_class (context, "start");
2123 gnm_style_context_get_color (context, GTK_STATE_FLAG_SELECTED, &rgba);
2124 go_color_from_gdk_rgba (&rgba, &style->line.color);
2125 style->line.width = width;
2129 void
2130 gnm_pane_size_guide_stop (GnmPane *pane)
2132 g_return_if_fail (pane != NULL);
2134 g_clear_object (&pane->size_guide.start);
2135 g_clear_object (&pane->size_guide.guide);
2139 * gnm_pane_size_guide_motion:
2140 * @p: #GnmPane
2141 * @vert: %TRUE for a vertical guide, %FALSE for horizontal
2142 * @guide_pos: in unscaled sheet pixel coords
2144 * Moves the guide line to @guide_pos.
2145 * NOTE : gnm_pane_size_guide_start must be called before any calls to
2146 * gnm_pane_size_guide_motion
2148 void
2149 gnm_pane_size_guide_motion (GnmPane *pane, gboolean vert, gint64 guide_pos)
2151 GocItem *resize_guide = GOC_ITEM (pane->size_guide.guide);
2152 double const scale = 1. / resize_guide->canvas->pixels_per_unit;
2153 double x;
2155 x = scale * (guide_pos - .5);
2156 if (vert)
2157 goc_item_set (resize_guide, "x0", x, "x1", x, NULL);
2158 else
2159 goc_item_set (resize_guide, "y0", x, "y1", x, NULL);
2162 /****************************************************************************/
2164 static void
2165 cb_update_ctrl_pts (SheetObject *so, GocItem **ctrl_pts, GnmPane *pane)
2167 double *coords = g_hash_table_lookup (
2168 pane->simple.scg->selected_objects, so);
2169 scg_object_anchor_to_coords (pane->simple.scg, sheet_object_get_anchor (so), coords);
2170 gnm_pane_object_update_bbox (pane, so);
2173 /* Called when the zoom changes */
2174 void
2175 gnm_pane_reposition_cursors (GnmPane *pane)
2177 GSList *l;
2179 gnm_item_cursor_reposition (pane->cursor.std);
2180 if (NULL != pane->cursor.rangesel)
2181 gnm_item_cursor_reposition (pane->cursor.rangesel);
2182 if (NULL != pane->cursor.special)
2183 gnm_item_cursor_reposition (pane->cursor.special);
2184 for (l = pane->cursor.expr_range; l; l = l->next)
2185 gnm_item_cursor_reposition (GNM_ITEM_CURSOR (l->data));
2186 for (l = pane->cursor.animated; l; l = l->next)
2187 gnm_item_cursor_reposition (GNM_ITEM_CURSOR (l->data));
2189 /* ctrl pts do not scale with the zoom, compensate */
2190 if (pane->drag.ctrl_pts != NULL)
2191 g_hash_table_foreach (pane->drag.ctrl_pts,
2192 (GHFunc) cb_update_ctrl_pts, pane);
2195 gboolean
2196 gnm_pane_cursor_bound_set (GnmPane *pane, GnmRange const *r)
2198 return gnm_item_cursor_bound_set (pane->cursor.std, r);
2201 /****************************************************************************/
2203 gboolean
2204 gnm_pane_rangesel_bound_set (GnmPane *pane, GnmRange const *r)
2206 return gnm_item_cursor_bound_set (pane->cursor.rangesel, r);
2208 void
2209 gnm_pane_rangesel_start (GnmPane *pane, GnmRange const *r)
2211 GocItem *item;
2212 SheetControlGUI *scg = pane->simple.scg;
2214 g_return_if_fail (pane->cursor.rangesel == NULL);
2216 /* Hide the primary cursor while the range selection cursor is visible
2217 * and we are selecting on a different sheet than the expr being edited */
2218 if (scg_sheet (scg) != wb_control_cur_sheet (scg_wbc (scg)))
2219 gnm_item_cursor_set_visibility (pane->cursor.std, FALSE);
2220 item = goc_item_new (pane->grid_items,
2221 gnm_item_cursor_get_type (),
2222 "SheetControlGUI", scg,
2223 "style", GNM_ITEM_CURSOR_ANTED,
2224 NULL);
2225 pane->cursor.rangesel = GNM_ITEM_CURSOR (item);
2226 gnm_item_cursor_bound_set (pane->cursor.rangesel, r);
2229 void
2230 gnm_pane_rangesel_stop (GnmPane *pane)
2232 g_return_if_fail (pane->cursor.rangesel != NULL);
2234 g_clear_object (&pane->cursor.rangesel);
2236 /* Make the primary cursor visible again */
2237 gnm_item_cursor_set_visibility (pane->cursor.std, TRUE);
2239 gnm_pane_slide_stop (pane);
2242 /****************************************************************************/
2244 gboolean
2245 gnm_pane_special_cursor_bound_set (GnmPane *pane, GnmRange const *r)
2247 return gnm_item_cursor_bound_set (pane->cursor.special, r);
2250 void
2251 gnm_pane_special_cursor_start (GnmPane *pane, int style, int button)
2253 GocItem *item;
2254 GocCanvas *canvas = GOC_CANVAS (pane);
2256 g_return_if_fail (pane->cursor.special == NULL);
2257 item = goc_item_new (
2258 GOC_GROUP (canvas->root),
2259 gnm_item_cursor_get_type (),
2260 "SheetControlGUI", pane->simple.scg,
2261 "style", style,
2262 "button", button,
2263 NULL);
2264 pane->cursor.special = GNM_ITEM_CURSOR (item);
2267 void
2268 gnm_pane_special_cursor_stop (GnmPane *pane)
2270 g_return_if_fail (pane->cursor.special != NULL);
2272 g_clear_object (&pane->cursor.special);
2275 void
2276 gnm_pane_mouse_cursor_set (GnmPane *pane, GdkCursor *c)
2278 g_object_ref (c);
2279 if (pane->mouse_cursor)
2280 g_object_unref (pane->mouse_cursor);
2281 pane->mouse_cursor = c;
2284 /****************************************************************************/
2287 void
2288 gnm_pane_expr_cursor_bound_set (GnmPane *pane, GnmRange const *r,
2289 GOColor color)
2291 GnmItemCursor *cursor;
2293 cursor = (GnmItemCursor *) goc_item_new
2294 (GOC_GROUP (GOC_CANVAS (pane)->root),
2295 gnm_item_cursor_get_type (),
2296 "SheetControlGUI", pane->simple.scg,
2297 "style", GNM_ITEM_CURSOR_EXPR_RANGE,
2298 "color", color,
2299 NULL);
2301 gnm_item_cursor_bound_set (cursor, r);
2302 pane->cursor.expr_range = g_slist_prepend
2303 (pane->cursor.expr_range, cursor);
2306 void
2307 gnm_pane_expr_cursor_stop (GnmPane *pane)
2309 g_slist_free_full (pane->cursor.expr_range, g_object_unref);
2310 pane->cursor.expr_range = NULL;
2313 /****************************************************************************/
2315 void
2316 gnm_pane_edit_start (GnmPane *pane)
2318 GocCanvas *canvas = GOC_CANVAS (pane);
2320 g_return_if_fail (pane->editor == NULL);
2322 /* edit item handles visibility checks */
2323 pane->editor = (GnmItemEdit *) goc_item_new (
2324 GOC_GROUP (canvas->root),
2325 gnm_item_edit_get_type (),
2326 "SheetControlGUI", pane->simple.scg,
2327 NULL);
2330 void
2331 gnm_pane_edit_stop (GnmPane *pane)
2333 g_clear_object (&pane->editor);
2336 void
2337 gnm_pane_objects_drag (GnmPane *pane, SheetObject *so,
2338 gdouble new_x, gdouble new_y, int drag_type,
2339 gboolean symmetric,gboolean snap_to_grid)
2341 double dx, dy;
2342 dx = new_x - pane->drag.last_x;
2343 dy = new_y - pane->drag.last_y;
2344 pane->drag.had_motion = TRUE;
2345 scg_objects_drag (pane->simple.scg, pane,
2346 so, &dx, &dy, drag_type, symmetric, snap_to_grid, TRUE);
2348 pane->drag.last_x += dx;
2349 pane->drag.last_y += dy;
2352 /* new_x and new_y are in world coords */
2353 static void
2354 gnm_pane_object_move (GnmPane *pane, GObject *ctrl_pt,
2355 gdouble new_x, gdouble new_y,
2356 gboolean symmetric,
2357 gboolean snap_to_grid)
2359 int const idx = GPOINTER_TO_INT (g_object_get_data (ctrl_pt, "index"));
2360 pane->cur_object = g_object_get_data (G_OBJECT (ctrl_pt), "so");
2362 gnm_pane_objects_drag (pane, pane->cur_object, new_x, new_y, idx,
2363 symmetric, snap_to_grid);
2364 if (idx != 8)
2365 gnm_pane_display_obj_size_tip (pane, GOC_ITEM (ctrl_pt));
2368 static gboolean
2369 cb_slide_handler (GnmPane *pane, GnmPaneSlideInfo const *info)
2371 guint64 x, y;
2372 SheetControlGUI const *scg = pane->simple.scg;
2373 double const scale = 1. / GOC_CANVAS (pane)->pixels_per_unit;
2375 x = scg_colrow_distance_get (scg, TRUE, pane->first.col, info->col);
2376 x += pane->first_offset.x;
2377 y = scg_colrow_distance_get (scg, FALSE, pane->first.row, info->row);
2378 y += pane->first_offset.y;
2380 gnm_pane_object_move (pane, info->user_data,
2381 x * scale, y * scale, FALSE, FALSE);
2383 return TRUE;
2386 static void
2387 cb_ptr_array_free (GPtrArray *actions)
2389 g_ptr_array_free (actions, TRUE);
2392 /* event and so can be NULL */
2393 void
2394 gnm_pane_display_object_menu (GnmPane *pane, SheetObject *so, GdkEvent *event)
2396 SheetControlGUI *scg = pane->simple.scg;
2397 GPtrArray *actions = g_ptr_array_new ();
2398 GtkWidget *menu;
2399 unsigned i = 0;
2401 if (NULL != so && (!scg->selected_objects ||
2402 NULL == g_hash_table_lookup (scg->selected_objects, so)))
2403 scg_object_select (scg, so);
2405 sheet_object_populate_menu (so, actions);
2407 if (actions->len == 0) {
2408 g_ptr_array_free (actions, TRUE);
2409 return;
2412 menu = sheet_object_build_menu
2413 (sheet_object_get_view (so, (SheetObjectViewContainer *) pane),
2414 actions, &i);
2415 g_object_set_data_full (G_OBJECT (menu), "actions", actions,
2416 (GDestroyNotify)cb_ptr_array_free);
2417 gtk_widget_show_all (menu);
2418 gnumeric_popup_menu (GTK_MENU (menu), event);
2421 static void
2422 cb_collect_selected_objs (SheetObject *so, double *coords, GSList **accum)
2424 *accum = g_slist_prepend (*accum, so);
2427 static void
2428 cb_pane_popup_menu (GnmPane *pane)
2430 SheetControlGUI *scg = pane->simple.scg;
2432 /* ignore new_object, it is not visible, and should not create a
2433 * context menu */
2434 if (NULL != scg->selected_objects) {
2435 GSList *accum = NULL;
2436 g_hash_table_foreach (scg->selected_objects,
2437 (GHFunc) cb_collect_selected_objs, &accum);
2438 if (NULL != accum && NULL == accum->next)
2439 gnm_pane_display_object_menu (pane, accum->data, NULL);
2440 g_slist_free (accum);
2441 } else {
2442 /* the popup-menu signal is a binding. the grid almost always
2443 * has focus we need to cheat to find out if the user
2444 * realllllly wants a col/row header menu */
2445 gboolean is_col = FALSE;
2446 gboolean is_row = FALSE;
2447 GdkWindow *gdk_win = gdk_device_get_window_at_position (
2448 gtk_get_current_event_device (),
2449 NULL, NULL);
2451 if (gdk_win != NULL) {
2452 gpointer gtk_win_void = NULL;
2453 GtkWindow *gtk_win = NULL;
2454 gdk_window_get_user_data (gdk_win, &gtk_win_void);
2455 gtk_win = gtk_win_void;
2456 if (gtk_win != NULL) {
2457 if (gtk_win == (GtkWindow *)pane->col.canvas)
2458 is_col = TRUE;
2459 else if (gtk_win == (GtkWindow *)pane->row.canvas)
2460 is_row = TRUE;
2464 scg_context_menu (scg, NULL, is_col, is_row);
2468 static void
2469 control_point_set_cursor (SheetControlGUI const *scg, GocItem *ctrl_pt)
2471 SheetObject *so = g_object_get_data (G_OBJECT (ctrl_pt), "so");
2472 int idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (ctrl_pt), "index"));
2473 double const *coords = g_hash_table_lookup (scg->selected_objects, so);
2474 gboolean invert_h = coords [0] > coords [2];
2475 gboolean invert_v = coords [1] > coords [3];
2476 GdkCursorType cursor;
2478 if (goc_canvas_get_direction (ctrl_pt->canvas) == GOC_DIRECTION_RTL)
2479 invert_h = !invert_h;
2481 switch (idx) {
2482 case 1: invert_v = !invert_v;
2483 /* fallthrough */
2484 case 6: cursor = invert_v ? GDK_TOP_SIDE : GDK_BOTTOM_SIDE;
2485 break;
2487 case 3: invert_h = !invert_h;
2488 /* fallthrough */
2489 case 4: cursor = invert_h ? GDK_LEFT_SIDE : GDK_RIGHT_SIDE;
2490 break;
2492 case 2: invert_h = !invert_h;
2493 /* fallthrough */
2494 case 0: cursor = invert_v
2495 ? (invert_h ? GDK_BOTTOM_RIGHT_CORNER : GDK_BOTTOM_LEFT_CORNER)
2496 : (invert_h ? GDK_TOP_RIGHT_CORNER : GDK_TOP_LEFT_CORNER);
2497 break;
2499 case 7: invert_h = !invert_h;
2500 /* fallthrough */
2501 case 5: cursor = invert_v
2502 ? (invert_h ? GDK_TOP_RIGHT_CORNER : GDK_TOP_LEFT_CORNER)
2503 : (invert_h ? GDK_BOTTOM_RIGHT_CORNER : GDK_BOTTOM_LEFT_CORNER);
2504 break;
2506 case 8:
2507 default:
2508 cursor = GDK_FLEUR;
2510 gnm_widget_set_cursor_type (GTK_WIDGET (ctrl_pt->canvas), cursor);
2513 static void
2514 target_list_add_list (GtkTargetList *targets, GtkTargetList *added_targets)
2516 unsigned n;
2517 GtkTargetEntry *gte;
2519 g_return_if_fail (targets != NULL);
2521 if (added_targets == NULL)
2522 return;
2524 gte = gtk_target_table_new_from_list (added_targets, &n);
2525 gtk_target_list_add_table (targets, gte, n);
2526 gtk_target_table_free (gte, n);
2530 * Drag one or more sheet objects using GTK drag and drop, to the same
2531 * sheet, another workbook, another gnumeric or a different application.
2533 static void
2534 gnm_pane_drag_begin (GnmPane *pane, SheetObject *so, GdkEvent *event)
2536 GtkTargetList *targets, *im_targets;
2537 GocCanvas *canvas = GOC_CANVAS (pane);
2538 SheetControlGUI *scg = pane->simple.scg;
2539 GSList *objects;
2540 SheetObject *imageable = NULL, *exportable = NULL;
2541 GSList *ptr;
2542 SheetObject *candidate;
2544 targets = gtk_target_list_new (drag_types_out,
2545 G_N_ELEMENTS (drag_types_out));
2546 objects = go_hash_keys (scg->selected_objects);
2547 for (ptr = objects; ptr != NULL; ptr = ptr->next) {
2548 candidate = GNM_SO (ptr->data);
2550 if (exportable == NULL &&
2551 GNM_IS_SO_EXPORTABLE (candidate))
2552 exportable = candidate;
2553 if (imageable == NULL &&
2554 GNM_IS_SO_IMAGEABLE (candidate))
2555 imageable = candidate;
2558 if (exportable) {
2559 im_targets = sheet_object_exportable_get_target_list (exportable);
2560 if (im_targets != NULL) {
2561 target_list_add_list (targets, im_targets);
2562 gtk_target_list_unref (im_targets);
2565 if (imageable) {
2566 im_targets = sheet_object_get_target_list (imageable);
2567 if (im_targets != NULL) {
2568 target_list_add_list (targets, im_targets);
2569 gtk_target_list_unref (im_targets);
2574 if (gnm_debug_flag ("dnd")) {
2575 unsigned i, n;
2576 GtkTargetEntry *gte = gtk_target_table_new_from_list (targets, &n);
2577 g_printerr ("%u offered formats:\n", n);
2578 for (i = 0; i < n; i++)
2579 g_printerr ("%s\n", gte[n].target);
2580 gtk_target_table_free (gte, n);
2583 gtk_drag_begin (GTK_WIDGET (canvas), targets,
2584 GDK_ACTION_COPY | GDK_ACTION_MOVE,
2585 pane->drag.button, event);
2586 gtk_target_list_unref (targets);
2587 g_slist_free (objects);
2590 void
2591 gnm_pane_object_start_resize (GnmPane *pane, int button, guint64 x, gint64 y,
2592 SheetObject *so, int drag_type, gboolean is_creation)
2594 GocItem **ctrl_pts;
2596 g_return_if_fail (GNM_IS_SO (so));
2597 g_return_if_fail (0 <= drag_type);
2598 g_return_if_fail (drag_type < 9);
2600 ctrl_pts = g_hash_table_lookup (pane->drag.ctrl_pts, so);
2602 g_return_if_fail (NULL != ctrl_pts);
2604 if (is_creation && !sheet_object_can_resize (so)) {
2605 scg_objects_drag_commit (pane->simple.scg, 9, TRUE,
2606 NULL, NULL, NULL);
2607 return;
2609 gnm_simple_canvas_grab (ctrl_pts[drag_type]);
2610 pane->drag.created_objects = is_creation;
2611 pane->drag.button = button;
2612 pane->drag.last_x = pane->drag.origin_x = x;
2613 pane->drag.last_y = pane->drag.origin_y = y;
2614 pane->drag.had_motion = FALSE;
2615 gnm_pane_slide_init (pane);
2616 gnm_widget_set_cursor_type (GTK_WIDGET (pane), GDK_HAND2);
2620 GnmControlCircleItem
2622 typedef GocCircle GnmControlCircle;
2623 typedef GocCircleClass GnmControlCircleClass;
2625 #define CONTROL_TYPE_CIRCLE (control_circle_get_type ())
2626 #define CONTROL_CIRCLE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CONTROL_TYPE_CIRCLE, GnmControlCircle))
2627 #define CONTROL_IS_CIRCLE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CONTROL_TYPE_CIRCLE))
2629 static GType control_circle_get_type (void);
2631 static gboolean
2632 control_point_button_pressed (GocItem *item, int button, double x, double y)
2634 GnmPane *pane = GNM_PANE (item->canvas);
2635 GdkEventButton *event = (GdkEventButton *) goc_canvas_get_cur_event (item->canvas);
2636 SheetObject *so;
2637 int idx;
2639 if (0 != pane->drag.button)
2640 return TRUE;
2642 x *= goc_canvas_get_pixels_per_unit (item->canvas);
2643 y *= goc_canvas_get_pixels_per_unit (item->canvas);
2644 so = g_object_get_data (G_OBJECT (item), "so");
2645 idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2646 switch (event->button) {
2647 case 1:
2648 case 2: gnm_pane_object_start_resize (pane, button, x, y, so, idx, FALSE);
2649 break;
2650 case 3: gnm_pane_display_object_menu (pane, so, (GdkEvent *) event);
2651 break;
2652 default: /* Ignore mouse wheel events */
2653 return FALSE;
2655 return TRUE;
2658 static gboolean
2659 control_point_button_released (GocItem *item, int button, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
2661 GnmPane *pane = GNM_PANE (item->canvas);
2662 SheetControlGUI *scg = pane->simple.scg;
2663 SheetObject *so;
2664 int idx;
2666 if (pane->drag.button != button)
2667 return TRUE;
2668 so = g_object_get_data (G_OBJECT (item), "so");
2669 idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2670 pane->drag.button = 0;
2671 gnm_simple_canvas_ungrab (item);
2672 gnm_pane_slide_stop (pane);
2673 control_point_set_cursor (scg, item);
2674 if (idx == 8)
2675 ; /* ignore fake event generated by the dnd code */
2676 else if (pane->drag.had_motion)
2677 scg_objects_drag_commit (scg, idx,
2678 pane->drag.created_objects,
2679 NULL, NULL, NULL);
2680 else if (pane->drag.created_objects && idx == 7) {
2681 double w, h;
2682 sheet_object_default_size (so, &w, &h);
2683 scg_objects_drag (scg, NULL, NULL, &w, &h, 7, FALSE, FALSE, FALSE);
2684 scg_objects_drag_commit (scg, 7, TRUE,
2685 NULL, NULL, NULL);
2687 gnm_pane_clear_obj_size_tip (pane);
2688 return TRUE;
2691 static gboolean
2692 control_point_motion (GocItem *item, double x, double y)
2694 GnmPane *pane = GNM_PANE (item->canvas);
2695 GdkEventMotion *event = (GdkEventMotion *) goc_canvas_get_cur_event (item->canvas);
2696 SheetObject *so;
2697 int idx;
2699 if (0 == pane->drag.button)
2700 return TRUE;
2702 x *= goc_canvas_get_pixels_per_unit (item->canvas);
2703 y *= goc_canvas_get_pixels_per_unit (item->canvas);
2704 /* TODO : motion is still too granular along the internal axis
2705 * when the other axis is external.
2706 * eg drag from middle of sheet down. The x axis is still internal
2707 * onlt the y is external, however, since we are autoscrolling
2708 * we are limited to moving with col/row coords, not x,y.
2709 * Possible solution would be to change the EXTERIOR_ONLY flag
2710 * to something like USE_PIXELS_INSTEAD_OF_COLROW and change
2711 * the semantics of the col,row args in the callback. However,
2712 * that is more work than I want to do right now.
2714 so = g_object_get_data (G_OBJECT (item), "so");
2715 idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2716 if (idx == 8)
2717 gnm_pane_drag_begin (pane, so, (GdkEvent *) event);
2718 else if (gnm_pane_handle_motion (pane,
2719 item->canvas, x, y,
2720 GNM_PANE_SLIDE_X | GNM_PANE_SLIDE_Y |
2721 GNM_PANE_SLIDE_EXTERIOR_ONLY,
2722 cb_slide_handler, item))
2723 gnm_pane_object_move (pane, G_OBJECT (item),
2724 x, y,
2725 (event->state & GDK_CONTROL_MASK) != 0,
2726 (event->state & GDK_SHIFT_MASK) != 0);
2727 return TRUE;
2730 static gboolean
2731 control_point_button2_pressed (GocItem *item, int button, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
2733 GnmPane *pane = GNM_PANE (item->canvas);
2734 SheetControlGUI *scg = pane->simple.scg;
2735 SheetObject *so;
2737 so = g_object_get_data (G_OBJECT (item), "so");
2738 if (pane->drag.button == 1)
2739 sheet_object_get_editor (so, GNM_SHEET_CONTROL (scg));
2740 return TRUE;
2743 static void
2744 update_control_point_colors (GocItem *item, GtkStateFlags flags)
2746 GtkStyleContext *context = goc_item_get_style_context (item);
2747 GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (item));
2748 GdkRGBA *fore, *back;
2750 gtk_style_context_get (context, flags,
2751 "color", &fore,
2752 "background-color", &back,
2753 NULL);
2754 go_color_from_gdk_rgba (fore, &style->line.color);
2755 go_color_from_gdk_rgba (back, &style->fill.pattern.back);
2756 gdk_rgba_free (fore);
2757 gdk_rgba_free (back);
2758 goc_item_invalidate (item);
2761 static gboolean
2762 control_point_enter_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
2764 GnmPane *pane = GNM_PANE (item->canvas);
2765 SheetControlGUI *scg = pane->simple.scg;
2766 int idx;
2768 control_point_set_cursor (scg, item);
2770 pane->cur_object = g_object_get_data (G_OBJECT (item), "so");
2771 idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2772 if (idx != 8) {
2773 update_control_point_colors (item, GTK_STATE_FLAG_PRELIGHT);
2774 gnm_pane_display_obj_size_tip (pane, item);
2776 return TRUE;
2779 static gboolean
2780 control_point_leave_notify (GocItem *item, G_GNUC_UNUSED double x, G_GNUC_UNUSED double y)
2782 GnmPane *pane = GNM_PANE (item->canvas);
2783 SheetControlGUI *scg = pane->simple.scg;
2784 int idx;
2786 control_point_set_cursor (scg, item);
2788 idx = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "index"));
2789 if (idx != 8) {
2790 update_control_point_colors (item, GTK_STATE_FLAG_NORMAL);
2791 gnm_pane_clear_obj_size_tip (pane);
2793 pane->cur_object = NULL;
2794 return TRUE;
2797 static void
2798 control_circle_class_init (GocItemClass *item_klass)
2800 item_klass->button_pressed = control_point_button_pressed;
2801 item_klass->button_released = control_point_button_released;
2802 item_klass->motion = control_point_motion;
2803 item_klass->button2_pressed = control_point_button2_pressed;
2804 item_klass->enter_notify = control_point_enter_notify;
2805 item_klass->leave_notify = control_point_leave_notify;
2808 static GSF_CLASS (GnmControlCircle, control_circle,
2809 control_circle_class_init, NULL,
2810 GOC_TYPE_CIRCLE)
2812 #define GNM_ITEM_ACETATE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), item_acetate_get_type (), ItemAcetate))
2813 #define GNM_IS_ITEM_ACETATE(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), item_acetate_get_type ()))
2815 #define MARGIN 10
2817 static GType item_acetate_get_type (void);
2819 typedef GocRectangle ItemAcetate;
2820 typedef GocRectangleClass ItemAcetateClass;
2822 static double
2823 item_acetate_distance (GocItem *item, double x, double y, GocItem **actual_item)
2825 if (x < (item->x0 - MARGIN) ||
2826 x > (item->x1 + MARGIN) ||
2827 y < (item->y0 - MARGIN) ||
2828 y > (item->y1 + MARGIN))
2829 return DBL_MAX;
2830 *actual_item = item;
2831 return 0.;
2834 static void
2835 item_acetate_class_init (GocItemClass *item_class)
2837 item_class->distance = item_acetate_distance;
2838 item_class->button_pressed = control_point_button_pressed;
2839 item_class->button_released = control_point_button_released;
2840 item_class->motion = control_point_motion;
2841 item_class->button2_pressed = control_point_button2_pressed;
2842 item_class->enter_notify = control_point_enter_notify;
2843 item_class->leave_notify = control_point_leave_notify;
2846 static GSF_CLASS (ItemAcetate, item_acetate,
2847 item_acetate_class_init, NULL,
2848 GOC_TYPE_RECTANGLE)
2851 * new_control_point:
2852 * @pane: #GnmPane
2853 * @idx: control point index to be created
2854 * @x: x coordinate of control point
2855 * @y: y coordinate of control point
2857 * This is used to create a number of control points in a sheet
2858 * object, the meaning of them is used in other parts of the code
2859 * to belong to the following locations:
2861 * 0 -------- 1 -------- 2
2862 * | |
2863 * 3 4
2864 * | |
2865 * 5 -------- 6 -------- 7
2867 * 8 == a clear overlay that extends slightly beyond the region
2868 * 9 == an optional stippled rectangle for moving/resizing expensive
2869 * objects
2871 static GocItem *
2872 new_control_point (GnmPane *pane, SheetObject *so, int idx, double x, double y)
2874 GOStyle *style;
2875 GocItem *item;
2876 int radius, outline;
2877 double scale = GOC_CANVAS (pane)->pixels_per_unit;
2879 gtk_widget_style_get (GTK_WIDGET (pane),
2880 "control-circle-size", &radius,
2881 "control-circle-outline", &outline,
2882 NULL);
2884 style = go_style_new ();
2885 style->line.width = outline;
2886 style->line.auto_color = FALSE;
2887 style->line.dash_type = GO_LINE_SOLID; /* anything but 0 */
2888 style->line.pattern = GO_PATTERN_SOLID;
2889 item = goc_item_new (
2890 pane->action_items,
2891 CONTROL_TYPE_CIRCLE,
2892 "x", x,
2893 "y", y,
2894 "radius", radius / scale,
2895 NULL);
2896 g_object_unref (style);
2898 update_control_point_colors (item, GTK_STATE_FLAG_NORMAL);
2900 g_object_set_data (G_OBJECT (item), "index", GINT_TO_POINTER (idx));
2901 g_object_set_data (G_OBJECT (item), "so", so);
2903 return item;
2907 * set_item_x_y:
2908 * Changes the x and y position of the idx-th control point,
2909 * creating the control point if necessary.
2911 static void
2912 set_item_x_y (GnmPane *pane, SheetObject *so, GocItem **ctrl_pts,
2913 int idx, double x, double y, gboolean visible)
2915 double scale = GOC_CANVAS (pane)->pixels_per_unit;
2916 if (ctrl_pts[idx] == NULL)
2917 ctrl_pts[idx] = new_control_point (pane, so, idx, x / scale, y / scale);
2918 else
2919 goc_item_set (ctrl_pts[idx], "x", x / scale, "y", y / scale, NULL);
2920 if (visible)
2921 goc_item_show (ctrl_pts[idx]);
2922 else
2923 goc_item_hide (ctrl_pts[idx]);
2926 #define normalize_high_low(d1,d2) if (d1<d2) { double tmp=d1; d1=d2; d2=tmp;}
2928 static void
2929 set_acetate_coords (GnmPane *pane, SheetObject *so, GocItem **ctrl_pts,
2930 double l, double t, double r, double b)
2932 double scale = goc_canvas_get_pixels_per_unit (GOC_CANVAS (pane));
2933 int radius, outline;
2935 if (!sheet_object_rubber_band_directly (so)) {
2936 if (NULL == ctrl_pts[9]) {
2937 GOStyle *style = go_style_new ();
2938 GtkStyleContext *context;
2939 GdkRGBA rgba;
2940 GocItem *item;
2942 ctrl_pts[9] = item = goc_item_new (pane->action_items,
2943 GOC_TYPE_RECTANGLE,
2944 NULL);
2945 context = goc_item_get_style_context (item);
2946 gtk_style_context_add_class (context, "object-size");
2947 gtk_style_context_add_class (context, "rubber-band");
2949 style->fill.auto_type = FALSE;
2950 style->fill.type = GO_STYLE_FILL_PATTERN;
2951 style->fill.auto_back = FALSE;
2952 style->fill.pattern.back = 0;
2953 style->fill.auto_fore = FALSE;
2954 style->fill.pattern.fore = 0;
2955 style->line.pattern = GO_PATTERN_FOREGROUND_SOLID;
2956 style->line.width = 0.;
2957 style->line.auto_color = FALSE;
2958 style->line.color = 0;
2959 gnm_style_context_get_color (context, GTK_STATE_FLAG_NORMAL, &rgba);
2960 go_color_from_gdk_rgba (&rgba, &style->line.fore);
2961 go_styled_object_set_style (GO_STYLED_OBJECT (item),
2962 style);
2963 g_object_unref (style);
2964 goc_item_lower_to_bottom (item);
2966 normalize_high_low (r, l);
2967 normalize_high_low (b, t);
2968 goc_item_set (ctrl_pts[9],
2969 "x", l / scale, "y", t / scale,
2970 "width", (r - l) / scale, "height", (b - t) / scale,
2971 NULL);
2972 } else {
2973 double coords[4];
2974 SheetObjectView *sov = sheet_object_get_view (so, (SheetObjectViewContainer *)pane);
2975 if (NULL == sov)
2976 sov = sheet_object_new_view (so, (SheetObjectViewContainer *)pane);
2978 coords [0] = l; coords [2] = r; coords [1] = t; coords [3] = b;
2979 if (NULL != sov)
2980 sheet_object_view_set_bounds (sov, coords, TRUE);
2981 normalize_high_low (r, l);
2982 normalize_high_low (b, t);
2985 gtk_widget_style_get (GTK_WIDGET (pane),
2986 "control-circle-size", &radius,
2987 "control-circle-outline", &outline,
2988 NULL);
2990 l -= (radius + outline) / 2 - 1;
2991 r += (radius + outline) / 2;
2992 t -= (radius + outline) / 2 - 1;
2993 b += (radius + outline) / 2;
2995 if (NULL == ctrl_pts[8]) {
2996 GOStyle *style = go_style_new ();
2997 GocItem *item;
2999 style->fill.auto_type = FALSE;
3000 style->fill.type = GO_STYLE_FILL_PATTERN;
3001 style->fill.auto_back = FALSE;
3002 go_pattern_set_solid (&style->fill.pattern, 0);
3003 style->line.auto_dash = FALSE;
3004 style->line.dash_type = GO_LINE_NONE;
3005 /* work around the screwup in shapes that adds a large
3006 * border to anything that uses miter (is this required for
3007 * a rectangle in goc-canvas? */
3008 style->line.join = CAIRO_LINE_JOIN_ROUND;
3009 item = goc_item_new (
3010 pane->action_items,
3011 item_acetate_get_type (),
3012 "style", style,
3013 NULL);
3014 g_object_unref (style);
3015 g_object_set_data (G_OBJECT (item), "index",
3016 GINT_TO_POINTER (8));
3017 g_object_set_data (G_OBJECT (item), "so", so);
3019 ctrl_pts[8] = item;
3021 goc_item_set (ctrl_pts[8],
3022 "x", l / scale,
3023 "y", t / scale,
3024 "width", (r - l) / scale,
3025 "height", (b - t) / scale,
3026 NULL);
3029 void
3030 gnm_pane_object_unselect (GnmPane *pane, SheetObject *so)
3032 gnm_pane_clear_obj_size_tip (pane);
3033 g_hash_table_remove (pane->drag.ctrl_pts, so);
3037 * gnm_pane_object_update_bbox:
3038 * @pane: #GnmPane
3039 * @so: #SheetObject
3041 * Updates the position and potentially creates control points
3042 * for manipulating the size/position of @so.
3044 void
3045 gnm_pane_object_update_bbox (GnmPane *pane, SheetObject *so)
3047 GocItem **ctrl_pts = g_hash_table_lookup (pane->drag.ctrl_pts, so);
3048 double const *pts = g_hash_table_lookup (
3049 pane->simple.scg->selected_objects, so);
3050 int radius, outline, total_size;
3052 if (ctrl_pts == NULL) {
3053 ctrl_pts = g_new0 (GocItem *, 10);
3054 g_hash_table_insert (pane->drag.ctrl_pts, so, ctrl_pts);
3057 g_return_if_fail (ctrl_pts != NULL);
3059 gtk_widget_style_get (GTK_WIDGET (pane),
3060 "control-circle-size", &radius,
3061 "control-circle-outline", &outline,
3062 NULL);
3063 /* space for 2 halves and a full */
3064 total_size = radius * 4 + outline * 2;
3066 /* set the acetate 1st so that the other points will override it */
3067 set_acetate_coords (pane, so, ctrl_pts, pts[0], pts[1], pts[2], pts[3]);
3068 if (sheet_object_can_resize (so)) {
3069 set_item_x_y (pane, so, ctrl_pts, 0, pts[0], pts[1], TRUE);
3070 set_item_x_y (pane, so, ctrl_pts, 1, (pts[0] + pts[2]) / 2, pts[1],
3071 fabs (pts[2]-pts[0]) >= total_size);
3072 set_item_x_y (pane, so, ctrl_pts, 2, pts[2], pts[1], TRUE);
3073 set_item_x_y (pane, so, ctrl_pts, 3, pts[0], (pts[1] + pts[3]) / 2,
3074 fabs (pts[3]-pts[1]) >= total_size);
3075 set_item_x_y (pane, so, ctrl_pts, 4, pts[2], (pts[1] + pts[3]) / 2,
3076 fabs (pts[3]-pts[1]) >= total_size);
3077 set_item_x_y (pane, so, ctrl_pts, 5, pts[0], pts[3], TRUE);
3078 set_item_x_y (pane, so, ctrl_pts, 6, (pts[0] + pts[2]) / 2, pts[3],
3079 fabs (pts[2]-pts[0]) >= total_size);
3080 set_item_x_y (pane, so, ctrl_pts, 7, pts[2], pts[3], TRUE);
3084 static void
3085 cb_bounds_changed (SheetObject *so, GocItem *sov)
3087 double coords[4], *cur;
3088 SheetControlGUI *scg = GNM_SIMPLE_CANVAS (sov->canvas)->scg;
3089 if (GNM_PANE (sov->canvas)->drag.button != 0)
3090 return; /* do not reset bounds during drag */
3092 scg_object_anchor_to_coords (scg, sheet_object_get_anchor (so), coords);
3093 if (NULL != scg->selected_objects &&
3094 NULL != (cur = g_hash_table_lookup (scg->selected_objects, so))) {
3095 int i;
3096 for (i = 4; i-- > 0 ;) cur[i] = coords[i];
3097 gnm_pane_object_update_bbox (GNM_PANE (sov->canvas), so);
3100 sheet_object_view_set_bounds (GNM_SO_VIEW (sov),
3101 coords, so->flags & SHEET_OBJECT_IS_VISIBLE);
3105 * gnm_pane_object_register:
3106 * @so: A sheet object
3107 * @view: A canvas item acting as a view for @so
3108 * @selectable: Add handlers for selecting and editing the object
3110 * Setup some standard callbacks for manipulating a view of a sheet object.
3111 * Returns: (transfer none): @view set to a #SheetObjectView.
3113 SheetObjectView *
3114 gnm_pane_object_register (SheetObject *so, GocItem *view, gboolean selectable)
3116 g_signal_connect_object (so, "bounds-changed",
3117 G_CALLBACK (cb_bounds_changed), view, 0);
3118 return GNM_SO_VIEW (view);
3122 * gnm_pane_object_widget_register:
3123 * @so: A sheet object
3124 * @widget: The widget for the sheet object view
3125 * @view: A canvas item acting as a view for @so
3127 * Setup some standard callbacks for manipulating widgets as views of sheet
3128 * objects.
3130 void
3131 gnm_pane_widget_register (SheetObject *so, GtkWidget *w, GocItem *view)
3133 if (GTK_IS_CONTAINER (w)) {
3134 GList *ptr, *children = gtk_container_get_children (GTK_CONTAINER (w));
3135 for (ptr = children ; ptr != NULL; ptr = ptr->next)
3136 gnm_pane_widget_register (so, ptr->data, view);
3137 g_list_free (children);
3141 void
3142 gnm_pane_set_direction (GnmPane *pane, GocDirection direction)
3144 goc_canvas_set_direction (GOC_CANVAS (pane), direction);
3145 if (pane->col.canvas != NULL)
3146 goc_canvas_set_direction (pane->col.canvas, direction);