aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2021-06-07 17:58:54 +0200
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2021-06-08 18:48:16 +0200
commit073f5f28b835d3bda5e8771383c26d78d9740768 (patch)
tree20ed540730c940d82d9c6b4cd81408bec6c42cd1
parent0507d6a8b2bc590faf97c5f7d406218d1980470b (diff)
downloadsciteco-073f5f28b835d3bda5e8771383c26d78d9740768.tar.gz
get rid of the GObject Builder (GOB2): converted teco-gtk-info-popup.gob and teco-gtk-label.gob to plain C
* Using modern GObject idioms and macros greatly reduces the necessary boilerplate code. * The plain C versions of our GObject classes are now "final" (cannot be derived) This means we can hide the instance structures from the headers and avoid using explicit private fields. * Avoids some deprecation warnings when building the Gtk UI. * GOB2 is apparently no longer maintained, so this seems like a good idea in the long run. * The most important reason however is that there is no precompiled GOB2 for Windows which prevents compilation on native Windows hosts, eg. during nightly builds. This is even more important as Gtk+3 is distributed on Windows practically exclusively via MSYS. (ArchLinux contains MinGW gtk3 packages as well, so cross-compiling from ArchLinux would have been an alternative.)
-rw-r--r--.github/workflows/ci.yml2
-rw-r--r--.github/workflows/nightly.yml2
-rw-r--r--.gitignore2
-rw-r--r--INSTALL3
-rw-r--r--configure.ac2
-rw-r--r--debian/control2
-rw-r--r--m4/gob2.m458
-rw-r--r--src/interface-curses/curses-info-popup.c2
-rw-r--r--src/interface-gtk/Makefile.am17
-rw-r--r--src/interface-gtk/gtk-info-popup.c457
-rw-r--r--src/interface-gtk/gtk-info-popup.h45
-rw-r--r--src/interface-gtk/gtk-label.c271
-rw-r--r--src/interface-gtk/gtk-label.h34
-rw-r--r--src/interface-gtk/interface.c4
-rw-r--r--src/interface-gtk/teco-gtk-info-popup.gob446
-rw-r--r--src/interface-gtk/teco-gtk-label.gob253
16 files changed, 817 insertions, 783 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b09ff86..8e8c945 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,7 @@ jobs:
sudo apt-get install -y
build-essential
autoconf automake libtool
- libglib2.0-dev libncurses-dev libgtk-3-dev gob2 xvfb
+ libglib2.0-dev libncurses-dev libgtk-3-dev xvfb
groff doxygen
- name: Configure Build
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index f6e6ca0..1582119 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -28,7 +28,7 @@ jobs:
sudo apt-get install -y
devscripts build-essential lintian debhelper dh-exec
autoconf automake libtool
- libglib2.0-dev libncurses-dev libgtk-3-dev gob2 xvfb
+ libglib2.0-dev libncurses-dev libgtk-3-dev xvfb
groff-base
# NOTE: We need to configure the build directory only to generate distribute.mk.
diff --git a/.gitignore b/.gitignore
index 47c9379..823c690 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,8 +34,6 @@ testsuite.dir
/src/sciteco-minimal
# Generated source files
-/src/interface-gtk/teco-gtk-info-popup.[ch]
-/src/interface-gtk/teco-gtk-label.[ch]
/src/symbols-scintilla.c
/src/symbols-scilexer.c
diff --git a/INSTALL b/INSTALL
index eb5ff8b..b166c93 100644
--- a/INSTALL
+++ b/INSTALL
@@ -34,7 +34,6 @@ SciTECO Build and Runtime Dependencies
* other curses implementations might work as well but are untested
* When choosing the GTK interface:
* GTK+ v3.12 or later: http://www.gtk.org/
- * GObject Builder v2.0.20 or later: http://www.jirka.org/gob.html
* GNU roff (groff): https://www.gnu.org/software/groff/
Required at build-time, but it is already shipped on most
UNIX-like systems to format man pages.
@@ -54,7 +53,7 @@ On Ubuntu, you can install all dependencies you could possibly need
as follows:
$ sudo apt-get install git build-essential autoconf automake libtool \
- libglib2.0-dev libncurses-dev libgtk-3-dev gob2 \
+ libglib2.0-dev libncurses-dev libgtk-3-dev \
groff doxygen
Building from Source Tar Ball or Repository
diff --git a/configure.ac b/configure.ac
index 2d272c0..c16acf1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -297,8 +297,6 @@ gtk)
LIBS="$LIBS $LIBGTK_LIBS"
])
- GOB2_CHECK(2.0.20)
-
AC_DEFINE(INTERFACE_GTK, 1, [Build with GTK+ 3.0 support])
# For Scintilla:
diff --git a/debian/control b/debian/control
index 5852e5a..7e8cc5c 100644
--- a/debian/control
+++ b/debian/control
@@ -4,7 +4,7 @@ Priority: optional
Maintainer: Robin Haberkorn <robin.haberkorn@googlemail.com>
Build-Depends: debhelper (>= 10), dh-exec, g++ (>= 4:4.4), libglib2.0-dev (>= 2.44),
ncurses-base, ncurses-term, libncurses5-dev,
- libgtk-3-dev (>= 3.12), gob2 (>= 2.0.20), xvfb,
+ libgtk-3-dev (>= 3.12), xvfb,
groff-base
Standards-Version: 3.9.2
Homepage: http://sciteco.sf.net/
diff --git a/m4/gob2.m4 b/m4/gob2.m4
deleted file mode 100644
index 196d6c2..0000000
--- a/m4/gob2.m4
+++ /dev/null
@@ -1,58 +0,0 @@
-dnl
-dnl GOB_HOOK(script if found, fail)
-dnl if fail = "failure", abort if GOB not found
-dnl
-
-
-AC_DEFUN([GOB2_HOOK],[
- AC_PATH_PROG(GOB2,gob2)
- if test ! x$GOB2 = x; then
- if test ! x$1 = x; then
- AC_MSG_CHECKING(for gob-2 >= $1)
- g_r_ve=`echo $1|sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
- g_r_ma=`echo $1|sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
- g_r_mi=`echo $1|sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
- g_ve=`$GOB2 --version 2>&1|sed 's/Gob version \([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'`
- g_ma=`$GOB2 --version 2>&1|sed 's/Gob version \([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'`
- g_mi=`$GOB2 --version 2>&1|sed 's/Gob version \([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'`
-
- if test $g_ve -eq $g_r_ve; then
- if test $g_ma -ge $g_r_ma; then
- if test $g_mi -ge $g_r_mi; then
- AC_MSG_RESULT(ok)
- else
- if test $g_ma -gt $g_r_ma; then
- AC_MSG_RESULT(ok)
- else
- AC_MSG_ERROR("found $g_ve.$g_ma.$g_mi requires $g_r_ve.$g_r_ma.$g_r_mi")
- fi
- fi
- else
- AC_MSG_ERROR("found $g_ve.$g_ma.$g_mi requires $g_r_ve.$g_r_ma.$g_r_mi")
- fi
- else
- if test $g_ve -gt $g_r_ve; then
- AC_MSG_RESULT(ok)
- else
- AC_MSG_ERROR(major version $g_ve found but $g_r_ve required)
- fi
- fi
-
- unset gob_version
- unset g_ve
- unset g_ma
- unset g_mi
- unset g_r_ve
- unset g_r_ma
- unset g_r_mi
- fi
- AC_SUBST(GOB2)
- $2
- else
- $3
- fi
-])
-
-AC_DEFUN([GOB2_CHECK],[
- GOB2_HOOK($1,[],[AC_MSG_ERROR([Cannot find GOB-2, check http://www.5z.com/jirka/gob.html])])
-])
diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c
index 7d661a2..339acc5 100644
--- a/src/interface-curses/curses-info-popup.c
+++ b/src/interface-curses/curses-info-popup.c
@@ -30,7 +30,7 @@
#include "curses-info-popup.h"
/*
- * FIXME: This is redundant with teco-gtk-info-popup.gob.
+ * FIXME: This is redundant with gtk-info-popup.c.
*/
typedef struct {
teco_stailq_entry_t entry;
diff --git a/src/interface-gtk/Makefile.am b/src/interface-gtk/Makefile.am
index af26519..d470f85 100644
--- a/src/interface-gtk/Makefile.am
+++ b/src/interface-gtk/Makefile.am
@@ -3,20 +3,9 @@ AM_CPPFLAGS += -I$(top_srcdir)/contrib/rb3ptr \
AM_CFLAGS = -std=gnu11 -Wall -Wno-initializer-overrides -Wno-unused-value
-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.c
-nodist_libsciteco_interface_la_SOURCES = teco-gtk-info-popup.c \
- teco-gtk-label.c
+libsciteco_interface_la_SOURCES = interface.c \
+ gtk-info-popup.c gtk-info-popup.h \
+ gtk-label.c gtk-label.h
dist_pkgdata_DATA = fallback.css
-
-CLEANFILES = $(BUILT_SOURCES)
-
-%.c %.h %-private.h : %.gob
- @GOB2@ --gtk3 $<
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c
new file mode 100644
index 0000000..43569ea
--- /dev/null
+++ b/src/interface-gtk/gtk-info-popup.c
@@ -0,0 +1,457 @@
+/*
+ * 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 <string.h>
+#include <math.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "list.h"
+#include "string-utils.h"
+#include "gtk-label.h"
+#include "gtk-info-popup.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;
+
+struct _TecoGtkInfoPopup {
+ GtkEventBox parent_instance;
+
+ GtkAdjustment *hadjustment, *vadjustment;
+
+ GtkWidget *flow_box;
+ GStringChunk *chunk;
+ teco_stailq_head_t list;
+ guint idle_id;
+ gboolean frozen;
+};
+
+static gboolean teco_gtk_info_popup_scroll_event(GtkWidget *widget, GdkEventScroll *event);
+static void teco_gtk_info_popup_show(GtkWidget *widget);
+static void teco_gtk_info_popup_vadjustment_changed(GtkAdjustment *vadjustment, GtkWidget *scrollbar);
+
+G_DEFINE_TYPE(TecoGtkInfoPopup, teco_gtk_info_popup, GTK_TYPE_EVENT_BOX)
+
+/** Overrides GObject::finalize() (object destructor) */
+static void
+teco_gtk_info_popup_finalize(GObject *obj_self)
+{
+ TecoGtkInfoPopup *self = TECO_GTK_INFO_POPUP(obj_self);
+
+ if (self->chunk)
+ g_string_chunk_free(self->chunk);
+
+ teco_stailq_entry_t *entry;
+ while ((entry = teco_stailq_remove_head(&self->list)))
+ g_free(entry);
+
+ /* chain up to parent class */
+ G_OBJECT_CLASS(teco_gtk_info_popup_parent_class)->finalize(obj_self);
+}
+
+static void
+teco_gtk_info_popup_class_init(TecoGtkInfoPopupClass *klass)
+{
+ GTK_WIDGET_CLASS(klass)->scroll_event = teco_gtk_info_popup_scroll_event;
+ GTK_WIDGET_CLASS(klass)->show = teco_gtk_info_popup_show;
+ G_OBJECT_CLASS(klass)->finalize = teco_gtk_info_popup_finalize;
+}
+
+static void
+teco_gtk_info_popup_init(TecoGtkInfoPopup *self)
+{
+ self->hadjustment = gtk_adjustment_new(0, 0, 0, 0, 0, 0);
+ self->vadjustment = gtk_adjustment_new(0, 0, 0, 0, 0, 0);
+
+ /*
+ * 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(teco_gtk_info_popup_vadjustment_changed), scrollbar);
+
+ self->flow_box = gtk_flow_box_new();
+ /* take as little height as necessary */
+ gtk_orientable_set_orientation(GTK_ORIENTABLE(self->flow_box),
+ GTK_ORIENTATION_HORIZONTAL);
+ //gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(self->flow_box), TRUE);
+ /* this for focus handling only, not for scrolling */
+ gtk_flow_box_set_hadjustment(GTK_FLOW_BOX(self->flow_box),
+ self->hadjustment);
+ gtk_flow_box_set_vadjustment(GTK_FLOW_BOX(self->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), self->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);
+
+ self->chunk = g_string_chunk_new(32);
+ self->list = TECO_STAILQ_HEAD_INITIALIZER(&self->list);
+ self->idle_id = 0;
+ self->frozen = FALSE;
+}
+
+gboolean
+teco_gtk_info_popup_get_position_in_overlay(GtkOverlay *overlay, GtkWidget *widget,
+ GdkRectangle *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;
+}
+
+/** Overrides GtkWidget::scroll_event() */
+static gboolean
+teco_gtk_info_popup_scroll_event(GtkWidget *widget, GdkEventScroll *event)
+{
+ TecoGtkInfoPopup *self = TECO_GTK_INFO_POPUP(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;
+}
+
+static void
+teco_gtk_info_popup_vadjustment_changed(GtkAdjustment *vadjustment, GtkWidget *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);
+}
+
+GtkWidget *
+teco_gtk_info_popup_new(void)
+{
+ return GTK_WIDGET(g_object_new(TECO_TYPE_GTK_INFO_POPUP, NULL));
+}
+
+GIcon *
+teco_gtk_info_popup_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;
+}
+
+void
+teco_gtk_info_popup_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type,
+ const gchar *name, gssize len, gboolean highlight)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (TECO_IS_GTK_INFO_POPUP (self));
+
+ 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,
+ self->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(&self->list, &entry->entry);
+}
+
+static void
+teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type,
+ const gchar *name, gssize len, gboolean highlight)
+{
+ g_return_if_fail(self != NULL);
+ g_return_if_fail(TECO_IS_GTK_INFO_POPUP(self));
+
+ 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 = teco_gtk_info_popup_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(self->flow_box), hbox);
+}
+
+static gboolean
+teco_gtk_info_popup_idle_cb(TecoGtkInfoPopup *self)
+{
+ g_return_val_if_fail(self != NULL, FALSE);
+ g_return_val_if_fail(TECO_IS_GTK_INFO_POPUP(self), FALSE);
+
+ /*
+ * 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(&self->list);
+ if (G_UNLIKELY(!head)) {
+ if (self->frozen)
+ gdk_window_thaw_updates(gtk_widget_get_window(GTK_WIDGET(self)));
+ self->frozen = FALSE;
+ self->idle_id = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ teco_gtk_info_popup_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 (self->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)));
+ self->frozen = FALSE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+/** Overrides GtkWidget::show() */
+static void
+teco_gtk_info_popup_show(GtkWidget *widget)
+{
+ TecoGtkInfoPopup *self = TECO_GTK_INFO_POPUP(widget);
+
+ if (!self->idle_id) {
+ self->idle_id = gdk_threads_add_idle((GSourceFunc)teco_gtk_info_popup_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);
+ self->frozen = TRUE;
+ }
+ }
+
+ /* chain to parent class */
+ GTK_WIDGET_CLASS(teco_gtk_info_popup_parent_class)->show(widget);
+}
+
+void
+teco_gtk_info_popup_scroll_page(TecoGtkInfoPopup *self)
+{
+ g_return_if_fail(self != NULL);
+ g_return_if_fail(TECO_IS_GTK_INFO_POPUP(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(self->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);
+}
+
+static void
+teco_gtk_info_popup_destroy_cb(GtkWidget *widget, gpointer user_data)
+{
+ gtk_widget_destroy(widget);
+}
+
+void
+teco_gtk_info_popup_clear(TecoGtkInfoPopup *self)
+{
+ g_return_if_fail(self != NULL);
+ g_return_if_fail(TECO_IS_GTK_INFO_POPUP(self));
+
+ gtk_container_foreach(GTK_CONTAINER(self->flow_box), teco_gtk_info_popup_destroy_cb, NULL);
+
+ /*
+ * If there are still queued popoup entries, the next teco_gtk_info_popup_idle_cb()
+ * invocation will also stop the GSource.
+ */
+ teco_stailq_entry_t *entry;
+ while ((entry = teco_stailq_remove_head(&self->list)))
+ g_free(entry);
+
+ g_string_chunk_clear(self->chunk);
+}
diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h
new file mode 100644
index 0000000..04a4d42
--- /dev/null
+++ b/src/interface-gtk/gtk-info-popup.h
@@ -0,0 +1,45 @@
+/*
+ * 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/>.
+ */
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "interface.h"
+
+#define TECO_TYPE_GTK_INFO_POPUP teco_gtk_info_popup_get_type()
+G_DECLARE_FINAL_TYPE(TecoGtkInfoPopup, teco_gtk_info_popup, TECO, GTK_INFO_POPUP, GtkEventBox)
+
+GtkWidget *teco_gtk_info_popup_new(void);
+
+void teco_gtk_info_popup_add(TecoGtkInfoPopup *self,
+ teco_popup_entry_type_t type,
+ const gchar *name, gssize len,
+ gboolean highlight);
+void teco_gtk_info_popup_scroll_page(TecoGtkInfoPopup *self);
+void teco_gtk_info_popup_clear(TecoGtkInfoPopup *self);
+
+gboolean teco_gtk_info_popup_get_position_in_overlay(GtkOverlay *overlay,
+ GtkWidget *widget,
+ GdkRectangle *allocation,
+ gpointer user_data);
+GIcon *teco_gtk_info_popup_get_icon_for_path(const gchar *path,
+ const gchar *fallback_name);
diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c
new file mode 100644
index 0000000..537269d
--- /dev/null
+++ b/src/interface-gtk/gtk-label.c
@@ -0,0 +1,271 @@
+/*
+ * 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 <string.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "sciteco.h"
+#include "string-utils.h"
+
+#include "gtk-label.h"
+
+#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16))
+
+struct _TecoGtkLabel {
+ GtkLabel parent_instance;
+
+ PangoColor fg, bg;
+ guint16 fg_alpha, bg_alpha;
+
+ teco_string_t string;
+};
+
+G_DEFINE_TYPE(TecoGtkLabel, teco_gtk_label, GTK_TYPE_LABEL)
+
+/** Overrides GObject::finalize() (object destructor) */
+static void
+teco_gtk_label_finalize(GObject *obj_self)
+{
+ TecoGtkLabel *self = TECO_GTK_LABEL(obj_self);
+
+ teco_string_clear(&self->string);
+
+ /* chain up to parent class */
+ G_OBJECT_CLASS(teco_gtk_label_parent_class)->finalize(obj_self);
+}
+
+/** Overrides GtkWidget::style_updated() */
+static void
+teco_gtk_label_style_updated(GtkWidget *widget)
+{
+ TecoGtkLabel *self = TECO_GTK_LABEL(widget);
+
+ /* chain to parent class */
+ GTK_WIDGET_CLASS(teco_gtk_label_parent_class)->style_updated(widget);
+
+ GtkStyleContext *style = gtk_widget_get_style_context(widget);
+
+ GdkRGBA normal_color;
+ gtk_style_context_get_color(style, GTK_STATE_NORMAL, &normal_color);
+ self->bg.red = GDK_TO_PANGO_COLOR(normal_color.red);
+ self->bg.green = GDK_TO_PANGO_COLOR(normal_color.green);
+ self->bg.blue = GDK_TO_PANGO_COLOR(normal_color.blue);
+ self->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->fg.red = normal_color.red > 0.5 ? 0 : G_MAXUINT16;
+ self->fg.green = normal_color.green > 0.5 ? 0 : G_MAXUINT16;
+ self->fg.blue = normal_color.blue > 0.5 ? 0 : G_MAXUINT16;
+ /* try hard to get a transparent foreground anyway */
+ self->fg_alpha = 0;
+
+ /*
+ * The widget might be styled after the text has been set on it,
+ * we must recreate the Pango attributes.
+ */
+ if (self->string.len > 0) {
+ PangoAttrList *attribs = NULL;
+ g_autofree gchar *plaintext = NULL;
+
+ teco_gtk_label_parse_string(self->string.data, self->string.len,
+ &self->fg, self->fg_alpha,
+ &self->bg, self->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))));
+ }
+}
+
+static void
+teco_gtk_label_class_init(TecoGtkLabelClass *klass)
+{
+ GTK_WIDGET_CLASS(klass)->style_updated = teco_gtk_label_style_updated;
+ G_OBJECT_CLASS(klass)->finalize = teco_gtk_label_finalize;
+}
+
+static void teco_gtk_label_init(TecoGtkLabel *self) {}
+
+GtkWidget *
+teco_gtk_label_new(const gchar *str, gssize len)
+{
+ TecoGtkLabel *widget = TECO_GTK_LABEL(g_object_new(TECO_TYPE_GTK_LABEL, NULL));
+
+ teco_gtk_label_set_text(widget, str, len);
+
+ return GTK_WIDGET(widget);
+}
+
+static void
+teco_gtk_label_add_highlight_attribs(PangoAttrList *attribs, PangoColor *fg, guint16 fg_alpha,
+ PangoColor *bg, guint16 bg_alpha, guint index, gsize len)
+{
+ 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;
+ attr->end_index = index + len;
+ pango_attr_list_insert(attribs, attr);
+
+ attr = pango_attr_background_alpha_new(bg_alpha);
+ attr->start_index = index;
+ attr->end_index = index + len;
+ pango_attr_list_insert(attribs, attr);
+#endif
+
+ attr = pango_attr_foreground_new(fg->red, fg->green, fg->blue);
+ attr->start_index = index;
+ attr->end_index = index + len;
+ pango_attr_list_insert(attribs, attr);
+
+ attr = pango_attr_background_new(bg->red, bg->green, bg->blue);
+ attr->start_index = index;
+ attr->end_index = index + len;
+ pango_attr_list_insert(attribs, attr);
+}
+
+void
+teco_gtk_label_parse_string(const gchar *str, gssize len, PangoColor *fg, guint16 fg_alpha,
+ PangoColor *bg, guint16 bg_alpha, PangoAttrList **attribs, gchar **text)
+{
+ 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 += 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
+ * teco_view_set_presentations()
+ */
+ switch (*str) {
+ case '\e':
+ teco_gtk_label_add_highlight_attribs(*attribs,
+ fg, fg_alpha,
+ bg, bg_alpha,
+ index, 1);
+ (*text)[index++] = '$';
+ break;
+ case '\r':
+ teco_gtk_label_add_highlight_attribs(*attribs,
+ fg, fg_alpha,
+ bg, bg_alpha,
+ index, 2);
+ (*text)[index++] = 'C';
+ (*text)[index++] = 'R';
+ break;
+ case '\n':
+ teco_gtk_label_add_highlight_attribs(*attribs,
+ fg, fg_alpha,
+ bg, bg_alpha,
+ index, 2);
+ (*text)[index++] = 'L';
+ (*text)[index++] = 'F';
+ break;
+ case '\t':
+ teco_gtk_label_add_highlight_attribs(*attribs,
+ fg, fg_alpha,
+ bg, bg_alpha,
+ index, 3);
+ (*text)[index++] = 'T';
+ (*text)[index++] = 'A';
+ (*text)[index++] = 'B';
+ break;
+ default:
+ if (TECO_IS_CTL(*str)) {
+ teco_gtk_label_add_highlight_attribs(*attribs,
+ fg, fg_alpha,
+ bg, bg_alpha,
+ index, 2);
+ (*text)[index++] = '^';
+ (*text)[index++] = TECO_CTL_ECHO(*str);
+ } else {
+ (*text)[index++] = *str;
+ }
+ break;
+ }
+
+ str++;
+ len--;
+ }
+
+ /* null-terminate generated text */
+ (*text)[index] = '\0';
+}
+
+void
+teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len)
+{
+ g_return_if_fail(self != NULL);
+ g_return_if_fail(TECO_IS_GTK_LABEL(self));
+
+ teco_string_clear(&self->string);
+ teco_string_init(&self->string, str, len < 0 ? strlen(str) : len);
+
+ g_autofree gchar *plaintext = NULL;
+
+ if (self->string.len > 0) {
+ PangoAttrList *attribs = NULL;
+
+ teco_gtk_label_parse_string(self->string.data, self->string.len,
+ &self->fg, self->fg_alpha,
+ &self->bg, self->bg_alpha,
+ &attribs, &plaintext);
+
+ gtk_label_set_attributes(GTK_LABEL(self), attribs);
+ pango_attr_list_unref(attribs);
+ }
+
+ gtk_label_set_text(GTK_LABEL(self), plaintext);
+}
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
new file mode 100644
index 0000000..e20646d
--- /dev/null
+++ b/src/interface-gtk/gtk-label.h
@@ -0,0 +1,34 @@
+/*
+ * 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/>.
+ */
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+#define TECO_TYPE_GTK_LABEL teco_gtk_label_get_type()
+G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel)
+
+GtkWidget *teco_gtk_label_new(const gchar *str, gssize len);
+
+void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len);
+
+void teco_gtk_label_parse_string(const gchar *str, gssize len,
+ PangoColor *fg, guint16 fg_alpha,
+ PangoColor *bg, guint16 bg_alpha,
+ PangoAttrList **attribs, gchar **text);
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index afc8fe3..1e4e32b 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -41,8 +41,8 @@
#include <Scintilla.h>
#include <ScintillaWidget.h>
-#include "teco-gtk-info-popup.h"
-#include "teco-gtk-label.h"
+#include "gtk-info-popup.h"
+#include "gtk-label.h"
#include "sciteco.h"
#include "error.h"
diff --git a/src/interface-gtk/teco-gtk-info-popup.gob b/src/interface-gtk/teco-gtk-info-popup.gob
deleted file mode 100644
index f08b0d7..0000000
--- a/src/interface-gtk/teco-gtk-info-popup.gob
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * 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/teco-gtk-label.gob b/src/interface-gtk/teco-gtk-label.gob
deleted file mode 100644
index 2167dbc..0000000
--- a/src/interface-gtk/teco-gtk-label.gob
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * 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 <glib/gprintf.h>
-
-#include <gdk/gdk.h>
-
-#include "sciteco.h"
-#include "string-utils.h"
-
-#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16))
-%}
-
-%h{
-#include <gtk/gtk.h>
-%}
-
-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);
-
- PARENT_HANDLER(widget);
- GtkStyleContext *style = gtk_widget_get_style_context(widget);
-
- GdkRGBA normal_color;
- gtk_style_context_get_color(style, GTK_STATE_NORMAL, &normal_color);
- 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.
- */
- 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 */
- 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, gssize len)
- {
- Self *widget = GET_NEW;
-
- self_set_text(widget, str, len);
-
- return GTK_WIDGET(widget);
- }
-
- private void
- add_highlight_attribs(Pango:Attr:List *attribs,
- Pango:Color *fg, guint16 fg_alpha,
- Pango:Color *bg, guint16 bg_alpha,
- guint index, gsize len)
- {
- 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;
- attr->end_index = index + len;
- pango_attr_list_insert(attribs, attr);
-
- attr = pango_attr_background_alpha_new(bg_alpha);
- attr->start_index = index;
- attr->end_index = index + len;
- pango_attr_list_insert(attribs, attr);
-#endif
-
- attr = pango_attr_foreground_new(fg->red, fg->green, fg->blue);
- attr->start_index = index;
- attr->end_index = index + len;
- pango_attr_list_insert(attribs, attr);
-
- attr = pango_attr_background_new(bg->red, bg->green, bg->blue);
- attr->start_index = index;
- attr->end_index = index + len;
- pango_attr_list_insert(attribs, attr);
- }
-
- public void
- parse_string(const gchar *str, gssize len,
- Pango:Color *fg, guint16 fg_alpha,
- Pango:Color *bg, guint16 bg_alpha,
- Pango:Attr:List **attribs, gchar **text)
- {
- 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 += 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
- * teco_view_set_presentations()
- */
- switch (*str) {
- case '\e':
- self_add_highlight_attribs(*attribs,
- fg, fg_alpha,
- bg, bg_alpha,
- index, 1);
- (*text)[index++] = '$';
- break;
- case '\r':
- self_add_highlight_attribs(*attribs,
- fg, fg_alpha,
- bg, bg_alpha,
- index, 2);
- (*text)[index++] = 'C';
- (*text)[index++] = 'R';
- break;
- case '\n':
- self_add_highlight_attribs(*attribs,
- fg, fg_alpha,
- bg, bg_alpha,
- index, 2);
- (*text)[index++] = 'L';
- (*text)[index++] = 'F';
- break;
- case '\t':
- self_add_highlight_attribs(*attribs,
- fg, fg_alpha,
- bg, bg_alpha,
- index, 3);
- (*text)[index++] = 'T';
- (*text)[index++] = 'A';
- (*text)[index++] = 'B';
- break;
- default:
- if (TECO_IS_CTL(*str)) {
- self_add_highlight_attribs(*attribs,
- fg, fg_alpha,
- bg, bg_alpha,
- index, 2);
- (*text)[index++] = '^';
- (*text)[index++] = TECO_CTL_ECHO(*str);
- } else {
- (*text)[index++] = *str;
- }
- break;
- }
-
- str++;
- len--;
- }
-
- /* null-terminate generated text */
- (*text)[index] = '\0';
- }
-
- public void
- set_text(self, const gchar *str, gssize len)
- {
- teco_string_clear(&selfp->string);
- teco_string_init(&selfp->string, str, len < 0 ? strlen(str) : len);
-
- g_autofree gchar *plaintext = NULL;
-
- 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_text(GTK_LABEL(self), plaintext);
- }
-}