diff --git a/fiv-view.c b/fiv-view.c index 5f57899..26be2eb 100644 --- a/fiv-view.c +++ b/fiv-view.c @@ -50,6 +50,8 @@ fiv_view_command_get_type(void) struct _FivView { GtkWidget parent_instance; + + gchar *messages; ///< Image load information gchar *uri; ///< Path to the current image (if any) cairo_surface_t *image; ///< The loaded image (sequence) cairo_surface_t *page; ///< Current page within image, weak @@ -114,7 +116,8 @@ static FivIoOrientation view_right[9] = { }; enum { - PROP_SCALE = 1, + PROP_MESSAGES = 1, + PROP_SCALE, PROP_SCALE_TO_FIT, PROP_ENABLE_CMS, PROP_FILTER, @@ -147,6 +150,7 @@ fiv_view_finalize(GObject *gobject) g_clear_pointer(&self->image, cairo_surface_destroy); g_clear_pointer(&self->page_scaled, cairo_surface_destroy); g_free(self->uri); + g_free(self->messages); G_OBJECT_CLASS(fiv_view_parent_class)->finalize(gobject); } @@ -157,6 +161,9 @@ fiv_view_get_property( { FivView *self = FIV_VIEW(object); switch (property_id) { + case PROP_MESSAGES: + g_value_set_string(value, self->messages); + break; case PROP_SCALE: g_value_set_double(value, self->scale); break; @@ -253,8 +260,8 @@ fiv_view_get_preferred_height(GtkWidget *widget, gint *minimum, gint *natural) { FivView *self = FIV_VIEW(widget); if (self->scale_to_fit) { - *natural = ceil(get_surface_dimensions(self).height); *minimum = 1; + *natural = MAX(*minimum, ceil(get_surface_dimensions(self).height)); } else { int dw, dh; get_display_dimensions(self, &dw, &dh); @@ -267,8 +274,8 @@ fiv_view_get_preferred_width(GtkWidget *widget, gint *minimum, gint *natural) { FivView *self = FIV_VIEW(widget); if (self->scale_to_fit) { - *natural = ceil(get_surface_dimensions(self).width); *minimum = 1; + *natural = MAX(*minimum, ceil(get_surface_dimensions(self).width)); } else { int dw, dh; get_display_dimensions(self, &dw, &dh); @@ -279,9 +286,9 @@ fiv_view_get_preferred_width(GtkWidget *widget, gint *minimum, gint *natural) static void prescale_page(FivView *self) { - FivIoRenderClosure *closure = - cairo_surface_get_user_data(self->page, &fiv_io_key_render); - if (!closure) + FivIoRenderClosure *closure = NULL; + if (!self->image || !(closure = + cairo_surface_get_user_data(self->page, &fiv_io_key_render))) return; // TODO(p): Restart the animation. No vector formats currently animate. @@ -678,8 +685,9 @@ switch_page(FivView *self, cairo_surface_t *page) self->frame = self->page = page; prescale_page(self); - if ((self->orientation = (uintptr_t) cairo_surface_get_user_data( - self->page, &fiv_io_key_orientation)) == FivIoOrientationUnknown) + if (!self->page || + (self->orientation = (uintptr_t) cairo_surface_get_user_data( + self->page, &fiv_io_key_orientation)) == FivIoOrientationUnknown) self->orientation = FivIoOrientation0; start_animating(self); @@ -1044,6 +1052,9 @@ fiv_view_class_init(FivViewClass *klass) object_class->get_property = fiv_view_get_property; object_class->set_property = fiv_view_set_property; + view_properties[PROP_MESSAGES] = g_param_spec_string( + "messages", "Messages", "Informative messages from the last image load", + NULL, G_PARAM_READABLE); view_properties[PROP_SCALE] = g_param_spec_double( "scale", "Scale", "Zoom level", 0, G_MAXDOUBLE, 1.0, G_PARAM_READABLE); @@ -1151,15 +1162,8 @@ fiv_view_init(FivView *self) // TODO(p): Progressive picture loading, or at least async/cancellable. gboolean -fiv_view_open(FivView *self, const gchar *uri, GError **error) +fiv_view_set_uri(FivView *self, const gchar *uri) { - cairo_surface_t *surface = fiv_io_open( - uri, self->enable_cms ? self->screen_cms_profile : NULL, FALSE, error); - if (!surface) - return FALSE; - if (self->image) - cairo_surface_destroy(self->image); - // This is extremely expensive, and only works sometimes. g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); if (self->enhance) { @@ -1168,6 +1172,17 @@ fiv_view_open(FivView *self, const gchar *uri, GError **error) G_OBJECT(self), view_properties[PROP_ENHANCE]); } + GError *error = NULL; + cairo_surface_t *surface = fiv_io_open( + uri, self->enable_cms ? self->screen_cms_profile : NULL, FALSE, &error); + + g_clear_pointer(&self->messages, g_free); + g_clear_pointer(&self->image, cairo_surface_destroy); + if (error) { + self->messages = g_strdup(error->message); + g_error_free(error); + } + self->frame = self->page = NULL; self->image = surface; switch_page(self, self->image); @@ -1176,8 +1191,9 @@ fiv_view_open(FivView *self, const gchar *uri, GError **error) g_free(self->uri); self->uri = g_strdup(uri); + g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_MESSAGES]); g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_HAS_IMAGE]); - return TRUE; + return surface != NULL; } static void @@ -1208,11 +1224,17 @@ reload(FivView *self) cairo_surface_t *surface = fiv_io_open(self->uri, self->enable_cms ? self->screen_cms_profile : NULL, self->enhance, &error); - if (!surface) { - show_error_dialog(get_toplevel(GTK_WIDGET(self)), error); - return FALSE; + + g_clear_pointer(&self->messages, g_free); + if (error) { + self->messages = g_strdup(error->message); + g_error_free(error); } + g_object_notify_by_pspec(G_OBJECT(self), view_properties[PROP_MESSAGES]); + if (error) + return FALSE; + g_clear_pointer(&self->image, cairo_surface_destroy); g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); switch_page(self, (self->image = surface)); diff --git a/fiv-view.h b/fiv-view.h index 54093b7..7df00e0 100644 --- a/fiv-view.h +++ b/fiv-view.h @@ -23,7 +23,8 @@ G_DECLARE_FINAL_TYPE(FivView, fiv_view, FIV, VIEW, GtkWidget) /// Try to open the given file, synchronously, to be displayed by the widget. -gboolean fiv_view_open(FivView *self, const gchar *uri, GError **error); +/// The current image is cleared on failure. +gboolean fiv_view_set_uri(FivView *self, const gchar *uri); // And this is how you avoid glib-mkenums. typedef enum _FivViewCommand { diff --git a/fiv.c b/fiv.c index 7102db0..49a04a5 100644 --- a/fiv.c +++ b/fiv.c @@ -542,6 +542,8 @@ struct { GtkWidget *view_box; GtkWidget *view_toolbar; + GtkWidget *view_info; + GtkWidget *view_info_label; GtkWidget *toolbar[TOOLBAR_COUNT]; GtkWidget *view; } g; @@ -717,33 +719,39 @@ on_sort_direction(G_GNUC_UNUSED GtkMenuItem *item, gpointer data) g_object_set(g.model, "sort-descending", (gboolean) (intptr_t) data, NULL); } +static void +on_notify_view_messages(FivView *view, G_GNUC_UNUSED gpointer user_data) +{ + gchar *messages = NULL; + g_object_get(view, "messages", &messages, NULL); + if (messages) { + gchar *escaped = g_markup_escape_text(messages, -1); + g_free(messages); + gchar *message = g_strdup_printf("Error: %s", escaped); + g_free(escaped); + gtk_label_set_markup(GTK_LABEL(g.view_info_label), message); + g_free(message); + gtk_widget_show(g.view_info); + } else { + gtk_widget_hide(g.view_info); + } +} + static void open(const gchar *uri) { GFile *file = g_file_new_for_uri(uri); + if (fiv_view_set_uri(FIV_VIEW(g.view), uri)) + gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri); - GError *error = NULL; - if (!fiv_view_open(FIV_VIEW(g.view), uri, &error)) { - const char *path = g_file_peek_path(file); - // TODO(p): Use g_file_info_get_display_name(). - gchar *base = g_filename_display_basename(path ? path : uri); - g_object_unref(file); - - g_prefix_error(&error, "%s: ", base); - show_error_dialog(error); - g_free(base); - return; - } - - gtk_recent_manager_add_item(gtk_recent_manager_get_default(), uri); g_list_free_full(g.directory_forward, g_free); g.directory_forward = NULL; - g_free(g.uri); g.uri = g_strdup(uri); // So that load_directory() itself can be used for reloading. gchar *parent = parent_uri(file); + g_object_unref(file); if (!g.files->len /* hack to always load the directory after launch */ || !g.directory || strcmp(parent, g.directory)) load_directory_without_switching(parent); @@ -1785,9 +1793,26 @@ main(int argc, char *argv[]) gtk_box_pack_start(GTK_BOX(g.view_toolbar), gtk_separator_new(GTK_ORIENTATION_VERTICAL), FALSE, FALSE, 0); + g.view_info = gtk_info_bar_new(); + // The button cannot be made flat or reflect the message type under Adwaita. + gtk_info_bar_set_show_close_button(GTK_INFO_BAR(g.view_info), TRUE); + // Do not use gtk_info_bar_set_revealed(), as it animates. + gtk_info_bar_set_message_type(GTK_INFO_BAR(g.view_info), GTK_MESSAGE_ERROR); + g_signal_connect(g.view_info, "response", + G_CALLBACK(gtk_widget_hide), NULL); + + g.view_info_label = gtk_label_new(NULL); + gtk_label_set_line_wrap(GTK_LABEL(g.view_info_label), TRUE); + gtk_container_add( + GTK_CONTAINER(gtk_info_bar_get_content_area(GTK_INFO_BAR(g.view_info))), + g.view_info_label); + g_signal_connect(g.view, "notify::messages", + G_CALLBACK(on_notify_view_messages), NULL); + // Need to put the toolbar at the top, because of the horizontal scrollbar. g.view_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_box_pack_start(GTK_BOX(g.view_box), g.view_toolbar, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(g.view_box), g.view_info, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(g.view_box), view_scroller, TRUE, TRUE, 0); g.browser_scroller = gtk_scrolled_window_new(NULL, NULL);