aboutsummaryrefslogtreecommitdiff
path: root/fiv-view.c
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2022-08-05 11:33:23 +0200
committerPřemysl Eric Janouch <p@janouch.name>2022-08-05 11:38:12 +0200
commit086dd66aa9c3cc333304579330b6e773e9c5071f (patch)
treee94bc7b32ce75b955f6feb1a2a7598c2aeebe26c /fiv-view.c
parent8c6fe0ad321a263ee56a3d6439d7b99630a1755c (diff)
downloadfiv-086dd66aa9c3cc333304579330b6e773e9c5071f.tar.gz
fiv-086dd66aa9c3cc333304579330b6e773e9c5071f.tar.xz
fiv-086dd66aa9c3cc333304579330b6e773e9c5071f.zip
Add the information dialog to context menus
Images don't need to be open for ExifTool to work. This also enables inspecting unsupported files, such as video.
Diffstat (limited to 'fiv-view.c')
-rw-r--r--fiv-view.c276
1 files changed, 2 insertions, 274 deletions
diff --git a/fiv-view.c b/fiv-view.c
index 283f0b0..09cba86 100644
--- a/fiv-view.c
+++ b/fiv-view.c
@@ -19,6 +19,7 @@
#include "fiv-io.h"
#include "fiv-view.h"
+#include "fiv-context-menu.h"
#include <math.h>
#include <stdbool.h>
@@ -87,8 +88,6 @@ struct _FivView {
G_DEFINE_TYPE_EXTENDED(FivView, fiv_view, GTK_TYPE_WIDGET, 0,
G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE, NULL))
-G_DEFINE_QUARK(fiv-view-cancellable-quark, fiv_view_cancellable)
-
typedef struct _Dimensions {
double width, height;
} Dimensions;
@@ -1153,281 +1152,10 @@ save_as(FivView *self, cairo_surface_t *frame)
}
}
-// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-static GtkWidget *
-info_start_group(GtkWidget *vbox, const char *group)
-{
- GtkWidget *label = gtk_label_new(group);
- gtk_widget_set_hexpand(label, TRUE);
- gtk_widget_set_halign(label, GTK_ALIGN_FILL);
- PangoAttrList *attrs = pango_attr_list_new();
- pango_attr_list_insert(attrs, pango_attr_weight_new(PANGO_WEIGHT_BOLD));
- gtk_label_set_attributes(GTK_LABEL(label), attrs);
- pango_attr_list_unref(attrs);
-
- GtkWidget *grid = gtk_grid_new();
- GtkWidget *expander = gtk_expander_new(NULL);
- gtk_expander_set_label_widget(GTK_EXPANDER(expander), label);
- gtk_expander_set_expanded(GTK_EXPANDER(expander), TRUE);
- gtk_container_add(GTK_CONTAINER(expander), grid);
- gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
- gtk_box_pack_start(GTK_BOX(vbox), expander, FALSE, FALSE, 0);
- return grid;
-}
-
-static GtkWidget *
-info_parse(char *tsv)
-{
- GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
- GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
-
- const char *last_group = NULL;
- GtkWidget *grid = NULL;
- int line = 1, row = 0;
- 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)) {
- grid = info_start_group(vbox, (last_group = group));
- row = 0;
- }
-
- GtkWidget *a = gtk_label_new(tag);
- gtk_size_group_add_widget(sg, a);
- gtk_label_set_selectable(GTK_LABEL(a), TRUE);
- gtk_label_set_xalign(GTK_LABEL(a), 0.);
- gtk_grid_attach(GTK_GRID(grid), a, 0, row, 1, 1);
-
- GtkWidget *b = gtk_label_new(value);
- gtk_label_set_selectable(GTK_LABEL(b), TRUE);
- gtk_label_set_xalign(GTK_LABEL(b), 0.);
- gtk_label_set_line_wrap(GTK_LABEL(b), TRUE);
- gtk_widget_set_hexpand(b, TRUE);
- gtk_grid_attach(GTK_GRID(grid), b, 1, row, 1, 1);
- row++;
- }
- g_object_unref(sg);
- return vbox;
-}
-
-static GtkWidget *
-info_make_bar(const char *message)
-{
- GtkWidget *info = gtk_info_bar_new();
- gtk_info_bar_set_message_type(GTK_INFO_BAR(info), GTK_MESSAGE_WARNING);
- GtkWidget *info_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info));
- gtk_container_add(GTK_CONTAINER(info_area), gtk_label_new(message));
- return info;
-}
-
-static void
-info_redirect_error(gpointer dialog, GError *error)
-{
- // The dialog has been closed and destroyed.
- if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
- g_error_free(error);
- return;
- }
-
- GtkContainer *content_area =
- GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog)));
- gtk_container_foreach(content_area, (GtkCallback) gtk_widget_destroy, NULL);
- gtk_container_add(content_area, info_make_bar(error->message));
- if (g_error_matches(error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT)) {
- gtk_box_pack_start(GTK_BOX(content_area),
- gtk_label_new("Please install ExifTool."), TRUE, FALSE, 12);
- }
-
- g_error_free(error);
- gtk_widget_show_all(GTK_WIDGET(dialog));
-}
-
-static gchar *
-bytes_to_utf8(GBytes *bytes)
-{
- gsize length = 0;
- gconstpointer data = g_bytes_get_data(bytes, &length);
- gchar *utf8 = data ? g_utf8_make_valid(data, length) : g_strdup("");
- g_bytes_unref(bytes);
- return utf8;
-}
-
-static void
-on_info_finished(GObject *source_object, GAsyncResult *res, gpointer user_data)
-{
- GError *error = NULL;
- GBytes *bytes_out = NULL, *bytes_err = NULL;
- if (!g_subprocess_communicate_finish(
- G_SUBPROCESS(source_object), res, &bytes_out, &bytes_err, &error)) {
- info_redirect_error(user_data, error);
- return;
- }
-
- gchar *out = bytes_to_utf8(bytes_out);
- gchar *err = bytes_to_utf8(bytes_err);
-
- GtkWidget *dialog = GTK_WIDGET(user_data);
- GtkWidget *content_area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
- gtk_container_foreach(
- GTK_CONTAINER(content_area), (GtkCallback) gtk_widget_destroy, NULL);
-
- GtkWidget *scroller = gtk_scrolled_window_new(NULL, NULL);
- gtk_box_pack_start(GTK_BOX(content_area), scroller, TRUE, TRUE, 0);
- GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
- gtk_container_add(GTK_CONTAINER(scroller), vbox);
- if (*err)
- gtk_container_add(GTK_CONTAINER(vbox), info_make_bar(g_strstrip(err)));
-
- GtkWidget *info = info_parse(out);
- gtk_style_context_add_class(
- gtk_widget_get_style_context(info), "fiv-information");
- gtk_box_pack_start(GTK_BOX(vbox), info, TRUE, TRUE, 0);
-
- g_free(out);
- g_free(err);
- gtk_widget_show_all(dialog);
-}
-
-static void
-info_spawn(GtkWidget *dialog, const char *path, GBytes *bytes_in)
-{
- int flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE;
- if (bytes_in)
- flags |= G_SUBPROCESS_FLAGS_STDIN_PIPE;
-
- // TODO(p): Add a fallback to internal capabilities.
- // The simplest is to specify the filename and the resolution.
- GError *error = NULL;
- GSubprocess *subprocess = g_subprocess_new(flags, &error, "exiftool",
- "-tab", "-groupNames", "-duplicates", "-extractEmbedded", "--binary",
- "-quiet", "--", path, NULL);
- if (error) {
- info_redirect_error(dialog, error);
- return;
- }
-
- GCancellable *cancellable =
- g_object_get_qdata(G_OBJECT(dialog), fiv_view_cancellable_quark());
- g_subprocess_communicate_async(
- subprocess, bytes_in, cancellable, on_info_finished, dialog);
- g_object_unref(subprocess);
-}
-
-static void
-on_info_loaded(GObject *source_object, GAsyncResult *res, gpointer user_data)
-{
- gchar *file_data = NULL;
- gsize file_len = 0;
- GError *error = NULL;
- if (!g_file_load_contents_finish(
- G_FILE(source_object), res, &file_data, &file_len, NULL, &error)) {
- info_redirect_error(user_data, error);
- return;
- }
-
- GtkWidget *dialog = GTK_WIDGET(user_data);
- GBytes *bytes_in = g_bytes_new_take(file_data, file_len);
- info_spawn(dialog, "-", bytes_in);
- g_bytes_unref(bytes_in);
-}
-
-static void
-on_info_queried(GObject *source_object, GAsyncResult *res, gpointer user_data)
-{
- GFile *file = G_FILE(source_object);
- GError *error = NULL;
- GFileInfo *info = g_file_query_info_finish(file, res, &error);
- gboolean cancelled =
- error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
- g_clear_error(&error);
- if (cancelled)
- return;
-
- gchar *path = NULL;
- const char *target_uri = g_file_info_get_attribute_string(
- info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
- if (target_uri) {
- GFile *target = g_file_new_for_uri(target_uri);
- path = g_file_get_path(target);
- g_object_unref(target);
- }
- g_object_unref(info);
-
- GtkWidget *dialog = GTK_WIDGET(user_data);
- GCancellable *cancellable =
- g_object_get_qdata(G_OBJECT(dialog), fiv_view_cancellable_quark());
- if (path) {
- info_spawn(dialog, path, NULL);
- g_free(path);
- } else {
- g_file_load_contents_async(file, cancellable, on_info_loaded, dialog);
- }
-}
-
static void
info(FivView *self)
{
- GtkWidget *dialog = gtk_widget_new(GTK_TYPE_DIALOG,
- "use-header-bar", TRUE,
- "title", "Information",
- "transient-for", get_toplevel(GTK_WIDGET(self)),
- "destroy-with-parent", TRUE, NULL);
-
- // When the window closes, we cancel all asynchronous calls.
- GCancellable *cancellable = g_cancellable_new();
- g_object_set_qdata_full(G_OBJECT(dialog), fiv_view_cancellable_quark(),
- cancellable, g_object_unref);
- g_signal_connect_swapped(
- dialog, "destroy", G_CALLBACK(g_cancellable_cancel), cancellable);
-
- GtkWidget *spinner = gtk_spinner_new();
- gtk_spinner_start(GTK_SPINNER(spinner));
- gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
- spinner, TRUE, TRUE, 12);
- gtk_window_set_default_size(GTK_WINDOW(dialog), 600, 800);
- gtk_widget_show_all(dialog);
-
- // Mostly for URIs with no local path--we pipe these into ExifTool.
- GFile *file = g_file_new_for_uri(self->uri);
- gchar *parse_name = g_file_get_parse_name(file);
- gtk_header_bar_set_subtitle(
- GTK_HEADER_BAR(gtk_dialog_get_header_bar(GTK_DIALOG(dialog))),
- parse_name);
- g_free(parse_name);
-
- gchar *path = g_file_get_path(file);
- if (path) {
- info_spawn(dialog, path, NULL);
- g_free(path);
- } else {
- // Several GVfs schemes contain pseudo-symlinks
- // that don't give out filesystem paths directly.
- g_file_query_info_async(file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
- G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, cancellable,
- on_info_queried, dialog);
- }
- g_object_unref(file);
+ fiv_context_menu_information(get_toplevel(GTK_WIDGET(self)), self->uri);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -