diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2021-06-07 17:58:54 +0200 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2021-06-08 18:48:16 +0200 |
commit | 073f5f28b835d3bda5e8771383c26d78d9740768 (patch) | |
tree | 20ed540730c940d82d9c6b4cd81408bec6c42cd1 /src/interface-gtk/gtk-label.c | |
parent | 0507d6a8b2bc590faf97c5f7d406218d1980470b (diff) | |
download | sciteco-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.)
Diffstat (limited to 'src/interface-gtk/gtk-label.c')
-rw-r--r-- | src/interface-gtk/gtk-label.c | 271 |
1 files changed, 271 insertions, 0 deletions
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); +} |