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