1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
4 * gnm-cell-combo-view.c: A canvas object for an in-cell combo-box
6 * Copyright (C) 2006-2007 Jody Goldberg (jody@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
24 #include <gnumeric-config.h>
25 #include "gnm-cell-combo-view.h"
26 #include "gnm-cell-combo-view-impl.h"
30 #include "sheet-control-gui.h"
31 #include "sheet-merge.h"
32 #include "gnm-pane-impl.h"
35 #include <goffice/goffice.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <gsf/gsf-impl-utils.h>
42 #define AUTOSCROLL_ID "autoscroll-id"
43 #define AUTOSCROLL_DIR "autoscroll-dir"
45 static void ccombo_popup_destroy (GtkWidget
*list
);
48 ccombo_create_arrow (GnmCComboView
*ccombo
, SheetObject
*so
)
50 GnmCComboViewClass
*klass
= GNM_CCOMBO_VIEW_GET_CLASS (ccombo
);
51 return (klass
->create_arrow
) (so
);
55 ccombo_activate (GtkTreeView
*list
, gboolean button
)
57 SheetObjectView
*sov
= g_object_get_data (G_OBJECT (list
), SOV_ID
);
58 GocItem
*view
= GOC_ITEM (sov
);
59 GnmPane
*pane
= GNM_PANE (view
->canvas
);
60 GnmCComboViewClass
*klass
= GNM_CCOMBO_VIEW_GET_CLASS (sov
);
62 if ((klass
->activate
) (sheet_object_view_get_so (sov
), list
,
63 scg_wbcg (pane
->simple
.scg
), button
))
65 ccombo_popup_destroy (GTK_WIDGET (list
));
72 ccombo_create_list (GnmCComboView
*ccombo
, SheetObject
*so
,
73 GtkTreePath
**clip
, GtkTreePath
**select
, gboolean
*make_buttons
)
75 GnmCComboViewClass
*klass
= GNM_CCOMBO_VIEW_GET_CLASS (ccombo
);
76 return (klass
->create_list
) (so
, clip
, select
, make_buttons
);
79 /****************************************************************************/
81 /* Cut and paste from gtkwindow.c */
83 ccombo_focus_change (GtkWidget
*widget
, gboolean in
)
87 g_object_ref (widget
);
89 gtk_widget_set_can_focus (widget
, in
);
91 fevent
.type
= GDK_FOCUS_CHANGE
;
92 fevent
.window
= gtk_widget_get_window (widget
);
95 gtk_widget_event (widget
, (GdkEvent
*)&fevent
);
97 g_object_notify (G_OBJECT (widget
), "has-focus");
99 g_object_unref (widget
);
103 cb_ccombo_autoscroll (GtkTreeView
*list
)
106 GtkTreePath
*path
= NULL
;
107 gpointer dir
= g_object_get_data (G_OBJECT (list
), AUTOSCROLL_DIR
);
109 gtk_tree_view_get_cursor (list
, &path
, NULL
);
110 if (GPOINTER_TO_INT (dir
) > 0) {
112 /* why does _next not return a boolean ? list _prev */
113 gtk_tree_path_next (path
);
114 ok
= gtk_tree_model_get_iter (gtk_tree_view_get_model (list
),
117 ok
= gtk_tree_path_prev (path
);
120 gtk_tree_selection_select_path (gtk_tree_view_get_selection (list
), path
);
121 gtk_tree_view_set_cursor (list
, path
, NULL
, FALSE
);
123 gtk_tree_path_free (path
);
128 ccombo_autoscroll_set (GObject
*list
, int dir
)
130 gpointer id
= g_object_get_data (list
, AUTOSCROLL_ID
);
133 guint timer_id
= g_timeout_add (50,
134 (GSourceFunc
)cb_ccombo_autoscroll
, list
);
135 g_object_set_data (list
, AUTOSCROLL_ID
,
136 GUINT_TO_POINTER (timer_id
));
138 } else if (dir
== 0) {
139 g_source_remove (GPOINTER_TO_UINT (id
));
140 g_object_set_data (list
, AUTOSCROLL_ID
, NULL
);
142 g_object_set_data (list
, AUTOSCROLL_DIR
, GINT_TO_POINTER (dir
));
146 ccombo_popup_destroy (GtkWidget
*list
)
148 ccombo_autoscroll_set (G_OBJECT (list
), 0);
149 ccombo_focus_change (list
, FALSE
);
150 gtk_widget_destroy (gtk_widget_get_toplevel (list
));
154 cb_ccombo_key_press (G_GNUC_UNUSED GtkWidget
*popup
, GdkEventKey
*event
, GtkWidget
*list
)
156 switch (event
->keyval
) {
157 case GDK_KEY_Escape
:
158 ccombo_popup_destroy (list
);
161 case GDK_KEY_KP_Down
:
166 if (!(event
->state
& GDK_MOD1_MASK
))
169 case GDK_KEY_KP_Enter
:
170 case GDK_KEY_Return
:
171 ccombo_activate (GTK_TREE_VIEW (list
), FALSE
);
180 cb_ccombo_popup_motion (G_GNUC_UNUSED GtkWidget
*widget
, GdkEventMotion
*event
,
186 gtk_widget_get_allocation (GTK_WIDGET (list
), &la
);
188 gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (list
)),
190 if (event
->y_root
< base
)
192 else if (event
->y_root
>= (base
+ la
.height
))
196 ccombo_autoscroll_set (G_OBJECT (list
), dir
);
201 cb_ccombo_list_motion (GtkWidget
*widget
, GdkEventMotion
*event
,
207 gtk_widget_get_allocation (widget
, &wa
);
209 if (event
->x
>= 0 && event
->y
>= 0 &&
210 event
->x
< wa
.width
&& event
->y
< wa
.height
&&
211 gtk_tree_view_get_path_at_pos (list
, event
->x
, event
->y
,
212 &path
, NULL
, NULL
, NULL
)) {
213 gtk_tree_selection_select_path (gtk_tree_view_get_selection (list
), path
);
214 gtk_tree_view_set_cursor (list
, path
, NULL
, FALSE
);
215 gtk_tree_path_free (path
);
217 ccombo_autoscroll_set (G_OBJECT (list
), 0);
222 cb_ccombo_list_button_press (GtkWidget
*list
,
223 G_GNUC_UNUSED GdkEventButton
*event
,
226 if (event
->button
== 1)
227 g_signal_connect (popup
, "motion_notify_event",
228 G_CALLBACK (cb_ccombo_popup_motion
), list
);
233 cb_ccombo_button_press (GtkWidget
*popup
, GdkEventButton
*event
,
236 /* btn1 down outside the popup cancels */
237 if (event
->button
== 1 &&
238 event
->window
!= gtk_widget_get_window (popup
)) {
239 ccombo_popup_destroy (list
);
246 cb_ccombo_button_release (GtkWidget
*popup
, GdkEventButton
*event
,
249 if (event
->button
== 1) {
250 if (gtk_get_event_widget ((GdkEvent
*) event
) == GTK_WIDGET (list
))
251 return ccombo_activate (list
, FALSE
);
253 g_signal_handlers_disconnect_by_func (popup
,
254 G_CALLBACK (cb_ccombo_popup_motion
), list
);
255 ccombo_autoscroll_set (G_OBJECT (list
), 0);
260 static void cb_ccombo_button_pressed (SheetObjectView
*sov
) { gnm_cell_combo_view_popdown (sov
, GDK_CURRENT_TIME
); }
261 static void cb_ccombo_ok_button (GtkTreeView
*list
) { ccombo_activate (list
, TRUE
); }
262 static void cb_ccombo_cancel_button (GtkWidget
*list
) { ccombo_popup_destroy (list
); }
265 cb_realize_treeview (GtkWidget
*list
, GtkWidget
*sw
)
269 GtkTreePath
*clip
= g_object_get_data (G_OBJECT (list
), "clip");
271 gtk_widget_get_preferred_size (GTK_WIDGET (list
), &req
, NULL
);
273 gtk_tree_view_get_background_area (GTK_TREE_VIEW (list
),
276 gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW (sw
), req
.width
);
278 gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (sw
), rect
.y
);
282 * gnm_cell_combo_view_popdown:
283 * @sov: #SheetObjectView
284 * @activate_time: event time
286 * Open the popup window associated with @sov
289 gnm_cell_combo_view_popdown (SheetObjectView
*sov
, guint32 activate_time
)
291 GocItem
*view
= GOC_ITEM (sov
);
292 GnmPane
*pane
= GNM_PANE (view
->canvas
);
293 SheetControlGUI
*scg
= pane
->simple
.scg
;
294 SheetObject
*so
= sheet_object_view_get_so (sov
);
295 Sheet
const *sheet
= sheet_object_get_sheet (so
);
296 GtkWidget
*frame
, *popup
, *list
, *container
;
298 gboolean make_buttons
= FALSE
;
299 GtkTreePath
*clip
= NULL
, *select
= NULL
;
300 GtkWindow
*toplevel
= wbcg_toplevel (scg_wbcg (scg
));
301 GdkWindow
*popup_window
;
303 GnmRange
const *merge
;
305 popup
= gtk_window_new (GTK_WINDOW_POPUP
);
307 gtk_window_set_type_hint (GTK_WINDOW (popup
), GDK_WINDOW_TYPE_HINT_COMBO
);
308 gtk_window_group_add_window (gtk_window_get_group (toplevel
), GTK_WINDOW (popup
));
309 go_gtk_window_set_transient (toplevel
, GTK_WINDOW (popup
));
310 gtk_window_set_resizable (GTK_WINDOW (popup
), FALSE
);
311 gtk_window_set_decorated (GTK_WINDOW (popup
), FALSE
);
312 gtk_window_set_screen (GTK_WINDOW (popup
),
313 gtk_widget_get_screen (GTK_WIDGET (toplevel
)));
315 list
= ccombo_create_list (GNM_CCOMBO_VIEW (sov
), so
, &clip
, &select
, &make_buttons
);
317 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list
), FALSE
);
318 g_object_set_data (G_OBJECT (list
), SOV_ID
, sov
);
320 frame
= gtk_frame_new (NULL
);
321 gtk_frame_set_shadow_type (GTK_FRAME (frame
), GTK_SHADOW_OUT
);
324 range_dump (&so
->anchor
.cell_bound
, "");
325 g_printerr (" : so = %p, view = %p\n", so
, view
);
328 GtkWidget
*sw
= gtk_scrolled_window_new (
329 gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (list
)),
330 gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (list
)));
331 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw
),
332 GTK_POLICY_AUTOMATIC
,
334 g_object_set_data_full (G_OBJECT (list
),
336 (GDestroyNotify
)gtk_tree_path_free
);
338 gtk_container_add (GTK_CONTAINER (sw
), list
);
341 * Do the sizing in a realize handler as newer versions of
342 * gtk+ give us zero sizes until then.
344 g_signal_connect_after (list
, "realize",
345 G_CALLBACK (cb_realize_treeview
),
352 GtkWidget
*vbox
= gtk_box_new (GTK_ORIENTATION_VERTICAL
, 0);
353 GtkWidget
*hbox
= gtk_box_new (GTK_ORIENTATION_HORIZONTAL
, 0);
356 button
= gtk_button_new_from_stock (GTK_STOCK_CANCEL
);
357 g_signal_connect_swapped (button
, "clicked",
358 G_CALLBACK (cb_ccombo_cancel_button
), list
);
359 gtk_box_pack_start (GTK_BOX (hbox
), button
, FALSE
, TRUE
, 6);
360 button
= gtk_button_new_from_stock (GTK_STOCK_OK
);
361 g_signal_connect_swapped (button
, "clicked",
362 G_CALLBACK (cb_ccombo_ok_button
), list
);
363 gtk_box_pack_start (GTK_BOX (hbox
), button
, FALSE
, TRUE
, 6);
365 gtk_box_pack_start (GTK_BOX (vbox
), container
, FALSE
, TRUE
, 6);
366 gtk_box_pack_start (GTK_BOX (vbox
), hbox
, FALSE
, TRUE
, 6);
370 gtk_container_add (GTK_CONTAINER (frame
), container
);
373 gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (pane
)),
375 if (sheet
->text_is_rtl
) {
377 gtk_widget_get_allocation (GTK_WIDGET (pane
), &pa
);
379 root_x
-= scg_colrow_distance_get (scg
, TRUE
,
381 so
->anchor
.cell_bound
.start
.col
+ 1);
383 root_x
+= scg_colrow_distance_get (scg
, TRUE
,
385 so
->anchor
.cell_bound
.start
.col
);
386 merge
= gnm_sheet_merge_is_corner (sheet
, &(so
->anchor
.cell_bound
.start
));
387 gtk_window_move (GTK_WINDOW (popup
), root_x
,
388 root_y
+ scg_colrow_distance_get
391 so
->anchor
.cell_bound
.start
.row
+
392 ((merge
== NULL
) ? 1 : range_height (merge
))));
394 gtk_container_add (GTK_CONTAINER (popup
), frame
);
396 g_signal_connect (popup
, "key_press_event",
397 G_CALLBACK (cb_ccombo_key_press
), list
);
398 g_signal_connect (popup
, "button_press_event",
399 G_CALLBACK (cb_ccombo_button_press
), list
);
400 g_signal_connect_after (popup
, "button_release_event",
401 G_CALLBACK (cb_ccombo_button_release
), list
);
402 g_signal_connect (list
, "motion_notify_event",
403 G_CALLBACK (cb_ccombo_list_motion
), list
);
404 g_signal_connect (list
, "button_press_event",
405 G_CALLBACK (cb_ccombo_list_button_press
), popup
);
407 gtk_widget_show_all (popup
);
409 /* after we show the window setup the selection (showing the list
410 * clears the selection) */
411 if (select
!= NULL
) {
412 gtk_tree_selection_select_path (
413 gtk_tree_view_get_selection (GTK_TREE_VIEW (list
)),
415 gtk_tree_view_set_cursor (GTK_TREE_VIEW (list
),
416 select
, NULL
, FALSE
);
417 gtk_tree_path_free (select
);
420 gtk_widget_grab_focus (popup
);
421 gtk_widget_grab_focus (GTK_WIDGET (list
));
422 ccombo_focus_change (GTK_WIDGET (list
), TRUE
);
424 popup_window
= gtk_widget_get_window (popup
);
426 device
= gtk_get_current_event_device ();
427 if (0 == gdk_device_grab (device
, popup_window
,
428 GDK_OWNERSHIP_APPLICATION
, TRUE
,
429 GDK_BUTTON_PRESS_MASK
|
430 GDK_BUTTON_RELEASE_MASK
|
431 GDK_POINTER_MOTION_MASK
,
432 NULL
, activate_time
)) {
433 if (0 == gdk_device_grab (gdk_device_get_associated_device (device
),
435 GDK_OWNERSHIP_APPLICATION
, TRUE
,
437 GDK_KEY_RELEASE_MASK
,
438 NULL
, activate_time
))
439 gtk_grab_add (popup
);
441 gdk_device_ungrab (device
, activate_time
);
446 * gnm_cell_combo_view_new:
449 * @container: SheetObjectViewContainer (a GnmPane)
451 * Create and register an in cell combo to pick from an autofilter list.
454 gnm_cell_combo_view_new (SheetObject
*so
, GType type
,
455 SheetObjectViewContainer
*container
)
457 GnmPane
*pane
= GNM_PANE (container
);
458 GtkWidget
*view_widget
= gtk_button_new ();
459 GocItem
*ccombo
= goc_item_new (pane
->object_views
, type
, NULL
);
460 goc_item_new (GOC_GROUP (ccombo
), GOC_TYPE_WIDGET
,
461 "widget", view_widget
,
463 gtk_widget_set_can_focus (view_widget
, FALSE
);
465 gtk_container_add (GTK_CONTAINER (view_widget
),
466 ccombo_create_arrow (GNM_CCOMBO_VIEW (ccombo
), so
));
467 g_signal_connect_swapped (view_widget
, "pressed",
468 G_CALLBACK (cb_ccombo_button_pressed
), ccombo
);
469 gtk_widget_show_all (view_widget
);
471 return gnm_pane_object_register (so
, ccombo
, FALSE
);
475 gnm_cell_combo_view_init (SheetObjectView
*view
)
477 view
->resize_mode
= GNM_SO_RESIZE_AUTO
;
480 GSF_CLASS (GnmCComboView
, gnm_ccombo_view
,
481 NULL
, gnm_cell_combo_view_init
,