[gtk+] Add GtkBubbleWindow
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+] Add GtkBubbleWindow
- Date: Tue, 5 Mar 2013 22:06:07 +0000 (UTC)
commit 51be6b88dbff3ff6ed83d1247508ad58c0278df6
Author: Carlos Garnacho <carlos lanedo com>
Date: Fri Jan 11 17:32:08 2013 +0100
Add GtkBubbleWindow
This popup window widget can be used for touch friendly context
menus that point to a concrete area.
docs/reference/gtk/gtk-docs.sgml | 1 +
docs/reference/gtk/gtk3-sections.txt | 28 +
docs/reference/gtk/gtk3.types.in | 1 +
gtk/Makefile.am | 2 +
gtk/gtkbubblewindow.c | 1016 ++++++++++++++++++++++++++++++++++
gtk/gtkbubblewindow.h | 81 +++
6 files changed, 1129 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml
index 6260fb8..c82e8f7 100644
--- a/docs/reference/gtk/gtk-docs.sgml
+++ b/docs/reference/gtk/gtk-docs.sgml
@@ -89,6 +89,7 @@
<xi:include href="xml/gtkaboutdialog.xml" />
<xi:include href="xml/gtkassistant.xml" />
<xi:include href="xml/gtkoffscreenwindow.xml" />
+ <xi:include href="xml/gtkbubblewindow.xml" />
</chapter>
<chapter id="DisplayWidgets">
diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt
index 5db1c72..b881cbf 100644
--- a/docs/reference/gtk/gtk3-sections.txt
+++ b/docs/reference/gtk/gtk3-sections.txt
@@ -7494,3 +7494,31 @@ GTK_COLOR_CHOOSER_DIALOG_GET_CLASS
GtkColorChooserDialogPrivate
gtk_color_chooser_dialog_get_type
</SECTION>
+
+<SECTION>
+<FILE>gtkbubblewindow</FILE>
+<TITLE>GtkBubbleWindow</TITLE>
+GtkBubbleWindow
+gtk_bubble_window_new
+gtk_bubble_window_set_relative_to
+gtk_bubble_window_get_relative_to
+gtk_bubble_window_set_pointing_to
+gtk_bubble_window_get_pointing_to
+gtk_bubble_window_set_position
+gtk_bubble_window_get_position
+gtk_bubble_window_popup
+gtk_bubble_window_popdown
+gtk_bubble_window_grab
+gtk_bubble_window_ungrab
+
+<SUBSECTION Standard>
+GTK_TYPE_BUBBLE_WINDOW
+GTK_BUBBLE_WINDOW
+GTK_BUBBLE_WINDOW_CLASS
+GTK_IS_BUBBLE_WINDOW
+GTK_IS_BUBBLE_WINDOW_CLASS
+GTK_BUBBLE_WINDOW_GET_CLASS
+
+<SUBSECTION Private>
+gtk_bubble_window_get_type
+</SECTION>
diff --git a/docs/reference/gtk/gtk3.types.in b/docs/reference/gtk/gtk3.types.in
index 3f378b3..9cc637d 100644
--- a/docs/reference/gtk/gtk3.types.in
+++ b/docs/reference/gtk/gtk3.types.in
@@ -23,6 +23,7 @@ gtk_aspect_frame_get_type
gtk_assistant_get_type
gtk_bin_get_type
gtk_box_get_type
+gtk_bubble_window_get_type
gtk_builder_get_type
gtk_buildable_get_type
gtk_button_box_get_type
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 973f3c8..ef7a7dc 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -202,6 +202,7 @@ gtk_public_h_sources = \
gtkbindings.h \
gtkborder.h \
gtkbox.h \
+ gtkbubblewindow.h \
gtkbuilder.h \
gtkbuildable.h \
gtkbutton.h \
@@ -621,6 +622,7 @@ gtk_base_c_sources = \
gtkborder.c \
gtkborderimage.c \
gtkbox.c \
+ gtkbubblewindow.c \
gtkbuildable.c \
gtkbuilder.c \
gtkbuilderparser.c \
diff --git a/gtk/gtkbubblewindow.c b/gtk/gtkbubblewindow.c
new file mode 100644
index 0000000..4198a95
--- /dev/null
+++ b/gtk/gtkbubblewindow.c
@@ -0,0 +1,1016 @@
+/* GTK - The GIMP Toolkit
+ * Copyright © 2013 Carlos Garnacho <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gtkbubblewindow
+ * @Short_description: Bubble-like context window
+ * @Title: GtkBubbleWindow
+ *
+ * GtkBubbleWindow is a bubble-like context window, primarily mean for
+ * context-dependent helpers on touch interfaces.
+ *
+ * In order to place a GtkBubbleWindow to point to some other area,
+ * use gtk_bubble_window_set_relative_to(), gtk_bubble_window_set_pointing_to()
+ * and gtk_bubble_window_set_position(). Although it is usually more
+ * convenient to use gtk_bubble_window_popup() which handles all of those
+ * at once.
+ *
+ * By default, no grabs are performed on the GtkBubbleWindow, leaving
+ * the popup/popdown semantics up to the caller, gtk_bubble_window_grab()
+ * can be used to grab the window for a device pair, bringing #GtkMenu alike
+ * popdown behavior by default on keyboard/pointer interaction. Grabs need
+ * to be undone through gtk_bubble_window_ungrab().
+ */
+
+#include "config.h"
+#include <gdk/gdk.h>
+#include <cairo-gobject.h>
+#include "gtkbubblewindow.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+
+#define TAIL_GAP_WIDTH 24
+#define TAIL_HEIGHT 12
+
+#define POS_IS_VERTICAL(p) ((p) == GTK_POS_TOP || (p) == GTK_POS_BOTTOM)
+
+#define GRAB_EVENT_MASK \
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | \
+ GDK_POINTER_MOTION_MASK
+
+typedef struct _GtkBubbleWindowPrivate GtkBubbleWindowPrivate;
+
+enum {
+ PROP_RELATIVE_TO = 1,
+ PROP_POINTING_TO,
+ PROP_POSITION
+};
+
+struct _GtkBubbleWindowPrivate
+{
+ GdkDevice *device;
+ GdkWindow *relative_to;
+ cairo_rectangle_int_t pointing_to;
+ gint win_x;
+ gint win_y;
+ guint has_pointing_to : 1;
+ guint grabbed : 1;
+ guint preferred_position : 2;
+ guint final_position : 2;
+};
+
+G_DEFINE_TYPE (GtkBubbleWindow, gtk_bubble_window, GTK_TYPE_WINDOW)
+
+static void
+gtk_bubble_window_init (GtkBubbleWindow *window)
+{
+ GtkWidget *widget;
+ GdkScreen *screen;
+ GdkVisual *visual;
+
+ widget = GTK_WIDGET (window);
+ window->_priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
+ GTK_TYPE_BUBBLE_WINDOW,
+ GtkBubbleWindowPrivate);
+ gtk_window_set_default_size (GTK_WINDOW (window),
+ TAIL_GAP_WIDTH, TAIL_GAP_WIDTH);
+ gtk_widget_set_app_paintable (widget, TRUE);
+
+ screen = gtk_widget_get_screen (widget);
+ visual = gdk_screen_get_rgba_visual (screen);
+
+ if (visual)
+ gtk_widget_set_visual (widget, visual);
+}
+
+static GObject *
+gtk_bubble_window_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObject *object;
+
+ object =
+ G_OBJECT_CLASS (gtk_bubble_window_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_properties);
+ g_object_set (object, "type", GTK_WINDOW_POPUP, NULL);
+
+ return object;
+}
+
+static void
+gtk_bubble_window_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ case PROP_RELATIVE_TO:
+ gtk_bubble_window_set_relative_to (GTK_BUBBLE_WINDOW (object),
+ g_value_get_object (value));
+ break;
+ case PROP_POINTING_TO:
+ gtk_bubble_window_set_pointing_to (GTK_BUBBLE_WINDOW (object),
+ g_value_get_boxed (value));
+ break;
+ case PROP_POSITION:
+ gtk_bubble_window_set_position (GTK_BUBBLE_WINDOW (object),
+ g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_bubble_window_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkBubbleWindowPrivate *priv = GTK_BUBBLE_WINDOW (object)->_priv;
+
+ switch (prop_id)
+ {
+ case PROP_RELATIVE_TO:
+ g_value_set_object (value, priv->relative_to);
+ break;
+ case PROP_POINTING_TO:
+ g_value_set_boxed (value, &priv->pointing_to);
+ break;
+ case PROP_POSITION:
+ g_value_set_enum (value, priv->preferred_position);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gtk_bubble_window_finalize (GObject *object)
+{
+ GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (object);
+ GtkBubbleWindowPrivate *priv = window->_priv;;
+
+ gtk_bubble_window_popdown (window);
+
+ if (priv->relative_to)
+ g_object_unref (priv->relative_to);
+
+ G_OBJECT_CLASS (gtk_bubble_window_parent_class)->finalize (object);
+}
+
+static void
+_gtk_bubble_window_get_pointed_to_coords (GtkBubbleWindow *window,
+ gint *x,
+ gint *y,
+ cairo_rectangle_int_t *root_rect)
+{
+ GtkBubbleWindowPrivate *priv = window->_priv;
+ cairo_rectangle_int_t rect;
+ GdkScreen *screen;
+
+ rect = priv->pointing_to;
+ screen = gtk_widget_get_screen (GTK_WIDGET (window));
+
+ if (priv->relative_to)
+ gdk_window_get_root_coords (priv->relative_to,
+ rect.x, rect.y, &rect.x, &rect.y);
+
+ if (POS_IS_VERTICAL (priv->final_position))
+ {
+ *x = CLAMP (rect.x + (rect.width / 2),
+ 0, gdk_screen_get_width (screen));
+ *y = rect.y;
+
+ if (priv->final_position == GTK_POS_BOTTOM)
+ (*y) += rect.height;
+ }
+ else
+ {
+ *y = CLAMP (rect.y + (rect.height / 2),
+ 0, gdk_screen_get_height (screen));
+ *x = rect.x;
+
+ if (priv->final_position == GTK_POS_RIGHT)
+ (*x) += rect.width;
+ }
+
+ if (root_rect)
+ *root_rect = rect;
+}
+
+static void
+_gtk_bubble_window_apply_tail_path (GtkBubbleWindow *window,
+ cairo_t *cr,
+ GtkAllocation *allocation)
+{
+ GtkBubbleWindowPrivate *priv = window->_priv;
+ gint base, tip, x, y;
+
+ _gtk_bubble_window_get_pointed_to_coords (window, &x, &y, NULL);
+
+ if (priv->final_position == GTK_POS_BOTTOM ||
+ priv->final_position == GTK_POS_RIGHT)
+ {
+ base = TAIL_HEIGHT;
+ tip = 0;
+ }
+ else if (priv->final_position == GTK_POS_TOP)
+ {
+ base = allocation->height - TAIL_HEIGHT;
+ tip = allocation->height;
+ }
+ else if (priv->final_position == GTK_POS_LEFT)
+ {
+ base = allocation->width - TAIL_HEIGHT;
+ tip = allocation->width;
+ }
+
+ if (POS_IS_VERTICAL (priv->final_position))
+ {
+ cairo_move_to (cr, CLAMP (x - priv->win_x - TAIL_GAP_WIDTH / 2,
+ 0, allocation->width - TAIL_GAP_WIDTH), base);
+ cairo_line_to (cr, CLAMP (x - priv->win_x + TAIL_GAP_WIDTH / 2,
+ TAIL_GAP_WIDTH, allocation->width), base);
+ cairo_line_to (cr, CLAMP (x - priv->win_x, 0, allocation->width), tip);
+ }
+ else
+ {
+ cairo_move_to (cr, base,
+ CLAMP (y - priv->win_y - TAIL_GAP_WIDTH / 2,
+ 0, allocation->height - TAIL_GAP_WIDTH));
+ cairo_line_to (cr, base,
+ CLAMP (y - priv->win_y + TAIL_GAP_WIDTH / 2,
+ TAIL_GAP_WIDTH, allocation->height));
+ cairo_line_to (cr, tip, CLAMP (y - priv->win_y, 0, allocation->height));
+ }
+
+ cairo_close_path (cr);
+}
+
+static void
+_gtk_bubble_window_apply_border_path (GtkBubbleWindow *window,
+ cairo_t *cr)
+{
+ GtkBubbleWindowPrivate *priv;
+ GtkAllocation allocation;
+
+ priv = window->_priv;
+ gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+
+ if (priv->final_position == GTK_POS_TOP)
+ cairo_rectangle (cr, 0, 0, allocation.width, allocation.height - TAIL_HEIGHT);
+ else if (priv->final_position == GTK_POS_BOTTOM)
+ cairo_rectangle (cr, 0, TAIL_HEIGHT , allocation.width,
+ allocation.height - TAIL_HEIGHT);
+ else if (priv->final_position == GTK_POS_LEFT)
+ cairo_rectangle (cr, 0, 0, allocation.width - TAIL_HEIGHT, allocation.height);
+ else if (priv->final_position == GTK_POS_RIGHT)
+ cairo_rectangle (cr, TAIL_HEIGHT, 0,
+ allocation.width - TAIL_HEIGHT, allocation.height);
+
+ _gtk_bubble_window_apply_tail_path (window, cr, &allocation);
+}
+
+static void
+_gtk_bubble_window_update_shape (GtkBubbleWindow *window)
+{
+ cairo_surface_t *surface;
+ cairo_region_t *region;
+ GdkWindow *win;
+ cairo_t *cr;
+
+ win = gtk_widget_get_window (GTK_WIDGET (window));
+ surface =
+ gdk_window_create_similar_surface (win,
+ CAIRO_CONTENT_COLOR_ALPHA,
+ gdk_window_get_width (win),
+ gdk_window_get_height (win));
+
+ cr = cairo_create (surface);
+ _gtk_bubble_window_apply_border_path (window, cr);
+ cairo_fill (cr);
+ cairo_destroy (cr);
+
+ region = gdk_cairo_region_create_from_surface (surface);
+ cairo_surface_destroy (surface);
+
+ if (!gtk_widget_is_composited (GTK_WIDGET (window)))
+ gtk_widget_shape_combine_region (GTK_WIDGET (window), region);
+
+ gtk_widget_input_shape_combine_region (GTK_WIDGET (window), region);
+ cairo_region_destroy (region);
+}
+
+static void
+_gtk_bubble_window_update_position (GtkBubbleWindow *window)
+{
+ GtkBubbleWindowPrivate *priv;
+ cairo_rectangle_int_t rect;
+ GtkAllocation allocation;
+ gint win_x, win_y, x, y;
+ GdkScreen *screen;
+
+ priv = window->_priv;
+ screen = gtk_widget_get_screen (GTK_WIDGET (window));
+ gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+ priv->final_position = priv->preferred_position;
+ rect = priv->pointing_to;
+
+ _gtk_bubble_window_get_pointed_to_coords (window, &x, &y, &rect);
+
+ /* Check whether there's enough room on the
+ * preferred side, move to the opposite one if not.
+ */
+ if (priv->preferred_position == GTK_POS_TOP && rect.y < allocation.height)
+ priv->final_position = GTK_POS_BOTTOM;
+ else if (priv->preferred_position == GTK_POS_BOTTOM &&
+ rect.y > gdk_screen_get_height (screen) - allocation.height)
+ priv->final_position = GTK_POS_TOP;
+ else if (priv->preferred_position == GTK_POS_LEFT && rect.x < allocation.width)
+ priv->final_position = GTK_POS_RIGHT;
+ else if (priv->preferred_position == GTK_POS_RIGHT &&
+ rect.x > gdk_screen_get_width (screen) - allocation.width)
+ priv->final_position = GTK_POS_LEFT;
+
+ if (POS_IS_VERTICAL (priv->final_position))
+ {
+ win_x = CLAMP (x - allocation.width / 2,
+ 0, gdk_screen_get_width (screen) - allocation.width);
+ win_y = y;
+
+ if (priv->final_position == GTK_POS_TOP)
+ win_y -= allocation.height;
+ }
+ else
+ {
+ win_y = CLAMP (y - allocation.height / 2,
+ 0, gdk_screen_get_height (screen) - allocation.height);
+ win_x = x;
+
+ if (priv->final_position == GTK_POS_LEFT)
+ win_x -= allocation.width;
+
+ }
+
+ priv->win_x = win_x;
+ priv->win_y = win_y;
+ gtk_window_move (GTK_WINDOW (window), win_x, win_y);
+
+ gtk_widget_queue_resize (GTK_WIDGET (window));
+}
+
+static gboolean
+gtk_bubble_window_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkStyleContext *context;
+ GtkAllocation allocation;
+ GtkWidget *child;
+
+ cairo_save (cr);
+ context = gtk_widget_get_style_context (widget);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ if (gtk_widget_is_composited (widget))
+ {
+ cairo_save (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0);
+ cairo_paint (cr);
+ cairo_restore (cr);
+
+ _gtk_bubble_window_apply_border_path (GTK_BUBBLE_WINDOW (widget), cr);
+ cairo_clip (cr);
+ }
+
+ gtk_render_background (context, cr, 0, 0,
+ allocation.width, allocation.height);
+ child = gtk_bin_get_child (GTK_BIN (widget));
+
+ if (child)
+ gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
+
+ cairo_restore (cr);
+
+ return TRUE;
+}
+
+static void
+gtk_bubble_window_get_preferred_width (GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkBubbleWindowPrivate *priv;
+ GtkWidget *child;
+ gint min, nat;
+
+ priv = GTK_BUBBLE_WINDOW (widget)->_priv;
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ min = nat = 0;
+
+ if (child)
+ gtk_widget_get_preferred_width (child, &min, &nat);
+
+ if (!POS_IS_VERTICAL (priv->final_position))
+ {
+ min += TAIL_HEIGHT;
+ nat += TAIL_HEIGHT;
+ }
+
+ if (minimum_width)
+ *minimum_width = MAX (min, TAIL_GAP_WIDTH);
+
+ if (natural_width)
+ *natural_width = MAX (nat, TAIL_GAP_WIDTH);
+}
+
+static void
+gtk_bubble_window_get_preferred_height (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkBubbleWindowPrivate *priv;
+ GtkWidget *child;
+ gint min, nat;
+
+ priv = GTK_BUBBLE_WINDOW (widget)->_priv;
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ min = nat = 0;
+
+ if (child)
+ gtk_widget_get_preferred_height (child, &min, &nat);
+
+ if (POS_IS_VERTICAL (priv->final_position))
+ {
+ min += TAIL_HEIGHT;
+ nat += TAIL_HEIGHT;
+ }
+
+ if (minimum_height)
+ *minimum_height = MAX (min, TAIL_GAP_WIDTH);
+
+ if (natural_height)
+ *natural_height = MAX (nat, TAIL_GAP_WIDTH);
+}
+
+static void
+gtk_bubble_window_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkBubbleWindowPrivate *priv;
+ GtkWidget *child;
+
+ priv = GTK_BUBBLE_WINDOW (widget)->_priv;
+ gtk_widget_set_allocation (widget, allocation);
+ child = gtk_bin_get_child (GTK_BIN (widget));
+
+ if (child)
+ {
+ GtkAllocation child_alloc;
+
+ child_alloc.x = child_alloc.y = 0;
+ child_alloc.width = allocation->width;
+ child_alloc.height = allocation->height;
+
+ if (POS_IS_VERTICAL (priv->final_position))
+ child_alloc.height -= TAIL_HEIGHT;
+ else
+ child_alloc.width -= TAIL_HEIGHT;
+
+ if (priv->final_position == GTK_POS_BOTTOM)
+ child_alloc.y += TAIL_HEIGHT;
+ else if (priv->final_position == GTK_POS_RIGHT)
+ child_alloc.x += TAIL_HEIGHT;
+
+ gtk_widget_size_allocate (child, &child_alloc);
+ }
+
+ if (gtk_widget_get_realized (widget))
+ _gtk_bubble_window_update_shape (GTK_BUBBLE_WINDOW (widget));
+
+ if (gtk_widget_get_visible (widget))
+ _gtk_bubble_window_update_position (GTK_BUBBLE_WINDOW (widget));
+}
+
+static gboolean
+gtk_bubble_window_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkWidget *child;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+
+ if (child && event->window == gtk_widget_get_window (widget))
+ {
+ GtkAllocation child_alloc;
+
+ gtk_widget_get_allocation (child, &child_alloc);
+
+ if (event->x < child_alloc.x ||
+ event->x > child_alloc.x + child_alloc.width ||
+ event->y < child_alloc.y ||
+ event->y > child_alloc.y + child_alloc.height)
+ gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
+ }
+ else
+ gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+gtk_bubble_window_key_press (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ if (event->keyval == GDK_KEY_Escape)
+ {
+ gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (widget));
+ return GDK_EVENT_STOP;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+gtk_bubble_window_grab_broken (GtkWidget *widget,
+ GdkEventGrabBroken *grab_broken)
+{
+ GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
+ GtkBubbleWindowPrivate *priv;
+ GdkDevice *event_device;
+
+ priv = window->_priv;
+ event_device = gdk_event_get_device ((GdkEvent *) grab_broken);
+
+ if (event_device == priv->device ||
+ event_device == gdk_device_get_associated_device (priv->device))
+ gtk_bubble_window_ungrab (window);
+
+ return FALSE;
+}
+
+static void
+gtk_bubble_window_grab_notify (GtkWidget *widget,
+ gboolean was_grabbed)
+{
+ GtkBubbleWindow *window = GTK_BUBBLE_WINDOW (widget);
+ GtkBubbleWindowPrivate *priv;
+
+ priv = window->_priv;
+
+ if (priv->device && gtk_widget_device_is_shadowed (widget, priv->device))
+ gtk_bubble_window_ungrab (window);
+}
+
+static void
+gtk_bubble_window_screen_changed (GtkWidget *widget,
+ GdkScreen *previous_screen)
+{
+ GdkScreen *screen;
+ GdkVisual *visual;
+
+ screen = gtk_widget_get_screen (widget);
+ visual = gdk_screen_get_rgba_visual (screen);
+
+ if (visual)
+ gtk_widget_set_visual (widget, visual);
+}
+
+static void
+gtk_bubble_window_class_init (GtkBubbleWindowClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructor = gtk_bubble_window_constructor;
+ object_class->set_property = gtk_bubble_window_set_property;
+ object_class->get_property = gtk_bubble_window_get_property;
+ object_class->finalize = gtk_bubble_window_finalize;
+
+ widget_class->get_preferred_width = gtk_bubble_window_get_preferred_width;
+ widget_class->get_preferred_height = gtk_bubble_window_get_preferred_height;
+ widget_class->size_allocate = gtk_bubble_window_size_allocate;
+ widget_class->draw = gtk_bubble_window_draw;
+ widget_class->button_press_event = gtk_bubble_window_button_press;
+ widget_class->key_press_event = gtk_bubble_window_key_press;
+ widget_class->grab_broken_event = gtk_bubble_window_grab_broken;
+ widget_class->grab_notify = gtk_bubble_window_grab_notify;
+ widget_class->screen_changed = gtk_bubble_window_screen_changed;
+
+ g_object_class_install_property (object_class,
+ PROP_RELATIVE_TO,
+ g_param_spec_object ("relative-to",
+ P_("Relative to"),
+ P_("Window the bubble window points to"),
+ GDK_TYPE_WINDOW,
+ GTK_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_POINTING_TO,
+ g_param_spec_boxed ("pointing-to",
+ P_("Pointing to"),
+ P_("Rectangle the bubble window points to"),
+ CAIRO_GOBJECT_TYPE_RECTANGLE_INT,
+ GTK_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_POSITION,
+ g_param_spec_enum ("position",
+ P_("Position"),
+ P_("Position to place the bubble window"),
+ GTK_TYPE_POSITION_TYPE, GTK_POS_TOP,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_type_class_add_private (klass, sizeof (GtkBubbleWindowPrivate));
+}
+
+static void
+_gtk_bubble_window_update_relative_to (GtkBubbleWindow *window,
+ GdkWindow *relative_to)
+{
+ GtkBubbleWindowPrivate *priv;
+
+ priv = window->_priv;
+
+ if (priv->relative_to == relative_to)
+ return;
+
+ if (priv->relative_to)
+ g_object_unref (priv->relative_to);
+
+ priv->relative_to = (relative_to) ? g_object_ref (relative_to) : NULL;
+ g_object_notify (G_OBJECT (window), "relative-to");
+}
+
+static void
+_gtk_bubble_window_update_pointing_to (GtkBubbleWindow *window,
+ cairo_rectangle_int_t *pointing_to)
+{
+ GtkBubbleWindowPrivate *priv;
+
+ priv = window->_priv;
+ priv->pointing_to = *pointing_to;
+ priv->has_pointing_to = TRUE;
+ g_object_notify (G_OBJECT (window), "pointing-to");
+}
+
+static void
+_gtk_bubble_window_update_preferred_position (GtkBubbleWindow *window,
+ GtkPositionType position)
+{
+ GtkBubbleWindowPrivate *priv;
+
+ priv = window->_priv;
+ priv->preferred_position = position;
+ g_object_notify (G_OBJECT (window), "position");
+}
+
+/**
+ * gtk_bubble_window_new:
+ *
+ * Returns a newly created #GtkBubblewindow
+ *
+ * Returns: a new #GtkBubbleWindow
+ *
+ * Since: 3.8
+ **/
+GtkWidget *
+gtk_bubble_window_new (void)
+{
+ return g_object_new (GTK_TYPE_BUBBLE_WINDOW, NULL);
+}
+
+/**
+ * gtk_bubble_window_set_relative_to:
+ * @window: a #GtkBubbleWindow
+ * @relative_to: a #GdkWindow
+ *
+ * Sets the #GdkWindow to act as the origin of coordinates of
+ * @window, or %NULL to use the root window. See
+ * gtk_bubble_window_set_pointing_to()
+ *
+ * If @window is currently visible, it will be moved to reflect
+ * this change.
+ *
+ * Since: 3.8
+ **/
+void
+gtk_bubble_window_set_relative_to (GtkBubbleWindow *window,
+ GdkWindow *relative_to)
+{
+ g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
+ g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
+
+ _gtk_bubble_window_update_relative_to (window, relative_to);
+
+ if (gtk_widget_get_visible (GTK_WIDGET (window)))
+ _gtk_bubble_window_update_position (window);
+}
+
+/**
+ * gtk_bubble_window_get_relative_to:
+ * @window: a #GtkBubbleWindow
+ *
+ * Returns the #GdkWindow used as the origin of coordinates.
+ * If @window is currently visible, it will be moved to reflect
+ * this change.
+ *
+ * Returns: the #GdkWindow @window is placed upon
+ *
+ * Since: 3.8
+ **/
+GdkWindow *
+gtk_bubble_window_get_relative_to (GtkBubbleWindow *window)
+{
+ GtkBubbleWindowPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), NULL);
+
+ priv = window->_priv;
+
+ return priv->relative_to;
+}
+
+/**
+ * gtk_bubble_window_set_pointing_to:
+ * @window: a #GtkBubbleWindow
+ * @rect: rectangle to point to
+ *
+ * Sets the rectangle that @window will point to, the coordinates
+ * of this rectangle are relative to the #GdkWindow set through
+ * gtk_bubble_window_set_relative_to().
+ *
+ * Since: 3.8
+ **/
+void
+gtk_bubble_window_set_pointing_to (GtkBubbleWindow *window,
+ cairo_rectangle_int_t *rect)
+{
+ g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
+ g_return_if_fail (rect != NULL);
+
+ _gtk_bubble_window_update_pointing_to (window, rect);
+
+ if (gtk_widget_get_visible (GTK_WIDGET (window)))
+ _gtk_bubble_window_update_position (window);
+}
+
+/**
+ * gtk_bubble_window_get_pointing_to:
+ * @window: a #GtkBubbleWindow
+ * @rect: (out): location to store the rectangle
+ *
+ * If a rectangle to point to is set, this function will return
+ * %TRUE and fill in @rect with the rectangle @window is currently
+ * pointing to.
+ *
+ * Returns: %TRUE if a rectangle is set
+ *
+ * Since: 3.8
+ **/
+gboolean
+gtk_bubble_window_get_pointing_to (GtkBubbleWindow *window,
+ cairo_rectangle_int_t *rect)
+{
+ GtkBubbleWindowPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
+
+ priv = window->_priv;
+
+ if (rect)
+ *rect = priv->pointing_to;
+
+ return priv->has_pointing_to;
+}
+
+/**
+ * gtk_bubble_window_set_position:
+ * @window: a #GtkBubbleWindow
+ * @position: preferred bubble position
+ *
+ * Sets the preferred position for @window to appear.
+ * If @window is currently visible, it will be moved to reflect
+ * this change.
+ *
+ * <note>
+ * This preference will be respected where possible, although
+ * on lack of space (eg. if close to the screen edges), the
+ * #GtkBubbleWindow may choose to appear on the opposite side
+ * </note>
+ *
+ * Since: 3.8
+ **/
+void
+gtk_bubble_window_set_position (GtkBubbleWindow *window,
+ GtkPositionType position)
+{
+ g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
+ g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
+
+ _gtk_bubble_window_update_preferred_position (window, position);
+
+ if (gtk_widget_get_visible (GTK_WIDGET (window)))
+ _gtk_bubble_window_update_position (window);
+}
+
+/**
+ * gtk_bubble_window_get_position:
+ * @window: a #GtkBubbleWindow
+ *
+ * Returns the preferred position to place @window
+ *
+ * Returns: The preferred position
+ *
+ * Since: 3.8
+ **/
+GtkPositionType
+gtk_bubble_window_get_position (GtkBubbleWindow *window)
+{
+ GtkBubbleWindowPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), GTK_POS_TOP);
+
+ priv = window->_priv;
+
+ return priv->preferred_position;
+}
+
+/**
+ * gtk_bubble_window_popup:
+ * @window: a #GtkBubbleWindow
+ * @relative_to: #GdkWindow to position upon
+ * @pointing_to: rectangle to point to, in @relative_to coordinates
+ * @position: preferred position for @window
+ *
+ * This function sets atomically all #GtkBubbleWindow position
+ * parameters, and shows/updates @window
+ *
+ * Since: 3.8
+ **/
+void
+gtk_bubble_window_popup (GtkBubbleWindow *window,
+ GdkWindow *relative_to,
+ cairo_rectangle_int_t *pointing_to,
+ GtkPositionType position)
+{
+ g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
+ g_return_if_fail (!relative_to || GDK_IS_WINDOW (relative_to));
+ g_return_if_fail (position >= GTK_POS_LEFT && position <= GTK_POS_BOTTOM);
+ g_return_if_fail (pointing_to != NULL);
+
+ _gtk_bubble_window_update_preferred_position (window, position);
+ _gtk_bubble_window_update_relative_to (window, relative_to);
+ _gtk_bubble_window_update_pointing_to (window, pointing_to);
+
+ if (!gtk_widget_get_visible (GTK_WIDGET (window)))
+ gtk_widget_show (GTK_WIDGET (window));
+
+ _gtk_bubble_window_update_position (window);
+}
+
+/**
+ * gtk_bubble_window_popdown:
+ * @window: a #GtkBubbleWindow
+ *
+ * Removes the window from the screen
+ *
+ * <note>
+ * If a grab was previously added through gtk_bubble_window_grab(),
+ * the grab will be removed by this function.
+ * </note>
+ *
+ * Since: 3.8
+ **/
+void
+gtk_bubble_window_popdown (GtkBubbleWindow *window)
+{
+ GtkBubbleWindowPrivate *priv = window->_priv;
+
+ g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
+
+ if (priv->grabbed)
+ gtk_bubble_window_ungrab (window);
+
+ if (gtk_widget_get_visible (GTK_WIDGET (window)))
+ gtk_widget_hide (GTK_WIDGET (window));
+}
+
+/**
+ * gtk_bubble_window_grab:
+ * @window: a #GtkBubbleWindow
+ * @device: a master #GdkDevice
+ * @activate_time: timestamp to perform the grab
+ *
+ * This function performs GDK and GTK+ grabs on @device and
+ * its paired #GdkDevice. After this call all pointer/keyboard
+ * events will be handled by @window.
+ *
+ * Calling this also brings in a #GtkMenu alike behavior, clicking
+ * outside the #GtkBubbleWindow or pressing the Escape key will
+ * popdown the menu by default.
+ *
+ * <note>
+ * If there was a previous grab, it will be undone before doing
+ * the requested grab.
+ * </note>
+ *
+ * Returns: %TRUE if the grab was successful
+ *
+ * Since: 3.8
+ **/
+gboolean
+gtk_bubble_window_grab (GtkBubbleWindow *window,
+ GdkDevice *device,
+ guint32 activate_time)
+{
+ GtkBubbleWindowPrivate *priv;
+ GdkDevice *other_device;
+ GdkWindow *grab_window;
+ GdkGrabStatus status;
+
+ g_return_val_if_fail (GTK_IS_BUBBLE_WINDOW (window), FALSE);
+ g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
+ g_return_val_if_fail (gdk_device_get_device_type (device) == GDK_DEVICE_TYPE_MASTER, FALSE);
+
+ priv = window->_priv;
+
+ if (!priv->has_pointing_to ||
+ gdk_window_is_destroyed (priv->relative_to))
+ return FALSE;
+
+ if (priv->device)
+ gtk_bubble_window_ungrab (window);
+
+ gtk_widget_realize (GTK_WIDGET (window));
+ grab_window = gtk_widget_get_window (GTK_WIDGET (window));
+ other_device = gdk_device_get_associated_device (device);
+
+ status = gdk_device_grab (device, grab_window,
+ GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
+ NULL, activate_time);
+
+ if (status == GDK_GRAB_SUCCESS)
+ {
+ status = gdk_device_grab (other_device, grab_window,
+ GDK_OWNERSHIP_WINDOW, TRUE, GRAB_EVENT_MASK,
+ NULL, activate_time);
+
+ /* Ungrab the first device on error */
+ if (status != GDK_GRAB_SUCCESS)
+ gdk_device_ungrab (device, activate_time);
+ }
+
+ if (status == GDK_GRAB_SUCCESS)
+ {
+ gtk_device_grab_add (GTK_WIDGET (window), device, TRUE);
+ priv->device = device;
+ }
+
+ return status == GDK_GRAB_SUCCESS;
+}
+
+/**
+ * gtk_bubble_window_ungrab:
+ * @window: a #GtkBubbleWindow
+ *
+ * This functions undoes a grab added through gtk_bubble_window_grab()
+ * in this @window,
+ *
+ * Since: 3.8
+ **/
+void
+gtk_bubble_window_ungrab (GtkBubbleWindow *window)
+{
+ GtkBubbleWindowPrivate *priv;
+
+ g_return_if_fail (GTK_IS_BUBBLE_WINDOW (window));
+
+ priv = window->_priv;
+
+ if (!priv->device)
+ return;
+
+ gdk_device_ungrab (priv->device, GDK_CURRENT_TIME);
+ gdk_device_ungrab (gdk_device_get_associated_device (priv->device),
+ GDK_CURRENT_TIME);
+ gtk_device_grab_remove (GTK_WIDGET (window), priv->device);
+ priv->device = NULL;
+}
diff --git a/gtk/gtkbubblewindow.h b/gtk/gtkbubblewindow.h
new file mode 100644
index 0000000..5c0a9fd
--- /dev/null
+++ b/gtk/gtkbubblewindow.h
@@ -0,0 +1,81 @@
+/* GTK - The GIMP Toolkit
+ * Copyright © 2013 Carlos Garnacho <carlosg gnome org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GTK_BUBBLE_WINDOW_H__
+#define __GTK_BUBBLE_WINDOW_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_BUBBLE_WINDOW (gtk_bubble_window_get_type ())
+#define GTK_BUBBLE_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_BUBBLE_WINDOW,
GtkBubbleWindow))
+#define GTK_BUBBLE_WINDOW_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), GTK_TYPE_BUBBLE_WINDOW,
GtkBubbleWindowClass))
+#define GTK_IS_BUBBLE_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_BUBBLE_WINDOW))
+#define GTK_IS_BUBBLE_WINDOW_CLASS(o) (G_TYPE_CHECK_CLASS_TYPE ((o), GTK_TYPE_BUBBLE_WINDOW))
+#define GTK_BUBBLE_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_BUBBLE_WINDOW,
GtkBubbleWindowClass))
+
+typedef struct _GtkBubbleWindow GtkBubbleWindow;
+typedef struct _GtkBubbleWindowClass GtkBubbleWindowClass;
+
+struct _GtkBubbleWindow
+{
+ GtkWindow parent_instance;
+
+ /*< private >*/
+ gpointer _priv;
+};
+
+struct _GtkBubbleWindowClass
+{
+ GtkWindowClass parent_class;
+};
+
+GType gtk_bubble_window_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gtk_bubble_window_new (void);
+
+void gtk_bubble_window_set_relative_to (GtkBubbleWindow *window,
+ GdkWindow *relative_to);
+GdkWindow * gtk_bubble_window_get_relative_to (GtkBubbleWindow *window);
+
+void gtk_bubble_window_set_pointing_to (GtkBubbleWindow *window,
+ cairo_rectangle_int_t *rect);
+gboolean gtk_bubble_window_get_pointing_to (GtkBubbleWindow *window,
+ cairo_rectangle_int_t *rect);
+void gtk_bubble_window_set_position (GtkBubbleWindow *window,
+ GtkPositionType position);
+
+GtkPositionType
+ gtk_bubble_window_get_position (GtkBubbleWindow *window);
+
+void gtk_bubble_window_popup (GtkBubbleWindow *window,
+ GdkWindow *relative_to,
+ cairo_rectangle_int_t *pointing_to,
+ GtkPositionType position);
+
+void gtk_bubble_window_popdown (GtkBubbleWindow *window);
+
+gboolean gtk_bubble_window_grab (GtkBubbleWindow *window,
+ GdkDevice *device,
+ guint32 activate_time);
+
+void gtk_bubble_window_ungrab (GtkBubbleWindow *window);
+
+G_END_DECLS
+
+#endif /* __GTK_BUBBLE_POPUP_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]