aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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);
- }
-}