diff --git a/demos/gtk-demo/Makefile.am b/demos/gtk-demo/Makefile.am
index 97bc571b56..89a25dce68 100644
--- a/demos/gtk-demo/Makefile.am
+++ b/demos/gtk-demo/Makefile.am
@@ -41,6 +41,7 @@ demos = \
panes.c \
pickers.c \
pixbufs.c \
+ popover.c \
printing.c \
revealer.c \
rotated_text.c \
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index d8efe4f0bf..76bb3043ad 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -110,6 +110,7 @@
panes.c
pickers.c
pixbufs.c
+ popover.c
printing.c
revealer.c
rotated_text.c
@@ -133,4 +134,7 @@
messages.txt
apple-red.png
+
+ popover.ui
+
diff --git a/demos/gtk-demo/popover.c b/demos/gtk-demo/popover.c
new file mode 100644
index 0000000000..809fcef706
--- /dev/null
+++ b/demos/gtk-demo/popover.c
@@ -0,0 +1,183 @@
+/* Popovers
+ *
+ * A bubble-like window containing contextual information or options.
+ * GtkPopovers can be attached to any widget, and will be displayed
+ * within the same window, but on top of all its content.
+ */
+
+#include
+
+static void
+toggle_changed_cb (GtkToggleButton *button,
+ GtkWidget *popover)
+{
+ gtk_widget_set_visible (popover,
+ gtk_toggle_button_get_active (button));
+}
+
+static GtkWidget *
+create_popover (GtkWidget *parent,
+ GtkWidget *child,
+ GtkPositionType pos)
+{
+ GtkWidget *popover;
+
+ popover = gtk_popover_new (parent);
+ gtk_popover_set_position (GTK_POPOVER (popover), pos);
+ gtk_container_add (GTK_CONTAINER (popover), child);
+ gtk_container_set_border_width (GTK_CONTAINER (popover), 6);
+ gtk_widget_show (child);
+
+ return popover;
+}
+
+static GtkWidget *
+create_complex_popover (GtkWidget *parent,
+ GtkPositionType pos)
+{
+ GtkWidget *popover, *content;
+ GtkBuilder *builder;
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_resource (builder, "/popover/popover.ui", NULL);
+ content = GTK_WIDGET (gtk_builder_get_object (builder, "box"));
+ g_object_ref (content);
+ gtk_widget_unparent (content);
+ g_object_unref (builder);
+
+ popover = create_popover (parent, content, GTK_POS_BOTTOM);
+ g_object_unref (content);
+
+ gtk_widget_set_size_request (popover, 200, -1);
+ gtk_widget_set_vexpand (popover, TRUE);
+
+ gtk_widget_set_margin_start (popover, 10);
+ gtk_widget_set_margin_end (popover, 10);
+ gtk_widget_set_margin_bottom (popover, 10);
+
+ return popover;
+}
+
+static void
+entry_size_allocate_cb (GtkEntry *entry,
+ GtkAllocation *allocation,
+ gpointer user_data)
+{
+ GtkEntryIconPosition popover_pos;
+ GtkPopover *popover = user_data;
+ cairo_rectangle_int_t rect;
+
+ if (gtk_widget_is_visible (GTK_WIDGET (popover)))
+ {
+ popover_pos =
+ GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (entry),
+ "popover-icon-pos"));
+ gtk_entry_get_icon_area (entry, popover_pos, &rect);
+ gtk_popover_set_pointing_to (GTK_POPOVER (popover), &rect);
+ }
+}
+
+static void
+entry_icon_press_cb (GtkEntry *entry,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GtkWidget *popover = user_data;
+ cairo_rectangle_int_t rect;
+
+ gtk_entry_get_icon_area (entry, icon_pos, &rect);
+ gtk_popover_set_pointing_to (GTK_POPOVER (popover), &rect);
+ gtk_widget_show (popover);
+ gtk_grab_add (popover);
+
+ g_object_set_data (G_OBJECT (entry), "popover-icon-pos",
+ GUINT_TO_POINTER (icon_pos));
+}
+
+static void
+day_selected_cb (GtkCalendar *calendar,
+ gpointer user_data)
+{
+ cairo_rectangle_int_t rect;
+ GtkAllocation allocation;
+ GtkWidget *popover;
+ GdkEvent *event;
+
+ event = gtk_get_current_event ();
+
+ if (event->type != GDK_BUTTON_PRESS)
+ return;
+
+ gdk_window_coords_to_parent (event->button.window,
+ event->button.x, event->button.y,
+ &event->button.x, &event->button.y);
+ gtk_widget_get_allocation (GTK_WIDGET (calendar), &allocation);
+ rect.x = event->button.x - allocation.x;
+ rect.y = event->button.y - allocation.y;
+ rect.width = rect.height = 1;
+
+ popover = create_popover (GTK_WIDGET (calendar),
+ gtk_entry_new (),
+ GTK_POS_BOTTOM);
+ gtk_popover_set_pointing_to (GTK_POPOVER (popover), &rect);
+
+ gtk_widget_show (popover);
+ gtk_grab_add (popover);
+
+ gdk_event_free (event);
+}
+
+GtkWidget *
+do_popover (GtkWidget *do_widget)
+{
+ static GtkWidget *window = NULL;
+ GtkWidget *popover, *box, *widget;
+
+ if (!window)
+ {
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 24);
+ gtk_container_set_border_width (GTK_CONTAINER (box), 24);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &window);
+
+ widget = gtk_toggle_button_new_with_label ("Button");
+ popover = create_popover (widget,
+ gtk_label_new ("This popover does not grab input"),
+ GTK_POS_TOP);
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (toggle_changed_cb), popover);
+ gtk_container_add (GTK_CONTAINER (box), widget);
+
+ widget = gtk_entry_new ();
+ popover = create_complex_popover (widget, GTK_POS_TOP);
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (widget),
+ GTK_ENTRY_ICON_PRIMARY, "edit-find");
+ gtk_entry_set_icon_from_icon_name (GTK_ENTRY (widget),
+ GTK_ENTRY_ICON_SECONDARY, "edit-clear");
+
+ g_signal_connect (widget, "icon-press",
+ G_CALLBACK (entry_icon_press_cb), popover);
+ g_signal_connect (widget, "size-allocate",
+ G_CALLBACK (entry_size_allocate_cb), popover);
+ gtk_container_add (GTK_CONTAINER (box), widget);
+
+ widget = gtk_calendar_new ();
+ g_signal_connect (widget, "day-selected",
+ G_CALLBACK (day_selected_cb), NULL);
+ gtk_container_add (GTK_CONTAINER (box), widget);
+ }
+
+ if (!gtk_widget_get_visible (window))
+ gtk_widget_show_all (window);
+ else
+ {
+ gtk_widget_destroy (window);
+ window = NULL;
+ }
+
+ return window;
+}
diff --git a/demos/gtk-demo/popover.ui b/demos/gtk-demo/popover.ui
new file mode 100644
index 0000000000..33fabe669f
--- /dev/null
+++ b/demos/gtk-demo/popover.ui
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+