[anjuta] libanjuta: Add a combo box with a drop down treeview



commit cf4b9b83ba0aa7668bc10bb316b7fea3a43870b7
Author: SÃbastien Granjoux <seb sfo free fr>
Date:   Tue Nov 1 13:17:49 2011 +0100

    libanjuta: Add a combo box with a drop down treeview

 libanjuta/Makefile.am            |    4 +-
 libanjuta/anjuta-glade-catalog.c |    1 +
 libanjuta/anjuta-glade.xml       |   11 +
 libanjuta/anjuta-tree-combo.c    | 1044 ++++++++++++++++++++++++++++++++++++++
 libanjuta/anjuta-tree-combo.h    |   77 +++
 5 files changed, 1136 insertions(+), 1 deletions(-)
---
diff --git a/libanjuta/Makefile.am b/libanjuta/Makefile.am
index 6c24d9d..7369cb0 100644
--- a/libanjuta/Makefile.am
+++ b/libanjuta/Makefile.am
@@ -127,7 +127,9 @@ libanjuta_3_la_SOURCES= \
 	anjuta-entry.h \
 	anjuta-entry.c \
 	anjuta-environment-editor.h \
-	anjuta-environment-editor.c
+	anjuta-environment-editor.c \
+	anjuta-tree-combo.h \
+	anjuta-tree-combo.c
 
 # Glade module
 if HAVE_PLUGIN_GLADE
diff --git a/libanjuta/anjuta-glade-catalog.c b/libanjuta/anjuta-glade-catalog.c
index ff0f15c..e82ff1f 100644
--- a/libanjuta/anjuta-glade-catalog.c
+++ b/libanjuta/anjuta-glade-catalog.c
@@ -7,4 +7,5 @@
 #include <libanjuta/anjuta-file-drop-entry.h>
 #include <libanjuta/anjuta-entry.h>
 #include <libanjuta/anjuta-environment-editor.h>
+#include <libanjuta/anjuta-tree-combo.h>
 
diff --git a/libanjuta/anjuta-glade.xml b/libanjuta/anjuta-glade.xml
index 5b43cef..b162935 100644
--- a/libanjuta/anjuta-glade.xml
+++ b/libanjuta/anjuta-glade.xml
@@ -57,6 +57,16 @@
 		 <glade-widget-class name="AnjutaEnvironmentEditor" title="Environment editor"
 		 generic-name="environment_editor" />
 
+		 <glade-widget-class name="AnjutaTreeComboBox" title="Tree combo box"
+		 generic-name="combo">
+			<properties>
+				<property id="glade-type" disabled="True"/>
+				<property id="label" disabled="True"/>
+				<property id="use-underline" disabled="True"/>
+				<property id="stock" disabled="True"/>
+			</properties>
+		</glade-widget-class>
+
 	</glade-widget-classes>
 
 	<glade-widget-group name="Anjuta" title="Anjuta">
@@ -68,5 +78,6 @@
 		<glade-widget-class-ref name="AnjutaFileDropEntry" />
 		<glade-widget-class-ref name="AnjutaEntry" />
 		<glade-widget-class-ref name="AnjutaEnvironmentEditor" />
+		<glade-widget-class-ref name="AnjutaTreeComboBox" />
 	</glade-widget-group>
 </glade-catalog>
