evolution-3.6.4/widgets/table/e-cell-combo.c

No issues found

  1 /*
  2  * e-cell-combo.c: Combo cell renderer
  3  *
  4  * This program is free software; you can redistribute it and/or
  5  * modify it under the terms of the GNU Lesser General Public
  6  * License as published by the Free Software Foundation; either
  7  * version 2 of the License, or (at your option) version 3.
  8  *
  9  * This program is distributed in the hope that it will be useful,
 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 12  * Lesser General Public License for more details.
 13  *
 14  * You should have received a copy of the GNU Lesser General Public
 15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
 16  *
 17  *
 18  * Authors:
 19  *		Damon Chaplin <damon@ximian.com>
 20  *
 21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 22  *
 23  */
 24 
 25 /*
 26  * ECellCombo - a subclass of ECellPopup used to support popup lists like a
 27  * GtkCombo widget. It only supports a basic popup list of strings at present,
 28  * with no auto-completion.
 29  */
 30 
 31 /*
 32  * Notes: (handling pointer grabs and GTK+ grabs is a nightmare!)
 33  *
 34  * o We must grab the pointer when we show the popup, so that if any buttons
 35  *   are pressed outside the application we hide the popup.
 36  *
 37  * o We have to be careful when popping up any widgets which also grab the
 38  *   pointer at some point, since we will lose our own pointer grab.
 39  *   When we pop up a list it will grab the pointer itself when an item is
 40  *   selected, and release the grab when the button is released.
 41  *   Fortunately we hide the popup at this point, so it isn't a problem.
 42  *   But for other types of widgets in the popup it could cause trouble.
 43  *   - I think GTK+ should provide help for this (nested pointer grabs?).
 44  *
 45  * o We must set the 'owner_events' flag of the pointer grab to TRUE so that
 46  *   pointer events get reported to all the application windows as normal.
 47  *   If we don't do this then the widgets in the popup may not work properly.
 48  *
 49  * o We must do a gtk_grab_add() so that we only allow events to go to the
 50  *   widgets within the popup (though some special events still get reported
 51  *   to the widget owning the window). Doing th gtk_grab_add() on the toplevel
 52  *   popup window should be fine. We can then check for any events that should
 53  *   close the popup, like the Escape key, or a button press outside the popup.
 54  */
 55 
 56 #ifdef HAVE_CONFIG_H
 57 #include <config.h>
 58 #endif
 59 
 60 #include <string.h>
 61 
 62 #include <gdk/gdkkeysyms.h>
 63 #include <gtk/gtk.h>
 64 
 65 #include <glib/gi18n.h>
 66 #include "e-util/e-util.h"
 67 #include "e-util/e-unicode.h"
 68 
 69 #include "e-table-item.h"
 70 #include "e-cell-combo.h"
 71 #include "e-cell-text.h"
 72 
 73 #define d(x)
 74 
 75 /* The height to make the popup list if there aren't any items in it. */
 76 #define	E_CELL_COMBO_LIST_EMPTY_HEIGHT	15
 77 
 78 static void	e_cell_combo_dispose		(GObject *object);
 79 static gint	e_cell_combo_do_popup		(ECellPopup *ecp,
 80 						 GdkEvent *event,
 81 						 gint row,
 82 						 gint view_col);
 83 static void	e_cell_combo_select_matching_item
 84 						(ECellCombo *ecc);
 85 static void	e_cell_combo_show_popup		(ECellCombo *ecc,
 86 						 gint row,
 87 						 gint view_col);
 88 static void	e_cell_combo_get_popup_pos	(ECellCombo *ecc,
 89 						 gint row,
 90 						 gint view_col,
 91 						 gint *x,
 92 						 gint *y,
 93 						 gint *height,
 94 						 gint *width);
 95 static void	e_cell_combo_selection_changed	(GtkTreeSelection *selection,
 96 						 ECellCombo *ecc);
 97 static gint	e_cell_combo_button_press	(GtkWidget *popup_window,
 98 						 GdkEvent *event,
 99 						 ECellCombo *ecc);
