/*
* Copyright (C) 2012-2016 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 .
*/
requires 2.0.20
%ctop{
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include
#include
#ifndef HAVE_GTK_FLOW_BOX_NEW
#include "gtkflowbox.h"
#endif
%}
%h{
#include
%}
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("%s",
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);
}
}