diff --git a/libanjuta/anjuta-tree-combo.c b/libanjuta/anjuta-tree-combo.c
new file mode 100644
index 0000000..74ab567
--- /dev/null
+++ b/libanjuta/anjuta-tree-combo.c
@@ -0,0 +1,1044 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4; coding: utf-8 -*- */
+
+/* anjuta-tree-combo.c
+ *
+ * Copyright (C) 2011  SÃbastien Granjoux
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+
+#include "anjuta-tree-combo.h"
+
+
+/* Types
+ *---------------------------------------------------------------------------*/
+
+struct _AnjutaTreeComboBoxPrivate
+{
+	GtkTreeModel *model;
+
+	GtkWidget *cell_view;
+	GtkCellArea *area;
+
+	GtkWidget *popup_window;
+	GtkWidget *scrolled_window;
+	GtkWidget *tree_view;
+
+	GdkDevice *grab_pointer;
+	GdkDevice *grab_keyboard;
+
+	guint scroll_timer;
+	gboolean	auto_scroll;
+  	guint resize_idle_id;
+ 	gboolean popup_in_progress;
+
+	gint active; 						/* Only temporary */
+	GtkTreeRowReference *active_row;	/* Current selected row */
+};
+
+enum {
+	CHANGED,
+	POPUP,
+	POPDOWN,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0,};
+
+#define SCROLL_TIME  100
+
+static void     anjuta_tree_combo_box_cell_layout_init     (GtkCellLayoutIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (AnjutaTreeComboBox, anjuta_tree_combo_box, GTK_TYPE_TOGGLE_BUTTON,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT,
+                                                anjuta_tree_combo_box_cell_layout_init))
+
+
+
+/* Helpers functions
+ *---------------------------------------------------------------------------*/
+
+static gboolean
+popup_grab_on_window (GdkWindow *window,
+                      GdkDevice *keyboard,
+                      GdkDevice *pointer)
+{
+	if (keyboard &&
+	    gdk_device_grab (keyboard, window,
+	                     GDK_OWNERSHIP_WINDOW, TRUE,
+	                     GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
+	                     NULL, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
+    	return FALSE;
+
+	if (pointer &&
+	    gdk_device_grab (pointer, window,
+	                     GDK_OWNERSHIP_WINDOW, TRUE,
+	                     GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+	                     GDK_POINTER_MOTION_MASK,
+	                     NULL, GDK_CURRENT_TIME) != GDK_GRAB_SUCCESS)
+	{
+		if (keyboard)
+			gdk_device_ungrab (keyboard, GDK_CURRENT_TIME);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+
+/* Private functions
+ *---------------------------------------------------------------------------*/
+
+static void
+anjuta_tree_combo_box_popup_position (AnjutaTreeComboBox *combo,
+                                      gint        *x,
+                                      gint        *y,
+                                      gint        *width,
+                                      gint        *height)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+	GtkAllocation allocation;
+	GdkScreen *screen;
+	gint monitor_num;
+	GdkRectangle monitor;
+	GtkRequisition popup_req;
+	GtkPolicyType hpolicy, vpolicy;
+	GdkWindow *window;
+
+	GtkWidget *widget = GTK_WIDGET (combo);
+
+	*x = *y = 0;
+
+	gtk_widget_get_allocation (widget, &allocation);
+
+	if (!gtk_widget_get_has_window (widget))
+	{
+		*x += allocation.x;
+		*y += allocation.y;
+	}
+
+	window = gtk_widget_get_window (widget);
+
+	gdk_window_get_root_coords (gtk_widget_get_window (widget),
+	                            *x, *y, x, y);
+
+	*width = allocation.width;
+
+	hpolicy = vpolicy = GTK_POLICY_NEVER;
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
+                                  hpolicy, vpolicy);
+
+	gtk_widget_get_preferred_size (priv->scrolled_window, &popup_req, NULL);
+
+	if (popup_req.width > *width)
+	{
+		hpolicy = GTK_POLICY_ALWAYS;
+		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
+		                                hpolicy, vpolicy);
+	}
+
+	*height = popup_req.height;
+
+	screen = gtk_widget_get_screen (GTK_WIDGET (combo));
+	monitor_num = gdk_screen_get_monitor_at_window (screen, window);
+	gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+	if (gtk_widget_get_direction (GTK_WIDGET (combo)) == GTK_TEXT_DIR_RTL)
+		*x = *x + allocation.width - *width;
+
+	if (*x < monitor.x)
+		*x = monitor.x;
+	else if (*x + *width > monitor.x + monitor.width)
+		*x = monitor.x + monitor.width - *width;
+
+	if (*y + allocation.height + *height <= monitor.y + monitor.height)
+		*y += allocation.height;
+	else if (*y - *height >= monitor.y)
+		*y -= *height;
+	else if (monitor.y + monitor.height - (*y + allocation.height) > *y - monitor.y)
+	{
+		*y += allocation.height;
+		*height = monitor.y + monitor.height - *y;
+	}
+	else
+	{
+		*height = *y - monitor.y;
+		*y = monitor.y;
+	}
+
+	if (popup_req.height > *height)
+	{
+		vpolicy = GTK_POLICY_ALWAYS;
+
+		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
+		                                hpolicy, vpolicy);
+	}
+}
+
+static void
+anjuta_tree_combo_box_popup_for_device (AnjutaTreeComboBox *combo,
+                                        GdkDevice   *device)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+	gint x, y, width, height;
+	GtkWidget *toplevel;
+	GdkDevice *keyboard, *pointer;
+
+	g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
+	g_return_if_fail (GDK_IS_DEVICE (device));
+
+	if (!gtk_widget_get_realized (GTK_WIDGET (combo)))
+		return;
+
+	if (gtk_widget_get_mapped (priv->popup_window))
+		return;
+
+	if (priv->grab_pointer && priv->grab_keyboard)
+		return;
+
+	if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
+	{
+		keyboard = device;
+		pointer = gdk_device_get_associated_device (device);
+	}
+	else
+	{
+		pointer = device;
+		keyboard = gdk_device_get_associated_device (device);
+	}
+
+	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo));
+
+	if (GTK_IS_WINDOW (toplevel))
+		gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
+		                             GTK_WINDOW (priv->popup_window));
+
+	gtk_widget_show_all (priv->scrolled_window);
+	anjuta_tree_combo_box_popup_position (combo, &x, &y, &width, &height);
+
+	gtk_widget_set_size_request (priv->popup_window, width, height);
+	gtk_window_move (GTK_WINDOW (priv->popup_window), x, y);
+
+	gtk_tree_view_set_hover_expand (GTK_TREE_VIEW (priv->tree_view),
+	                                TRUE);
+
+	/* popup */
+	gtk_widget_show (priv->popup_window);
+
+	gtk_widget_grab_focus (priv->popup_window);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo),
+	                              TRUE);
+
+	if (!gtk_widget_has_focus (priv->tree_view))
+		gtk_widget_grab_focus (priv->tree_view);
+
+	if (!popup_grab_on_window (gtk_widget_get_window (priv->popup_window),
+	                           keyboard, pointer))
+	{
+		gtk_widget_hide (priv->popup_window);
+		return;
+	}
+
+	gtk_device_grab_add (priv->popup_window, pointer, TRUE);
+	priv->grab_pointer = pointer;
+	priv->grab_keyboard = keyboard;
+}
+
+static void
+anjuta_tree_combo_box_popup (AnjutaTreeComboBox *combo)
+{
+	GdkDevice *device;
+
+	device = gtk_get_current_event_device ();
+
+	if (!device)
+	{
+		GdkDeviceManager *device_manager;
+		GdkDisplay *display;
+		GList *devices;
+
+		display = gtk_widget_get_display (GTK_WIDGET (combo));
+		device_manager = gdk_display_get_device_manager (display);
+
+		/* No device was set, pick the first master device */
+		devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER);
+		device = devices->data;
+		g_list_free (devices);
+	}
+
+	anjuta_tree_combo_box_popup_for_device (combo, device);
+}
+
+static void
+anjuta_tree_combo_box_popdown (AnjutaTreeComboBox *combo)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
+
+	if (!gtk_widget_get_realized (GTK_WIDGET (combo)))
+		return;
+
+	gtk_device_grab_remove (priv->popup_window, priv->grab_pointer);
+	gtk_widget_hide (priv->popup_window);
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo),
+	                              FALSE);
+
+	priv->grab_pointer = NULL;
+	priv->grab_keyboard = NULL;
+}
+
+static void
+anjuta_tree_combo_box_unset_model (AnjutaTreeComboBox *combo)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	if (priv->model != NULL)
+	{
+		g_object_unref (priv->model);
+		priv->model = NULL;
+	}
+
+	if (priv->active_row)
+    {
+		gtk_tree_row_reference_free (priv->active_row);
+		priv->active_row = NULL;
+	}
+
+	if (priv->cell_view)
+	{
+		gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), NULL);
+	}
+
+	if (priv->tree_view)
+	{
+		gtk_tree_view_set_model (GTK_TREE_VIEW (priv->tree_view), NULL);
+	}
+}
+
+static void
+anjuta_tree_combo_box_popup_destroy (AnjutaTreeComboBox *combo)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	if (priv->scroll_timer)
+	{
+		g_source_remove (priv->scroll_timer);
+		priv->scroll_timer = 0;
+	}
+
+	if (priv->resize_idle_id)
+	{
+		g_source_remove (priv->resize_idle_id);
+		priv->resize_idle_id = 0;
+	}
+
+	if (priv->popup_window)
+    {
+		gtk_widget_destroy (priv->popup_window);
+		priv->popup_window = NULL;
+	}
+}
+
+static void
+anjuta_tree_combo_box_set_active_path (AnjutaTreeComboBox *combo,
+                                       GtkTreePath *path)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+	gboolean valid;
+
+	valid = gtk_tree_row_reference_valid (priv->active_row);
+
+	if (path && valid)
+	{
+		GtkTreePath *active_path;
+		gint cmp;
+
+		active_path = gtk_tree_row_reference_get_path (priv->active_row);
+		cmp = gtk_tree_path_compare (path, active_path);
+		gtk_tree_path_free (active_path);
+		if (cmp == 0) return;
+	}
+
+	if (priv->active_row)
+	{
+		gtk_tree_row_reference_free (priv->active_row);
+		priv->active_row = NULL;
+	}
+
+	if (!path)
+	{
+		gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view)));
+       	gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), NULL);
+		if (!valid)	return;
+    }
+	else
+    {
+		priv->active_row = gtk_tree_row_reference_new (priv->model, path);
+
+        gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->cell_view), path);
+		gtk_tree_view_expand_to_path (GTK_TREE_VIEW (priv->tree_view), path);
+		gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->tree_view), path, NULL, FALSE);
+		gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->tree_view), path, NULL, TRUE, 0.5, 0.0);
+	}
+
+	g_signal_emit (combo, signals[CHANGED], 0);
+}
+
+
+
+/* Callbacks functions
+ *---------------------------------------------------------------------------*/
+
+static void
+anjuta_tree_combo_box_auto_scroll (AnjutaTreeComboBox *combo,
+                                   gint         x,
+                                   gint         y)
+{
+	GtkAdjustment *adj;
+	GtkAllocation allocation;
+	GtkWidget *tree_view = combo->priv->tree_view;
+	gdouble value;
+
+	gtk_widget_get_allocation (tree_view, &allocation);
+
+	adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (combo->priv->scrolled_window));
+	if (adj && gtk_adjustment_get_upper (adj) - gtk_adjustment_get_lower (adj) > gtk_adjustment_get_page_size (adj))
+	{
+		if (x <= allocation.x &&
+		    gtk_adjustment_get_lower (adj) < gtk_adjustment_get_value (adj))
+		{
+			value = gtk_adjustment_get_value (adj) - (allocation.x - x + 1);
+			gtk_adjustment_set_value (adj, value);
+		}
+		else if (x >= allocation.x + allocation.width &&
+		         gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj) > gtk_adjustment_get_value (adj))
+		{
+			value = gtk_adjustment_get_value (adj) + (x - allocation.x - allocation.width + 1);
+			gtk_adjustment_set_value (adj, MAX (value, 0.0));
+		}
+	}
+
+	adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (combo->priv->scrolled_window));
+	if (adj && gtk_adjustment_get_upper (adj) - gtk_adjustment_get_lower (adj) > gtk_adjustment_get_page_size (adj))
+	{
+		if (y <= allocation.y &&
+		    gtk_adjustment_get_lower (adj) < gtk_adjustment_get_value (adj))
+		{
+			value = gtk_adjustment_get_value (adj) - (allocation.y - y + 1);
+			gtk_adjustment_set_value (adj, value);
+		}
+		else if (y >= allocation.height &&
+		         gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj) > gtk_adjustment_get_value (adj))
+		{
+			value = gtk_adjustment_get_value (adj) + (y - allocation.height + 1);
+			gtk_adjustment_set_value (adj, MAX (value, 0.0));
+		}
+	}
+}
+
+static gboolean
+anjuta_tree_combo_box_scroll_timeout (AnjutaTreeComboBox *combo)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+	gint x, y;
+
+	if (priv->auto_scroll)
+    {
+		gdk_window_get_device_position (gtk_widget_get_window (priv->tree_view),
+		                                priv->grab_pointer,
+		                                &x, &y, NULL);
+		anjuta_tree_combo_box_auto_scroll (combo, x, y);
+    }
+
+	return TRUE;
+}
+
+static gboolean
+anjuta_tree_combo_box_key_press (GtkWidget   *widget,
+                                 GdkEventKey *event,
+                                 gpointer     user_data)
+{
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
+	GtkTreeIter iter;
+
+	if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_ISO_Enter || event->keyval == GDK_KEY_KP_Enter ||
+	    event->keyval == GDK_KEY_space || event->keyval == GDK_KEY_KP_Space)
+	{
+		GtkTreeModel *model = NULL;
+
+		anjuta_tree_combo_box_popdown (combo);
+
+		if (combo->priv->model)
+		{
+			GtkTreeSelection *sel;
+
+			sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (combo->priv->tree_view));
+
+			if (gtk_tree_selection_get_selected (sel, &model, &iter))
+				anjuta_tree_combo_box_set_active_iter (combo, &iter);
+		}
+
+		return TRUE;
+	}
+
+	if (!gtk_bindings_activate_event (G_OBJECT (widget), event))
+	{
+		/* The tree hasn't managed the
+		 * event, forward it to the combobox
+		 */
+		if (gtk_bindings_activate_event (G_OBJECT (combo), event)) return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+anjuta_tree_combo_box_enter_notify (GtkWidget        *widget,
+                                         GdkEventCrossing *event,
+                                         gpointer          data)
+{
+  AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (data);
+
+  combo->priv->auto_scroll = TRUE;
+
+  return TRUE;
+}
+
+static gboolean
+anjuta_tree_combo_box_resize_idle (gpointer user_data)
+{
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+	gint x, y, width, height;
+
+	if (priv->tree_view && gtk_widget_get_mapped (priv->popup_window))
+	{
+		anjuta_tree_combo_box_popup_position (combo, &x, &y, &width, &height);
+
+		gtk_widget_set_size_request (priv->popup_window, width, height);
+		gtk_window_move (GTK_WINDOW (priv->popup_window), x, y);
+	}
+
+	priv->resize_idle_id = 0;
+
+	return FALSE;
+}
+
+static void
+anjuta_tree_combo_box_popup_resize (AnjutaTreeComboBox *combo)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	if (!priv->resize_idle_id)
+	{
+		priv->resize_idle_id = gdk_threads_add_idle (anjuta_tree_combo_box_resize_idle, combo);
+	}
+}
+
+static void
+anjuta_tree_combo_box_row_expanded (GtkTreeModel     *model,
+                                          GtkTreePath      *path,
+                                          GtkTreeIter      *iter,
+                                          gpointer          user_data)
+{
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
+
+	anjuta_tree_combo_box_popup_resize (combo);
+}
+
+static gboolean
+anjuta_tree_combo_box_button_pressed (GtkWidget      *widget,
+                                      GdkEventButton *event,
+                                      gpointer        user_data)
+{
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
+
+	if (ewidget == priv->popup_window)
+		return TRUE;
+
+	if ((ewidget != GTK_WIDGET (combo)) ||
+		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (combo)))
+	{
+		return FALSE;
+	}
+
+	if (!gtk_widget_has_focus (GTK_WIDGET (combo)))
+	{
+		gtk_widget_grab_focus (GTK_WIDGET (combo));
+	}
+
+	anjuta_tree_combo_box_popup_for_device (combo, event->device);
+
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (combo), TRUE);
+
+	priv->auto_scroll = FALSE;
+	if (priv->scroll_timer == 0)
+		priv->scroll_timer = gdk_threads_add_timeout (SCROLL_TIME,
+		                                              (GSourceFunc) anjuta_tree_combo_box_scroll_timeout,
+		                                              combo);
+
+	priv->popup_in_progress = TRUE;
+
+	return TRUE;
+}
+
+static gboolean
+anjuta_tree_combo_box_button_released (GtkWidget      *widget,
+                                       GdkEventButton *event,
+                                       gpointer        user_data)
+{
+	gboolean ret;
+	GtkTreePath *path = NULL;
+	GtkTreeIter iter;
+
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	gboolean popup_in_progress = FALSE;
+
+	GtkWidget *ewidget = gtk_get_event_widget ((GdkEvent *)event);
+
+	if (priv->popup_in_progress)
+	{
+		popup_in_progress = TRUE;
+		priv->popup_in_progress = FALSE;
+	}
+
+	gtk_tree_view_set_hover_expand (GTK_TREE_VIEW (priv->tree_view),
+	                                FALSE);
+	if (priv->scroll_timer)
+	{
+		g_source_remove (priv->scroll_timer);
+		priv->scroll_timer = 0;
+	}
+
+	if (ewidget != priv->tree_view)
+	{
+		if ((ewidget == GTK_WIDGET (combo)) &&
+			!popup_in_progress &&
+			gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (combo)))
+		{
+			anjuta_tree_combo_box_popdown (combo);
+			return TRUE;
+		}
+
+		/* released outside treeview */
+		if (ewidget != GTK_WIDGET (combo))
+        {
+			anjuta_tree_combo_box_popdown (combo);
+
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	/* select something cool */
+	ret = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (priv->tree_view),
+	                                     event->x, event->y,
+	                                     &path,
+	                                     NULL, NULL, NULL);
+
+	if (!ret)
+		return TRUE; /* clicked outside window? */
+
+	gtk_tree_model_get_iter (priv->model, &iter, path);
+	gtk_tree_path_free (path);
+
+	anjuta_tree_combo_box_popdown (combo);
+
+	anjuta_tree_combo_box_set_active_iter (combo, &iter);
+
+	return TRUE;
+}
+
+static void
+anjuta_tree_combo_box_button_toggled (GtkWidget *widget,
+                   gpointer   user_data)
+{
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (user_data);
+
+	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
+	{
+        anjuta_tree_combo_box_popup (combo);
+    }
+	else
+	{
+		anjuta_tree_combo_box_popdown (combo);
+	}
+}
+
+static void
+anjuta_tree_combo_box_popup_setup (AnjutaTreeComboBox *combo)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+	GtkTreeSelection *sel;
+	GtkWidget *toplevel;
+
+	priv->popup_window = gtk_window_new (GTK_WINDOW_POPUP);
+	gtk_window_set_type_hint (GTK_WINDOW (priv->popup_window),
+	                          GDK_WINDOW_TYPE_HINT_COMBO);
+
+	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo));
+	if (GTK_IS_WINDOW (toplevel))
+	{
+		gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
+		                             GTK_WINDOW (priv->popup_window));
+		gtk_window_set_transient_for (GTK_WINDOW (priv->popup_window),
+		                              GTK_WINDOW (toplevel));
+	}
+
+	gtk_window_set_resizable (GTK_WINDOW (priv->popup_window), FALSE);
+	gtk_window_set_screen (GTK_WINDOW (priv->popup_window),
+	                       gtk_widget_get_screen (GTK_WIDGET (combo)));
+
+	priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
+	                                GTK_POLICY_NEVER,
+	                                GTK_POLICY_NEVER);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->scrolled_window),
+	                                     GTK_SHADOW_IN);
+	gtk_container_add (GTK_CONTAINER (priv->popup_window),
+	                   priv->scrolled_window);
+
+
+	priv->tree_view = gtk_tree_view_new ();
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
+	gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE);
+	gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (priv->tree_view), TRUE);
+	gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view),
+                               gtk_tree_view_column_new_with_area (priv->area));
+	gtk_container_add (GTK_CONTAINER (priv->scrolled_window),
+	                   priv->tree_view);
+
+	/* set sample/popup widgets */
+	g_signal_connect (priv->tree_view, "key-press-event",
+	                  G_CALLBACK (anjuta_tree_combo_box_key_press),
+	                  combo);
+	g_signal_connect (priv->tree_view, "enter-notify-event",
+	                  G_CALLBACK (anjuta_tree_combo_box_enter_notify),
+	                  combo);
+	g_signal_connect (priv->tree_view, "row-expanded",
+	                  G_CALLBACK (anjuta_tree_combo_box_row_expanded),
+	                  combo);
+	g_signal_connect (priv->tree_view, "row-collapsed",
+	                  G_CALLBACK (anjuta_tree_combo_box_row_expanded),
+	                  combo);
+	g_signal_connect (priv->popup_window, "button-press-event",
+	                  G_CALLBACK (anjuta_tree_combo_box_button_pressed),
+	                  combo);
+	g_signal_connect (priv->popup_window, "button-release-event",
+	                  G_CALLBACK (anjuta_tree_combo_box_button_released),
+	                  combo);
+
+	gtk_widget_show (priv->scrolled_window);
+}
+
+
+
+
+/* Public functions
+ *---------------------------------------------------------------------------*/
+
+void
+anjuta_tree_combo_box_set_model (AnjutaTreeComboBox *combo,
+                                 GtkTreeModel *model)
+{
+	g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
+
+	anjuta_tree_combo_box_unset_model (combo);
+
+	if (model != NULL)
+	{
+		AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+		priv->model = g_object_ref (model);
+		gtk_cell_view_set_model (GTK_CELL_VIEW (priv->cell_view), model);
+		gtk_tree_view_set_model (GTK_TREE_VIEW (priv->tree_view), model);
+
+
+		if (priv->active != -1)
+    	{
+     	 	/* If an index was set in advance, apply it now */
+      		anjuta_tree_combo_box_set_active (combo, priv->active);
+      		priv->active = -1;
+    	}
+		else
+		{
+			GtkTreeIter iter;
+			gtk_tree_model_get_iter_first (model, &iter);
+			anjuta_tree_combo_box_set_active_iter (combo, &iter);
+		}
+	}
+}
+
+GtkTreeModel*
+anjuta_tree_combo_box_get_model (AnjutaTreeComboBox *combo)
+{
+	return combo->priv->model;
+}
+
+void
+anjuta_tree_combo_box_set_active (AnjutaTreeComboBox *combo,
+                              gint index)
+{
+	GtkTreePath *path = NULL;
+
+	g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
+	g_return_if_fail (index >= -1);
+
+	if (combo->priv->model == NULL)
+	{
+      /* Save index, in case the model is set after the index */
+      combo->priv->active = index;
+      return;
+	}
+
+	if (index != -1) path = gtk_tree_path_new_from_indices (index, -1);
+
+	anjuta_tree_combo_box_set_active_path (combo, path);
+
+	gtk_tree_path_free (path);
+}
+
+
+void
+anjuta_tree_combo_box_set_active_iter (AnjutaTreeComboBox *combo,
+                                   GtkTreeIter *iter)
+{
+	GtkTreePath *path = NULL;
+
+	g_return_if_fail (ANJUTA_IS_TREE_COMBO_BOX (combo));
+
+	if (iter)
+	{
+    	path = gtk_tree_model_get_path (combo->priv->model, iter);
+	}
+
+	anjuta_tree_combo_box_set_active_path (combo, path);
+
+	gtk_tree_path_free (path);
+}
+
+gboolean
+anjuta_tree_combo_box_get_active_iter (AnjutaTreeComboBox *combo,
+                                   GtkTreeIter *iter)
+{
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+	GtkTreePath *path;
+	gboolean valid = FALSE;
+
+	path = gtk_tree_row_reference_get_path (priv->active_row);
+	if (path)
+	{
+		valid = gtk_tree_model_get_iter (priv->model, iter, path);
+		gtk_tree_path_free (path);
+	}
+
+	return valid;
+}
+
+
+GtkWidget *
+anjuta_tree_combo_box_new (void)
+{
+	return g_object_new (ANJUTA_TYPE_TREE_COMBO_BOX, NULL);
+}
+
+
+
+/* Implement GtkCellLayout interface
+ *---------------------------------------------------------------------------*/
+
+static GtkCellArea *
+anjuta_tree_combo_box_cell_layout_get_area (GtkCellLayout *cell_layout)
+{
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (cell_layout);
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	return priv->area;
+}
+
+
+static void
+anjuta_tree_combo_box_cell_layout_init (GtkCellLayoutIface *iface)
+{
+  iface->get_area = anjuta_tree_combo_box_cell_layout_get_area;
+}
+
+
+
+/* Implement GtkWidget functions
+ *---------------------------------------------------------------------------*/
+
+static void
+anjuta_tree_combo_box_destroy (GtkWidget *widget)
+{
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (widget);
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	anjuta_tree_combo_box_popdown (combo);
+
+	GTK_WIDGET_CLASS (anjuta_tree_combo_box_parent_class)->destroy (widget);
+
+	priv->cell_view = NULL;
+	priv->tree_view = NULL;
+}
+
+
+
+/* Implement GObject functions
+ *---------------------------------------------------------------------------*/
+
+static void
+anjuta_tree_combo_box_dispose (GObject *object)
+{
+	AnjutaTreeComboBox *combo = ANJUTA_TREE_COMBO_BOX (object);
+	AnjutaTreeComboBoxPrivate *priv = combo->priv;
+
+	if (priv->area)
+    {
+      g_object_unref (priv->area);
+      priv->area = NULL;
+    }
+
+	anjuta_tree_combo_box_unset_model (combo);
+
+	anjuta_tree_combo_box_popup_destroy (combo);
+
+	G_OBJECT_CLASS (anjuta_tree_combo_box_parent_class)->dispose (object);
+}
+
+static void
+anjuta_tree_combo_box_init (AnjutaTreeComboBox *combo)
+{
+  	AnjutaTreeComboBoxPrivate *priv;
+	GtkWidget *box, *sep, *arrow;
+
+	priv = G_TYPE_INSTANCE_GET_PRIVATE (combo,
+	                                    ANJUTA_TYPE_TREE_COMBO_BOX,
+	                                    AnjutaTreeComboBoxPrivate);
+	combo->priv = priv;
+
+	priv->model = NULL;
+	priv->active = -1;
+	priv->active_row = NULL;
+
+	gtk_widget_push_composite_child ();
+
+	gtk_widget_set_halign (GTK_WIDGET (combo), GTK_ALIGN_FILL);
+	gtk_widget_show (GTK_WIDGET (combo));
+
+	g_signal_connect (combo, "button-press-event",
+	                  G_CALLBACK (anjuta_tree_combo_box_button_pressed), combo);
+	g_signal_connect (combo, "toggled",
+	                  G_CALLBACK (anjuta_tree_combo_box_button_toggled), combo);
+
+
+	box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
+	gtk_container_add (GTK_CONTAINER (combo), box);
+	gtk_widget_show (box);
+
+	priv->area = gtk_cell_area_box_new ();
+	g_object_ref_sink (priv->area);
+
+	priv->cell_view = gtk_cell_view_new_with_context (priv->area, NULL);
+	gtk_box_pack_start (GTK_BOX (box), priv->cell_view, TRUE, TRUE, 0);
+	gtk_widget_show (priv->cell_view);
+
+	sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+	gtk_box_pack_start (GTK_BOX (box), sep, FALSE, FALSE, 0);
+	gtk_widget_show (sep);
+
+	arrow = gtk_arrow_new (GTK_ARROW_DOWN,GTK_SHADOW_NONE);
+	gtk_box_pack_start (GTK_BOX (box), arrow, FALSE, FALSE, 0);
+	gtk_widget_show (arrow);
+
+	gtk_widget_pop_composite_child ();
+
+	anjuta_tree_combo_box_popup_setup (combo);
+}
+
+static void
+anjuta_tree_combo_box_class_init (AnjutaTreeComboBoxClass * class)
+{
+	GObjectClass *gobject_class;
+	GtkWidgetClass *widget_class;
+	GtkBindingSet *binding_set;
+
+	widget_class = GTK_WIDGET_CLASS (class);
+	widget_class->destroy = anjuta_tree_combo_box_destroy;
+
+	gobject_class = G_OBJECT_CLASS (class);
+	gobject_class->dispose = anjuta_tree_combo_box_dispose;
+
+	/* Signals */
+	signals[CHANGED] = g_signal_new ("changed",
+	                                 G_OBJECT_CLASS_TYPE (class),
+	                                 G_SIGNAL_RUN_LAST,
+	                                 G_STRUCT_OFFSET (AnjutaTreeComboBoxClass, changed),
+	                                 NULL, NULL,
+	                                 g_cclosure_marshal_VOID__VOID,
+	                                 G_TYPE_NONE, 0);
+
+	signals[POPUP] = g_signal_new_class_handler ("popup",
+	                                             G_OBJECT_CLASS_TYPE (class),
+	                                             G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+	                                             G_CALLBACK (anjuta_tree_combo_box_popup),
+	                                             NULL, NULL,
+	                                             g_cclosure_marshal_VOID__VOID,
+	                                             G_TYPE_NONE, 0);
+
+	signals[POPDOWN] = g_signal_new_class_handler ("popdown",
+	                                               G_OBJECT_CLASS_TYPE (class),
+	                                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+	                                               G_CALLBACK (anjuta_tree_combo_box_popdown),
+	                                               NULL, NULL,
+	                                               g_cclosure_marshal_VOID__VOID,
+	                                               G_TYPE_NONE, 0);
+
+	/* Key bindings */
+	binding_set = gtk_binding_set_by_class (widget_class);
+
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, GDK_MOD1_MASK,
+	                              "popup", 0);
+  	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Down, GDK_MOD1_MASK,
+	                              "popup", 0);
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, GDK_MOD1_MASK,
+	                              "popdown", 0);
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Up, GDK_MOD1_MASK,
+	                              "popdown", 0);
+	gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0,
+	                              "popdown", 0);
+
+	g_type_class_add_private (class, sizeof (AnjutaTreeComboBoxPrivate));
+}
diff --git a/libanjuta/anjuta-tree-combo.h b/libanjuta/anjuta-tree-combo.h
new file mode 100644
index 0000000..b325151
--- /dev/null
+++ b/libanjuta/anjuta-tree-combo.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4; coding: utf-8 -*- */
+
+/* anjuta-tree-combo.h
+ *
+ * Copyright (C) 2011  SÃbastien Granjoux
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef _ANJUTA_TREE_COMBO_H_
+#define _ANJUTA_TREE_COMBO_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ANJUTA_TYPE_TREE_COMBO_BOX            (anjuta_tree_combo_box_get_type ())
+#define ANJUTA_TREE_COMBO_BOX(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANJUTA_TYPE_TREE_COMBO_BOX, AnjutaTreeComboBox))
+#define ANJUTA_TREE_COMBO_BOX_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), ANJUTA_TYPE_TREE_COMBO_BOX, AnjutaTreeComboBoxClass))
+#define ANJUTA_IS_TREE_COMBO_BOX(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ANJUTA_TYPE_TREE_COMBO_BOX))
+#define ANJUTA_IS_TREE_COMBO_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANJUTA_TYPE_TREE_COMBO_BOX))
+#define ANJUTA_TREE_COMBO_BOX_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), ANJUTA_TYPE_TREE_COMBO_BOX, AnjutaTreeComboBoxClass))
+
+typedef struct _AnjutaTreeComboBox        AnjutaTreeComboBox;
+typedef struct _AnjutaTreeComboBoxPrivate AnjutaTreeComboBoxPrivate;
+typedef struct _AnjutaTreeComboBoxClass   AnjutaTreeComboBoxClass;
+
+struct _AnjutaTreeComboBox
+{
+  GtkToggleButton parent;
+
+  /*< private >*/
+  AnjutaTreeComboBoxPrivate *priv;
+};
+
+struct _AnjutaTreeComboBoxClass
+{
+  /*< private >*/
+  GtkToggleButtonClass parent_class;
+
+  /* signals */
+  void     (* changed)           (AnjutaTreeComboBox *combo);
+};
+
+
+GType			anjuta_tree_combo_box_get_type         (void) G_GNUC_CONST;
+GtkWidget *		anjuta_tree_combo_box_new              (void);
+
+void			anjuta_tree_combo_box_set_model			(AnjutaTreeComboBox *combo,
+						                                    GtkTreeModel *model);
+GtkTreeModel*	anjuta_tree_combo_box_get_model			(AnjutaTreeComboBox *combo);
+
+void			anjuta_tree_combo_box_set_active 		(AnjutaTreeComboBox *combo,
+					                                      gint index);
+void			anjuta_tree_combo_box_set_active_iter	(AnjutaTreeComboBox *combo,
+                                                         GtkTreeIter *iter);
+gboolean		anjuta_tree_combo_box_get_active_iter	(AnjutaTreeComboBox *combo,
+					                                     GtkTreeIter *iter);
+
+
+G_END_DECLS
+
+#endif /* _ANJUTA_TREE_COMBO_H_ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]