100 static gint	e_cell_combo_button_release	(GtkWidget *popup_window,
101 						 GdkEventButton *event,
102 						 ECellCombo *ecc);
103 static gint	e_cell_combo_key_press		(GtkWidget *popup_window,
104 						 GdkEventKey *event,
105 						 ECellCombo *ecc);
106 static void	e_cell_combo_update_cell	(ECellCombo *ecc);
107 static void	e_cell_combo_restart_edit	(ECellCombo *ecc);
108 
109 G_DEFINE_TYPE (ECellCombo, e_cell_combo, E_TYPE_CELL_POPUP)
110 
111 static void
112 e_cell_combo_class_init (ECellComboClass *class)
113 {
114 	ECellPopupClass *ecpc = E_CELL_POPUP_CLASS (class);
115 	GObjectClass *object_class = G_OBJECT_CLASS (class);
116 
117 	object_class->dispose = e_cell_combo_dispose;
118 
119 	ecpc->popup = e_cell_combo_do_popup;
120 }
121 
122 static void
123 e_cell_combo_init (ECellCombo *ecc)
124 {
125 	GtkWidget *frame;
126 	AtkObject *a11y;
127 	GtkListStore *store;
128 	GtkTreeSelection *selection;
129 	GtkScrolledWindow *scrolled_window;
130 
131 	/* We create one popup window for the ECell, since there will only
132 	 * ever be one popup in use at a time. */
133 	ecc->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
134 
135 	gtk_window_set_type_hint (
136 		GTK_WINDOW (ecc->popup_window), GDK_WINDOW_TYPE_HINT_COMBO);
137 	gtk_window_set_resizable (GTK_WINDOW (ecc->popup_window), TRUE);
138 
139 	frame = gtk_frame_new (NULL);
140 	gtk_container_add (GTK_CONTAINER (ecc->popup_window), frame);
141 	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
142 	gtk_widget_show (frame);
143 
144 	ecc->popup_scrolled_window = gtk_scrolled_window_new (NULL, NULL);
145 	scrolled_window = GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window);
146 
147 	gtk_scrolled_window_set_policy (
148 		scrolled_window, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
149 	gtk_widget_set_can_focus (
150 		gtk_scrolled_window_get_hscrollbar (scrolled_window), FALSE);
151 	gtk_widget_set_can_focus (
152 		gtk_scrolled_window_get_vscrollbar (scrolled_window), FALSE);
153 	gtk_container_add (GTK_CONTAINER (frame), ecc->popup_scrolled_window);
154 	gtk_widget_show (ecc->popup_scrolled_window);
155 
156 	store = gtk_list_store_new (1, G_TYPE_STRING);
157 	ecc->popup_tree_view =
158 		gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
159 	g_object_unref (store);
160 
161 	gtk_tree_view_append_column (
162 		GTK_TREE_VIEW (ecc->popup_tree_view),
163 		gtk_tree_view_column_new_with_attributes (
164 			"Text", gtk_cell_renderer_text_new (),
165 			"text", 0, NULL));
166 
167 	gtk_tree_view_set_headers_visible (
168 		GTK_TREE_VIEW (ecc->popup_tree_view), FALSE);
169 
170 	selection = gtk_tree_view_get_selection (
171 		GTK_TREE_VIEW (ecc->popup_tree_view));
172 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
173 	gtk_scrolled_window_add_with_viewport (
174 		GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window),
175 		ecc->popup_tree_view);
176 	gtk_container_set_focus_vadjustment (
177 		GTK_CONTAINER (ecc->popup_tree_view),
178 		gtk_scrolled_window_get_vadjustment (
179 		GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)));
180 	gtk_container_set_focus_hadjustment (
181 		GTK_CONTAINER (ecc->popup_tree_view),
182 		gtk_scrolled_window_get_hadjustment (
183 		GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)));
184 	gtk_widget_show (ecc->popup_tree_view);
185 
186 	a11y = gtk_widget_get_accessible (ecc->popup_tree_view);
187 	atk_object_set_name (a11y, _("popup list"));
188 
189 	g_signal_connect (
190 		selection, "changed",
191 		G_CALLBACK (e_cell_combo_selection_changed), ecc);
192 	g_signal_connect (
193 		ecc->popup_window, "button_press_event",
194 		G_CALLBACK (e_cell_combo_button_press), ecc);
195 	g_signal_connect (
196 		ecc->popup_window, "button_release_event",
197 		G_CALLBACK (e_cell_combo_button_release), ecc);
198 	g_signal_connect (
199 		ecc->popup_window, "key_press_event",
200 		G_CALLBACK (e_cell_combo_key_press), ecc);
201 }
202 
203 /**
204  * e_cell_combo_new:
205  *
206  * Creates a new ECellCombo renderer.
207  *
208  * Returns: an ECellCombo object.
209  */
210 ECell *
211 e_cell_combo_new (void)
212 {
213 	return g_object_new (E_TYPE_CELL_COMBO, NULL);
214 }
215 
216 /*
217  * GObject::dispose method
218  */
219 static void
220 e_cell_combo_dispose (GObject *object)
221 {
222 	ECellCombo *ecc = E_CELL_COMBO (object);
223 
224 	if (ecc->popup_window)
225 		gtk_widget_destroy (ecc->popup_window);
226 	ecc->popup_window = NULL;
227 
228 	G_OBJECT_CLASS (e_cell_combo_parent_class)->dispose (object);
229 }
230 
231 void
232 e_cell_combo_set_popdown_strings (ECellCombo *ecc,
233                                   GList *strings)
234 {
235 	GList *elem;
236 	GtkListStore *store;
237 
238 	g_return_if_fail (E_IS_CELL_COMBO (ecc));
239 	g_return_if_fail (strings != NULL);
240 
241 	store = GTK_LIST_STORE (
242 		gtk_tree_view_get_model (
243 		GTK_TREE_VIEW (ecc->popup_tree_view)));
244 	gtk_list_store_clear (store);
245 
246 	for (elem = strings; elem; elem = elem->next) {
247 		GtkTreeIter iter;
248 		gchar *utf8_text = elem->data;
249 
250 		gtk_list_store_append (store, &iter);
251 		gtk_list_store_set (store, &iter, 0, utf8_text, -1);
252 	}
253 }
254 
255 static gint
256 e_cell_combo_do_popup (ECellPopup *ecp,
257                        GdkEvent *event,
258                        gint row,
259                        gint view_col)
260 {
261 	ECellCombo *ecc = E_CELL_COMBO (ecp);
262 	GtkTreeSelection *selection;
263 	GdkWindow *window;
264 	guint32 time;
265 	gint error_code;
266 
267 	selection = gtk_tree_view_get_selection (
268 		GTK_TREE_VIEW (ecc->popup_tree_view));
269 
270 	g_signal_handlers_block_by_func (
271 		selection, e_cell_combo_selection_changed, ecc);
272 
273 	e_cell_combo_show_popup (ecc, row, view_col);
274 	e_cell_combo_select_matching_item (ecc);
275 
276 	g_signal_handlers_unblock_by_func (
277 		selection, e_cell_combo_selection_changed, ecc);
278 
279 	if (event->type == GDK_BUTTON_PRESS)
280 		time = event->button.time;
281 	else
282 		time = event->key.time;
283 
284 	window = gtk_widget_get_window (ecc->popup_tree_view);
285 
286 	error_code = gdk_pointer_grab (
287 		window, TRUE,
288 		GDK_ENTER_NOTIFY_MASK |
289 		GDK_BUTTON_PRESS_MASK |
290 		GDK_BUTTON_RELEASE_MASK |
291 		GDK_POINTER_MOTION_HINT_MASK |
292 		GDK_BUTTON1_MOTION_MASK,
293 		NULL, NULL, time);
294 
295 	if (error_code != 0)
296 		g_warning ("Failed to get pointer grab (%i)", error_code);
297 
298 	gtk_grab_add (ecc->popup_window);
299 	gdk_keyboard_grab (window, TRUE, time);
300 
301 	return TRUE;
302 }
303 
304 static void
305 e_cell_combo_select_matching_item (ECellCombo *ecc)
306 {
307 	ECellPopup *ecp = E_CELL_POPUP (ecc);
308 	ECellView *ecv = (ECellView *) ecp->popup_cell_view;
309 	ECellText *ecell_text = E_CELL_TEXT (ecp->child);
310 	ETableItem *eti;
311 	ETableCol *ecol;
312 	gboolean found = FALSE;
313 	gchar *cell_text;
314 	gboolean valid;
315 	GtkTreeSelection *selection;
316 	GtkTreeIter iter;
317 	GtkTreeModel *model;
318 
319 	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
320 
321 	ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
322 	cell_text = e_cell_text_get_text (
323 		ecell_text, ecv->e_table_model,
324 		ecol->col_idx, ecp->popup_row);
325 
326 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (ecc->popup_tree_view));
327 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ecc->popup_tree_view));
328 
329 	for (valid = gtk_tree_model_get_iter_first (model, &iter);
330 	     valid && !found;
331 	     valid = gtk_tree_model_iter_next (model, &iter)) {
332 		gchar *str = NULL;
333 
334 		gtk_tree_model_get (model, &iter, 0, &str, -1);
335 
336 		if (str && g_str_equal (str, cell_text)) {
337 			GtkTreePath *path;
338 
339 			path = gtk_tree_model_get_path (model, &iter);
340 			gtk_tree_view_set_cursor (
341 				GTK_TREE_VIEW (ecc->popup_tree_view),
342 				path, NULL, FALSE);
343 			gtk_tree_path_free (path);
344 
345 			found = TRUE;
346 		}
347 
348 		g_free (str);
349 	}
350 
351 	if (!found)
352 		gtk_tree_selection_unselect_all (selection);
353 
354 	e_cell_text_free_text (ecell_text, cell_text);
355 }
356 
357 static void
358 e_cell_combo_show_popup (ECellCombo *ecc,
359                          gint row,
360                          gint view_col)
361 {
362 	GdkWindow *window;
363 	GtkAllocation allocation;
364 	gint x, y, width, height, old_width, old_height;
365 
366 	gtk_widget_get_allocation (ecc->popup_window, &allocation);
367 
368 	/* This code is practically copied from GtkCombo. */
369 	old_width = allocation.width;
370 	old_height = allocation.height;
371 
372 	e_cell_combo_get_popup_pos (ecc, row, view_col, &x, &y, &height, &width);
373 
374 	/* workaround for gtk_scrolled_window_size_allocate bug */
375 	if (old_width != width || old_height != height) {
376 		gtk_widget_hide (
377 			gtk_scrolled_window_get_hscrollbar (
378 			GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)));
379 		gtk_widget_hide (
380 			gtk_scrolled_window_get_vscrollbar (
381 			GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)));
382 	}
383 
384 	gtk_window_move (GTK_WINDOW (ecc->popup_window), x, y);
385 	gtk_widget_set_size_request (ecc->popup_window, width, height);
386 	gtk_widget_realize (ecc->popup_window);
387 	window = gtk_widget_get_window (ecc->popup_window);
388 	gdk_window_resize (window, width, height);
389 	gtk_widget_show (ecc->popup_window);
390 
391 	e_cell_popup_set_shown (E_CELL_POPUP (ecc), TRUE);
392 	d (g_print ("%s: popup_shown = TRUE\n", __FUNCTION__));
393 }
394 
395 /* Calculates the size and position of the popup window (like GtkCombo). */
396 static void
397 e_cell_combo_get_popup_pos (ECellCombo *ecc,
398                             gint row,
399                             gint view_col,
400                             gint *x,
401                             gint *y,
402                             gint *height,
403                             gint *width)
404 {
405 	ECellPopup *ecp = E_CELL_POPUP (ecc);
406 	ETableItem *eti;
407 	GtkWidget *canvas;
408 	GtkWidget *widget;
409 	GtkWidget *popwin_child;
410 	GtkWidget *popup_child;
411 	GtkStyle *popwin_style;
412 	GtkStyle *popup_style;
413 	GdkWindow *window;
414 	GtkBin *popwin;
415 	GtkScrolledWindow *popup;
416 	GtkRequisition requisition;
417 	GtkRequisition list_requisition;
418 	gboolean show_vscroll = FALSE, show_hscroll = FALSE;
419 	gint avail_height, avail_width, min_height, work_height, screen_width;
420 	gint column_width, row_height, scrollbar_width;
421 	gdouble x1, y1;
422 	gdouble wx, wy;
423 
424 	eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view);
425 	canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas);
426 
427 	/* This code is practically copied from GtkCombo. */
428 	popup  = GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window);
429 	popwin = GTK_BIN (ecc->popup_window);
430 
431 	window = gtk_widget_get_window (canvas);
432 	gdk_window_get_origin (window, x, y);
433 
434 	x1 = e_table_header_col_diff (eti->header, 0, view_col + 1);
435 	y1 = e_table_item_row_diff (eti, 0, row + 1);
436 	column_width = e_table_header_col_diff (
437 		eti->header, view_col, view_col + 1);
438 	row_height = e_table_item_row_diff (eti, row, row + 1);
439 	gnome_canvas_item_i2w (GNOME_CANVAS_ITEM (eti), &x1, &y1);
440 
441 	gnome_canvas_world_to_window (
442 		GNOME_CANVAS (canvas), x1, y1, &wx, &wy);
443 
444 	x1 = wx;
445 	y1 = wy;
446 
447 	*x += x1;
448 	/* The ETable positions don't include the grid lines, I think, so we add 1. */
449 	*y += y1 + 1 - (gint)
450 		 gtk_adjustment_get_value (
451 			gtk_scrollable_get_vadjustment (
452 			GTK_SCROLLABLE (&((GnomeCanvas *) canvas)->layout)))
453 		+ ((GnomeCanvas *) canvas)->zoom_yofs;
454 
455 	widget = gtk_scrolled_window_get_vscrollbar (popup);
456 	gtk_widget_get_child_requisition (widget, &requisition);
457 
458 	scrollbar_width =
459 		requisition.width
460 		+ GTK_SCROLLED_WINDOW_CLASS (G_OBJECT_GET_CLASS (popup))->scrollbar_spacing;
461 
462 	avail_height = gdk_screen_height () - *y;
463 
464 	/* We'll use the entire screen width if needed, but we save space for
465 	 * the vertical scrollbar in case we need to show that. */
466 	screen_width = gdk_screen_width ();
467 	avail_width = screen_width - scrollbar_width;
468 
469 	widget = gtk_scrolled_window_get_vscrollbar (popup);
470 	gtk_widget_get_child_requisition (widget, &requisition);
471 
472 	gtk_widget_get_preferred_size (ecc->popup_tree_view, &list_requisition, NULL);
473 	min_height = MIN (list_requisition.height, requisition.height);
474 	if (!gtk_tree_model_iter_n_children (
475 			gtk_tree_view_get_model (
476 			GTK_TREE_VIEW (ecc->popup_tree_view)), NULL))
477 		list_requisition.height += E_CELL_COMBO_LIST_EMPTY_HEIGHT;
478 
479 	popwin_child = gtk_bin_get_child (popwin);
480 	popwin_style = gtk_widget_get_style (popwin_child);
481 
482 	popup_child = gtk_bin_get_child (GTK_BIN (popup));
483 	popup_style = gtk_widget_get_style (popup_child);
484 
485 	/* Calculate the desired width. */
486 	*width = list_requisition.width
487 		+ 2 * popwin_style->xthickness
488 		+ 2 * gtk_container_get_border_width (GTK_CONTAINER (popwin_child))
489 		+ 2 * gtk_container_get_border_width (GTK_CONTAINER (popup))
490 		+ 2 * gtk_container_get_border_width (GTK_CONTAINER (popup_child))
491 		+ 2 * popup_style->xthickness;
492 
493 	/* Use at least the same width as the column. */
494 	if (*width < column_width)
495 		*width = column_width;
496 
497 	/* If it is larger than the available width, use that instead and show
498 	 * the horizontal scrollbar. */
499 	if (*width > avail_width) {
500 		*width = avail_width;
501 		show_hscroll = TRUE;
502 	}
503 
504 	/* Calculate all the borders etc. that we need to add to the height. */
505 	work_height = (2 * popwin_style->ythickness
506 		       + 2 * gtk_container_get_border_width (GTK_CONTAINER (popwin_child))
507 		       + 2 * gtk_container_get_border_width (GTK_CONTAINER (popup))
508 		       + 2 * gtk_container_get_border_width (GTK_CONTAINER (popup_child))
509 		       + 2 * popup_style->xthickness);
510 
511 	widget = gtk_scrolled_window_get_hscrollbar (popup);
512 	gtk_widget_get_child_requisition (widget, &requisition);
513 
514 	/* Add on the height of the horizontal scrollbar if we need it. */
515 	if (show_hscroll)
516 		work_height +=
517 			requisition.height +
518 			GTK_SCROLLED_WINDOW_GET_CLASS (popup)->scrollbar_spacing;
519 
520 	/* Check if it fits in the available height. */
521 	if (work_height + list_requisition.height > avail_height) {
522 		/* It doesn't fit, so we see if we have the minimum space
523 		 * needed. */
524 		if (work_height + min_height > avail_height
525 		    && *y - row_height > avail_height) {
526 			/* We don't, so we show the popup above the cell
527 			 * instead of below it. */
528 			avail_height = *y - row_height;
529 			*y -= (work_height + list_requisition.height
530 			       + row_height);
531 			if (*y < 0)
532 				*y = 0;
533 		}
534 	}
535 
536 	/* Check if we still need the vertical scrollbar. */
537 	if (work_height + list_requisition.height > avail_height) {
538 		*width += scrollbar_width;
539 		show_vscroll = TRUE;
540 	}
541 
542 	/* We try to line it up with the right edge of the column, but we don't
543 	 * want it to go off the edges of the screen. */
544 	if (*x > screen_width)
545 		*x = screen_width;
546 	*x -= *width;
547 	if (*x < 0)
548 		*x = 0;
549 
550 	if (show_vscroll)
551 		*height = avail_height;
552 	else
553 		*height = work_height + list_requisition.height;
554 }
555 
556 static void
557 e_cell_combo_selection_changed (GtkTreeSelection *selection,
558                                 ECellCombo *ecc)
559 {
560 	GtkTreeIter iter;
561 	GtkTreeModel *model;
562 
563 	if (!gtk_widget_get_realized (ecc->popup_window) ||
564 	    !gtk_tree_selection_get_selected (selection, &model, &iter))
565 		return;
566 
567 	e_cell_combo_update_cell (ecc);
568 	e_cell_combo_restart_edit (ecc);
569 }
570 
571 /* This handles button press events in the popup window.
572  * Note that since we have a pointer grab on this window, we also get button
573  * press events for windows outside the application here, so we hide the popup
574  * window if that happens. We also get propagated events from child widgets
575  * which we ignore. */
576 static gint
577 e_cell_combo_button_press (GtkWidget *popup_window,
578                            GdkEvent *event,
579                            ECellCombo *ecc)
580 {
581 	GtkWidget *event_widget;
582 
583 	event_widget = gtk_get_event_widget (event);
584 
585 	/* If the button press was for a widget inside the popup list, but
586 	 * not the popup window itself, then we ignore the event and return
587 	 * FALSE. Otherwise we will hide the popup.
588 	 * Note that since we have a pointer grab on the popup list, button
589 	 * presses outside the application will be reported to this window,
590 	 * which is why we hide the popup in this case. */
591 	while (event_widget) {
592 		event_widget = gtk_widget_get_parent (event_widget);
593 		if (event_widget == ecc->popup_tree_view)
594 			return FALSE;
595 	}
596 
597 	gtk_grab_remove (ecc->popup_window);
598 	gdk_pointer_ungrab (event->button.time);
599 	gdk_keyboard_ungrab (event->button.time);
600 	gtk_widget_hide (ecc->popup_window);
601 
602 	e_cell_popup_set_shown (E_CELL_POPUP (ecc), FALSE);
603 	d (g_print ("%s: popup_shown = FALSE\n", __FUNCTION__));
604 
605 	/* We don't want to update the cell here. Since the list is in browse
606 	 * mode there will always be one item selected, so when we popup the
607 	 * list one item is selected even if it doesn't match the current text
608 	 * in the cell. So if you click outside the popup (which is what has
609 	 * happened here) it is better to not update the cell. */
610 	/*e_cell_combo_update_cell (ecc);*/
611 	e_cell_combo_restart_edit (ecc);
612 
613 	return TRUE;
614 }
615 
616 /* This handles button release events in the popup window. If the button is
617  * released inside the list, we want to hide the popup window and update the
618  * cell with the new selection. */
619 static gint
620 e_cell_combo_button_release (GtkWidget *popup_window,
621                              GdkEventButton *event,
622                              ECellCombo *ecc)
623 {
624 	GtkWidget *event_widget;
625 
626 	event_widget = gtk_get_event_widget ((GdkEvent *) event);
627 
628 	/* See if the button was released in the list (or its children). */
629 	while (event_widget && event_widget != ecc->popup_tree_view)
630 		event_widget = gtk_widget_get_parent (event_widget);
631 
632 	/* If it wasn't, then we just ignore the event. */
633 	if (event_widget != ecc->popup_tree_view)
634 		return FALSE;
635 
636 	/* The button was released inside the list, so we hide the popup and
637 	 * update the cell to reflect the new selection. */
638 	gtk_grab_remove (ecc->popup_window);
639 	gdk_pointer_ungrab (event->time);
640 	gdk_keyboard_ungrab (event->time);
641 	gtk_widget_hide (ecc->popup_window);
642 
643 	e_cell_popup_set_shown (E_CELL_POPUP (ecc), FALSE);
644 	d (g_print ("%s: popup_shown = FALSE\n", __FUNCTION__));
645 
646 	e_cell_combo_update_cell (ecc);
647 	e_cell_combo_restart_edit (ecc);
648 
649 	return TRUE;
650 }
651 
652 /* This handles key press events in the popup window. If the Escape key is
653  * pressed we hide the popup, and do not change the cell contents. */
654 static gint
655 e_cell_combo_key_press (GtkWidget *popup_window,
656                         GdkEventKey *event,
657                         ECellCombo *ecc)
658 {
659 	/* If the Escape key is pressed we hide the popup. */
660 	if (event->keyval != GDK_KEY_Escape
661 	    && event->keyval != GDK_KEY_Return
662 	    && event->keyval != GDK_KEY_KP_Enter
663 	    && event->keyval != GDK_KEY_ISO_Enter
664 	    && event->keyval != GDK_KEY_3270_Enter)
665 		return FALSE;
666 
667 	if (event->keyval == GDK_KEY_Escape &&
668 	   (!ecc->popup_window || !gtk_widget_get_visible (ecc->popup_window)))
669 		return FALSE;
670 
671 	gtk_grab_remove (ecc->popup_window);
672 	gdk_pointer_ungrab (event->time);
673 	gdk_keyboard_ungrab (event->time);
674 	gtk_widget_hide (ecc->popup_window);
675 
676 	e_cell_popup_set_shown (E_CELL_POPUP (ecc), FALSE);
677 	d (g_print ("%s: popup_shown = FALSE\n", __FUNCTION__));
678 
679 	if (event->keyval != GDK_KEY_Escape)
680 		e_cell_combo_update_cell (ecc);
681 
682 	e_cell_combo_restart_edit (ecc);
683 
684 	return TRUE;
685 }
686 
687 static void
688 e_cell_combo_update_cell (ECellCombo *ecc)
689 {
690 	ECellPopup *ecp = E_CELL_POPUP (ecc);
691 	ECellView *ecv = (ECellView *) ecp->popup_cell_view;
692 	ECellText *ecell_text = E_CELL_TEXT (ecp->child);
693 	ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view);
694 	ETableCol *ecol;
695 	GtkTreeSelection *selection = gtk_tree_view_get_selection (
696 		GTK_TREE_VIEW (ecc->popup_tree_view));
697 	GtkTreeModel *model;
698 	GtkTreeIter iter;
699 	gchar *text = NULL, *old_text;
700 
701 	/* Return if no item is selected. */
702 	if (!gtk_tree_selection_get_selected (selection, &model, &iter))
703 		return;
704 
705 	/* Get the text of the selected item. */
706 	gtk_tree_model_get (model, &iter, 0, &text, -1);
707 	g_return_if_fail (text != NULL);
708 
709 	/* Compare it with the existing cell contents. */
710 	ecol = e_table_header_get_column (eti->header, ecp->popup_view_col);
711 
712 	old_text = e_cell_text_get_text (
713 		ecell_text, ecv->e_table_model,
714 		ecol->col_idx, ecp->popup_row);
715 
716 	/* If they are different, update the cell contents. */
717 	if (old_text && strcmp (old_text, text)) {
718 		e_cell_text_set_value (
719 			ecell_text, ecv->e_table_model,
720 			ecol->col_idx, ecp->popup_row, text);
721 	}
722 
723 	e_cell_text_free_text (ecell_text, old_text);
724 	g_free (text);
725 }
726 
727 static void
728 e_cell_combo_restart_edit (ECellCombo *ecc)
729 {
730 	/* This doesn't work. ETable stops the edit straight-away again. */
731 #if 0
732 	ECellView *ecv = (ECellView *) ecc->popup_cell_view;
733 	ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view);
734 
735 	e_table_item_enter_edit (eti, ecc->popup_view_col, ecc->popup_row);
736 #endif
737 }