Add ability to use different thumbnail sizes
This commit is contained in:
parent
f4b727589b
commit
2b17ed838a
371
fastiv-browser.c
371
fastiv-browser.c
|
@ -41,6 +41,8 @@
|
|||
struct _FastivBrowser {
|
||||
GtkWidget parent_instance;
|
||||
|
||||
FastivIoThumbnailSize item_size; ///< Thumbnail height in pixels
|
||||
|
||||
GArray *entries; ///< [Entry]
|
||||
GArray *layouted_rows; ///< [Row]
|
||||
int selected;
|
||||
|
@ -55,7 +57,6 @@ typedef struct entry Entry;
|
|||
typedef struct item Item;
|
||||
typedef struct row Row;
|
||||
|
||||
static const double g_row_height = 256;
|
||||
static const double g_permitted_width_multiplier = 2;
|
||||
|
||||
// Could be split out to also-idiomatic row-spacing/column-spacing properties.
|
||||
|
@ -114,7 +115,7 @@ append_row(FastivBrowser *self, int *y, int x, GArray *items_array)
|
|||
}));
|
||||
|
||||
// Not trying to pack them vertically, but this would be the place to do it.
|
||||
*y += g_row_height;
|
||||
*y += (int) self->item_size;
|
||||
*y += self->item_border_y;
|
||||
}
|
||||
|
||||
|
@ -199,13 +200,13 @@ draw_outer_border(FastivBrowser *self, cairo_t *cr, int width, int height)
|
|||
}
|
||||
|
||||
static GdkRectangle
|
||||
item_extents(const Item *item, const Row *row)
|
||||
item_extents(FastivBrowser *self, const Item *item, const Row *row)
|
||||
{
|
||||
int width = cairo_image_surface_get_width(item->entry->thumbnail);
|
||||
int height = cairo_image_surface_get_height(item->entry->thumbnail);
|
||||
return (GdkRectangle) {
|
||||
.x = row->x_offset + item->x_offset,
|
||||
.y = row->y_offset + g_row_height - height,
|
||||
.y = row->y_offset + (int) self->item_size - height,
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
|
@ -217,7 +218,7 @@ entry_at(FastivBrowser *self, int x, int y)
|
|||
for (guint i = 0; i < self->layouted_rows->len; i++) {
|
||||
const Row *row = &g_array_index(self->layouted_rows, Row, i);
|
||||
for (Item *item = row->items; item->entry; item++) {
|
||||
GdkRectangle extents = item_extents(item, row);
|
||||
GdkRectangle extents = item_extents(self, item, row);
|
||||
if (x >= extents.x &&
|
||||
y >= extents.y &&
|
||||
x <= extents.x + extents.width &&
|
||||
|
@ -243,7 +244,7 @@ draw_row(FastivBrowser *self, cairo_t *cr, const Row *row)
|
|||
gtk_style_context_get_border(style, state, &border);
|
||||
for (Item *item = row->items; item->entry; item++) {
|
||||
cairo_save(cr);
|
||||
GdkRectangle extents = item_extents(item, row);
|
||||
GdkRectangle extents = item_extents(self, item, row);
|
||||
cairo_translate(cr, extents.x - border.left, extents.y - border.top);
|
||||
|
||||
gtk_style_context_save(style);
|
||||
|
@ -281,6 +282,159 @@ draw_row(FastivBrowser *self, cairo_t *cr, const Row *row)
|
|||
gtk_style_context_restore(style);
|
||||
}
|
||||
|
||||
// --- Thumbnails --------------------------------------------------------------
|
||||
|
||||
// NOTE: "It is important to note that when an image with an alpha channel is
|
||||
// scaled, linear encoded, pre-multiplied component values must be used!"
|
||||
static cairo_surface_t *
|
||||
rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
|
||||
{
|
||||
if (!thumbnail)
|
||||
return thumbnail;
|
||||
|
||||
int width = cairo_image_surface_get_width(thumbnail);
|
||||
int height = cairo_image_surface_get_height(thumbnail);
|
||||
|
||||
double scale_x = 1;
|
||||
double scale_y = 1;
|
||||
if (width > g_permitted_width_multiplier * height) {
|
||||
scale_x = g_permitted_width_multiplier * row_height / width;
|
||||
scale_y = round(scale_x * height) / height;
|
||||
} else {
|
||||
scale_y = row_height / height;
|
||||
scale_x = round(scale_y * width) / width;
|
||||
}
|
||||
if (scale_x == 1 && scale_y == 1)
|
||||
return thumbnail;
|
||||
|
||||
int projected_width = round(scale_x * width);
|
||||
int projected_height = round(scale_y * height);
|
||||
cairo_surface_t *scaled = cairo_image_surface_create(
|
||||
CAIRO_FORMAT_ARGB32, projected_width, projected_height);
|
||||
|
||||
// pixman can take gamma into account when scaling, unlike Cairo.
|
||||
struct pixman_f_transform xform_floating;
|
||||
struct pixman_transform xform;
|
||||
|
||||
// PIXMAN_a8r8g8b8_sRGB can be used for gamma-correct results,
|
||||
// but it's an incredibly slow transformation
|
||||
pixman_format_code_t format = PIXMAN_a8r8g8b8;
|
||||
|
||||
pixman_image_t *src = pixman_image_create_bits(format, width, height,
|
||||
(uint32_t *) cairo_image_surface_get_data(thumbnail),
|
||||
cairo_image_surface_get_stride(thumbnail));
|
||||
pixman_image_t *dest = pixman_image_create_bits(format,
|
||||
cairo_image_surface_get_width(scaled),
|
||||
cairo_image_surface_get_height(scaled),
|
||||
(uint32_t *) cairo_image_surface_get_data(scaled),
|
||||
cairo_image_surface_get_stride(scaled));
|
||||
|
||||
pixman_f_transform_init_scale(&xform_floating, scale_x, scale_y);
|
||||
pixman_f_transform_invert(&xform_floating, &xform_floating);
|
||||
pixman_transform_from_pixman_f_transform(&xform, &xform_floating);
|
||||
pixman_image_set_transform(src, &xform);
|
||||
pixman_image_set_filter(src, PIXMAN_FILTER_BILINEAR, NULL, 0);
|
||||
pixman_image_set_repeat(src, PIXMAN_REPEAT_PAD);
|
||||
|
||||
pixman_image_composite(PIXMAN_OP_SRC, src, NULL, dest, 0, 0, 0, 0, 0, 0,
|
||||
projected_width, projected_height);
|
||||
pixman_image_unref(src);
|
||||
pixman_image_unref(dest);
|
||||
|
||||
cairo_surface_destroy(thumbnail);
|
||||
cairo_surface_mark_dirty(scaled);
|
||||
return scaled;
|
||||
}
|
||||
|
||||
static void
|
||||
entry_add_thumbnail(gpointer data, gpointer user_data)
|
||||
{
|
||||
Entry *self = data;
|
||||
g_clear_object(&self->icon);
|
||||
if (self->thumbnail)
|
||||
cairo_surface_destroy(self->thumbnail);
|
||||
|
||||
FastivIoThumbnailSize size = FASTIV_BROWSER(user_data)->item_size;
|
||||
self->thumbnail = rescale_thumbnail(
|
||||
fastiv_io_lookup_thumbnail(self->filename, size), (int) size);
|
||||
if (self->thumbnail)
|
||||
return;
|
||||
|
||||
// Fall back to symbolic icons, though there's only so much we can do
|
||||
// in parallel--GTK+ isn't thread-safe.
|
||||
GFile *file = g_file_new_for_path(self->filename);
|
||||
GFileInfo *info = g_file_query_info(file,
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME
|
||||
"," G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON,
|
||||
G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
||||
g_object_unref(file);
|
||||
if (info) {
|
||||
GIcon *icon = g_file_info_get_symbolic_icon(info);
|
||||
if (icon)
|
||||
self->icon = g_object_ref(icon);
|
||||
g_object_unref(info);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
materialize_icon(FastivBrowser *self, Entry *entry)
|
||||
{
|
||||
if (!entry->icon)
|
||||
return;
|
||||
|
||||
// Fucker will still give us non-symbolic icons, no more playing nice.
|
||||
// TODO(p): Investigate a bit closer. We may want to abandon the idea
|
||||
// of using GLib to look up icons for us, derive a list from a guessed
|
||||
// MIME type, with "-symbolic" prefixes and fallbacks,
|
||||
// and use gtk_icon_theme_choose_icon() instead.
|
||||
// TODO(p): Make sure we have /some/ icon for every entry.
|
||||
// TODO(p): We might want to populate these on an as-needed basis.
|
||||
GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon(
|
||||
gtk_icon_theme_get_default(), entry->icon, (int) self->item_size / 2,
|
||||
GTK_ICON_LOOKUP_FORCE_SYMBOLIC);
|
||||
if (!icon_info)
|
||||
return;
|
||||
|
||||
// Bílá, bílá, bílá, bílá... komu by se nelíbí-lá...
|
||||
// We do not want any highlights, nor do we want to remember the style.
|
||||
const GdkRGBA white = {1, 1, 1, 1};
|
||||
GdkPixbuf *pixbuf = gtk_icon_info_load_symbolic(
|
||||
icon_info, &white, &white, &white, &white, NULL, NULL);
|
||||
if (pixbuf) {
|
||||
int outer_size = (int) self->item_size;
|
||||
entry->thumbnail =
|
||||
cairo_image_surface_create(CAIRO_FORMAT_A8, outer_size, outer_size);
|
||||
|
||||
// "Note that the resulting pixbuf may not be exactly this size;"
|
||||
// though GTK_ICON_LOOKUP_FORCE_SIZE is also an option.
|
||||
int x = (outer_size - gdk_pixbuf_get_width(pixbuf)) / 2;
|
||||
int y = (outer_size - gdk_pixbuf_get_height(pixbuf)) / 2;
|
||||
|
||||
cairo_t *cr = cairo_create(entry->thumbnail);
|
||||
gdk_cairo_set_source_pixbuf(cr, pixbuf, x, y);
|
||||
cairo_paint(cr);
|
||||
cairo_destroy(cr);
|
||||
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
g_object_unref(icon_info);
|
||||
}
|
||||
|
||||
static void
|
||||
reload_thumbnails(FastivBrowser *self)
|
||||
{
|
||||
GThreadPool *pool = g_thread_pool_new(
|
||||
entry_add_thumbnail, self, g_get_num_processors(), FALSE, NULL);
|
||||
for (guint i = 0; i < self->entries->len; i++)
|
||||
g_thread_pool_push(pool, &g_array_index(self->entries, Entry, i), NULL);
|
||||
g_thread_pool_free(pool, FALSE, TRUE);
|
||||
|
||||
for (guint i = 0; i < self->entries->len; i++)
|
||||
materialize_icon(self, &g_array_index(self->entries, Entry, i));
|
||||
|
||||
gtk_widget_queue_resize(GTK_WIDGET(self));
|
||||
}
|
||||
|
||||
// --- Boilerplate -------------------------------------------------------------
|
||||
|
||||
// TODO(p): For proper navigation, we need to implement GtkScrollable.
|
||||
|
@ -288,6 +442,13 @@ G_DEFINE_TYPE_EXTENDED(FastivBrowser, fastiv_browser, GTK_TYPE_WIDGET, 0,
|
|||
/* G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE,
|
||||
fastiv_browser_scrollable_init) */)
|
||||
|
||||
enum {
|
||||
PROP_THUMBNAIL_SIZE = 1,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *browser_properties[N_PROPERTIES];
|
||||
|
||||
enum {
|
||||
ITEM_ACTIVATED,
|
||||
LAST_SIGNAL,
|
||||
|
@ -308,6 +469,37 @@ fastiv_browser_finalize(GObject *gobject)
|
|||
G_OBJECT_CLASS(fastiv_browser_parent_class)->finalize(gobject);
|
||||
}
|
||||
|
||||
static void
|
||||
fastiv_browser_get_property(
|
||||
GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
FastivBrowser *self = FASTIV_BROWSER(object);
|
||||
switch (property_id) {
|
||||
case PROP_THUMBNAIL_SIZE:
|
||||
g_value_set_enum(value, self->item_size);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fastiv_browser_set_property(
|
||||
GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
FastivBrowser *self = FASTIV_BROWSER(object);
|
||||
switch (property_id) {
|
||||
case PROP_THUMBNAIL_SIZE:
|
||||
if (g_value_get_enum(value) != (int) self->item_size) {
|
||||
self->item_size = g_value_get_enum(value);
|
||||
reload_thumbnails(self);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static GtkSizeRequestMode
|
||||
fastiv_browser_get_request_mode(G_GNUC_UNUSED GtkWidget *widget)
|
||||
{
|
||||
|
@ -323,7 +515,8 @@ fastiv_browser_get_preferred_width(
|
|||
|
||||
GtkBorder padding = {};
|
||||
gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
|
||||
*minimum = *natural = g_permitted_width_multiplier * g_row_height +
|
||||
*minimum = *natural =
|
||||
g_permitted_width_multiplier * (int) self->item_size +
|
||||
padding.left + 2 * self->item_border_x + padding.right;
|
||||
}
|
||||
|
||||
|
@ -401,7 +594,7 @@ fastiv_browser_draw(GtkWidget *widget, cairo_t *cr)
|
|||
.x = 0,
|
||||
.y = row->y_offset - self->item_border_y,
|
||||
.width = allocation.width,
|
||||
.height = g_row_height + 2 * self->item_border_y,
|
||||
.height = (int) self->item_size + 2 * self->item_border_y,
|
||||
};
|
||||
if (!have_clip || gdk_rectangle_intersect(&clip, &extents, NULL))
|
||||
draw_row(self, cr, row);
|
||||
|
@ -520,6 +713,19 @@ fastiv_browser_class_init(FastivBrowserClass *klass)
|
|||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS(klass);
|
||||
object_class->finalize = fastiv_browser_finalize;
|
||||
object_class->get_property = fastiv_browser_get_property;
|
||||
object_class->set_property = fastiv_browser_set_property;
|
||||
|
||||
browser_properties[PROP_THUMBNAIL_SIZE] = g_param_spec_enum(
|
||||
"thumbnail-size", "Thumbnail size", "The thumbnail height to use",
|
||||
FASTIV_TYPE_IO_THUMBNAIL_SIZE, FASTIV_IO_THUMBNAIL_SIZE_NORMAL,
|
||||
G_PARAM_READWRITE);
|
||||
g_object_class_install_properties(
|
||||
object_class, N_PROPERTIES, browser_properties);
|
||||
|
||||
browser_signals[ITEM_ACTIVATED] = g_signal_new("item-activated",
|
||||
G_TYPE_FROM_CLASS(klass), 0, 0, NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 2, G_TYPE_STRING, GTK_TYPE_PLACES_OPEN_FLAGS);
|
||||
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
|
||||
widget_class->get_request_mode = fastiv_browser_get_request_mode;
|
||||
|
@ -533,10 +739,6 @@ fastiv_browser_class_init(FastivBrowserClass *klass)
|
|||
widget_class->motion_notify_event = fastiv_browser_motion_notify_event;
|
||||
widget_class->style_updated = fastiv_browser_style_updated;
|
||||
|
||||
browser_signals[ITEM_ACTIVATED] = g_signal_new("item-activated",
|
||||
G_TYPE_FROM_CLASS(klass), 0, 0, NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 2, G_TYPE_STRING, GTK_TYPE_PLACES_OPEN_FLAGS);
|
||||
|
||||
// TODO(p): Later override "screen_changed", recreate Pango layouts there,
|
||||
// if we get to have any, or otherwise reflect DPI changes.
|
||||
gtk_widget_class_set_css_name(widget_class, "fastiv-browser");
|
||||
|
@ -552,96 +754,15 @@ fastiv_browser_init(FastivBrowser *self)
|
|||
self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row));
|
||||
g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
|
||||
|
||||
self->item_size = FASTIV_IO_THUMBNAIL_SIZE_NORMAL;
|
||||
self->selected = -1;
|
||||
self->glow = cairo_image_surface_create(CAIRO_FORMAT_A1, 0, 0);
|
||||
|
||||
g_signal_connect_swapped(gtk_settings_get_default(),
|
||||
"notify::gtk-icon-theme-name", G_CALLBACK(reload_thumbnails), self);
|
||||
}
|
||||
|
||||
// NOTE: "It is important to note that when an image with an alpha channel is
|
||||
// scaled, linear encoded, pre-multiplied component values must be used!"
|
||||
static cairo_surface_t *
|
||||
rescale_thumbnail(cairo_surface_t *thumbnail)
|
||||
{
|
||||
if (!thumbnail)
|
||||
return thumbnail;
|
||||
|
||||
int width = cairo_image_surface_get_width(thumbnail);
|
||||
int height = cairo_image_surface_get_height(thumbnail);
|
||||
|
||||
double scale_x = 1;
|
||||
double scale_y = 1;
|
||||
if (width > g_permitted_width_multiplier * height) {
|
||||
scale_x = g_permitted_width_multiplier * g_row_height / width;
|
||||
scale_y = round(scale_x * height) / height;
|
||||
} else {
|
||||
scale_y = g_row_height / height;
|
||||
scale_x = round(scale_y * width) / width;
|
||||
}
|
||||
if (scale_x == 1 && scale_y == 1)
|
||||
return thumbnail;
|
||||
|
||||
int projected_width = round(scale_x * width);
|
||||
int projected_height = round(scale_y * height);
|
||||
cairo_surface_t *scaled = cairo_image_surface_create(
|
||||
CAIRO_FORMAT_ARGB32, projected_width, projected_height);
|
||||
|
||||
// pixman can take gamma into account when scaling, unlike Cairo.
|
||||
struct pixman_f_transform xform_floating;
|
||||
struct pixman_transform xform;
|
||||
|
||||
// PIXMAN_a8r8g8b8_sRGB can be used for gamma-correct results,
|
||||
// but it's an incredibly slow transformation
|
||||
pixman_format_code_t format = PIXMAN_a8r8g8b8;
|
||||
|
||||
pixman_image_t *src = pixman_image_create_bits(format, width, height,
|
||||
(uint32_t *) cairo_image_surface_get_data(thumbnail),
|
||||
cairo_image_surface_get_stride(thumbnail));
|
||||
pixman_image_t *dest = pixman_image_create_bits(format,
|
||||
cairo_image_surface_get_width(scaled),
|
||||
cairo_image_surface_get_height(scaled),
|
||||
(uint32_t *) cairo_image_surface_get_data(scaled),
|
||||
cairo_image_surface_get_stride(scaled));
|
||||
|
||||
pixman_f_transform_init_scale(&xform_floating, scale_x, scale_y);
|
||||
pixman_f_transform_invert(&xform_floating, &xform_floating);
|
||||
pixman_transform_from_pixman_f_transform(&xform, &xform_floating);
|
||||
pixman_image_set_transform(src, &xform);
|
||||
pixman_image_set_filter(src, PIXMAN_FILTER_BILINEAR, NULL, 0);
|
||||
pixman_image_set_repeat(src, PIXMAN_REPEAT_PAD);
|
||||
|
||||
pixman_image_composite(PIXMAN_OP_SRC, src, NULL, dest, 0, 0, 0, 0, 0, 0,
|
||||
projected_width, projected_height);
|
||||
pixman_image_unref(src);
|
||||
pixman_image_unref(dest);
|
||||
|
||||
cairo_surface_destroy(thumbnail);
|
||||
cairo_surface_mark_dirty(scaled);
|
||||
return scaled;
|
||||
}
|
||||
|
||||
static void
|
||||
entry_add_thumbnail(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
Entry *self = data;
|
||||
self->thumbnail =
|
||||
rescale_thumbnail(fastiv_io_lookup_thumbnail(self->filename));
|
||||
if (self->thumbnail)
|
||||
return;
|
||||
|
||||
// Fall back to symbolic icons, though there's only so much we can do
|
||||
// in parallel--GTK+ isn't thread-safe.
|
||||
GFile *file = g_file_new_for_path(self->filename);
|
||||
GFileInfo *info = g_file_query_info(file,
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME
|
||||
"," G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON,
|
||||
G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
||||
g_object_unref(file);
|
||||
if (info) {
|
||||
GIcon *icon = g_file_info_get_symbolic_icon(info);
|
||||
if (icon)
|
||||
self->icon = g_object_ref(icon);
|
||||
g_object_unref(info);
|
||||
}
|
||||
}
|
||||
// --- Public interface --------------------------------------------------------
|
||||
|
||||
void
|
||||
fastiv_browser_load(
|
||||
|
@ -676,56 +797,6 @@ fastiv_browser_load(
|
|||
}
|
||||
g_object_unref(enumerator);
|
||||
|
||||
GThreadPool *pool = g_thread_pool_new(
|
||||
entry_add_thumbnail, NULL, g_get_num_processors(), FALSE, NULL);
|
||||
for (guint i = 0; i < self->entries->len; i++)
|
||||
g_thread_pool_push(pool, &g_array_index(self->entries, Entry, i), NULL);
|
||||
g_thread_pool_free(pool, FALSE, TRUE);
|
||||
|
||||
for (guint i = 0; i < self->entries->len; i++) {
|
||||
Entry *entry = &g_array_index(self->entries, Entry, i);
|
||||
if (!entry->icon)
|
||||
continue;
|
||||
|
||||
// Fucker will still give us non-symbolic icons, no more playing nice.
|
||||
// TODO(p): Investigate a bit closer. We may want to abandon the idea
|
||||
// of using GLib to look up icons for us, derive a list from a guessed
|
||||
// MIME type, with "-symbolic" prefixes and fallbacks,
|
||||
// and use gtk_icon_theme_choose_icon() instead.
|
||||
// TODO(p): Make sure we have /some/ icon for every entry.
|
||||
// TODO(p): GtkSettings -> notify::gtk-icon-theme-name?
|
||||
// TODO(p): We might want to populate these on an as-needed basis.
|
||||
GtkIconInfo *icon_info =
|
||||
gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(),
|
||||
entry->icon, g_row_height / 2, GTK_ICON_LOOKUP_FORCE_SYMBOLIC);
|
||||
if (!icon_info)
|
||||
continue;
|
||||
|
||||
// Bílá, bílá, bílá, bílá... komu by se nelíbí-lá...
|
||||
// We do not want any highlights, nor do we want to remember the style.
|
||||
const GdkRGBA white = {1, 1, 1, 1};
|
||||
GdkPixbuf *pixbuf = gtk_icon_info_load_symbolic(
|
||||
icon_info, &white, &white, &white, &white, NULL, NULL);
|
||||
if (pixbuf) {
|
||||
int outer_size = g_row_height;
|
||||
entry->thumbnail = cairo_image_surface_create(
|
||||
CAIRO_FORMAT_A8, outer_size, outer_size);
|
||||
|
||||
// "Note that the resulting pixbuf may not be exactly this size;"
|
||||
// though GTK_ICON_LOOKUP_FORCE_SIZE is also an option.
|
||||
int x = (outer_size - gdk_pixbuf_get_width(pixbuf)) / 2;
|
||||
int y = (outer_size - gdk_pixbuf_get_height(pixbuf)) / 2;
|
||||
|
||||
cairo_t *cr = cairo_create(entry->thumbnail);
|
||||
gdk_cairo_set_source_pixbuf(cr, pixbuf, x, y);
|
||||
cairo_paint(cr);
|
||||
cairo_destroy(cr);
|
||||
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
g_object_unref(icon_info);
|
||||
}
|
||||
|
||||
// TODO(p): Sort and filter the entries.
|
||||
gtk_widget_queue_resize(GTK_WIDGET(self));
|
||||
// TODO(p): Sort the entries before.
|
||||
reload_thumbnails(self);
|
||||
}
|
||||
|
|
50
fastiv-io.c
50
fastiv-io.c
|
@ -102,6 +102,29 @@ fastiv_io_all_supported_media_types(void)
|
|||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
GType
|
||||
fastiv_io_thumbnail_size_get_type(void)
|
||||
{
|
||||
static gsize guard;
|
||||
if (g_once_init_enter(&guard)) {
|
||||
#define XX(name, value, dir) {FASTIV_IO_THUMBNAIL_SIZE_ ## name, \
|
||||
"FASTIV_IO_THUMBNAIL_SIZE_" #name, #name},
|
||||
static const GEnumValue values[] = {FASTIV_IO_THUMBNAIL_SIZES(XX) {}};
|
||||
#undef XX
|
||||
GType type = g_enum_register_static(
|
||||
g_intern_static_string("FastivIoThumbnailSize"), values);
|
||||
g_once_init_leave(&guard, type);
|
||||
}
|
||||
return guard;
|
||||
}
|
||||
|
||||
#define XX(name, value, dir) {FASTIV_IO_THUMBNAIL_SIZE_ ## name, dir},
|
||||
FastivIoThumbnailSizeInfo fastiv_io_thumbnail_sizes[] = {
|
||||
FASTIV_IO_THUMBNAIL_SIZES(XX) {}};
|
||||
#undef XX
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
#define FASTIV_IO_ERROR fastiv_io_error_quark()
|
||||
|
||||
G_DEFINE_QUARK(fastiv-io-error-quark, fastiv_io_error)
|
||||
|
@ -981,8 +1004,20 @@ fail_init:
|
|||
}
|
||||
|
||||
cairo_surface_t *
|
||||
fastiv_io_lookup_thumbnail(const gchar *target)
|
||||
fastiv_io_lookup_thumbnail(const gchar *target, FastivIoThumbnailSize size)
|
||||
{
|
||||
// TODO(p): Consider having linear enum constants,
|
||||
// and using a trivial lookup table. This is ridiculous.
|
||||
int size_index = 0;
|
||||
while (fastiv_io_thumbnail_sizes[size_index].size &&
|
||||
fastiv_io_thumbnail_sizes[size_index].size < size)
|
||||
size_index++;
|
||||
int size_count = size_index;
|
||||
while (fastiv_io_thumbnail_sizes[size_count].size)
|
||||
size_count++;
|
||||
|
||||
g_return_val_if_fail(fastiv_io_thumbnail_sizes[size_index].size, NULL);
|
||||
|
||||
GStatBuf st;
|
||||
if (g_stat(target, &st))
|
||||
return NULL;
|
||||
|
@ -997,17 +1032,22 @@ fastiv_io_lookup_thumbnail(const gchar *target)
|
|||
gchar *cache_dir = get_xdg_home_dir("XDG_CACHE_HOME", ".cache");
|
||||
|
||||
cairo_surface_t *result = NULL;
|
||||
const gchar *sizes[] = {"large", "x-large", "xx-large", "normal"};
|
||||
GError *error = NULL;
|
||||
for (gsize i = 0; !result && i < G_N_ELEMENTS(sizes); i++) {
|
||||
gchar *path = g_strdup_printf(
|
||||
"%s/thumbnails/%s/%s.png", cache_dir, sizes[i], sum);
|
||||
for (int i = 0; i < size_count; i++) {
|
||||
int size_use = size_index + i;
|
||||
if (size_use >= size_count)
|
||||
size_use = size_count - i - 1;
|
||||
|
||||
gchar *path = g_strdup_printf("%s/thumbnails/%s/%s.png", cache_dir,
|
||||
fastiv_io_thumbnail_sizes[size_use].directory_name, sum);
|
||||
result = read_spng_thumbnail(path, uri, st.st_mtim.tv_sec, &error);
|
||||
if (error) {
|
||||
g_debug("%s: %s", path, error->message);
|
||||
g_clear_error(&error);
|
||||
}
|
||||
g_free(path);
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
|
||||
g_free(cache_dir);
|
||||
|
|
29
fastiv-io.h
29
fastiv-io.h
|
@ -19,12 +19,39 @@
|
|||
|
||||
#include <cairo.h>
|
||||
#include <glib.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
extern const char *fastiv_io_supported_media_types[];
|
||||
|
||||
char **fastiv_io_all_supported_media_types(void);
|
||||
|
||||
// And this is how you avoid glib-mkenums.
|
||||
typedef enum _FastivIoThumbnailSize {
|
||||
#define FASTIV_IO_THUMBNAIL_SIZES(XX) \
|
||||
XX(SMALL, 128, "normal") \
|
||||
XX(NORMAL, 256, "large") \
|
||||
XX(LARGE, 512, "x-large") \
|
||||
XX(HUGE, 1024, "xx-large")
|
||||
#define XX(name, value, dir) FASTIV_IO_THUMBNAIL_SIZE_ ## name = value,
|
||||
FASTIV_IO_THUMBNAIL_SIZES(XX)
|
||||
#undef XX
|
||||
FASTIV_IO_THUMBNAIL_SIZE_MIN = FASTIV_IO_THUMBNAIL_SIZE_SMALL,
|
||||
FASTIV_IO_THUMBNAIL_SIZE_MAX = FASTIV_IO_THUMBNAIL_SIZE_HUGE
|
||||
} FastivIoThumbnailSize;
|
||||
|
||||
GType fastiv_io_thumbnail_size_get_type(void) G_GNUC_CONST;
|
||||
#define FASTIV_TYPE_IO_THUMBNAIL_SIZE (fastiv_io_thumbnail_size_get_type())
|
||||
|
||||
typedef struct _FastivIoThumbnailSizeInfo {
|
||||
FastivIoThumbnailSize size; ///< Nominal size in pixels
|
||||
const char *directory_name; ///< thumbnail-spec directory name
|
||||
} FastivIoThumbnailSizeInfo;
|
||||
|
||||
// The array is null-terminated.
|
||||
extern FastivIoThumbnailSizeInfo fastiv_io_thumbnail_sizes[];
|
||||
|
||||
cairo_surface_t *fastiv_io_open(const gchar *path, GError **error);
|
||||
cairo_surface_t *fastiv_io_open_from_data(
|
||||
const char *data, size_t len, const gchar *path, GError **error);
|
||||
cairo_surface_t *fastiv_io_lookup_thumbnail(const gchar *target);
|
||||
cairo_surface_t *fastiv_io_lookup_thumbnail(
|
||||
const gchar *target, FastivIoThumbnailSize size);
|
||||
|
|
58
fastiv.c
58
fastiv.c
|
@ -71,6 +71,8 @@ struct {
|
|||
GtkWidget *browser_scroller;
|
||||
GtkWidget *browser_paned;
|
||||
GtkWidget *browser_sidebar;
|
||||
GtkWidget *plus;
|
||||
GtkWidget *minus;
|
||||
} g;
|
||||
|
||||
static gboolean
|
||||
|
@ -330,6 +332,35 @@ on_open_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar, GFile *location,
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_toolbar_zoom(G_GNUC_UNUSED GtkButton *button, gpointer user_data)
|
||||
{
|
||||
FastivIoThumbnailSize size = FASTIV_IO_THUMBNAIL_SIZE_MIN;
|
||||
g_object_get(g.browser, "thumbnail-size", &size, NULL);
|
||||
|
||||
gintptr position = -1, count = 0, adjustment = (gintptr) user_data;
|
||||
while (fastiv_io_thumbnail_sizes[count].size) {
|
||||
if (fastiv_io_thumbnail_sizes[count].size == size)
|
||||
position = count;
|
||||
count++;
|
||||
}
|
||||
|
||||
position += adjustment;
|
||||
g_return_if_fail(position >= 0 && position < count);
|
||||
g_object_set(g.browser, "thumbnail-size",
|
||||
fastiv_io_thumbnail_sizes[position].size, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
on_notify_thumbnail_size(
|
||||
GObject *object, GParamSpec *param_spec, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
FastivIoThumbnailSize size = 0;
|
||||
g_object_get(object, g_param_spec_get_name(param_spec), &size, NULL);
|
||||
gtk_widget_set_sensitive(g.plus, size < FASTIV_IO_THUMBNAIL_SIZE_MAX);
|
||||
gtk_widget_set_sensitive(g.minus, size > FASTIV_IO_THUMBNAIL_SIZE_MIN);
|
||||
}
|
||||
|
||||
// Cursor keys, e.g., simply cannot be bound through accelerators
|
||||
// (and GtkWidget::keynav-failed would arguably be an awful solution).
|
||||
//
|
||||
|
@ -559,27 +590,30 @@ main(int argc, char *argv[])
|
|||
g_signal_connect(g.browser_sidebar, "open-location",
|
||||
G_CALLBACK(on_open_location), NULL);
|
||||
|
||||
GtkWidget *plus = gtk_button_new_from_icon_name("zoom-in-symbolic",
|
||||
g.plus = gtk_button_new_from_icon_name("zoom-in-symbolic",
|
||||
GTK_ICON_SIZE_BUTTON);
|
||||
gtk_widget_set_tooltip_text(plus, "Larger thumbnails");
|
||||
GtkWidget *minus = gtk_button_new_from_icon_name("zoom-out-symbolic",
|
||||
gtk_widget_set_tooltip_text(g.plus, "Larger thumbnails");
|
||||
g_signal_connect(g.plus, "clicked",
|
||||
G_CALLBACK(on_toolbar_zoom), (gpointer) +1);
|
||||
|
||||
g.minus = gtk_button_new_from_icon_name("zoom-out-symbolic",
|
||||
GTK_ICON_SIZE_BUTTON);
|
||||
gtk_widget_set_tooltip_text(minus, "Smaller thumbnails");
|
||||
gtk_widget_set_tooltip_text(g.minus, "Smaller thumbnails");
|
||||
g_signal_connect(g.minus, "clicked",
|
||||
G_CALLBACK(on_toolbar_zoom), (gpointer) -1);
|
||||
|
||||
GtkWidget *zoom_group = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_style_context_add_class(
|
||||
gtk_widget_get_style_context(zoom_group), GTK_STYLE_CLASS_LINKED);
|
||||
gtk_box_pack_start(GTK_BOX(zoom_group), plus, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(zoom_group), minus, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(zoom_group), g.plus, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(zoom_group), g.minus, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *funnel = gtk_toggle_button_new();
|
||||
gtk_container_add(GTK_CONTAINER(funnel),
|
||||
gtk_image_new_from_icon_name("funnel-symbolic", GTK_ICON_SIZE_BUTTON));
|
||||
gtk_widget_set_tooltip_text(funnel, "Hide unsupported files");
|
||||
|
||||
// TODO(p): Implement these as well.
|
||||
gtk_widget_set_sensitive(plus, FALSE);
|
||||
gtk_widget_set_sensitive(minus, FALSE);
|
||||
g_signal_connect(funnel, "toggled",
|
||||
G_CALLBACK(on_filtering_toggled), NULL);
|
||||
|
||||
GtkBox *toolbar =
|
||||
fastiv_sidebar_get_toolbar(FASTIV_SIDEBAR(g.browser_sidebar));
|
||||
|
@ -611,7 +645,9 @@ main(int argc, char *argv[])
|
|||
g.supported_globs = extract_mime_globs((const char **) types);
|
||||
g_strfreev(types);
|
||||
|
||||
g_signal_connect(funnel, "toggled", G_CALLBACK(on_filtering_toggled), NULL);
|
||||
g_signal_connect(g.browser, "notify::thumbnail-size",
|
||||
G_CALLBACK(on_notify_thumbnail_size), NULL);
|
||||
on_toolbar_zoom(NULL, (gpointer) 0);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(funnel), TRUE);
|
||||
|
||||
g.files = g_ptr_array_new_full(16, g_free);
|
||||
|
|
Loading…
Reference in New Issue