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