Compare commits
3 Commits
ad29013e44
...
b35590a51c
Author | SHA1 | Date | |
---|---|---|---|
b35590a51c | |||
9899a26635 | |||
24f9d21ca7 |
@ -5,8 +5,7 @@ fastiv
|
|||||||
raw photos, HEIC, AVIF, WebP, SVG, X11 cursors and TIFF, or whatever gdk-pixbuf
|
raw photos, HEIC, AVIF, WebP, SVG, X11 cursors and TIFF, or whatever gdk-pixbuf
|
||||||
loads.
|
loads.
|
||||||
|
|
||||||
It still has some road to go, but it's already become quite usable,
|
Its development status can be summarized as '`beta`'.
|
||||||
and it has received basic polishing.
|
|
||||||
|
|
||||||
Non-goals
|
Non-goals
|
||||||
---------
|
---------
|
||||||
@ -27,7 +26,7 @@ Build dependencies: Meson, pkg-config +
|
|||||||
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
|
Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info,
|
||||||
spng>=0.7.0, libturbojpeg +
|
spng>=0.7.0, libturbojpeg +
|
||||||
Optional dependencies: LibRaw, librsvg-2.0, xcursor, libwebp, libheif, libtiff,
|
Optional dependencies: LibRaw, librsvg-2.0, xcursor, libwebp, libheif, libtiff,
|
||||||
gdk-pixbuf-2.0
|
gdk-pixbuf-2.0, ExifTool
|
||||||
|
|
||||||
$ git clone --recursive https://git.janouch.name/p/fastiv.git
|
$ git clone --recursive https://git.janouch.name/p/fastiv.git
|
||||||
$ meson builddir
|
$ meson builddir
|
||||||
|
233
fastiv.c
233
fastiv.c
@ -52,7 +52,7 @@ exit_fatal(const gchar *format, ...)
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Help --------------------------------------------------------------------
|
// --- Keyboard shortcuts ------------------------------------------------------
|
||||||
// Fuck XML, this can be easily represented in static structures.
|
// Fuck XML, this can be easily represented in static structures.
|
||||||
// Though it would be nice if the accelerators could be customized.
|
// Though it would be nice if the accelerators could be customized.
|
||||||
|
|
||||||
@ -81,76 +81,81 @@ static struct key help_keys_general[] = {
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct key_group help_keys_browser[] = {
|
||||||
|
{"General", help_keys_general},
|
||||||
|
{"View", (struct key[]) {
|
||||||
|
{"F9", "Toggle navigation sidebar"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{"Navigation", (struct key[]) {
|
||||||
|
{"<control>l", "Open location..."},
|
||||||
|
{"<control>n", "Open a new window"},
|
||||||
|
{"<alt>Left", "Go back in history"},
|
||||||
|
{"<alt>Right", "Go forward in history"},
|
||||||
|
{"<alt>Up", "Go to parent directory"},
|
||||||
|
{"<alt>Home", "Go home"},
|
||||||
|
{"F5 r <control>r", "Refresh"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct key_group help_keys_view[] = {
|
||||||
|
{"General", help_keys_general},
|
||||||
|
{"View", (struct key[]) {
|
||||||
|
{"F8", "Toggle toolbar"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{"Navigation", (struct key[]) {
|
||||||
|
{"<control>l", "Open location..."},
|
||||||
|
{"<control>n", "Open a new window"},
|
||||||
|
{"Left Up Page_Up", "Previous image"},
|
||||||
|
{"Right Down Page_Down", "Next image"},
|
||||||
|
{"Return <alt>Left", "Return to browser"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{"Zoom", (struct key[]) {
|
||||||
|
{"<control>0", "Set zoom to 100%"},
|
||||||
|
{"1...9", "Set zoom to N:1"},
|
||||||
|
{"plus <control>plus", "Zoom in"},
|
||||||
|
{"minus <control>minus", "Zoom out"},
|
||||||
|
{"w", "Zoom to fit width if larger"},
|
||||||
|
{"h", "Zoom to fit height if larger"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{"Orientation", (struct key[]) {
|
||||||
|
{"less", "Rotate anticlockwise"},
|
||||||
|
{"equal", "Mirror"},
|
||||||
|
{"greater", "Rotate clockwise"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{"Configuration", (struct key[]) {
|
||||||
|
{"x", "Toggle scale to fit if larger"},
|
||||||
|
{"i", "Toggle smooth scaling"},
|
||||||
|
{"t", "Toggle transparency highlighting"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{"Control", (struct key[]) {
|
||||||
|
{"bracketleft", "Previous page"},
|
||||||
|
{"bracketright", "Next page"},
|
||||||
|
{"braceleft", "Previous frame"},
|
||||||
|
{"braceright", "Next frame"},
|
||||||
|
{"space", "Toggle playback"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{"Tools", (struct key[]) {
|
||||||
|
{"<control>p", "Print..."},
|
||||||
|
{"<control>s", "Save page as..."},
|
||||||
|
{"<control><shift>s", "Save frame as..."},
|
||||||
|
{"<alt>Return", "Show file information"},
|
||||||
|
{}
|
||||||
|
}},
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
static struct key_section help_keys[] = {
|
static struct key_section help_keys[] = {
|
||||||
{"Browser", "browser", (struct key_group[]) {
|
{"Browser", "browser", help_keys_browser},
|
||||||
{"General", help_keys_general},
|
{"View", "view", help_keys_view},
|
||||||
{"View", (struct key[]) {
|
|
||||||
{"F9", "Toggle navigation sidebar"},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{"Navigation", (struct key[]) {
|
|
||||||
{"<control>l", "Open location..."},
|
|
||||||
{"<control>n", "Open a new window"},
|
|
||||||
{"<alt>Left", "Go back in history"},
|
|
||||||
{"<alt>Right", "Go forward in history"},
|
|
||||||
{"<alt>Up", "Go to parent directory"},
|
|
||||||
{"<alt>Home", "Go home"},
|
|
||||||
{"F5 r <control>r", "Refresh"},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{"View", "view", (struct key_group[]) {
|
|
||||||
{"General", help_keys_general},
|
|
||||||
{"View", (struct key[]) {
|
|
||||||
{"F8", "Toggle toolbar"},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{"Navigation", (struct key[]) {
|
|
||||||
{"<control>l", "Open location..."},
|
|
||||||
{"<control>n", "Open a new window"},
|
|
||||||
{"Left Up Page_Up", "Previous image"},
|
|
||||||
{"Right Down Page_Down", "Next image"},
|
|
||||||
{"Return <alt>Left", "Return to browser"},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{"Zoom", (struct key[]) {
|
|
||||||
{"<control>0", "Set zoom to 100%"},
|
|
||||||
{"1...9", "Set zoom to N:1"},
|
|
||||||
{"plus <control>plus", "Zoom in"},
|
|
||||||
{"minus <control>minus", "Zoom out"},
|
|
||||||
{"w", "Zoom to fit width if larger"},
|
|
||||||
{"h", "Zoom to fit height if larger"},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{"Orientation", (struct key[]) {
|
|
||||||
{"less", "Rotate anticlockwise"},
|
|
||||||
{"equal", "Mirror"},
|
|
||||||
{"greater", "Rotate clockwise"},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{"Configuration", (struct key[]) {
|
|
||||||
{"x", "Toggle scale to fit if larger"},
|
|
||||||
{"i", "Toggle smooth scaling"},
|
|
||||||
{"t", "Toggle transparency highlighting"},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{"Control", (struct key[]) {
|
|
||||||
{"bracketleft", "Previous page"},
|
|
||||||
{"bracketright", "Next page"},
|
|
||||||
{"braceleft", "Previous frame"},
|
|
||||||
{"braceright", "Next frame"},
|
|
||||||
{"space", "Toggle playback"},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{"Tools", (struct key[]) {
|
|
||||||
{"<control>p", "Print..."},
|
|
||||||
{"<control>s", "Save page as..."},
|
|
||||||
{"<control><shift>s", "Save frame as..."},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{}
|
|
||||||
}},
|
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -199,7 +204,6 @@ make_key_window(void)
|
|||||||
|
|
||||||
// TODO(p): See if it's possible to give separators room to shrink
|
// TODO(p): See if it's possible to give separators room to shrink
|
||||||
// by some minor amount of pixels, margin-wise.
|
// by some minor amount of pixels, margin-wise.
|
||||||
// TODO(p): Implement commented-out actions.
|
|
||||||
#define B make_toolbar_button
|
#define B make_toolbar_button
|
||||||
#define T make_toolbar_toggle
|
#define T make_toolbar_toggle
|
||||||
#define TOOLBAR(XX) \
|
#define TOOLBAR(XX) \
|
||||||
@ -230,7 +234,7 @@ make_key_window(void)
|
|||||||
/* XX(COLOR, B("preferences-color-symbolic", "Color management")) */ \
|
/* XX(COLOR, B("preferences-color-symbolic", "Color management")) */ \
|
||||||
XX(SAVE, B("document-save-as-symbolic", "Save as...")) \
|
XX(SAVE, B("document-save-as-symbolic", "Save as...")) \
|
||||||
XX(PRINT, B("document-print-symbolic", "Print...")) \
|
XX(PRINT, B("document-print-symbolic", "Print...")) \
|
||||||
/* XX(INFO, B("info-symbolic", "Information")) */ \
|
XX(INFO, B("info-symbolic", "Information")) \
|
||||||
XX(S5, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
|
XX(S5, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL)) \
|
||||||
XX(LEFT, B("object-rotate-left-symbolic", "Rotate left")) \
|
XX(LEFT, B("object-rotate-left-symbolic", "Rotate left")) \
|
||||||
XX(MIRROR, B("object-flip-horizontal-symbolic", "Mirror")) \
|
XX(MIRROR, B("object-flip-horizontal-symbolic", "Mirror")) \
|
||||||
@ -944,6 +948,7 @@ on_view_actions_changed(void)
|
|||||||
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_CHECKERBOARD], has_image);
|
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_CHECKERBOARD], has_image);
|
||||||
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_SAVE], has_image);
|
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_SAVE], has_image);
|
||||||
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_PRINT], has_image);
|
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_PRINT], has_image);
|
||||||
|
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_INFO], has_image);
|
||||||
|
|
||||||
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_LEFT], has_image);
|
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_LEFT], has_image);
|
||||||
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_MIRROR], has_image);
|
gtk_widget_set_sensitive(g.toolbar[TOOLBAR_MIRROR], has_image);
|
||||||
@ -1088,6 +1093,7 @@ make_view_toolbar(void)
|
|||||||
toolbar_toggler(TOOLBAR_CHECKERBOARD, "checkerboard");
|
toolbar_toggler(TOOLBAR_CHECKERBOARD, "checkerboard");
|
||||||
toolbar_command(TOOLBAR_PRINT, FIV_VIEW_COMMAND_PRINT);
|
toolbar_command(TOOLBAR_PRINT, FIV_VIEW_COMMAND_PRINT);
|
||||||
toolbar_command(TOOLBAR_SAVE, FIV_VIEW_COMMAND_SAVE_PAGE);
|
toolbar_command(TOOLBAR_SAVE, FIV_VIEW_COMMAND_SAVE_PAGE);
|
||||||
|
toolbar_command(TOOLBAR_INFO, FIV_VIEW_COMMAND_INFO);
|
||||||
toolbar_command(TOOLBAR_LEFT, FIV_VIEW_COMMAND_ROTATE_LEFT);
|
toolbar_command(TOOLBAR_LEFT, FIV_VIEW_COMMAND_ROTATE_LEFT);
|
||||||
toolbar_command(TOOLBAR_MIRROR, FIV_VIEW_COMMAND_MIRROR);
|
toolbar_command(TOOLBAR_MIRROR, FIV_VIEW_COMMAND_MIRROR);
|
||||||
toolbar_command(TOOLBAR_RIGHT, FIV_VIEW_COMMAND_ROTATE_RIGHT);
|
toolbar_command(TOOLBAR_RIGHT, FIV_VIEW_COMMAND_ROTATE_RIGHT);
|
||||||
@ -1119,6 +1125,43 @@ make_view_toolbar(void)
|
|||||||
return view_toolbar;
|
return view_toolbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is incredibly broken https://stackoverflow.com/a/51054396/76313
|
||||||
|
// thus resolving the problem using overlaps.
|
||||||
|
// We're trying to be universal for light and dark themes both. It's hard.
|
||||||
|
static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \
|
||||||
|
fiv-view, fiv-browser { background: @content_view_bg; } \
|
||||||
|
placessidebar.fiv .toolbar { padding: 2px 6px; } \
|
||||||
|
placessidebar.fiv box > separator { margin: 4px 0; } \
|
||||||
|
#toolbar button { padding-left: 0; padding-right: 0; } \
|
||||||
|
#toolbar > button:first-child { padding-left: 4px; } \
|
||||||
|
#toolbar > button:last-child { padding-right: 4px; } \
|
||||||
|
#toolbar separator { \
|
||||||
|
background: mix(@insensitive_fg_color, \
|
||||||
|
@insensitive_bg_color, 0.4); margin: 6px 10px; \
|
||||||
|
} \
|
||||||
|
fiv-browser { padding: 5px; } \
|
||||||
|
fiv-browser.item { \
|
||||||
|
color: mix(#000, @content_view_bg, 0.625); margin: 8px; \
|
||||||
|
border: 2px solid #fff; \
|
||||||
|
} \
|
||||||
|
fiv-browser.item, fiv-view.checkerboard { \
|
||||||
|
background: @theme_bg_color; background-image: \
|
||||||
|
linear-gradient(45deg, @fiv-tile 26%, transparent 26%), \
|
||||||
|
linear-gradient(-45deg, @fiv-tile 26%, transparent 26%), \
|
||||||
|
linear-gradient(45deg, transparent 74%, @fiv-tile 74%), \
|
||||||
|
linear-gradient(-45deg, transparent 74%, @fiv-tile 74%); \
|
||||||
|
background-size: 40px 40px; \
|
||||||
|
background-position: 0 0, 0 20px, 20px -20px, -20px 0px; \
|
||||||
|
} \
|
||||||
|
fiv-browser.item:backdrop { \
|
||||||
|
color: mix(#000, @content_view_bg, 0.875); \
|
||||||
|
border-color: mix(#fff, @content_view_bg, 0.5); \
|
||||||
|
} \
|
||||||
|
fiv-browser.item.symbolic { \
|
||||||
|
border-color: transparent; color: shade(@theme_bg_color, 0.875); \
|
||||||
|
background: @theme_bg_color; background-image: none; \
|
||||||
|
}";
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
@ -1165,45 +1208,9 @@ main(int argc, char *argv[])
|
|||||||
gtk_icon_theme_add_resource_path(
|
gtk_icon_theme_add_resource_path(
|
||||||
gtk_icon_theme_get_default(), "/org/gnome/design/IconLibrary/");
|
gtk_icon_theme_get_default(), "/org/gnome/design/IconLibrary/");
|
||||||
|
|
||||||
// This is incredibly broken https://stackoverflow.com/a/51054396/76313
|
|
||||||
// thus resolving the problem using overlaps.
|
|
||||||
// We're trying to be universal for light and dark themes both. It's hard.
|
|
||||||
const char *style = "@define-color fiv-tile @content_view_bg; \
|
|
||||||
fiv-view, fiv-browser { background: @content_view_bg; } \
|
|
||||||
placessidebar.fiv .toolbar { padding: 2px 6px; } \
|
|
||||||
placessidebar.fiv box > separator { margin: 4px 0; } \
|
|
||||||
#toolbar button { padding-left: 0; padding-right: 0; } \
|
|
||||||
#toolbar > button:first-child { padding-left: 4px; } \
|
|
||||||
#toolbar > button:last-child { padding-right: 4px; } \
|
|
||||||
#toolbar separator { \
|
|
||||||
background: mix(@insensitive_fg_color, \
|
|
||||||
@insensitive_bg_color, 0.4); margin: 6px 10px; \
|
|
||||||
} \
|
|
||||||
fiv-browser { padding: 5px; } \
|
|
||||||
fiv-browser.item { \
|
|
||||||
color: mix(#000, @content_view_bg, 0.625); margin: 8px; \
|
|
||||||
border: 2px solid #fff; \
|
|
||||||
} \
|
|
||||||
fiv-browser.item, fiv-view.checkerboard { \
|
|
||||||
background: @theme_bg_color; background-image: \
|
|
||||||
linear-gradient(45deg, @fiv-tile 26%, transparent 26%), \
|
|
||||||
linear-gradient(-45deg, @fiv-tile 26%, transparent 26%), \
|
|
||||||
linear-gradient(45deg, transparent 74%, @fiv-tile 74%), \
|
|
||||||
linear-gradient(-45deg, transparent 74%, @fiv-tile 74%); \
|
|
||||||
background-size: 40px 40px; \
|
|
||||||
background-position: 0 0, 0 20px, 20px -20px, -20px 0px; \
|
|
||||||
} \
|
|
||||||
fiv-browser.item:backdrop { \
|
|
||||||
color: mix(#000, @content_view_bg, 0.875); \
|
|
||||||
border-color: mix(#fff, @content_view_bg, 0.5); \
|
|
||||||
} \
|
|
||||||
fiv-browser.item.symbolic { \
|
|
||||||
border-color: transparent; color: shade(@theme_bg_color, 0.875); \
|
|
||||||
background: @theme_bg_color; background-image: none; \
|
|
||||||
}";
|
|
||||||
|
|
||||||
GtkCssProvider *provider = gtk_css_provider_new();
|
GtkCssProvider *provider = gtk_css_provider_new();
|
||||||
gtk_css_provider_load_from_data(provider, style, strlen(style), NULL);
|
gtk_css_provider_load_from_data(
|
||||||
|
provider, stylesheet, sizeof stylesheet - 1, NULL);
|
||||||
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
|
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
|
||||||
GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
g_object_unref(provider);
|
g_object_unref(provider);
|
||||||
@ -1238,10 +1245,8 @@ main(int argc, char *argv[])
|
|||||||
gtk_container_set_focus_hadjustment(GTK_CONTAINER(browser_port), NULL);
|
gtk_container_set_focus_hadjustment(GTK_CONTAINER(browser_port), NULL);
|
||||||
gtk_container_set_focus_vadjustment(GTK_CONTAINER(browser_port), NULL);
|
gtk_container_set_focus_vadjustment(GTK_CONTAINER(browser_port), NULL);
|
||||||
|
|
||||||
// TODO(p): As with GtkFileChooserWidget, bind:
|
// TODO(p): As with GtkFileChooserWidget, bind C-h to filtering,
|
||||||
// - C-h to filtering,
|
// and mayhaps forward the rest to the sidebar, somehow.
|
||||||
// - M-Up to going a level above,
|
|
||||||
// - mayhaps forward the rest to the sidebar, somehow.
|
|
||||||
g.browser_sidebar = g_object_new(FIV_TYPE_SIDEBAR, NULL);
|
g.browser_sidebar = g_object_new(FIV_TYPE_SIDEBAR, NULL);
|
||||||
g_signal_connect(g.browser_sidebar, "open-location",
|
g_signal_connect(g.browser_sidebar, "open-location",
|
||||||
G_CALLBACK(on_open_location), NULL);
|
G_CALLBACK(on_open_location), NULL);
|
||||||
|
134
fiv-view.c
134
fiv-view.c
@ -786,6 +786,132 @@ save_as(FivView *self, gboolean frame)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum { INFO_KEY, INFO_VALUE, INFO_WEIGHT, INFO_COUNT };
|
||||||
|
|
||||||
|
static GtkTreeModel *
|
||||||
|
info_model(char *tsv)
|
||||||
|
{
|
||||||
|
GtkTreeStore *store = gtk_tree_store_new(
|
||||||
|
INFO_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
|
||||||
|
GtkTreeIter category = {}, entry = {};
|
||||||
|
const char *last_group = NULL;
|
||||||
|
|
||||||
|
int line = 1;
|
||||||
|
for (char *nl; (nl = strchr(tsv, '\n')); line++, tsv = ++nl) {
|
||||||
|
*nl = 0;
|
||||||
|
if (nl > tsv && nl[-1] == '\r')
|
||||||
|
nl[-1] = 0;
|
||||||
|
|
||||||
|
char *group = tsv, *tag = strchr(group, '\t');
|
||||||
|
if (!tag) {
|
||||||
|
g_warning("ExifTool parse error on line %d", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*tag++ = 0;
|
||||||
|
for (char *p = group; *p; p++)
|
||||||
|
if (*p == '_')
|
||||||
|
*p = ' ';
|
||||||
|
|
||||||
|
char *value = strchr(tag, '\t');
|
||||||
|
if (!value) {
|
||||||
|
g_warning("ExifTool parse error on line %d", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*value++ = 0;
|
||||||
|
if (!last_group || strcmp(last_group, group)) {
|
||||||
|
last_group = group;
|
||||||
|
|
||||||
|
gtk_tree_store_append(store, &category, NULL);
|
||||||
|
gtk_tree_store_set(store, &category, INFO_KEY, group,
|
||||||
|
INFO_WEIGHT, PANGO_WEIGHT_BOLD, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_tree_store_append(store, &entry, &category);
|
||||||
|
gtk_tree_store_set(store, &entry, INFO_KEY, tag, INFO_VALUE, value,
|
||||||
|
INFO_WEIGHT, PANGO_WEIGHT_NORMAL, -1);
|
||||||
|
}
|
||||||
|
return GTK_TREE_MODEL(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
info(FivView *self)
|
||||||
|
{
|
||||||
|
// TODO(p): Add a fallback to internal capabilities.
|
||||||
|
// The simplest is to specify the filename and the resolution.
|
||||||
|
GtkWindow *window = get_toplevel(GTK_WIDGET(self));
|
||||||
|
int flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE;
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
GSubprocess *subprocess = g_subprocess_new(flags, &error, "exiftool",
|
||||||
|
"-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary",
|
||||||
|
"-quiet", "--", self->path, NULL);
|
||||||
|
if (error) {
|
||||||
|
show_error_dialog(window, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gchar *out = NULL, *err = NULL;
|
||||||
|
if (!g_subprocess_communicate_utf8(
|
||||||
|
subprocess, NULL, NULL, &out, &err, &error)) {
|
||||||
|
show_error_dialog(window, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *dialog = gtk_widget_new(GTK_TYPE_DIALOG,
|
||||||
|
"use-header-bar", TRUE,
|
||||||
|
"title", "Information",
|
||||||
|
"transient-for", window,
|
||||||
|
"destroy-with-parent", TRUE, NULL);
|
||||||
|
|
||||||
|
GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
|
||||||
|
if (*err) {
|
||||||
|
GtkWidget *info = gtk_info_bar_new();
|
||||||
|
GtkInfoBar *info_bar = GTK_INFO_BAR(info);
|
||||||
|
gtk_info_bar_set_message_type(info_bar, GTK_MESSAGE_WARNING);
|
||||||
|
|
||||||
|
GtkWidget *info_area = gtk_info_bar_get_content_area(info_bar);
|
||||||
|
GtkWidget *label = gtk_label_new(g_strstrip(err));
|
||||||
|
gtk_container_add(GTK_CONTAINER(info_area), label);
|
||||||
|
|
||||||
|
gtk_container_add(GTK_CONTAINER(content_area), info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(p): Replace this disaster with:
|
||||||
|
// GtkBox -> GtkExpander, GtkBox/GtkGrid -> GtkLabel, GtkSizeGroup.
|
||||||
|
// We'll lose search, but the user will be able to copy text out.
|
||||||
|
GtkWidget *view = gtk_tree_view_new();
|
||||||
|
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1,
|
||||||
|
"Field", gtk_cell_renderer_text_new(), "text", INFO_KEY,
|
||||||
|
"weight", INFO_WEIGHT, NULL);
|
||||||
|
gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1,
|
||||||
|
"Value", gtk_cell_renderer_text_new(), "text", INFO_VALUE, NULL);
|
||||||
|
|
||||||
|
GtkTreeModel *model = info_model(out);
|
||||||
|
gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
|
||||||
|
gtk_tree_view_expand_all(GTK_TREE_VIEW(view));
|
||||||
|
g_object_unref(model);
|
||||||
|
|
||||||
|
// GtkTreeView doesn't have a useful natural height.
|
||||||
|
GtkWidget *scroller = gtk_scrolled_window_new(NULL, NULL);
|
||||||
|
gtk_scrolled_window_set_max_content_width(
|
||||||
|
GTK_SCROLLED_WINDOW(scroller), 800);
|
||||||
|
gtk_scrolled_window_set_propagate_natural_width(
|
||||||
|
GTK_SCROLLED_WINDOW(scroller), TRUE);
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(dialog), -1, 800);
|
||||||
|
|
||||||
|
gtk_widget_set_hexpand(view, TRUE);
|
||||||
|
gtk_widget_set_vexpand(view, TRUE);
|
||||||
|
gtk_container_add(GTK_CONTAINER(scroller), view);
|
||||||
|
gtk_container_add(GTK_CONTAINER(content_area), scroller);
|
||||||
|
|
||||||
|
g_free(out);
|
||||||
|
g_free(err);
|
||||||
|
g_object_unref(subprocess);
|
||||||
|
gtk_widget_show_all(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
static inline gboolean
|
static inline gboolean
|
||||||
@ -823,6 +949,12 @@ fiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event)
|
|||||||
return save_as(self, TRUE);
|
return save_as(self, TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (state == GDK_MOD1_MASK) {
|
||||||
|
switch (event->keyval) {
|
||||||
|
case GDK_KEY_Return:
|
||||||
|
return command(self, FIV_VIEW_COMMAND_INFO);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (state != 0)
|
if (state != 0)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
@ -1043,6 +1175,8 @@ fiv_view_command(FivView *self, FivViewCommand command)
|
|||||||
print(self);
|
print(self);
|
||||||
break; case FIV_VIEW_COMMAND_SAVE_PAGE:
|
break; case FIV_VIEW_COMMAND_SAVE_PAGE:
|
||||||
save_as(self, FALSE);
|
save_as(self, FALSE);
|
||||||
|
break; case FIV_VIEW_COMMAND_INFO:
|
||||||
|
info(self);
|
||||||
|
|
||||||
break; case FIV_VIEW_COMMAND_ZOOM_IN:
|
break; case FIV_VIEW_COMMAND_ZOOM_IN:
|
||||||
set_scale(self, self->scale * SCALE_STEP);
|
set_scale(self, self->scale * SCALE_STEP);
|
||||||
|
@ -45,6 +45,7 @@ typedef enum _FivViewCommand {
|
|||||||
FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD,
|
FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD,
|
||||||
FIV_VIEW_COMMAND_PRINT,
|
FIV_VIEW_COMMAND_PRINT,
|
||||||
FIV_VIEW_COMMAND_SAVE_PAGE,
|
FIV_VIEW_COMMAND_SAVE_PAGE,
|
||||||
|
FIV_VIEW_COMMAND_INFO,
|
||||||
|
|
||||||
FIV_VIEW_COMMAND_ZOOM_IN,
|
FIV_VIEW_COMMAND_ZOOM_IN,
|
||||||
FIV_VIEW_COMMAND_ZOOM_OUT,
|
FIV_VIEW_COMMAND_ZOOM_OUT,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user