diff --git a/po/POTFILES.in b/po/POTFILES.in index 8e3e9e5198ef63aab6aed64f4719d20a720668c2..7b865f28105ec1a9d15041ebeb94c0c31c9358b0 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -75,7 +75,6 @@ src/gnome-cmd-xfer.cc src/intviewer/cp437.cc src/intviewer/datapresentation.cc src/intviewer/fileops.cc -src/intviewer/image-render.cc src/intviewer/inputmodes.cc src/intviewer/search_dialog.rs src/intviewer/search_progress_dialog.rs diff --git a/src/intviewer/image-render.cc b/src/intviewer/image-render.cc deleted file mode 100644 index 1acabf641318bcdddc8d244bff80a088fc706ab4..0000000000000000000000000000000000000000 --- a/src/intviewer/image-render.cc +++ /dev/null @@ -1,995 +0,0 @@ -/** - * @file image-render.cc - * @brief Part of GNOME Commander - A GNOME based file manager - * - * @copyright (C) 2006 Assaf Gordon\n - * @copyright (C) 2007-2012 Piotr Eljasiak\n - * @copyright (C) 2013-2024 Uwe Scholz\n - * - * @copyright This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * @copyright This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * @copyright You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include - -#include -#include -#include -#include - -#include - -#include - -#include "image-render.h" -#include "gnome-cmd-includes.h" -#include "gnome-cmd-file.h" -#include "utils.h" - -using namespace std; - - -#define IMAGE_RENDER_DEFAULT_WIDTH 100 -#define IMAGE_RENDER_DEFAULT_HEIGHT 200 -#define INC_VALUE 25.0 - -enum { - PROP_0, - PROP_HADJUSTMENT, - PROP_VADJUSTMENT, - PROP_HSCROLL_POLICY, - PROP_VSCROLL_POLICY -}; - -enum { - IMAGE_STATUS_CHANGED, - LAST_SIGNAL -}; - -static guint image_render_signals[LAST_SIGNAL] = { 0 }; - - -struct ImageRenderClass -{ - GtkWidgetClass parent_class; - void (*image_status_changed) (ImageRender *obj, ImageRender::Status *status); -}; - -// Class Private Data -struct ImageRenderPrivate -{ - guint button; // The button pressed to drag an image - double mouseX; // Old x position before mouse move - double mouseY; // Old y position before mouse move - - GtkAdjustment *h_adjustment; - GtkScrollablePolicy hscroll_policy; - - GtkAdjustment *v_adjustment; - GtkScrollablePolicy vscroll_policy; - - gchar *filename; - gboolean scaled_pixbuf_loaded; - GdkPixbuf *orig_pixbuf; - GdkPixbuf *disp_pixbuf; - gboolean best_fit; - gdouble scale_factor; - - GThread *pixbuf_loading_thread; - gint orig_pixbuf_loaded; -}; - -// Gtk class related static functions -static void image_render_redraw (ImageRender *w); - -static gboolean image_render_key_press (GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, gpointer user_data); -static void image_render_scroll (GtkEventControllerScroll *controller, double dx, double dy, gpointer user_data); - -static void image_render_realize (GtkWidget *widget, gpointer user_data); -static void image_render_measure (GtkWidget* widget, GtkOrientation orientation, int for_size, int* minimum, int* natural, int* minimum_baseline, int* natural_baseline); -static void image_render_size_allocate (GtkWidget *widget, int width, int height, int baseline); -static void image_render_draw (GtkWidget* widget, GtkSnapshot* snapshot); -static void image_render_button_press (GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data); -static void image_render_button_release (GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data); -static void image_render_motion_notify (GtkEventControllerMotion *controller, double x, double y, gpointer user_data); -static void image_render_h_adjustment_update (ImageRender *obj); -static void image_render_h_adjustment_changed (GtkAdjustment *adjustment, gpointer data); -static void image_render_h_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data); -static void image_render_v_adjustment_update (ImageRender *obj); -static void image_render_v_adjustment_changed (GtkAdjustment *adjustment, gpointer data); -static void image_render_v_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data); - -void image_render_start_background_pixbuf_loading (ImageRender *w); -void image_render_load_scaled_pixbuf (ImageRender *obj); -void image_render_wait_for_loader_thread (ImageRender *obj); - -static void image_render_free_pixbuf (ImageRender *obj); -static void image_render_prepare_disp_pixbuf (ImageRender *obj); -static void image_render_update_adjustments (ImageRender *obj); - - -G_DEFINE_TYPE_EXTENDED (ImageRender, - image_render, - GTK_TYPE_WIDGET, - 0, - G_ADD_PRIVATE (ImageRender) - G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL)) - - -static void image_render_set_h_adjustment (ImageRender *obj, GtkAdjustment *adjustment) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - if (priv->h_adjustment) - { - g_signal_handlers_disconnect_matched (priv->h_adjustment, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, obj); - g_object_unref (priv->h_adjustment); - } - - priv->h_adjustment = adjustment ? adjustment : gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); - g_object_ref (priv->h_adjustment); - - gtk_adjustment_set_lower (priv->h_adjustment, 0.0); - gtk_adjustment_set_upper (priv->h_adjustment, priv->disp_pixbuf ? gdk_pixbuf_get_width (priv->disp_pixbuf) : 0.0); - gtk_adjustment_set_step_increment (priv->h_adjustment, INC_VALUE); - gtk_adjustment_set_page_increment (priv->h_adjustment, 100.0); - gtk_adjustment_set_page_size (priv->h_adjustment, 100.0); - - g_signal_connect (priv->h_adjustment, "changed", G_CALLBACK (image_render_h_adjustment_changed), obj); - g_signal_connect (priv->h_adjustment, "value-changed", G_CALLBACK (image_render_h_adjustment_value_changed), obj); - - image_render_h_adjustment_update (obj); -} - - -static void image_render_set_v_adjustment (ImageRender *obj, GtkAdjustment *adjustment) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - if (priv->v_adjustment) - { - g_signal_handlers_disconnect_matched (priv->v_adjustment, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, obj); - g_object_unref (priv->v_adjustment); - } - - priv->v_adjustment = adjustment ? adjustment : gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0); - g_object_ref (priv->v_adjustment); - - gtk_adjustment_set_lower (priv->v_adjustment, 0.0); - gtk_adjustment_set_upper (priv->v_adjustment, priv->disp_pixbuf ? gdk_pixbuf_get_height (priv->disp_pixbuf) : 0.0); - gtk_adjustment_set_step_increment (priv->v_adjustment, INC_VALUE); - gtk_adjustment_set_page_increment (priv->v_adjustment, 100.0); - gtk_adjustment_set_page_size (priv->v_adjustment, 100.0); - - g_signal_connect (priv->v_adjustment, "changed", G_CALLBACK (image_render_v_adjustment_changed), obj); - g_signal_connect (priv->v_adjustment, "value-changed", G_CALLBACK (image_render_v_adjustment_value_changed), obj); - - image_render_v_adjustment_update (obj); -} - - -static void image_render_init (ImageRender *w) -{ - auto priv = static_cast(image_render_get_instance_private (w)); - - priv->button = 0; - - priv->scaled_pixbuf_loaded = FALSE; - priv->filename = NULL; - - priv->h_adjustment = NULL; - priv->hscroll_policy = GTK_SCROLL_MINIMUM; - - priv->v_adjustment = NULL; - priv->vscroll_policy = GTK_SCROLL_MINIMUM; - - priv->best_fit = FALSE; - priv->scale_factor = 1.3; - priv->orig_pixbuf = NULL; - priv->disp_pixbuf = NULL; - - gtk_widget_set_can_focus (GTK_WIDGET (w), TRUE); - - g_signal_connect_after (w, "realize", G_CALLBACK (image_render_realize), w); - - GtkEventController *scroll_controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES); - gtk_widget_add_controller (GTK_WIDGET (w), GTK_EVENT_CONTROLLER (scroll_controller)); - g_signal_connect (scroll_controller, "scroll", G_CALLBACK (image_render_scroll), w); - - GtkGesture *button_gesture = gtk_gesture_click_new (); - gtk_widget_add_controller (GTK_WIDGET (w), GTK_EVENT_CONTROLLER (button_gesture)); - g_signal_connect (button_gesture, "pressed", G_CALLBACK (image_render_button_press), w); - g_signal_connect (button_gesture, "released", G_CALLBACK (image_render_button_release), w); - - GtkEventController* motion_controller = gtk_event_controller_motion_new (); - gtk_widget_add_controller (GTK_WIDGET (w), GTK_EVENT_CONTROLLER (motion_controller)); - g_signal_connect (motion_controller, "motion", G_CALLBACK (image_render_motion_notify), w); - - GtkEventController *key_controller = gtk_event_controller_key_new (); - gtk_widget_add_controller (GTK_WIDGET (w), GTK_EVENT_CONTROLLER (key_controller)); - g_signal_connect (key_controller, "key-pressed", G_CALLBACK (image_render_key_press), w); -} - - -static void image_render_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) -{ - auto priv = static_cast(image_render_get_instance_private (IMAGE_RENDER (obj))); - switch (prop_id) - { - case PROP_HADJUSTMENT: - g_value_set_object (value, priv->h_adjustment); - break; - case PROP_VADJUSTMENT: - g_value_set_object (value, priv->v_adjustment); - break; - case PROP_HSCROLL_POLICY: - g_value_set_enum (value, priv->hscroll_policy); - break; - case PROP_VSCROLL_POLICY: - g_value_set_enum (value, priv->vscroll_policy); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); - break; - } -} - - -static void image_render_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) -{ - ImageRender *w = IMAGE_RENDER (obj); - auto priv = static_cast(image_render_get_instance_private (w)); - switch (prop_id) - { - case PROP_HADJUSTMENT: - image_render_set_h_adjustment (w, GTK_ADJUSTMENT (g_value_get_object (value))); - break; - case PROP_VADJUSTMENT: - image_render_set_v_adjustment (w, GTK_ADJUSTMENT (g_value_get_object (value))); - break; - case PROP_HSCROLL_POLICY: - { - GtkScrollablePolicy policy = static_cast (g_value_get_enum (value)); - if (priv->hscroll_policy != policy) - { - priv->hscroll_policy = policy; - gtk_widget_queue_resize (GTK_WIDGET (w)); - g_object_notify_by_pspec (obj, pspec); - } - } - break; - case PROP_VSCROLL_POLICY: - { - GtkScrollablePolicy policy = static_cast (g_value_get_enum (value)); - if (priv->vscroll_policy != policy) - { - priv->vscroll_policy = policy; - gtk_widget_queue_resize (GTK_WIDGET (w)); - g_object_notify_by_pspec (obj, pspec); - } - } - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); - break; - } -} - - -static void image_render_finalize (GObject *object) -{ - ImageRender *w = IMAGE_RENDER (object); - auto priv = static_cast(image_render_get_instance_private (w)); - - /* there are TWO references to the ImageRender object: - one in the parent widget, the other in the loader thread. - - "Destroy" might be called twice (don't know why, yet) - if the viewer is closed (by the user) - before the loader thread finishes. - - If this is the case, we don't want to block while waiting for the loader thread (bad user responsiveness). - So if "destroy" is called while the loader thread is still running, we simply exit, know "destroy" will be called again - once the loader thread is done (because it calls "g_object_unref" on the ImageRender object). - */ - if (priv->pixbuf_loading_thread && g_atomic_int_get (&priv->orig_pixbuf_loaded)==0) - { - // Loader thread still running, so do nothing - } - else - { - image_render_free_pixbuf (w); - - g_clear_object (&priv->v_adjustment); - g_clear_object (&priv->h_adjustment); - } - - G_OBJECT_CLASS (image_render_parent_class)->finalize (object); -} - - -static void image_render_class_init (ImageRenderClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->set_property = image_render_set_property; - object_class->get_property = image_render_get_property; - object_class->finalize = image_render_finalize; - - widget_class->snapshot = image_render_draw; - widget_class->measure = image_render_measure; - widget_class->size_allocate = image_render_size_allocate; - - g_object_class_override_property (object_class, PROP_HADJUSTMENT, "hadjustment"); - g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment"); - g_object_class_override_property (object_class, PROP_HSCROLL_POLICY, "hscroll-policy"); - g_object_class_override_property (object_class, PROP_VSCROLL_POLICY, "vscroll-policy"); - - image_render_signals[IMAGE_STATUS_CHANGED] = - g_signal_new ("image-status-changed", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (ImageRenderClass, image_status_changed), - NULL, NULL, - g_cclosure_marshal_VOID__POINTER, - G_TYPE_NONE, - 1, G_TYPE_POINTER); -} - - -void image_render_notify_status_changed (ImageRender *w) -{ - g_return_if_fail (IS_IMAGE_RENDER (w)); - auto priv = static_cast(image_render_get_instance_private (w)); - - ImageRender::Status stat; - - memset(&stat, 0, sizeof(stat)); - - stat.best_fit = priv->best_fit; - stat.scale_factor = priv->scale_factor; - - if (priv->orig_pixbuf) - { - stat.image_width = gdk_pixbuf_get_width (priv->orig_pixbuf); - stat.image_height = gdk_pixbuf_get_height (priv->orig_pixbuf); - stat.bits_per_sample = gdk_pixbuf_get_bits_per_sample (priv->orig_pixbuf); - } - - g_signal_emit (w, image_render_signals[IMAGE_STATUS_CHANGED], 0, &stat); -} - - -static gboolean image_render_key_press (GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, gpointer user_data) -{ - g_return_val_if_fail (IS_IMAGE_RENDER (user_data), FALSE); - auto imageRender = IMAGE_RENDER (user_data); - auto priv = static_cast(image_render_get_instance_private (imageRender)); - - switch (keyval) - { - case GDK_KEY_Up: - gtk_adjustment_set_value (priv->v_adjustment, gtk_adjustment_get_value (priv->v_adjustment) - INC_VALUE); - return TRUE; - case GDK_KEY_Down: - gtk_adjustment_set_value (priv->v_adjustment, gtk_adjustment_get_value (priv->v_adjustment) + INC_VALUE); - return TRUE; - case GDK_KEY_Left: - gtk_adjustment_set_value (priv->h_adjustment, gtk_adjustment_get_value (priv->h_adjustment) - INC_VALUE); - return TRUE; - case GDK_KEY_Right: - gtk_adjustment_set_value (priv->h_adjustment, gtk_adjustment_get_value (priv->h_adjustment) + INC_VALUE); - return TRUE; - default: - return FALSE; - } -} - - -static void image_render_scroll(GtkEventControllerScroll *controller, double dx, double dy, gpointer user_data) -{ - g_return_if_fail (IS_IMAGE_RENDER (user_data)); - auto imageRender = IMAGE_RENDER (user_data); - auto priv = static_cast(image_render_get_instance_private (imageRender)); - - if (state_is_ctrl(get_modifiers_state())) - std::swap(dx, dy); - - gtk_adjustment_set_value (priv->h_adjustment, gtk_adjustment_get_value (priv->h_adjustment) + INC_VALUE * dx); - gtk_adjustment_set_value (priv->v_adjustment, gtk_adjustment_get_value (priv->v_adjustment) + INC_VALUE * dy); -} - - -static void image_render_realize (GtkWidget *widget, gpointer user_data) -{ - ImageRender *obj = IMAGE_RENDER (widget); - auto priv = static_cast(image_render_get_instance_private (obj)); - - if (!priv->scaled_pixbuf_loaded) - image_render_load_scaled_pixbuf (obj); -} - - -static void image_render_redraw (ImageRender *w) -{ - if (!gtk_widget_get_realized (GTK_WIDGET (w))) - return; - - image_render_notify_status_changed (w); - gtk_widget_queue_draw (GTK_WIDGET (w)); -} - - -static void image_render_measure (GtkWidget* widget, GtkOrientation orientation, int for_size, int* minimum, int* natural, int* minimum_baseline, int* natural_baseline) -{ - if (orientation == GTK_ORIENTATION_HORIZONTAL) - *minimum = *natural = IMAGE_RENDER_DEFAULT_WIDTH; - else - *minimum = *natural = IMAGE_RENDER_DEFAULT_HEIGHT; -} - - -static void image_render_size_allocate (GtkWidget *widget, int width, int height, int baseline) -{ - g_return_if_fail (IS_IMAGE_RENDER (widget)); - image_render_prepare_disp_pixbuf (IMAGE_RENDER (widget)); -} - - -static void image_render_draw (GtkWidget* widget, GtkSnapshot* snapshot) -{ - ImageRender *w = IMAGE_RENDER (widget); - auto priv = static_cast(image_render_get_instance_private (w)); - - GtkAllocation widget_allocation; - gtk_widget_get_allocation (widget, &widget_allocation); - - if (!priv->disp_pixbuf) - return; - - graphene_rect_t rect = { { 0, 0 }, { (float) widget_allocation.width, (float) widget_allocation.height } }; - cairo_t *cr = gtk_snapshot_append_cairo (snapshot, &rect); - - gint xc, yc; - - if (priv->best_fit || - (gdk_pixbuf_get_width (priv->disp_pixbuf) < widget_allocation.width && - gdk_pixbuf_get_height (priv->disp_pixbuf) < widget_allocation.height)) - { - xc = widget_allocation.width / 2 - gdk_pixbuf_get_width (priv->disp_pixbuf)/2; - yc = widget_allocation.height / 2 - gdk_pixbuf_get_height (priv->disp_pixbuf)/2; - - cairo_translate (cr, xc, yc); - gdk_cairo_set_source_pixbuf (cr, priv->disp_pixbuf, 0.0, 0.0); - cairo_paint (cr); - } - else - { - gint src_x, src_y; - gint dst_x, dst_y; - gint width, height; - - if (widget_allocation.width > gdk_pixbuf_get_width (priv->disp_pixbuf)) - { - src_x = 0; - dst_x = widget_allocation.width / 2 - gdk_pixbuf_get_width (priv->disp_pixbuf)/2; - width = gdk_pixbuf_get_width (priv->disp_pixbuf); - } - else - { - src_x = (int) gtk_adjustment_get_value (priv->h_adjustment); - dst_x = 0; - width = MIN(widget_allocation.width, gdk_pixbuf_get_width (priv->disp_pixbuf)); - if (src_x + width > gdk_pixbuf_get_width (priv->disp_pixbuf)) - src_x = gdk_pixbuf_get_width (priv->disp_pixbuf) - width; - } - - - if ((int) gtk_adjustment_get_value (priv->h_adjustment) > gdk_pixbuf_get_height (priv->disp_pixbuf)) - { - src_y = 0; - dst_y = widget_allocation.height / 2 - gdk_pixbuf_get_height (priv->disp_pixbuf)/2; - height = gdk_pixbuf_get_height (priv->disp_pixbuf); - } - else - { - src_y = (int) gtk_adjustment_get_value (priv->v_adjustment); - dst_y = 0; - height = MIN(widget_allocation.height, gdk_pixbuf_get_height (priv->disp_pixbuf)); - - if (src_y + height > gdk_pixbuf_get_height (priv->disp_pixbuf)) - src_y = gdk_pixbuf_get_height (priv->disp_pixbuf) - height; - } - -#if 0 - fprintf(stderr, "src(%d, %d), dst(%d, %d), size(%d, %d) origsize(%d, %d) alloc(%d, %d) ajd(%d, %d)\n", - src_x, src_y, - dst_x, dst_y, - width, height, - gdk_pixbuf_get_width (w->priv->disp_pixbuf), - gdk_pixbuf_get_height (w->priv->disp_pixbuf), - widget_allocation.width, - widget_allocation.height, - (int)w->priv->h_adjustment->value, - (int)w->priv->v_adjustment->value); -#endif - cairo_translate (cr, -src_x, -src_y); - gdk_cairo_set_source_pixbuf (cr, priv->disp_pixbuf, dst_x, dst_y); - cairo_paint (cr); - } - - cairo_destroy (cr); -} - - -static void image_render_button_press (GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) -{ - g_return_if_fail (IS_IMAGE_RENDER (user_data)); - ImageRender *w = IMAGE_RENDER (user_data); - auto priv = static_cast(image_render_get_instance_private (w)); - - if (n_press == 1 && !priv->button) - { - priv->button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); - priv->mouseX = x; - priv->mouseY = y; - } -} - - -static void image_render_button_release (GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) -{ - g_return_if_fail (IS_IMAGE_RENDER (user_data)); - ImageRender *w = IMAGE_RENDER (user_data); - auto priv = static_cast(image_render_get_instance_private (w)); - - auto button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); - - if (priv->button == button) - priv->button = 0; -} - - -static void image_render_motion_notify (GtkEventControllerMotion *controller, double x, double y, gpointer user_data) -{ - g_return_if_fail (IS_IMAGE_RENDER (user_data)); - auto imageRender = IMAGE_RENDER (user_data); - auto priv = static_cast(image_render_get_instance_private (imageRender)); - - if (priv->button != 0) - { - gtk_adjustment_set_value (priv->h_adjustment, gtk_adjustment_get_value (priv->h_adjustment) + (priv->mouseX - x)); - gtk_adjustment_set_value (priv->v_adjustment, gtk_adjustment_get_value (priv->v_adjustment) + (priv->mouseY - y)); - - priv->mouseX = x; - priv->mouseY = y; - } -} - - -static void image_render_h_adjustment_update (ImageRender *obj) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - gfloat new_value = gtk_adjustment_get_value (priv->h_adjustment); - - if (new_value < gtk_adjustment_get_lower (priv->h_adjustment)) - new_value = gtk_adjustment_get_lower (priv->h_adjustment); - - if (new_value > gtk_adjustment_get_upper (priv->h_adjustment)) - new_value = gtk_adjustment_get_upper (priv->h_adjustment); - - if (new_value != gtk_adjustment_get_value (priv->h_adjustment)) - { - gtk_adjustment_set_value (priv->h_adjustment, new_value); - g_signal_emit_by_name (priv->h_adjustment, "value-changed"); - } - - /* TODO: Update the widget in response to the adjusments' change - Note: The change can be in 'value' or in 'lower'/'upper's */ - - image_render_redraw(obj); -} - - -static void image_render_h_adjustment_changed (GtkAdjustment *adjustment, gpointer data) -{ - g_return_if_fail (adjustment != NULL); - g_return_if_fail (data != NULL); - - ImageRender *obj = IMAGE_RENDER (data); - - image_render_h_adjustment_update (obj); -} - - -static void image_render_h_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data) -{ - g_return_if_fail (adjustment != NULL); - g_return_if_fail (data != NULL); - - ImageRender *obj = IMAGE_RENDER (data); - - image_render_h_adjustment_update (obj); -} - - -static void image_render_v_adjustment_update (ImageRender *obj) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - gfloat new_value = gtk_adjustment_get_value (priv->v_adjustment); - - if (new_value < gtk_adjustment_get_lower (priv->v_adjustment)) - new_value = gtk_adjustment_get_lower (priv->v_adjustment); - - if (new_value > gtk_adjustment_get_upper (priv->v_adjustment)) - new_value = gtk_adjustment_get_upper (priv->v_adjustment); - - if (new_value != gtk_adjustment_get_value (priv->v_adjustment)) - { - gtk_adjustment_set_value (priv->v_adjustment, new_value); - g_signal_emit_by_name (priv->v_adjustment, "value-changed"); - } - - /* TODO: Update the widget in response to the adjusments' change - Note: The change can be in 'value' or in 'lower'/'upper's */ - - image_render_redraw(obj); -} - - -static void image_render_v_adjustment_changed (GtkAdjustment *adjustment, gpointer data) -{ - g_return_if_fail (adjustment != NULL); - g_return_if_fail (data != NULL); - - ImageRender *obj = IMAGE_RENDER (data); - - image_render_v_adjustment_update (obj); -} - - -static void image_render_v_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data) -{ - g_return_if_fail (adjustment != NULL); - g_return_if_fail (data != NULL); - - ImageRender *obj = IMAGE_RENDER (data); - - image_render_v_adjustment_update (obj); -} - - -static void image_render_free_pixbuf (ImageRender *obj) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - image_render_wait_for_loader_thread (obj); - - priv->orig_pixbuf_loaded = 0; - priv->scaled_pixbuf_loaded = FALSE; - g_clear_object (&priv->orig_pixbuf); - g_clear_object (&priv->disp_pixbuf); - g_clear_pointer (&priv->filename, g_free); -} - - -static gpointer image_render_pixbuf_loading_thread (ImageRender *obj) -{ - auto priv = static_cast(image_render_get_instance_private (obj)); - - GError *err = NULL; - - priv->orig_pixbuf = gdk_pixbuf_new_from_file (priv->filename, &err); - - g_atomic_int_inc (&priv->orig_pixbuf_loaded); - - g_object_unref (obj); - - return NULL; -} - - -inline void image_render_wait_for_loader_thread (ImageRender *obj) -{ - g_return_if_fail (IS_IMAGE_RENDER (obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - if (priv->pixbuf_loading_thread) - { - /* - ugly hack: use a busy wait loop, until the loader thread is done. - - rational: the loader thread might be running after the widget is destroyed (if the user closed the viewer very quickly, - and the loader is still reading a very large image). If this happens, this (and all the 'destroy' functions) - will be called from the loader thread's context, and using g_thread_join() will crash the application. - */ - - while (g_atomic_int_get (&priv->orig_pixbuf_loaded)==0) - g_usleep(1000); - - priv->orig_pixbuf_loaded = 0; - priv->pixbuf_loading_thread = NULL; - } -} - - -void image_render_load_scaled_pixbuf (ImageRender *obj) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - g_return_if_fail (priv->filename!=NULL); - g_return_if_fail (priv->scaled_pixbuf_loaded==FALSE); - - g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (obj))); - - int width = gtk_widget_get_width (GTK_WIDGET (obj)); - int height = gtk_widget_get_height (GTK_WIDGET (obj)); - - if (width <= 0 || height <= 0) - return; - - GError *err = NULL; - - priv->disp_pixbuf = gdk_pixbuf_new_from_file_at_scale (priv->filename, width, height, TRUE, &err); - - if (err) - { - g_warning ("pixbuf loading failed: %s", err->message); - g_error_free (err); - priv->orig_pixbuf = NULL; - priv->disp_pixbuf = NULL; - return; - } - - priv->scaled_pixbuf_loaded = TRUE; -} - - -inline void image_render_start_background_pixbuf_loading (ImageRender *obj) -{ - g_return_if_fail (IS_IMAGE_RENDER (obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - g_return_if_fail (priv->filename!=NULL); - - if (priv->pixbuf_loading_thread!=NULL) - return; - - priv->orig_pixbuf_loaded = 0; - - // Start background loading - g_object_ref (obj); - priv->pixbuf_loading_thread = g_thread_new("pixbuf_load", (GThreadFunc) image_render_pixbuf_loading_thread, (gpointer) obj); -} - - -void image_render_load_file (ImageRender *obj, const gchar *filename) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - image_render_free_pixbuf (obj); - - g_return_if_fail (priv->filename==NULL); - - priv->filename = g_strdup (filename); - priv->scaled_pixbuf_loaded = FALSE; - priv->orig_pixbuf_loaded = 0; - image_render_start_background_pixbuf_loading (obj); -} - - -static void image_render_prepare_disp_pixbuf (ImageRender *obj) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - // this will be set only if the loader thread finished loading the big pixbuf - if (g_atomic_int_get (&priv->orig_pixbuf_loaded)==0) - return; - - if (!gtk_widget_get_realized (GTK_WIDGET (obj))) - return; - - g_clear_object (&priv->disp_pixbuf); - - if (gdk_pixbuf_get_height (priv->orig_pixbuf)==0) - return; - - if (priv->best_fit) - { - GtkAllocation obj_allocation; - gtk_widget_get_allocation (GTK_WIDGET (obj), &obj_allocation); - - if (gdk_pixbuf_get_height (priv->orig_pixbuf) < obj_allocation.height && - gdk_pixbuf_get_width (priv->orig_pixbuf) < obj_allocation.width) - { - // no need to scale down - - priv->disp_pixbuf = priv->orig_pixbuf; - g_object_ref (priv->disp_pixbuf); - return; - } - - int height = obj_allocation.height; - int width = (((double) obj_allocation.height) / - gdk_pixbuf_get_height (priv->orig_pixbuf))* - gdk_pixbuf_get_width (priv->orig_pixbuf); - - if (width >= obj_allocation.width) - { - width = obj_allocation.width; - height = (((double)obj_allocation.width) / - gdk_pixbuf_get_width (priv->orig_pixbuf))* - gdk_pixbuf_get_height (priv->orig_pixbuf); - } - - if (width<=1 || height<=1) - { - priv->disp_pixbuf = NULL; - return; - } - - priv->disp_pixbuf = gdk_pixbuf_scale_simple (priv->orig_pixbuf, width, height, GDK_INTERP_NEAREST); - } - else - { - // not "best_fit" = scaling mode - priv->disp_pixbuf = gdk_pixbuf_scale_simple( - priv->orig_pixbuf, - (int)(gdk_pixbuf_get_width (priv->orig_pixbuf) * priv->scale_factor), - (int)(gdk_pixbuf_get_height (priv->orig_pixbuf) * priv->scale_factor), - GDK_INTERP_NEAREST); - } - - image_render_update_adjustments (obj); -} - - -static void image_render_update_adjustments (ImageRender *obj) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - if (!priv->disp_pixbuf) - return; - - GtkAllocation obj_allocation; - gtk_widget_get_allocation (GTK_WIDGET (obj), &obj_allocation); - - if (priv->best_fit || - (gdk_pixbuf_get_width (priv->disp_pixbuf) < obj_allocation.width && - gdk_pixbuf_get_height (priv->disp_pixbuf) < obj_allocation.height)) - { - if (priv->h_adjustment) - { - gtk_adjustment_set_lower (priv->h_adjustment, 0); - gtk_adjustment_set_upper (priv->h_adjustment, 0); - gtk_adjustment_set_value (priv->h_adjustment, 0); - } - if (priv->v_adjustment) - { - gtk_adjustment_set_lower (priv->v_adjustment, 0); - gtk_adjustment_set_upper (priv->v_adjustment, 0); - gtk_adjustment_set_value (priv->v_adjustment, 0); - } - } - else - { - if (priv->h_adjustment) - { - gtk_adjustment_set_lower (priv->h_adjustment, 0); - gtk_adjustment_set_upper (priv->h_adjustment, gdk_pixbuf_get_width (priv->disp_pixbuf)); - gtk_adjustment_set_page_size (priv->h_adjustment, obj_allocation.width); - } - if (priv->v_adjustment) - { - gtk_adjustment_set_lower (priv->v_adjustment, 0); - gtk_adjustment_set_upper (priv->v_adjustment, gdk_pixbuf_get_height (priv->disp_pixbuf)); - gtk_adjustment_set_page_size (priv->v_adjustment, obj_allocation.height); - } - } -} - - -void image_render_set_best_fit(ImageRender *obj, gboolean active) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - priv->best_fit = active; - image_render_prepare_disp_pixbuf (obj); - image_render_redraw (obj); -} - - -gboolean image_render_get_best_fit(ImageRender *obj) -{ - g_return_val_if_fail (IS_IMAGE_RENDER(obj), FALSE); - auto priv = static_cast(image_render_get_instance_private (obj)); - - return priv->best_fit; -} - - -void image_render_set_scale_factor(ImageRender *obj, double scalefactor) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - - priv->scale_factor = scalefactor; - image_render_prepare_disp_pixbuf (obj); - image_render_redraw(obj); -} - - -double image_render_get_scale_factor(ImageRender *obj) -{ - g_return_val_if_fail (IS_IMAGE_RENDER(obj), 1); - auto priv = static_cast(image_render_get_instance_private (obj)); - - return priv->scale_factor; -} - - -void image_render_operation(ImageRender *obj, ImageRender::DISPLAYMODE op) -{ - g_return_if_fail (IS_IMAGE_RENDER(obj)); - auto priv = static_cast(image_render_get_instance_private (obj)); - g_return_if_fail (priv->orig_pixbuf); - - GdkPixbuf *temp = NULL; - - switch (op) - { - case ImageRender::ROTATE_CLOCKWISE: - temp = gdk_pixbuf_rotate_simple (priv->orig_pixbuf, GDK_PIXBUF_ROTATE_CLOCKWISE); - break; - case ImageRender::ROTATE_COUNTERCLOCKWISE: - temp = gdk_pixbuf_rotate_simple (priv->orig_pixbuf, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE); - break; - case ImageRender::ROTATE_UPSIDEDOWN: - temp = gdk_pixbuf_rotate_simple (priv->orig_pixbuf, GDK_PIXBUF_ROTATE_UPSIDEDOWN); - break; - case ImageRender::FLIP_VERTICAL: - temp = gdk_pixbuf_flip (priv->orig_pixbuf, FALSE); - break; - case ImageRender::FLIP_HORIZONTAL: - temp = gdk_pixbuf_flip (priv->orig_pixbuf, TRUE); - break; - default: - g_return_if_fail (!"Unknown image operation"); - } - - g_object_unref (priv->orig_pixbuf); - - priv->orig_pixbuf = temp; - - image_render_prepare_disp_pixbuf (obj); -} - -GdkPixbuf *image_render_get_origin_pixbuf (ImageRender *obj) -{ - g_return_val_if_fail (IS_IMAGE_RENDER(obj), nullptr); - auto priv = static_cast(image_render_get_instance_private (obj)); - - return priv->orig_pixbuf; -} diff --git a/src/intviewer/image-render.h b/src/intviewer/image-render.h deleted file mode 100644 index ed69b07c5eefe871eda1fb4395de049275087220..0000000000000000000000000000000000000000 --- a/src/intviewer/image-render.h +++ /dev/null @@ -1,78 +0,0 @@ -/** - * @file image-render.h - * @brief Part of GNOME Commander - A GNOME based file manager - * - * @copyright (C) 2006 Assaf Gordon\n - * @copyright (C) 2007-2012 Piotr Eljasiak\n - * @copyright (C) 2013-2024 Uwe Scholz\n - * - * @copyright This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * @copyright This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * @copyright You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#pragma once - -#define TYPE_IMAGE_RENDER (image_render_get_type ()) -#define IMAGE_RENDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_IMAGE_RENDER, ImageRender)) -#define IMAGE_RENDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_IMAGE_RENDER, ImageRenderClass)) -#define IS_IMAGE_RENDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_IMAGE_RENDER)) -#define IS_IMAGE_RENDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_IMAGE_RENDER)) -#define IMAGE_RENDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_IMAGE_RENDER, ImageRenderClass)) - - -extern "C" GType image_render_get_type (); - - -struct ImageRender -{ - GtkWidget parent; - - struct Status - { - gboolean best_fit; - gdouble scale_factor; - gint image_width; - gint image_height; - gint bits_per_sample; - }; - - enum DISPLAYMODE - { - ROTATE_CLOCKWISE, - ROTATE_COUNTERCLOCKWISE, - ROTATE_UPSIDEDOWN, - FLIP_VERTICAL, - FLIP_HORIZONTAL - }; -}; - -inline GtkWidget *image_render_new () -{ - return (GtkWidget *) g_object_new (TYPE_IMAGE_RENDER, NULL); -} - -extern "C" void image_render_load_file (ImageRender *obj, const gchar *filename); - -extern "C" void image_render_notify_status_changed (ImageRender *w); - -extern "C" void image_render_set_best_fit (ImageRender *obj, gboolean active); -extern "C" gboolean image_render_get_best_fit (ImageRender *obj); - -extern "C" void image_render_set_scale_factor (ImageRender *obj, double scalefactor); -extern "C" double image_render_get_scale_factor (ImageRender *obj); - -extern "C" void image_render_operation (ImageRender *obj, ImageRender::DISPLAYMODE op); - -extern "C" GdkPixbuf *image_render_get_origin_pixbuf (ImageRender *obj); diff --git a/src/intviewer/image_render.rs b/src/intviewer/image_render.rs index 7ccb3138261188da97f933238971dc84fb51c67d..74927c0024d060849b21ec1decf107333d025100 100644 --- a/src/intviewer/image_render.rs +++ b/src/intviewer/image_render.rs @@ -17,120 +17,587 @@ * For more details see the file COPYING. */ -use gtk::{ - gdk_pixbuf, - glib::{ - ffi::gboolean, - object::ObjectType, - translate::{from_glib_borrow, from_glib_none, ToGlibPtr}, - }, -}; +use gtk::{gdk, gdk_pixbuf, glib, graphene, prelude::*, subclass::prelude::*}; use std::path::Path; -pub mod ffi { +const IMAGE_RENDER_DEFAULT_WIDTH: i32 = 100; +const IMAGE_RENDER_DEFAULT_HEIGHT: i32 = 200; +const INC_VALUE: f64 = 25.0; + +pub mod imp { use super::*; - use gtk::{gdk_pixbuf::ffi::GdkPixbuf, glib::ffi::GType}; - use std::ffi::c_char; + use crate::utils::get_modifiers_state; + use std::{ + cell::{Cell, RefCell}, + sync::OnceLock, + }; + + pub struct DragState { + /// The button pressed to drag an image + pub button: u32, + /// Old x position before mouse move + pub x: f64, + /// Old y position before mouse move + pub y: f64, + } - #[repr(C)] + #[derive(glib::Properties)] + #[properties(wrapper_type = super::ImageRender)] pub struct ImageRender { - _data: [u8; 0], - _marker: std::marker::PhantomData<(*mut u8, std::marker::PhantomPinned)>, + pub original_pixbuf: RefCell>, + pub display_pixbuf: RefCell>, + pub drag_state: RefCell>, + + #[property(get, set = Self::set_hadjustment, override_interface = gtk::Scrollable)] + pub hadjustment: RefCell>, + pub hadjustment_handler_ids: RefCell>, + + #[property(get, set = Self::set_vadjustment, override_interface = gtk::Scrollable)] + pub vadjustment: RefCell>, + pub vadjustment_handler_ids: RefCell>, + + #[property(get, set = Self::set_hscroll_policy, override_interface = gtk::Scrollable)] + pub hscroll_policy: Cell, + #[property(get, set = Self::set_vscroll_policy, override_interface = gtk::Scrollable)] + pub vscroll_policy: Cell, + + #[property(get, set = Self::set_best_fit)] + pub best_fit: Cell, + #[property(get, set = Self::set_scale_factor)] + pub scale_factor: Cell, } - extern "C" { - pub fn image_render_get_type() -> GType; + #[glib::object_subclass] + impl ObjectSubclass for ImageRender { + const NAME: &'static str = "GnomeCmdImageRender"; + type Type = super::ImageRender; + type ParentType = gtk::Widget; + type Interfaces = (gtk::Scrollable,); - pub fn image_render_get_origin_pixbuf(r: *mut ImageRender) -> *mut GdkPixbuf; + fn new() -> Self { + Self { + original_pixbuf: Default::default(), + display_pixbuf: Default::default(), + drag_state: Default::default(), + + hadjustment: Default::default(), + hadjustment_handler_ids: Default::default(), + + vadjustment: Default::default(), + vadjustment_handler_ids: Default::default(), + + hscroll_policy: Cell::new(gtk::ScrollablePolicy::Minimum), + vscroll_policy: Cell::new(gtk::ScrollablePolicy::Minimum), + + best_fit: Default::default(), + scale_factor: Cell::new(1.3), + } + } + } - pub fn image_render_get_best_fit(r: *mut ImageRender) -> gboolean; - pub fn image_render_set_best_fit(r: *mut ImageRender, best_fit: gboolean); + #[glib::derived_properties] + impl ObjectImpl for ImageRender { + fn constructed(&self) { + self.parent_constructed(); - pub fn image_render_set_scale_factor(r: *mut ImageRender, factor: f64); - pub fn image_render_get_scale_factor(r: *mut ImageRender) -> f64; + let widget = self.obj(); - pub fn image_render_operation(r: *mut ImageRender, op: i32); + widget.set_can_focus(true); - pub fn image_render_load_file(r: *mut ImageRender, filename: *const c_char); + let scroll_controller = + gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::BOTH_AXES); + scroll_controller.connect_scroll(glib::clone!( + #[weak(rename_to = imp)] + self, + #[upgrade_or] + glib::Propagation::Proceed, + move |_, dx, dy| { + imp.scroll(dx, dy); + glib::Propagation::Proceed + } + )); + widget.add_controller(scroll_controller); - pub fn image_render_notify_status_changed(r: *mut ImageRender); + let button_gesture = gtk::GestureClick::new(); + button_gesture.connect_pressed(glib::clone!( + #[weak(rename_to = imp)] + self, + move |g, n_press, x, y| imp.button_press(n_press, x, y, g.current_button()) + )); + button_gesture.connect_released(glib::clone!( + #[weak(rename_to = imp)] + self, + move |g, _, _, _| imp.button_release(g.current_button()) + )); + widget.add_controller(button_gesture); + + let motion_controller = gtk::EventControllerMotion::new(); + motion_controller.connect_motion(glib::clone!( + #[weak(rename_to = imp)] + self, + move |_, x, y| imp.motion(x, y) + )); + widget.add_controller(motion_controller); + + let key_controller = gtk::EventControllerKey::new(); + key_controller.connect_key_pressed(glib::clone!( + #[weak(rename_to = imp)] + self, + #[upgrade_or] + glib::Propagation::Proceed, + move |_, key, _, _| imp.key_press(key) + )); + widget.add_controller(key_controller); + } + + fn signals() -> &'static [glib::subclass::Signal] { + static SIGNALS: OnceLock> = OnceLock::new(); + SIGNALS.get_or_init(|| { + vec![glib::subclass::Signal::builder("image-status-changed").build()] + }) + } } - #[derive(Copy, Clone)] - #[repr(C)] - pub struct ImageRenderClass { - pub parent_class: gtk::ffi::GtkWidgetClass, + impl WidgetImpl for ImageRender { + fn measure(&self, orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) { + if orientation == gtk::Orientation::Horizontal { + ( + IMAGE_RENDER_DEFAULT_WIDTH, + IMAGE_RENDER_DEFAULT_WIDTH, + -1, + -1, + ) + } else { + ( + IMAGE_RENDER_DEFAULT_HEIGHT, + IMAGE_RENDER_DEFAULT_HEIGHT, + -1, + -1, + ) + } + } + + fn size_allocate(&self, _width: i32, _height: i32, _baseline: i32) { + self.prepare_display_pixbuf(); + } + + fn snapshot(&self, snapshot: >k::Snapshot) { + let Some(disp_pixbuf) = self.display_pixbuf.borrow().clone() else { + return; + }; + + let width = self.obj().width(); + let height = self.obj().height(); + let disp_pixbuf_width = disp_pixbuf.width(); + let disp_pixbuf_height = disp_pixbuf.height(); + + let dw = (width - disp_pixbuf_width) as f64; + let dh = (height - disp_pixbuf_height) as f64; + + let rect = graphene::Rect::new(0_f32, 0_f32, width as f32, height as f32); + let cr = snapshot.append_cairo(&rect); + + let paint_result = if self.best_fit.get() || (dw >= 0.0 && dh >= 0.0) { + cr.translate(dw / 2.0, dh / 2.0); + cr.set_source_pixbuf(&disp_pixbuf, 0.0, 0.0); + cr.paint() + } else { + let src_x; + let src_y; + let dst_x; + let dst_y; + + if dw >= 0.0 { + src_x = 0.0; + dst_x = dw / 2.0; + } else { + src_x = self + .obj() + .hadjustment() + .map(|a| a.value().clamp(0.0, -dw)) + .unwrap_or_default(); + dst_x = 0.0; + } + + if dh >= 0.0 { + src_y = 0.0; + dst_y = dh / 2.0; + } else { + src_y = self + .obj() + .vadjustment() + .map(|a| a.value().clamp(0.0, -dh)) + .unwrap_or_default(); + dst_y = 0.0; + } + + cr.translate(-src_x, -src_y); + cr.set_source_pixbuf(&disp_pixbuf, dst_x, dst_y); + cr.paint() + }; + + if let Err(error) = paint_result { + eprintln!("Cairo paint failure: {error}"); + } + } } -} -glib::wrapper! { - pub struct ImageRender(Object) - @extends gtk::Widget; + impl ScrollableImpl for ImageRender {} + + impl ImageRender { + fn set_hadjustment(&self, adjustment: Option) { + if let Some(adjustment) = self.obj().hadjustment() { + for handler_id in self.hadjustment_handler_ids.take() { + adjustment.disconnect(handler_id); + } + } + + let adjustment = adjustment.unwrap_or_default(); + self.hadjustment.replace(Some(adjustment.clone())); - match fn { - type_ => || ffi::image_render_get_type(), + adjustment.set_lower(0.0); + adjustment.set_upper( + self.display_pixbuf + .borrow() + .as_ref() + .map(|p| p.width()) + .unwrap_or_default() as f64, + ); + adjustment.set_step_increment(INC_VALUE); + adjustment.set_page_increment(100.0); + adjustment.set_page_size(100.0); + + self.hadjustment_handler_ids + .borrow_mut() + .push(adjustment.connect_changed(glib::clone!( + #[weak(rename_to = imp)] + self, + move |_| imp.redraw() + ))); + self.hadjustment_handler_ids + .borrow_mut() + .push(adjustment.connect_value_changed(glib::clone!( + #[weak(rename_to = imp)] + self, + move |_| imp.redraw() + ))); + + self.redraw(); + } + + fn set_vadjustment(&self, adjustment: Option) { + if let Some(adjustment) = self.obj().vadjustment() { + for handler_id in self.vadjustment_handler_ids.take() { + adjustment.disconnect(handler_id); + } + } + + let adjustment = adjustment.unwrap_or_default(); + self.vadjustment.replace(Some(adjustment.clone())); + + adjustment.set_lower(0.0); + adjustment.set_upper( + self.display_pixbuf + .borrow() + .as_ref() + .map(|p| p.height()) + .unwrap_or_default() as f64, + ); + adjustment.set_step_increment(INC_VALUE); + adjustment.set_page_increment(100.0); + adjustment.set_page_size(100.0); + + self.vadjustment_handler_ids + .borrow_mut() + .push(adjustment.connect_changed(glib::clone!( + #[weak(rename_to = imp)] + self, + move |_| imp.redraw() + ))); + self.vadjustment_handler_ids + .borrow_mut() + .push(adjustment.connect_value_changed(glib::clone!( + #[weak(rename_to = imp)] + self, + move |_| imp.redraw() + ))); + + self.redraw(); + } + + fn set_hscroll_policy(&self, policy: gtk::ScrollablePolicy) { + self.hscroll_policy.set(policy); + self.obj().queue_resize(); + } + + fn set_vscroll_policy(&self, policy: gtk::ScrollablePolicy) { + self.vscroll_policy.set(policy); + self.obj().queue_resize(); + } + + fn redraw(&self) { + self.obj().notify_status_changed(); + self.obj().queue_draw(); + } + + fn move_rel_x(&self, dx: f64) { + if let Some(hadjustment) = self.obj().hadjustment() { + hadjustment.set_value( + (hadjustment.value() + dx).clamp(hadjustment.lower(), hadjustment.upper()), + ); + } + } + + fn move_rel_y(&self, dy: f64) { + if let Some(vadjustment) = self.obj().vadjustment() { + vadjustment.set_value( + (vadjustment.value() + dy).clamp(vadjustment.lower(), vadjustment.upper()), + ); + } + } + + fn scroll(&self, mut dx: f64, mut dy: f64) { + let state = self + .obj() + .root() + .and_downcast_ref::() + .and_then(get_modifiers_state); + + if state == Some(gdk::ModifierType::CONTROL_MASK) { + std::mem::swap(&mut dx, &mut dy); + } + + self.move_rel_x(INC_VALUE * dx); + self.move_rel_y(INC_VALUE * dy); + } + + fn button_press(&self, n_press: i32, x: f64, y: f64, button: u32) { + if n_press == 1 && self.drag_state.borrow().is_none() { + self.drag_state.replace(Some(DragState { button, x, y })); + } + } + + fn button_release(&self, button: u32) { + if self + .drag_state + .borrow() + .as_ref() + .map(|s| s.button == button) + .unwrap_or_default() + { + self.drag_state.replace(None); + } + } + + fn motion(&self, x: f64, y: f64) { + if let Some(state) = self.drag_state.borrow_mut().as_mut() { + self.move_rel_x(state.x - x); + self.move_rel_y(state.y - y); + state.x = x; + state.y = y; + } + } + + fn key_press(&self, key: gdk::Key) -> glib::Propagation { + match key { + gdk::Key::Up => { + self.move_rel_y(-INC_VALUE); + glib::Propagation::Stop + } + gdk::Key::Down => { + self.move_rel_y(INC_VALUE); + glib::Propagation::Stop + } + gdk::Key::Left => { + self.move_rel_x(-INC_VALUE); + glib::Propagation::Stop + } + gdk::Key::Right => { + self.move_rel_x(INC_VALUE); + glib::Propagation::Stop + } + _ => glib::Propagation::Proceed, + } + } + + fn set_best_fit(&self, best_fit: bool) { + self.best_fit.set(best_fit); + self.prepare_display_pixbuf(); + self.redraw(); + } + + fn set_scale_factor(&self, scale_factor: f64) { + self.scale_factor.set(scale_factor); + self.prepare_display_pixbuf(); + self.redraw(); + } + + pub(super) fn prepare_display_pixbuf(&self) { + self.display_pixbuf.replace(None); + let Some(pixbuf) = self.original_pixbuf.borrow().clone() else { + return; + }; + + let pixbuf_width = pixbuf.width(); + let pixbuf_height = pixbuf.height(); + + if pixbuf_width == 0 || pixbuf_height == 0 { + return; + } + + if self.best_fit.get() { + let width = self.obj().width(); + let height = self.obj().height(); + + if pixbuf_width < width && pixbuf_height < height { + // no need to scale down + self.display_pixbuf.replace(Some(pixbuf)); + return; + } + + let dest_width; + let dest_height; + if height * pixbuf_width >= width * pixbuf_height { + dest_width = width; + dest_height = width * pixbuf_height / pixbuf_width; + } else { + dest_width = height * pixbuf_width / pixbuf_height; + dest_height = height; + } + + if dest_width >= 1 && dest_height >= 1 { + self.display_pixbuf.replace(pixbuf.scale_simple( + dest_width, + dest_height, + gdk_pixbuf::InterpType::Nearest, + )); + } + } else { + let scale_factor = self.scale_factor.get(); + + self.display_pixbuf.replace(pixbuf.scale_simple( + (pixbuf_width as f64 * scale_factor) as i32, + (pixbuf_height as f64 * scale_factor) as i32, + gdk_pixbuf::InterpType::Nearest, + )); + } + + self.update_adjustments(); + } + + fn update_adjustments(&self) { + let Some(pixbuf) = self.display_pixbuf.borrow().clone() else { + return; + }; + + let obj_width = self.obj().width(); + let obj_height = self.obj().height(); + let pixbuf_width = pixbuf.width(); + let pixbuf_height = pixbuf.height(); + + if self.best_fit.get() || pixbuf_width <= obj_width && pixbuf_height <= obj_height { + if let Some(hadjustment) = self.obj().hadjustment() { + hadjustment.set_lower(0.0); + hadjustment.set_upper(0.0); + hadjustment.set_value(0.0); + } + if let Some(vadjustment) = self.obj().vadjustment() { + vadjustment.set_lower(0.0); + vadjustment.set_upper(0.0); + vadjustment.set_value(0.0); + } + } else { + if let Some(hadjustment) = self.obj().hadjustment() { + hadjustment.set_lower(0.0); + hadjustment.set_upper(pixbuf_width as f64); + hadjustment.set_page_size(obj_width as f64); + } + if let Some(vadjustment) = self.obj().vadjustment() { + vadjustment.set_lower(0.0); + vadjustment.set_upper(pixbuf_height as f64); + vadjustment.set_page_size(obj_height as f64); + } + } + } } } +glib::wrapper! { + pub struct ImageRender(ObjectSubclass) + @extends gtk::Widget, + @implements gtk::Scrollable; +} + impl ImageRender { pub fn new() -> Self { glib::Object::builder().build() } pub fn origin_pixbuf(&self) -> Option { - unsafe { from_glib_none(ffi::image_render_get_origin_pixbuf(self.to_glib_none().0)) } + self.imp().original_pixbuf.borrow().clone() } - pub fn best_fit(&self) -> bool { - unsafe { ffi::image_render_get_best_fit(self.to_glib_none().0) != 0 } - } - - pub fn set_best_fit(&self, best_fit: bool) { - unsafe { ffi::image_render_set_best_fit(self.to_glib_none().0, best_fit as gboolean) } - } + pub fn operation(&self, operation: ImageOperation) { + let Some(pixbuf) = self.imp().original_pixbuf.borrow_mut().clone() else { + return; + }; - pub fn scale_factor(&self) -> f64 { - unsafe { ffi::image_render_get_scale_factor(self.to_glib_none().0) } - } - - pub fn set_scale_factor(&self, factor: f64) { - unsafe { ffi::image_render_set_scale_factor(self.to_glib_none().0, factor) } - } + let new_pixbuf = match operation { + ImageOperation::RotateClockwise => { + pixbuf.rotate_simple(gdk_pixbuf::PixbufRotation::Clockwise) + } + ImageOperation::RotateCounterclockwise => { + pixbuf.rotate_simple(gdk_pixbuf::PixbufRotation::Counterclockwise) + } + ImageOperation::RotateUpsideDown => { + pixbuf.rotate_simple(gdk_pixbuf::PixbufRotation::Upsidedown) + } + ImageOperation::FlipVertical => pixbuf.flip(false), + ImageOperation::FlipHorizontal => pixbuf.flip(true), + }; - pub fn operation(&self, operation: i32) { - unsafe { ffi::image_render_operation(self.to_glib_none().0, operation) } + if let Some(new_pixbuf) = new_pixbuf { + self.imp().original_pixbuf.replace(Some(new_pixbuf)); + self.imp().prepare_display_pixbuf(); + } } pub fn load_file(&self, filename: &Path) { - unsafe { ffi::image_render_load_file(self.to_glib_none().0, filename.to_glib_none().0) } + self.imp().original_pixbuf.replace(None); + self.imp().display_pixbuf.replace(None); + + match gdk_pixbuf::Pixbuf::from_file(filename) { + Ok(pixbuf) => { + self.imp().original_pixbuf.replace(Some(pixbuf)); + } + Err(error) => { + eprintln!("Failed to load image: {error}"); + } + } } pub fn notify_status_changed(&self) { - unsafe { ffi::image_render_notify_status_changed(self.to_glib_none().0) } + self.emit_by_name::<()>("image-status-changed", &[]); } pub fn connect_image_status_changed( &self, f: F, ) -> glib::SignalHandlerId { - unsafe extern "C" fn pressed_trampoline( - this: *mut ffi::ImageRender, - _status: glib::ffi::gpointer, - f: glib::ffi::gpointer, - ) { - let f: &F = &*(f as *const F); - f(&from_glib_borrow(this)) - } - unsafe { - let f: Box = Box::new(f); - glib::signal::connect_raw( - self.as_ptr() as *mut _, - c"image-status-changed".as_ptr() as *const _, - Some(std::mem::transmute::<*const (), unsafe extern "C" fn()>( - pressed_trampoline:: as *const (), - )), - Box::into_raw(f), - ) - } + self.connect_closure( + "image-status-changed", + false, + glib::closure_local!(move |self_: &Self| (f)(self_)), + ) } } + +#[derive(Clone, Copy, strum::FromRepr)] +#[repr(i32)] +pub enum ImageOperation { + RotateClockwise, + RotateCounterclockwise, + RotateUpsideDown, + FlipVertical, + FlipHorizontal, +} diff --git a/src/intviewer/libgviewer.h b/src/intviewer/libgviewer.h index 287f175fb97d2492ec10cdc490008623fb71a771..f808e05cb902fe6206a82fdbb8e686978af0664a 100644 --- a/src/intviewer/libgviewer.h +++ b/src/intviewer/libgviewer.h @@ -31,6 +31,5 @@ #include "fileops.h" #include "inputmodes.h" #include "datapresentation.h" -#include "image-render.h" #include "text-render.h" #include "searcher.h" diff --git a/src/intviewer/meson.build b/src/intviewer/meson.build index caa8fa1e5ded2deef9930d4ae845d818bee1f56c..103fb68873ce53a9aa96ebef4d9be89198d6dd60 100644 --- a/src/intviewer/meson.build +++ b/src/intviewer/meson.build @@ -5,7 +5,6 @@ intviewer_headers = files( 'datapresentation.h', 'fileops.h', 'gvtypes.h', - 'image-render.h', 'inputmodes.h', 'libgviewer.h', 'searcher.h', @@ -19,7 +18,6 @@ intviewer_sources = files( 'cp437.cc', 'datapresentation.cc', 'fileops.cc', - 'image-render.cc', 'inputmodes.cc', 'searcher.cc', 'text-render.cc', diff --git a/src/intviewer/window.rs b/src/intviewer/window.rs index 60a018daf2ba23da5cd9e0c9a5dd7a7597772fad..0735bbf9ef2d8610dd473b0b389010b7108df0e9 100644 --- a/src/intviewer/window.rs +++ b/src/intviewer/window.rs @@ -60,7 +60,7 @@ mod imp { use crate::{ file_metainfo_view::FileMetainfoView, intviewer::{ - image_render::ImageRender, + image_render::{ImageOperation, ImageRender}, search_dialog::{SearchDialog, SearchRequest}, text_render::TextRenderDisplayMode, }, @@ -583,7 +583,10 @@ mod imp { } fn image_operation(&self, op: Option<&glib::Variant>) { - let Some(operation) = op.and_then(|v| v.get::()) else { + let Some(operation) = op + .and_then(|v| v.get::()) + .and_then(ImageOperation::from_repr) + else { return; }; self.image_render.operation(operation); diff --git a/tests/iv_imagerenderer_test.cc b/tests/iv_imagerenderer_test.cc deleted file mode 100644 index 43e6c8cc848357aa6a0c8564677ec6d2118591bb..0000000000000000000000000000000000000000 --- a/tests/iv_imagerenderer_test.cc +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @file iv_imagerenderer_test.cc - * @brief Part of GNOME Commander - A GNOME based file manager - * - * @details In this single test the creation of a gtk widget is tested - * in which two elements are visualized. It is not to be considered to - * be a unit test, as individual functions are not fully tested! - * - * @copyright (C) 2006 Assaf Gordon\n - * @copyright (C) 2007-2012 Piotr Eljasiak\n - * @copyright (C) 2013-2024 Uwe Scholz\n - * - * @copyright This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * @copyright This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * @copyright You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include -#include "gtest/gtest.h" - -class ImageRendererBestFitTest : public ::testing::TestWithParam {}; - -INSTANTIATE_TEST_CASE_P(InstantiationName, - ImageRendererBestFitTest, - ::testing::Bool()); - -TEST_P(ImageRendererBestFitTest, image_renderer_set_best_fit_test) { - GtkWidget *imgr; - imgr = image_render_new(); - image_render_set_best_fit(IMAGE_RENDER(imgr),GetParam()); - ASSERT_EQ(GetParam(), image_render_get_best_fit(IMAGE_RENDER(imgr))); -} - -//////////////////////////////////////////////////////////////////////// - -class ImageRendererScaleFactorTest : public ::testing::Test {}; - -TEST_F(ImageRendererScaleFactorTest, image_renderer_set_scale_factor_test) { - GtkWidget *imgr; - imgr = image_render_new(); - image_render_set_scale_factor(IMAGE_RENDER(imgr), 1); - ASSERT_EQ(1, image_render_get_scale_factor(IMAGE_RENDER(imgr))); -} diff --git a/tests/meson.build b/tests/meson.build index fe4e1f67034d8c6c1ea3d8ad54c5cf91170089a9..51bd11ac8b103e0a43c3565f587534a93c2c5fca 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,17 +1,6 @@ # *** Internal Viewer Tests *** Most of these only consist of serialised # function calls for acceptance tests, actually. Functions of the internal # viewer library are not fully tested by unit tests. -test('test_iv_viewerwidget', - executable('test-iv-imagerenderer', - ['iv_imagerenderer_test.cc', 'gcmd_tests_main.cc'], - dependencies: [common_deps, intviewer_dep, gtest], - include_directories: [configuration_inc, libgcmd_inc, sources_inc], - c_args: ['-DTEST_DIR="@0@"'.format(meson.current_source_dir())], - link_with: [intviewer_lib], - override_options : ['c_std=c14', 'cpp_std=c++14'], - ), -) - test('test_iv_inputmodes', executable('test-iv-inputmodes', ['iv_inputmodes_test.cc', 'gcmd_tests_main.cc'],