diff --git a/AUTHORS.txt b/AUTHORS.txt index b2d1333d2..9d1309026 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -15,6 +15,7 @@ Development contributors, in last name alphabetical order: Maciek Dworak Michael Ezra Flössie + Rüdiger Franke Jean-Christophe Frisch Ilias Giarimis Alberto Griggio diff --git a/rtgui/filecatalog.cc b/rtgui/filecatalog.cc index 42a2263e2..34d6c8aa3 100644 --- a/rtgui/filecatalog.cc +++ b/rtgui/filecatalog.cc @@ -42,6 +42,7 @@ #include "pathutils.h" #include "thumbnail.h" #include "toolbar.h" +#include "inspector.h" using namespace std; @@ -2508,6 +2509,15 @@ bool FileCatalog::handleShortcutKey (GdkEventKey* event) } } + if (!ctrl && !alt) { + switch (event->keyval) { + case GDK_KEY_f: + case GDK_KEY_F: + fileBrowser->getInspector()->showWindow(!shift); + return true; + } + } + return fileBrowser->keyPressed(event); } diff --git a/rtgui/filepanel.cc b/rtgui/filepanel.cc index 1a66aed7c..983a0840c 100644 --- a/rtgui/filepanel.cc +++ b/rtgui/filepanel.cc @@ -115,9 +115,9 @@ FilePanel::FilePanel () : parent(nullptr), error(0) Gtk::Label* devLab = Gtk::manage ( new Gtk::Label (M("MAIN_TAB_DEVELOP")) ); devLab->set_name ("LabelRightNotebook"); devLab->set_angle (90); - Gtk::Label* inspectLab = Gtk::manage ( new Gtk::Label (M("MAIN_TAB_INSPECT")) ); - inspectLab->set_name ("LabelRightNotebook"); - inspectLab->set_angle (90); + //Gtk::Label* inspectLab = Gtk::manage ( new Gtk::Label (M("MAIN_TAB_INSPECT")) ); + //inspectLab->set_name ("LabelRightNotebook"); + //inspectLab->set_angle (90); Gtk::Label* filtLab = Gtk::manage ( new Gtk::Label (M("MAIN_TAB_FILTER")) ); filtLab->set_name ("LabelRightNotebook"); filtLab->set_angle (90); @@ -132,7 +132,7 @@ FilePanel::FilePanel () : parent(nullptr), error(0) tpcPaned->pack2 (*history, true, false); rightNotebook->append_page (*sFilterPanel, *filtLab); - rightNotebook->append_page (*inspectorPanel, *inspectLab); + //rightNotebook->append_page (*inspectorPanel, *inspectLab); rightNotebook->append_page (*tpcPaned, *devLab); //rightNotebook->append_page (*taggingBox, *tagLab); commented out: currently the tab is empty ... rightNotebook->append_page (*sExportPanel, *exportLab); diff --git a/rtgui/inspector.cc b/rtgui/inspector.cc index 9002cc389..7d5d44e44 100644 --- a/rtgui/inspector.cc +++ b/rtgui/inspector.cc @@ -82,9 +82,25 @@ InspectorBuffer::~InspectorBuffer() { // return deg; //} -Inspector::Inspector () : currImage(nullptr), zoom(0.0), active(false) +Inspector::Inspector () : currImage(nullptr), scaled(false), scale(1.0), zoomScale(1.0), zoomScaleBegin(1.0), active(false), pinned(false), dirty(false) { set_name("Inspector"); + window.set_visible(false); + window.set_title("RawTherapee Inspector"); + + window.add_events(Gdk::KEY_PRESS_MASK); + window.signal_key_release_event().connect(sigc::mem_fun(*this, &Inspector::on_key_release)); + window.signal_key_press_event().connect(sigc::mem_fun(*this, &Inspector::on_key_press)); + + add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); + gestureZoom = Gtk::GestureZoom::create(*this); + gestureZoom->signal_begin().connect(sigc::mem_fun(*this, &Inspector::on_zoom_begin)); + gestureZoom->signal_scale_changed().connect(sigc::mem_fun(*this, &Inspector::on_zoom_scale_changed)); + + window.add(*this); + window.show_all(); + window.set_visible(false); + active = true; // always track inspected thumbnails } Inspector::~Inspector() @@ -92,8 +108,184 @@ Inspector::~Inspector() deleteBuffers(); } +void Inspector::showWindow(bool scaled) +{ + this->scaled = scaled; + window.fullscreen(); + window.set_visible(true); + pinned = false; +} + +bool Inspector::on_key_release(GdkEventKey *event) +{ + if (!pinned) { + switch (event->keyval) { + case GDK_KEY_f: + case GDK_KEY_F: + zoomScale = 1.0; + window.set_visible(false); + return true; + } + } + return false; +} + +bool Inspector::on_key_press(GdkEventKey *event) +{ + switch (event->keyval) { + case GDK_KEY_z: + case GDK_KEY_F: + if (pinned || scaled) + zoomScale = 1.0; // reset if not key hold + scaled = false; + queue_draw(); + return true; + case GDK_KEY_f: + if (pinned || !scaled) + zoomScale = 1.0; // reset if not key hold + scaled = true; + queue_draw(); + return true; + case GDK_KEY_Escape: + zoomScale = 1.0; + window.set_visible(false); + return true; + } + + return false; +} + +bool Inspector::on_button_press_event(GdkEventButton *event) +{ + if (event->type == GDK_BUTTON_PRESS) { + if (!pinned) + // pin window with mouse click + pinned = true; + return true; + } + return false; +} + +bool Inspector::on_scroll_event(GdkEventScroll *event) +{ + if (!currImage) + return false; + + bool alt = event->state & GDK_MOD1_MASK; + int deviceScale = get_scale_factor(); + int imW = currImage->imgBuffer.getWidth(); + int imH = currImage->imgBuffer.getHeight(); + +#ifdef GDK_WINDOWING_QUARTZ + // event reports speed of scroll wheel + double step_x = -event->delta_x; + double step_y = event->delta_y; +#else + // assume fixed step of 5% + double step_x = 5; + double step_y = 5; +#endif + int delta_x = 0; + int delta_y = 0; + switch (event->direction) { + case GDK_SCROLL_SMOOTH: +#ifdef GDK_WINDOWING_QUARTZ + // no additional step for smooth scrolling + delta_x = event->delta_x * deviceScale; + delta_y = event->delta_y * deviceScale; +#else + // apply step to smooth scrolling as well + delta_x = event->delta_x * deviceScale * step_x * imW / 100; + delta_y = event->delta_y * deviceScale * step_y * imH / 100; +#endif + break; + case GDK_SCROLL_DOWN: + delta_y = step_y * deviceScale * imH / 100; + break; + case GDK_SCROLL_UP: + delta_y = -step_y * deviceScale * imH / 100; + break; + case GDK_SCROLL_LEFT: + delta_x = step_x * deviceScale * imW / 100; + break; + case GDK_SCROLL_RIGHT: + delta_x = -step_x * deviceScale * imW / 100; + break; + } + + if (alt) { + // zoom + beginZoom(event->x, event->y); + if (std::fabs(delta_y) > std::fabs(delta_x)) + on_zoom_scale_changed(1.0 - (double)delta_y / imH / deviceScale); + else + on_zoom_scale_changed(1.0 - (double)delta_x / imW / deviceScale); + return true; + } + + // scroll + moveCenter(delta_x, delta_y, imW, imH, deviceScale); + + if (!dirty) { + dirty = true; + queue_draw(); + } + + return true; +} + +void Inspector::moveCenter(int delta_x, int delta_y, int imW, int imH, int deviceScale) +{ + rtengine::Coord margin; // limit to image size + margin.x = rtengine::min(window.get_width() * deviceScale / scale, imW) / 2; + margin.y = rtengine::min(window.get_height() * deviceScale / scale, imH) / 2; + center.set(rtengine::LIM(center.x + delta_x, margin.x, imW - margin.x), + rtengine::LIM(center.y + delta_y, margin.y, imH - margin.y)); +} + +void Inspector::beginZoom(double x, double y) +{ + int deviceScale = get_scale_factor(); + int imW = currImage->imgBuffer.getWidth(); + int imH = currImage->imgBuffer.getHeight(); + + // limit center to image size + moveCenter(0, 0, imW, imH, deviceScale); + + // store center and current position for zooming + dcenterBegin.x = (x - window.get_width()/2) / scale * deviceScale; + dcenterBegin.y = (y - window.get_height()/2) / scale * deviceScale; + centerBegin = center; + zoomScaleBegin = zoomScale; + +} + +void Inspector::on_zoom_begin(GdkEventSequence *s) +{ + double x, y; + if (gestureZoom->get_point(s, x, y)) + beginZoom(x, y); +} + +void Inspector::on_zoom_scale_changed(double zscale) +{ + if (!currImage) + return; + + zoomScale = rtengine::LIM(zoomScaleBegin * zscale, 0.01, 16.0); + double dcenterRatio = 1.0 - zoomScaleBegin / zoomScale; + center.x = centerBegin.x + dcenterBegin.x * dcenterRatio; + center.y = centerBegin.y + dcenterBegin.y * dcenterRatio; + + if (!dirty) { + dirty = true; + queue_draw(); + } +} + bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) { + dirty = false; Glib::RefPtr win = get_window(); @@ -116,10 +308,24 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) rtengine::Coord availableSize; rtengine::Coord topLeft; rtengine::Coord dest(0, 0); - availableSize.x = win->get_width(); - availableSize.y = win->get_height(); - int imW = currImage->imgBuffer.getWidth(); - int imH = currImage->imgBuffer.getHeight(); + int deviceScale = get_scale_factor(); + availableSize.x = win->get_width() * deviceScale; + availableSize.y = win->get_height() * deviceScale; + int imW = rtengine::max(currImage->imgBuffer.getWidth(), 1); + int imH = rtengine::max(currImage->imgBuffer.getHeight(), 1); + scale = rtengine::min((double)availableSize.x / imW, (double)availableSize.y / imH); + if (scaled) { + // reduce size of image to fit into window, no further zoom down + zoomScale = rtengine::max(zoomScale, 1.0); + scale *= zoomScale; + } + else { + // limit zoom to fill at least complete window or 1:1 + zoomScale = rtengine::max(zoomScale, rtengine::min(1.0, scale)); + scale = zoomScale; + } + availableSize.x /= scale; + availableSize.y /= scale; if (imW < availableSize.x) { // center the image in the available space along X @@ -146,7 +352,6 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) topLeft.y -= availableSize.y; topLeft.y = rtengine::max(topLeft.y, 0); } - //printf("center: %d, %d (img: %d, %d) (availableSize: %d, %d) (topLeft: %d, %d)\n", center.x, center.y, imW, imH, availableSize.x, availableSize.y, topLeft.x, topLeft.y); // define the destination area @@ -163,24 +368,50 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) Glib::RefPtr style = get_style_context(); // draw the background - style->render_background(cr, 0, 0, get_width(), get_height()); + //style->render_background(cr, 0, 0, get_width(), get_height()); - /* --- old method + ///* --- old method (the new method does not seem to work) c = style->get_background_color (Gtk::STATE_FLAG_NORMAL); cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue()); cr->set_line_width (0); cr->rectangle (0, 0, availableSize.x, availableSize.y); cr->fill (); - */ + //*/ - currImage->imgBuffer.copySurface(win); + bool scaledImage = scale != 1.0; + if (deviceScale == 1 && !scaledImage) { + // standard drawing + currImage->imgBuffer.copySurface(win); + } + else { + // consider device scale and image scale + if (deviceScale > 1) { + // use full device resolution and let it scale the image (macOS) + cairo_surface_set_device_scale(cr->get_target()->cobj(), scale, scale); + scaledImage = false; + } + int viewW = rtengine::min(imW, availableSize.x); + int viewH = rtengine::min(imH, availableSize.y); + Glib::RefPtr crop = Gdk::Pixbuf::create(currImage->imgBuffer.getSurface(), topLeft.x, topLeft.y, viewW, viewH); + if (!scaledImage) { + Gdk::Cairo::set_source_pixbuf(cr, crop, dest.x, dest.y); + } + else { + // scale crop as the device does not seem to support it (Linux) + crop = crop->scale_simple(viewW*scale, viewH*scale, Gdk::INTERP_BILINEAR); + Gdk::Cairo::set_source_pixbuf(cr, crop, dest.x*scale, dest.y*scale); + } + cr->paint(); + } + /* --- not for separate window // draw the frame c = style->get_border_color (Gtk::STATE_FLAG_NORMAL); cr->set_source_rgb (c.get_red(), c.get_green(), c.get_blue()); cr->set_line_width (1); cr->rectangle (0.5, 0.5, availableSize.x - 1, availableSize.y - 1); cr->stroke (); + */ } return true; @@ -309,7 +540,7 @@ void Inspector::setActive(bool state) flushBuffers(); } - active = state; + //active = state; } diff --git a/rtgui/inspector.h b/rtgui/inspector.h index 1526f90be..9a99fb8a6 100644 --- a/rtgui/inspector.h +++ b/rtgui/inspector.h @@ -47,12 +47,30 @@ private: rtengine::Coord center; std::vector images; InspectorBuffer* currImage; - double zoom; + bool scaled; // fit image into window + double scale; // current scale + double zoomScale, zoomScaleBegin; // scale during zoom + rtengine::Coord centerBegin, dcenterBegin; // center during zoom bool active; + bool pinned; + bool dirty; sigc::connection delayconn; Glib::ustring next_image_path; + Gtk::Window window; + bool on_key_release(GdkEventKey *event); + bool on_key_press(GdkEventKey *event); + + bool on_button_press_event(GdkEventButton *event) override; + bool on_scroll_event(GdkEventScroll *event) override; + void moveCenter(int delta_x, int delta_y, int imW, int imH, int deviceScale); + + Glib::RefPtr gestureZoom; + void beginZoom(double x, double y); + void on_zoom_begin(GdkEventSequence *); + void on_zoom_scale_changed(double zscale); + bool on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) override; void deleteBuffers(); @@ -62,6 +80,11 @@ public: Inspector(); ~Inspector() override; + /** @brief Show or hide window + * @param scaled fit image into window + */ + void showWindow(bool scaled); + /** @brief Mouse movement to a new position * @param pos Location of the mouse, in percentage (i.e. [0;1] range) relative to the full size image ; -1,-1 == out of the image * @param transform H/V flip and coarse rotation transformation