diff options
Diffstat (limited to 'src/interface-gtk/gtk-info-popup.gob')
-rw-r--r-- | src/interface-gtk/gtk-info-popup.gob | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/src/interface-gtk/gtk-info-popup.gob b/src/interface-gtk/gtk-info-popup.gob new file mode 100644 index 0000000..5082091 --- /dev/null +++ b/src/interface-gtk/gtk-info-popup.gob @@ -0,0 +1,269 @@ +requires 2.0.20 + +%ctop{ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <math.h> + +#include <glib/gprintf.h> +#include <gio/gio.h> + +#ifndef HAVE_GTK_FLOW_BOX_NEW +#include "gtkflowbox.h" +#endif +%} + +%h{ +#include <gtk/gtk.h> +%} + +enum GTK_INFO_POPUP { + PLAIN, + FILE, + DIRECTORY +} Gtk:Info:Popup:Entry:Type; + +class Gtk:Info:Popup from Gtk:Event:Box { + public GtkAdjustment *hadjustment; + public GtkAdjustment *vadjustment; + + private GtkWidget *flow_box; + + init(self) + { + GtkWidget *box, *viewport; + + /* + * A box containing a viewport and scrollbar will + * "emulate" a scrolled window. + * We cannot use a scrolled window since it ignores + * the preferred height of its viewport which breaks + * height-for-width management. + */ + box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + + self->hadjustment = gtk_adjustment_new(0, 0, 0, 0, 0, 0); + self->vadjustment = gtk_adjustment_new(0, 0, 0, 0, 0, 0); + + GtkWidget *scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, + self->vadjustment); + + self->_priv->flow_box = gtk_flow_box_new(); + /* take as little height as necessary */ + gtk_orientable_set_orientation(GTK_ORIENTABLE(self->_priv->flow_box), + GTK_ORIENTATION_HORIZONTAL); + //gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(self->_priv->flow_box), TRUE); + /* this for focus handling only, not for scrolling */ + gtk_flow_box_set_hadjustment(GTK_FLOW_BOX(self->_priv->flow_box), + self->hadjustment); + gtk_flow_box_set_vadjustment(GTK_FLOW_BOX(self->_priv->flow_box), + self->vadjustment); + + gtk_container_set_border_width(GTK_CONTAINER(self->_priv->flow_box), 10); + + viewport = gtk_viewport_new(self->hadjustment, self->vadjustment); + gtk_container_add(GTK_CONTAINER(viewport), self->_priv->flow_box); + + gtk_box_pack_start(GTK_BOX(box), viewport, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(box), scrollbar, FALSE, FALSE, 0); + gtk_widget_show_all(box); + + /* + * The top-level widget is a GtkEventBox, so it can have + * a background. We assign a default color since the popup + * will usually be put in an overlay and we don't want it + * to be transparent. + * It can also be styled with rounded corners etc. + * FIXME: This method of setting a background color is + * deprecated. We are supposed to use CSS style providers... + */ + gtk_container_add(GTK_CONTAINER(self), box); + GdkRGBA color = {0.5, 0.5, 0.5, 1.0}; + gtk_widget_override_background_color(GTK_WIDGET(self), GTK_STATE_FLAG_NORMAL, + &color); + } + + override (Gtk:Widget) void + size_allocate(Gtk:Widget *widget, + Gtk:Allocation *allocation) + { + GtkWidget *parent = gtk_widget_get_parent(widget); + GtkAllocation parent_alloc; + + + /* + * Adjust the allocation of the popup to be within the + * bounds of its parent widget which GtkOvelay does + * not seem to do automatically. + * Also Gtk does not seem to query this widget's + * preferred height. + * FIXME: Only works if the GtkInfoPopup is added + * directly to the GtkOverlay. + */ + gtk_widget_get_allocation(parent, &parent_alloc); + allocation->width = MIN(allocation->width, parent_alloc.width); + if (allocation->height > parent_alloc.height) { + allocation->y += allocation->height - parent_alloc.height; + allocation->height = parent_alloc.height; + } + + /* + * Allocate the adjusted/clipped allocation + */ + PARENT_HANDLER(widget, allocation); + } + + /* + * Adapted from GtkScrolledWindow's gtk_scrolled_window_scroll_event() + * since the viewport does not react to scroll events. + * 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, GdkEventScroll *event) + { + Self *self = GTK_INFO_POPUP(widget); + gdouble delta_x, delta_y; + + if (gdk_event_get_scroll_deltas((GdkEvent *)event, + &delta_x, &delta_y)) { + GtkAdjustment *adj = self->vadjustment; + gdouble page_size = gtk_adjustment_get_page_size(adj); + gdouble scroll_unit = pow(page_size, 2.0 / 3.0); + gdouble new_value; + + new_value = CLAMP(gtk_adjustment_get_value(adj) + delta_y * scroll_unit, + gtk_adjustment_get_lower(adj), + gtk_adjustment_get_upper(adj) - + gtk_adjustment_get_page_size(adj)); + + gtk_adjustment_set_value(adj, new_value); + + return TRUE; + } + + return FALSE; + } + + public GtkWidget * + new(void) + { + Self *widget = GET_NEW; + return GTK_WIDGET(widget); + } + + private GIcon * + get_icon_for_path(const gchar *path, const gchar *fallback_name) + { + GFile *file; + GFileInfo *info; + GIcon *icon = NULL; + + file = g_file_new_for_path(path); + info = g_file_query_info(file, "standard::icon", 0, NULL, NULL); + if (info) { + icon = g_file_info_get_icon(info); + g_object_ref(icon); + g_object_unref(info); + } else { + /* fall back to standard icon, but this can still return NULL! */ + icon = g_icon_new_for_string(fallback_name, NULL); + } + g_object_unref(file); + + return icon; + } + + public void + add(self, Gtk:Info:Popup:Entry:Type type, + const gchar *name, gboolean highlight) + { + GtkWidget *hbox; + GtkWidget *label; + gchar *markup; + + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + + if (type == GTK_INFO_POPUP_FILE || type == GTK_INFO_POPUP_DIRECTORY) { + const gchar *fallback = type == GTK_INFO_POPUP_FILE ? "text-x-generic" + : "folder"; + GIcon *icon; + + icon = self_get_icon_for_path(name, fallback); + if (icon) { + GtkWidget *image; + + image = gtk_image_new_from_gicon(icon, GTK_ICON_SIZE_MENU); + g_object_unref(icon); + gtk_box_pack_start(GTK_BOX(hbox), image, + FALSE, FALSE, 0); + } + } + + /* + * FIXME: setting Pango attributes directly would be + * much more efficient + */ + label = gtk_label_new(NULL); + markup = g_markup_printf_escaped("<span weight=\"%s\">%s</span>", + highlight ? "bold" : "normal", + name); + gtk_label_set_markup(GTK_LABEL(label), markup); + g_free(markup); + gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5); + gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); + + gtk_widget_show_all(hbox); + gtk_container_add(GTK_CONTAINER(self->_priv->flow_box), hbox); + } + + public void + scroll_page(self) + { + GtkAdjustment *adj = self->vadjustment; + gdouble new_value; + + if (gtk_adjustment_get_value(adj) + gtk_adjustment_get_page_size(adj) == + gtk_adjustment_get_upper(adj)) { + /* wrap and scroll back to the top */ + new_value = gtk_adjustment_get_lower(adj); + } else { + /* scroll one page */ + GList *child_list; + + new_value = gtk_adjustment_get_value(adj) + + gtk_adjustment_get_page_size(adj); + + /* + * Adjust this so only complete entries are shown. + * Effectively, this rounds down to the line height. + */ + child_list = gtk_container_get_children(GTK_CONTAINER(self->_priv->flow_box)); + if (child_list) { + new_value -= (gint)new_value % + gtk_widget_get_allocated_height(GTK_WIDGET(child_list->data)); + g_list_free(child_list); + } + + /* clip to the maximum possible value */ + new_value = MIN(new_value, gtk_adjustment_get_upper(adj)); + } + + gtk_adjustment_set_value(adj, new_value); + } + + public void + clear(self) + { + GList *children; + + children = gtk_container_get_children(GTK_CONTAINER(self->_priv->flow_box)); + for (GList *cur = g_list_first(children); + cur != NULL; + cur = g_list_next(cur)) + gtk_widget_destroy(GTK_WIDGET(cur->data)); + g_list_free(children); + } +} |