diff options
Diffstat (limited to 'src/interface-gtk/teco-gtk-info-popup.gob')
-rw-r--r-- | src/interface-gtk/teco-gtk-info-popup.gob | 446 |
1 files changed, 0 insertions, 446 deletions
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); - } -} |