GUI: Dead kittens.
[gnumeric.git] / src / widgets / gnm-cell-combo-view.c
blobe637cffa4d23aa544458668d8be2a37fbe31092b
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
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
21 * USA
24 #include <gnumeric-config.h>
25 #include "gnm-cell-combo-view.h"
26 #include "gnm-cell-combo-view-impl.h"
28 #include "wbc-gtk.h"
29 #include "sheet.h"
30 #include "sheet-control-gui.h"
31 #include "sheet-merge.h"
32 #include "gnm-pane-impl.h"
33 #include "ranges.h"
35 #include <goffice/goffice.h>
36 #include <gtk/gtk.h>
37 #include <gdk/gdk.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <gsf/gsf-impl-utils.h>
41 #define SOV_ID "sov"
42 #define AUTOSCROLL_ID "autoscroll-id"
43 #define AUTOSCROLL_DIR "autoscroll-dir"
45 static void ccombo_popup_destroy (GtkWidget *list);
47 static GtkWidget *
48 ccombo_create_arrow (GnmCComboView *ccombo, SheetObject *so)
50 GnmCComboViewClass *klass = GNM_CCOMBO_VIEW_GET_CLASS (ccombo);
51 return (klass->create_arrow) (so);
54 static gboolean
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));
66 return TRUE;
68 return FALSE;
71 static GtkWidget *
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 */
82 static void
83 ccombo_focus_change (GtkWidget *widget, gboolean in)
85 GdkEventFocus fevent;
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);
93 fevent.in = in;
95 gtk_widget_event (widget, (GdkEvent *)&fevent);
97 g_object_notify (G_OBJECT (widget), "has-focus");
99 g_object_unref (widget);
102 static gint
103 cb_ccombo_autoscroll (GtkTreeView *list)
105 gboolean ok;
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) {
111 GtkTreeIter iter;
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),
115 &iter, path);
116 } else
117 ok = gtk_tree_path_prev (path);
119 if (ok) {
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);
124 return ok;
127 static void
128 ccombo_autoscroll_set (GObject *list, int dir)
130 gpointer id = g_object_get_data (list, AUTOSCROLL_ID);
131 if (id == NULL) {
132 if (dir != 0) {
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));
145 static void
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));
153 static gint
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);
159 return TRUE;
161 case GDK_KEY_KP_Down :
162 case GDK_KEY_Down :
163 case GDK_KEY_KP_Up :
164 /* fallthrough */
165 case GDK_KEY_Up :
166 if (!(event->state & GDK_MOD1_MASK))
167 return FALSE;
169 case GDK_KEY_KP_Enter :
170 case GDK_KEY_Return :
171 ccombo_activate (GTK_TREE_VIEW (list), FALSE);
172 return TRUE;
173 default :
176 return FALSE;
179 static gboolean
180 cb_ccombo_popup_motion (G_GNUC_UNUSED GtkWidget *widget, GdkEventMotion *event,
181 GtkTreeView *list)
183 int base, dir = 0;
184 GtkAllocation la;
186 gtk_widget_get_allocation (GTK_WIDGET (list), &la);
188 gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (list)),
189 NULL, &base);
190 if (event->y_root < base)
191 dir = -1;
192 else if (event->y_root >= (base + la.height))
193 dir = 1;
194 else
195 dir = 0;
196 ccombo_autoscroll_set (G_OBJECT (list), dir);
197 return FALSE;
200 static gboolean
201 cb_ccombo_list_motion (GtkWidget *widget, GdkEventMotion *event,
202 GtkTreeView *list)
204 GtkTreePath *path;
205 GtkAllocation wa;
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);
218 return FALSE;
221 static gint
222 cb_ccombo_list_button_press (GtkWidget *list,
223 G_GNUC_UNUSED GdkEventButton *event,
224 GtkWidget *popup)
226 if (event->button == 1)
227 g_signal_connect (popup, "motion_notify_event",
228 G_CALLBACK (cb_ccombo_popup_motion), list);
229 return FALSE;
232 static gint
233 cb_ccombo_button_press (GtkWidget *popup, GdkEventButton *event,
234 GtkWidget *list)
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);
240 return TRUE;
242 return FALSE;
245 static gint
246 cb_ccombo_button_release (GtkWidget *popup, GdkEventButton *event,
247 GtkTreeView *list)
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);
257 return FALSE;
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); }
264 static void
265 cb_realize_treeview (GtkWidget *list, GtkWidget *sw)
267 GtkRequisition req;
268 GdkRectangle rect;
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),
274 clip, NULL, &rect);
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
288 void
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;
297 int root_x, root_y;
298 gboolean make_buttons = FALSE;
299 GtkTreePath *clip = NULL, *select = NULL;
300 GtkWindow *toplevel = wbcg_toplevel (scg_wbcg (scg));
301 GdkWindow *popup_window;
302 GdkDevice *device;
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);
323 #if 0
324 range_dump (&so->anchor.cell_bound, "");
325 g_printerr (" : so = %p, view = %p\n", so, view);
326 #endif
327 if (clip != NULL) {
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,
333 GTK_POLICY_ALWAYS);
334 g_object_set_data_full (G_OBJECT (list),
335 "clip", clip,
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),
346 sw);
347 container = sw;
348 } else
349 container = list;
351 if (make_buttons) {
352 GtkWidget *vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
353 GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
355 GtkWidget *button;
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);
367 container = vbox;
370 gtk_container_add (GTK_CONTAINER (frame), container);
372 /* do the popup */
373 gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (pane)),
374 &root_x, &root_y);
375 if (sheet->text_is_rtl) {
376 GtkAllocation pa;
377 gtk_widget_get_allocation (GTK_WIDGET (pane), &pa);
378 root_x += pa.width;
379 root_x -= scg_colrow_distance_get (scg, TRUE,
380 pane->first.col,
381 so->anchor.cell_bound.start.col + 1);
382 } else
383 root_x += scg_colrow_distance_get (scg, TRUE,
384 pane->first.col,
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
389 (scg, FALSE,
390 pane->first.row,
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)),
414 select);
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),
434 popup_window,
435 GDK_OWNERSHIP_APPLICATION, TRUE,
436 GDK_KEY_PRESS_MASK |
437 GDK_KEY_RELEASE_MASK,
438 NULL, activate_time))
439 gtk_grab_add (popup);
440 else
441 gdk_device_ungrab (device, activate_time);
446 * gnm_cell_combo_view_new:
447 * @so: #SheetObject
448 * @type: #GType
449 * @container: SheetObjectViewContainer (a GnmPane)
451 * Create and register an in cell combo to pick from an autofilter list.
453 SheetObjectView *
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,
462 NULL);
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);
474 static void
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,
482 GNM_SO_VIEW_TYPE)