Updated Spanish translation
[anjuta.git] / libanjuta / anjuta-tree-combo.c
blob0c3067635d354423db296e813c53835432916161
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4; coding: utf-8 -*- */
3 /* anjuta-tree-combo.c
5 * Copyright (C) 2011 Sébastien Granjoux
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
17 * You should have received a copy of the GNU General Public
18 * License along with this program; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
30 #include <glib/gi18n.h>
32 #include "anjuta-tree-combo.h"
35 /* Types
36 *---------------------------------------------------------------------------*/
38 struct _AnjutaTreeComboBoxPrivate
40 GtkTreeModel *model;
42 GtkWidget *cell_view;
43 GtkCellArea *area;
45 GtkWidget *invalid_notebook;
46 GtkWidget *invalid_label;
48 GtkWidget *popup_window;
49 GtkWidget *scrolled_window;
50 GtkWidget *tree_view;
52 GdkDevice *grab_pointer;
53 GdkDevice *grab_keyboard;
55 guint scroll_timer;
56 gboolean auto_scroll;
57 guint resize_idle_id;
58 gboolean popup_in_progress;
60 gint active; /* Only temporary */
61 GtkTreeRowReference *active_row; /* Current selected row */
63 GtkTreeModelFilterVisibleFunc valid_func;
64 gpointer valid_data;
65 GDestroyNotify valid_destroy;
68 enum {
69 INVALID_PAGE = 0,
70 VALID_PAGE = 1
73 enum {
74 CHANGED,
75 POPUP,
76 POPDOWN,
77 LAST_SIGNAL
80 enum {
81 PROP_0,
82 PROP_MODEL
85 static guint signals[LAST_SIGNAL] = {0,};
87 #define SCROLL_TIME 100
89 static void anjuta_tree_combo_box_cell_layout_init (GtkCellLayoutIface *iface);
91 G_DEFINE_TYPE_WITH_CODE (AnjutaTreeComboBox, anjuta_tree_combo_box, GTK_TYPE_TOGGLE_BUTTON,
92 G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
93 anjuta_tree_combo_box_cell_layout_init))
97 /* Helpers functions
98 *---------------------------------------------------------------------------*/
100 static gboolean
101 popup_grab_on_window (GdkWindow *window,
102 GdkDevice *keyboard,
103 GdkDevice *pointer)
105 if (keyboard &&
106 gdk_device_grab (keyboard, window,
107 GDK_OWNERSHIP_WINDOW, TRUE,
108 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
109 NULL, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
110 return FALSE;
112 if (pointer &&
113 gdk_device_grab (pointer, window,
114 GDK_OWNERSHIP_WINDOW, TRUE,
115 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
116 GDK_POINTER_MOTION_MASK,
117 NULL, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
119 if (keyboard)
120 gdk_device_ungrab (keyboard, GDK_CURRENT_TIME);
122 return FALSE;
125 return TRUE;
130 /* Private functions
131 *---------------------------------------------------------------------------*/
133 static void
134 anjuta_tree_combo_box_popup_position (AnjutaTreeComboBox *combo,
135 gint *x,
136 gint *y,
137 gint *width,
138 gint *height)
140 AnjutaTreeComboBoxPrivate *priv = combo->priv;
141 GtkAllocation allocation;
142 GdkScreen *screen;
143 gint monitor_num;
144 GdkRectangle monitor;
145 GtkRequisition popup_req;
146 GtkPolicyType hpolicy, vpolicy;
147 GdkWindow *window;
149 GtkWidget *widget = GTK_WIDGET (combo);
151 *x = *y = 0;
153 gtk_widget_get_allocation (widget, &allocation);
155 if (!gtk_widget_get_has_window (widget))
157 *x += allocation.x;
158 *y += allocation.y;
161 window = gtk_widget_get_window (widget);
163 gdk_window_get_root_coords (gtk_widget_get_window (widget),
164 *x, *y, x, y);
166 *width = allocation.width;
168 hpolicy = vpolicy = GTK_POLICY_NEVER;
169 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
170 hpolicy, vpolicy);
172 gtk_widget_get_preferred_size (priv->scrolled_window, &popup_req, NULL);
174 if (popup_req.width > *width)
176 hpolicy = GTK_POLICY_ALWAYS;
177 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
178 hpolicy, vpolicy);
181 *height = popup_req.height;
183 screen = gtk_widget_get_screen (GTK_WIDGET (combo));
184 monitor_num = gdk_screen_get_monitor_at_window (screen, window);
185 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
187 if (gtk_widget_get_direction (GTK_WIDGET (combo)) == GTK_TEXT_DIR_RTL)
188 *x = *x + allocation.width - *width;
190 if (*x < monitor.x)
191 *x = monitor.x;
192 else if (*x + *width > monitor.x + monitor.width)
193 *x = monitor.x + monitor.width - *width;
195 if (*y + allocation.height + *height <= monitor.y + monitor.height)
196 *y += allocation.height;
197 else if (*y - *height >= monitor.y)
198 *y -= *height;
199 else if (monitor.y + monitor.height - (*y + allocation.height) > *y - monitor.y)
201 *y += allocation.height;
202 *height = monitor.y + monitor.height - *y;
204 else
206 *height = *y - monitor.y;
207 *y = monitor.y;
210 if (popup_req.height > *height)
212 vpolicy = GTK_POLICY_ALWAYS;
214 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
215 hpolicy, vpolicy);
219 static void
220 anjuta_tree_combo_box_popup_for_device (AnjutaTreeComboBox *combo,
221 GdkDevice *device)
223 AnjutaTreeComboBoxPrivate *priv = combo->priv;
224 gint x, y, width, height;
225 GtkWidget *toplevel;
226 GdkDevice *keyboard, *pointer;
228 g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
229 g_return_if_fail (GDK_IS_DEVICE (device));
231 if (!gtk_widget_get_realized (GTK_WIDGET (combo)))
232 return;
234 if (gtk_widget_get_mapped (priv->popup_window))
235 return;
237 if (priv->grab_pointer && priv->grab_keyboard)
238 return;
240 if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
242 keyboard = device;
243 pointer = gdk_device_get_associated_device (device);
245 else
247 pointer = device;
248 keyboard = gdk_device_get_associated_device (device);
251 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo));
253 if (GTK_IS_WINDOW (toplevel))
254 gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
255 GTK_WINDOW (priv->popup_window));
257 gtk_widget_show_all (priv->scrolled_window);
258 anjuta_tree_combo_box_popup_position (combo, &x, &y, &width, &height);
260 gtk_widget_set_size_request (priv->popup_window, width, height);
261 gtk_window_move (GTK_WINDOW (priv->popup_window), x, y);
263 gtk_tree_view_set_hover_expand (GTK_TREE_VIEW (priv->tree_view),
264 TRUE);
266 /* popup */
267 gtk_widget_show (priv->popup_window);
269 gtk_widget_grab_focus (priv->popup_window);
270 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo),
271 TRUE);
273 if (!gtk_widget_has_focus (priv->tree_view))
274 gtk_widget_grab_focus (priv->tree_view);
276 if (!popup_grab_on_window (gtk_widget_get_window (priv->popup_window),
277 keyboard, pointer))
279 gtk_widget_hide (priv->popup_window);
280 return;
283 gtk_device_grab_add (priv->popup_window, pointer, TRUE);
284 priv->grab_pointer = pointer;
285 priv->grab_keyboard = keyboard;
288 static void
289 anjuta_tree_combo_box_popup (AnjutaTreeComboBox *combo)
291 GdkDevice *device;
292 GtkWidgetClass *widget_class;
293 GtkBindingSet *binding_set;
295 device = gtk_get_current_event_device ();
297 if (!device)
299 GdkDeviceManager *device_manager;
300 GdkDisplay *display;
301 GList *devices;
303 display = gtk_widget_get_display (GTK_WIDGET (combo));
304 device_manager = gdk_display_get_device_manager (display);
306 /* No device was set, pick the first master device */
307 devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER);
308 device = devices->data;
309 g_list_free (devices);
312 anjuta_tree_combo_box_popup_for_device (combo, device);
314 widget_class = GTK_WIDGET_GET_CLASS(combo);
315 binding_set = gtk_binding_set_by_class (widget_class);
316 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
317 "popdown", 0);
320 static void
321 anjuta_tree_combo_box_popdown (AnjutaTreeComboBox *combo)
323 AnjutaTreeComboBoxPrivate *priv = combo->priv;
324 GtkWidgetClass *widget_class;
325 GtkBindingSet *binding_set;
327 g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
329 widget_class = GTK_WIDGET_GET_CLASS(combo);
330 binding_set = gtk_binding_set_by_class (widget_class);
331 gtk_binding_entry_remove (binding_set, GDK_KEY_Escape, 0);
333 if (!gtk_widget_get_realized (GTK_WIDGET (combo)))
334 return;
336 gtk_device_grab_remove (priv->popup_window, priv->grab_pointer);
337 gtk_widget_hide (priv->popup_window);
338 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo),
339 FALSE);
341 priv->grab_pointer = NULL;
342 priv->grab_keyboard = NULL;
345 static void
346 anjuta_tree_combo_box_unset_model (AnjutaTreeComboBox *combo)
348 AnjutaTreeComboBoxPrivate *priv = combo->priv;
350 if (priv->model != NULL)
352 g_object_unref (priv->model);
353 priv->model = NULL;
356 if (priv->active_row)
358 gtk_tree_row_reference_free (priv->active_row);
359 priv->active_row = NULL;
362 if (priv->cell_view)
364 gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), NULL);
367 if (priv->tree_view)
369 gtk_tree_view_set_model (GTK_TREE_VIEW (priv->tree_view), NULL);
373 static void
374 anjuta_tree_combo_box_popup_destroy (AnjutaTreeComboBox *combo)
376 AnjutaTreeComboBoxPrivate *priv = combo->priv;
378 if (priv->scroll_timer)
380 g_source_remove (priv->scroll_timer);
381 priv->scroll_timer = 0;
384 if (priv->resize_idle_id)
386 g_source_remove (priv->resize_idle_id);
387 priv->resize_idle_id = 0;
390 if (priv->popup_window)
392 gtk_widget_destroy (priv->popup_window);
393 priv->popup_window = NULL;
397 static void
398 anjuta_tree_combo_box_set_active_path (AnjutaTreeComboBox *combo,
399 GtkTreePath *path)
401 AnjutaTreeComboBoxPrivate *priv = combo->priv;
402 gboolean valid;
404 valid = gtk_tree_row_reference_valid (priv->active_row);
406 if (path && valid)
408 GtkTreePath *active_path;
409 gint cmp;
411 active_path = gtk_tree_row_reference_get_path (priv->active_row);
412 cmp = gtk_tree_path_compare (path, active_path);
413 gtk_tree_path_free (active_path);
414 if (cmp == 0) return;
417 if (priv->active_row)
419 gtk_tree_row_reference_free (priv->active_row);
420 priv->active_row = NULL;
423 if (!path)
425 gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view)));
426 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL);
427 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->invalid_notebook), INVALID_PAGE);
428 if (!valid) return;
430 else
432 GtkTreeIter iter;
434 priv->active_row = gtk_tree_row_reference_new (priv->model, path);
436 gtk_tree_view_expand_to_path (GTK_TREE_VIEW (priv->tree_view), path);
437 gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->tree_view), path, NULL, FALSE);
438 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->tree_view), path, NULL, TRUE, 0.5, 0.0);
440 if ((priv->valid_func == NULL) ||
441 (gtk_tree_model_get_iter (combo->priv->model, &iter, path) &&
442 priv->valid_func (combo->priv->model, &iter, priv->valid_data)))
444 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), path);
445 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->invalid_notebook), VALID_PAGE);
447 else
449 gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL);
450 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->invalid_notebook), INVALID_PAGE);
454 g_signal_emit (combo, signals[CHANGED], 0);
459 /* Callbacks functions
460 *---------------------------------------------------------------------------*/
462 static gboolean
463 anjuta_tree_can_select_node (GtkTreeSelection *selection,
464 GtkTreeModel *model,
465 GtkTreePath *path,
466 gboolean path_currently_selected,
467 gpointer data)
469 AnjutaTreeComboBoxPrivate *priv = ANJUTA_TREE_COMBO_BOX (data)->priv;
470 GtkTreeIter iter;
472 return path_currently_selected ||
473 (priv->valid_func == NULL) ||
474 (gtk_tree_model_get_iter (priv->model, &iter, path) &&
475 priv->valid_func (priv->model, &iter, priv->valid_data));
478 static void
479 anjuta_tree_combo_box_auto_scroll (AnjutaTreeComboBox *combo,
480 gint x,
481 gint y)
483 GtkAdjustment *adj;
484 GtkAllocation allocation;
485 GtkWidget *tree_view = combo->priv->tree_view;
486 gdouble value;
488 gtk_widget_get_allocation (tree_view, &allocation);
490 adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (combo->priv->scrolled_window));
491 if (adj && gtk_adjustment_get_upper (adj) - gtk_adjustment_get_lower (adj) > gtk_adjustment_get_page_size (adj))
493 if (x <= allocation.x &&
494 gtk_adjustment_get_lower (adj) < gtk_adjustment_get_value (adj))
496 value = gtk_adjustment_get_value (adj) - (allocation.x - x + 1);
497 gtk_adjustment_set_value (adj, value);
499 else if (x >= allocation.x + allocation.width &&
500 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj) > gtk_adjustment_get_value (adj))
502 value = gtk_adjustment_get_value (adj) + (x - allocation.x - allocation.width + 1);
503 gtk_adjustment_set_value (adj, MAX (value, 0.0));
507 adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (combo->priv->scrolled_window));
508 if (adj && gtk_adjustment_get_upper (adj) - gtk_adjustment_get_lower (adj) > gtk_adjustment_get_page_size (adj))
510 if (y <= allocation.y &&
511 gtk_adjustment_get_lower (adj) < gtk_adjustment_get_value (adj))
513 value = gtk_adjustment_get_value (adj) - (allocation.y - y + 1);
514 gtk_adjustment_set_value (adj, value);
516 else if (y >= allocation.height &&
517 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj) > gtk_adjustment_get_value (adj))
519 value = gtk_adjustment_get_value (adj) + (y - allocation.height + 1);
520 gtk_adjustment_set_value (adj, MAX (value, 0.0));
525 static gboolean
526 anjuta_tree_combo_box_scroll_timeout (AnjutaTreeComboBox *combo)
528 AnjutaTreeComboBoxPrivate *priv = combo->priv;
529 gint x, y;
531 if (priv->auto_scroll)
533 gdk_window_get_device_position (gtk_widget_get_window (priv->tree_view),
534 priv->grab_pointer,
535 &x, &y, NULL);
536 anjuta_tree_combo_box_auto_scroll (combo, x, y);
539 return TRUE;
542 static gboolean
543 anjuta_tree_combo_box_key_press (GtkWidget *widget,
544 GdkEventKey *event,
545 gpointer user_data)
547 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
548 GtkTreeIter iter;
550 if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_ISO_Enter || event->keyval == GDK_KEY_KP_Enter ||
551 event->keyval == GDK_KEY_space || event->keyval == GDK_KEY_KP_Space)
553 GtkTreeModel *model = NULL;
555 if (combo->priv->model)
557 GtkTreeSelection *sel;
559 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (combo->priv->tree_view));
561 if (gtk_tree_selection_get_selected (sel, &model, &iter))
562 anjuta_tree_combo_box_set_active_iter (combo, &iter);
565 anjuta_tree_combo_box_popdown (combo);
567 return TRUE;
570 if (!gtk_bindings_activate_event (G_OBJECT (widget), event))
572 /* The tree hasn't managed the
573 * event, forward it to the combobox
575 if (gtk_bindings_activate_event (G_OBJECT (combo), event)) return TRUE;
578 return FALSE;
581 static gboolean
582 anjuta_tree_combo_box_enter_notify (GtkWidget *widget,
583 GdkEventCrossing *event,
584 gpointer data)
586 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (data);
588 combo->priv->auto_scroll = TRUE;
590 return TRUE;
593 static gboolean
594 anjuta_tree_combo_box_resize_idle (gpointer user_data)
596 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
597 AnjutaTreeComboBoxPrivate *priv = combo->priv;
598 gint x, y, width, height;
600 if (priv->tree_view && gtk_widget_get_mapped (priv->popup_window))
602 anjuta_tree_combo_box_popup_position (combo, &x, &y, &width, &height);
604 gtk_widget_set_size_request (priv->popup_window, width, height);
605 gtk_window_move (GTK_WINDOW (priv->popup_window), x, y);
608 priv->resize_idle_id = 0;
610 return FALSE;
613 static void
614 anjuta_tree_combo_box_popup_resize (AnjutaTreeComboBox *combo)
616 AnjutaTreeComboBoxPrivate *priv = combo->priv;
618 if (!priv->resize_idle_id)
620 priv->resize_idle_id = gdk_threads_add_idle (anjuta_tree_combo_box_resize_idle, combo);
624 static void
625 anjuta_tree_combo_box_row_expanded (GtkTreeModel *model,
626 GtkTreePath *path,
627 GtkTreeIter *iter,
628 gpointer user_data)
630 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
632 anjuta_tree_combo_box_popup_resize (combo);
635 static gboolean
636 anjuta_tree_combo_box_button_pressed (GtkWidget *widget,
637 GdkEventButton *event,
638 gpointer user_data)
640 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
641 AnjutaTreeComboBoxPrivate *priv = combo->priv;
643 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
645 if (ewidget == priv->popup_window)
646 return TRUE;
648 if ((ewidget != GTK_WIDGET (combo)) ||
649 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (combo)))
651 return FALSE;
654 if (!gtk_widget_has_focus (GTK_WIDGET (combo)))
656 gtk_widget_grab_focus (GTK_WIDGET (combo));
659 anjuta_tree_combo_box_popup_for_device (combo, event->device);
661 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo), TRUE);
663 priv->auto_scroll = FALSE;
664 if (priv->scroll_timer == 0)
665 priv->scroll_timer = gdk_threads_add_timeout (SCROLL_TIME,
666 (GSourceFunc) anjuta_tree_combo_box_scroll_timeout,
667 combo);
669 priv->popup_in_progress = TRUE;
671 return TRUE;
674 static gboolean
675 anjuta_tree_combo_box_button_released (GtkWidget *widget,
676 GdkEventButton *event,
677 gpointer user_data)
679 gboolean ret;
680 GtkTreePath *path = NULL;
681 GtkTreeIter iter;
683 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
684 AnjutaTreeComboBoxPrivate *priv = combo->priv;
686 gboolean popup_in_progress = FALSE;
688 GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
690 if (priv->popup_in_progress)
692 popup_in_progress = TRUE;
693 priv->popup_in_progress = FALSE;
696 gtk_tree_view_set_hover_expand (GTK_TREE_VIEW (priv->tree_view),
697 FALSE);
698 if (priv->scroll_timer)
700 g_source_remove (priv->scroll_timer);
701 priv->scroll_timer = 0;
704 if (ewidget != priv->tree_view)
706 if ((ewidget == GTK_WIDGET (combo)) &&
707 !popup_in_progress &&
708 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (combo)))
710 anjuta_tree_combo_box_popdown (combo);
711 return TRUE;
714 /* released outside treeview */
715 if (ewidget != GTK_WIDGET (combo))
717 anjuta_tree_combo_box_popdown (combo);
719 return TRUE;
722 return FALSE;
725 /* select something cool */
726 ret = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (priv->tree_view),
727 event->x, event->y,
728 &path,
729 NULL, NULL, NULL);
731 if (!ret)
732 return TRUE; /* clicked outside window? */
734 gtk_tree_model_get_iter (priv->model, &iter, path);
736 if (anjuta_tree_can_select_node (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view)),
737 priv->model,
738 path,
739 FALSE,
740 combo))
742 anjuta_tree_combo_box_popdown (combo);
743 anjuta_tree_combo_box_set_active_iter (combo, &iter);
745 gtk_tree_path_free (path);
747 return TRUE;
750 static void
751 anjuta_tree_combo_box_button_toggled (GtkWidget *widget,
752 gpointer user_data)
754 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
756 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
758 anjuta_tree_combo_box_popup (combo);
760 else
762 anjuta_tree_combo_box_popdown (combo);
766 static void
767 anjuta_tree_combo_box_popup_setup (AnjutaTreeComboBox *combo)
769 AnjutaTreeComboBoxPrivate *priv = combo->priv;
770 GtkTreeSelection *sel;
771 GtkWidget *toplevel;
773 priv->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
774 gtk_window_set_type_hint (GTK_WINDOW (priv->popup_window),
775 GDK_WINDOW_TYPE_HINT_COMBO);
777 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo));
778 if (GTK_IS_WINDOW (toplevel))
780 gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
781 GTK_WINDOW (priv->popup_window));
782 gtk_window_set_transient_for (GTK_WINDOW (priv->popup_window),
783 GTK_WINDOW (toplevel));
786 gtk_window_set_resizable (GTK_WINDOW (priv->popup_window), FALSE);
787 gtk_window_set_screen (GTK_WINDOW (priv->popup_window),
788 gtk_widget_get_screen (GTK_WIDGET (combo)));
790 priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
791 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
792 GTK_POLICY_NEVER,
793 GTK_POLICY_NEVER);
794 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->scrolled_window),
795 GTK_SHADOW_IN);
796 gtk_container_add (GTK_CONTAINER (priv->popup_window),
797 priv->scrolled_window);
800 priv->tree_view = gtk_tree_view_new ();
801 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
802 gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
803 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE);
804 gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (priv->tree_view), TRUE);
805 gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view),
806 gtk_tree_view_column_new_with_area (priv->area));
807 gtk_container_add (GTK_CONTAINER (priv->scrolled_window),
808 priv->tree_view);
810 /* set sample/popup widgets */
811 g_signal_connect (priv->tree_view, "key-press-event",
812 G_CALLBACK (anjuta_tree_combo_box_key_press),
813 combo);
814 g_signal_connect (priv->tree_view, "enter-notify-event",
815 G_CALLBACK (anjuta_tree_combo_box_enter_notify),
816 combo);
817 g_signal_connect (priv->tree_view, "row-expanded",
818 G_CALLBACK (anjuta_tree_combo_box_row_expanded),
819 combo);
820 g_signal_connect (priv->tree_view, "row-collapsed",
821 G_CALLBACK (anjuta_tree_combo_box_row_expanded),
822 combo);
823 g_signal_connect (priv->popup_window, "button-press-event",
824 G_CALLBACK (anjuta_tree_combo_box_button_pressed),
825 combo);
826 g_signal_connect (priv->popup_window, "button-release-event",
827 G_CALLBACK (anjuta_tree_combo_box_button_released),
828 combo);
830 gtk_widget_show (priv->scrolled_window);
834 /* Public functions
835 *---------------------------------------------------------------------------*/
837 void
838 anjuta_tree_combo_box_set_model (AnjutaTreeComboBox *combo,
839 GtkTreeModel *model)
841 g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
843 anjuta_tree_combo_box_unset_model (combo);
845 if (model != NULL)
847 AnjutaTreeComboBoxPrivate *priv = combo->priv;
849 priv->model = g_object_ref (model);
850 gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), model);
851 gtk_tree_view_set_model (GTK_TREE_VIEW (priv->tree_view), model);
854 if (priv->active != -1)
856 /* If an index was set in advance, apply it now */
857 anjuta_tree_combo_box_set_active (combo, priv->active);
858 priv->active = -1;
860 else
862 GtkTreeIter iter;
863 gtk_tree_model_get_iter_first (model, &iter);
864 anjuta_tree_combo_box_set_active_iter (combo, &iter);
869 GtkTreeModel*
870 anjuta_tree_combo_box_get_model (AnjutaTreeComboBox *combo)
872 return combo->priv->model;
875 void
876 anjuta_tree_combo_box_set_active (AnjutaTreeComboBox *combo,
877 gint index)
879 GtkTreePath *path = NULL;
881 g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
882 g_return_if_fail (index >= -1);
884 if (combo->priv->model == NULL)
886 /* Save index, in case the model is set after the index */
887 combo->priv->active = index;
888 return;
891 if (index != -1) path = gtk_tree_path_new_from_indices (index, -1);
893 anjuta_tree_combo_box_set_active_path (combo, path);
895 gtk_tree_path_free (path);
899 void
900 anjuta_tree_combo_box_set_active_iter (AnjutaTreeComboBox *combo,
901 GtkTreeIter *iter)
903 GtkTreePath *path = NULL;
905 g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
907 if (iter)
909 path = gtk_tree_model_get_path (combo->priv->model, iter);
912 anjuta_tree_combo_box_set_active_path (combo, path);
914 gtk_tree_path_free (path);
917 gboolean
918 anjuta_tree_combo_box_get_active_iter (AnjutaTreeComboBox *combo,
919 GtkTreeIter *iter)
921 AnjutaTreeComboBoxPrivate *priv = combo->priv;
922 GtkTreePath *path;
923 gboolean valid = FALSE;
925 if (priv->active_row != NULL)
927 path = gtk_tree_row_reference_get_path (priv->active_row);
928 if (path)
930 valid = gtk_tree_model_get_iter (priv->model, iter, path);
931 gtk_tree_path_free (path);
935 return valid;
938 void
939 anjuta_tree_combo_box_set_valid_function (AnjutaTreeComboBox *combo,
940 GtkTreeModelFilterVisibleFunc func,
941 gpointer data,
942 GDestroyNotify destroy)
944 AnjutaTreeComboBoxPrivate *priv = combo->priv;
945 GtkTreeSelection *selection;
947 if (priv->valid_destroy != NULL)
949 priv->valid_destroy (priv->valid_data);
951 priv->valid_func = func;
952 priv->valid_data = data;
953 priv->valid_destroy = destroy;
956 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
957 gtk_tree_selection_set_select_function (selection, func != NULL ? anjuta_tree_can_select_node : NULL, func != NULL ? combo : NULL, NULL);
960 void
961 anjuta_tree_combo_box_set_invalid_text (AnjutaTreeComboBox *combo,
962 const gchar *str)
964 AnjutaTreeComboBoxPrivate *priv = combo->priv;
966 gtk_label_set_text (GTK_LABEL (priv->invalid_label), str);
970 GtkWidget *
971 anjuta_tree_combo_box_new (void)
973 return g_object_new (ANJUTA_TYPE_TREE_COMBO_BOX, NULL);
978 /* Implement GtkCellLayout interface
979 *---------------------------------------------------------------------------*/
981 static GtkCellArea *
982 anjuta_tree_combo_box_cell_layout_get_area (GtkCellLayout *cell_layout)
984 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (cell_layout);
985 AnjutaTreeComboBoxPrivate *priv = combo->priv;
987 return priv->area;
991 static void
992 anjuta_tree_combo_box_cell_layout_init (GtkCellLayoutIface *iface)
994 iface->get_area = anjuta_tree_combo_box_cell_layout_get_area;
999 /* Implement GtkWidget functions
1000 *---------------------------------------------------------------------------*/
1002 static void
1003 anjuta_tree_combo_box_destroy (GtkWidget *widget)
1005 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (widget);
1006 AnjutaTreeComboBoxPrivate *priv = combo->priv;
1008 anjuta_tree_combo_box_popdown (combo);
1010 GTK_WIDGET_CLASS (anjuta_tree_combo_box_parent_class)->destroy (widget);
1012 priv->cell_view = NULL;
1013 priv->tree_view = NULL;
1018 /* Implement GObject functions
1019 *---------------------------------------------------------------------------*/
1021 static void
1022 anjuta_tree_combo_box_dispose (GObject *object)
1024 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (object);
1025 AnjutaTreeComboBoxPrivate *priv = combo->priv;
1027 if (priv->area)
1029 g_object_unref (priv->area);
1030 priv->area = NULL;
1033 if (priv->valid_destroy != NULL)
1035 priv->valid_destroy (priv->valid_data);
1036 priv->valid_destroy = NULL;
1038 anjuta_tree_combo_box_unset_model (combo);
1040 anjuta_tree_combo_box_popup_destroy (combo);
1042 G_OBJECT_CLASS (anjuta_tree_combo_box_parent_class)->dispose (object);
1045 static void
1046 anjuta_tree_combo_box_init (AnjutaTreeComboBox *combo)
1048 AnjutaTreeComboBoxPrivate *priv;
1049 GtkWidget *box, *sep, *arrow;
1050 GtkWidget *image, *ibox;
1052 priv = G_TYPE_INSTANCE_GET_PRIVATE (combo,
1053 ANJUTA_TYPE_TREE_COMBO_BOX,
1054 AnjutaTreeComboBoxPrivate);
1055 combo->priv = priv;
1057 priv->model = NULL;
1058 priv->active = -1;
1059 priv->active_row = NULL;
1061 priv->valid_func = NULL;
1062 priv->valid_destroy = NULL;
1064 gtk_widget_set_halign (GTK_WIDGET (combo), GTK_ALIGN_FILL);
1065 gtk_widget_show (GTK_WIDGET (combo));
1067 g_signal_connect (combo, "button-press-event",
1068 G_CALLBACK (anjuta_tree_combo_box_button_pressed), combo);
1069 g_signal_connect (combo, "toggled",
1070 G_CALLBACK (anjuta_tree_combo_box_button_toggled), combo);
1073 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
1074 gtk_container_add (GTK_CONTAINER (combo), box);
1075 gtk_widget_show (box);
1077 priv->invalid_notebook = gtk_notebook_new ();
1078 gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->invalid_notebook), FALSE);
1079 gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->invalid_notebook), FALSE);
1080 gtk_box_pack_start (GTK_BOX (box), priv->invalid_notebook, TRUE, TRUE, 0);
1082 ibox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
1083 gtk_notebook_append_page (GTK_NOTEBOOK (priv->invalid_notebook), ibox, NULL);
1084 gtk_widget_show (ibox);
1086 image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_SMALL_TOOLBAR);
1087 gtk_box_pack_start (GTK_BOX (ibox), image, FALSE, FALSE, 0);
1088 gtk_widget_show (image);
1090 priv->invalid_label = gtk_label_new (_("<Invalid>"));
1091 gtk_misc_set_alignment (GTK_MISC (priv->invalid_label), 0, 1);
1092 gtk_misc_set_padding (GTK_MISC (priv->invalid_label), 3, 3);
1093 gtk_box_pack_start (GTK_BOX (ibox), priv->invalid_label, TRUE, TRUE, 0);
1094 gtk_widget_show (priv->invalid_label);
1096 priv->area = gtk_cell_area_box_new ();
1097 g_object_ref_sink (priv->area);
1099 priv->cell_view = gtk_cell_view_new_with_context (priv->area, NULL);
1100 gtk_notebook_append_page (GTK_NOTEBOOK (priv->invalid_notebook), priv->cell_view, NULL);
1101 gtk_widget_show (priv->cell_view);
1103 sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
1104 gtk_box_pack_start (GTK_BOX (box), sep, FALSE, FALSE, 0);
1105 gtk_widget_show (sep);
1107 arrow = gtk_arrow_new (GTK_ARROW_DOWN,GTK_SHADOW_NONE);
1108 gtk_box_pack_start (GTK_BOX (box), arrow, FALSE, FALSE, 0);
1109 gtk_widget_show (arrow);
1111 gtk_widget_show_all (GTK_WIDGET (combo));
1113 anjuta_tree_combo_box_popup_setup (combo);
1116 static void
1117 anjuta_tree_combo_box_set_property (GObject *object,
1118 guint prop_id,
1119 const GValue *value,
1120 GParamSpec *pspec)
1122 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (object);
1124 switch (prop_id)
1126 case PROP_MODEL:
1127 anjuta_tree_combo_box_set_model (combo, g_value_get_object (value));
1128 break;
1129 default:
1130 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1131 break;
1135 static void
1136 anjuta_tree_combo_box_get_property (GObject *object,
1137 guint prop_id,
1138 GValue *value,
1139 GParamSpec *pspec)
1141 AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (object);
1143 switch (prop_id)
1145 case PROP_MODEL:
1146 g_value_set_object (value, combo->priv->model);
1147 break;
1148 default:
1149 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1150 break;
1154 static void
1155 anjuta_tree_combo_box_class_init (AnjutaTreeComboBoxClass * class)
1157 GObjectClass *gobject_class;
1158 GtkWidgetClass *widget_class;
1159 GtkBindingSet *binding_set;
1161 widget_class = GTK_WIDGET_CLASS (class);
1162 widget_class->destroy = anjuta_tree_combo_box_destroy;
1164 gobject_class = G_OBJECT_CLASS (class);
1165 gobject_class->dispose = anjuta_tree_combo_box_dispose;
1166 gobject_class->set_property = anjuta_tree_combo_box_set_property;
1167 gobject_class->get_property = anjuta_tree_combo_box_get_property;
1170 /* Signals */
1171 signals[CHANGED] = g_signal_new ("changed",
1172 G_OBJECT_CLASS_TYPE (class),
1173 G_SIGNAL_RUN_LAST,
1174 G_STRUCT_OFFSET (AnjutaTreeComboBoxClass, changed),
1175 NULL, NULL,
1176 g_cclosure_marshal_VOID__VOID,
1177 G_TYPE_NONE, 0);
1179 signals[POPUP] = g_signal_new_class_handler ("popup",
1180 G_OBJECT_CLASS_TYPE (class),
1181 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1182 G_CALLBACK (anjuta_tree_combo_box_popup),
1183 NULL, NULL,
1184 g_cclosure_marshal_VOID__VOID,
1185 G_TYPE_NONE, 0);
1187 signals[POPDOWN] = g_signal_new_class_handler ("popdown",
1188 G_OBJECT_CLASS_TYPE (class),
1189 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1190 G_CALLBACK (anjuta_tree_combo_box_popdown),
1191 NULL, NULL,
1192 g_cclosure_marshal_VOID__VOID,
1193 G_TYPE_NONE, 0);
1196 * AnjutaTreeCombo:model:
1198 * The model from which the combo box takes the values shown
1199 * in the list.
1202 g_object_class_install_property (gobject_class,
1203 PROP_MODEL,
1204 g_param_spec_object ("model",
1205 _("ComboBox model"),
1206 _("The model for the combo box"),
1207 GTK_TYPE_TREE_MODEL,
1208 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1210 /* Key bindings */
1211 binding_set = gtk_binding_set_by_class (widget_class);
1213 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, GDK_MOD1_MASK,
1214 "popup", 0);
1215 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Down, GDK_MOD1_MASK,
1216 "popup", 0);
1217 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, GDK_MOD1_MASK,
1218 "popdown", 0);
1219 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Up, GDK_MOD1_MASK,
1220 "popdown", 0);
1222 g_type_class_add_private (class, sizeof (AnjutaTreeComboBoxPrivate));