aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/interface-gtk
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2021-05-30 02:38:43 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2021-05-30 03:12:56 +0200
commit432ad24e382681f1c13b07e8486e91063dd96e2e (patch)
tree51838adac822767bd5884b9383cd4c72f29d3840 /src/interface-gtk
parent524bc3960e6a6e5645ce904e20f72479e24e0a23 (diff)
downloadsciteco-432ad24e382681f1c13b07e8486e91063dd96e2e.tar.gz
THE GREAT CEEIFICATION EVENT
This is a total conversion of SciTECO to plain C (GNU C11). The chance was taken to improve a lot of internal datastructures, fix fundamental bugs and lay the foundations of future features. The GTK user interface is now in an useable state! All changes have been squashed together. The language itself has almost not changed at all, except for: * Detection of string terminators (usually Escape) now takes the string building characters into account. A string is only terminated outside of string building characters. In other words, you can now for instance write I^EQ[Hello$world]$ This removes one of the last bits of shellisms which is out of place in SciTECO where no tokenization/lexing is performed. Consequently, the current termination character can also be escaped using ^Q/^R. This is used by auto completions to make sure that strings are inserted verbatim and without unwanted sideeffects. * All strings can now safely contain null-characters (see also: 8-bit cleanliness). The null-character itself (^@) is not (yet) a valid SciTECO command, though. An incomplete list of changes: * We got rid of the BSD headers for RB trees and lists/queues. The problem with them was that they used a form of metaprogramming only to gain a bit of type safety. It also resulted in less readble code. This was a C++ desease. The new code avoids metaprogramming only to gain type safety. The BSD tree.h has been replaced by rb3ptr by Jens Stimpfle (https://github.com/jstimpfle/rb3ptr). This implementation is also more memory efficient than BSD's. The BSD list.h and queue.h has been replaced with a custom src/list.h. * Fixed crashes, performance issues and compatibility issues with the Gtk 3 User Interface. It is now more or less ready for general use. The GDK lock is no longer used to avoid using deprecated functions. On the downside, the new implementation (driving the Gtk event loop stepwise) is even slower than the old one. A few glitches remain (see TODO), but it is hoped that they will be resolved by the Scintilla update which will be performed soon. * A lot of program units have been split up, so they are shorter and easier to maintain: core-commands.c, qreg-commands.c, goto-commands.c, file-utils.h. * Parser states are simply structs of callbacks now. They still use a kind of polymorphy using a preprocessor trick. TECO_DEFINE_STATE() takes an initializer list that will be merged with the default list of field initializers. To "subclass" states, you can simply define new macros that add initializers to existing macros. * Parsers no longer have a "transitions" table but the input_cb() may use switch-case statements. There are also teco_machine_main_transition_t now which can be used to implement simple transitions. Additionally, you can specify functions to execute during transitions. This largely avoids long switch-case-statements. * Parsers are embeddable/reusable now, at least in parse-only mode. This does not currently bring any advantages but may later be used to write a Scintilla lexer for TECO syntax highlighting. Once parsers are fully embeddable, it will also be possible to run TECO macros in a kind of coroutine which would allow them to process string arguments in real time. * undo.[ch] still uses metaprogramming extensively but via the C preprocessor of course. On the downside, most undo token generators must be initiated explicitly (theoretically we could have used embedded functions / trampolines to instantiate automatically but this has turned out to be dangereous). There is a TECO_DEFINE_UNDO_CALL() to generate closures for arbitrary functions now (ie. to call an arbitrary function at undo-time). This simplified a lot of code and is much shorter than manually pushing undo tokens in many cases. * Instead of the ridiculous C++ Curiously Recurring Template Pattern to achieve static polymorphy for user interface implementations, we now simply declare all functions to implement in interface.h and link in the implementations. This is possible since we no longer hace to define interface subclasses (all state is static variables in the interface's *.c files). * Headers are now significantly shorter than in C++ since we can often hide more of our "class" implementations. * Memory counting is based on dlmalloc for most platforms now. Unfortunately, there is no malloc implementation that provides an efficient constant-time memory counter that is guaranteed to decrease when freeing memory. But since we use a defined malloc implementation now, malloc_usable_size() can be used safely for tracking memory use. malloc() replacement is very tricky on Windows, so we use a poll thread on Windows. This can also be enabled on other supported platforms using --disable-malloc-replacement. All in all, I'm still not pleased with the state of memory limiting. It is a mess. * Error handling uses GError now. This has the advantage that the GError codes can be reused once we support error catching in the SciTECO language. * Added a few more test suite cases. * Haiku is no longer supported as builds are instable and I did not manage to debug them - quite possibly Haiku bugs were responsible. * Glib v2.44 or later are now required. The GTK UI requires Gtk+ v3.12 or later now. The GtkFlowBox fallback and sciteco-wrapper workaround are no longer required. * We now extensively use the GCC/Clang-specific g_auto feature (automatic deallocations when leaving the current code block). * Updated copyright to 2021. SciTECO has been in continuous development, even though there have been no commits since 2018. * Since these changes are so significant, the target release has been set to v2.0. It is planned that beginning with v3.0, the language will be kept stable.
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);
}
}