aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-gtk
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2016-01-31 05:04:50 +0100
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2016-01-31 05:04:50 +0100
commitea0179e342343f5fbefb265bde4dea0d475f0781 (patch)
tree70af3013fe80707c802e88cae93bb5d504da2f55 /src/interface-gtk
parent2951f147ad24737e2c40a5fa4ace611a9b3fb829 (diff)
downloadsciteco-ea0179e342343f5fbefb265bde4dea0d475f0781.tar.gz
interfaces have their own automake subdirectories and convenience libraries now
* use libtool convenience libraries as much as possible (for all static libraries except Scintilla) * improves separation of language and user interface implementations (e.g. the Gtk widgets are not interesting for the rest of SciTECO) * the Curses popup widget can now be factored out of interface-curses.cpp * some common CPPFLAGS are now defined by ./configure via AM_CPPFLAGS, so they don't have to be repeated in each submodule. * fixed building the Curses UI: GTK_FLOW_BOX_FALLBACK conditional must always be defined.
Diffstat (limited to 'src/interface-gtk')
-rw-r--r--src/interface-gtk/Makefile.am23
-rw-r--r--src/interface-gtk/gtk-info-popup.gob269
-rw-r--r--src/interface-gtk/gtkflowbox.c4795
-rw-r--r--src/interface-gtk/gtkflowbox.h180
-rw-r--r--src/interface-gtk/interface-gtk.cpp759
-rw-r--r--src/interface-gtk/interface-gtk.h171
6 files changed, 6197 insertions, 0 deletions
diff --git a/src/interface-gtk/Makefile.am b/src/interface-gtk/Makefile.am
new file mode 100644
index 0000000..64b4625
--- /dev/null
+++ b/src/interface-gtk/Makefile.am
@@ -0,0 +1,23 @@
+AM_CPPFLAGS += -I$(top_srcdir)/src
+
+AM_CFLAGS = -Wall -std=c99
+AM_CXXFLAGS = -Wall -Wno-char-subscripts
+if CLANG
+AM_CXXFLAGS += -Wno-mismatched-tags
+endif
+
+EXTRA_DIST = gtk-info-popup.gob
+BUILT_SOURCES = gtk-info-popup.c \
+ gtk-info-popup.h gtk-info-popup-private.h
+
+noinst_LTLIBRARIES = libsciteco-interface.la
+libsciteco_interface_la_SOURCES = interface-gtk.cpp interface-gtk.h
+if GTK_FLOW_BOX_FALLBACK
+libsciteco_interface_la_SOURCES += gtkflowbox.c gtkflowbox.h
+endif
+nodist_libsciteco_interface_la_SOURCES = gtk-info-popup.c
+
+CLEANFILES = $(BUILT_SOURCES)
+
+%.c %.h %-private.h : %.gob
+ @GOB2@ --gtk3 $<
diff --git a/src/interface-gtk/gtk-info-popup.gob b/src/interface-gtk/gtk-info-popup.gob
new file mode 100644
index 0000000..5082091
--- /dev/null
+++ b/src/interface-gtk/gtk-info-popup.gob
@@ -0,0 +1,269 @@
+requires 2.0.20
+
+%ctop{
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <math.h>
+
+#include <glib/gprintf.h>
+#include <gio/gio.h>
+
+#ifndef HAVE_GTK_FLOW_BOX_NEW
+#include "gtkflowbox.h"
+#endif
+%}
+
+%h{
+#include <gtk/gtk.h>
+%}
+
+enum GTK_INFO_POPUP {
+ PLAIN,
+ FILE,
+ DIRECTORY
+} Gtk:Info:Popup:Entry:Type;
+
+class Gtk:Info:Popup from Gtk:Event:Box {
+ public GtkAdjustment *hadjustment;
+ public GtkAdjustment *vadjustment;
+
+ private GtkWidget *flow_box;
+
+ init(self)
+ {
+ GtkWidget *box, *viewport;
+
+ /*
+ * A box containing a viewport and scrollbar will
+ * "emulate" a scrolled window.
+ * We cannot use a scrolled window since it ignores
+ * the preferred height of its viewport which breaks
+ * height-for-width management.
+ */
+ box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+
+ self->hadjustment = gtk_adjustment_new(0, 0, 0, 0, 0, 0);
+ self->vadjustment = gtk_adjustment_new(0, 0, 0, 0, 0, 0);
+
+ GtkWidget *scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL,
+ self->vadjustment);
+
+ self->_priv->flow_box = gtk_flow_box_new();
+ /* take as little height as necessary */
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(self->_priv->flow_box),
+ GTK_ORIENTATION_HORIZONTAL);
+ //gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(self->_priv->flow_box), TRUE);
+ /* this for focus handling only, not for scrolling */
+ gtk_flow_box_set_hadjustment(GTK_FLOW_BOX(self->_priv->flow_box),
+ self->hadjustment);
+ gtk_flow_box_set_vadjustment(GTK_FLOW_BOX(self->_priv->flow_box),
+ self->vadjustment);
+
+ gtk_container_set_border_width(GTK_CONTAINER(self->_priv->flow_box), 10);
+
+ viewport = gtk_viewport_new(self->hadjustment, self->vadjustment);
+ gtk_container_add(GTK_CONTAINER(viewport), self->_priv->flow_box);
+
+ gtk_box_pack_start(GTK_BOX(box), viewport, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(box), scrollbar, FALSE, FALSE, 0);
+ gtk_widget_show_all(box);
+
+ /*
+ * The top-level widget is a GtkEventBox, so it can have
+ * a background. We assign a default color since the popup
+ * will usually be put in an overlay and we don't want it
+ * to be transparent.
+ * It can also be styled with rounded corners etc.
+ * FIXME: This method of setting a background color is
+ * deprecated. We are supposed to use CSS style providers...
+ */
+ gtk_container_add(GTK_CONTAINER(self), box);
+ GdkRGBA color = {0.5, 0.5, 0.5, 1.0};
+ gtk_widget_override_background_color(GTK_WIDGET(self), GTK_STATE_FLAG_NORMAL,
+ &color);
+ }
+
+ override (Gtk:Widget) void
+ size_allocate(Gtk:Widget *widget,
+ Gtk:Allocation *allocation)
+ {
+ GtkWidget *parent = gtk_widget_get_parent(widget);
+ GtkAllocation parent_alloc;
+
+
+ /*
+ * Adjust the allocation of the popup to be within the
+ * bounds of its parent widget which GtkOvelay does
+ * not seem to do automatically.
+ * Also Gtk does not seem to query this widget's
+ * preferred height.
+ * FIXME: Only works if the GtkInfoPopup is added
+ * directly to the GtkOverlay.
+ */
+ gtk_widget_get_allocation(parent, &parent_alloc);
+ allocation->width = MIN(allocation->width, parent_alloc.width);
+ if (allocation->height > parent_alloc.height) {
+ allocation->y += allocation->height - parent_alloc.height;
+ allocation->height = parent_alloc.height;
+ }
+
+ /*
+ * Allocate the adjusted/clipped allocation
+ */
+ PARENT_HANDLER(widget, allocation);
+ }
+
+ /*
+ * Adapted from GtkScrolledWindow's gtk_scrolled_window_scroll_event()
+ * since the viewport does not react to scroll events.
+ * FIXME: May need to handle non-delta scrolling, i.e. GDK_SCROLL_UP
+ * and GDK_SCROLL_DOWN.
+ */
+ override (Gtk:Widget) gboolean
+ scroll_event(Gtk:Widget *widget, GdkEventScroll *event)
+ {
+ Self *self = GTK_INFO_POPUP(widget);
+ gdouble delta_x, delta_y;
+
+ if (gdk_event_get_scroll_deltas((GdkEvent *)event,
+ &delta_x, &delta_y)) {
+ GtkAdjustment *adj = self->vadjustment;
+ gdouble page_size = gtk_adjustment_get_page_size(adj);
+ gdouble scroll_unit = pow(page_size, 2.0 / 3.0);
+ gdouble new_value;
+
+ new_value = CLAMP(gtk_adjustment_get_value(adj) + delta_y * scroll_unit,
+ gtk_adjustment_get_lower(adj),
+ gtk_adjustment_get_upper(adj) -
+ gtk_adjustment_get_page_size(adj));
+
+ gtk_adjustment_set_value(adj, new_value);
+
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ public GtkWidget *
+ new(void)
+ {
+ Self *widget = GET_NEW;
+ return GTK_WIDGET(widget);
+ }
+
+ private GIcon *
+ get_icon_for_path(const gchar *path, const gchar *fallback_name)
+ {
+ GFile *file;
+ GFileInfo *info;
+ GIcon *icon = NULL;
+
+ file = g_file_new_for_path(path);
+ info = g_file_query_info(file, "standard::icon", 0, NULL, NULL);
+ if (info) {
+ icon = g_file_info_get_icon(info);
+ g_object_ref(icon);
+ g_object_unref(info);
+ } else {
+ /* fall back to standard icon, but this can still return NULL! */
+ icon = g_icon_new_for_string(fallback_name, NULL);
+ }
+ g_object_unref(file);
+
+ return icon;
+ }
+
+ public void
+ add(self, Gtk:Info:Popup:Entry:Type type,
+ const gchar *name, gboolean highlight)
+ {
+ GtkWidget *hbox;
+ GtkWidget *label;
+ gchar *markup;
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
+
+ if (type == GTK_INFO_POPUP_FILE || type == GTK_INFO_POPUP_DIRECTORY) {
+ const gchar *fallback = type == GTK_INFO_POPUP_FILE ? "text-x-generic"
+ : "folder";
+ GIcon *icon;
+
+ icon = self_get_icon_for_path(name, fallback);
+ if (icon) {
+ GtkWidget *image;
+
+ image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU);
+ g_object_unref(icon);
+ gtk_box_pack_start(GTK_BOX(hbox), image,
+ FALSE, FALSE, 0);
+ }
+ }
+
+ /*
+ * FIXME: setting Pango attributes directly would be
+ * much more efficient
+ */
+ label = gtk_label_new(NULL);
+ markup = g_markup_printf_escaped("<span weight=\"%s\">%s</span>",
+ highlight ? "bold" : "normal",
+ name);
+ gtk_label_set_markup(GTK_LABEL(label), markup);
+ g_free(markup);
+ gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5);
+ gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+
+ gtk_widget_show_all(hbox);
+ gtk_container_add(GTK_CONTAINER(self->_priv->flow_box), hbox);
+ }
+
+ public void
+ scroll_page(self)
+ {
+ GtkAdjustment *adj = self->vadjustment;
+ gdouble new_value;
+
+ if (gtk_adjustment_get_value(adj) + gtk_adjustment_get_page_size(adj) ==
+ gtk_adjustment_get_upper(adj)) {
+ /* wrap and scroll back to the top */
+ new_value = gtk_adjustment_get_lower(adj);
+ } else {
+ /* scroll one page */
+ GList *child_list;
+
+ new_value = gtk_adjustment_get_value(adj) +
+ gtk_adjustment_get_page_size(adj);
+
+ /*
+ * Adjust this so only complete entries are shown.
+ * Effectively, this rounds down to the line height.
+ */
+ child_list = gtk_container_get_children(GTK_CONTAINER(self->_priv->flow_box));
+ if (child_list) {
+ new_value -= (gint)new_value %
+ gtk_widget_get_allocated_height(GTK_WIDGET(child_list->data));
+ g_list_free(child_list);
+ }
+
+ /* clip to the maximum possible value */
+ new_value = MIN(new_value, gtk_adjustment_get_upper(adj));
+ }
+
+ gtk_adjustment_set_value(adj, new_value);
+ }
+
+ public void
+ clear(self)
+ {
+ GList *children;
+
+ children = gtk_container_get_children(GTK_CONTAINER(self->_priv->flow_box));
+ for (GList *cur = g_list_first(children);
+ cur != NULL;
+ cur = g_list_next(cur))
+ gtk_widget_destroy(GTK_WIDGET(cur->data));
+ g_list_free(children);
+ }
+}
diff --git a/src/interface-gtk/gtkflowbox.c b/src/interface-gtk/gtkflowbox.c
new file mode 100644
index 0000000..1a5c2e9
--- /dev/null
+++ b/src/interface-gtk/gtkflowbox.c
@@ -0,0 +1,4795 @@
+/*
+ * Copyright (C) 2007-2010 Openismus GmbH
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Tristan Van Berkom <tristanvb@openismus.com>
+ * Matthias Clasen <mclasen@redhat.com>
+ * William Jon McCann <jmccann@redhat.com>
+ */
+
+/* Preamble {{{1 */
+
+/**
+ * SECTION:gtkflowbox
+ * @Short_Description: A container that allows reflowing its children
+ * @Title: GtkFlowBox
+ *
+ * A GtkFlowBox positions child widgets in sequence according to its
+ * orientation.
+ *
+ * For instance, with the horizontal orientation, the widgets will be
+ * arranged from left to right, starting a new row under the previous
+ * row when necessary. Reducing the width in this case will require more
+ * rows, so a larger height will be requested.
+ *
+ * Likewise, with the vertical orientation, the widgets will be arranged
+ * from top to bottom, starting a new column to the right when necessary.
+ * Reducing the height will require more columns, so a larger width will
+ * be requested.
+ *
+ * The children of a GtkFlowBox can be dynamically sorted and filtered.
+ *
+ * Although a GtkFlowBox must have only #GtkFlowBoxChild children,
+ * you can add any kind of widget to it via gtk_container_add(), and
+ * a GtkFlowBoxChild widget will automatically be inserted between
+ * the box and the widget.
+ *
+ * Also see #GtkListBox.
+ *
+ * GtkFlowBox was added in GTK+ 3.12.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+
+#include "gtkflowbox.h"
+
+/* Forward declarations and utilities {{{1 */
+
+static void gtk_flow_box_update_cursor (GtkFlowBox *box,
+ GtkFlowBoxChild *child);
+static void gtk_flow_box_select_and_activate (GtkFlowBox *box,
+ GtkFlowBoxChild *child);
+static void gtk_flow_box_update_selection (GtkFlowBox *box,
+ GtkFlowBoxChild *child,
+ gboolean modify,
+ gboolean extend);
+static void gtk_flow_box_apply_filter (GtkFlowBox *box,
+ GtkFlowBoxChild *child);
+static void gtk_flow_box_apply_sort (GtkFlowBox *box,
+ GtkFlowBoxChild *child);
+static gint gtk_flow_box_sort (GtkFlowBoxChild *a,
+ GtkFlowBoxChild *b,
+ GtkFlowBox *box);
+
+static void
+get_current_selection_modifiers (GtkWidget *widget,
+ gboolean *modify,
+ gboolean *extend)
+{
+ GdkModifierType state = 0;
+ GdkModifierType mask;
+
+ *modify = FALSE;
+ *extend = FALSE;
+
+ if (gtk_get_current_event_state (&state))
+ {
+ mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION);
+ if ((state & mask) == mask)
+ *modify = TRUE;
+ mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
+ if ((state & mask) == mask)
+ *extend = TRUE;
+ }
+}
+
+static void
+path_from_horizontal_line_rects (cairo_t *cr,
+ GdkRectangle *lines,
+ gint n_lines)
+{
+ gint start_line, end_line;
+ GdkRectangle *r;
+ gint i;
+
+ /* Join rows vertically by extending to the middle */
+ for (i = 0; i < n_lines - 1; i++)
+ {
+ GdkRectangle *r1 = &lines[i];
+ GdkRectangle *r2 = &lines[i+1];
+ gint gap, old;
+
+ gap = r2->y - (r1->y + r1->height);
+ r1->height += gap / 2;
+ old = r2->y;
+ r2->y = r1->y + r1->height;
+ r2->height += old - r2->y;
+ }
+
+ cairo_new_path (cr);
+ start_line = 0;
+
+ do
+ {
+ for (i = start_line; i < n_lines; i++)
+ {
+ r = &lines[i];
+ if (i == start_line)
+ cairo_move_to (cr, r->x + r->width, r->y);
+ else
+ cairo_line_to (cr, r->x + r->width, r->y);
+ cairo_line_to (cr, r->x + r->width, r->y + r->height);
+
+ if (i < n_lines - 1 &&
+ (r->x + r->width < lines[i+1].x ||
+ r->x > lines[i+1].x + lines[i+1].width))
+ {
+ i++;
+ break;
+ }
+ }
+ end_line = i;
+ for (i = end_line - 1; i >= start_line; i--)
+ {
+ r = &lines[i];
+ cairo_line_to (cr, r->x, r->y + r->height);
+ cairo_line_to (cr, r->x, r->y);
+ }
+ cairo_close_path (cr);
+ start_line = end_line;
+ }
+ while (end_line < n_lines);
+}
+
+static void
+path_from_vertical_line_rects (cairo_t *cr,
+ GdkRectangle *lines,
+ gint n_lines)
+{
+ gint start_line, end_line;
+ GdkRectangle *r;
+ gint i;
+
+ /* Join rows horizontally by extending to the middle */
+ for (i = 0; i < n_lines - 1; i++)
+ {
+ GdkRectangle *r1 = &lines[i];
+ GdkRectangle *r2 = &lines[i+1];
+ gint gap, old;
+
+ gap = r2->x - (r1->x + r1->width);
+ r1->width += gap / 2;
+ old = r2->x;
+ r2->x = r1->x + r1->width;
+ r2->width += old - r2->x;
+ }
+
+ cairo_new_path (cr);
+ start_line = 0;
+
+ do
+ {
+ for (i = start_line; i < n_lines; i++)
+ {
+ r = &lines[i];
+ if (i == start_line)
+ cairo_move_to (cr, r->x, r->y + r->height);
+ else
+ cairo_line_to (cr, r->x, r->y + r->height);
+ cairo_line_to (cr, r->x + r->width, r->y + r->height);
+
+ if (i < n_lines - 1 &&
+ (r->y + r->height < lines[i+1].y ||
+ r->y > lines[i+1].y + lines[i+1].height))
+ {
+ i++;
+ break;
+ }
+ }
+ end_line = i;
+ for (i = end_line - 1; i >= start_line; i--)
+ {
+ r = &lines[i];
+ cairo_line_to (cr, r->x + r->width, r->y);
+ cairo_line_to (cr, r->x, r->y);
+ }
+ cairo_close_path (cr);
+ start_line = end_line;
+ }
+ while (end_line < n_lines);
+}
+
+/* GtkFlowBoxChild {{{1 */
+
+/* GObject boilerplate {{{2 */
+
+enum {
+ CHILD_ACTIVATE,
+ CHILD_LAST_SIGNAL
+};
+
+static guint child_signals[CHILD_LAST_SIGNAL] = { 0 };
+
+typedef struct _GtkFlowBoxChildPrivate GtkFlowBoxChildPrivate;
+struct _GtkFlowBoxChildPrivate
+{
+ GSequenceIter *iter;
+ gboolean selected;
+};
+
+#define CHILD_PRIV(child) ((GtkFlowBoxChildPrivate*)gtk_flow_box_child_get_instance_private ((GtkFlowBoxChild*)(child)))
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkFlowBoxChild, gtk_flow_box_child, GTK_TYPE_BIN)
+
+/* Internal API {{{2 */
+
+static GtkFlowBox *
+gtk_flow_box_child_get_box (GtkFlowBoxChild *child)
+{
+ GtkWidget *parent;
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (child));
+ if (parent && GTK_IS_FLOW_BOX (parent))
+ return GTK_FLOW_BOX (parent);
+
+ return NULL;
+}
+
+static void
+gtk_flow_box_child_set_focus (GtkFlowBoxChild *child)
+{
+ GtkFlowBox *box = gtk_flow_box_child_get_box (child);
+ gboolean modify;
+ gboolean extend;
+
+ get_current_selection_modifiers (GTK_WIDGET (box), &modify, &extend);
+
+ if (modify)
+ gtk_flow_box_update_cursor (box, child);
+ else
+ gtk_flow_box_update_selection (box, child, FALSE, FALSE);
+}
+
+/* GtkWidget implementation {{{2 */
+
+static gboolean
+gtk_flow_box_child_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ gboolean had_focus = FALSE;
+ GtkWidget *child;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+
+ g_object_get (widget, "has-focus", &had_focus, NULL);
+ if (had_focus)
+ {
+ /* If on row, going right, enter into possible container */
+ if (child &&
+ (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD))
+ {
+ if (gtk_widget_child_focus (GTK_WIDGET (child), direction))
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else if (gtk_container_get_focus_child (GTK_CONTAINER (widget)) != NULL)
+ {
+ /* Child has focus, always navigate inside it first */
+ if (gtk_widget_child_focus (child, direction))
+ return TRUE;
+
+ /* If exiting child container to the left, select child */
+ if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
+ {
+ gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget));
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else
+ {
+ /* If coming from the left, enter into possible container */
+ if (child &&
+ (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD))
+ {
+ if (gtk_widget_child_focus (child, direction))
+ return TRUE;
+ }
+
+ gtk_flow_box_child_set_focus (GTK_FLOW_BOX_CHILD (widget));
+ return TRUE;
+ }
+}
+
+static void
+gtk_flow_box_child_activate (GtkFlowBoxChild *child)
+{
+ GtkFlowBox *box;
+
+ box = gtk_flow_box_child_get_box (child);
+ if (box)
+ gtk_flow_box_select_and_activate (box, child);
+}
+
+static gboolean
+gtk_flow_box_child_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkAllocation allocation = {0};
+ GtkStyleContext* context;
+ GtkStateFlags state;
+ GtkBorder border;
+ gint focus_pad;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ context = gtk_widget_get_style_context (widget);
+ state = gtk_widget_get_state_flags (widget);
+
+ gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height);
+ gtk_render_frame (context, cr, 0, 0, allocation.width, allocation.height);
+
+ if (gtk_widget_has_visible_focus (widget))
+ {
+ gtk_style_context_get_border (context, state, &border);
+
+ gtk_style_context_get_style (context,
+ "focus-padding", &focus_pad,
+ NULL);
+ gtk_render_focus (context, cr, border.left + focus_pad, border.top + focus_pad,
+ allocation.width - 2 * focus_pad - border.left - border.right,
+ allocation.height - 2 * focus_pad - border.top - border.bottom);
+ }
+
+ GTK_WIDGET_CLASS (gtk_flow_box_child_parent_class)->draw (widget, cr);
+
+ return TRUE;
+}
+
+/* Size allocation {{{3 */
+
+static void
+gtk_flow_box_child_get_full_border (GtkFlowBoxChild *child,
+ GtkBorder *full_border)
+{
+ GtkWidget *widget = GTK_WIDGET (child);
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ GtkBorder padding, border;
+ int focus_width, focus_pad;
+
+ context = gtk_widget_get_style_context (widget);
+ state = gtk_style_context_get_state (context);
+
+ gtk_style_context_get_padding (context, state, &padding);
+ gtk_style_context_get_border (context, state, &border);
+ gtk_style_context_get_style (context,
+ "focus-line-width", &focus_width,
+ "focus-padding", &focus_pad,
+ NULL);
+
+ full_border->left = padding.left + border.left + focus_width + focus_pad;
+ full_border->right = padding.right + border.right + focus_width + focus_pad;
+ full_border->top = padding.top + border.top + focus_width + focus_pad;
+ full_border->bottom = padding.bottom + border.bottom + focus_width + focus_pad;
+}
+
+static GtkSizeRequestMode
+gtk_flow_box_child_get_request_mode (GtkWidget *widget)
+{
+ GtkFlowBox *box;
+
+ box = gtk_flow_box_child_get_box (GTK_FLOW_BOX_CHILD (widget));
+ if (box)
+ return gtk_widget_get_request_mode (GTK_WIDGET (box));
+ else
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void gtk_flow_box_child_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+static void gtk_flow_box_child_get_preferred_height (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height);
+
+static void
+gtk_flow_box_child_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ GtkWidget *child;
+ gint child_min = 0, child_natural = 0;
+ GtkBorder full_border = { 0, };
+
+ gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border);
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (child && gtk_widget_get_visible (child))
+ gtk_widget_get_preferred_height_for_width (child, width - full_border.left - full_border.right,
+ &child_min, &child_natural);
+
+ if (minimum_height)
+ *minimum_height = full_border.top + child_min + full_border.bottom;
+ if (natural_height)
+ *natural_height = full_border.top + child_natural + full_border.bottom;
+ }
+ else
+ {
+ gtk_flow_box_child_get_preferred_height (widget, minimum_height, natural_height);
+ }
+}
+
+static void
+gtk_flow_box_child_get_preferred_width (GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ GtkWidget *child;
+ gint child_min = 0, child_natural = 0;
+ GtkBorder full_border = { 0, };
+
+ gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border);
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (child && gtk_widget_get_visible (child))
+ gtk_widget_get_preferred_width (child, &child_min, &child_natural);
+
+ if (minimum_width)
+ *minimum_width = full_border.left + child_min + full_border.right;
+ if (natural_width)
+ *natural_width = full_border.left + child_natural + full_border.right;
+ }
+ else
+ {
+ gint natural_height;
+
+ gtk_flow_box_child_get_preferred_height (widget, NULL, &natural_height);
+ gtk_flow_box_child_get_preferred_width_for_height (widget, natural_height,
+ minimum_width, natural_width);
+ }
+}
+
+static void
+gtk_flow_box_child_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ gtk_flow_box_child_get_preferred_width (widget, minimum_width, natural_width);
+ }
+ else
+ {
+ GtkWidget *child;
+ gint child_min = 0, child_natural = 0;
+ GtkBorder full_border = { 0, };
+
+ gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border);
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (child && gtk_widget_get_visible (child))
+ gtk_widget_get_preferred_width_for_height (child, height - full_border.top - full_border.bottom,
+ &child_min, &child_natural);
+
+ if (minimum_width)
+ *minimum_width = full_border.left + child_min + full_border.right;
+ if (natural_width)
+ *natural_width = full_border.left + child_natural + full_border.right;
+ }
+}
+
+static void
+gtk_flow_box_child_get_preferred_height (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ if (gtk_flow_box_child_get_request_mode (widget) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ gint natural_width;
+
+ gtk_flow_box_child_get_preferred_width (widget, NULL, &natural_width);
+ gtk_flow_box_child_get_preferred_height_for_width (widget, natural_width,
+ minimum_height, natural_height);
+ }
+ else
+ {
+ GtkWidget *child;
+ gint child_min = 0, child_natural = 0;
+ GtkBorder full_border = { 0, };
+
+ gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &full_border);
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (child && gtk_widget_get_visible (child))
+ gtk_widget_get_preferred_height (child, &child_min, &child_natural);
+
+ if (minimum_height)
+ *minimum_height = full_border.top + child_min + full_border.bottom;
+ if (natural_height)
+ *natural_height = full_border.top + child_natural + full_border.bottom;
+ }
+}
+
+static void
+gtk_flow_box_child_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkWidget *child;
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (child && gtk_widget_get_visible (child))
+ {
+ GtkAllocation child_allocation;
+ GtkBorder border = { 0, };
+
+ gtk_flow_box_child_get_full_border (GTK_FLOW_BOX_CHILD (widget), &border);
+
+ child_allocation.x = allocation->x + border.left;
+ child_allocation.y = allocation->y + border.top;
+ child_allocation.width = allocation->width - border.left - border.right;
+ child_allocation.height = allocation->height - border.top - border.bottom;
+
+ child_allocation.width = MAX (1, child_allocation.width);
+ child_allocation.height = MAX (1, child_allocation.height);
+
+ gtk_widget_size_allocate (child, &child_allocation);
+ }
+}
+
+/* GObject implementation {{{2 */
+
+static void
+gtk_flow_box_child_class_init (GtkFlowBoxChildClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ widget_class->draw = gtk_flow_box_child_draw;
+ widget_class->get_request_mode = gtk_flow_box_child_get_request_mode;
+ widget_class->get_preferred_height = gtk_flow_box_child_get_preferred_height;
+ widget_class->get_preferred_height_for_width = gtk_flow_box_child_get_preferred_height_for_width;
+ widget_class->get_preferred_width = gtk_flow_box_child_get_preferred_width;
+ widget_class->get_preferred_width_for_height = gtk_flow_box_child_get_preferred_width_for_height;
+ widget_class->size_allocate = gtk_flow_box_child_size_allocate;
+ widget_class->focus = gtk_flow_box_child_focus;
+
+ class->activate = gtk_flow_box_child_activate;
+
+ /**
+ * GtkFlowBoxChild::activate:
+ * @child: The child on which the signal is emitted
+ *
+ * The ::activate signal is emitted when the user activates
+ * a child widget in a #GtkFlowBox, either by clicking or
+ * double-clicking, or by using the Space or Enter key.
+ *
+ * While this signal is used as a
+ * [keybinding signal][GtkBindingSignal],
+ * it can be used by applications for their own purposes.
+ */
+ child_signals[CHILD_ACTIVATE] =
+ g_signal_new ("activate",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkFlowBoxChildClass, activate),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ widget_class->activate_signal = child_signals[CHILD_ACTIVATE];
+
+ gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_LIST_ITEM);
+}
+
+static void
+gtk_flow_box_child_init (GtkFlowBoxChild *child)
+{
+ GtkStyleContext *context;
+
+ gtk_widget_set_can_focus (GTK_WIDGET (child), TRUE);
+ gtk_widget_set_redraw_on_allocate (GTK_WIDGET (child), TRUE);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (child));
+ gtk_style_context_add_class (context, "grid-child");
+}
+
+/* Public API {{{2 */
+
+/**
+ * gtk_flow_box_child_new:
+ *
+ * Creates a new #GtkFlowBoxChild, to be used as a child
+ * of a #GtkFlowBox.
+ *
+ * Returns: a new #GtkFlowBoxChild
+ *
+ * Since: 3.12
+ */
+GtkWidget *
+gtk_flow_box_child_new (void)
+{
+ return g_object_new (GTK_TYPE_FLOW_BOX_CHILD, NULL);
+}
+
+/**
+ * gtk_flow_box_child_get_index:
+ * @child: a #GtkFlowBoxChild
+ *
+ * Gets the current index of the @child in its #GtkFlowBox container.
+ *
+ * Returns: the index of the @child, or -1 if the @child is not
+ * in a flow box.
+ *
+ * Since: 3.12
+ */
+gint
+gtk_flow_box_child_get_index (GtkFlowBoxChild *child)
+{
+ GtkFlowBoxChildPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), -1);
+
+ priv = CHILD_PRIV (child);
+
+ if (priv->iter != NULL)
+ return g_sequence_iter_get_position (priv->iter);
+
+ return -1;
+}
+
+/**
+ * gtk_flow_box_child_is_selected:
+ * @child: a #GtkFlowBoxChild
+ *
+ * Returns whether the @child is currently selected in its
+ * #GtkFlowBox container.
+ *
+ * Returns: %TRUE if @child is selected
+ *
+ * Since: 3.12
+ */
+gboolean
+gtk_flow_box_child_is_selected (GtkFlowBoxChild *child)
+{
+ g_return_val_if_fail (GTK_IS_FLOW_BOX_CHILD (child), FALSE);
+
+ return CHILD_PRIV (child)->selected;
+}
+
+/**
+ * gtk_flow_box_child_changed:
+ * @child: a #GtkFlowBoxChild
+ *
+ * Marks @child as changed, causing any state that depends on this
+ * to be updated. This affects sorting and filtering.
+ *
+ * Note that calls to this method must be in sync with the data
+ * used for the sorting and filtering functions. For instance, if
+ * the list is mirroring some external data set, and *two* children
+ * changed in the external data set when you call
+ * gtk_flow_box_child_changed() on the first child, the sort function
+ * must only read the new data for the first of the two changed
+ * children, otherwise the resorting of the children will be wrong.
+ *
+ * This generally means that if you don’t fully control the data
+ * model, you have to duplicate the data that affects the sorting
+ * and filtering functions into the widgets themselves. Another
+ * alternative is to call gtk_flow_box_invalidate_sort() on any
+ * model change, but that is more expensive.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_child_changed (GtkFlowBoxChild *child)
+{
+ GtkFlowBox *box;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
+
+ box = gtk_flow_box_child_get_box (child);
+
+ if (box == NULL)
+ return;
+
+ gtk_flow_box_apply_sort (box, child);
+ gtk_flow_box_apply_filter (box, child);
+}
+
+/* GtkFlowBox {{{1 */
+
+/* Constants {{{2 */
+
+#define DEFAULT_MAX_CHILDREN_PER_LINE 7
+#define RUBBERBAND_START_DISTANCE 32
+#define AUTOSCROLL_FAST_DISTANCE 32
+#define AUTOSCROLL_FACTOR 20
+#define AUTOSCROLL_FACTOR_FAST 10
+
+/* GObject boilerplate {{{2 */
+
+enum {
+ CHILD_ACTIVATED,
+ SELECTED_CHILDREN_CHANGED,
+ ACTIVATE_CURSOR_CHILD,
+ TOGGLE_CURSOR_CHILD,
+ MOVE_CURSOR,
+ SELECT_ALL,
+ UNSELECT_ALL,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum {
+ PROP_0,
+ PROP_ORIENTATION,
+ PROP_HOMOGENEOUS,
+ PROP_HALIGN_POLICY,
+ PROP_VALIGN_POLICY,
+ PROP_COLUMN_SPACING,
+ PROP_ROW_SPACING,
+ PROP_MIN_CHILDREN_PER_LINE,
+ PROP_MAX_CHILDREN_PER_LINE,
+ PROP_SELECTION_MODE,
+ PROP_ACTIVATE_ON_SINGLE_CLICK
+};
+
+typedef struct _GtkFlowBoxPrivate GtkFlowBoxPrivate;
+struct _GtkFlowBoxPrivate {
+ GtkOrientation orientation;
+ gboolean homogeneous;
+
+ guint row_spacing;
+ guint column_spacing;
+
+ GtkFlowBoxChild *prelight_child;
+ GtkFlowBoxChild *cursor_child;
+ GtkFlowBoxChild *selected_child;
+
+ gboolean active_child_active;
+ GtkFlowBoxChild *active_child;
+
+ GtkSelectionMode selection_mode;
+
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+ gboolean activate_on_single_click;
+
+ guint16 min_children_per_line;
+ guint16 max_children_per_line;
+ guint16 cur_children_per_line;
+
+ GSequence *children;
+
+ GtkFlowBoxFilterFunc filter_func;
+ gpointer filter_data;
+ GDestroyNotify filter_destroy;
+
+ GtkFlowBoxSortFunc sort_func;
+ gpointer sort_data;
+ GDestroyNotify sort_destroy;
+
+ gboolean track_motion;
+ gboolean rubberband_select;
+ GtkFlowBoxChild *rubberband_first;
+ GtkFlowBoxChild *rubberband_last;
+ gint button_down_x;
+ gint button_down_y;
+ gboolean rubberband_modify;
+ gboolean rubberband_extend;
+ GdkDevice *rubberband_device;
+
+ GtkScrollType autoscroll_mode;
+ guint autoscroll_id;
+};
+
+#define BOX_PRIV(box) ((GtkFlowBoxPrivate*)gtk_flow_box_get_instance_private ((GtkFlowBox*)(box)))
+
+G_DEFINE_TYPE_WITH_CODE (GtkFlowBox, gtk_flow_box, GTK_TYPE_CONTAINER,
+ G_ADD_PRIVATE (GtkFlowBox)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
+
+/* Internal API, utilities {{{2 */
+
+#define ORIENTATION_ALIGN(box) \
+ (BOX_PRIV(box)->orientation == GTK_ORIENTATION_HORIZONTAL \
+ ? gtk_widget_get_halign (GTK_WIDGET (box)) \
+ : gtk_widget_get_valign (GTK_WIDGET (box)))
+
+#define OPPOSING_ORIENTATION_ALIGN(box) \
+ (BOX_PRIV(box)->orientation == GTK_ORIENTATION_HORIZONTAL \
+ ? gtk_widget_get_valign (GTK_WIDGET (box)) \
+ : gtk_widget_get_halign (GTK_WIDGET (box)))
+
+/* Children are visible if they are shown by the app (visible)
+ * and not filtered out (child_visible) by the box
+ */
+static inline gboolean
+child_is_visible (GtkWidget *child)
+{
+ return gtk_widget_get_visible (child) &&
+ gtk_widget_get_child_visible (child);
+}
+
+static gint
+get_visible_children (GtkFlowBox *box)
+{
+ GSequenceIter *iter;
+ gint i = 0;
+
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+
+ child = g_sequence_get (iter);
+ if (child_is_visible (child))
+ i++;
+ }
+
+ return i;
+}
+
+static GtkFlowBoxChild *
+gtk_flow_box_find_child_at_pos (GtkFlowBox *box,
+ gint x,
+ gint y)
+{
+ GtkWidget *child;
+ GSequenceIter *iter;
+ GtkAllocation allocation;
+
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ child = g_sequence_get (iter);
+ if (!child_is_visible (child))
+ continue;
+ gtk_widget_get_allocation (child, &allocation);
+ if (x >= allocation.x && x < (allocation.x + allocation.width) &&
+ y >= allocation.y && y < (allocation.y + allocation.height))
+ return GTK_FLOW_BOX_CHILD (child);
+ }
+
+ return NULL;
+}
+
+static void
+gtk_flow_box_update_prelight (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ if (child != priv->prelight_child)
+ {
+ priv->prelight_child = child;
+ gtk_widget_queue_draw (GTK_WIDGET (box));
+ }
+}
+
+static void
+gtk_flow_box_update_active (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ gboolean val;
+
+ val = priv->active_child == child;
+ if (priv->active_child != NULL &&
+ val != priv->active_child_active)
+ {
+ priv->active_child_active = val;
+ gtk_widget_queue_draw (GTK_WIDGET (box));
+ }
+}
+
+static void
+gtk_flow_box_apply_filter (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ gboolean do_show;
+
+ do_show = TRUE;
+ if (priv->filter_func != NULL)
+ do_show = priv->filter_func (child, priv->filter_data);
+
+ gtk_widget_set_child_visible (GTK_WIDGET (child), do_show);
+}
+
+static void
+gtk_flow_box_apply_filter_all (GtkFlowBox *box)
+{
+ GSequenceIter *iter;
+
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkFlowBoxChild *child;
+
+ child = g_sequence_get (iter);
+ gtk_flow_box_apply_filter (box, child);
+ }
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+}
+
+static void
+gtk_flow_box_apply_sort (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ if (BOX_PRIV (box)->sort_func != NULL)
+ {
+ g_sequence_sort_changed (CHILD_PRIV (child)->iter,
+ (GCompareDataFunc)gtk_flow_box_sort, box);
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ }
+}
+
+/* Selection utilities {{{3 */
+
+static gboolean
+gtk_flow_box_child_set_selected (GtkFlowBoxChild *child,
+ gboolean selected)
+{
+ if (CHILD_PRIV (child)->selected != selected)
+ {
+ CHILD_PRIV (child)->selected = selected;
+ if (selected)
+ gtk_widget_set_state_flags (GTK_WIDGET (child),
+ GTK_STATE_FLAG_SELECTED, FALSE);
+ else
+ gtk_widget_unset_state_flags (GTK_WIDGET (child),
+ GTK_STATE_FLAG_SELECTED);
+
+ gtk_widget_queue_draw (GTK_WIDGET (child));
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_flow_box_unselect_all_internal (GtkFlowBox *box)
+{
+ GtkFlowBoxChild *child;
+ GSequenceIter *iter;
+ gboolean dirty = FALSE;
+
+ if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
+ return FALSE;
+
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ child = g_sequence_get (iter);
+ dirty |= gtk_flow_box_child_set_selected (child, FALSE);
+ }
+
+ return dirty;
+}
+
+static void
+gtk_flow_box_unselect_child_internal (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ if (!CHILD_PRIV (child)->selected)
+ return;
+
+ if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
+ return;
+ else if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
+ gtk_flow_box_unselect_all_internal (box);
+ else
+ gtk_flow_box_child_set_selected (child, FALSE);
+
+ g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+}
+
+static void
+gtk_flow_box_update_cursor (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ BOX_PRIV (box)->cursor_child = child;
+ gtk_widget_grab_focus (GTK_WIDGET (child));
+ gtk_widget_queue_draw (GTK_WIDGET (child));
+}
+
+static void
+gtk_flow_box_select_child_internal (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ if (CHILD_PRIV (child)->selected)
+ return;
+
+ if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_NONE)
+ return;
+ if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
+ gtk_flow_box_unselect_all_internal (box);
+
+ gtk_flow_box_child_set_selected (child, TRUE);
+ BOX_PRIV (box)->selected_child = child;
+
+ g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+}
+
+static void
+gtk_flow_box_select_all_between (GtkFlowBox *box,
+ GtkFlowBoxChild *child1,
+ GtkFlowBoxChild *child2,
+ gboolean modify)
+{
+ GSequenceIter *iter, *iter1, *iter2;
+
+ if (child1)
+ iter1 = CHILD_PRIV (child1)->iter;
+ else
+ iter1 = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+
+ if (child2)
+ iter2 = CHILD_PRIV (child2)->iter;
+ else
+ iter2 = g_sequence_get_end_iter (BOX_PRIV (box)->children);
+
+ if (g_sequence_iter_compare (iter2, iter1) < 0)
+ {
+ iter = iter1;
+ iter1 = iter2;
+ iter2 = iter;
+ }
+
+ for (iter = iter1;
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+
+ child = g_sequence_get (iter);
+ if (child_is_visible (child))
+ {
+ if (modify)
+ gtk_flow_box_child_set_selected (GTK_FLOW_BOX_CHILD (child), !CHILD_PRIV (child)->selected);
+ else
+ gtk_flow_box_child_set_selected (GTK_FLOW_BOX_CHILD (child), TRUE);
+ }
+
+ if (g_sequence_iter_compare (iter, iter2) == 0)
+ break;
+ }
+}
+
+static void
+gtk_flow_box_update_selection (GtkFlowBox *box,
+ GtkFlowBoxChild *child,
+ gboolean modify,
+ gboolean extend)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ gtk_flow_box_update_cursor (box, child);
+
+ if (priv->selection_mode == GTK_SELECTION_NONE)
+ return;
+
+ if (priv->selection_mode == GTK_SELECTION_BROWSE)
+ {
+ gtk_flow_box_unselect_all_internal (box);
+ gtk_flow_box_child_set_selected (child, TRUE);
+ priv->selected_child = child;
+ }
+ else if (priv->selection_mode == GTK_SELECTION_SINGLE)
+ {
+ gboolean was_selected;
+
+ was_selected = CHILD_PRIV (child)->selected;
+ gtk_flow_box_unselect_all_internal (box);
+ gtk_flow_box_child_set_selected (child, modify ? !was_selected : TRUE);
+ priv->selected_child = CHILD_PRIV (child)->selected ? child : NULL;
+ }
+ else /* GTK_SELECTION_MULTIPLE */
+ {
+ if (extend)
+ {
+ gtk_flow_box_unselect_all_internal (box);
+ if (priv->selected_child == NULL)
+ {
+ gtk_flow_box_child_set_selected (child, TRUE);
+ priv->selected_child = child;
+ }
+ else
+ gtk_flow_box_select_all_between (box, priv->selected_child, child, FALSE);
+ }
+ else
+ {
+ if (modify)
+ {
+ gtk_flow_box_child_set_selected (child, !CHILD_PRIV (child)->selected);
+ }
+ else
+ {
+ gtk_flow_box_unselect_all_internal (box);
+ gtk_flow_box_child_set_selected (child, !CHILD_PRIV (child)->selected);
+ priv->selected_child = child;
+ }
+ }
+ }
+
+ g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+}
+
+static void
+gtk_flow_box_select_and_activate (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ if (child != NULL)
+ {
+ gtk_flow_box_select_child_internal (box, child);
+ gtk_flow_box_update_cursor (box, child);
+ g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
+ }
+}
+
+/* Focus utilities {{{3 */
+
+static GSequenceIter *
+gtk_flow_box_get_previous_focusable (GtkFlowBox *box,
+ GSequenceIter *iter)
+{
+ GtkFlowBoxChild *child;
+
+ while (!g_sequence_iter_is_begin (iter))
+ {
+ iter = g_sequence_iter_prev (iter);
+ child = g_sequence_get (iter);
+ if (child_is_visible (GTK_WIDGET (child)) &&
+ gtk_widget_is_sensitive (GTK_WIDGET (child)))
+ return iter;
+ }
+
+ return NULL;
+}
+
+static GSequenceIter *
+gtk_flow_box_get_next_focusable (GtkFlowBox *box,
+ GSequenceIter *iter)
+{
+ GtkFlowBoxChild *child;
+
+ while (TRUE)
+ {
+ iter = g_sequence_iter_next (iter);
+ if (g_sequence_iter_is_end (iter))
+ return NULL;
+ child = g_sequence_get (iter);
+ if (child_is_visible (GTK_WIDGET (child)) &&
+ gtk_widget_is_sensitive (GTK_WIDGET (child)))
+ return iter;
+ }
+
+ return NULL;
+}
+
+static GSequenceIter *
+gtk_flow_box_get_first_focusable (GtkFlowBox *box)
+{
+ GSequenceIter *iter;
+ GtkFlowBoxChild *child;
+
+ iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ if (g_sequence_iter_is_end (iter))
+ return NULL;
+
+ child = g_sequence_get (iter);
+ if (child_is_visible (GTK_WIDGET (child)) &&
+ gtk_widget_is_sensitive (GTK_WIDGET (child)))
+ return iter;
+
+ return gtk_flow_box_get_next_focusable (box, iter);
+}
+
+static GSequenceIter *
+gtk_flow_box_get_last_focusable (GtkFlowBox *box)
+{
+ GSequenceIter *iter;
+
+ iter = g_sequence_get_end_iter (BOX_PRIV (box)->children);
+ return gtk_flow_box_get_previous_focusable (box, iter);
+}
+
+
+static GSequenceIter *
+gtk_flow_box_get_above_focusable (GtkFlowBox *box,
+ GSequenceIter *iter)
+{
+ GtkFlowBoxChild *child = NULL;
+ gint i;
+
+ while (TRUE)
+ {
+ i = 0;
+ while (i < BOX_PRIV (box)->cur_children_per_line)
+ {
+ if (g_sequence_iter_is_begin (iter))
+ return NULL;
+ iter = g_sequence_iter_prev (iter);
+ child = g_sequence_get (iter);
+ if (child_is_visible (GTK_WIDGET (child)))
+ i++;
+ }
+ if (child && gtk_widget_get_sensitive (GTK_WIDGET (child)))
+ return iter;
+ }
+
+ return NULL;
+}
+
+static GSequenceIter *
+gtk_flow_box_get_below_focusable (GtkFlowBox *box,
+ GSequenceIter *iter)
+{
+ GtkFlowBoxChild *child = NULL;
+ gint i;
+
+ while (TRUE)
+ {
+ i = 0;
+ while (i < BOX_PRIV (box)->cur_children_per_line)
+ {
+ iter = g_sequence_iter_next (iter);
+ if (g_sequence_iter_is_end (iter))
+ return NULL;
+ child = g_sequence_get (iter);
+ if (child_is_visible (GTK_WIDGET (child)))
+ i++;
+ }
+ if (child && gtk_widget_get_sensitive (GTK_WIDGET (child)))
+ return iter;
+ }
+
+ return NULL;
+}
+
+/* GtkWidget implementation {{{2 */
+
+/* Size allocation {{{3 */
+
+/* Used in columned modes where all items share at least their
+ * equal widths or heights
+ */
+static void
+get_max_item_size (GtkFlowBox *box,
+ GtkOrientation orientation,
+ gint *min_size,
+ gint *nat_size)
+{
+ GSequenceIter *iter;
+ gint max_min_size = 0;
+ gint max_nat_size = 0;
+
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+ gint child_min, child_nat;
+
+ child = g_sequence_get (iter);
+
+ if (!child_is_visible (child))
+ continue;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_width (child, &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_height (child, &child_min, &child_nat);
+
+ max_min_size = MAX (max_min_size, child_min);
+ max_nat_size = MAX (max_nat_size, child_nat);
+ }
+
+ if (min_size)
+ *min_size = max_min_size;
+
+ if (nat_size)
+ *nat_size = max_nat_size;
+}
+
+
+/* Gets the largest minimum/natural size for a given size (used to get
+ * the largest item heights for a fixed item width and the opposite)
+ */
+static void
+get_largest_size_for_opposing_orientation (GtkFlowBox *box,
+ GtkOrientation orientation,
+ gint item_size,
+ gint *min_item_size,
+ gint *nat_item_size)
+{
+ GSequenceIter *iter;
+ gint max_min_size = 0;
+ gint max_nat_size = 0;
+
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+ gint child_min, child_nat;
+
+ child = g_sequence_get (iter);
+
+ if (!child_is_visible (child))
+ continue;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_height_for_width (child,
+ item_size,
+ &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_width_for_height (child,
+ item_size,
+ &child_min, &child_nat);
+
+ max_min_size = MAX (max_min_size, child_min);
+ max_nat_size = MAX (max_nat_size, child_nat);
+ }
+
+ if (min_item_size)
+ *min_item_size = max_min_size;
+
+ if (nat_item_size)
+ *nat_item_size = max_nat_size;
+}
+
+/* Gets the largest minimum/natural size on a single line for a given size
+ * (used to get the largest line heights for a fixed item width and the opposite
+ * while iterating over a list of children, note the new index is returned)
+ */
+static GSequenceIter *
+get_largest_size_for_line_in_opposing_orientation (GtkFlowBox *box,
+ GtkOrientation orientation,
+ GSequenceIter *cursor,
+ gint line_length,
+ GtkRequestedSize *item_sizes,
+ gint extra_pixels,
+ gint *min_item_size,
+ gint *nat_item_size)
+{
+ GSequenceIter *iter;
+ gint max_min_size = 0;
+ gint max_nat_size = 0;
+ gint i;
+
+ i = 0;
+ for (iter = cursor;
+ !g_sequence_iter_is_end (iter) && i < line_length;
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+ gint child_min, child_nat, this_item_size;
+
+ child = g_sequence_get (iter);
+
+ if (!child_is_visible (child))
+ continue;
+
+ /* Distribute the extra pixels to the first children in the line
+ * (could be fancier and spread them out more evenly) */
+ this_item_size = item_sizes[i].minimum_size;
+ if (extra_pixels > 0 && ORIENTATION_ALIGN (box) == GTK_ALIGN_FILL)
+ {
+ this_item_size++;
+ extra_pixels--;
+ }
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_height_for_width (child,
+ this_item_size,
+ &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_width_for_height (child,
+ this_item_size,
+ &child_min, &child_nat);
+
+ max_min_size = MAX (max_min_size, child_min);
+ max_nat_size = MAX (max_nat_size, child_nat);
+
+ i++;
+ }
+
+ if (min_item_size)
+ *min_item_size = max_min_size;
+
+ if (nat_item_size)
+ *nat_item_size = max_nat_size;
+
+ /* Return next item in the list */
+ return iter;
+}
+
+/* fit_aligned_item_requests() helper */
+static gint
+gather_aligned_item_requests (GtkFlowBox *box,
+ GtkOrientation orientation,
+ gint line_length,
+ gint item_spacing,
+ gint n_children,
+ GtkRequestedSize *item_sizes)
+{
+ GSequenceIter *iter;
+ gint i;
+ gint extra_items, natural_line_size = 0;
+
+ extra_items = n_children % line_length;
+
+ i = 0;
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+ GtkAlign item_align;
+ gint child_min, child_nat;
+ gint position;
+
+ child = g_sequence_get (iter);
+
+ if (!child_is_visible (child))
+ continue;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_width (child,
+ &child_min, &child_nat);
+ else
+ gtk_widget_get_preferred_height (child,
+ &child_min, &child_nat);
+
+ /* Get the index and push it over for the last line when spreading to the end */
+ position = i % line_length;
+
+ item_align = ORIENTATION_ALIGN (box);
+ if (item_align == GTK_ALIGN_END && i >= n_children - extra_items)
+ position += line_length - extra_items;
+
+ /* Round up the size of every column/row */
+ item_sizes[position].minimum_size = MAX (item_sizes[position].minimum_size, child_min);
+ item_sizes[position].natural_size = MAX (item_sizes[position].natural_size, child_nat);
+
+ i++;
+ }
+
+ for (i = 0; i < line_length; i++)
+ natural_line_size += item_sizes[i].natural_size;
+
+ natural_line_size += (line_length - 1) * item_spacing;
+
+ return natural_line_size;
+}
+
+static GtkRequestedSize *
+fit_aligned_item_requests (GtkFlowBox *box,
+ GtkOrientation orientation,
+ gint avail_size,
+ gint item_spacing,
+ gint *line_length, /* in-out */
+ gint items_per_line,
+ gint n_children)
+{
+ GtkRequestedSize *sizes, *try_sizes;
+ gint try_line_size, try_length;
+
+ sizes = g_new0 (GtkRequestedSize, *line_length);
+
+ /* get the sizes for the initial guess */
+ try_line_size = gather_aligned_item_requests (box,
+ orientation,
+ *line_length,
+ item_spacing,
+ n_children,
+ sizes);
+
+ /* Try columnizing the whole thing and adding an item to the end of
+ * the line; try to fit as many columns into the available size as
+ * possible
+ */
+ for (try_length = *line_length + 1; try_line_size < avail_size; try_length++)
+ {
+ try_sizes = g_new0 (GtkRequestedSize, try_length);
+ try_line_size = gather_aligned_item_requests (box,
+ orientation,
+ try_length,
+ item_spacing,
+ n_children,
+ try_sizes);
+
+ if (try_line_size <= avail_size &&
+ items_per_line >= try_length)
+ {
+ *line_length = try_length;
+
+ g_free (sizes);
+ sizes = try_sizes;
+ }
+ else
+ {
+ /* oops, this one failed; stick to the last size that fit and then return */
+ g_free (try_sizes);
+ break;
+ }
+ }
+
+ return sizes;
+}
+
+typedef struct {
+ GArray *requested;
+ gint extra_pixels;
+} AllocatedLine;
+
+static gint
+get_offset_pixels (GtkAlign align,
+ gint pixels)
+{
+ gint offset;
+
+ switch (align) {
+ case GTK_ALIGN_START:
+ case GTK_ALIGN_FILL:
+ offset = 0;
+ break;
+ case GTK_ALIGN_CENTER:
+ offset = pixels / 2;
+ break;
+ case GTK_ALIGN_END:
+ offset = pixels;
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return offset;
+}
+
+static void
+gtk_flow_box_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ GtkAllocation child_allocation;
+ gint avail_size, avail_other_size, min_items, item_spacing, line_spacing;
+ GtkAlign item_align;
+ GtkAlign line_align;
+ GdkWindow *window;
+ GtkRequestedSize *line_sizes = NULL;
+ GtkRequestedSize *item_sizes = NULL;
+ gint min_item_size, nat_item_size;
+ gint line_length;
+ gint item_size = 0;
+ gint line_size = 0, min_fixed_line_size = 0, nat_fixed_line_size = 0;
+ gint line_offset, item_offset, n_children, n_lines, line_count;
+ gint extra_pixels = 0, extra_per_item = 0, extra_extra = 0;
+ gint extra_line_pixels = 0, extra_per_line = 0, extra_line_extra = 0;
+ gint i, this_line_size;
+ GSequenceIter *iter;
+
+ child_allocation.x = 0;
+ child_allocation.y = 0;
+ child_allocation.width = 0;
+ child_allocation.height = 0;
+
+ gtk_widget_set_allocation (widget, allocation);
+ window = gtk_widget_get_window (widget);
+ if (window != NULL)
+ gdk_window_move_resize (window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+
+ child_allocation.x = 0;
+ child_allocation.y = 0;
+ child_allocation.width = allocation->width;
+
+ min_items = MAX (1, priv->min_children_per_line);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ avail_size = allocation->width;
+ avail_other_size = allocation->height;
+ item_spacing = priv->column_spacing; line_spacing = priv->row_spacing;
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ avail_size = allocation->height;
+ avail_other_size = allocation->width;
+ item_spacing = priv->row_spacing;
+ line_spacing = priv->column_spacing;
+ }
+
+ item_align = ORIENTATION_ALIGN (box);
+ line_align = OPPOSING_ORIENTATION_ALIGN (box);
+
+ /* Get how many lines we'll be needing to flow */
+ n_children = get_visible_children (box);
+ if (n_children <= 0)
+ return;
+
+ /*
+ * Deal with ALIGNED/HOMOGENEOUS modes first, start with
+ * initial guesses at item/line sizes
+ */
+ get_max_item_size (box, priv->orientation, &min_item_size, &nat_item_size);
+ if (nat_item_size <= 0)
+ return;
+
+ /* By default flow at the natural item width */
+ line_length = avail_size / (nat_item_size + item_spacing);
+
+ /* After the above aproximation, check if we cant fit one more on the line */
+ if (line_length * item_spacing + (line_length + 1) * nat_item_size <= avail_size)
+ line_length++;
+
+ /* Its possible we were allocated just less than the natural width of the
+ * minimum item flow length */
+ line_length = MAX (min_items, line_length);
+ line_length = MIN (line_length, priv->max_children_per_line);
+
+ /* Here we just use the largest height-for-width and use that for the height
+ * of all lines */
+ if (priv->homogeneous)
+ {
+ n_lines = n_children / line_length;
+ if ((n_children % line_length) > 0)
+ n_lines++;
+
+ n_lines = MAX (n_lines, 1);
+
+ /* Now we need the real item allocation size */
+ item_size = (avail_size - (line_length - 1) * item_spacing) / line_length;
+
+ /* Cut out the expand space if we're not distributing any */
+ if (item_align != GTK_ALIGN_FILL)
+ item_size = MIN (item_size, nat_item_size);
+
+ get_largest_size_for_opposing_orientation (box,
+ priv->orientation,
+ item_size,
+ &min_fixed_line_size,
+ &nat_fixed_line_size);
+
+ /* resolve a fixed 'line_size' */
+ line_size = (avail_other_size - (n_lines - 1) * line_spacing) / n_lines;
+
+ if (line_align != GTK_ALIGN_FILL)
+ line_size = MIN (line_size, nat_fixed_line_size);
+
+ /* Get the real extra pixels incase of GTK_ALIGN_START lines */
+ extra_pixels = avail_size - (line_length - 1) * item_spacing - item_size * line_length;
+ extra_line_pixels = avail_other_size - (n_lines - 1) * line_spacing - line_size * n_lines;
+ }
+ else
+ {
+ gboolean first_line = TRUE;
+
+ /* Find the amount of columns that can fit aligned into the available space
+ * and collect their requests.
+ */
+ item_sizes = fit_aligned_item_requests (box,
+ priv->orientation,
+ avail_size,
+ item_spacing,
+ &line_length,
+ priv->max_children_per_line,
+ n_children);
+
+ /* Calculate the number of lines after determining the final line_length */
+ n_lines = n_children / line_length;
+ if ((n_children % line_length) > 0)
+ n_lines++;
+
+ n_lines = MAX (n_lines, 1);
+ line_sizes = g_new0 (GtkRequestedSize, n_lines);
+
+ /* Get the available remaining size */
+ avail_size -= (line_length - 1) * item_spacing;
+ for (i = 0; i < line_length; i++)
+ avail_size -= item_sizes[i].minimum_size;
+
+ /* Perform a natural allocation on the columnized items and get the remaining pixels */
+ if (avail_size > 0)
+ extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
+
+ /* Now that we have the size of each column of items find the size of each individual
+ * line based on the aligned item sizes.
+ */
+
+ for (i = 0, iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter) && i < n_lines;
+ i++)
+ {
+ iter = get_largest_size_for_line_in_opposing_orientation (box,
+ priv->orientation,
+ iter,
+ line_length,
+ item_sizes,
+ extra_pixels,
+ &line_sizes[i].minimum_size,
+ &line_sizes[i].natural_size);
+
+
+ /* Its possible a line is made of completely invisible children */
+ if (line_sizes[i].natural_size > 0)
+ {
+ if (first_line)
+ first_line = FALSE;
+ else
+ avail_other_size -= line_spacing;
+
+ avail_other_size -= line_sizes[i].minimum_size;
+
+ line_sizes[i].data = GINT_TO_POINTER (i);
+ }
+ }
+
+ /* Distribute space among lines naturally */
+ if (avail_other_size > 0)
+ extra_line_pixels = gtk_distribute_natural_allocation (avail_other_size, n_lines, line_sizes);
+ }
+
+ /*
+ * Initial sizes of items/lines guessed at this point,
+ * go on to distribute expand space if needed.
+ */
+
+ priv->cur_children_per_line = line_length;
+
+ /* FIXME: This portion needs to consider which columns
+ * and rows asked for expand space and distribute those
+ * accordingly for the case of ALIGNED allocation.
+ *
+ * If at least one child in a column/row asked for expand;
+ * we should make that row/column expand entirely.
+ */
+
+ /* Calculate expand space per item */
+ if (item_align == GTK_ALIGN_FILL)
+ {
+ extra_per_item = extra_pixels / line_length;
+ extra_extra = extra_pixels % line_length;
+ }
+
+ /* Calculate expand space per line */
+ if (line_align == GTK_ALIGN_FILL)
+ {
+ extra_per_line = extra_line_pixels / n_lines;
+ extra_line_extra = extra_line_pixels % n_lines;
+ }
+
+ /*
+ * Prepare item/line initial offsets and jump into the
+ * real allocation loop.
+ */
+ line_offset = item_offset = 0;
+
+ /* prepend extra space to item_offset/line_offset for SPREAD_END */
+ item_offset += get_offset_pixels (item_align, extra_pixels);
+ line_offset += get_offset_pixels (line_align, extra_line_pixels);
+
+ /* Get the allocation size for the first line */
+ if (priv->homogeneous)
+ this_line_size = line_size;
+ else
+ {
+ this_line_size = line_sizes[0].minimum_size;
+
+ if (line_align == GTK_ALIGN_FILL)
+ {
+ this_line_size += extra_per_line;
+
+ if (extra_line_extra > 0)
+ this_line_size++;
+ }
+ }
+
+ i = 0;
+ line_count = 0;
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+ gint position;
+ gint this_item_size;
+
+ child = g_sequence_get (iter);
+
+ if (!child_is_visible (child))
+ continue;
+
+ /* Get item position */
+ position = i % line_length;
+
+ /* adjust the line_offset/count at the beginning of each new line */
+ if (i > 0 && position == 0)
+ {
+ /* Push the line_offset */
+ line_offset += this_line_size + line_spacing;
+
+ line_count++;
+
+ /* Get the new line size */
+ if (priv->homogeneous)
+ this_line_size = line_size;
+ else
+ {
+ this_line_size = line_sizes[line_count].minimum_size;
+
+ if (line_align == GTK_ALIGN_FILL)
+ {
+ this_line_size += extra_per_line;
+
+ if (line_count < extra_line_extra)
+ this_line_size++;
+ }
+ }
+
+ item_offset = 0;
+
+ if (item_align == GTK_ALIGN_CENTER)
+ {
+ item_offset += get_offset_pixels (item_align, extra_pixels);
+ }
+ else if (item_align == GTK_ALIGN_END)
+ {
+ item_offset += get_offset_pixels (item_align, extra_pixels);
+
+ /* If we're on the last line, prepend the space for
+ * any leading items */
+ if (line_count == n_lines -1)
+ {
+ gint extra_items = n_children % line_length;
+
+ if (priv->homogeneous)
+ {
+ item_offset += item_size * (line_length - extra_items);
+ item_offset += item_spacing * (line_length - extra_items);
+ }
+ else
+ {
+ gint j;
+
+ for (j = 0; j < (line_length - extra_items); j++)
+ {
+ item_offset += item_sizes[j].minimum_size;
+ item_offset += item_spacing;
+ }
+ }
+ }
+ }
+ }
+
+ /* Push the index along for the last line when spreading to the end */
+ if (item_align == GTK_ALIGN_END && line_count == n_lines -1)
+ {
+ gint extra_items = n_children % line_length;
+
+ position += line_length - extra_items;
+ }
+
+ if (priv->homogeneous)
+ this_item_size = item_size;
+ else
+ this_item_size = item_sizes[position].minimum_size;
+
+ if (item_align == GTK_ALIGN_FILL)
+ {
+ this_item_size += extra_per_item;
+
+ if (position < extra_extra)
+ this_item_size++;
+ }
+
+ /* Do the actual allocation */
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ child_allocation.x = item_offset;
+ child_allocation.y = line_offset;
+ child_allocation.width = this_item_size;
+ child_allocation.height = this_line_size;
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ child_allocation.x = line_offset;
+ child_allocation.y = item_offset;
+ child_allocation.width = this_line_size;
+ child_allocation.height = this_item_size;
+ }
+
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;
+ gtk_widget_size_allocate (child, &child_allocation);
+
+ item_offset += this_item_size;
+ item_offset += item_spacing;
+
+ i++;
+ }
+
+ g_free (item_sizes);
+ g_free (line_sizes);
+}
+
+static GtkSizeRequestMode
+gtk_flow_box_get_request_mode (GtkWidget *widget)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+
+ return (BOX_PRIV (box)->orientation == GTK_ORIENTATION_HORIZONTAL) ?
+ GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH : GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
+}
+
+/* Gets the largest minimum and natural length of
+ * 'line_length' consecutive items when aligned into rows/columns */
+static void
+get_largest_aligned_line_length (GtkFlowBox *box,
+ GtkOrientation orientation,
+ gint line_length,
+ gint *min_size,
+ gint *nat_size)
+{
+ GSequenceIter *iter;
+ gint max_min_size = 0;
+ gint max_nat_size = 0;
+ gint spacing, i;
+ GtkRequestedSize *aligned_item_sizes;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ spacing = BOX_PRIV (box)->column_spacing;
+ else
+ spacing = BOX_PRIV (box)->row_spacing;
+
+ aligned_item_sizes = g_new0 (GtkRequestedSize, line_length);
+
+ /* Get the largest sizes of each index in the line.
+ */
+ i = 0;
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+ gint child_min, child_nat;
+
+ child = g_sequence_get (iter);
+ if (!child_is_visible (child))
+ continue;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ gtk_widget_get_preferred_width (child,
+ &child_min, &child_nat);
+ else /* GTK_ORIENTATION_VERTICAL */
+ gtk_widget_get_preferred_height (child,
+ &child_min, &child_nat);
+
+ aligned_item_sizes[i % line_length].minimum_size =
+ MAX (aligned_item_sizes[i % line_length].minimum_size, child_min);
+
+ aligned_item_sizes[i % line_length].natural_size =
+ MAX (aligned_item_sizes[i % line_length].natural_size, child_nat);
+
+ i++;
+ }
+
+ /* Add up the largest indexes */
+ for (i = 0; i < line_length; i++)
+ {
+ max_min_size += aligned_item_sizes[i].minimum_size;
+ max_nat_size += aligned_item_sizes[i].natural_size;
+ }
+
+ g_free (aligned_item_sizes);
+
+ max_min_size += (line_length - 1) * spacing;
+ max_nat_size += (line_length - 1) * spacing;
+
+ if (min_size)
+ *min_size = max_min_size;
+
+ if (nat_size)
+ *nat_size = max_nat_size;
+}
+
+
+static void
+gtk_flow_box_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ gint min_item_width, nat_item_width;
+ gint min_items, nat_items;
+ gint min_width, nat_width;
+
+ min_items = MAX (1, priv->min_children_per_line);
+ nat_items = MAX (min_items, priv->max_children_per_line);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ min_width = nat_width = 0;
+
+ if (!priv->homogeneous)
+ {
+ /* When not homogeneous; horizontally oriented boxes
+ * need enough width for the widest row */
+ if (min_items == 1)
+ {
+ get_max_item_size (box,
+ GTK_ORIENTATION_HORIZONTAL,
+ &min_item_width,
+ &nat_item_width);
+
+ min_width += min_item_width;
+ nat_width += nat_item_width;
+ }
+ else
+ {
+ gint min_line_length, nat_line_length;
+
+ get_largest_aligned_line_length (box,
+ GTK_ORIENTATION_HORIZONTAL,
+ min_items,
+ &min_line_length,
+ &nat_line_length);
+
+ if (nat_items > min_items)
+ get_largest_aligned_line_length (box,
+ GTK_ORIENTATION_HORIZONTAL,
+ nat_items,
+ NULL,
+ &nat_line_length);
+
+ min_width += min_line_length;
+ nat_width += nat_line_length;
+ }
+ }
+ else /* In homogeneous mode; horizontally oriented boxs
+ * give the same width to all children */
+ {
+ get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL,
+ &min_item_width, &nat_item_width);
+
+ min_width += min_item_width * min_items;
+ min_width += (min_items -1) * priv->column_spacing;
+
+ nat_width += nat_item_width * nat_items;
+ nat_width += (nat_items -1) * priv->column_spacing;
+ }
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ /* Return the width for the minimum height */
+ gint min_height;
+
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, NULL);
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_width_for_height (widget,
+ min_height,
+ &min_width,
+ &nat_width);
+
+ }
+
+ if (minimum_size)
+ *minimum_size = min_width;
+
+ if (natural_size)
+ *natural_size = nat_width;
+}
+
+static void
+gtk_flow_box_get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ gint min_item_height, nat_item_height;
+ gint min_items, nat_items;
+ gint min_height, nat_height;
+
+ min_items = MAX (1, priv->min_children_per_line);
+ nat_items = MAX (min_items, priv->max_children_per_line);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ /* Return the height for the minimum width */
+ gint min_width;
+
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL);
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_height_for_width (widget,
+ min_width,
+ &min_height,
+ &nat_height);
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ min_height = nat_height = 0;
+
+ if (! priv->homogeneous)
+ {
+ /* When not homogeneous; vertically oriented boxes
+ * need enough height for the tallest column */
+ if (min_items == 1)
+ {
+ get_max_item_size (box, GTK_ORIENTATION_VERTICAL,
+ &min_item_height, &nat_item_height);
+
+ min_height += min_item_height;
+ nat_height += nat_item_height;
+ }
+ else
+ {
+ gint min_line_length, nat_line_length;
+
+ get_largest_aligned_line_length (box,
+ GTK_ORIENTATION_VERTICAL,
+ min_items,
+ &min_line_length,
+ &nat_line_length);
+
+ if (nat_items > min_items)
+ get_largest_aligned_line_length (box,
+ GTK_ORIENTATION_VERTICAL,
+ nat_items,
+ NULL,
+ &nat_line_length);
+
+ min_height += min_line_length;
+ nat_height += nat_line_length;
+ }
+
+ }
+ else
+ {
+ /* In homogeneous mode; vertically oriented boxes
+ * give the same height to all children
+ */
+ get_max_item_size (box,
+ GTK_ORIENTATION_VERTICAL,
+ &min_item_height,
+ &nat_item_height);
+
+ min_height += min_item_height * min_items;
+ min_height += (min_items -1) * priv->row_spacing;
+
+ nat_height += nat_item_height * nat_items;
+ nat_height += (nat_items -1) * priv->row_spacing;
+ }
+ }
+
+ if (minimum_size)
+ *minimum_size = min_height;
+
+ if (natural_size)
+ *natural_size = nat_height;
+}
+
+static void
+gtk_flow_box_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ gint min_item_width, nat_item_width;
+ gint min_items;
+ gint min_height, nat_height;
+ gint avail_size, n_children;
+
+ min_items = MAX (1, priv->min_children_per_line);
+
+ min_height = 0;
+ nat_height = 0;
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ gint min_width;
+ gint line_length;
+ gint item_size, extra_pixels;
+
+ n_children = get_visible_children (box);
+ if (n_children <= 0)
+ goto out;
+
+ /* Make sure its no smaller than the minimum */
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, NULL);
+
+ avail_size = MAX (width, min_width);
+ if (avail_size <= 0)
+ goto out;
+
+ get_max_item_size (box, GTK_ORIENTATION_HORIZONTAL, &min_item_width, &nat_item_width);
+ if (nat_item_width <= 0)
+ goto out;
+
+ /* By default flow at the natural item width */
+ line_length = avail_size / (nat_item_width + priv->column_spacing);
+
+ /* After the above aproximation, check if we cant fit one more on the line */
+ if (line_length * priv->column_spacing + (line_length + 1) * nat_item_width <= avail_size)
+ line_length++;
+
+ /* Its possible we were allocated just less than the natural width of the
+ * minimum item flow length
+ */
+ line_length = MAX (min_items, line_length);
+ line_length = MIN (line_length, priv->max_children_per_line);
+
+ /* Now we need the real item allocation size */
+ item_size = (avail_size - (line_length - 1) * priv->column_spacing) / line_length;
+
+ /* Cut out the expand space if we're not distributing any */
+ if (gtk_widget_get_halign (widget) != GTK_ALIGN_FILL)
+ {
+ item_size = MIN (item_size, nat_item_width);
+ extra_pixels = 0;
+ }
+ else
+ /* Collect the extra pixels for expand children */
+ extra_pixels = (avail_size - (line_length - 1) * priv->column_spacing) % line_length;
+
+ if (priv->homogeneous)
+ {
+ gint min_item_height, nat_item_height;
+ gint lines;
+
+ /* Here we just use the largest height-for-width and
+ * add up the size accordingly
+ */
+ get_largest_size_for_opposing_orientation (box,
+ GTK_ORIENTATION_HORIZONTAL,
+ item_size,
+ &min_item_height,
+ &nat_item_height);
+
+ /* Round up how many lines we need to allocate for */
+ lines = n_children / line_length;
+ if ((n_children % line_length) > 0)
+ lines++;
+
+ min_height = min_item_height * lines;
+ nat_height = nat_item_height * lines;
+
+ min_height += (lines - 1) * priv->row_spacing;
+ nat_height += (lines - 1) * priv->row_spacing;
+ }
+ else
+ {
+ gint min_line_height, nat_line_height, i;
+ gboolean first_line = TRUE;
+ GtkRequestedSize *item_sizes;
+ GSequenceIter *iter;
+
+ /* First get the size each set of items take to span the line
+ * when aligning the items above and below after flowping.
+ */
+ item_sizes = fit_aligned_item_requests (box,
+ priv->orientation,
+ avail_size,
+ priv->column_spacing,
+ &line_length,
+ priv->max_children_per_line,
+ n_children);
+
+ /* Get the available remaining size */
+ avail_size -= (line_length - 1) * priv->column_spacing;
+ for (i = 0; i < line_length; i++)
+ avail_size -= item_sizes[i].minimum_size;
+
+ if (avail_size > 0)
+ extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
+
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);)
+ {
+ iter = get_largest_size_for_line_in_opposing_orientation (box,
+ GTK_ORIENTATION_HORIZONTAL,
+ iter,
+ line_length,
+ item_sizes,
+ extra_pixels,
+ &min_line_height,
+ &nat_line_height);
+ /* Its possible the line only had invisible widgets */
+ if (nat_line_height > 0)
+ {
+ if (first_line)
+ first_line = FALSE;
+ else
+ {
+ min_height += priv->row_spacing;
+ nat_height += priv->row_spacing;
+ }
+
+ min_height += min_line_height;
+ nat_height += nat_line_height;
+ }
+ }
+
+ g_free (item_sizes);
+ }
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ /* Return the minimum height */
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, &nat_height);
+ }
+
+ out:
+
+ if (minimum_height)
+ *minimum_height = min_height;
+
+ if (natural_height)
+ *natural_height = nat_height;
+}
+
+static void
+gtk_flow_box_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ gint min_item_height, nat_item_height;
+ gint min_items;
+ gint min_width, nat_width;
+ gint avail_size, n_children;
+
+ min_items = MAX (1, priv->min_children_per_line);
+
+ min_width = 0;
+ nat_width = 0;
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ /* Return the minimum width */
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_width (widget, &min_width, &nat_width);
+ }
+ else /* GTK_ORIENTATION_VERTICAL */
+ {
+ gint min_height;
+ gint line_length;
+ gint item_size, extra_pixels;
+
+ n_children = get_visible_children (box);
+ if (n_children <= 0)
+ goto out;
+
+ /* Make sure its no smaller than the minimum */
+ GTK_WIDGET_GET_CLASS (widget)->get_preferred_height (widget, &min_height, NULL);
+
+ avail_size = MAX (height, min_height);
+ if (avail_size <= 0)
+ goto out;
+
+ get_max_item_size (box, GTK_ORIENTATION_VERTICAL, &min_item_height, &nat_item_height);
+
+ /* By default flow at the natural item width */
+ line_length = avail_size / (nat_item_height + priv->row_spacing);
+
+ /* After the above aproximation, check if we cant fit one more on the line */
+ if (line_length * priv->row_spacing + (line_length + 1) * nat_item_height <= avail_size)
+ line_length++;
+
+ /* Its possible we were allocated just less than the natural width of the
+ * minimum item flow length
+ */
+ line_length = MAX (min_items, line_length);
+ line_length = MIN (line_length, priv->max_children_per_line);
+
+ /* Now we need the real item allocation size */
+ item_size = (avail_size - (line_length - 1) * priv->row_spacing) / line_length;
+
+ /* Cut out the expand space if we're not distributing any */
+ if (gtk_widget_get_valign (widget) != GTK_ALIGN_FILL)
+ {
+ item_size = MIN (item_size, nat_item_height);
+ extra_pixels = 0;
+ }
+ else
+ /* Collect the extra pixels for expand children */
+ extra_pixels = (avail_size - (line_length - 1) * priv->row_spacing) % line_length;
+
+ if (priv->homogeneous)
+ {
+ gint min_item_width, nat_item_width;
+ gint lines;
+
+ /* Here we just use the largest height-for-width and
+ * add up the size accordingly
+ */
+ get_largest_size_for_opposing_orientation (box,
+ GTK_ORIENTATION_VERTICAL,
+ item_size,
+ &min_item_width,
+ &nat_item_width);
+
+ /* Round up how many lines we need to allocate for */
+ n_children = get_visible_children (box);
+ lines = n_children / line_length;
+ if ((n_children % line_length) > 0)
+ lines++;
+
+ min_width = min_item_width * lines;
+ nat_width = nat_item_width * lines;
+
+ min_width += (lines - 1) * priv->column_spacing;
+ nat_width += (lines - 1) * priv->column_spacing;
+ }
+ else
+ {
+ gint min_line_width, nat_line_width, i;
+ gboolean first_line = TRUE;
+ GtkRequestedSize *item_sizes;
+ GSequenceIter *iter;
+
+ /* First get the size each set of items take to span the line
+ * when aligning the items above and below after flowping.
+ */
+ item_sizes = fit_aligned_item_requests (box,
+ priv->orientation,
+ avail_size,
+ priv->row_spacing,
+ &line_length,
+ priv->max_children_per_line,
+ n_children);
+
+ /* Get the available remaining size */
+ avail_size -= (line_length - 1) * priv->column_spacing;
+ for (i = 0; i < line_length; i++)
+ avail_size -= item_sizes[i].minimum_size;
+
+ if (avail_size > 0)
+ extra_pixels = gtk_distribute_natural_allocation (avail_size, line_length, item_sizes);
+
+ for (iter = g_sequence_get_begin_iter (priv->children);
+ !g_sequence_iter_is_end (iter);)
+ {
+ iter = get_largest_size_for_line_in_opposing_orientation (box,
+ GTK_ORIENTATION_VERTICAL,
+ iter,
+ line_length,
+ item_sizes,
+ extra_pixels,
+ &min_line_width,
+ &nat_line_width);
+
+ /* Its possible the last line only had invisible widgets */
+ if (nat_line_width > 0)
+ {
+ if (first_line)
+ first_line = FALSE;
+ else
+ {
+ min_width += priv->column_spacing;
+ nat_width += priv->column_spacing;
+ }
+
+ min_width += min_line_width;
+ nat_width += nat_line_width;
+ }
+ }
+ g_free (item_sizes);
+ }
+ }
+
+ out:
+ if (minimum_width)
+ *minimum_width = min_width;
+
+ if (natural_width)
+ *natural_width = nat_width;
+}
+
+/* Drawing {{{3 */
+
+static gboolean
+gtk_flow_box_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ GtkAllocation allocation = { 0, };
+ GtkStyleContext* context;
+
+ gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
+ context = gtk_widget_get_style_context (GTK_WIDGET (box));
+ gtk_render_background (context, cr, 0, 0, allocation.width, allocation.height);
+
+ GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->draw (widget, cr);
+
+ if (priv->rubberband_first && priv->rubberband_last)
+ {
+ GSequenceIter *iter, *iter1, *iter2;
+ GdkRectangle line_rect, rect;
+ GArray *lines;
+ gboolean vertical;
+
+ vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
+
+ cairo_save (cr);
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+ iter1 = CHILD_PRIV (priv->rubberband_first)->iter;
+ iter2 = CHILD_PRIV (priv->rubberband_last)->iter;
+
+ if (g_sequence_iter_compare (iter2, iter1) < 0)
+ {
+ iter = iter1;
+ iter1 = iter2;
+ iter2 = iter;
+ }
+
+ line_rect.width = 0;
+ lines = g_array_new (FALSE, FALSE, sizeof (GdkRectangle));
+
+ for (iter = iter1;
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ GtkWidget *child;
+
+ child = g_sequence_get (iter);
+ gtk_widget_get_allocation (GTK_WIDGET (child), &rect);
+ if (line_rect.width == 0)
+ line_rect = rect;
+ else
+ {
+ if ((vertical && rect.x == line_rect.x) ||
+ (!vertical && rect.y == line_rect.y))
+ gdk_rectangle_union (&rect, &line_rect, &line_rect);
+ else
+ {
+ g_array_append_val (lines, line_rect);
+ line_rect = rect;
+ }
+ }
+
+ if (g_sequence_iter_compare (iter, iter2) == 0)
+ break;
+ }
+
+ if (line_rect.width != 0)
+ g_array_append_val (lines, line_rect);
+
+ if (lines->len > 0)
+ {
+ GtkStateFlags state;
+ cairo_path_t *path;
+ GtkBorder border;
+ GdkRGBA border_color;
+
+ if (vertical)
+ path_from_vertical_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
+ else
+ path_from_horizontal_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
+
+ /* For some reason we need to copy and reapply the path,
+ * or it gets eaten by gtk_render_background()
+ */
+ path = cairo_copy_path (cr);
+
+ cairo_save (cr);
+ cairo_clip (cr);
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_render_background (context, cr,
+ 0, 0,
+ allocation.width, allocation.height);
+ cairo_restore (cr);
+
+ cairo_append_path (cr, path);
+ cairo_path_destroy (path);
+
+ state = gtk_widget_get_state_flags (widget);
+ gtk_style_context_get_border_color (context, state, &border_color);
+ gtk_style_context_get_border (context, state, &border);
+
+ cairo_set_line_width (cr, border.left);
+ gdk_cairo_set_source_rgba (cr, &border_color);
+ cairo_stroke (cr);
+ }
+ g_array_free (lines, TRUE);
+
+ gtk_style_context_restore (context);
+ cairo_restore (cr);
+ }
+
+ return TRUE;
+}
+
+/* Autoscrolling {{{3 */
+
+static void
+remove_autoscroll (GtkFlowBox *box)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ if (priv->autoscroll_id)
+ {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (box), priv->autoscroll_id);
+ priv->autoscroll_id = 0;
+ }
+
+ priv->autoscroll_mode = GTK_SCROLL_NONE;
+}
+
+static gboolean
+autoscroll_cb (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer data)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (data);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ GtkAdjustment *adjustment;
+ gdouble factor;
+ gdouble increment;
+ gdouble value;
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ adjustment = priv->vadjustment;
+ else
+ adjustment = priv->hadjustment;
+
+ switch (priv->autoscroll_mode)
+ {
+ case GTK_SCROLL_STEP_FORWARD:
+ factor = AUTOSCROLL_FACTOR;
+ break;
+ case GTK_SCROLL_STEP_BACKWARD:
+ factor = - AUTOSCROLL_FACTOR;
+ break;
+ case GTK_SCROLL_PAGE_FORWARD:
+ factor = AUTOSCROLL_FACTOR_FAST;
+ break;
+ case GTK_SCROLL_PAGE_BACKWARD:
+ factor = - AUTOSCROLL_FACTOR_FAST;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ increment = gtk_adjustment_get_step_increment (adjustment) / factor;
+
+ value = gtk_adjustment_get_value (adjustment);
+ value += increment;
+ gtk_adjustment_set_value (adjustment, value);
+
+ if (priv->rubberband_select)
+ {
+ gint x, y;
+ GtkFlowBoxChild *child;
+
+ gdk_window_get_device_position (gtk_widget_get_window (widget),
+ priv->rubberband_device,
+ &x, &y, NULL);
+
+ child = gtk_flow_box_find_child_at_pos (box, x, y);
+
+ gtk_flow_box_update_prelight (box, child);
+ gtk_flow_box_update_active (box, child);
+
+ if (child != NULL)
+ priv->rubberband_last = child;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+add_autoscroll (GtkFlowBox *box)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ if (priv->autoscroll_id != 0 ||
+ priv->autoscroll_mode == GTK_SCROLL_NONE)
+ return;
+
+ priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (box),
+ (GtkTickCallback)autoscroll_cb,
+ box,
+ NULL);
+}
+
+static gboolean
+get_view_rect (GtkFlowBox *box,
+ GdkRectangle *rect)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ GtkWidget *parent;
+ GdkWindow *view;
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (box));
+ if (GTK_IS_VIEWPORT (parent))
+ {
+ view = gtk_viewport_get_view_window (GTK_VIEWPORT (parent));
+ rect->x = gtk_adjustment_get_value (priv->hadjustment);
+ rect->y = gtk_adjustment_get_value (priv->vadjustment);
+ rect->width = gdk_window_get_width (view);
+ rect->height = gdk_window_get_height (view);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+update_autoscroll_mode (GtkFlowBox *box,
+ gint x,
+ gint y)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ GtkScrollType mode = GTK_SCROLL_NONE;
+ GdkRectangle rect;
+ gint size, pos;
+
+ if (priv->rubberband_select && get_view_rect (box, &rect))
+ {
+ if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ size = rect.width;
+ pos = x - rect.x;
+ }
+ else
+ {
+ size = rect.height;
+ pos = y - rect.y;
+ }
+
+ if (pos < 0 - AUTOSCROLL_FAST_DISTANCE)
+ mode = GTK_SCROLL_PAGE_BACKWARD;
+ else if (pos > size + AUTOSCROLL_FAST_DISTANCE)
+ mode = GTK_SCROLL_PAGE_FORWARD;
+ else if (pos < 0)
+ mode = GTK_SCROLL_STEP_BACKWARD;
+ else if (pos > size)
+ mode = GTK_SCROLL_STEP_FORWARD;
+ }
+
+ if (mode != priv->autoscroll_mode)
+ {
+ remove_autoscroll (box);
+ priv->autoscroll_mode = mode;
+ add_autoscroll (box);
+ }
+}
+
+/* Event handling {{{3 */
+
+static gboolean
+gtk_flow_box_enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxChild *child;
+
+ if (event->window != gtk_widget_get_window (GTK_WIDGET (box)))
+ return FALSE;
+
+ child = gtk_flow_box_find_child_at_pos (box, event->x, event->y);
+ gtk_flow_box_update_prelight (box, child);
+ gtk_flow_box_update_active (box, child);
+
+ return FALSE;
+}
+
+static gboolean
+gtk_flow_box_leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxChild *child = NULL;
+
+ if (event->window != gtk_widget_get_window (GTK_WIDGET (box)))
+ return FALSE;
+
+ if (event->detail != GDK_NOTIFY_INFERIOR)
+ child = NULL;
+ else
+ child = gtk_flow_box_find_child_at_pos (box, event->x, event->y);
+
+ gtk_flow_box_update_prelight (box, child);
+ gtk_flow_box_update_active (box, child);
+
+ return FALSE;
+}
+
+static gboolean
+gtk_flow_box_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ GtkFlowBoxChild *child;
+ GdkWindow *window;
+ GdkWindow *event_window;
+ gint relative_x;
+ gint relative_y;
+ gdouble parent_x;
+ gdouble parent_y;
+
+ window = gtk_widget_get_window (GTK_WIDGET (box));
+ event_window = event->window;
+ relative_x = event->x;
+ relative_y = event->y;
+
+ while ((event_window != NULL) && (event_window != window))
+ {
+ gdk_window_coords_to_parent (event_window,
+ relative_x, relative_y,
+ &parent_x, &parent_y);
+ relative_x = parent_x;
+ relative_y = parent_y;
+ event_window = gdk_window_get_effective_parent (event_window);
+ }
+
+ child = gtk_flow_box_find_child_at_pos (box, relative_x, relative_y);
+ gtk_flow_box_update_prelight (box, child);
+ gtk_flow_box_update_active (box, child);
+
+ if (priv->track_motion)
+ {
+ if (!priv->rubberband_select &&
+ (event->x - priv->button_down_x) * (event->x - priv->button_down_x) +
+ (event->y - priv->button_down_y) * (event->y - priv->button_down_y) > RUBBERBAND_START_DISTANCE * RUBBERBAND_START_DISTANCE)
+ {
+ priv->rubberband_select = TRUE;
+ priv->rubberband_first = gtk_flow_box_find_child_at_pos (box, priv->button_down_x, priv->button_down_y);
+
+ /* Grab focus here, so Escape-to-stop-rubberband works */
+ gtk_flow_box_update_cursor (box, priv->rubberband_first);
+ }
+
+ if (priv->rubberband_select)
+ {
+ if (priv->rubberband_first == NULL)
+ priv->rubberband_first = child;
+ if (child != NULL)
+ priv->rubberband_last = child;
+
+ update_autoscroll_mode (box, event->x, event->y);
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_flow_box_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ GtkFlowBoxChild *child;
+
+ if (event->button == GDK_BUTTON_PRIMARY)
+ {
+ child = gtk_flow_box_find_child_at_pos (box, event->x, event->y);
+ if (child != NULL)
+ {
+ priv->active_child = child;
+ priv->active_child_active = TRUE;
+ gtk_widget_queue_draw (GTK_WIDGET (box));
+ if (event->type == GDK_2BUTTON_PRESS &&
+ !priv->activate_on_single_click)
+ {
+ g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
+ return TRUE;
+ }
+ }
+
+ if (priv->selection_mode == GTK_SELECTION_MULTIPLE)
+ {
+ priv->track_motion = TRUE;
+ priv->rubberband_select = FALSE;
+ priv->rubberband_first = NULL;
+ priv->rubberband_last = NULL;
+ priv->button_down_x = event->x;
+ priv->button_down_y = event->y;
+ priv->rubberband_device = gdk_event_get_device ((GdkEvent*)event);
+ get_current_selection_modifiers (widget, &priv->rubberband_modify, &priv->rubberband_extend);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+gtk_flow_box_stop_rubberband (GtkFlowBox *box)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ priv->rubberband_select = FALSE;
+ priv->rubberband_first = NULL;
+ priv->rubberband_last = NULL;
+ priv->rubberband_device = NULL;
+
+ remove_autoscroll (box);
+
+ gtk_widget_queue_draw (GTK_WIDGET (box));
+}
+
+static gboolean
+gtk_flow_box_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ if (event->button == GDK_BUTTON_PRIMARY)
+ {
+ if (priv->active_child != NULL && priv->active_child_active)
+ {
+ if (priv->activate_on_single_click)
+ gtk_flow_box_select_and_activate (box, priv->active_child);
+ else
+ {
+ gboolean modify;
+ gboolean extend;
+ GdkDevice *device;
+
+ get_current_selection_modifiers (widget, &modify, &extend);
+
+ /* With touch, we default to modifying the selection.
+ * You can still clear the selection and start over
+ * by holding Ctrl.
+ */
+ device = gdk_event_get_source_device ((GdkEvent *)event);
+ if (gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN)
+ modify = !modify;
+
+ gtk_flow_box_update_selection (box, priv->active_child, modify, extend);
+ }
+ }
+
+ priv->active_child = NULL;
+ priv->active_child_active = FALSE;
+ gtk_widget_queue_draw (GTK_WIDGET (box));
+ }
+
+ priv->track_motion = FALSE;
+ if (priv->rubberband_select)
+ {
+ if (!priv->rubberband_extend && !priv->rubberband_modify)
+ gtk_flow_box_unselect_all_internal (box);
+ gtk_flow_box_select_all_between (box, priv->rubberband_first, priv->rubberband_last, priv->rubberband_modify);
+
+ gtk_flow_box_stop_rubberband (box);
+
+ g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_flow_box_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ if (priv->rubberband_select)
+ {
+ if (event->keyval == GDK_KEY_Escape)
+ {
+ gtk_flow_box_stop_rubberband (box);
+ return TRUE;
+ }
+ }
+
+ return GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->key_press_event (widget, event);
+}
+
+static void
+gtk_flow_box_grab_notify (GtkWidget *widget,
+ gboolean was_grabbed)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ if (!was_grabbed)
+ {
+ if (priv->rubberband_select)
+ gtk_flow_box_stop_rubberband (box);
+ }
+}
+
+/* Realize and map {{{3 */
+
+static void
+gtk_flow_box_realize (GtkWidget *widget)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkAllocation allocation;
+ GdkWindowAttr attributes = {0};
+ GdkWindow *window;
+
+ gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
+ gtk_widget_set_realized (GTK_WIDGET (box), TRUE);
+
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (box))
+ | GDK_ENTER_NOTIFY_MASK
+ | GDK_LEAVE_NOTIFY_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_EXPOSURE_MASK
+ | GDK_KEY_PRESS_MASK
+ | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+
+ window = gdk_window_new (gtk_widget_get_parent_window (GTK_WIDGET (box)),
+ &attributes, GDK_WA_X | GDK_WA_Y);
+ gtk_widget_register_window (GTK_WIDGET (box), window);
+ gtk_widget_set_window (GTK_WIDGET (box), window);
+ gtk_style_context_set_background (gtk_widget_get_style_context (GTK_WIDGET (box)), window);
+}
+
+static void
+gtk_flow_box_unmap (GtkWidget *widget)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+
+ remove_autoscroll (box);
+
+ GTK_WIDGET_CLASS (gtk_flow_box_parent_class)->unmap (widget);
+}
+
+/* GtkContainer implementation {{{2 */
+
+static void
+gtk_flow_box_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ gtk_flow_box_insert (GTK_FLOW_BOX (container), child, -1);
+}
+
+static void
+gtk_flow_box_remove (GtkContainer *container,
+ GtkWidget *widget)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (container);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ gboolean was_visible;
+ gboolean was_selected;
+ GtkFlowBoxChild *child;
+
+ if (GTK_IS_FLOW_BOX_CHILD (widget))
+ child = GTK_FLOW_BOX_CHILD (widget);
+ else
+ {
+ child = (GtkFlowBoxChild*)gtk_widget_get_parent (widget);
+ if (!GTK_IS_FLOW_BOX_CHILD (child))
+ {
+ g_warning ("Tried to remove non-child %p\n", widget);
+ return;
+ }
+ }
+
+ was_visible = child_is_visible (GTK_WIDGET (child));
+ was_selected = CHILD_PRIV (child)->selected;
+
+ if (child == priv->prelight_child)
+ priv->prelight_child = NULL;
+ if (child == priv->active_child)
+ priv->active_child = NULL;
+ if (child == priv->selected_child)
+ priv->selected_child = NULL;
+
+ gtk_widget_unparent (GTK_WIDGET (child));
+ g_sequence_remove (CHILD_PRIV (child)->iter);
+
+ if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box)))
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+
+ if (was_selected)
+ g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+}
+
+static void
+gtk_flow_box_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_target)
+{
+ GSequenceIter *iter;
+ GtkWidget *child;
+
+ iter = g_sequence_get_begin_iter (BOX_PRIV (container)->children);
+ while (!g_sequence_iter_is_end (iter))
+ {
+ child = g_sequence_get (iter);
+ iter = g_sequence_iter_next (iter);
+ callback (child, callback_target);
+ }
+}
+
+static GType
+gtk_flow_box_child_type (GtkContainer *container)
+{
+ return GTK_TYPE_FLOW_BOX_CHILD;
+}
+
+/* Keynav {{{2 */
+
+static gboolean
+gtk_flow_box_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (widget);
+ GtkWidget *focus_child;
+ GSequenceIter *iter;
+ GtkFlowBoxChild *next_focus_child;
+
+ focus_child = gtk_container_get_focus_child (GTK_CONTAINER (box));
+ next_focus_child = NULL;
+
+ if (focus_child != NULL)
+ {
+ if (gtk_widget_child_focus (focus_child, direction))
+ return TRUE;
+
+ iter = CHILD_PRIV (focus_child)->iter;
+
+ if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
+ iter = gtk_flow_box_get_previous_focusable (box, iter);
+ else if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD)
+ iter = gtk_flow_box_get_next_focusable (box, iter);
+ else if (direction == GTK_DIR_UP)
+ iter = gtk_flow_box_get_above_focusable (box, iter);
+ else if (direction == GTK_DIR_DOWN)
+ iter = gtk_flow_box_get_below_focusable (box, iter);
+
+ if (iter != NULL)
+ next_focus_child = g_sequence_get (iter);
+ }
+ else
+ {
+ if (BOX_PRIV (box)->selected_child)
+ next_focus_child = BOX_PRIV (box)->selected_child;
+ else
+ {
+ if (direction == GTK_DIR_UP || direction == GTK_DIR_TAB_BACKWARD)
+ iter = gtk_flow_box_get_last_focusable (box);
+ else
+ iter = gtk_flow_box_get_first_focusable (box);
+
+ if (iter != NULL)
+ next_focus_child = g_sequence_get (iter);
+ }
+ }
+
+ if (next_focus_child == NULL)
+ {
+ if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN ||
+ direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
+ {
+ if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ if (gtk_widget_child_focus (GTK_WIDGET (next_focus_child), direction))
+ return TRUE;
+
+ return TRUE;
+}
+
+static void
+gtk_flow_box_add_move_binding (GtkBindingSet *binding_set,
+ guint keyval,
+ GdkModifierType modmask,
+ GtkMovementStep step,
+ gint count)
+{
+ GdkDisplay *display;
+ GdkModifierType extend_mod_mask = GDK_SHIFT_MASK;
+ GdkModifierType modify_mod_mask = GDK_CONTROL_MASK;
+
+ display = gdk_display_get_default ();
+ if (display)
+ {
+ extend_mod_mask = gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_EXTEND_SELECTION);
+ modify_mod_mask = gdk_keymap_get_modifier_mask (gdk_keymap_get_for_display (display),
+ GDK_MODIFIER_INTENT_MODIFY_SELECTION);
+ }
+
+ gtk_binding_entry_add_signal (binding_set, keyval, modmask,
+ "move-cursor", 2,
+ GTK_TYPE_MOVEMENT_STEP, step,
+ G_TYPE_INT, count,
+ NULL);
+ gtk_binding_entry_add_signal (binding_set, keyval, modmask | extend_mod_mask,
+ "move-cursor", 2,
+ GTK_TYPE_MOVEMENT_STEP, step,
+ G_TYPE_INT, count,
+ NULL);
+ gtk_binding_entry_add_signal (binding_set, keyval, modmask | modify_mod_mask,
+ "move-cursor", 2,
+ GTK_TYPE_MOVEMENT_STEP, step,
+ G_TYPE_INT, count,
+ NULL);
+ gtk_binding_entry_add_signal (binding_set, keyval, modmask | extend_mod_mask | modify_mod_mask,
+ "move-cursor", 2,
+ GTK_TYPE_MOVEMENT_STEP, step,
+ G_TYPE_INT, count,
+ NULL);
+}
+
+static void
+gtk_flow_box_activate_cursor_child (GtkFlowBox *box)
+{
+ gtk_flow_box_select_and_activate (box, BOX_PRIV (box)->cursor_child);
+}
+
+static void
+gtk_flow_box_toggle_cursor_child (GtkFlowBox *box)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ if (priv->cursor_child == NULL)
+ return;
+
+ if ((priv->selection_mode == GTK_SELECTION_SINGLE ||
+ priv->selection_mode == GTK_SELECTION_MULTIPLE) &&
+ CHILD_PRIV (priv->cursor_child)->selected)
+ gtk_flow_box_unselect_child_internal (box, priv->cursor_child);
+ else
+ gtk_flow_box_select_and_activate (box, priv->cursor_child);
+}
+
+static void
+gtk_flow_box_move_cursor (GtkFlowBox *box,
+ GtkMovementStep step,
+ gint count)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+ gboolean modify;
+ gboolean extend;
+ GtkFlowBoxChild *child;
+ GtkFlowBoxChild *prev;
+ GtkFlowBoxChild *next;
+ GtkAllocation allocation;
+ gint page_size;
+ GSequenceIter *iter;
+ gint start;
+ GtkAdjustment *adjustment;
+ gboolean vertical;
+
+ vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
+
+ if (vertical)
+ {
+ switch (step)
+ {
+ case GTK_MOVEMENT_VISUAL_POSITIONS:
+ step = GTK_MOVEMENT_DISPLAY_LINES;
+ break;
+ case GTK_MOVEMENT_DISPLAY_LINES:
+ step = GTK_MOVEMENT_VISUAL_POSITIONS;
+ break;
+ default: ;
+ }
+ }
+
+ child = NULL;
+ switch (step)
+ {
+ case GTK_MOVEMENT_VISUAL_POSITIONS:
+ if (priv->cursor_child != NULL)
+ {
+ iter = CHILD_PRIV (priv->cursor_child)->iter;
+ if (gtk_widget_get_direction (GTK_WIDGET (box)) == GTK_TEXT_DIR_RTL)
+ count = - count;
+
+ while (count < 0 && iter != NULL)
+ {
+ iter = gtk_flow_box_get_previous_focusable (box, iter);
+ count = count + 1;
+ }
+ while (count > 0 && iter != NULL)
+ {
+ iter = gtk_flow_box_get_next_focusable (box, iter);
+ count = count - 1;
+ }
+
+ if (iter != NULL && !g_sequence_iter_is_end (iter))
+ child = g_sequence_get (iter);
+ }
+ break;
+
+ case GTK_MOVEMENT_BUFFER_ENDS:
+ if (count < 0)
+ iter = gtk_flow_box_get_first_focusable (box);
+ else
+ iter = gtk_flow_box_get_last_focusable (box);
+ if (iter != NULL)
+ child = g_sequence_get (iter);
+ break;
+
+ case GTK_MOVEMENT_DISPLAY_LINES:
+ if (priv->cursor_child != NULL)
+ {
+ iter = CHILD_PRIV (priv->cursor_child)->iter;
+
+ while (count < 0 && iter != NULL)
+ {
+ iter = gtk_flow_box_get_above_focusable (box, iter);
+ count = count + 1;
+ }
+ while (count > 0 && iter != NULL)
+ {
+ iter = gtk_flow_box_get_below_focusable (box, iter);
+ count = count - 1;
+ }
+
+ if (iter != NULL)
+ child = g_sequence_get (iter);
+ }
+ break;
+
+ case GTK_MOVEMENT_PAGES:
+ page_size = 100;
+ adjustment = vertical ? priv->hadjustment : priv->vadjustment;
+ if (adjustment)
+ page_size = gtk_adjustment_get_page_increment (adjustment);
+
+ if (priv->cursor_child != NULL)
+ {
+ child = priv->cursor_child;
+ iter = CHILD_PRIV (child)->iter;
+ gtk_widget_get_allocation (GTK_WIDGET (child), &allocation);
+ start = vertical ? allocation.x : allocation.y;
+
+ if (count < 0)
+ {
+ gint i = 0;
+
+ /* Up */
+ while (iter != NULL)
+ {
+ iter = gtk_flow_box_get_previous_focusable (box, iter);
+ if (iter == NULL)
+ break;
+
+ prev = g_sequence_get (iter);
+
+ /* go up an even number of rows */
+ if (i % priv->cur_children_per_line == 0)
+ {
+ gtk_widget_get_allocation (GTK_WIDGET (prev), &allocation);
+ if ((vertical ? allocation.x : allocation.y) < start - page_size)
+ break;
+ }
+
+ child = prev;
+ i++;
+ }
+ }
+ else
+ {
+ gint i = 0;
+
+ /* Down */
+ while (!g_sequence_iter_is_end (iter))
+ {
+ iter = gtk_flow_box_get_next_focusable (box, iter);
+ if (g_sequence_iter_is_end (iter))
+ break;
+
+ next = g_sequence_get (iter);
+
+ if (i % priv->cur_children_per_line == 0)
+ {
+ gtk_widget_get_allocation (GTK_WIDGET (next), &allocation);
+ if ((vertical ? allocation.x : allocation.y) > start + page_size)
+ break;
+ }
+
+ child = next;
+ i++;
+ }
+ }
+ gtk_widget_get_allocation (GTK_WIDGET (child), &allocation);
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ if (child == NULL || child == priv->cursor_child)
+ {
+ GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN;
+
+ if (!gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box));
+
+ if (toplevel)
+ gtk_widget_child_focus (toplevel,
+ direction == GTK_DIR_UP ?
+ GTK_DIR_TAB_BACKWARD :
+ GTK_DIR_TAB_FORWARD);
+
+ }
+
+ return;
+ }
+
+ get_current_selection_modifiers (GTK_WIDGET (box), &modify, &extend);
+
+ gtk_flow_box_update_cursor (box, child);
+ if (!modify)
+ gtk_flow_box_update_selection (box, child, FALSE, extend);
+}
+
+/* Selection {{{2 */
+
+static void
+gtk_flow_box_selected_children_changed (GtkFlowBox *box)
+{
+}
+
+/* GObject implementation {{{2 */
+
+static void
+gtk_flow_box_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (object);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, priv->orientation);
+ break;
+ case PROP_HOMOGENEOUS:
+ g_value_set_boolean (value, priv->homogeneous);
+ break;
+ case PROP_COLUMN_SPACING:
+ g_value_set_uint (value, priv->column_spacing);
+ break;
+ case PROP_ROW_SPACING:
+ g_value_set_uint (value, priv->row_spacing);
+ break;
+ case PROP_MIN_CHILDREN_PER_LINE:
+ g_value_set_uint (value, priv->min_children_per_line);
+ break;
+ case PROP_MAX_CHILDREN_PER_LINE:
+ g_value_set_uint (value, priv->max_children_per_line);
+ break;
+ case PROP_SELECTION_MODE:
+ g_value_set_enum (value, priv->selection_mode);
+ break;
+ case PROP_ACTIVATE_ON_SINGLE_CLICK:
+ g_value_set_boolean (value, priv->activate_on_single_click);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_flow_box_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkFlowBox *box = GTK_FLOW_BOX (object);
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ priv->orientation = g_value_get_enum (value);
+ /* Re-box the children in the new orientation */
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ break;
+ case PROP_HOMOGENEOUS:
+ gtk_flow_box_set_homogeneous (box, g_value_get_boolean (value));
+ break;
+ case PROP_COLUMN_SPACING:
+ gtk_flow_box_set_column_spacing (box, g_value_get_uint (value));
+ break;
+ case PROP_ROW_SPACING:
+ gtk_flow_box_set_row_spacing (box, g_value_get_uint (value));
+ break;
+ case PROP_MIN_CHILDREN_PER_LINE:
+ gtk_flow_box_set_min_children_per_line (box, g_value_get_uint (value));
+ break;
+ case PROP_MAX_CHILDREN_PER_LINE:
+ gtk_flow_box_set_max_children_per_line (box, g_value_get_uint (value));
+ break;
+ case PROP_SELECTION_MODE:
+ gtk_flow_box_set_selection_mode (box, g_value_get_enum (value));
+ break;
+ case PROP_ACTIVATE_ON_SINGLE_CLICK:
+ gtk_flow_box_set_activate_on_single_click (box, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_flow_box_finalize (GObject *obj)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (obj);
+
+ if (priv->filter_destroy != NULL)
+ priv->filter_destroy (priv->filter_data);
+ if (priv->sort_destroy != NULL)
+ priv->sort_destroy (priv->sort_data);
+
+ g_sequence_free (priv->children);
+ g_clear_object (&priv->hadjustment);
+ g_clear_object (&priv->vadjustment);
+
+ G_OBJECT_CLASS (gtk_flow_box_parent_class)->finalize (obj);
+}
+
+static void
+gtk_flow_box_class_init (GtkFlowBoxClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
+ GtkBindingSet *binding_set;
+
+ object_class->finalize = gtk_flow_box_finalize;
+ object_class->get_property = gtk_flow_box_get_property;
+ object_class->set_property = gtk_flow_box_set_property;
+
+ widget_class->enter_notify_event = gtk_flow_box_enter_notify_event;
+ widget_class->leave_notify_event = gtk_flow_box_leave_notify_event;
+ widget_class->motion_notify_event = gtk_flow_box_motion_notify_event;
+ widget_class->size_allocate = gtk_flow_box_size_allocate;
+ widget_class->realize = gtk_flow_box_realize;
+ widget_class->unmap = gtk_flow_box_unmap;
+ widget_class->focus = gtk_flow_box_focus;
+ widget_class->draw = gtk_flow_box_draw;
+ widget_class->button_press_event = gtk_flow_box_button_press_event;
+ widget_class->button_release_event = gtk_flow_box_button_release_event;
+ widget_class->key_press_event = gtk_flow_box_key_press_event;
+ widget_class->grab_notify = gtk_flow_box_grab_notify;
+ widget_class->get_request_mode = gtk_flow_box_get_request_mode;
+ widget_class->get_preferred_width = gtk_flow_box_get_preferred_width;
+ widget_class->get_preferred_height = gtk_flow_box_get_preferred_height;
+ widget_class->get_preferred_height_for_width = gtk_flow_box_get_preferred_height_for_width;
+ widget_class->get_preferred_width_for_height = gtk_flow_box_get_preferred_width_for_height;
+
+ container_class->add = gtk_flow_box_add;
+ container_class->remove = gtk_flow_box_remove;
+ container_class->forall = gtk_flow_box_forall;
+ container_class->child_type = gtk_flow_box_child_type;
+ gtk_container_class_handle_border_width (container_class);
+
+ class->activate_cursor_child = gtk_flow_box_activate_cursor_child;
+ class->toggle_cursor_child = gtk_flow_box_toggle_cursor_child;
+ class->move_cursor = gtk_flow_box_move_cursor;
+ class->select_all = gtk_flow_box_select_all;
+ class->unselect_all = gtk_flow_box_unselect_all;
+ class->selected_children_changed = gtk_flow_box_selected_children_changed;
+
+ g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
+
+ /**
+ * GtkFlowBox:selection-mode:
+ *
+ * The selection mode used by the flow box.
+ */
+ g_object_class_install_property (object_class,
+ PROP_SELECTION_MODE,
+ g_param_spec_enum ("selection-mode",
+ "Selection mode",
+ "The selection mode",
+ GTK_TYPE_SELECTION_MODE,
+ GTK_SELECTION_SINGLE,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtkFlowBox:activate-on-single-click:
+ *
+ * Determines whether children can be activated with a single
+ * click, or require a double-click.
+ */
+ g_object_class_install_property (object_class,
+ PROP_ACTIVATE_ON_SINGLE_CLICK,
+ g_param_spec_boolean ("activate-on-single-click",
+ "Activate on Single Click",
+ "Activate row on a single click",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtkFlowBox:homogeneous:
+ *
+ * Determines whether all children should be allocated the
+ * same size.
+ */
+ g_object_class_install_property (object_class,
+ PROP_HOMOGENEOUS,
+ g_param_spec_boolean ("homogeneous",
+ "Homogeneous",
+ "Whether the children should all be the same size",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtkFlowBox:min-children-per-line:
+ *
+ * The minimum number of children to allocate consecutively
+ * in the given orientation.
+ *
+ * Setting the minimum children per line ensures
+ * that a reasonably small height will be requested
+ * for the overall minimum width of the box.
+ */
+ g_object_class_install_property (object_class,
+ PROP_MIN_CHILDREN_PER_LINE,
+ g_param_spec_uint ("min-children-per-line",
+ "Minimum Children Per Line",
+ "The minimum number of children to allocate "
+ "consecutively in the given orientation.",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtkFlowBox:max-children-per-line:
+ *
+ * The maximum amount of children to request space for consecutively
+ * in the given orientation.
+ */
+ g_object_class_install_property (object_class,
+ PROP_MAX_CHILDREN_PER_LINE,
+ g_param_spec_uint ("max-children-per-line",
+ "Maximum Children Per Line",
+ "The maximum amount of children to request space for "
+ "consecutively in the given orientation.",
+ 0,
+ G_MAXUINT,
+ DEFAULT_MAX_CHILDREN_PER_LINE,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtkFlowBox:row-spacing:
+ *
+ * The amount of vertical space between two children.
+ */
+ g_object_class_install_property (object_class,
+ PROP_ROW_SPACING,
+ g_param_spec_uint ("row-spacing",
+ "Vertical spacing",
+ "The amount of vertical space between two children",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtkFlowBox:column-spacing:
+ *
+ * The amount of horizontal space between two children.
+ */
+ g_object_class_install_property (object_class,
+ PROP_COLUMN_SPACING,
+ g_param_spec_uint ("column-spacing",
+ "Horizontal spacing",
+ "The amount of horizontal space between two children",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE));
+
+ /**
+ * GtkFlowBox::child-activated:
+ * @box: the #GtkFlowBox on which the signal is emitted
+ * @child: the child that is activated
+ *
+ * The ::child-activated signal is emitted when a child has been
+ * activated by the user.
+ */
+ signals[CHILD_ACTIVATED] = g_signal_new ("child-activated",
+ GTK_TYPE_FLOW_BOX,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GtkFlowBoxClass, child_activated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_FLOW_BOX_CHILD);
+
+ /**
+ * GtkFlowBox::selected-children-changed:
+ * @box: the #GtkFlowBox on wich the signal is emitted
+ *
+ * The ::selected-children-changed signal is emitted when the
+ * set of selected children changes.
+ *
+ * Use gtk_flow_box_selected_foreach() or
+ * gtk_flow_box_get_selected_children() to obtain the
+ * selected children.
+ */
+ signals[SELECTED_CHILDREN_CHANGED] = g_signal_new ("selected-children-changed",
+ GTK_TYPE_FLOW_BOX,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GtkFlowBoxClass, selected_children_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GtkFlowBox::activate-cursor-child:
+ * @box: the #GtkFlowBox on which the signal is emitted
+ *
+ * The ::activate-cursor-child signal is a
+ * [keybinding signal][GtkBindingSignal]
+ * which gets emitted when the user activates the @box.
+ */
+ signals[ACTIVATE_CURSOR_CHILD] = g_signal_new ("activate-cursor-child",
+ GTK_TYPE_FLOW_BOX,
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkFlowBoxClass, activate_cursor_child),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GtkFlowBox::toggle-cursor-child:
+ * @box: the #GtkFlowBox on which the signal is emitted
+ *
+ * The ::toggle-cursor-child signal is a
+ * [keybinding signal][GtkBindingSignal]
+ * which toggles the selection of the child that has the focus.
+ *
+ * The default binding for this signal is Ctrl-Space.
+ */
+ signals[TOGGLE_CURSOR_CHILD] = g_signal_new ("toggle-cursor-child",
+ GTK_TYPE_FLOW_BOX,
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkFlowBoxClass, toggle_cursor_child),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GtkFlowBox::move-cursor:
+ * @box: the #GtkFlowBox on which the signal is emitted
+ * @step: the granularity fo the move, as a #GtkMovementStep
+ * @count: the number of @step units to move
+ *
+ * The ::move-cursor signal is a
+ * [keybinding signal][GtkBindingSignal]
+ * which gets emitted when the user initiates a cursor movement.
+ * If the cursor is not visible in @text_view, this signal causes
+ * the viewport to be moved instead.
+ *
+ * Applications should not connect to it, but may emit it with
+ * g_signal_emit_by_name() if they need to control the cursor
+ * programmatically.
+ *
+ * The default bindings for this signal come in two variants,
+ * the variant with the Shift modifier extends the selection,
+ * the variant without the Shift modifer does not.
+ * There are too many key combinations to list them all here.
+ * - Arrow keys move by individual children
+ * - Home/End keys move to the ends of the box
+ * - PageUp/PageDown keys move vertically by pages
+ */
+ signals[MOVE_CURSOR] = g_signal_new ("move-cursor",
+ GTK_TYPE_FLOW_BOX,
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkFlowBoxClass, move_cursor),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT);
+ /**
+ * GtkFlowBox::select-all:
+ * @box: the #GtkFlowBox on which the signal is emitted
+ *
+ * The ::select-all signal is a
+ * [keybinding signal][GtkBindingSignal]
+ * which gets emitted to select all children of the box, if
+ * the selection mode permits it.
+ *
+ * The default bindings for this signal is Ctrl-a.
+ */
+ signals[SELECT_ALL] = g_signal_new ("select-all",
+ GTK_TYPE_FLOW_BOX,
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkFlowBoxClass, select_all),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /**
+ * GtkFlowBox::unselect-all:
+ * @box: the #GtkFlowBox on which the signal is emitted
+ *
+ * The ::unselect-all signal is a
+ * [keybinding signal][GtkBindingSignal]
+ * which gets emitted to unselect all children of the box, if
+ * the selection mode permits it.
+ *
+ * The default bindings for this signal is Ctrl-Shift-a.
+ */
+ signals[UNSELECT_ALL] = g_signal_new ("unselect-all",
+ GTK_TYPE_FLOW_BOX,
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkFlowBoxClass, unselect_all),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ widget_class->activate_signal = signals[ACTIVATE_CURSOR_CHILD];
+
+ binding_set = gtk_binding_set_by_class (class);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Home, 0,
+ GTK_MOVEMENT_BUFFER_ENDS, -1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Home, 0,
+ GTK_MOVEMENT_BUFFER_ENDS, -1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_End, 0,
+ GTK_MOVEMENT_BUFFER_ENDS, 1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_End, 0,
+ GTK_MOVEMENT_BUFFER_ENDS, 1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Up, 0,
+ GTK_MOVEMENT_DISPLAY_LINES, -1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Up, 0,
+ GTK_MOVEMENT_DISPLAY_LINES, -1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Down, 0,
+ GTK_MOVEMENT_DISPLAY_LINES, 1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Down, 0,
+ GTK_MOVEMENT_DISPLAY_LINES, 1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Up, 0,
+ GTK_MOVEMENT_PAGES, -1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
+ GTK_MOVEMENT_PAGES, -1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Down, 0,
+ GTK_MOVEMENT_PAGES, 1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
+ GTK_MOVEMENT_PAGES, 1);
+
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Right, 0,
+ GTK_MOVEMENT_VISUAL_POSITIONS, 1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Right, 0,
+ GTK_MOVEMENT_VISUAL_POSITIONS, 1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_Left, 0,
+ GTK_MOVEMENT_VISUAL_POSITIONS, -1);
+ gtk_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Left, 0,
+ GTK_MOVEMENT_VISUAL_POSITIONS, -1);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
+ "toggle-cursor-child", 0, NULL);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
+ "toggle-cursor-child", 0, NULL);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK,
+ "select-all", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+ "unselect-all", 0);
+}
+
+static void
+gtk_flow_box_init (GtkFlowBox *box)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ gtk_widget_set_has_window (GTK_WIDGET (box), TRUE);
+ gtk_widget_set_redraw_on_allocate (GTK_WIDGET (box), TRUE);
+
+ priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+ priv->selection_mode = GTK_SELECTION_SINGLE;
+ priv->max_children_per_line = DEFAULT_MAX_CHILDREN_PER_LINE;
+ priv->column_spacing = 0;
+ priv->row_spacing = 0;
+ priv->activate_on_single_click = TRUE;
+
+ priv->children = g_sequence_new (NULL);
+}
+
+ /* Public API {{{2 */
+
+/**
+ * gtk_flow_box_new:
+ *
+ * Creates a GtkFlowBox.
+ *
+ * Returns: a new #GtkFlowBox container
+ *
+ * Since: 3.12
+ */
+GtkWidget *
+gtk_flow_box_new (void)
+{
+ return (GtkWidget *)g_object_new (GTK_TYPE_FLOW_BOX, NULL);
+}
+
+/**
+ * gtk_flow_box_insert:
+ * @box: a #GtkFlowBox
+ * @widget: the #GtkWidget to add
+ * @position: the position to insert @child in
+ *
+ * Inserts the @widget into @box at @position.
+ *
+ * If a sort function is set, the widget will actually be inserted
+ * at the calculated position and this function has the same effect
+ * as gtk_container_add().
+ *
+ * If @position is -1, or larger than the total number of children
+ * in the @box, then the @widget will be appended to the end.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_insert (GtkFlowBox *box,
+ GtkWidget *widget,
+ gint position)
+{
+ GtkFlowBoxPrivate *priv;
+ GtkFlowBoxChild *child;
+ GSequenceIter *iter;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ priv = BOX_PRIV (box);
+
+ if (GTK_IS_FLOW_BOX_CHILD (widget))
+ child = GTK_FLOW_BOX_CHILD (widget);
+ else
+ {
+ child = GTK_FLOW_BOX_CHILD (gtk_flow_box_child_new ());
+ gtk_widget_show (GTK_WIDGET (child));
+ gtk_container_add (GTK_CONTAINER (child), widget);
+ }
+
+ if (priv->sort_func != NULL)
+ iter = g_sequence_insert_sorted (priv->children, child,
+ (GCompareDataFunc)gtk_flow_box_sort, box);
+ else if (position == 0)
+ iter = g_sequence_prepend (priv->children, child);
+ else if (position == -1)
+ iter = g_sequence_append (priv->children, child);
+ else
+ {
+ GSequenceIter *pos;
+ pos = g_sequence_get_iter_at_pos (priv->children, position);
+ iter = g_sequence_insert_before (pos, child);
+ }
+
+ CHILD_PRIV (child)->iter = iter;
+ gtk_widget_set_parent (GTK_WIDGET (child), GTK_WIDGET (box));
+ gtk_flow_box_apply_filter (box, child);
+}
+
+/**
+ * gtk_flow_box_get_child_at_index:
+ * @box: a #GtkFlowBox
+ * @idx: the position of the child
+ *
+ * Gets the nth child in the @box.
+ *
+ * Returns: (transfer none): the child widget, which will
+ * always be a #GtkFlowBoxChild
+ *
+ * Since: 3.12
+ */
+GtkFlowBoxChild *
+gtk_flow_box_get_child_at_index (GtkFlowBox *box,
+ gint idx)
+{
+ GSequenceIter *iter;
+
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL);
+
+ iter = g_sequence_get_iter_at_pos (BOX_PRIV (box)->children, idx);
+ if (iter)
+ return g_sequence_get (iter);
+
+ return NULL;
+}
+
+/**
+ * gtk_flow_box_set_hadjustment:
+ * @box: a #GtkFlowBox
+ * @adjustment: an adjustment which should be adjusted
+ * when the focus is moved among the descendents of @container
+ *
+ * Hooks up an adjustment to focus handling in @box.
+ * The adjustment is also used for autoscrolling during
+ * rubberband selection. See gtk_scrolled_window_get_hadjustment()
+ * for a typical way of obtaining the adjustment, and
+ * gtk_flow_box_set_vadjustment()for setting the vertical
+ * adjustment.
+ *
+ * The adjustments have to be in pixel units and in the same
+ * coordinate system as the allocation for immediate children
+ * of the box.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_hadjustment (GtkFlowBox *box,
+ GtkAdjustment *adjustment)
+{
+ GtkFlowBoxPrivate *priv;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+
+ priv = BOX_PRIV (box);
+
+ g_object_ref (adjustment);
+ if (priv->hadjustment)
+ g_object_unref (priv->hadjustment);
+ priv->hadjustment = adjustment;
+ gtk_container_set_focus_hadjustment (GTK_CONTAINER (box), adjustment);
+}
+
+/**
+ * gtk_flow_box_set_vadjustment:
+ * @box: a #GtkFlowBox
+ * @adjustment: an adjustment which should be adjusted
+ * when the focus is moved among the descendents of @container
+ *
+ * Hooks up an adjustment to focus handling in @box.
+ * The adjustment is also used for autoscrolling during
+ * rubberband selection. See gtk_scrolled_window_get_vadjustment()
+ * for a typical way of obtaining the adjustment, and
+ * gtk_flow_box_set_hadjustment()for setting the horizontal
+ * adjustment.
+ *
+ * The adjustments have to be in pixel units and in the same
+ * coordinate system as the allocation for immediate children
+ * of the box.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_vadjustment (GtkFlowBox *box,
+ GtkAdjustment *adjustment)
+{
+ GtkFlowBoxPrivate *priv;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+ g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+
+ priv = BOX_PRIV (box);
+
+ g_object_ref (adjustment);
+ if (priv->vadjustment)
+ g_object_unref (priv->vadjustment);
+ priv->vadjustment = adjustment;
+ gtk_container_set_focus_vadjustment (GTK_CONTAINER (box), adjustment);
+}
+
+/* Setters and getters {{{2 */
+
+/**
+ * gtk_flow_box_get_homogeneous:
+ * @box: a #GtkFlowBox
+ *
+ * Returns whether the box is homogeneous (all children are the
+ * same size). See gtk_box_set_homogeneous().
+ *
+ * Returns: %TRUE if the box is homogeneous.
+ *
+ * Since: 3.12
+ */
+gboolean
+gtk_flow_box_get_homogeneous (GtkFlowBox *box)
+{
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
+
+ return BOX_PRIV (box)->homogeneous;
+}
+
+/**
+ * gtk_flow_box_set_homogeneous:
+ * @box: a #GtkFlowBox
+ * @homogeneous: %TRUE to create equal allotments,
+ * %FALSE for variable allotments
+ *
+ * Sets the #GtkFlowBox:homogeneous property of @box, controlling
+ * whether or not all children of @box are given equal space
+ * in the box.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_homogeneous (GtkFlowBox *box,
+ gboolean homogeneous)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ homogeneous = homogeneous != FALSE;
+
+ if (BOX_PRIV (box)->homogeneous != homogeneous)
+ {
+ BOX_PRIV (box)->homogeneous = homogeneous;
+
+ g_object_notify (G_OBJECT (box), "homogeneous");
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ }
+}
+
+/**
+ * gtk_flow_box_set_row_spacing:
+ * @box: a #GtkFlowBox
+ * @spacing: the spacing to use
+ *
+ * Sets the vertical space to add between children.
+ * See the #GtkFlowBox:row-spacing property.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_row_spacing (GtkFlowBox *box,
+ guint spacing)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ if (BOX_PRIV (box)->row_spacing != spacing)
+ {
+ BOX_PRIV (box)->row_spacing = spacing;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ g_object_notify (G_OBJECT (box), "row-spacing");
+ }
+}
+
+/**
+ * gtk_flow_box_get_row_spacing:
+ * @box: a #GtkFlowBox
+ *
+ * Gets the vertical spacing.
+ *
+ * Returns: the vertical spacing
+ *
+ * Since: 3.12
+ */
+guint
+gtk_flow_box_get_row_spacing (GtkFlowBox *box)
+{
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
+
+ return BOX_PRIV (box)->row_spacing;
+}
+
+/**
+ * gtk_flow_box_set_column_spacing:
+ * @box: a #GtkFlowBox
+ * @spacing: the spacing to use
+ *
+ * Sets the horizontal space to add between children.
+ * See the #GtkFlowBox:column-spacing property.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_column_spacing (GtkFlowBox *box,
+ guint spacing)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ if (BOX_PRIV (box)->column_spacing != spacing)
+ {
+ BOX_PRIV (box)->column_spacing = spacing;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ g_object_notify (G_OBJECT (box), "column-spacing");
+ }
+}
+
+/**
+ * gtk_flow_box_get_column_spacing:
+ * @box: a #GtkFlowBox
+ *
+ * Gets the horizontal spacing.
+ *
+ * Returns: the horizontal spacing
+ *
+ * Since: 3.12
+ */
+guint
+gtk_flow_box_get_column_spacing (GtkFlowBox *box)
+{
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
+
+ return BOX_PRIV (box)->column_spacing;
+}
+
+/**
+ * gtk_flow_box_set_min_children_per_line:
+ * @box: a #GtkFlowBox
+ * @n_children: the minimum number of children per line
+ *
+ * Sets the minimum number of children to line up
+ * in @box’s orientation before flowing.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_min_children_per_line (GtkFlowBox *box,
+ guint n_children)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ if (BOX_PRIV (box)->min_children_per_line != n_children)
+ {
+ BOX_PRIV (box)->min_children_per_line = n_children;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ g_object_notify (G_OBJECT (box), "min-children-per-line");
+ }
+}
+
+/**
+ * gtk_flow_box_get_min_children_per_line:
+ * @box: a #GtkFlowBox
+ *
+ * Gets the minimum number of children per line.
+ *
+ * Returns: the minimum number of children per line
+ *
+ * Since: 3.12
+ */
+guint
+gtk_flow_box_get_min_children_per_line (GtkFlowBox *box)
+{
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
+
+ return BOX_PRIV (box)->min_children_per_line;
+}
+
+/**
+ * gtk_flow_box_set_max_children_per_line:
+ * @box: a #GtkFlowBox
+ * @n_children: the maximum number of children per line
+ *
+ * Sets the maximum number of children to request and
+ * allocate space for in @box’s orientation.
+ *
+ * Setting the maximum number of children per line
+ * limits the overall natural size request to be no more
+ * than @n_children children long in the given orientation.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_max_children_per_line (GtkFlowBox *box,
+ guint n_children)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ if (BOX_PRIV (box)->max_children_per_line != n_children)
+ {
+ BOX_PRIV (box)->max_children_per_line = n_children;
+
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ g_object_notify (G_OBJECT (box), "max-children-per-line");
+ }
+}
+
+/**
+ * gtk_flow_box_get_max_children_per_line:
+ * @box: a #GtkFlowBox
+ *
+ * Gets the maximum number of children per line.
+ *
+ * Returns: the maximum number of children per line
+ *
+ * Since: 3.12
+ */
+guint
+gtk_flow_box_get_max_children_per_line (GtkFlowBox *box)
+{
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
+
+ return BOX_PRIV (box)->max_children_per_line;
+}
+
+/**
+ * gtk_flow_box_set_activate_on_single_click:
+ * @box: a #GtkFlowBox
+ * @single: %TRUE to emit child-activated on a single click
+ *
+ * If @single is %TRUE, children will be activated when you click
+ * on them, otherwise you need to double-click.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_activate_on_single_click (GtkFlowBox *box,
+ gboolean single)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ single = single != FALSE;
+
+ if (BOX_PRIV (box)->activate_on_single_click != single)
+ {
+ BOX_PRIV (box)->activate_on_single_click = single;
+ g_object_notify (G_OBJECT (box), "activate-on-single-click");
+ }
+}
+
+/**
+ * gtk_flow_box_get_activate_on_single_click:
+ * @box: a #GtkFlowBox
+ *
+ * Returns whether children activate on single clicks.
+ *
+ * Returns: %TRUE if children are activated on single click,
+ * %FALSE otherwise
+ *
+ * Since: 3.12
+ */
+gboolean
+gtk_flow_box_get_activate_on_single_click (GtkFlowBox *box)
+{
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), FALSE);
+
+ return BOX_PRIV (box)->activate_on_single_click;
+}
+
+ /* Selection handling {{{2 */
+
+/**
+ * gtk_flow_box_get_selected_children:
+ * @box: a #GtkFlowBox
+ *
+ * Creates a list of all selected children.
+ *
+ * Returns: (element-type GtkFlowBoxChild) (transfer container):
+ * A #GList containing the #GtkWidget for each selected child.
+ * Free with g_list_free() when done.
+ *
+ * Since: 3.12
+ */
+GList *
+gtk_flow_box_get_selected_children (GtkFlowBox *box)
+{
+ GtkFlowBoxChild *child;
+ GSequenceIter *iter;
+ GList *selected = NULL;
+
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), NULL);
+
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ child = g_sequence_get (iter);
+ if (CHILD_PRIV (child)->selected)
+ selected = g_list_prepend (selected, child);
+ }
+
+ return g_list_reverse (selected);
+}
+
+/**
+ * gtk_flow_box_select_child:
+ * @box: a #GtkFlowBox
+ * @child: a child of @box
+ *
+ * Selects a single child of @box, if the selection
+ * mode allows it.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_select_child (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+ g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
+
+ gtk_flow_box_select_child_internal (box, child);
+}
+
+/**
+ * gtk_flow_box_unselect_child:
+ * @box: a #GtkFlowBox
+ * @child: a child of @box
+ *
+ * Unselects a single child of @box, if the selection
+ * mode allows it.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_unselect_child (GtkFlowBox *box,
+ GtkFlowBoxChild *child)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+ g_return_if_fail (GTK_IS_FLOW_BOX_CHILD (child));
+
+ gtk_flow_box_unselect_child_internal (box, child);
+}
+
+/**
+ * gtk_flow_box_select_all:
+ * @box: a #GtkFlowBox
+ *
+ * Select all children of @box, if the selection
+ * mode allows it.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_select_all (GtkFlowBox *box)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
+ return;
+
+ if (g_sequence_get_length (BOX_PRIV (box)->children) > 0)
+ {
+ gtk_flow_box_select_all_between (box, NULL, NULL, FALSE);
+ g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+ }
+}
+
+/**
+ * gtk_flow_box_unselect_all:
+ * @box: a #GtkFlowBox
+ *
+ * Unselect all children of @box, if the selection
+ * mode allows it.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_unselect_all (GtkFlowBox *box)
+{
+ gboolean dirty = FALSE;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_BROWSE)
+ return;
+
+ dirty = gtk_flow_box_unselect_all_internal (box);
+
+ if (dirty)
+ g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+}
+
+/**
+ * GtkFlowBoxForeachFunc:
+ * @box: a #GtkFlowBox
+ * @child: a #GtkFlowBoxChild
+ * @user_data: (closure): user data
+ *
+ * A function used by gtk_flow_box_selected_foreach().
+ * It will be called on every selected child of the @box.
+ *
+ * Since: 3.12
+ */
+
+/**
+ * gtk_flow_box_selected_foreach:
+ * @box: a #GtkFlowBox
+ * @func: (scope call): the function to call for each selected child
+ * @data: user data to pass to the function
+ *
+ * Calls a function for each selected child.
+ *
+ * Note that the selection cannot be modified from within
+ * this function.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_selected_foreach (GtkFlowBox *box,
+ GtkFlowBoxForeachFunc func,
+ gpointer data)
+{
+ GtkFlowBoxChild *child;
+ GSequenceIter *iter;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
+ !g_sequence_iter_is_end (iter);
+ iter = g_sequence_iter_next (iter))
+ {
+ child = g_sequence_get (iter);
+ if (CHILD_PRIV (child)->selected)
+ (*func) (box, child, data);
+ }
+}
+
+/**
+ * gtk_flow_box_set_selection_mode:
+ * @box: a #GtkFlowBox
+ * @mode: the new selection mode
+ *
+ * Sets how selection works in @box.
+ * See #GtkSelectionMode for details.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_selection_mode (GtkFlowBox *box,
+ GtkSelectionMode mode)
+{
+ gboolean dirty = FALSE;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ if (mode == BOX_PRIV (box)->selection_mode)
+ return;
+
+ if (mode == GTK_SELECTION_NONE ||
+ BOX_PRIV (box)->selection_mode == GTK_SELECTION_MULTIPLE)
+ {
+ dirty = gtk_flow_box_unselect_all_internal (box);
+ BOX_PRIV (box)->selected_child = NULL;
+ }
+
+ BOX_PRIV (box)->selection_mode = mode;
+
+ g_object_notify (G_OBJECT (box), "selection-mode");
+
+ if (dirty)
+ g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+}
+
+/**
+ * gtk_flow_box_get_selection_mode:
+ * @box: a #GtkFlowBox
+ *
+ * Gets the selection mode of @box.
+ *
+ * Returns: the #GtkSelectionMode
+ *
+ * Since: 3.12
+ */
+GtkSelectionMode
+gtk_flow_box_get_selection_mode (GtkFlowBox *box)
+{
+ g_return_val_if_fail (GTK_IS_FLOW_BOX (box), GTK_SELECTION_SINGLE);
+
+ return BOX_PRIV (box)->selection_mode;
+}
+
+/* Filtering {{{2 */
+
+/**
+ * GtkFlowBoxFilterFunc:
+ * @child: a #GtkFlowBoxChild that may be filtered
+ * @user_data: (closure): user data
+ *
+ * A function that will be called whenrever a child changes
+ * or is added. It lets you control if the child should be
+ * visible or not.
+ *
+ * Returns: %TRUE if the row should be visible, %FALSE otherwise
+ *
+ * Since: 3.12
+ */
+
+/**
+ * gtk_flow_box_set_filter_func:
+ * @box: a #GtkFlowBox
+ * @filter_func: (closure user_data) (allow-none): callback that
+ * lets you filter which children to show
+ * @user_data: user data passed to @filter_func
+ * @destroy: destroy notifier for @user_data
+ *
+ * By setting a filter function on the @box one can decide dynamically
+ * which of the children to show. For instance, to implement a search
+ * function that only shows the children matching the search terms.
+ *
+ * The @filter_func will be called for each child after the call, and
+ * it will continue to be called each time a child changes (via
+ * gtk_flow_box_child_changed()) or when gtk_flow_box_invalidate_filter()
+ * is called.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_filter_func (GtkFlowBox *box,
+ GtkFlowBoxFilterFunc filter_func,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ GtkFlowBoxPrivate *priv;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ priv = BOX_PRIV (box);
+
+ if (priv->filter_destroy != NULL)
+ priv->filter_destroy (priv->filter_data);
+
+ priv->filter_func = filter_func;
+ priv->filter_data = user_data;
+ priv->filter_destroy = destroy;
+
+ gtk_flow_box_apply_filter_all (box);
+}
+
+/**
+ * gtk_flow_box_invalidate_filter:
+ * @box: a #GtkFlowBox
+ *
+ * Updates the filtering for all children.
+ *
+ * Call this function when the result of the filter
+ * function on the @box is changed due ot an external
+ * factor. For instance, this would be used if the
+ * filter function just looked for a specific search
+ * term, and the entry with the string has changed.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_invalidate_filter (GtkFlowBox *box)
+{
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ if (BOX_PRIV (box)->filter_func != NULL)
+ gtk_flow_box_apply_filter_all (box);
+}
+
+/* Sorting {{{2 */
+
+/**
+ * GtkFlowBoxSortFunc:
+ * @child1: the first child
+ * @child2: the second child
+ * @user_data: (closure): user data
+ *
+ * A function to compare two children to determine which
+ * should come first.
+ *
+ * Returns: < 0 if @child1 should be before @child2, 0 if
+ * the are equal, and > 0 otherwise
+ *
+ * Since: 3.12
+ */
+
+/**
+ * gtk_flow_box_set_sort_func:
+ * @box: a #GtkFlowBox
+ * @sort_func: (closure user_data) (allow-none): the sort function
+ * @user_data: user data passed to @sort_func
+ * @destroy: destroy notifier for @user_data
+ *
+ * By setting a sort function on the @box, one can dynamically
+ * reorder the children of the box, based on the contents of
+ * the children.
+ *
+ * The @sort_func will be called for each child after the call,
+ * and will continue to be called each time a child changes (via
+ * gtk_flow_box_child_changed()) and when gtk_flow_box_invalidate_sort()
+ * is called.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_set_sort_func (GtkFlowBox *box,
+ GtkFlowBoxSortFunc sort_func,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ GtkFlowBoxPrivate *priv;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ priv = BOX_PRIV (box);
+
+ if (priv->sort_destroy != NULL)
+ priv->sort_destroy (priv->sort_data);
+
+ priv->sort_func = sort_func;
+ priv->sort_data = user_data;
+ priv->sort_destroy = destroy;
+
+ gtk_flow_box_invalidate_sort (box);
+}
+
+static gint
+gtk_flow_box_sort (GtkFlowBoxChild *a,
+ GtkFlowBoxChild *b,
+ GtkFlowBox *box)
+{
+ GtkFlowBoxPrivate *priv = BOX_PRIV (box);
+
+ return priv->sort_func (a, b, priv->sort_data);
+}
+
+/**
+ * gtk_flow_box_invalidate_sort:
+ * @box: a #GtkFlowBox
+ *
+ * Updates the sorting for all children.
+ *
+ * Call this when the result of the sort function on
+ * @box is changed due to an external factor.
+ *
+ * Since: 3.12
+ */
+void
+gtk_flow_box_invalidate_sort (GtkFlowBox *box)
+{
+ GtkFlowBoxPrivate *priv;
+
+ g_return_if_fail (GTK_IS_FLOW_BOX (box));
+
+ priv = BOX_PRIV (box);
+
+ if (priv->sort_func != NULL)
+ {
+ g_sequence_sort (priv->children,
+ (GCompareDataFunc)gtk_flow_box_sort, box);
+ gtk_widget_queue_resize (GTK_WIDGET (box));
+ }
+}
+
+/* vim:set foldmethod=marker expandtab: */
diff --git a/src/interface-gtk/gtkflowbox.h b/src/interface-gtk/gtkflowbox.h
new file mode 100644
index 0000000..6f0549f
--- /dev/null
+++ b/src/interface-gtk/gtkflowbox.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2010 Openismus GmbH
+ * Copyright (C) 2013 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+ *
+ * Authors:
+ * Tristan Van Berkom <tristanvb@openismus.com>
+ * Matthias Clasen <mclasen@redhat.com>
+ * William Jon McCann <jmccann@redhat.com>
+ */
+
+#ifndef __GTK_FLOW_BOX_H__
+#define __GTK_FLOW_BOX_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+
+#define GTK_TYPE_FLOW_BOX (gtk_flow_box_get_type ())
+#define GTK_FLOW_BOX(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FLOW_BOX, GtkFlowBox))
+#define GTK_FLOW_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FLOW_BOX, GtkFlowBoxClass))
+#define GTK_IS_FLOW_BOX(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FLOW_BOX))
+#define GTK_IS_FLOW_BOX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FLOW_BOX))
+#define GTK_FLOW_BOX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_FLOW_BOX, GtkFlowBoxClass))
+
+typedef struct _GtkFlowBox GtkFlowBox;
+typedef struct _GtkFlowBoxClass GtkFlowBoxClass;
+
+typedef struct _GtkFlowBoxChild GtkFlowBoxChild;
+typedef struct _GtkFlowBoxChildClass GtkFlowBoxChildClass;
+
+struct _GtkFlowBox
+{
+ GtkContainer container;
+};
+
+struct _GtkFlowBoxClass
+{
+ GtkContainerClass parent_class;
+
+ void (*child_activated) (GtkFlowBox *box,
+ GtkFlowBoxChild *child);
+ void (*selected_children_changed) (GtkFlowBox *box);
+ void (*activate_cursor_child) (GtkFlowBox *box);
+ void (*toggle_cursor_child) (GtkFlowBox *box);
+ void (*move_cursor) (GtkFlowBox *box,
+ GtkMovementStep step,
+ gint count);
+ void (*select_all) (GtkFlowBox *box);
+ void (*unselect_all) (GtkFlowBox *box);
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+ void (*_gtk_reserved5) (void);
+ void (*_gtk_reserved6) (void);
+};
+
+#define GTK_TYPE_FLOW_BOX_CHILD (gtk_flow_box_child_get_type ())
+#define GTK_FLOW_BOX_CHILD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_FLOW_BOX_CHILD, GtkFlowBoxChild))
+#define GTK_FLOW_BOX_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FLOW_BOX_CHILD, GtkFlowBoxChildClass))
+#define GTK_IS_FLOW_BOX_CHILD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_FLOW_BOX_CHILD))
+#define GTK_IS_FLOW_BOX_CHILD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_FLOW_BOX_CHILD))
+#define GTK_FLOW_BOX_CHILD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EG_TYPE_FLOW_BOX_CHILD, GtkFlowBoxChildClass))
+
+struct _GtkFlowBoxChild
+{
+ GtkBin parent_instance;
+};
+
+struct _GtkFlowBoxChildClass
+{
+ GtkBinClass parent_class;
+
+ void (* activate) (GtkFlowBoxChild *child);
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+};
+
+GType gtk_flow_box_child_get_type (void) G_GNUC_CONST;
+GtkWidget* gtk_flow_box_child_new (void);
+gint gtk_flow_box_child_get_index (GtkFlowBoxChild *child);
+gboolean gtk_flow_box_child_is_selected (GtkFlowBoxChild *child);
+void gtk_flow_box_child_changed (GtkFlowBoxChild *child);
+
+
+GType gtk_flow_box_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gtk_flow_box_new (void);
+void gtk_flow_box_set_homogeneous (GtkFlowBox *box,
+ gboolean homogeneous);
+gboolean gtk_flow_box_get_homogeneous (GtkFlowBox *box);
+void gtk_flow_box_set_row_spacing (GtkFlowBox *box,
+ guint spacing);
+guint gtk_flow_box_get_row_spacing (GtkFlowBox *box);
+
+void gtk_flow_box_set_column_spacing (GtkFlowBox *box,
+ guint spacing);
+guint gtk_flow_box_get_column_spacing (GtkFlowBox *box);
+
+void gtk_flow_box_set_min_children_per_line (GtkFlowBox *box,
+ guint n_children);
+guint gtk_flow_box_get_min_children_per_line (GtkFlowBox *box);
+
+void gtk_flow_box_set_max_children_per_line (GtkFlowBox *box,
+ guint n_children);
+guint gtk_flow_box_get_max_children_per_line (GtkFlowBox *box);
+void gtk_flow_box_set_activate_on_single_click (GtkFlowBox *box,
+ gboolean single);
+gboolean gtk_flow_box_get_activate_on_single_click (GtkFlowBox *box);
+
+void gtk_flow_box_insert (GtkFlowBox *box,
+ GtkWidget *widget,
+ gint position);
+GtkFlowBoxChild *gtk_flow_box_get_child_at_index (GtkFlowBox *box,
+ gint idx);
+
+typedef void (* GtkFlowBoxForeachFunc) (GtkFlowBox *box,
+ GtkFlowBoxChild *child,
+ gpointer user_data);
+
+void gtk_flow_box_selected_foreach (GtkFlowBox *box,
+ GtkFlowBoxForeachFunc func,
+ gpointer data);
+GList *gtk_flow_box_get_selected_children (GtkFlowBox *box);
+void gtk_flow_box_select_child (GtkFlowBox *box,
+ GtkFlowBoxChild *child);
+void gtk_flow_box_unselect_child (GtkFlowBox *box,
+ GtkFlowBoxChild *child);
+void gtk_flow_box_select_all (GtkFlowBox *box);
+void gtk_flow_box_unselect_all (GtkFlowBox *box);
+void gtk_flow_box_set_selection_mode (GtkFlowBox *box,
+ GtkSelectionMode mode);
+GtkSelectionMode gtk_flow_box_get_selection_mode (GtkFlowBox *box);
+void gtk_flow_box_set_hadjustment (GtkFlowBox *box,
+ GtkAdjustment *adjustment);
+void gtk_flow_box_set_vadjustment (GtkFlowBox *box,
+ GtkAdjustment *adjustment);
+
+typedef gboolean (*GtkFlowBoxFilterFunc) (GtkFlowBoxChild *child,
+ gpointer user_data);
+
+void gtk_flow_box_set_filter_func (GtkFlowBox *box,
+ GtkFlowBoxFilterFunc filter_func,
+ gpointer user_data,
+ GDestroyNotify destroy);
+void gtk_flow_box_invalidate_filter (GtkFlowBox *box);
+
+typedef gint (*GtkFlowBoxSortFunc) (GtkFlowBoxChild *child1,
+ GtkFlowBoxChild *child2,
+ gpointer user_data);
+
+void gtk_flow_box_set_sort_func (GtkFlowBox *box,
+ GtkFlowBoxSortFunc sort_func,
+ gpointer user_data,
+ GDestroyNotify destroy);
+void gtk_flow_box_invalidate_sort (GtkFlowBox *box);
+
+G_END_DECLS
+
+
+#endif /* __GTK_FLOW_BOX_H__ */
diff --git a/src/interface-gtk/interface-gtk.cpp b/src/interface-gtk/interface-gtk.cpp
new file mode 100644
index 0000000..9b0b0aa
--- /dev/null
+++ b/src/interface-gtk/interface-gtk.cpp
@@ -0,0 +1,759 @@
+/*
+ * Copyright (C) 2012-2016 Robin Haberkorn
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+/*
+ * FIXME: Because of gdk_threads_enter().
+ * The only way to do it in Gtk3 style would be using
+ * idle callbacks into the main thread and sync barriers (inefficient!)
+ * or doing it single-threaded and ticking the Gtk main loop
+ * (may be inefficient since gtk_events_pending() is doing
+ * syscalls; however that may be ailed by doing it less frequently).
+ */
+#define GDK_DISABLE_DEPRECATION_WARNINGS
+#include <gdk/gdk.h>
+
+#include <gtk/gtk.h>
+#include "gtk-info-popup.h"
+
+#include <Scintilla.h>
+#include <ScintillaWidget.h>
+
+#include "sciteco.h"
+#include "string-utils.h"
+#include "cmdline.h"
+#include "qregisters.h"
+#include "ring.h"
+#include "interface.h"
+#include "interface-gtk.h"
+
+/*
+ * Signal handlers (e.g. for handling SIGTERM) are only
+ * available on Unix and beginning with v2.30, while
+ * we still support v2.28.
+ * Handlers using `signal()` cannot be used easily for
+ * this purpose.
+ */
+#if defined(G_OS_UNIX) && GLIB_CHECK_VERSION(2,30,0)
+#include <glib-unix.h>
+#define SCITECO_HANDLE_SIGNALS
+#endif
+
+namespace SciTECO {
+
+extern "C" {
+
+static void scintilla_notify(ScintillaObject *sci, uptr_t idFrom,
+ SCNotification *notify, gpointer user_data);
+
+static gpointer exec_thread_cb(gpointer data);
+static gboolean cmdline_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
+ gpointer user_data);
+static gboolean window_delete_cb(GtkWidget *w, GdkEventAny *e,
+ gpointer user_data);
+
+static gboolean sigterm_handler(gpointer user_data) G_GNUC_UNUSED;
+
+} /* extern "C" */
+
+#define UNNAMED_FILE "(Unnamed)"
+
+void
+ViewGtk::initialize_impl(void)
+{
+ gint events;
+
+ gdk_threads_enter();
+
+ sci = SCINTILLA(scintilla_new());
+ /*
+ * We don't want the object to be destroyed
+ * when it is removed from the vbox.
+ */
+ g_object_ref_sink(G_OBJECT(sci));
+
+ scintilla_set_id(sci, 0);
+
+ gtk_widget_set_size_request(get_widget(), 500, 300);
+
+ /*
+ * This disables mouse and key events on this view.
+ * For some strange reason, masking events on
+ * the event box does NOT work.
+ * NOTE: Scroll events are still allowed - scrolling
+ * is currently not under direct control of SciTECO
+ * (i.e. it is OK the side effects of scrolling are not
+ * tracked).
+ */
+ gtk_widget_set_can_focus(get_widget(), FALSE);
+ events = gtk_widget_get_events(get_widget());
+ events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+ events &= ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
+ gtk_widget_set_events(get_widget(), events);
+
+ g_signal_connect(G_OBJECT(sci), SCINTILLA_NOTIFY,
+ G_CALLBACK(scintilla_notify), NULL);
+
+ /*
+ * setup() calls Scintilla messages, so we must unlock
+ * here already to avoid deadlocks.
+ */
+ gdk_threads_leave();
+
+ setup();
+}
+
+void
+InterfaceGtk::main_impl(int &argc, char **&argv)
+{
+ static const Cmdline empty_cmdline;
+ GtkWidget *overlay_widget;
+ GtkWidget *info_content;
+
+ /*
+ * g_thread_init() is required prior to v2.32
+ * (we still support v2.28) but generates a warning
+ * on newer versions.
+ */
+#if !GLIB_CHECK_VERSION(2,32,0)
+ g_thread_init(NULL);
+#endif
+ gdk_threads_init();
+ gtk_init(&argc, &argv);
+
+ /*
+ * The event queue is initialized now, so we can
+ * pass it as user data to C-linkage callbacks.
+ */
+ event_queue = g_async_queue_new();
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(window), PACKAGE_NAME);
+ g_signal_connect(G_OBJECT(window), "delete-event",
+ G_CALLBACK(window_delete_cb), event_queue);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+
+ info_current = g_strdup(PACKAGE_NAME);
+
+ /*
+ * The event box is the parent of all Scintilla views
+ * that should be displayed.
+ * This is handy when adding or removing current views,
+ * enabling and disabling GDK updates and in order to filter
+ * mouse and keyboard events going to Scintilla.
+ */
+ overlay_widget = gtk_overlay_new();
+ event_box_widget = gtk_event_box_new();
+ gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_widget), TRUE);
+ gtk_container_add(GTK_CONTAINER(overlay_widget), event_box_widget);
+ gtk_box_pack_start(GTK_BOX(vbox), overlay_widget, TRUE, TRUE, 0);
+
+ info_widget = gtk_info_bar_new();
+ info_content = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_widget));
+ message_widget = gtk_label_new("");
+ gtk_misc_set_alignment(GTK_MISC(message_widget), 0., 0.);
+ gtk_container_add(GTK_CONTAINER(info_content), message_widget);
+ gtk_box_pack_start(GTK_BOX(vbox), info_widget, FALSE, FALSE, 0);
+
+ cmdline_widget = gtk_entry_new();
+ gtk_entry_set_has_frame(GTK_ENTRY(cmdline_widget), FALSE);
+ gtk_editable_set_editable(GTK_EDITABLE(cmdline_widget), FALSE);
+ widget_set_font(cmdline_widget, "Courier");
+ g_signal_connect(G_OBJECT(cmdline_widget), "key-press-event",
+ G_CALLBACK(cmdline_key_pressed_cb), event_queue);
+ gtk_box_pack_start(GTK_BOX(vbox), cmdline_widget, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+
+ /*
+ * Popup widget will be shown in the bottom
+ * of the overlay widget (i.e. the Scintilla views),
+ * filling the entire width.
+ */
+ popup_widget = gtk_info_popup_new();
+ gtk_widget_set_halign(popup_widget, GTK_ALIGN_FILL);
+ gtk_widget_set_valign(popup_widget, GTK_ALIGN_END);
+ gtk_overlay_add_overlay(GTK_OVERLAY(overlay_widget), popup_widget);
+
+ gtk_widget_grab_focus(cmdline_widget);
+
+ cmdline_update(&empty_cmdline);
+}
+
+void
+InterfaceGtk::vmsg_impl(MessageType type, const gchar *fmt, va_list ap)
+{
+ static const GtkMessageType type2gtk[] = {
+ /* [MSG_USER] = */ GTK_MESSAGE_OTHER,
+ /* [MSG_INFO] = */ GTK_MESSAGE_INFO,
+ /* [MSG_WARNING] = */ GTK_MESSAGE_WARNING,
+ /* [MSG_ERROR] = */ GTK_MESSAGE_ERROR
+ };
+
+ va_list aq;
+ gchar buf[255];
+
+ /*
+ * stdio_vmsg() leaves `ap` undefined and we are expected
+ * to do the same and behave like vprintf().
+ */
+ va_copy(aq, ap);
+ stdio_vmsg(type, fmt, ap);
+ g_vsnprintf(buf, sizeof(buf), fmt, aq);
+ va_end(aq);
+
+ gdk_threads_enter();
+
+ gtk_info_bar_set_message_type(GTK_INFO_BAR(info_widget),
+ type2gtk[type]);
+ gtk_label_set_text(GTK_LABEL(message_widget), buf);
+
+ if (type == MSG_ERROR)
+ gtk_widget_error_bell(window);
+
+ gdk_threads_leave();
+}
+
+void
+InterfaceGtk::msg_clear(void)
+{
+ gdk_threads_enter();
+
+ gtk_info_bar_set_message_type(GTK_INFO_BAR(info_widget),
+ GTK_MESSAGE_OTHER);
+ gtk_label_set_text(GTK_LABEL(message_widget), "");
+
+ gdk_threads_leave();
+}
+
+void
+InterfaceGtk::show_view_impl(ViewGtk *view)
+{
+ current_view = view;
+}
+
+void
+InterfaceGtk::info_update_impl(const QRegister *reg)
+{
+ gchar *name = String::canonicalize_ctl(reg->name);
+
+ g_free(info_current);
+ info_current = g_strconcat(PACKAGE_NAME " - <QRegister> ",
+ name, NIL);
+ g_free(name);
+}
+
+void
+InterfaceGtk::info_update_impl(const Buffer *buffer)
+{
+ g_free(info_current);
+ info_current = g_strconcat(PACKAGE_NAME " - <Buffer> ",
+ buffer->filename ? : UNNAMED_FILE,
+ buffer->dirty ? "*" : "", NIL);
+}
+
+void
+InterfaceGtk::cmdline_insert_chr(gint &pos, gchar chr)
+{
+ gchar buffer[5+1];
+
+ /*
+ * NOTE: This mapping is similar to
+ * View::set_representations()
+ */
+ switch (chr) {
+ case CTL_KEY_ESC:
+ strcpy(buffer, "$");
+ break;
+ case '\r':
+ strcpy(buffer, "<CR>");
+ break;
+ case '\n':
+ strcpy(buffer, "<LF>");
+ break;
+ case '\t':
+ strcpy(buffer, "<TAB>");
+ break;
+ default:
+ if (IS_CTL(chr)) {
+ buffer[0] = '^';
+ buffer[1] = CTL_ECHO(chr);
+ buffer[2] = '\0';
+ } else {
+ buffer[0] = chr;
+ buffer[1] = '\0';
+ }
+ }
+
+ gtk_editable_insert_text(GTK_EDITABLE(cmdline_widget),
+ buffer, -1, &pos);
+}
+
+void
+InterfaceGtk::cmdline_update_impl(const Cmdline *cmdline)
+{
+ gint pos = 1;
+ gint cmdline_len;
+
+ gdk_threads_enter();
+
+ /*
+ * We don't know if the new command line is similar to
+ * the old one, so we can just as well rebuild it.
+ */
+ gtk_entry_set_text(GTK_ENTRY(cmdline_widget), "*");
+
+ /* format effective command line */
+ for (guint i = 0; i < cmdline->len; i++)
+ cmdline_insert_chr(pos, (*cmdline)[i]);
+ /* save end of effective command line */
+ cmdline_len = pos;
+
+ /* format rubbed out command line */
+ for (guint i = cmdline->len; i < cmdline->len+cmdline->rubout_len; i++)
+ cmdline_insert_chr(pos, (*cmdline)[i]);
+
+ /* set cursor after effective command line */
+ gtk_editable_set_position(GTK_EDITABLE(cmdline_widget), cmdline_len);
+
+ gdk_threads_leave();
+}
+
+void
+InterfaceGtk::popup_add_impl(PopupEntryType type,
+ const gchar *name, bool highlight)
+{
+ static const GtkInfoPopupEntryType type2gtk[] = {
+ /* [POPUP_PLAIN] = */ GTK_INFO_POPUP_PLAIN,
+ /* [POPUP_FILE] = */ GTK_INFO_POPUP_FILE,
+ /* [POPUP_DIRECTORY] = */ GTK_INFO_POPUP_DIRECTORY
+ };
+
+ gdk_threads_enter();
+
+ gtk_info_popup_add(GTK_INFO_POPUP(popup_widget),
+ type2gtk[type], name, highlight);
+
+ gdk_threads_leave();
+}
+
+void
+InterfaceGtk::popup_show_impl(void)
+{
+ gdk_threads_enter();
+
+ if (gtk_widget_get_visible(popup_widget))
+ gtk_info_popup_scroll_page(GTK_INFO_POPUP(popup_widget));
+ else
+ gtk_widget_show(popup_widget);
+
+ gdk_threads_leave();
+}
+
+void
+InterfaceGtk::popup_clear_impl(void)
+{
+ gdk_threads_enter();
+
+ if (gtk_widget_get_visible(popup_widget)) {
+ gtk_widget_hide(popup_widget);
+ gtk_info_popup_clear(GTK_INFO_POPUP(popup_widget));
+ }
+
+ gdk_threads_leave();
+}
+
+void
+InterfaceGtk::widget_set_font(GtkWidget *widget, const gchar *font_name)
+{
+ PangoFontDescription *font_desc;
+
+ font_desc = pango_font_description_from_string(font_name);
+ gtk_widget_modify_font(widget, font_desc);
+ pango_font_description_free(font_desc);
+}
+
+void
+InterfaceGtk::event_loop_impl(void)
+{
+ GThread *thread;
+
+ /*
+ * Assign an icon to the window.
+ * If the file could not be found, we fail silently.
+ * FIXME: On Windows, it may be better to load the icon compiled
+ * as a resource into the binary.
+ * FIXME: Provide all the different icon sizes we have
+ * (gtk_window_set_default_icon_list()).
+ */
+ gtk_window_set_default_icon_from_file(SCITECODATADIR G_DIR_SEPARATOR_S
+ "sciteco-48.png",
+ NULL);
+
+ /*
+ * When changing views, the new widget is not
+ * added immediately to avoid flickering in the GUI.
+ * It is only updated once per key press and only
+ * if it really changed.
+ * Therefore we must add the current view to the
+ * window initially.
+ * For the same reason, window title updates are
+ * deferred to once after every key press, so we must
+ * set the window title initially.
+ */
+ if (current_view) {
+ current_view_widget = current_view->get_widget();
+ gtk_container_add(GTK_CONTAINER(event_box_widget),
+ current_view_widget);
+ }
+ gtk_window_set_title(GTK_WINDOW(window), info_current);
+
+ gtk_widget_show_all(window);
+ /* don't show popup by default */
+ gtk_widget_hide(popup_widget);
+
+ /*
+ * SIGTERM emulates the "Close" key just like when
+ * closing the window if supported by this version of glib.
+ * Note that this replaces SciTECO's default SIGTERM handler
+ * so it will additionally raise(SIGINT).
+ */
+#ifdef SCITECO_HANDLE_SIGNALS
+ g_unix_signal_add(SIGTERM, sigterm_handler, event_queue);
+#endif
+
+ /*
+ * Start up SciTECO execution thread.
+ * Whenever it needs to send a Scintilla message
+ * it locks the GDK mutex.
+ */
+ thread = g_thread_new("sciteco-exec",
+ exec_thread_cb, event_queue);
+
+ /*
+ * NOTE: The watchers do not modify any GTK objects
+ * using one of the methods that lock the GDK mutex.
+ * This is from now on reserved to the execution
+ * thread. Therefore there can be no dead-locks.
+ */
+ gdk_threads_enter();
+ gtk_main();
+ gdk_threads_leave();
+
+ /*
+ * This usually means that the user requested program
+ * termination and the execution thread called
+ * gtk_main_quit().
+ * We still wait for the execution thread to shut down
+ * properly. This also frees `thread`.
+ */
+ g_thread_join(thread);
+
+ /*
+ * Make sure the window is hidden
+ * now already, as there may be code that has to be
+ * executed in batch mode.
+ */
+ gtk_widget_hide(window);
+}
+
+static gpointer
+exec_thread_cb(gpointer data)
+{
+ GAsyncQueue *event_queue = (GAsyncQueue *)data;
+
+ for (;;) {
+ GdkEventKey *event = (GdkEventKey *)g_async_queue_pop(event_queue);
+
+ bool is_shift = event->state & GDK_SHIFT_MASK;
+ bool is_ctl = event->state & GDK_CONTROL_MASK;
+
+ try {
+ sigint_occurred = FALSE;
+ interface.handle_key_press(is_shift, is_ctl, event->keyval);
+ sigint_occurred = FALSE;
+ } catch (Quit) {
+ /*
+ * SciTECO should terminate, so we exit
+ * this thread.
+ * The main loop will terminate and
+ * event_loop() will return.
+ */
+ gdk_event_free((GdkEvent *)event);
+
+ gdk_threads_enter();
+ gtk_main_quit();
+ gdk_threads_leave();
+ break;
+ }
+
+ gdk_event_free((GdkEvent *)event);
+ }
+
+ return NULL;
+}
+
+void
+InterfaceGtk::handle_key_press(bool is_shift, bool is_ctl, guint keyval)
+{
+ GdkWindow *view_window;
+ ViewGtk *last_view = current_view;
+
+ /*
+ * Avoid redraws of the current view by freezing updates
+ * on the view's GDK window (we're running in parallel
+ * to the main loop so there could be frequent redraws).
+ * By freezing updates, the behaviour is similar to
+ * the Curses UI.
+ */
+ gdk_threads_enter();
+ view_window = gtk_widget_get_parent_window(event_box_widget);
+ gdk_window_freeze_updates(view_window);
+ gdk_threads_leave();
+
+ switch (keyval) {
+ case GDK_KEY_Escape:
+ cmdline.keypress(CTL_KEY_ESC);
+ break;
+ case GDK_KEY_BackSpace:
+ cmdline.keypress(CTL_KEY('H'));
+ break;
+ case GDK_KEY_Tab:
+ cmdline.keypress('\t');
+ break;
+ case GDK_KEY_Return:
+ cmdline.keypress('\n');
+ break;
+
+ /*
+ * Function key macros
+ */
+#define FN(KEY, MACRO) \
+ case GDK_KEY_##KEY: cmdline.fnmacro(#MACRO); break
+#define FNS(KEY, MACRO) \
+ case GDK_KEY_##KEY: cmdline.fnmacro(is_shift ? "S" #MACRO : #MACRO); break
+ FN(Down, DOWN); FN(Up, UP);
+ FNS(Left, LEFT); FNS(Right, RIGHT);
+ FN(KP_Down, DOWN); FN(KP_Up, UP);
+ FNS(KP_Left, LEFT); FNS(KP_Right, RIGHT);
+ FNS(Home, HOME);
+ case GDK_KEY_F1...GDK_KEY_F35: {
+ gchar macro_name[3+1];
+
+ g_snprintf(macro_name, sizeof(macro_name),
+ "F%d", keyval - GDK_KEY_F1 + 1);
+ cmdline.fnmacro(macro_name);
+ break;
+ }
+ FNS(Delete, DC);
+ FNS(Insert, IC);
+ FN(Page_Down, NPAGE); FN(Page_Up, PPAGE);
+ FNS(Print, PRINT);
+ FN(KP_Home, A1); FN(KP_Prior, A3);
+ FN(KP_Begin, B2);
+ FN(KP_End, C1); FN(KP_Next, C3);
+ FNS(End, END);
+ FNS(Help, HELP);
+ FN(Close, CLOSE);
+#undef FNS
+#undef FN
+
+ /*
+ * Control keys and keys with printable representation
+ */
+ default:
+ gunichar u = gdk_keyval_to_unicode(keyval);
+
+ if (u && g_unichar_to_utf8(u, NULL) == 1) {
+ gchar key;
+
+ g_unichar_to_utf8(u, &key);
+ if (key > 0x7F)
+ break;
+ if (is_ctl)
+ key = CTL_KEY(g_ascii_toupper(key));
+
+ cmdline.keypress(key);
+ }
+ }
+
+ /*
+ * The info area is updated very often and setting the
+ * window title each time it is updated is VERY costly.
+ * So we set it here once after every keypress even if the
+ * info line did not change.
+ * View changes are also only applied here to the GTK
+ * window even though GDK updates have been frozen since
+ * the size reallocations are very costly.
+ */
+ gdk_threads_enter();
+
+ if (current_view != last_view) {
+ /*
+ * The last view's object is not guaranteed to
+ * still exist.
+ * However its widget is, due to reference counting.
+ */
+ if (current_view_widget)
+ gtk_container_remove(GTK_CONTAINER(event_box_widget),
+ current_view_widget);
+
+ current_view_widget = current_view->get_widget();
+
+ gtk_container_add(GTK_CONTAINER(event_box_widget),
+ current_view_widget);
+ gtk_widget_show(current_view_widget);
+ }
+
+ gtk_window_set_title(GTK_WINDOW(window), info_current);
+
+ gdk_window_thaw_updates(view_window);
+
+ gdk_threads_leave();
+}
+
+InterfaceGtk::~InterfaceGtk()
+{
+ g_free(info_current);
+ if (popup_widget)
+ gtk_widget_destroy(popup_widget);
+ if (window)
+ gtk_widget_destroy(window);
+
+ scintilla_release_resources();
+
+ if (event_queue) {
+ GdkEvent *e;
+
+ while ((e = (GdkEvent *)g_async_queue_try_pop(event_queue)))
+ gdk_event_free(e);
+
+ g_async_queue_unref(event_queue);
+ }
+}
+
+/*
+ * GTK+ callbacks
+ */
+
+static void
+scintilla_notify(ScintillaObject *sci, uptr_t idFrom,
+ SCNotification *notify, gpointer user_data)
+{
+ interface.process_notify(notify);
+}
+
+static gboolean
+cmdline_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
+ gpointer user_data)
+{
+ GAsyncQueue *event_queue = (GAsyncQueue *)user_data;
+
+ bool is_ctl = event->state & GDK_CONTROL_MASK;
+
+#ifdef DEBUG
+ g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n",
+ event->string, *event->string,
+ event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK);
+#endif
+
+ g_async_queue_lock(event_queue);
+
+ if (g_async_queue_length_unlocked(event_queue) >= 0 &&
+ is_ctl && gdk_keyval_to_upper(event->keyval) == GDK_KEY_C) {
+ /*
+ * Handle asynchronous interruptions if CTRL+C is pressed.
+ * This will usually send SIGINT to the entire process
+ * group and set `sigint_occurred`.
+ * If the execution thread is currently blocking,
+ * the key is delivered like an ordinary key press.
+ */
+ interrupt();
+ } else {
+ /*
+ * Copies the key-press event, since it must be evaluated
+ * by the exec_thread_cb. This is costly, but since we're
+ * using the event queue as a kind of keyboard buffer,
+ * who cares?
+ */
+ g_async_queue_push_unlocked(event_queue,
+ gdk_event_copy((GdkEvent *)event));
+ }
+
+ g_async_queue_unlock(event_queue);
+
+ return TRUE;
+}
+
+static gboolean
+window_delete_cb(GtkWidget *w, GdkEventAny *e, gpointer user_data)
+{
+ GAsyncQueue *event_queue = (GAsyncQueue *)user_data;
+ GdkEventKey *close_event;
+
+ /*
+ * Emulate that the "close" key was pressed
+ * which may then be handled by the execution thread
+ * which invokes the appropriate "function key macro"
+ * if it exists. Its default action will ensure that
+ * the execution thread shuts down and the main loop
+ * will eventually terminate.
+ */
+ close_event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS);
+ close_event->window = gtk_widget_get_parent_window(w);
+ close_event->keyval = GDK_KEY_Close;
+
+ g_async_queue_push(event_queue, close_event);
+
+ return TRUE;
+}
+
+static gboolean
+sigterm_handler(gpointer user_data)
+{
+ GAsyncQueue *event_queue = (GAsyncQueue *)user_data;
+ GdkEventKey *close_event;
+
+ /*
+ * Since this handler replaces the default one, we
+ * also have to make sure it interrupts.
+ */
+ interrupt();
+
+ /*
+ * Similar to window deletion - emulate "close" key press.
+ */
+ close_event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS);
+ close_event->keyval = GDK_KEY_Close;
+
+ g_async_queue_push(event_queue, close_event);
+
+ return G_SOURCE_CONTINUE;
+}
+
+} /* namespace SciTECO */
diff --git a/src/interface-gtk/interface-gtk.h b/src/interface-gtk/interface-gtk.h
new file mode 100644
index 0000000..b5200ea
--- /dev/null
+++ b/src/interface-gtk/interface-gtk.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2012-2016 Robin Haberkorn
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __INTERFACE_GTK_H
+#define __INTERFACE_GTK_H
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+/* FIXME: see interface-gtk.cpp */
+#define GDK_DISABLE_DEPRECATION_WARNINGS
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include <Scintilla.h>
+#include <ScintillaWidget.h>
+
+#include "interface.h"
+
+namespace SciTECO {
+
+typedef class ViewGtk : public View<ViewGtk> {
+ ScintillaObject *sci;
+
+public:
+ ViewGtk() : sci(NULL) {}
+
+ /* implementation of View::initialize() */
+ void initialize_impl(void);
+
+ inline ~ViewGtk()
+ {
+ /*
+ * This does NOT destroy the Scintilla object
+ * and GTK widget, if it is the current view
+ * (and therefore added to the vbox).
+ */
+ if (sci)
+ g_object_unref(G_OBJECT(sci));
+ }
+
+ inline GtkWidget *
+ get_widget(void)
+ {
+ return GTK_WIDGET(sci);
+ }
+
+ /* implementation of View::ssm() */
+ inline sptr_t
+ ssm_impl(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0)
+ {
+ sptr_t ret;
+
+ gdk_threads_enter();
+ ret = scintilla_send_message(sci, iMessage, wParam, lParam);
+ gdk_threads_leave();
+
+ return ret;
+ }
+} ViewCurrent;
+
+typedef class InterfaceGtk : public Interface<InterfaceGtk, ViewGtk> {
+ GtkWidget *window;
+ GtkWidget *vbox;
+
+ GtkWidget *event_box_widget;
+
+ gchar *info_current;
+ GtkWidget *info_widget;
+
+ GtkWidget *message_widget;
+ GtkWidget *cmdline_widget;
+
+ GtkWidget *popup_widget;
+
+ GtkWidget *current_view_widget;
+
+ GAsyncQueue *event_queue;
+
+public:
+ InterfaceGtk() : Interface(),
+ window(NULL),
+ vbox(NULL),
+ event_box_widget(NULL),
+ info_current(NULL), info_widget(NULL),
+ message_widget(NULL),
+ cmdline_widget(NULL),
+ popup_widget(NULL),
+ current_view_widget(NULL),
+ event_queue(NULL) {}
+ ~InterfaceGtk();
+
+ /* overrides Interface::get_options() */
+ inline GOptionGroup *
+ get_options(void)
+ {
+ return gtk_get_option_group(TRUE);
+ }
+
+ /* implementation of Interface::main() */
+ void main_impl(int &argc, char **&argv);
+
+ /* implementation of Interface::vmsg() */
+ void vmsg_impl(MessageType type, const gchar *fmt, va_list ap);
+ /* overrides Interface::msg_clear() */
+ void msg_clear(void);
+
+ /* implementation of Interface::show_view() */
+ void show_view_impl(ViewGtk *view);
+
+ /* implementation of Interface::info_update() */
+ void info_update_impl(const QRegister *reg);
+ void info_update_impl(const Buffer *buffer);
+
+ /* implementation of Interface::cmdline_update() */
+ void cmdline_update_impl(const Cmdline *cmdline);
+
+ /* implementation of Interface::popup_add() */
+ void popup_add_impl(PopupEntryType type,
+ const gchar *name, bool highlight = false);
+ /* implementation of Interface::popup_show() */
+ void popup_show_impl(void);
+
+ /* implementation of Interface::popup_is_shown() */
+ inline bool
+ popup_is_shown_impl(void)
+ {
+ bool ret;
+
+ gdk_threads_enter();
+ ret = gtk_widget_get_visible(popup_widget);
+ gdk_threads_leave();
+
+ return ret;
+ }
+ /* implementation of Interface::popup_clear() */
+ void popup_clear_impl(void);
+
+ /* main entry point (implementation) */
+ void event_loop_impl(void);
+
+ /*
+ * FIXME: This is for internal use only and could be
+ * hidden in a nested forward-declared friend struct.
+ */
+ void handle_key_press(bool is_shift, bool is_ctl, guint keyval);
+
+private:
+ static void widget_set_font(GtkWidget *widget, const gchar *font_name);
+
+ void cmdline_insert_chr(gint &pos, gchar chr);
+} InterfaceCurrent;
+
+} /* namespace SciTECO */
+
+#endif