aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-gtk
diff options
context:
space:
mode:
Diffstat (limited to 'src/interface-gtk')
-rw-r--r--src/interface-gtk/Makefile.am26
-rw-r--r--src/interface-gtk/fallback.css51
-rw-r--r--src/interface-gtk/gtk-info-popup.gob331
-rw-r--r--src/interface-gtk/gtkflowbox.c4795
-rw-r--r--src/interface-gtk/gtkflowbox.h180
-rw-r--r--src/interface-gtk/interface-gtk.cpp1132
-rw-r--r--src/interface-gtk/interface-gtk.h179
-rw-r--r--src/interface-gtk/interface.c1203
-rw-r--r--src/interface-gtk/teco-gtk-info-popup.gob446
-rw-r--r--src/interface-gtk/teco-gtk-label.gob (renamed from src/interface-gtk/gtk-canonicalized-label.gob)113
10 files changed, 1759 insertions, 6697 deletions
diff --git a/src/interface-gtk/Makefile.am b/src/interface-gtk/Makefile.am
index 825c5d3..af26519 100644
--- a/src/interface-gtk/Makefile.am
+++ b/src/interface-gtk/Makefile.am
@@ -1,22 +1,18 @@
-AM_CPPFLAGS += -I$(top_srcdir)/src
+AM_CPPFLAGS += -I$(top_srcdir)/contrib/rb3ptr \
+ -I$(top_srcdir)/src
-AM_CFLAGS = -Wall
-AM_CXXFLAGS = -Wall -Wno-char-subscripts
+AM_CFLAGS = -std=gnu11 -Wall -Wno-initializer-overrides -Wno-unused-value
-EXTRA_DIST = gtk-info-popup.gob \
- gtk-canonicalized-label.gob
-BUILT_SOURCES = gtk-info-popup.c \
- gtk-info-popup.h gtk-info-popup-private.h \
- gtk-canonicalized-label.c \
- gtk-canonicalized-label.h
+EXTRA_DIST = teco-gtk-info-popup.gob \
+ teco-gtk-label.gob
+BUILT_SOURCES = teco-gtk-info-popup.c teco-gtk-info-popup.h \
+ teco-gtk-info-popup-private.h \
+ teco-gtk-label.c teco-gtk-label.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 \
- gtk-canonicalized-label.c
+libsciteco_interface_la_SOURCES = interface.c
+nodist_libsciteco_interface_la_SOURCES = teco-gtk-info-popup.c \
+ teco-gtk-label.c
dist_pkgdata_DATA = fallback.css
diff --git a/src/interface-gtk/fallback.css b/src/interface-gtk/fallback.css
index c8f5431..90ad4ed 100644
--- a/src/interface-gtk/fallback.css
+++ b/src/interface-gtk/fallback.css
@@ -6,6 +6,13 @@
* This may cause problems with your current Gtk theme.
* You can copy this file to $SCITECOCONFIG/.teco_css
* to fix it up or add other style customizations.
+ * You could of course also import it using
+ * @import "/usr/share/sciteco/fallback.css";
+ *
+ * NOTE: Avoid using CSS element names like GtkLabel
+ * since Gtk switched from type names to custom names
+ * in Gtk+ v3.20 and it is impossible/cumbersome to
+ * write a CSS compatible with both.
*/
/*
@@ -17,14 +24,12 @@
* - type-label: The label showing the current document type
* - name-label: THe label showing the current document name
*/
-.info-qregister,
-.info-buffer {
+.info-buffer, .info-qregister {
background-color: @sciteco_default_fg_color;
background-image: none;
}
-.info-qregister GtkLabel,
-.info-buffer GtkLabel {
+.info-buffer *, .info-qregister * {
color: @sciteco_default_bg_color;
text-shadow: none;
}
@@ -39,11 +44,6 @@
}
/*
- * Scintilla views
- */
-ScintillaObject {}
-
-/*
* The message bar (#sciteco-message-bar).
*
* The "question" class refers to G_MESSAGE_QUESTION.
@@ -51,25 +51,32 @@ ScintillaObject {}
* reason that there is no class for G_MESSAGE_OTHER that
* we could use for styling.
*/
-#sciteco-message-bar.question {
- background-color: @sciteco_default_fg_color;
+#sciteco-message-bar .label {
+ color: @sciteco_default_bg_color;
+ text-shadow: none;
+}
+
+#sciteco-message-bar {
background-image: none;
}
-#sciteco-message-bar.question GtkLabel {
- color: @sciteco_default_bg_color;
- text-shadow: none;
+#sciteco-message-bar.question {
+ background-color: @sciteco_default_fg_color;
+}
+#sciteco-message-bar.info {
+ background-color: green;
+}
+#sciteco-message-bar.error {
+ background-color: yellow;
+}
+#sciteco-message-bar.error {
+ background-color: red;
}
/*
* The command line area (#sciteco-cmdline)
*/
-#sciteco-cmdline {
- color: @sciteco_default_fg_color;
- text-shadow: none;
- background-color: @sciteco_default_bg_color;
- background-image: none;
-}
+#sciteco-cmdline {}
/*
* The autocompletion popup (#sciteco-info-popup).
@@ -81,11 +88,11 @@ ScintillaObject {}
background-image: none;
}
-#sciteco-info-popup GtkLabel {
+#sciteco-info-popup .label {
color: @sciteco_calltip_fg_color;
text-shadow: none;
}
-#sciteco-info-popup .highlight GtkLabel {
+#sciteco-info-popup .highlight .label {
font-weight: bold;
}
diff --git a/src/interface-gtk/gtk-info-popup.gob b/src/interface-gtk/gtk-info-popup.gob
deleted file mode 100644
index edc6612..0000000
--- a/src/interface-gtk/gtk-info-popup.gob
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2012-2017 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/>.
- */
-
-requires 2.0.20
-
-%ctop{
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <math.h>
-
-#include <glib/gprintf.h>
-
-#ifndef HAVE_GTK_FLOW_BOX_NEW
-#include "gtkflowbox.h"
-#endif
-
-#include "gtk-canonicalized-label.h"
-%}
-
-%h{
-#include <gtk/gtk.h>
-#include <gdk/gdk.h>
-
-#include <gio/gio.h>
-%}
-
-enum GTK_INFO_POPUP {
- PLAIN,
- FILE,
- DIRECTORY
-} Gtk:Info:Popup:Entry:Type;
-
-/*
- * NOTE: Deriving from GtkEventBox ensures that we can
- * set a background on the entire popup widget.
- */
-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);
- /* show/hide the scrollbar dynamically */
- g_signal_connect(self->vadjustment, "changed",
- G_CALLBACK(self_vadjustment_changed), scrollbar);
-
- 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);
-
- viewport = gtk_viewport_new(self->hadjustment, self->vadjustment);
- gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
- 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);
-
- /*
- * NOTE: Everything shown except the top-level container.
- * Therefore a gtk_widget_show() is enough to show our popup.
- */
- gtk_container_add(GTK_CONTAINER(self), box);
- }
-
- /**
- * Allocate position in an overlay.
- *
- * This function can be used as the "get-child-position" signal
- * handler of a GtkOverlay in order to position the popup at the
- * bottom of the overlay's main child, spanning the entire width.
- * In contrast to the GtkOverlay's default allocation schemes,
- * this makes sure that the widget will not be larger than the
- * main child, so the popup properly scrolls when becoming too large
- * in height.
- *
- * @param user_data unused by this callback
- */
- public gboolean
- get_position_in_overlay(Gtk:Overlay *overlay, Gtk:Widget *widget,
- Gdk:Rectangle *allocation, gpointer user_data)
- {
- GtkWidget *main_child = gtk_bin_get_child(GTK_BIN(overlay));
- GtkAllocation main_child_alloc;
- gint natural_height;
-
- gtk_widget_get_allocation(main_child, &main_child_alloc);
- gtk_widget_get_preferred_height_for_width(widget,
- main_child_alloc.width,
- NULL, &natural_height);
-
- /*
- * FIXME: Probably due to some bug in the height-for-width
- * calculation of Gtk (at least in 3.10 or in the GtkFlowBox
- * fallback included with SciTECO), the natural height
- * is a bit too small to accommodate the entire GtkFlowBox,
- * resulting in the GtkViewport always scrolling.
- * This hack fixes it up in a NONPORTABLE manner.
- */
- natural_height += 5;
-
- allocation->width = main_child_alloc.width;
- allocation->height = MIN(natural_height, main_child_alloc.height);
- allocation->x = 0;
- allocation->y = main_child_alloc.height - allocation->height;
-
- return TRUE;
- }
-
- /*
- * Adapted from GtkScrolledWindow's gtk_scrolled_window_scroll_event()
- * since the viewport does not react to scroll events.
- * This is registered for our container widget instead of only for
- * GtkViewport since this is what GtkScrolledWindow does.
- * 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, Gdk:Event:Scroll *event)
- {
- Self *self = SELF(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;
- }
-
- private void
- vadjustment_changed(Gtk:Adjustment *vadjustment, gpointer user_data)
- {
- GtkWidget *scrollbar = GTK_WIDGET(user_data);
-
- /*
- * This shows/hides the widget using opacity instead of using
- * gtk_widget_set_visibility() since the latter would influence
- * size allocations. A widget with opacity 0 keeps its size.
- */
- gtk_widget_set_opacity(scrollbar,
- gtk_adjustment_get_upper(vadjustment) -
- gtk_adjustment_get_lower(vadjustment) >
- gtk_adjustment_get_page_size(vadjustment) ? 1 : 0);
- }
-
- public GtkWidget *
- new(void)
- {
- Self *widget = GET_NEW;
- return GTK_WIDGET(widget);
- }
-
- public 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;
-
- hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
- if (highlight)
- gtk_style_context_add_class(gtk_widget_get_style_context(hbox),
- "highlight");
-
- 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);
- }
- }
-
- label = gtk_canonicalized_label_new(name);
- gtk_widget_set_halign(label, GTK_ALIGN_START);
- gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
-
- /*
- * FIXME: This makes little sense once we've got mouse support.
- * But for the time being, it's a useful setting.
- */
- gtk_label_set_selectable(GTK_LABEL(label), TRUE);
-
- switch (type) {
- case GTK_INFO_POPUP_PLAIN:
- gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_START);
- break;
- case GTK_INFO_POPUP_FILE:
- case GTK_INFO_POPUP_DIRECTORY:
- gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
- break;
- }
-
- 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
deleted file mode 100644
index 1a5c2e9..0000000
--- a/src/interface-gtk/gtkflowbox.c
+++ /dev/null
@@ -1,4795 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 6f0549f..0000000
--- a/src/interface-gtk/gtkflowbox.h
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 9486802..0000000
--- a/src/interface-gtk/interface-gtk.cpp
+++ /dev/null
@@ -1,1132 +0,0 @@
-/*
- * Copyright (C) 2012-2017 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>
-#include <glib/gstdio.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 <gdk-pixbuf/gdk-pixbuf.h>
-
-#include <gtk/gtk.h>
-
-#include <gio/gio.h>
-
-#include <Scintilla.h>
-#include <ScintillaWidget.h>
-
-#include "gtk-info-popup.h"
-#include "gtk-canonicalized-label.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;
-
-static gboolean
-g_object_unref_idle_cb(gpointer user_data)
-{
- g_object_unref(user_data);
- return G_SOURCE_REMOVE;
-}
-
-} /* extern "C" */
-
-#define UNNAMED_FILE "(Unnamed)"
-
-#define USER_CSS_FILE ".teco_css"
-
-/** printf() format for CSS RGB colors given as guint32 */
-#define CSS_COLOR_FORMAT "#%06" G_GINT32_MODIFIER "X"
-
-/**
- * Convert Scintilla-style BGR color triple to
- * RGB.
- */
-static inline guint32
-bgr2rgb(guint32 bgr)
-{
- return ((bgr & 0x0000FF) << 16) |
- ((bgr & 0x00FF00) << 0) |
- ((bgr & 0xFF0000) >> 16);
-}
-
-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(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(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();
-}
-
-ViewGtk::~ViewGtk()
-{
- /*
- * This does NOT destroy the Scintilla object
- * and GTK widget, if it is the current view
- * (and therefore added to the vbox).
- * FIXME: This only uses an idle watcher
- * because the destructor can be called with
- * the Gdk lock held and without.
- * Once the threading model is revised this
- * can be simplified and inlined again.
- */
- if (sci)
- gdk_threads_add_idle(g_object_unref_idle_cb, sci);
-}
-
-GOptionGroup *
-InterfaceGtk::get_options(void)
-{
- const GOptionEntry entries[] = {
- {"no-csd", 0, G_OPTION_FLAG_IN_MAIN | G_OPTION_FLAG_REVERSE,
- G_OPTION_ARG_NONE, &use_csd,
- "Disable client-side decorations.", NULL},
- {NULL}
- };
-
- /*
- * Parsing the option context with the Gtk option group
- * will automatically initialize Gtk, but we do not yet
- * open the default display.
- */
- GOptionGroup *group = gtk_get_option_group(FALSE);
-
- g_option_group_add_entries(group, entries);
-
- return group;
-}
-
-void
-InterfaceGtk::init(void)
-{
- static const Cmdline empty_cmdline;
-
- GtkWidget *vbox;
- GtkWidget *overlay_widget, *overlay_vbox;
- GtkWidget *message_bar_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() is not necessary when using gtk_get_option_group(),
- * but this will open the default display.
- * FIXME: Perhaps it is possible to defer this until we initialize
- * interactive mode!?
- */
- gtk_init(NULL, NULL);
-
- /*
- * Register clipboard registers.
- * Unfortunately, we cannot find out which
- * clipboards/selections are supported on this system,
- * so we register only some default ones.
- */
- QRegisters::globals.insert(new QRegisterClipboard());
- QRegisters::globals.insert(new QRegisterClipboard("P"));
- QRegisters::globals.insert(new QRegisterClipboard("S"));
- QRegisters::globals.insert(new QRegisterClipboard("C"));
-
- /*
- * 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);
- g_signal_connect(G_OBJECT(window), "delete-event",
- G_CALLBACK(window_delete_cb), event_queue);
-
- vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-
- info_current = g_strdup("");
-
- /*
- * The info bar is tried to be made the title bar of the
- * window which also disables the default window decorations
- * (client-side decorations) unless --no-csd was specified.
- * NOTE: Client-side decoations could fail, leaving us with a
- * standard title bar and the info bar with close buttons.
- * Other window managers have undesirable side-effects.
- */
- info_bar_widget = gtk_header_bar_new();
- gtk_widget_set_name(info_bar_widget, "sciteco-info-bar");
- info_name_widget = gtk_canonicalized_label_new(NULL);
- gtk_widget_set_valign(info_name_widget, GTK_ALIGN_CENTER);
- gtk_style_context_add_class(gtk_widget_get_style_context(info_name_widget),
- "name-label");
- gtk_label_set_selectable(GTK_LABEL(info_name_widget), TRUE);
- /* NOTE: Header bar does not resize for multi-line labels */
- //gtk_label_set_line_wrap(GTK_LABEL(info_name_widget), TRUE);
- //gtk_label_set_lines(GTK_LABEL(info_name_widget), 2);
- gtk_header_bar_set_custom_title(GTK_HEADER_BAR(info_bar_widget), info_name_widget);
- info_image = gtk_image_new();
- gtk_header_bar_pack_start(GTK_HEADER_BAR(info_bar_widget), info_image);
- info_type_widget = gtk_label_new(NULL);
- gtk_widget_set_valign(info_type_widget, GTK_ALIGN_CENTER);
- gtk_style_context_add_class(gtk_widget_get_style_context(info_type_widget),
- "type-label");
- gtk_header_bar_pack_start(GTK_HEADER_BAR(info_bar_widget), info_type_widget);
- if (use_csd) {
- /* use client-side decorations */
- gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(info_bar_widget), TRUE);
- gtk_window_set_titlebar(GTK_WINDOW(window), info_bar_widget);
- } else {
- /* fall back to adding the info bar as an ordinary widget */
- gtk_box_pack_start(GTK_BOX(vbox), info_bar_widget, FALSE, FALSE, 0);
- }
-
- /*
- * Overlay widget will allow overlaying the Scintilla view
- * and message widgets with the info popup.
- * Therefore overlay_vbox (containing the view and popup)
- * will be the main child of the overlay.
- */
- overlay_widget = gtk_overlay_new();
- overlay_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-
- /*
- * 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.
- */
- event_box_widget = gtk_event_box_new();
- gtk_event_box_set_above_child(GTK_EVENT_BOX(event_box_widget), TRUE);
- gtk_box_pack_start(GTK_BOX(overlay_vbox), event_box_widget,
- TRUE, TRUE, 0);
-
- message_bar_widget = gtk_info_bar_new();
- gtk_widget_set_name(message_bar_widget, "sciteco-message-bar");
- message_bar_content = gtk_info_bar_get_content_area(GTK_INFO_BAR(message_bar_widget));
- /* NOTE: Messages are always pre-canonicalized */
- message_widget = gtk_label_new(NULL);
- gtk_label_set_selectable(GTK_LABEL(message_widget), TRUE);
- gtk_label_set_line_wrap(GTK_LABEL(message_widget), TRUE);
- gtk_container_add(GTK_CONTAINER(message_bar_content), message_widget);
- gtk_box_pack_start(GTK_BOX(overlay_vbox), message_bar_widget,
- FALSE, FALSE, 0);
-
- gtk_container_add(GTK_CONTAINER(overlay_widget), overlay_vbox);
- gtk_box_pack_start(GTK_BOX(vbox), overlay_widget, TRUE, TRUE, 0);
-
- cmdline_widget = gtk_entry_new();
- gtk_widget_set_name(cmdline_widget, "sciteco-cmdline");
- gtk_entry_set_has_frame(GTK_ENTRY(cmdline_widget), FALSE);
- gtk_editable_set_editable(GTK_EDITABLE(cmdline_widget), FALSE);
- 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_name(popup_widget, "sciteco-info-popup");
- gtk_overlay_add_overlay(GTK_OVERLAY(overlay_widget), popup_widget);
- g_signal_connect(overlay_widget, "get-child-position",
- G_CALLBACK(gtk_info_popup_get_position_in_overlay), NULL);
-
- gtk_widget_grab_focus(cmdline_widget);
-
- cmdline_update(&empty_cmdline);
-}
-
-void
-InterfaceGtk::vmsg_impl(MessageType type, const gchar *fmt, va_list ap)
-{
- /*
- * The message types are chosen such that there is a CSS class
- * for every one of them. GTK_MESSAGE_OTHER does not have
- * a CSS class.
- */
- static const GtkMessageType type2gtk[] = {
- /* [MSG_USER] = */ GTK_MESSAGE_QUESTION,
- /* [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(message_bar_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(message_bar_widget),
- GTK_MESSAGE_QUESTION);
- gtk_label_set_text(GTK_LABEL(message_widget), "");
-
- gdk_threads_leave();
-}
-
-void
-InterfaceGtk::show_view_impl(ViewGtk *view)
-{
- current_view = view;
-}
-
-void
-InterfaceGtk::refresh_info(void)
-{
- GtkStyleContext *style = gtk_widget_get_style_context(info_bar_widget);
- const gchar *info_type_str = PACKAGE;
- gchar *info_current_temp = g_strdup(info_current);
- gchar *info_current_canon;
- GIcon *icon;
- gchar *title;
-
- gtk_style_context_remove_class(style, "info-qregister");
- gtk_style_context_remove_class(style, "info-buffer");
- gtk_style_context_remove_class(style, "dirty");
-
- if (info_type == INFO_TYPE_BUFFER_DIRTY)
- String::append(info_current_temp, "*");
- gtk_canonicalized_label_set_text(GTK_CANONICALIZED_LABEL(info_name_widget),
- info_current_temp);
- info_current_canon = String::canonicalize_ctl(info_current_temp);
- g_free(info_current_temp);
-
- switch (info_type) {
- case INFO_TYPE_QREGISTER:
- gtk_style_context_add_class(style, "info-qregister");
-
- info_type_str = PACKAGE_NAME " - <QRegister> ";
- gtk_label_set_text(GTK_LABEL(info_type_widget), "QRegister");
- gtk_label_set_ellipsize(GTK_LABEL(info_name_widget),
- PANGO_ELLIPSIZE_START);
-
- /* FIXME: Use a Q-Register icon */
- gtk_image_clear(GTK_IMAGE(info_image));
- break;
-
- case INFO_TYPE_BUFFER_DIRTY:
- gtk_style_context_add_class(style, "dirty");
- /* fall through */
- case INFO_TYPE_BUFFER:
- gtk_style_context_add_class(style, "info-buffer");
-
- info_type_str = PACKAGE_NAME " - <Buffer> ";
- gtk_label_set_text(GTK_LABEL(info_type_widget), "Buffer");
- gtk_label_set_ellipsize(GTK_LABEL(info_name_widget),
- PANGO_ELLIPSIZE_MIDDLE);
-
- icon = gtk_info_popup_get_icon_for_path(info_current,
- "text-x-generic");
- if (!icon)
- break;
- gtk_image_set_from_gicon(GTK_IMAGE(info_image),
- icon, GTK_ICON_SIZE_LARGE_TOOLBAR);
- g_object_unref(icon);
- break;
- }
-
- title = g_strconcat(info_type_str, info_current_canon, NIL);
- gtk_window_set_title(GTK_WINDOW(window), title);
- g_free(title);
- g_free(info_current_canon);
-}
-
-void
-InterfaceGtk::info_update_impl(const QRegister *reg)
-{
- g_free(info_current);
- info_type = INFO_TYPE_QREGISTER;
- /* NOTE: will contain control characters */
- info_current = g_strdup(reg->name);
-}
-
-void
-InterfaceGtk::info_update_impl(const Buffer *buffer)
-{
- g_free(info_current);
- info_type = buffer->dirty ? INFO_TYPE_BUFFER_DIRTY
- : INFO_TYPE_BUFFER;
- info_current = g_strdup(buffer->filename ? : UNNAMED_FILE);
-}
-
-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();
-}
-
-static GdkAtom
-get_selection_by_name(const gchar *name)
-{
- /*
- * We can use gdk_atom_intern() to support arbitrary X11 selection
- * names. However, since we cannot find out which selections are
- * registered, we are only providing QRegisters for the three default
- * selections.
- * Checking them here avoids expensive X server roundtrips.
- */
- switch (*name) {
- case '\0': return GDK_NONE;
- case 'P': return GDK_SELECTION_PRIMARY;
- case 'S': return GDK_SELECTION_SECONDARY;
- case 'C': return GDK_SELECTION_CLIPBOARD;
- default: break;
- }
-
- return gdk_atom_intern(name, FALSE);
-}
-
-void
-InterfaceGtk::set_clipboard(const gchar *name, const gchar *str, gssize str_len)
-{
- GtkClipboard *clipboard;
-
- gdk_threads_enter();
-
- clipboard = gtk_clipboard_get(get_selection_by_name(name));
-
- /*
- * NOTE: function has compatible semantics for str_len < 0.
- */
- gtk_clipboard_set_text(clipboard, str, str_len);
-
- gdk_threads_leave();
-}
-
-gchar *
-InterfaceGtk::get_clipboard(const gchar *name, gsize *str_len)
-{
- GtkClipboard *clipboard;
- gchar *str;
-
- gdk_threads_enter();
-
- clipboard = gtk_clipboard_get(get_selection_by_name(name));
- /*
- * Could return NULL for an empty clipboard.
- * NOTE: This converts to UTF8 and we loose the ability
- * to get clipboard with embedded nulls.
- */
- str = gtk_clipboard_wait_for_text(clipboard);
-
- gdk_threads_leave();
-
- if (str_len)
- *str_len = str ? strlen(str) : 0;
- return str;
-}
-
-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::set_css_variables_from_view(ViewGtk *view)
-{
- guint font_size;
- gchar buffer[256];
-
- /*
- * Unfortunately, we cannot use CSS variables to pass around
- * font names and sizes, necessary for styling the command line
- * widget.
- * Therefore we just style it using generated CSS here.
- * This one of the few non-deprecated ways that Gtk leaves us
- * to set a custom font name.
- * CSS customizations have to take that into account.
- * NOTE: We don't actually know apriori how
- * large our buffer should be, but luckily STYLEGETFONT with
- * a sptr==0 will return only the size.
- * This is undocumented in the Scintilla docs.
- */
- gchar font_name[view->ssm(SCI_STYLEGETFONT, STYLE_DEFAULT) + 1];
- view->ssm(SCI_STYLEGETFONT, STYLE_DEFAULT, (sptr_t)font_name);
- font_size = view->ssm(SCI_STYLEGETSIZEFRACTIONAL, STYLE_DEFAULT);
-
- /*
- * Generates a CSS that sets some predefined color variables.
- * This effectively "exports" Scintilla styles into the CSS
- * world.
- * Those colors are used by the fallback.css shipping with SciTECO
- * in order to apply the SciTECO-controlled color scheme to all the
- * predefined UI elements.
- * They can also be used in user-customizations.
- */
- g_snprintf(buffer, sizeof(buffer),
- "@define-color sciteco_default_fg_color " CSS_COLOR_FORMAT ";"
- "@define-color sciteco_default_bg_color " CSS_COLOR_FORMAT ";"
- "@define-color sciteco_calltip_fg_color " CSS_COLOR_FORMAT ";"
- "@define-color sciteco_calltip_bg_color " CSS_COLOR_FORMAT ";"
- "#%s{"
- "font: %s %u.%u"
- "}",
- bgr2rgb(view->ssm(SCI_STYLEGETFORE, STYLE_DEFAULT)),
- bgr2rgb(view->ssm(SCI_STYLEGETBACK, STYLE_DEFAULT)),
- bgr2rgb(view->ssm(SCI_STYLEGETFORE, STYLE_CALLTIP)),
- bgr2rgb(view->ssm(SCI_STYLEGETBACK, STYLE_CALLTIP)),
- gtk_widget_get_name(cmdline_widget),
- font_name,
- font_size / SC_FONT_SIZE_MULTIPLIER,
- font_size % SC_FONT_SIZE_MULTIPLIER);
-
- /*
- * The GError and return value has been deprecated.
- * A CSS parsing error would point to a programming
- * error anyway.
- */
- gtk_css_provider_load_from_data(css_var_provider, buffer, -1, NULL);
-}
-
-void
-InterfaceGtk::event_loop_impl(void)
-{
- static const gchar *icon_files[] = {
- SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-16.png",
- SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-32.png",
- SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-48.png",
- NULL
- };
-
- GdkScreen *default_screen = gdk_screen_get_default();
- GtkCssProvider *user_css_provider;
- gchar *config_path, *user_css_file;
-
- GList *icon_list = NULL;
- 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.
- */
- for (const gchar **file = icon_files; *file; file++) {
- GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(*file, NULL);
-
- /* fail silently if there's a problem with one of the icons */
- if (icon_pixbuf)
- icon_list = g_list_append(icon_list, icon_pixbuf);
- }
-
- gtk_window_set_default_icon_list(icon_list);
-
- if (icon_list)
- g_list_free_full(icon_list, g_object_unref);
-
- refresh_info();
-
- /*
- * Initialize the CSS variable provider and the CSS provider
- * for the included fallback.css.
- * NOTE: The return value of gtk_css_provider_load() is deprecated.
- * Instead we could register for the "parsing-error" signal.
- * For the time being we just silently ignore parsing errors.
- * They will be printed to stderr by Gtk anyway.
- */
- css_var_provider = gtk_css_provider_new();
- if (current_view)
- /* set CSS variables initially */
- set_css_variables_from_view(current_view);
- gtk_style_context_add_provider_for_screen(default_screen,
- GTK_STYLE_PROVIDER(css_var_provider),
- GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
- user_css_provider = gtk_css_provider_new();
- /* get path of $SCITECOCONFIG/.teco_css */
- config_path = QRegisters::globals["$SCITECOCONFIG"]->get_string();
- user_css_file = g_build_filename(config_path, USER_CSS_FILE, NIL);
- if (g_file_test(user_css_file, G_FILE_TEST_IS_REGULAR))
- /* open user CSS */
- gtk_css_provider_load_from_path(user_css_provider,
- user_css_file, NULL);
- else
- /* use fallback CSS */
- gtk_css_provider_load_from_path(user_css_provider,
- SCITECODATADIR G_DIR_SEPARATOR_S
- "fallback.css",
- NULL);
- g_free(user_css_file);
- g_free(config_path);
- gtk_style_context_add_provider_for_screen(default_screen,
- GTK_STYLE_PROVIDER(user_css_provider),
- GTK_STYLE_PROVIDER_PRIORITY_USER);
-
- /*
- * 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_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 styles configured via Scintilla might change
- * with every keypress.
- */
- set_css_variables_from_view(current_view);
-
- /*
- * 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();
-
- refresh_info();
-
- 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);
- }
-
- gdk_window_thaw_updates(view_window);
-
- gdk_threads_leave();
-}
-
-InterfaceGtk::~InterfaceGtk()
-{
- g_free(info_current);
-
- 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);
- }
-
- if (css_var_provider)
- g_object_unref(css_var_provider);
-}
-
-/*
- * 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
deleted file mode 100644
index 82ed96b..0000000
--- a/src/interface-gtk/interface-gtk.h
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2012-2017 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);
-
- ~ViewGtk();
-
- 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> {
- GtkCssProvider *css_var_provider;
-
- GtkWidget *window;
-
- enum {
- INFO_TYPE_BUFFER = 0,
- INFO_TYPE_BUFFER_DIRTY,
- INFO_TYPE_QREGISTER
- } info_type;
- gchar *info_current;
- gboolean use_csd;
- GtkWidget *info_bar_widget;
- GtkWidget *info_image;
- GtkWidget *info_type_widget;
- GtkWidget *info_name_widget;
-
- GtkWidget *event_box_widget;
-
- GtkWidget *message_bar_widget;
- GtkWidget *message_widget;
-
- GtkWidget *cmdline_widget;
-
- GtkWidget *popup_widget;
-
- GtkWidget *current_view_widget;
-
- GAsyncQueue *event_queue;
-
-public:
- InterfaceGtk() : css_var_provider(NULL),
- window(NULL),
- info_type(INFO_TYPE_BUFFER), info_current(NULL),
- use_csd(TRUE),
- info_bar_widget(NULL),
- info_image(NULL), info_type_widget(NULL), info_name_widget(NULL),
- event_box_widget(NULL),
- message_bar_widget(NULL), message_widget(NULL),
- cmdline_widget(NULL),
- popup_widget(NULL),
- current_view_widget(NULL),
- event_queue(NULL) {}
- ~InterfaceGtk();
-
- /* overrides Interface::get_options() */
- GOptionGroup *get_options(void);
-
- /* override of Interface::init() */
- void init(void);
-
- /* 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);
-
- /* override of Interface::set_clipboard() */
- void set_clipboard(const gchar *name,
- const gchar *str = NULL, gssize str_len = -1);
- /* override of Interface::get_clipboard() */
- gchar *get_clipboard(const gchar *name, gsize *str_len = NULL);
-
- /* 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:
- void set_css_variables_from_view(ViewGtk *view);
-
- void refresh_info(void);
- void cmdline_insert_chr(gint &pos, gchar chr);
-} InterfaceCurrent;
-
-} /* namespace SciTECO */
-
-#endif
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
new file mode 100644
index 0000000..afc8fe3
--- /dev/null
+++ b/src/interface-gtk/interface.c
@@ -0,0 +1,1203 @@
+/*
+ * Copyright (C) 2012-2021 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>
+#include <glib/gstdio.h>
+
+#ifdef G_OS_UNIX
+#include <glib-unix.h>
+#endif
+
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <gtk/gtk.h>
+
+#include <gio/gio.h>
+
+#include <Scintilla.h>
+#include <ScintillaWidget.h>
+
+#include "teco-gtk-info-popup.h"
+#include "teco-gtk-label.h"
+
+#include "sciteco.h"
+#include "error.h"
+#include "string-utils.h"
+#include "cmdline.h"
+#include "qreg.h"
+#include "ring.h"
+#include "interface.h"
+
+//#define DEBUG
+
+static void teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
+ GdkRectangle *allocation,
+ gpointer user_data);
+static gboolean teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
+ gpointer user_data);
+static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event,
+ gpointer user_data);
+static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED;
+
+#define UNNAMED_FILE "(Unnamed)"
+
+#define USER_CSS_FILE ".teco_css"
+
+/** printf() format for CSS RGB colors given as guint32 */
+#define CSS_COLOR_FORMAT "#%06" G_GINT32_MODIFIER "X"
+
+/** Style used for the asterisk at the beginning of the command line */
+#define STYLE_ASTERISK 16
+
+/** Indicator number used for control characters in the command line */
+#define INDIC_CONTROLCHAR (INDIC_CONTAINER+0)
+/** Indicator number used for the rubbed out part of the command line */
+#define INDIC_RUBBEDOUT (INDIC_CONTAINER+1)
+
+/** Convert Scintilla-style BGR color triple to RGB. */
+static inline guint32
+teco_bgr2rgb(guint32 bgr)
+{
+ return GUINT32_SWAP_LE_BE(bgr) >> 8;
+}
+
+/*
+ * NOTE: The teco_view_t pointer is reused to directly
+ * point to the ScintillaObject.
+ * This saves one heap object per view.
+ */
+
+static void
+teco_view_scintilla_notify(ScintillaObject *sci, gint id,
+ struct SCNotification *notify, gpointer user_data)
+{
+ teco_interface_process_notify(notify);
+}
+
+teco_view_t *
+teco_view_new(void)
+{
+ ScintillaObject *sci = SCINTILLA(scintilla_new());
+ /*
+ * We don't want the object to be destroyed
+ * when it is removed from the vbox.
+ */
+ g_object_ref_sink(sci);
+
+ scintilla_set_id(sci, 0);
+
+ gtk_widget_set_size_request(GTK_WIDGET(sci), 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(GTK_WIDGET(sci), FALSE);
+ gint events = gtk_widget_get_events(GTK_WIDGET(sci));
+ events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+ events &= ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
+ gtk_widget_set_events(GTK_WIDGET(sci), events);
+
+ g_signal_connect(sci, SCINTILLA_NOTIFY,
+ G_CALLBACK(teco_view_scintilla_notify), NULL);
+
+ return (teco_view_t *)sci;
+}
+
+static inline GtkWidget *
+teco_view_get_widget(teco_view_t *ctx)
+{
+ return GTK_WIDGET(ctx);
+}
+
+sptr_t
+teco_view_ssm(teco_view_t *ctx, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
+{
+ return scintilla_send_message(SCINTILLA(ctx), iMessage, wParam, lParam);
+}
+
+static gboolean
+teco_view_free_idle_cb(gpointer user_data)
+{
+ /*
+ * This does NOT destroy the Scintilla object
+ * and GTK widget, if it is the current view
+ * (and therefore added to the vbox).
+ */
+ g_object_unref(user_data);
+ return G_SOURCE_REMOVE;
+}
+
+void
+teco_view_free(teco_view_t *ctx)
+{
+ /*
+ * FIXME: The widget is unreffed only in an idle watcher because
+ * Scintilla may have idle callbacks activated (see ScintillaGTK.cxx)
+ * and we must prevent use-after-frees.
+ * A simple g_idle_remove_by_data() does not suffice for some strange reason
+ * (perhaps it does not prevent the invocation of already activated watchers).
+ * This is a bug should better be fixed by reference counting in
+ * ScintillaGTK.cxx itself.
+ */
+ g_idle_add_full(G_PRIORITY_LOW, teco_view_free_idle_cb, SCINTILLA(ctx), NULL);
+}
+
+static struct {
+ GtkCssProvider *css_var_provider;
+
+ GtkWidget *window;
+
+ enum {
+ TECO_INFO_TYPE_BUFFER = 0,
+ TECO_INFO_TYPE_BUFFER_DIRTY,
+ TECO_INFO_TYPE_QREG
+ } info_type;
+ teco_string_t info_current;
+
+ gboolean no_csd;
+ GtkWidget *info_bar_widget;
+ GtkWidget *info_image;
+ GtkWidget *info_type_widget;
+ GtkWidget *info_name_widget;
+
+ GtkWidget *event_box_widget;
+
+ GtkWidget *message_bar_widget;
+ GtkWidget *message_widget;
+
+ teco_view_t *cmdline_view;
+
+ GtkWidget *popup_widget;
+
+ GtkWidget *current_view_widget;
+
+ GQueue *event_queue;
+} teco_interface;
+
+void
+teco_interface_init(void)
+{
+ /*
+ * gtk_init() is not necessary when using gtk_get_option_group(),
+ * but this will open the default display.
+ *
+ * FIXME: Perhaps it is possible to defer this until we initialize
+ * interactive mode!?
+ */
+ gtk_init(NULL, NULL);
+
+ /*
+ * Register clipboard registers.
+ * Unfortunately, we cannot find out which
+ * clipboards/selections are supported on this system,
+ * so we register only some default ones.
+ */
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
+
+ teco_interface.event_queue = g_queue_new();
+
+ teco_interface.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g_signal_connect(teco_interface.window, "delete-event",
+ G_CALLBACK(teco_interface_window_delete_cb), NULL);
+
+ g_signal_connect(teco_interface.window, "key-press-event",
+ G_CALLBACK(teco_interface_key_pressed_cb), NULL);
+
+ GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+
+ /*
+ * The info bar is tried to be made the title bar of the
+ * window which also disables the default window decorations
+ * (client-side decorations) unless --no-csd was specified.
+ *
+ * NOTE: Client-side decoations could fail, leaving us with a
+ * standard title bar and the info bar with close buttons.
+ * Other window managers have undesirable side-effects.
+ */
+ teco_interface.info_bar_widget = gtk_header_bar_new();
+ gtk_widget_set_name(teco_interface.info_bar_widget, "sciteco-info-bar");
+ teco_interface.info_name_widget = teco_gtk_label_new(NULL, 0);
+ gtk_widget_set_valign(teco_interface.info_name_widget, GTK_ALIGN_CENTER);
+ /* eases writing portable fallback.css that avoids CSS element names */
+ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_name_widget),
+ "label");
+ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_name_widget),
+ "name-label");
+ gtk_label_set_selectable(GTK_LABEL(teco_interface.info_name_widget), TRUE);
+ /* NOTE: Header bar does not resize for multi-line labels */
+ //gtk_label_set_line_wrap(GTK_LABEL(teco_interface.info_name_widget), TRUE);
+ //gtk_label_set_lines(GTK_LABEL(teco_interface.info_name_widget), 2);
+ gtk_header_bar_set_custom_title(GTK_HEADER_BAR(teco_interface.info_bar_widget),
+ teco_interface.info_name_widget);
+ teco_interface.info_image = gtk_image_new();
+ gtk_header_bar_pack_start(GTK_HEADER_BAR(teco_interface.info_bar_widget),
+ teco_interface.info_image);
+ teco_interface.info_type_widget = gtk_label_new(NULL);
+ gtk_widget_set_valign(teco_interface.info_type_widget, GTK_ALIGN_CENTER);
+ /* eases writing portable fallback.css that avoids CSS element names */
+ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_type_widget),
+ "label");
+ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_type_widget),
+ "type-label");
+ gtk_header_bar_pack_start(GTK_HEADER_BAR(teco_interface.info_bar_widget),
+ teco_interface.info_type_widget);
+ if (teco_interface.no_csd) {
+ /* fall back to adding the info bar as an ordinary widget */
+ gtk_box_pack_start(GTK_BOX(vbox), teco_interface.info_bar_widget,
+ FALSE, FALSE, 0);
+ } else {
+ /* use client-side decorations */
+ gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(teco_interface.info_bar_widget), TRUE);
+ gtk_window_set_titlebar(GTK_WINDOW(teco_interface.window),
+ teco_interface.info_bar_widget);
+ }
+
+ /*
+ * Overlay widget will allow overlaying the Scintilla view
+ * and message widgets with the info popup.
+ * Therefore overlay_vbox (containing the view and popup)
+ * will be the main child of the overlay.
+ */
+ GtkWidget *overlay_widget = gtk_overlay_new();
+ GtkWidget *overlay_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+
+ /*
+ * 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.
+ */
+ teco_interface.event_box_widget = gtk_event_box_new();
+ gtk_event_box_set_above_child(GTK_EVENT_BOX(teco_interface.event_box_widget), TRUE);
+ gtk_box_pack_start(GTK_BOX(overlay_vbox), teco_interface.event_box_widget,
+ TRUE, TRUE, 0);
+
+ teco_interface.message_bar_widget = gtk_info_bar_new();
+ gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar");
+ GtkWidget *message_bar_content =
+ gtk_info_bar_get_content_area(GTK_INFO_BAR(teco_interface.message_bar_widget));
+ /* NOTE: Messages are always pre-canonicalized */
+ teco_interface.message_widget = gtk_label_new(NULL);
+ /* eases writing portable fallback.css that avoids CSS element names */
+ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.message_widget),
+ "label");
+ gtk_label_set_selectable(GTK_LABEL(teco_interface.message_widget), TRUE);
+ gtk_label_set_line_wrap(GTK_LABEL(teco_interface.message_widget), TRUE);
+ gtk_container_add(GTK_CONTAINER(message_bar_content), teco_interface.message_widget);
+ gtk_box_pack_start(GTK_BOX(overlay_vbox), teco_interface.message_bar_widget,
+ FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(overlay_widget), overlay_vbox);
+ gtk_box_pack_start(GTK_BOX(vbox), overlay_widget, TRUE, TRUE, 0);
+
+ teco_interface.cmdline_view = teco_view_new();
+ teco_view_setup(teco_interface.cmdline_view);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SETUNDOCOLLECTION, FALSE, 0);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SETVSCROLLBAR, FALSE, 0);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SETMARGINTYPEN, 1, SC_MARGIN_TEXT);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETSTYLE, 0, STYLE_ASTERISK);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SETMARGINWIDTHN, 1,
+ teco_view_ssm(teco_interface.cmdline_view, SCI_TEXTWIDTH, STYLE_ASTERISK, (sptr_t)"*"));
+ teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETTEXT, 0, (sptr_t)"*");
+ teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETSTYLE, INDIC_CONTROLCHAR, INDIC_ROUNDBOX);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETALPHA, INDIC_CONTROLCHAR, 128);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETSTYLE, INDIC_RUBBEDOUT, INDIC_STRIKE);
+
+ GtkWidget *cmdline_widget = teco_view_get_widget(teco_interface.cmdline_view);
+ gtk_widget_set_name(cmdline_widget, "sciteco-cmdline");
+ g_signal_connect(cmdline_widget, "size-allocate",
+ G_CALLBACK(teco_interface_cmdline_size_allocate_cb), NULL);
+ gtk_box_pack_start(GTK_BOX(vbox), cmdline_widget, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(teco_interface.window), vbox);
+
+ /*
+ * Popup widget will be shown in the bottom
+ * of the overlay widget (i.e. the Scintilla views),
+ * filling the entire width.
+ */
+ teco_interface.popup_widget = teco_gtk_info_popup_new();
+ gtk_widget_set_name(teco_interface.popup_widget, "sciteco-info-popup");
+ gtk_overlay_add_overlay(GTK_OVERLAY(overlay_widget), teco_interface.popup_widget);
+ g_signal_connect(overlay_widget, "get-child-position",
+ G_CALLBACK(teco_gtk_info_popup_get_position_in_overlay), NULL);
+
+ /*
+ * FIXME: Nothing can really take the focus, so it will end up in the
+ * selectable labels unless we explicitly prevent it.
+ */
+ gtk_widget_set_can_focus(teco_interface.message_widget, FALSE);
+ gtk_widget_set_can_focus(teco_interface.info_name_widget, FALSE);
+
+ teco_cmdline_t empty_cmdline;
+ memset(&empty_cmdline, 0, sizeof(empty_cmdline));
+ teco_interface_cmdline_update(&empty_cmdline);
+}
+
+GOptionGroup *
+teco_interface_get_options(void)
+{
+ /*
+ * FIXME: On platforms where you want to disable CSD, you usually
+ * want to disable it always, so it should be configurable in the SciTECO
+ * profile.
+ * On the other hand, you could just install gtk3-nocsd.
+ */
+ static const GOptionEntry entries[] = {
+ {"no-csd", 0, G_OPTION_FLAG_IN_MAIN,
+ G_OPTION_ARG_NONE, &teco_interface.no_csd,
+ "Disable client-side decorations.", NULL},
+ {NULL}
+ };
+
+ /*
+ * Parsing the option context with the Gtk option group
+ * will automatically initialize Gtk, but we do not yet
+ * open the default display.
+ */
+ GOptionGroup *group = gtk_get_option_group(FALSE);
+
+ g_option_group_add_entries(group, entries);
+
+ return group;
+}
+
+void teco_interface_init_color(guint color, guint32 rgb) {}
+
+void
+teco_interface_vmsg(teco_msg_t type, const gchar *fmt, va_list ap)
+{
+ /*
+ * The message types are chosen such that there is a CSS class
+ * for every one of them. GTK_MESSAGE_OTHER does not have
+ * a CSS class.
+ */
+ static const GtkMessageType type2gtk[] = {
+ [TECO_MSG_USER] = GTK_MESSAGE_QUESTION,
+ [TECO_MSG_INFO] = GTK_MESSAGE_INFO,
+ [TECO_MSG_WARNING] = GTK_MESSAGE_WARNING,
+ [TECO_MSG_ERROR] = GTK_MESSAGE_ERROR
+ };
+
+ g_assert(type < G_N_ELEMENTS(type2gtk));
+
+ gchar buf[256];
+
+ /*
+ * stdio_vmsg() leaves `ap` undefined and we are expected
+ * to do the same and behave like vprintf().
+ */
+ va_list aq;
+ va_copy(aq, ap);
+ teco_interface_stdio_vmsg(type, fmt, ap);
+ g_vsnprintf(buf, sizeof(buf), fmt, aq);
+ va_end(aq);
+
+ gtk_info_bar_set_message_type(GTK_INFO_BAR(teco_interface.message_bar_widget),
+ type2gtk[type]);
+ gtk_label_set_text(GTK_LABEL(teco_interface.message_widget), buf);
+
+ if (type == TECO_MSG_ERROR)
+ gtk_widget_error_bell(teco_interface.window);
+}
+
+void
+teco_interface_msg_clear(void)
+{
+ gtk_info_bar_set_message_type(GTK_INFO_BAR(teco_interface.message_bar_widget),
+ GTK_MESSAGE_QUESTION);
+ gtk_label_set_text(GTK_LABEL(teco_interface.message_widget), "");
+}
+
+void
+teco_interface_show_view(teco_view_t *view)
+{
+ teco_interface_current_view = view;
+}
+
+static void
+teco_interface_refresh_info(void)
+{
+ GtkStyleContext *style = gtk_widget_get_style_context(teco_interface.info_bar_widget);
+
+ gtk_style_context_remove_class(style, "info-qregister");
+ gtk_style_context_remove_class(style, "info-buffer");
+ gtk_style_context_remove_class(style, "dirty");
+
+ g_auto(teco_string_t) info_current_temp;
+ teco_string_init(&info_current_temp,
+ teco_interface.info_current.data, teco_interface.info_current.len);
+ if (teco_interface.info_type == TECO_INFO_TYPE_BUFFER_DIRTY)
+ teco_string_append_c(&info_current_temp, '*');
+ teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.info_name_widget),
+ info_current_temp.data, info_current_temp.len);
+ g_autofree gchar *info_current_canon =
+ teco_string_echo(info_current_temp.data, info_current_temp.len);
+
+ const gchar *info_type_str = PACKAGE;
+ g_autoptr(GIcon) icon = NULL;
+
+ switch (teco_interface.info_type) {
+ case TECO_INFO_TYPE_QREG:
+ gtk_style_context_add_class(style, "info-qregister");
+
+ info_type_str = PACKAGE_NAME " - <QRegister> ";
+ gtk_label_set_text(GTK_LABEL(teco_interface.info_type_widget), "QRegister");
+ gtk_label_set_ellipsize(GTK_LABEL(teco_interface.info_name_widget),
+ PANGO_ELLIPSIZE_START);
+
+ /*
+ * FIXME: Perhaps we should use the SciTECO icon for Q-Registers.
+ */
+ icon = g_icon_new_for_string("emblem-generic", NULL);
+ break;
+
+ case TECO_INFO_TYPE_BUFFER_DIRTY:
+ gtk_style_context_add_class(style, "dirty");
+ /* fall through */
+ case TECO_INFO_TYPE_BUFFER:
+ gtk_style_context_add_class(style, "info-buffer");
+
+ info_type_str = PACKAGE_NAME " - <Buffer> ";
+ gtk_label_set_text(GTK_LABEL(teco_interface.info_type_widget), "Buffer");
+ gtk_label_set_ellipsize(GTK_LABEL(teco_interface.info_name_widget),
+ PANGO_ELLIPSIZE_MIDDLE);
+
+ icon = teco_gtk_info_popup_get_icon_for_path(teco_interface.info_current.data,
+ "text-x-generic");
+ break;
+ }
+
+ g_autofree gchar *title = g_strconcat(info_type_str, info_current_canon, NULL);
+ gtk_window_set_title(GTK_WINDOW(teco_interface.window), title);
+
+ if (icon) {
+ gint width, height;
+ gtk_icon_size_lookup(GTK_ICON_SIZE_LARGE_TOOLBAR, &width, &height);
+
+ gtk_image_set_from_gicon(GTK_IMAGE(teco_interface.info_image),
+ icon, GTK_ICON_SIZE_LARGE_TOOLBAR);
+ /* This is necessary so that oversized icons get scaled down. */
+ gtk_image_set_pixel_size(GTK_IMAGE(teco_interface.info_image), height);
+ }
+}
+
+void
+teco_interface_info_update_qreg(const teco_qreg_t *reg)
+{
+ teco_string_clear(&teco_interface.info_current);
+ teco_string_init(&teco_interface.info_current,
+ reg->head.name.data, reg->head.name.len);
+ teco_interface.info_type = TECO_INFO_TYPE_QREG;
+}
+
+void
+teco_interface_info_update_buffer(const teco_buffer_t *buffer)
+{
+ const gchar *filename = buffer->filename ? : UNNAMED_FILE;
+
+ teco_string_clear(&teco_interface.info_current);
+ teco_string_init(&teco_interface.info_current, filename, strlen(filename));
+ teco_interface.info_type = buffer->dirty ? TECO_INFO_TYPE_BUFFER_DIRTY
+ : TECO_INFO_TYPE_BUFFER;
+}
+
+/**
+ * Insert a single character into the command line.
+ *
+ * @fixme
+ * Control characters should be inserted verbatim since the Scintilla
+ * representations of them should be preferred.
+ * However, Scintilla would break the line on every CR/LF and there is
+ * currently no way to prevent this.
+ * Scintilla needs to be patched.
+ *
+ * @see teco_view_set_representations()
+ * @see teco_curses_format_str()
+ */
+static void
+teco_interface_cmdline_insert_c(gchar chr)
+{
+ gchar buffer[3+1] = "";
+
+ /*
+ * NOTE: This mapping is similar to teco_view_set_representations()
+ */
+ switch (chr) {
+ case '\e': strcpy(buffer, "$"); break;
+ case '\r': strcpy(buffer, "CR"); break;
+ case '\n': strcpy(buffer, "LF"); break;
+ case '\t': strcpy(buffer, "TAB"); break;
+ default:
+ if (TECO_IS_CTL(chr)) {
+ buffer[0] = '^';
+ buffer[1] = TECO_CTL_ECHO(chr);
+ buffer[2] = '\0';
+ }
+ }
+
+ if (*buffer) {
+ gsize len = strlen(buffer);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_APPENDTEXT, len, (sptr_t)buffer);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SETINDICATORCURRENT, INDIC_CONTROLCHAR, 0);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_INDICATORFILLRANGE,
+ teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0) - len, len);
+ } else {
+ teco_view_ssm(teco_interface.cmdline_view, SCI_APPENDTEXT, 1, (sptr_t)&chr);
+ }
+}
+
+void
+teco_interface_cmdline_update(const teco_cmdline_t *cmdline)
+{
+ /*
+ * We don't know if the new command line is similar to
+ * the old one, so we can just as well rebuild it.
+ *
+ * NOTE: teco_view_ssm() already locks the GDK lock.
+ */
+ teco_view_ssm(teco_interface.cmdline_view, SCI_CLEARALL, 0, 0);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SCROLLCARET, 0, 0);
+
+ /* format effective command line */
+ for (guint i = 0; i < cmdline->effective_len; i++)
+ teco_interface_cmdline_insert_c(cmdline->str.data[i]);
+
+ /* cursor should be after effective command line */
+ guint pos = teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_GOTOPOS, pos, 0);
+
+ /* format rubbed out command line */
+ for (guint i = cmdline->effective_len; i < cmdline->str.len; i++)
+ teco_interface_cmdline_insert_c(cmdline->str.data[i]);
+
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SETINDICATORCURRENT, INDIC_RUBBEDOUT, 0);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_INDICATORFILLRANGE, pos,
+ teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0) - pos);
+
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SCROLLCARET, 0, 0);
+}
+
+static GdkAtom
+teco_interface_get_selection_by_name(const gchar *name)
+{
+ /*
+ * We can use gdk_atom_intern() to support arbitrary X11 selection
+ * names. However, since we cannot find out which selections are
+ * registered, we are only providing QRegisters for the three default
+ * selections.
+ * Checking them here avoids expensive X server roundtrips.
+ */
+ switch (*name) {
+ case '\0': return GDK_NONE;
+ case 'P': return GDK_SELECTION_PRIMARY;
+ case 'S': return GDK_SELECTION_SECONDARY;
+ case 'C': return GDK_SELECTION_CLIPBOARD;
+ default: break;
+ }
+
+ return gdk_atom_intern(name, FALSE);
+}
+
+gboolean
+teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error)
+{
+ GtkClipboard *clipboard = gtk_clipboard_get(teco_interface_get_selection_by_name(name));
+
+ /*
+ * NOTE: function has compatible semantics for str_len < 0.
+ */
+ gtk_clipboard_set_text(clipboard, str, str_len);
+
+ return TRUE;
+}
+
+gboolean
+teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
+{
+ GtkClipboard *clipboard = gtk_clipboard_get(teco_interface_get_selection_by_name(name));
+ /*
+ * Could return NULL for an empty clipboard.
+ *
+ * FIXME: This converts to UTF8 and we loose the ability
+ * to get clipboard with embedded nulls.
+ */
+ g_autofree gchar *contents = gtk_clipboard_wait_for_text(clipboard);
+
+ *len = contents ? strlen(contents) : 0;
+ if (str)
+ *str = g_steal_pointer(&contents);
+
+ return TRUE;
+}
+
+void
+teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize name_len,
+ gboolean highlight)
+{
+ teco_gtk_info_popup_add(TECO_GTK_INFO_POPUP(teco_interface.popup_widget),
+ type, name, name_len, highlight);
+}
+
+void
+teco_interface_popup_show(void)
+{
+ if (gtk_widget_get_visible(teco_interface.popup_widget))
+ teco_gtk_info_popup_scroll_page(TECO_GTK_INFO_POPUP(teco_interface.popup_widget));
+ else
+ gtk_widget_show(teco_interface.popup_widget);
+}
+
+gboolean
+teco_interface_popup_is_shown(void)
+{
+ return gtk_widget_get_visible(teco_interface.popup_widget);
+}
+
+void
+teco_interface_popup_clear(void)
+{
+ if (gtk_widget_get_visible(teco_interface.popup_widget)) {
+ gtk_widget_hide(teco_interface.popup_widget);
+ teco_gtk_info_popup_clear(TECO_GTK_INFO_POPUP(teco_interface.popup_widget));
+ }
+}
+
+/**
+ * Whether the execution has been interrupted (CTRL+C).
+ *
+ * This is called regularily, so it is used to drive the
+ * main loop so that we can still process key presses.
+ *
+ * This approach is significantly slower in interactive mode
+ * than executing in a separate thread probably due to the
+ * system call overhead.
+ * But the GDK lock that would be necessary for synchronization
+ * has been deprecated.
+ */
+gboolean
+teco_interface_is_interrupted(void)
+{
+ if (gtk_main_level() > 0)
+ gtk_main_iteration_do(FALSE);
+
+ return teco_sigint_occurred != FALSE;
+}
+
+static void
+teco_interface_set_css_variables(teco_view_t *view)
+{
+ guint32 default_fg_color = teco_view_ssm(view, SCI_STYLEGETFORE, STYLE_DEFAULT, 0);
+ guint32 default_bg_color = teco_view_ssm(view, SCI_STYLEGETBACK, STYLE_DEFAULT, 0);
+ guint32 calltip_fg_color = teco_view_ssm(view, SCI_STYLEGETFORE, STYLE_CALLTIP, 0);
+ guint32 calltip_bg_color = teco_view_ssm(view, SCI_STYLEGETBACK, STYLE_CALLTIP, 0);
+
+ /*
+ * FIXME: Font and colors of Scintilla views cannot be set via CSS.
+ * But some day, there will be a way to send messages to the commandline view
+ * from SciTECO code via ES.
+ * Configuration will then be in the hands of color schemes.
+ *
+ * NOTE: We don't actually know apriori how large the font_size buffer should be,
+ * but luckily SCI_STYLEGETFONT with a sptr==0 will return only the size.
+ * This is undocumented in the Scintilla docs.
+ */
+ g_autofree gchar *font_name = g_malloc(teco_view_ssm(view, SCI_STYLEGETFONT, STYLE_DEFAULT, 0) + 1);
+ teco_view_ssm(view, SCI_STYLEGETFONT, STYLE_DEFAULT, (sptr_t)font_name);
+
+ teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFORE, STYLE_DEFAULT, default_fg_color);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBACK, STYLE_DEFAULT, default_bg_color);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)font_name);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETSIZE, STYLE_DEFAULT,
+ teco_view_ssm(view, SCI_STYLEGETSIZE, STYLE_DEFAULT, 0));
+ teco_view_ssm(teco_interface.cmdline_view, SCI_STYLECLEARALL, 0, 0);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFORE, STYLE_CALLTIP, calltip_fg_color);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBACK, STYLE_CALLTIP, calltip_bg_color);
+ teco_view_ssm(teco_interface.cmdline_view, SCI_SETCARETFORE, default_fg_color, 0);
+ /* used for the asterisk at the beginning of the command line */
+ teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBOLD, STYLE_ASTERISK, TRUE);
+ /* used for character representations */
+ teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETFORE, INDIC_CONTROLCHAR, default_fg_color);
+ /* used for the rubbed out command line */
+ teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETFORE, INDIC_RUBBEDOUT, default_fg_color);
+ /* this somehow gets reset */
+ teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETTEXT, 0, (sptr_t)"*");
+
+ guint text_height = teco_view_ssm(teco_interface.cmdline_view, SCI_TEXTHEIGHT, 0, 0);
+
+ /*
+ * Generates a CSS that sets some predefined color variables.
+ * This effectively "exports" Scintilla styles into the CSS
+ * world.
+ * Those colors are used by the fallback.css shipping with SciTECO
+ * in order to apply the SciTECO-controlled color scheme to all the
+ * predefined UI elements.
+ * They can also be used in user-customizations.
+ */
+ gchar css[256];
+ g_snprintf(css, sizeof(css),
+ "@define-color sciteco_default_fg_color " CSS_COLOR_FORMAT ";"
+ "@define-color sciteco_default_bg_color " CSS_COLOR_FORMAT ";"
+ "@define-color sciteco_calltip_fg_color " CSS_COLOR_FORMAT ";"
+ "@define-color sciteco_calltip_bg_color " CSS_COLOR_FORMAT ";",
+ teco_bgr2rgb(default_fg_color), teco_bgr2rgb(default_bg_color),
+ teco_bgr2rgb(calltip_fg_color), teco_bgr2rgb(calltip_bg_color));
+
+ /*
+ * The GError and return value has been deprecated.
+ * A CSS parsing error would point to a programming
+ * error anyway.
+ */
+ gtk_css_provider_load_from_data(teco_interface.css_var_provider, css, -1, NULL);
+
+ /*
+ * The font and size of the commandline view might have changed,
+ * so we resize it.
+ * This cannot be done via CSS or Scintilla messages.
+ * Currently, it is always exactly one line high in order to mimic the Curses UI.
+ */
+ gtk_widget_set_size_request(teco_view_get_widget(teco_interface.cmdline_view), -1, text_height);
+}
+
+static gboolean
+teco_interface_handle_key_press(guint keyval, guint state, GError **error)
+{
+ teco_view_t *last_view = teco_interface_current_view;
+
+ switch (keyval) {
+ case GDK_KEY_Escape:
+ if (!teco_cmdline_keypress_c('\e', error))
+ return FALSE;
+ break;
+ case GDK_KEY_BackSpace:
+ if (!teco_cmdline_keypress_c(TECO_CTL_KEY('H'), error))
+ return FALSE;
+ break;
+ case GDK_KEY_Tab:
+ if (!teco_cmdline_keypress_c('\t', error))
+ return FALSE;
+ break;
+ case GDK_KEY_Return:
+ if (!teco_cmdline_keypress_c('\n', error))
+ return FALSE;
+ break;
+
+ /*
+ * Function key macros
+ */
+#define FN(KEY, MACRO) \
+ case GDK_KEY_##KEY: \
+ if (!teco_cmdline_fnmacro(#MACRO, error)) \
+ return FALSE; \
+ break
+#define FNS(KEY, MACRO) \
+ case GDK_KEY_##KEY: \
+ if (!teco_cmdline_fnmacro(state & GDK_SHIFT_MASK ? "S" #MACRO : #MACRO, error)) \
+ return FALSE; \
+ 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);
+ if (!teco_cmdline_fnmacro(macro_name, error))
+ return FALSE;
+ 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)
+ break;
+
+ gchar key;
+
+ g_unichar_to_utf8(u, &key);
+ if (key > 0x7F)
+ break;
+ if (state & GDK_CONTROL_MASK)
+ key = TECO_CTL_KEY(g_ascii_toupper(key));
+
+ if (!teco_cmdline_keypress_c(key, error))
+ return FALSE;
+ }
+ }
+
+ /*
+ * The styles configured via Scintilla might change
+ * with every keypress.
+ */
+ teco_interface_set_css_variables(teco_interface_current_view);
+
+ /*
+ * 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.
+ */
+ teco_interface_refresh_info();
+
+ if (teco_interface_current_view != last_view) {
+ /*
+ * The last view's object is not guaranteed to
+ * still exist.
+ * However its widget is, due to reference counting.
+ */
+ if (teco_interface.current_view_widget)
+ gtk_container_remove(GTK_CONTAINER(teco_interface.event_box_widget),
+ teco_interface.current_view_widget);
+
+ teco_interface.current_view_widget = teco_view_get_widget(teco_interface_current_view);
+
+ gtk_container_add(GTK_CONTAINER(teco_interface.event_box_widget),
+ teco_interface.current_view_widget);
+ gtk_widget_show(teco_interface.current_view_widget);
+ }
+
+ return TRUE;
+}
+
+gboolean
+teco_interface_event_loop(GError **error)
+{
+ static const gchar *icon_files[] = {
+ SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-48.png",
+ SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-32.png",
+ SCITECODATADIR G_DIR_SEPARATOR_S "sciteco-16.png",
+ NULL
+ };
+
+ /*
+ * Assign an icon to the window.
+ *
+ * FIXME: On Windows, it may be better to load the icon compiled
+ * as a resource into the binary.
+ */
+ GList *icon_list = NULL;
+
+ for (const gchar **file = icon_files; *file; file++) {
+ GdkPixbuf *icon_pixbuf = gdk_pixbuf_new_from_file(*file, NULL);
+
+ /* fail silently if there's a problem with one of the icons */
+ if (icon_pixbuf)
+ icon_list = g_list_append(icon_list, icon_pixbuf);
+ }
+
+ gtk_window_set_default_icon_list(icon_list);
+
+ g_list_free_full(icon_list, g_object_unref);
+
+ teco_interface_refresh_info();
+
+ /*
+ * Initialize the CSS variable provider and the CSS provider
+ * for the included fallback.css.
+ */
+ teco_interface.css_var_provider = gtk_css_provider_new();
+ if (teco_interface_current_view)
+ /* set CSS variables initially */
+ teco_interface_set_css_variables(teco_interface_current_view);
+ GdkScreen *default_screen = gdk_screen_get_default();
+ gtk_style_context_add_provider_for_screen(default_screen,
+ GTK_STYLE_PROVIDER(teco_interface.css_var_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ /* get path of $SCITECOCONFIG/.teco_css */
+ teco_qreg_t *config_path_reg = teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECOCONFIG", 14);
+ g_assert(config_path_reg != NULL);
+ g_auto(teco_string_t) config_path = {NULL, 0};
+ if (!config_path_reg->vtable->get_string(config_path_reg, &config_path.data, &config_path.len, error))
+ return FALSE;
+ if (teco_string_contains(&config_path, '\0')) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Null-character not allowed in filenames");
+ return FALSE;
+ }
+ g_autofree gchar *user_css_file = g_build_filename(config_path.data, USER_CSS_FILE, NULL);
+
+ GtkCssProvider *user_css_provider = gtk_css_provider_new();
+ /*
+ * NOTE: The return value of gtk_css_provider_load() is deprecated.
+ * Instead we could register for the "parsing-error" signal.
+ * For the time being we just silently ignore parsing errors.
+ * They will be printed to stderr by Gtk anyway.
+ */
+ if (g_file_test(user_css_file, G_FILE_TEST_IS_REGULAR))
+ /* open user CSS */
+ gtk_css_provider_load_from_path(user_css_provider, user_css_file, NULL);
+ else
+ /* use fallback CSS */
+ gtk_css_provider_load_from_path(user_css_provider,
+ SCITECODATADIR G_DIR_SEPARATOR_S "fallback.css",
+ NULL);
+ gtk_style_context_add_provider_for_screen(default_screen,
+ GTK_STYLE_PROVIDER(user_css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER);
+
+ /*
+ * 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 (teco_interface_current_view) {
+ teco_interface.current_view_widget = teco_view_get_widget(teco_interface_current_view);
+ gtk_container_add(GTK_CONTAINER(teco_interface.event_box_widget),
+ teco_interface.current_view_widget);
+ }
+
+ gtk_widget_show_all(teco_interface.window);
+ /* don't show popup by default */
+ gtk_widget_hide(teco_interface.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).
+ *
+ * FIXME: On ^Z, we do not suspend properly. The window is still shown.
+ * Perhaps we should try to catch SIGTSTP?
+ * This does not work with g_unix_signal_add(), though, so any
+ * workaround would be tricky.
+ * We could create a pipe via g_unix_open_pipe() which we
+ * write to using write() in a normal signal handler.
+ * We can then add a watcher using g_unix_fd_add() which will
+ * hide the main window.
+ */
+#ifdef G_OS_UNIX
+ g_unix_signal_add(SIGTERM, teco_interface_sigterm_handler, NULL);
+#endif
+
+ gtk_main();
+
+ /*
+ * Make sure the window is hidden
+ * now already, as there may be code that has to be
+ * executed in batch mode.
+ */
+ gtk_widget_hide(teco_interface.window);
+
+ return TRUE;
+}
+
+void
+teco_interface_cleanup(void)
+{
+ teco_string_clear(&teco_interface.info_current);
+
+ if (teco_interface.window)
+ gtk_widget_destroy(teco_interface.window);
+
+ scintilla_release_resources();
+
+ if (teco_interface.event_queue)
+ g_queue_free_full(teco_interface.event_queue,
+ (GDestroyNotify)gdk_event_free);
+
+ if (teco_interface.css_var_provider)
+ g_object_unref(teco_interface.css_var_provider);
+}
+
+/*
+ * GTK+ callbacks
+ */
+
+/**
+ * Called when the commandline widget is resized.
+ * This should ensure that the caret jumps to the middle of the command line,
+ * imitating the behaviour of the current Curses command line.
+ */
+static void
+teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
+ GdkRectangle *allocation, gpointer user_data)
+{
+ /*
+ * The GDK lock is already held, so we avoid using teco_view_ssm().
+ */
+ scintilla_send_message(SCINTILLA(widget), SCI_SETXCARETPOLICY,
+ CARET_SLOP, allocation->width/2);
+}
+
+static gboolean
+teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+
+#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
+
+ if (teco_cmdline.pc < teco_cmdline.effective_len) {
+ /*
+ * We're already executing, so this event is processed
+ * from gtk_main_iteration_do().
+ * Unfortunately, gtk_main_level() is still 1 in this case.
+ *
+ * We might also completely replace the watchers
+ * during execution, but the current implementation is
+ * probably easier.
+ */
+ if (event->state & GDK_CONTROL_MASK &&
+ 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 `teco_sigint_occurred`.
+ * If the execution thread is currently blocking,
+ * the key is delivered like an ordinary key press.
+ */
+ teco_interrupt();
+ else
+ g_queue_push_tail(teco_interface.event_queue,
+ gdk_event_copy((GdkEvent *)event));
+
+ return TRUE;
+ }
+
+ g_queue_push_tail(teco_interface.event_queue, gdk_event_copy((GdkEvent *)event));
+
+ /*
+ * 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.
+ */
+ GdkWindow *top_window = gdk_window_get_toplevel(gtk_widget_get_window(teco_interface.window));
+ /*
+ * FIXME: A simple freeze will not suffice to prevent updates in code like <Sx$;>.
+ * gdk_window_freeze_toplevel_updates_libgtk_only() is deprecated, though.
+ * Perhaps this hack is no longer required after upgrading Scintilla.
+ *
+ * For the time being, we just live with the expected deprecation warnings,
+ * although they could theoretically be suppressed using
+ * `#pragma GCC diagnostic ignored`.
+ */
+ //gdk_window_freeze_updates(top_window);
+ gdk_window_freeze_toplevel_updates_libgtk_only(top_window);
+
+ /*
+ * The event queue might be filled when pressing keys when SciTECO
+ * is busy executing code.
+ */
+ do {
+ g_autoptr(GdkEvent) event = g_queue_pop_head(teco_interface.event_queue);
+
+ teco_sigint_occurred = FALSE;
+ teco_interface_handle_key_press(event->key.keyval, event->key.state, &error);
+ teco_sigint_occurred = FALSE;
+
+ if (g_error_matches(error, TECO_ERROR, TECO_ERROR_QUIT)) {
+ gtk_main_quit();
+ break;
+ }
+ } while (!g_queue_is_empty(teco_interface.event_queue));
+
+ gdk_window_thaw_toplevel_updates_libgtk_only(top_window);
+ //gdk_window_thaw_updates(top_window);
+
+ return TRUE;
+}
+
+static gboolean
+teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer user_data)
+{
+ /*
+ * 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.
+ */
+ g_autoptr(GdkEvent) close_event = gdk_event_new(GDK_KEY_PRESS);
+ close_event->key.window = gtk_widget_get_parent_window(widget);
+ close_event->key.keyval = GDK_KEY_Close;
+
+ return teco_interface_key_pressed_cb(widget, &close_event->key, NULL);
+}
+
+static gboolean
+teco_interface_sigterm_handler(gpointer user_data)
+{
+ /*
+ * Since this handler replaces the default signal handler,
+ * we also have to make sure it interrupts.
+ */
+ teco_interrupt();
+
+ /*
+ * Similar to window deletion - emulate "close" key press.
+ */
+ g_autoptr(GdkEvent) close_event = gdk_event_new(GDK_KEY_PRESS);
+ close_event->key.keyval = GDK_KEY_Close;
+
+ return teco_interface_key_pressed_cb(teco_interface.window, &close_event->key, NULL);
+}
diff --git a/src/interface-gtk/teco-gtk-info-popup.gob b/src/interface-gtk/teco-gtk-info-popup.gob
new file mode 100644
index 0000000..f08b0d7
--- /dev/null
+++ b/src/interface-gtk/teco-gtk-info-popup.gob
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2012-2021 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/>.
+ */
+
+requires 2.0.20
+
+%ctop{
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <math.h>
+
+#include <glib/gprintf.h>
+
+#include "list.h"
+#include "string-utils.h"
+#include "teco-gtk-label.h"
+%}
+
+%h{
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include <gio/gio.h>
+
+#include "interface.h"
+%}
+
+%{
+/*
+ * FIXME: This is redundant with curses-info-popup.c.
+ */
+typedef struct {
+ teco_stailq_entry_t entry;
+
+ teco_popup_entry_type_t type;
+ teco_string_t name;
+ gboolean highlight;
+} teco_popup_entry_t;
+%}
+
+/*
+ * NOTE: Deriving from GtkEventBox ensures that we can
+ * set a background on the entire popup widget.
+ */
+class Teco:Gtk:Info:Popup from Gtk:Event:Box {
+ /* These are added to other widgets, so they don't have to be destroyed */
+ public GtkAdjustment *hadjustment = {gtk_adjustment_new(0, 0, 0, 0, 0, 0)};
+ public GtkAdjustment *vadjustment = {gtk_adjustment_new(0, 0, 0, 0, 0, 0)};
+
+ private GtkWidget *flow_box = {gtk_flow_box_new()};
+
+ private GStringChunk *chunk = {g_string_chunk_new(32)}
+ destroywith g_string_chunk_free;
+ private teco_stailq_head_t list
+ destroy {
+ teco_stailq_entry_t *entry;
+ while ((entry = teco_stailq_remove_head(&VAR)))
+ g_free(entry);
+ };
+ private guint idle_id = 0;
+ private gboolean frozen = FALSE;
+
+ init(self)
+ {
+ /*
+ * 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.
+ */
+ GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+
+ GtkWidget *scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL,
+ self->vadjustment);
+ /* show/hide the scrollbar dynamically */
+ g_signal_connect(self->vadjustment, "changed",
+ G_CALLBACK(self_vadjustment_changed), scrollbar);
+
+ /* take as little height as necessary */
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(selfp->flow_box),
+ GTK_ORIENTATION_HORIZONTAL);
+ //gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(selfp->flow_box), TRUE);
+ /* this for focus handling only, not for scrolling */
+ gtk_flow_box_set_hadjustment(GTK_FLOW_BOX(selfp->flow_box),
+ self->hadjustment);
+ gtk_flow_box_set_vadjustment(GTK_FLOW_BOX(selfp->flow_box),
+ self->vadjustment);
+
+ GtkWidget *viewport = gtk_viewport_new(self->hadjustment, self->vadjustment);
+ gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
+ gtk_container_add(GTK_CONTAINER(viewport), selfp->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);
+
+ /*
+ * NOTE: Everything shown except the top-level container.
+ * Therefore a gtk_widget_show() is enough to show our popup.
+ */
+ gtk_container_add(GTK_CONTAINER(self), box);
+
+ selfp->list = TECO_STAILQ_HEAD_INITIALIZER(&selfp->list);
+ }
+
+ /**
+ * Allocate position in an overlay.
+ *
+ * This function can be used as the "get-child-position" signal
+ * handler of a GtkOverlay in order to position the popup at the
+ * bottom of the overlay's main child, spanning the entire width.
+ * In contrast to the GtkOverlay's default allocation schemes,
+ * this makes sure that the widget will not be larger than the
+ * main child, so the popup properly scrolls when becoming too large
+ * in height.
+ *
+ * @param user_data unused by this callback
+ */
+ public gboolean
+ get_position_in_overlay(Gtk:Overlay *overlay, Gtk:Widget *widget,
+ Gdk:Rectangle *allocation, gpointer user_data)
+ {
+ GtkWidget *main_child = gtk_bin_get_child(GTK_BIN(overlay));
+ GtkAllocation main_child_alloc;
+ gint natural_height;
+
+ gtk_widget_get_allocation(main_child, &main_child_alloc);
+ gtk_widget_get_preferred_height_for_width(widget,
+ main_child_alloc.width,
+ NULL, &natural_height);
+
+ /*
+ * FIXME: Probably due to some bug in the height-for-width
+ * calculation of Gtk (at least in 3.10 or in the GtkFlowBox
+ * fallback included with SciTECO), the natural height
+ * is a bit too small to accommodate the entire GtkFlowBox,
+ * resulting in the GtkViewport always scrolling.
+ * This hack fixes it up in a NONPORTABLE manner.
+ */
+ natural_height += 5;
+
+ allocation->width = main_child_alloc.width;
+ allocation->height = MIN(natural_height, main_child_alloc.height);
+ allocation->x = 0;
+ allocation->y = main_child_alloc.height - allocation->height;
+
+ return TRUE;
+ }
+
+ /*
+ * Adapted from GtkScrolledWindow's gtk_scrolled_window_scroll_event()
+ * since the viewport does not react to scroll events.
+ * This is registered for our container widget instead of only for
+ * GtkViewport since this is what GtkScrolledWindow does.
+ *
+ * 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, Gdk:Event:Scroll *event)
+ {
+ Self *self = SELF(widget);
+ gdouble delta_x, delta_y;
+
+ if (!gdk_event_get_scroll_deltas((GdkEvent *)event,
+ &delta_x, &delta_y))
+ return FALSE;
+
+ 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;
+ }
+
+ private void
+ vadjustment_changed(Gtk:Adjustment *vadjustment, Gtk:Widget *scrollbar)
+ {
+ /*
+ * This shows/hides the widget using opacity instead of using
+ * gtk_widget_set_visibility() since the latter would influence
+ * size allocations. A widget with opacity 0 keeps its size.
+ */
+ gtk_widget_set_opacity(scrollbar,
+ gtk_adjustment_get_upper(vadjustment) -
+ gtk_adjustment_get_lower(vadjustment) >
+ gtk_adjustment_get_page_size(vadjustment) ? 1 : 0);
+ }
+
+ public GtkWidget *
+ new(void)
+ {
+ return GTK_WIDGET(GET_NEW);
+ }
+
+ public GIcon *
+ get_icon_for_path(const gchar *path, const gchar *fallback_name)
+ {
+ GIcon *icon = NULL;
+
+ g_autoptr(GFile) file = g_file_new_for_path(path);
+ g_autoptr(GFileInfo) info = g_file_query_info(file, "standard::icon", 0, NULL, NULL);
+ if (info) {
+ icon = g_file_info_get_icon(info);
+ g_object_ref(icon);
+ } else {
+ /* fall back to standard icon, but this can still return NULL! */
+ icon = g_icon_new_for_string(fallback_name, NULL);
+ }
+
+ return icon;
+ }
+
+ public void
+ add(self, teco_popup_entry_type_t type,
+ const gchar *name, gssize len, gboolean highlight)
+ {
+ teco_popup_entry_t *entry = g_new(teco_popup_entry_t, 1);
+ entry->type = type;
+ /*
+ * Popup entries aren't removed individually, so we can
+ * more efficiently store them via GStringChunk.
+ */
+ teco_string_init_chunk(&entry->name, name, len < 0 ? strlen(name) : len,
+ selfp->chunk);
+ entry->highlight = highlight;
+
+ /*
+ * NOTE: We don't immediately create the Gtk+ widget and add it
+ * to the GtkFlowBox since it would be too slow for very large
+ * numbers of popup entries.
+ * Instead, we queue and process them in idle time only once the widget
+ * is shown. This ensures a good reactivity, even though the popup may
+ * not yet be complete when first shown.
+ *
+ * While it would be possible to show the widget before the first
+ * add() call to achieve the same effect, this would prevent keyboard
+ * interaction unless we add support for interruptions or drive
+ * the event loop manually.
+ */
+ teco_stailq_insert_tail(&selfp->list, &entry->entry);
+ }
+
+ override (Gtk:Widget) void
+ show(Gtk:Widget *widget)
+ {
+ Self *self = SELF(widget);
+
+ if (!selfp->idle_id) {
+ selfp->idle_id = gdk_threads_add_idle((GSourceFunc)self_idle_cb, self);
+
+ /*
+ * To prevent a visible popup build-up for small popups,
+ * the display is frozen until the popup is large enough for
+ * scrolling or until all entries have been added.
+ */
+ GdkWindow *window = gtk_widget_get_window(widget);
+ if (window) {
+ gdk_window_freeze_updates(window);
+ selfp->frozen = TRUE;
+ }
+ }
+
+ PARENT_HANDLER(widget);
+ }
+
+ private gboolean
+ idle_cb(self)
+ {
+ /*
+ * The more often this is repeated, the faster we will add all popup entries,
+ * but at the same time, the UI will be less responsive.
+ */
+ for (gint i = 0; i < 5; i++) {
+ teco_popup_entry_t *head = (teco_popup_entry_t *)teco_stailq_remove_head(&selfp->list);
+ if (G_UNLIKELY(!head)) {
+ if (selfp->frozen)
+ gdk_window_thaw_updates(gtk_widget_get_window(GTK_WIDGET(self)));
+ selfp->frozen = FALSE;
+ selfp->idle_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ self_idle_add(self, head->type, head->name.data, head->name.len, head->highlight);
+
+ /* All teco_popup_entry_t::names are freed via GStringChunk */
+ g_free(head);
+ }
+
+ if (selfp->frozen &&
+ gtk_adjustment_get_upper(self->vadjustment) -
+ gtk_adjustment_get_lower(self->vadjustment) > gtk_adjustment_get_page_size(self->vadjustment)) {
+ /* the GtkFlowBox needs scrolling - time to thaw */
+ gdk_window_thaw_updates(gtk_widget_get_window(GTK_WIDGET(self)));
+ selfp->frozen = FALSE;
+ }
+
+ return G_SOURCE_CONTINUE;
+ }
+
+ private void
+ idle_add(self, teco_popup_entry_type_t type,
+ const gchar *name, gssize len, gboolean highlight)
+ {
+ GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
+ if (highlight)
+ gtk_style_context_add_class(gtk_widget_get_style_context(hbox),
+ "highlight");
+
+ /*
+ * FIXME: The icon fetching takes about 1/3 of the time required to
+ * add all widgets.
+ * Perhaps it's possible to optimize this.
+ */
+ if (type == TECO_POPUP_FILE || type == TECO_POPUP_DIRECTORY) {
+ const gchar *fallback = type == TECO_POPUP_FILE ? "text-x-generic"
+ : "folder";
+
+ /*
+ * `name` is not guaranteed to be null-terminated.
+ */
+ g_autofree gchar *path = len < 0 ? g_strdup(name) : g_strndup(name, len);
+
+ g_autoptr(GIcon) icon = self_get_icon_for_path(path, fallback);
+ if (icon) {
+ gint width, height;
+ gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
+
+ GtkWidget *image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU);
+ /* This is necessary so that oversized icons get scaled down. */
+ gtk_image_set_pixel_size(GTK_IMAGE(image), height);
+ gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
+ }
+ }
+
+ GtkWidget *label = teco_gtk_label_new(name, len);
+ /*
+ * Gtk v3.20 changed the CSS element names.
+ * Adding a style class eases writing a portable fallback.css.
+ */
+ gtk_style_context_add_class(gtk_widget_get_style_context(label), "label");
+ gtk_widget_set_halign(label, GTK_ALIGN_START);
+ gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
+
+ /*
+ * FIXME: This makes little sense once we've got mouse support.
+ * But for the time being, it's a useful setting.
+ */
+ gtk_label_set_selectable(GTK_LABEL(label), TRUE);
+
+ switch (type) {
+ case TECO_POPUP_PLAIN:
+ gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_START);
+ break;
+ case TECO_POPUP_FILE:
+ case TECO_POPUP_DIRECTORY:
+ gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
+ break;
+ }
+
+ gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+
+ gtk_widget_show_all(hbox);
+ gtk_container_add(GTK_CONTAINER(selfp->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 */
+ 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.
+ */
+ GList *child_list = gtk_container_get_children(GTK_CONTAINER(selfp->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);
+ }
+
+ private void
+ destroy_cb(Gtk:Widget *widget, gpointer user_data)
+ {
+ gtk_widget_destroy(widget);
+ }
+
+ public void
+ clear(self)
+ {
+ gtk_container_foreach(GTK_CONTAINER(selfp->flow_box), self_destroy_cb, NULL);
+
+ /*
+ * If there are still queued popoup entries, the next self_idle_cb()
+ * invocation will also stop the GSource.
+ */
+ teco_stailq_entry_t *entry;
+ while ((entry = teco_stailq_remove_head(&selfp->list)))
+ g_free(entry);
+
+ g_string_chunk_clear(selfp->chunk);
+ }
+}
diff --git a/src/interface-gtk/gtk-canonicalized-label.gob b/src/interface-gtk/teco-gtk-label.gob
index c6adeeb..2167dbc 100644
--- a/src/interface-gtk/gtk-canonicalized-label.gob
+++ b/src/interface-gtk/teco-gtk-label.gob
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2017 Robin Haberkorn
+ * Copyright (C) 2012-2021 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
@@ -28,68 +28,86 @@ requires 2.0.20
#include <gdk/gdk.h>
-/*
- * NOTE: These definitions are also in sciteco.h,
- * but we cannot include them from a plain C file.
- */
-#define IS_CTL(C) ((C) < ' ')
-#define CTL_ECHO(C) ((C) | 0x40)
-#define CTL_KEY_ESC 27
+#include "sciteco.h"
+#include "string-utils.h"
-#define GDK_TO_PANGO_COLOR(X) \
- ((guint16)((X) * G_MAXUINT16))
+#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16))
%}
%h{
#include <gtk/gtk.h>
%}
-class Gtk:Canonicalized:Label from Gtk:Label {
+class Teco:Gtk:Label from Gtk:Label {
private PangoColor fg;
private guint16 fg_alpha;
private PangoColor bg;
private guint16 bg_alpha;
+ private teco_string_t string
+ destroy {
+ teco_string_clear(&VAR);
+ };
+
override (Gtk:Widget) void
style_updated(Gtk:Widget *widget)
{
Self *self = SELF(widget);
- GtkStyleContext *style;
- GdkRGBA normal_color;
-
PARENT_HANDLER(widget);
- style = gtk_widget_get_style_context(widget);
+ GtkStyleContext *style = gtk_widget_get_style_context(widget);
+ GdkRGBA normal_color;
gtk_style_context_get_color(style, GTK_STATE_NORMAL, &normal_color);
- self->_priv->bg.red = GDK_TO_PANGO_COLOR(normal_color.red);
- self->_priv->bg.green = GDK_TO_PANGO_COLOR(normal_color.green);
- self->_priv->bg.blue = GDK_TO_PANGO_COLOR(normal_color.blue);
- self->_priv->bg_alpha = GDK_TO_PANGO_COLOR(normal_color.alpha);
+ selfp->bg.red = GDK_TO_PANGO_COLOR(normal_color.red);
+ selfp->bg.green = GDK_TO_PANGO_COLOR(normal_color.green);
+ selfp->bg.blue = GDK_TO_PANGO_COLOR(normal_color.blue);
+ selfp->bg_alpha = GDK_TO_PANGO_COLOR(normal_color.alpha);
/*
* If Pango does not support transparent foregrounds,
* it will at least use a high-contrast foreground.
+ *
* NOTE: It would be very hard to get an appropriate background
* color even if Gtk supports it since the label itself may
* not have one but one of its parents.
+ *
* FIXME: We may want to honour the background color,
* so we can at least get decent reverse text when setting
* the background color in the CSS.
*/
- self->_priv->fg.red = G_MAXUINT16 - self->_priv->bg.red;
- self->_priv->fg.green = G_MAXUINT16 - self->_priv->bg.green;
- self->_priv->fg.blue = G_MAXUINT16 - self->_priv->bg.blue;
+ selfp->fg.red = normal_color.red > 0.5 ? 0 : G_MAXUINT16;
+ selfp->fg.green = normal_color.green > 0.5 ? 0 : G_MAXUINT16;
+ selfp->fg.blue = normal_color.blue > 0.5 ? 0 : G_MAXUINT16;
/* try hard to get a transparent foreground anyway */
- self->_priv->fg_alpha = G_MAXUINT16;
+ selfp->fg_alpha = 0;
+
+ /*
+ * The widget might be styled after the text has been set on it,
+ * we must recreate the Pango attributes.
+ */
+ if (selfp->string.len > 0) {
+ PangoAttrList *attribs = NULL;
+ g_autofree gchar *plaintext = NULL;
+
+ self_parse_string(selfp->string.data, selfp->string.len,
+ &selfp->fg, selfp->fg_alpha,
+ &selfp->bg, selfp->bg_alpha,
+ &attribs, &plaintext);
+
+ gtk_label_set_attributes(GTK_LABEL(self), attribs);
+ pango_attr_list_unref(attribs);
+
+ g_assert(!g_strcmp0(plaintext, gtk_label_get_text(GTK_LABEL(self))));
+ }
}
public GtkWidget *
- new(const gchar *str)
+ new(const gchar *str, gssize len)
{
Self *widget = GET_NEW;
- self_set_text(widget, str);
+ self_set_text(widget, str, len);
return GTK_WIDGET(widget);
}
@@ -102,6 +120,11 @@ class Gtk:Canonicalized:Label from Gtk:Label {
{
PangoAttribute *attr;
+ /*
+ * NOTE: Transparent foreground do not seem to work,
+ * even in Pango v1.38.
+ * Perhaps, this has been fixed in later versions.
+ */
#if PANGO_VERSION_CHECK(1,38,0)
attr = pango_attr_foreground_alpha_new(fg_alpha);
attr->start_index = index;
@@ -131,28 +154,27 @@ class Gtk:Canonicalized:Label from Gtk:Label {
Pango:Color *bg, guint16 bg_alpha,
Pango:Attr:List **attribs, gchar **text)
{
- gsize text_len = 1; /* for trailing 0 */
- gint index = 0;
-
if (len < 0)
len = strlen(str);
/*
* Approximate size of unformatted text.
*/
+ gsize text_len = 1; /* for trailing 0 */
for (gint i = 0; i < len; i++)
- text_len += IS_CTL(str[i]) ? 3 : 1;
+ text_len += TECO_IS_CTL(str[i]) ? 3 : 1;
*attribs = pango_attr_list_new();
*text = g_malloc(text_len);
+ gint index = 0;
while (len > 0) {
/*
* NOTE: This mapping is similar to
- * View::set_presentations()
+ * teco_view_set_presentations()
*/
switch (*str) {
- case CTL_KEY_ESC:
+ case '\e':
self_add_highlight_attribs(*attribs,
fg, fg_alpha,
bg, bg_alpha,
@@ -185,13 +207,13 @@ class Gtk:Canonicalized:Label from Gtk:Label {
(*text)[index++] = 'B';
break;
default:
- if (IS_CTL(*str)) {
+ if (TECO_IS_CTL(*str)) {
self_add_highlight_attribs(*attribs,
fg, fg_alpha,
bg, bg_alpha,
index, 2);
(*text)[index++] = '^';
- (*text)[index++] = CTL_ECHO(*str);
+ (*text)[index++] = TECO_CTL_ECHO(*str);
} else {
(*text)[index++] = *str;
}
@@ -207,20 +229,25 @@ class Gtk:Canonicalized:Label from Gtk:Label {
}
public void
- set_text(self, const gchar *str)
+ set_text(self, const gchar *str, gssize len)
{
- PangoAttrList *attribs = NULL;
- gchar *plaintext = NULL;
+ teco_string_clear(&selfp->string);
+ teco_string_init(&selfp->string, str, len < 0 ? strlen(str) : len);
+
+ g_autofree gchar *plaintext = NULL;
- if (str)
- self_parse_string(str, -1,
- &self->_priv->fg, self->_priv->fg_alpha,
- &self->_priv->bg, self->_priv->bg_alpha,
+ if (selfp->string.len > 0) {
+ PangoAttrList *attribs = NULL;
+
+ self_parse_string(selfp->string.data, selfp->string.len,
+ &selfp->fg, selfp->fg_alpha,
+ &selfp->bg, selfp->bg_alpha,
&attribs, &plaintext);
- gtk_label_set_attributes(GTK_LABEL(self), attribs);
- pango_attr_list_unref(attribs);
+ gtk_label_set_attributes(GTK_LABEL(self), attribs);
+ pango_attr_list_unref(attribs);
+ }
+
gtk_label_set_text(GTK_LABEL(self), plaintext);
- g_free(plaintext);
}
}