Compare commits
5 Commits
0bec06b55d
...
8efd11d4e5
Author | SHA1 | Date | |
---|---|---|---|
8efd11d4e5 | |||
a3855e8f12 | |||
e239aca6f4 | |||
e663368ee4 | |||
8070c7f9ee |
@ -2,12 +2,16 @@ fastiv
|
|||||||
======
|
======
|
||||||
|
|
||||||
'fastiv' is a fast image viewer, supporting BMP, PNG, GIF, JPEG, and optionally
|
'fastiv' is a fast image viewer, supporting BMP, PNG, GIF, JPEG, and optionally
|
||||||
RAW and SVG pictures, or whatever gdk-pixbuf loads. Currently, it's very basic.
|
RAW, SVG and X11 cursors, or whatever gdk-pixbuf loads.
|
||||||
|
|
||||||
|
It still has some road to go, but it's already become quite usable,
|
||||||
|
and it has received basic polishing.
|
||||||
|
|
||||||
Non-goals
|
Non-goals
|
||||||
---------
|
---------
|
||||||
- fancy UI--the focus is on speed of use first, colour accuracy second
|
- fancy UI--the focus is on speed of use first, colour accuracy second
|
||||||
- editing--that's what _editors_ are for, be it GIMP or Rawtherapee
|
- editing--that's what _editors_ are for, be it GIMP or Rawtherapee;
|
||||||
|
nothing beyond the most basic of adjustments is desired
|
||||||
- memory efficiency, though preloading can cause some pressure
|
- memory efficiency, though preloading can cause some pressure
|
||||||
- portability to non-UNIXy systems
|
- portability to non-UNIXy systems
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ struct _FastivBrowser {
|
|||||||
|
|
||||||
FastivIoThumbnailSize item_size; ///< Thumbnail size
|
FastivIoThumbnailSize item_size; ///< Thumbnail size
|
||||||
int item_height; ///< Thumbnail height in pixels
|
int item_height; ///< Thumbnail height in pixels
|
||||||
|
int item_spacing; ///< Space between items in pixels
|
||||||
|
|
||||||
GArray *entries; ///< [Entry]
|
GArray *entries; ///< [Entry]
|
||||||
GArray *layouted_rows; ///< [Row]
|
GArray *layouted_rows; ///< [Row]
|
||||||
@ -60,10 +61,6 @@ typedef struct row Row;
|
|||||||
|
|
||||||
static const double g_permitted_width_multiplier = 2;
|
static const double g_permitted_width_multiplier = 2;
|
||||||
|
|
||||||
// Could be split out to also-idiomatic row-spacing/column-spacing properties.
|
|
||||||
// TODO(p): Make a property for this.
|
|
||||||
static const int g_item_spacing = 1;
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
struct entry {
|
struct entry {
|
||||||
@ -106,7 +103,7 @@ static void
|
|||||||
append_row(FastivBrowser *self, int *y, int x, GArray *items_array)
|
append_row(FastivBrowser *self, int *y, int x, GArray *items_array)
|
||||||
{
|
{
|
||||||
if (self->layouted_rows->len)
|
if (self->layouted_rows->len)
|
||||||
*y += g_item_spacing;
|
*y += self->item_spacing;
|
||||||
|
|
||||||
*y += self->item_border_y;
|
*y += self->item_border_y;
|
||||||
g_array_append_val(self->layouted_rows, ((Row) {
|
g_array_append_val(self->layouted_rows, ((Row) {
|
||||||
@ -143,8 +140,8 @@ relayout(FastivBrowser *self, int width)
|
|||||||
2 * self->item_border_x;
|
2 * self->item_border_x;
|
||||||
if (!items->len) {
|
if (!items->len) {
|
||||||
// Just insert it, whether or not there's any space.
|
// Just insert it, whether or not there's any space.
|
||||||
} else if (x + g_item_spacing + width <= available_width) {
|
} else if (x + self->item_spacing + width <= available_width) {
|
||||||
x += g_item_spacing;
|
x += self->item_spacing;
|
||||||
} else {
|
} else {
|
||||||
append_row(self, &y,
|
append_row(self, &y,
|
||||||
padding.left + MAX(0, available_width - x) / 2, items);
|
padding.left + MAX(0, available_width - x) / 2, items);
|
||||||
@ -485,6 +482,23 @@ fastiv_browser_get_property(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
set_item_size(FastivBrowser *self, FastivIoThumbnailSize size)
|
||||||
|
{
|
||||||
|
if (size < FASTIV_IO_THUMBNAIL_SIZE_MIN ||
|
||||||
|
size > FASTIV_IO_THUMBNAIL_SIZE_MAX)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (size != self->item_size) {
|
||||||
|
self->item_size = size;
|
||||||
|
self->item_height = fastiv_io_thumbnail_sizes[self->item_size].size;
|
||||||
|
reload_thumbnails(self);
|
||||||
|
|
||||||
|
g_object_notify_by_pspec(
|
||||||
|
G_OBJECT(self), browser_properties[PROP_THUMBNAIL_SIZE]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
fastiv_browser_set_property(
|
fastiv_browser_set_property(
|
||||||
GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
|
GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
|
||||||
@ -492,11 +506,7 @@ fastiv_browser_set_property(
|
|||||||
FastivBrowser *self = FASTIV_BROWSER(object);
|
FastivBrowser *self = FASTIV_BROWSER(object);
|
||||||
switch (property_id) {
|
switch (property_id) {
|
||||||
case PROP_THUMBNAIL_SIZE:
|
case PROP_THUMBNAIL_SIZE:
|
||||||
if (g_value_get_enum(value) != (int) self->item_size) {
|
set_item_size(self, g_value_get_enum(value));
|
||||||
self->item_size = g_value_get_enum(value);
|
|
||||||
self->item_height = fastiv_io_thumbnail_sizes[self->item_size].size;
|
|
||||||
reload_thumbnails(self);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
||||||
@ -550,7 +560,8 @@ fastiv_browser_realize(GtkWidget *widget)
|
|||||||
|
|
||||||
.visual = gtk_widget_get_visual(widget),
|
.visual = gtk_widget_get_visual(widget),
|
||||||
.event_mask = gtk_widget_get_events(widget) | GDK_KEY_PRESS_MASK |
|
.event_mask = gtk_widget_get_events(widget) | GDK_KEY_PRESS_MASK |
|
||||||
GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK,
|
GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK |
|
||||||
|
GDK_SCROLL_MASK,
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need this window to receive input events at all.
|
// We need this window to receive input events at all.
|
||||||
@ -652,6 +663,51 @@ fastiv_browser_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
fastiv_browser_scroll_event(GtkWidget *widget, GdkEventScroll *event)
|
||||||
|
{
|
||||||
|
FastivBrowser *self = FASTIV_BROWSER(widget);
|
||||||
|
if ((event->state & gtk_accelerator_get_default_mod_mask()) !=
|
||||||
|
GDK_CONTROL_MASK)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
switch (event->direction) {
|
||||||
|
case GDK_SCROLL_UP:
|
||||||
|
set_item_size(self, self->item_size + 1);
|
||||||
|
return TRUE;
|
||||||
|
case GDK_SCROLL_DOWN:
|
||||||
|
set_item_size(self, self->item_size - 1);
|
||||||
|
return TRUE;
|
||||||
|
default:
|
||||||
|
// For some reason, we can also get GDK_SCROLL_SMOOTH.
|
||||||
|
// Left/right are good to steal from GtkScrolledWindow for consistency.
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
fastiv_browser_query_tooltip(GtkWidget *widget, gint x, gint y,
|
||||||
|
G_GNUC_UNUSED gboolean keyboard_tooltip, GtkTooltip *tooltip)
|
||||||
|
{
|
||||||
|
FastivBrowser *self = FASTIV_BROWSER(widget);
|
||||||
|
const Entry *entry = entry_at(self, x, y);
|
||||||
|
if (!entry)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
GFile *file = g_file_new_for_path(entry->filename);
|
||||||
|
GFileInfo *info = g_file_query_info(file,
|
||||||
|
G_FILE_ATTRIBUTE_STANDARD_NAME
|
||||||
|
"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
|
||||||
|
G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
||||||
|
g_object_unref(file);
|
||||||
|
if (!info)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gtk_tooltip_set_text(tooltip, g_file_info_get_display_name(info));
|
||||||
|
g_object_unref(info);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
fastiv_browser_style_updated(GtkWidget *widget)
|
fastiv_browser_style_updated(GtkWidget *widget)
|
||||||
{
|
{
|
||||||
@ -661,6 +717,11 @@ fastiv_browser_style_updated(GtkWidget *widget)
|
|||||||
GtkStyleContext *style = gtk_widget_get_style_context(widget);
|
GtkStyleContext *style = gtk_widget_get_style_context(widget);
|
||||||
GtkBorder border = {}, margin = {};
|
GtkBorder border = {}, margin = {};
|
||||||
|
|
||||||
|
int item_spacing = self->item_spacing;
|
||||||
|
gtk_widget_style_get(widget, "spacing", &self->item_spacing, NULL);
|
||||||
|
if (item_spacing != self->item_spacing)
|
||||||
|
gtk_widget_queue_resize(widget);
|
||||||
|
|
||||||
// Using a pseudo-class, because GTK+ regions are deprecated.
|
// Using a pseudo-class, because GTK+ regions are deprecated.
|
||||||
gtk_style_context_save(style);
|
gtk_style_context_save(style);
|
||||||
gtk_style_context_add_class(style, "item");
|
gtk_style_context_add_class(style, "item");
|
||||||
@ -741,8 +802,16 @@ fastiv_browser_class_init(FastivBrowserClass *klass)
|
|||||||
widget_class->size_allocate = fastiv_browser_size_allocate;
|
widget_class->size_allocate = fastiv_browser_size_allocate;
|
||||||
widget_class->button_press_event = fastiv_browser_button_press_event;
|
widget_class->button_press_event = fastiv_browser_button_press_event;
|
||||||
widget_class->motion_notify_event = fastiv_browser_motion_notify_event;
|
widget_class->motion_notify_event = fastiv_browser_motion_notify_event;
|
||||||
|
widget_class->scroll_event = fastiv_browser_scroll_event;
|
||||||
|
widget_class->query_tooltip = fastiv_browser_query_tooltip;
|
||||||
widget_class->style_updated = fastiv_browser_style_updated;
|
widget_class->style_updated = fastiv_browser_style_updated;
|
||||||
|
|
||||||
|
// Could be split to also-idiomatic row-spacing/column-spacing properties.
|
||||||
|
// The GParamSpec is sinked by this call.
|
||||||
|
gtk_widget_class_install_style_property(widget_class,
|
||||||
|
g_param_spec_int("spacing", "Spacing", "Space between items",
|
||||||
|
0, G_MAXINT, 1, G_PARAM_READWRITE));
|
||||||
|
|
||||||
// TODO(p): Later override "screen_changed", recreate Pango layouts there,
|
// TODO(p): Later override "screen_changed", recreate Pango layouts there,
|
||||||
// if we get to have any, or otherwise reflect DPI changes.
|
// if we get to have any, or otherwise reflect DPI changes.
|
||||||
gtk_widget_class_set_css_name(widget_class, "fastiv-browser");
|
gtk_widget_class_set_css_name(widget_class, "fastiv-browser");
|
||||||
@ -752,14 +821,14 @@ static void
|
|||||||
fastiv_browser_init(FastivBrowser *self)
|
fastiv_browser_init(FastivBrowser *self)
|
||||||
{
|
{
|
||||||
gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE);
|
gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE);
|
||||||
|
gtk_widget_set_has_tooltip(GTK_WIDGET(self), TRUE);
|
||||||
|
|
||||||
self->entries = g_array_new(FALSE, TRUE, sizeof(Entry));
|
self->entries = g_array_new(FALSE, TRUE, sizeof(Entry));
|
||||||
g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free);
|
g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free);
|
||||||
self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row));
|
self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row));
|
||||||
g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
|
g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
|
||||||
|
|
||||||
self->item_size = FASTIV_IO_THUMBNAIL_SIZE_NORMAL;
|
set_item_size(self, FASTIV_IO_THUMBNAIL_SIZE_NORMAL);
|
||||||
self->item_height = fastiv_io_thumbnail_sizes[self->item_size].size;
|
|
||||||
self->selected = -1;
|
self->selected = -1;
|
||||||
self->glow = cairo_image_surface_create(CAIRO_FORMAT_A1, 0, 0);
|
self->glow = cairo_image_surface_create(CAIRO_FORMAT_A1, 0, 0);
|
||||||
|
|
||||||
|
@ -67,6 +67,19 @@ fastiv_sidebar_class_init(FastivSidebarClass *klass)
|
|||||||
NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_FILE);
|
NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_rowlabel_query_tooltip(GtkWidget *widget,
|
||||||
|
G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y,
|
||||||
|
G_GNUC_UNUSED gboolean keyboard_tooltip, GtkTooltip *tooltip)
|
||||||
|
{
|
||||||
|
GtkLabel *label = GTK_LABEL(widget);
|
||||||
|
if (!pango_layout_is_ellipsized(gtk_label_get_layout(label)))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gtk_tooltip_set_text(tooltip, gtk_label_get_text(label));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
static GtkWidget *
|
static GtkWidget *
|
||||||
create_row(GFile *file, const char *icon_name)
|
create_row(GFile *file, const char *icon_name)
|
||||||
{
|
{
|
||||||
@ -88,6 +101,9 @@ create_row(GFile *file, const char *icon_name)
|
|||||||
|
|
||||||
GtkWidget *rowlabel = gtk_label_new(name);
|
GtkWidget *rowlabel = gtk_label_new(name);
|
||||||
gtk_label_set_ellipsize(GTK_LABEL(rowlabel), PANGO_ELLIPSIZE_END);
|
gtk_label_set_ellipsize(GTK_LABEL(rowlabel), PANGO_ELLIPSIZE_END);
|
||||||
|
gtk_widget_set_has_tooltip(rowlabel, TRUE);
|
||||||
|
g_signal_connect(rowlabel, "query-tooltip",
|
||||||
|
G_CALLBACK(on_rowlabel_query_tooltip), NULL);
|
||||||
gtk_style_context_add_class(
|
gtk_style_context_add_class(
|
||||||
gtk_widget_get_style_context(rowlabel), "sidebar-label");
|
gtk_widget_get_style_context(rowlabel), "sidebar-label");
|
||||||
gtk_container_add(GTK_CONTAINER(rowbox), rowlabel);
|
gtk_container_add(GTK_CONTAINER(rowbox), rowlabel);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user