window: Add lowlevel popovers API

Popovers are transient floating widgets that are confined to the
window space. These have their own GdkWindow that is set on top
of the regular window contents, so they can be used for popup menu
alike UIs with custom popup/popdown/grabs behavior.
This commit is contained in:
Carlos Garnacho 2013-11-07 23:49:21 +01:00
parent 89a51bd15d
commit 85a19c894f
2 changed files with 334 additions and 0 deletions

View File

@ -123,6 +123,15 @@
#define MNEMONICS_DELAY 300 /* ms */
typedef struct _GtkDeviceGrabInfo GtkDeviceGrabInfo;
typedef struct _GtkWindowPopover GtkWindowPopover;
struct _GtkWindowPopover
{
GtkWidget *widget;
GdkWindow *window;
GtkPositionType pos;
cairo_rectangle_int_t rect;
};
struct _GtkWindowPrivate
{
@ -137,6 +146,8 @@ struct _GtkWindowPrivate
GdkScreen *screen;
GtkApplication *application;
GHashTable *popovers;
GdkModifierType mnemonic_modifier;
GdkWindowTypeHint gdk_type_hint;
@ -1318,6 +1329,18 @@ gtk_window_close (GtkWindow *window)
gdk_threads_add_idle (send_delete_event, window);
}
static void
popover_destroy (GtkWindowPopover *popover)
{
if (popover->widget && gtk_widget_get_parent (popover->widget))
gtk_widget_unparent (popover->widget);
if (popover->window)
gdk_window_destroy (popover->window);
g_free (popover);
}
static void
gtk_window_init (GtkWindow *window)
{
@ -1368,6 +1391,8 @@ gtk_window_init (GtkWindow *window)
priv->has_resize_grip = TRUE;
priv->mnemonics_visible = TRUE;
priv->focus_visible = TRUE;
priv->popovers = g_hash_table_new_full (NULL, NULL, NULL,
(GDestroyNotify) popover_destroy);
g_object_ref_sink (window);
priv->has_user_ref_count = TRUE;
@ -5166,6 +5191,8 @@ gtk_window_finalize (GObject *object)
priv->mnemonics_display_timeout_id = 0;
}
g_hash_table_destroy (priv->popovers);
G_OBJECT_CLASS (gtk_window_parent_class)->finalize (object);
}
@ -5398,6 +5425,19 @@ gtk_window_hide (GtkWidget *widget)
gtk_grab_remove (widget);
}
static void
popover_map (GtkWidget *widget,
GtkWindowPopover *popover)
{
if (popover->window)
{
gdk_window_show (popover->window);
if (gtk_widget_get_visible (popover->widget))
gtk_widget_map (popover->widget);
}
}
static void
gtk_window_map (GtkWidget *widget)
{
@ -5511,6 +5551,8 @@ gtk_window_map (GtkWidget *widget)
if (priv->application)
gtk_application_handle_window_map (priv->application, window);
g_hash_table_foreach (priv->popovers, (GHFunc) popover_map, window);
}
static gboolean
@ -5531,6 +5573,18 @@ gtk_window_map_event (GtkWidget *widget,
return FALSE;
}
static void
popover_unmap (GtkWidget *widget,
GtkWindowPopover *popover)
{
if (popover->window)
{
if (gtk_widget_is_visible (popover->widget))
gtk_widget_unmap (popover->widget);
gdk_window_hide (popover->window);
}
}
static void
gtk_window_unmap (GtkWidget *widget)
{
@ -5547,6 +5601,7 @@ gtk_window_unmap (GtkWidget *widget)
return;
}
g_hash_table_foreach (priv->popovers, (GHFunc) popover_unmap, window);
gdk_window = gtk_widget_get_window (widget);
gtk_widget_set_mapped (widget, FALSE);
@ -5676,6 +5731,117 @@ gtk_window_get_remembered_size (GtkWindow *window,
}
}
static void
popover_get_rect (GtkWindowPopover *popover,
GtkWindow *window,
cairo_rectangle_int_t *rect)
{
GtkAllocation win_alloc;
GtkRequisition req;
gtk_widget_get_preferred_size (popover->widget, NULL, &req);
gtk_widget_get_allocation (GTK_WIDGET (window), &win_alloc);
rect->width = req.width;
rect->height = req.height;
if (popover->pos == GTK_POS_LEFT || popover->pos == GTK_POS_RIGHT)
{
if (req.height < win_alloc.height &&
gtk_widget_get_vexpand (popover->widget))
{
rect->y = 0;
rect->height = win_alloc.height;
}
else
rect->y = CLAMP (popover->rect.y + (popover->rect.height / 2) -
(req.height / 2), 0, win_alloc.height - req.height);
if (popover->pos == GTK_POS_LEFT)
{
rect->x = popover->rect.x - req.width;
if (rect->x > 0 && gtk_widget_get_hexpand (popover->widget))
{
rect->x = 0;
rect->width = popover->rect.x;
}
}
else
{
rect->x = popover->rect.x + popover->rect.width;
if (rect->x + rect->width < win_alloc.width &&
gtk_widget_get_hexpand (popover->widget))
rect->width = win_alloc.width - rect->x;
}
}
else if (popover->pos == GTK_POS_TOP || popover->pos == GTK_POS_BOTTOM)
{
if (req.width < win_alloc.width &&
gtk_widget_get_hexpand (popover->widget))
{
rect->x = 0;
rect->width = win_alloc.width;
}
else
rect->x = CLAMP (popover->rect.x + (popover->rect.width / 2) -
(req.width / 2), 0, win_alloc.width - req.width);
if (popover->pos == GTK_POS_TOP)
{
rect->y = popover->rect.y - req.height;
if (rect->y > 0 && gtk_widget_get_vexpand (popover->widget))
{
rect->y = 0;
rect->height = popover->rect.y;
}
}
else
{
rect->y = popover->rect.y + popover->rect.height;
if (rect->y + rect->height < win_alloc.height &&
gtk_widget_get_vexpand (popover->widget))
rect->height = win_alloc.height - rect->y;
}
}
}
static void
popover_realize (GtkWidget *widget,
GtkWindowPopover *popover,
GtkWindow *window)
{
cairo_rectangle_int_t rect;
GdkWindow *parent_window;
GdkWindowAttr attributes;
gint attributes_mask;
if (popover->window)
return;
popover_get_rect (popover, window, &rect);
attributes.window_type = GDK_WINDOW_CHILD;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.x = rect.x;
attributes.y = rect.y;
attributes.width = rect.width;
attributes.height = rect.height;
attributes.visual = gtk_widget_get_visual (widget);
attributes.event_mask = gtk_widget_get_events (popover->widget) |
GDK_EXPOSURE_MASK;
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
parent_window = gtk_widget_get_window (GTK_WIDGET (window));
popover->window =
gdk_window_new (parent_window, &attributes, attributes_mask);
gtk_widget_register_window (GTK_WIDGET (window), popover->window);
gtk_widget_set_parent_window (popover->widget, popover->window);
}
static void
gtk_window_realize (GtkWidget *widget)
{
@ -5926,12 +6092,23 @@ gtk_window_realize (GtkWidget *widget)
if (priv->has_resize_grip)
resize_grip_create_window (window);
g_hash_table_foreach (priv->popovers, (GHFunc) popover_realize, window);
old_scale = priv->scale;
priv->scale = gtk_widget_get_scale_factor (widget);
if (old_scale != priv->scale)
_gtk_widget_scale_changed (widget);
}
static void
popover_unrealize (GtkWidget *widget,
GtkWindowPopover *popover)
{
gtk_widget_unrealize (popover->widget);
gdk_window_destroy (popover->window);
popover->window = NULL;
}
static void
gtk_window_unrealize (GtkWidget *widget)
{
@ -5982,6 +6159,8 @@ gtk_window_unrealize (GtkWidget *widget)
}
}
g_hash_table_foreach (priv->popovers, (GHFunc) popover_unrealize, window);
GTK_WIDGET_CLASS (gtk_window_parent_class)->unrealize (widget);
}
@ -6697,6 +6876,32 @@ _gtk_window_set_allocation (GtkWindow *window,
*allocation_out = child_allocation;
}
static void
popover_size_allocate (GtkWidget *widget,
GtkWindowPopover *popover,
GtkWindow *window)
{
cairo_rectangle_int_t rect;
if (!popover->window)
return;
popover_get_rect (popover, window, &rect);
gdk_window_move_resize (popover->window, rect.x, rect.y,
rect.width, rect.height);
rect.x = rect.y = 0;
gtk_widget_size_allocate (widget, &rect);
if (gtk_widget_is_drawable (GTK_WIDGET (window)) &&
gtk_widget_is_visible (widget))
{
if (!gdk_window_is_visible (popover->window))
gdk_window_show (popover->window);
}
else if (gdk_window_is_visible (popover->window))
gdk_window_hide (popover->window);
}
static void
gtk_window_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
@ -6710,6 +6915,9 @@ gtk_window_size_allocate (GtkWidget *widget,
child = gtk_bin_get_child (GTK_BIN (window));
if (child && gtk_widget_get_visible (child))
gtk_widget_size_allocate (child, &child_allocation);
g_hash_table_foreach (window->priv->popovers,
(GHFunc) popover_size_allocate, widget);
}
static gint
@ -7619,8 +7827,11 @@ gtk_window_remove (GtkContainer *container,
GtkWidget *widget)
{
GtkWindow *window = GTK_WINDOW (container);
if (widget == window->priv->title_box)
unset_titlebar (window);
else if (g_hash_table_contains (window->priv->popovers, widget))
gtk_window_remove_popover (window, widget);
else
GTK_CONTAINER_CLASS (gtk_window_parent_class)->remove (container, widget);
}
@ -9395,10 +9606,12 @@ gtk_window_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkWindowPrivate *priv = GTK_WINDOW (widget)->priv;
GtkWindowPopover *popover;
GtkStyleContext *context;
gboolean ret = FALSE;
GtkAllocation allocation;
GtkBorder window_border;
GHashTableIter iter;
gint title_height;
context = gtk_widget_get_style_context (widget);
@ -9483,6 +9696,16 @@ gtk_window_draw (GtkWidget *widget,
gtk_style_context_restore (context);
}
g_hash_table_iter_init (&iter, priv->popovers);
while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &popover))
{
if (popover->window && gtk_widget_is_visible (popover->widget) &&
gtk_cairo_should_draw_window (cr, popover->window))
gtk_container_propagate_draw (GTK_CONTAINER (widget),
popover->widget, cr);
}
return ret;
}
@ -11697,3 +11920,102 @@ _gtk_window_get_shadow_width (GtkWindow *window,
{
get_shadow_width (GTK_WIDGET (window), border);
}
void
gtk_window_add_popover (GtkWindow *window,
GtkWidget *popover)
{
GtkWindowPrivate *priv;
GtkWindowPopover *data;
g_return_if_fail (GTK_IS_WINDOW (window));
g_return_if_fail (GTK_IS_WIDGET (popover));
g_return_if_fail (gtk_widget_get_parent (popover) == NULL);
priv = window->priv;
if (g_hash_table_contains (priv->popovers, popover))
return;
data = g_new0 (GtkWindowPopover, 1);
data->widget = popover;
g_hash_table_insert (priv->popovers, popover, data);
if (gtk_widget_get_realized (GTK_WIDGET (window)))
popover_realize (popover, data, window);
gtk_widget_set_parent (popover, GTK_WIDGET (window));
}
void
gtk_window_remove_popover (GtkWindow *window,
GtkWidget *popover)
{
GtkWindowPrivate *priv;
g_return_if_fail (GTK_IS_WINDOW (window));
g_return_if_fail (GTK_IS_WIDGET (popover));
priv = window->priv;
g_hash_table_remove (priv->popovers, popover);
}
/**
* gtk_window_set_popover_position:
* @window: a #GtkWindow
* @popover: #GtkWidget to be positioned on top of window contents
* @pos: Position of the popover, relative of the contents it's related to
* @rect: Disclosure rectangle, in @window coordinates
*
* Positions @popover so it is at the side given by @pos of the rectangle @rect.
* If @popover is set to expand horizontally or vertically, the position given
* by @pos will be used to determine how the popover is allowed to expand
* without covering the content it's logically attached to.
*
* Since: 3.12
**/
void
gtk_window_set_popover_position (GtkWindow *window,
GtkWidget *popover,
GtkPositionType pos,
const cairo_rectangle_int_t *rect)
{
GtkWindowPopover *data;
GtkWindowPrivate *priv;
g_return_if_fail (GTK_IS_WINDOW (window));
g_return_if_fail (GTK_IS_WIDGET (popover));
priv = window->priv;
data = g_hash_table_lookup (priv->popovers, popover);
if (!data)
{
g_warning ("Widget %s(%p) is not a popover of window %s",
gtk_widget_get_name (popover), popover,
gtk_widget_get_name (GTK_WIDGET (window)));
return;
}
else
{
if (data->pos == pos &&
memcmp (&data->rect, rect, sizeof (cairo_rectangle_int_t)) == 0)
return;
}
data->rect = *rect;
data->pos = pos;
if (gtk_widget_is_visible (popover))
{
if (!data->window)
{
popover_realize (popover, data, window);
popover_map (popover, data);
}
else
gdk_window_raise (data->window);
}
gtk_widget_queue_resize (popover);
}

View File

@ -483,6 +483,18 @@ void gtk_window_set_titlebar (GtkWindow *window,
GDK_AVAILABLE_IN_3_12
gboolean gtk_window_is_maximized (GtkWindow *window);
GDK_AVAILABLE_IN_3_12
void gtk_window_add_popover (GtkWindow *window,
GtkWidget *popover);
GDK_AVAILABLE_IN_3_12
void gtk_window_remove_popover (GtkWindow *window,
GtkWidget *popover);
GDK_AVAILABLE_IN_3_12
void gtk_window_set_popover_position (GtkWindow *window,
GtkWidget *popover,
GtkPositionType pos,
const cairo_rectangle_int_t *rect);
G_END_DECLS
#endif /* __GTK_WINDOW_H__ */