diff --git a/rtengine/perspectivecorrection.h b/rtengine/perspectivecorrection.h index c46c34147..f092f2c8c 100644 --- a/rtengine/perspectivecorrection.h +++ b/rtengine/perspectivecorrection.h @@ -20,12 +20,20 @@ #pragma once -#include "coord2d.h" -#include "procparams.h" -#include "imagesource.h" +#include namespace rtengine { +namespace procparams +{ + +class ProcParams; + +} + +class ImageSource; +class FramesMetaData; + class ControlLine { public: diff --git a/rtgui/CMakeLists.txt b/rtgui/CMakeLists.txt index cfadb7928..514204baf 100644 --- a/rtgui/CMakeLists.txt +++ b/rtgui/CMakeLists.txt @@ -34,6 +34,7 @@ set(NONCLISOURCEFILES colorappearance.cc coloredbar.cc colortoning.cc + controllines.cc controlspotpanel.cc coordinateadjuster.cc crop.cc diff --git a/rtgui/controllines.cc b/rtgui/controllines.cc new file mode 100644 index 000000000..8d39c9d59 --- /dev/null +++ b/rtgui/controllines.cc @@ -0,0 +1,457 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2020 Lawrence Lee + * + * RawTherapee 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 3 of the License, or + * (at your option) any later version. + * + * RawTherapee 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. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#include + +#include "controllines.h" +#include "editcallbacks.h" +#include "editwidgets.h" +#include "rtsurface.h" + +#include "../rtengine/perspectivecorrection.h" + +using namespace rtengine; + +::ControlLine::~ControlLine() = default; + +ControlLineManager::ControlLineManager(): + EditSubscriber(ET_OBJECTS), + canvas_area(new Rectangle()), + cursor(CSCrosshair), + draw_mode(false), + drawing_line(false), + edited(false), + prev_obj(-1), + selected_object(-1) +{ + canvas_area->filled = true; + canvas_area->topLeft = Coord(0, 0); + mouseOverGeometry.push_back(canvas_area.get()); + + line_icon_h = Cairo::RefPtr(new RTSurface("bidirectional-arrow-horizontal-hicontrast.png")); + line_icon_v = Cairo::RefPtr(new RTSurface("bidirectional-arrow-vertical-hicontrast.png")); + line_icon_h_prelight = Cairo::RefPtr(new RTSurface("bidirectional-arrow-horizontal-prelight.png")); + line_icon_v_prelight = Cairo::RefPtr(new RTSurface("bidirectional-arrow-vertical-prelight.png")); +} + +ControlLineManager::~ControlLineManager() = default; + +void ControlLineManager::setActive(bool active) +{ + EditDataProvider* provider = getEditProvider(); + + if (!provider || (this == provider->getCurrSubscriber()) == active) { + return; + } + + if (active) { + subscribe(); + + int ih, iw; + provider->getImageSize(iw, ih); + canvas_area->bottomRight = Coord(iw, ih); + } else { + unsubscribe(); + } +} + +void ControlLineManager::setDrawMode(bool draw) +{ + draw_mode = draw; +} + +size_t ControlLineManager::size(void) const +{ + return control_lines.size(); +} + +bool ControlLineManager::button1Pressed(int modifierKey) +{ + EditDataProvider* dataProvider = getEditProvider(); + + if (!dataProvider) { + return false; + } + + drag_delta = Coord(0, 0); + + const int object = dataProvider->getObject(); + if (object > 0) { // A control line. + if (object % ::ControlLine::OBJ_COUNT == 2) { // Icon. + action = Action::PICKING; + } else { + selected_object = object; + action = Action::DRAGGING; + } + } else if (draw_mode && (modifierKey & GDK_CONTROL_MASK)) { // Add new line. + addLine(dataProvider->posImage, dataProvider->posImage); + drawing_line = true; + selected_object = mouseOverGeometry.size() - 1; // Select endpoint. + action = Action::DRAGGING; + } + + return true; +} + +bool ControlLineManager::button1Released(void) +{ + action = Action::NONE; + if (selected_object > 0) { + mouseOverGeometry[selected_object]->state = Geometry::NORMAL; + } + edited = true; + callbacks->lineChanged(); + drawing_line = false; + selected_object = -1; + return false; +} + +bool ControlLineManager::button3Pressed(int modifierKey) +{ + EditDataProvider* provider = getEditProvider(); + + action = Action::NONE; + + if (!provider || provider->getObject() < 1) { + return false; + } + + action = Action::PICKING; + return false; +} + +bool ControlLineManager::pick1(bool picked) +{ + action = Action::NONE; + + if (!picked) { + return false; + } + + EditDataProvider* provider = getEditProvider(); + + if (!provider || provider->getObject() % ::ControlLine::OBJ_COUNT != 2) { + return false; + } + + // Change line type. + int object_id = provider->getObject(); + ::ControlLine& line = *control_lines[(object_id - 1) / ::ControlLine::OBJ_COUNT]; + + if (line.type == rtengine::ControlLine::HORIZONTAL) { + line.icon = line.icon_v; + line.type = rtengine::ControlLine::VERTICAL; + } else if (line.type == rtengine::ControlLine::VERTICAL) { + line.icon = line.icon_h; + line.type = rtengine::ControlLine::HORIZONTAL; + } + + visibleGeometry[object_id - 1] = line.icon.get(); + + edited = true; + callbacks->lineChanged(); + + return true; +} + +bool ControlLineManager::pick3(bool picked) +{ + action = Action::NONE; + + if (!picked) { + return false; + } + + EditDataProvider* provider = getEditProvider(); + + if (!provider) { + return false; + } + + removeLine((provider->getObject() - 1) / ::ControlLine::OBJ_COUNT); + prev_obj = -1; + selected_object = -1; + return false; +} + +bool ControlLineManager::drag1(int modifierKey) +{ + EditDataProvider* provider = getEditProvider(); + + if (!provider || selected_object < 1) { + return false; + } + + ::ControlLine& control_line = *control_lines[(selected_object - 1) / ::ControlLine::OBJ_COUNT]; + int component = selected_object % ::ControlLine::OBJ_COUNT; // 0 == end, 1 == line, 2 == icon, 3 == begin + Coord mouse = provider->posImage + provider->deltaImage; + Coord delta = provider->deltaImage - drag_delta; + int ih, iw; + provider->getImageSize(iw, ih); + + switch (component) { + case (0): // end + control_line.end->center = mouse; + control_line.end->center.clip(iw, ih); + control_line.line->end = control_line.end->center; + control_line.end->state = Geometry::DRAGGED; + break; + case (1): { // line + // Constrain delta so the end stays above the image. + Coord new_delta = control_line.end->center + delta; + new_delta.clip(iw, ih); + new_delta -= control_line.end->center; + // Constrain delta so the beginning stays above the image. + new_delta += control_line.begin->center; + new_delta.clip(iw, ih); + new_delta -= control_line.begin->center; + // Move all objects in the control line. + control_line.end->center += new_delta; + control_line.begin->center += new_delta; + control_line.line->end = control_line.end->center; + control_line.line->begin = control_line.begin->center; + drag_delta += new_delta; + control_line.line->state = Geometry::DRAGGED; + break; + } + case (3): // begin + control_line.begin->center = mouse; + control_line.begin->center.clip(iw, ih); + control_line.line->begin = control_line.begin->center; + control_line.begin->state = Geometry::DRAGGED; + break; + } + + control_line.icon_h->position.x = (control_line.begin->center.x + control_line.end->center.x) / 2; + control_line.icon_h->position.y = (control_line.begin->center.y + control_line.end->center.y) / 2; + control_line.icon_v->position.x = control_line.icon_h->position.x; + control_line.icon_v->position.y = control_line.icon_h->position.y; + + if (drawing_line) { + autoSetLineType(selected_object); + } + + return false; +} + +bool ControlLineManager::getEdited(void) const +{ + return edited; +} + +CursorShape ControlLineManager::getCursor(int objectID) const +{ + return cursor; +} + +bool ControlLineManager::mouseOver(int modifierKey) +{ + EditDataProvider* provider = getEditProvider(); + + if (!provider) { + return false; + } + + int cur_obj = provider->getObject(); + + if (cur_obj == 0) { // Canvas + if (draw_mode && modifierKey & GDK_CONTROL_MASK) { + cursor = CSPlus; + } else { + cursor = CSCrosshair; + } + } else if (cur_obj < 0) { // Nothing + cursor = CSArrow; + } else if (cur_obj % ::ControlLine::OBJ_COUNT == 2) { // Icon + visibleGeometry[cur_obj - 1]->state = Geometry::PRELIGHT; + cursor = CSArrow; + } else { // Object + visibleGeometry[cur_obj - 1]->state = Geometry::PRELIGHT; + cursor = CSMove2D; + } + + if (prev_obj != cur_obj && prev_obj > 0) { + visibleGeometry[prev_obj - 1]->state = Geometry::NORMAL; + } + prev_obj = cur_obj; + + return true; +} + +void ControlLineManager::switchOffEditMode(void) +{ + if (callbacks) { + callbacks->switchOffEditMode(); + } +} + +void ControlLineManager::setEdited(bool edited) +{ + this->edited = edited; +} + +void ControlLineManager::setEditProvider(EditDataProvider* provider) +{ + EditSubscriber::setEditProvider(provider); +} + +void ControlLineManager::setLines(const std::vector& lines) +{ + removeAll(); + for (auto&& line : lines) { + Coord start(line.x1, line.y1); + Coord end(line.x2, line.y2); + addLine(start, end, line.type); + } +} + +void ControlLineManager::addLine(Coord begin, Coord end, rtengine::ControlLine::Type type) +{ + constexpr int line_width = 2; + constexpr int handle_radius = 6; + std::unique_ptr line; + std::shared_ptr icon_h, icon_v; + std::unique_ptr begin_c, end_c; + + line = std::unique_ptr(new Line()); + line->datum = Geometry::IMAGE; + line->innerLineWidth = line_width; + line->begin = begin; + line->end = end; + + const Cairo::RefPtr null_surface = Cairo::RefPtr(nullptr); + + icon_h = std::make_shared(line_icon_h, null_surface, line_icon_h_prelight, + null_surface, null_surface, Geometry::DP_CENTERCENTER); + icon_h->position = Coord((begin.x + end.x) / 2, (begin.y + end.y) / 2); + + icon_v = std::make_shared(line_icon_v, null_surface, line_icon_v_prelight, + null_surface, null_surface, Geometry::DP_CENTERCENTER); + icon_v->position = Coord((begin.x + end.x) / 2, (begin.y + end.y) / 2); + + begin_c = std::unique_ptr(new Circle()); + begin_c->datum = Geometry::IMAGE; + begin_c->filled = true; + begin_c->radius = handle_radius; + begin_c->center = begin; + + end_c = std::unique_ptr(new Circle()); + end_c->datum = Geometry::IMAGE; + end_c->filled = true; + end_c->radius = handle_radius; + end_c->center = end; + + std::unique_ptr<::ControlLine> control_line(new ::ControlLine()); + control_line->begin = std::move(begin_c); + control_line->end = std::move(end_c); + control_line->icon_h = icon_h; + control_line->icon_v = icon_v; + if (type == rtengine::ControlLine::HORIZONTAL) { + control_line->icon = icon_h; + } else { + control_line->icon = icon_v; + } + control_line->line = std::move(line); + control_line->type = type; + + EditSubscriber::visibleGeometry.push_back(control_line->line.get()); + EditSubscriber::visibleGeometry.push_back(control_line->icon.get()); + EditSubscriber::visibleGeometry.push_back(control_line->begin.get()); + EditSubscriber::visibleGeometry.push_back(control_line->end.get()); + + EditSubscriber::mouseOverGeometry.push_back(control_line->line.get()); + EditSubscriber::mouseOverGeometry.push_back(control_line->icon.get()); + EditSubscriber::mouseOverGeometry.push_back(control_line->begin.get()); + EditSubscriber::mouseOverGeometry.push_back(control_line->end.get()); + + control_lines.push_back(std::move(control_line)); +} + +void ControlLineManager::autoSetLineType(int object_id) +{ + int line_id = (object_id - 1) / ::ControlLine::OBJ_COUNT; + ::ControlLine& line = *control_lines[line_id]; + + int dx = line.begin->center.x - line.end->center.x; + int dy = line.begin->center.y - line.end->center.y; + + if (dx < 0) { + dx = -dx; + } + if (dy < 0) { + dy = -dy; + } + + rtengine::ControlLine::Type type; + std::shared_ptr icon; + + if (dx > dy) { // More horizontal than vertical. + type = rtengine::ControlLine::HORIZONTAL; + icon = line.icon_h; + } else { + type = rtengine::ControlLine::VERTICAL; + icon = line.icon_v; + } + + if (type != line.type) { // Need to update line type. + line.type = type; + line.icon = icon; + visibleGeometry[line_id * ::ControlLine::OBJ_COUNT + 1] = line.icon.get(); + } +} + +void ControlLineManager::removeAll(void) +{ + visibleGeometry.clear(); + mouseOverGeometry.erase(mouseOverGeometry.begin() + 1, mouseOverGeometry.end()); + control_lines.clear(); + prev_obj = -1; + selected_object = -1; + edited = true; + callbacks->lineChanged(); +} + +void ControlLineManager::removeLine(size_t line_id) +{ + if (line_id >= control_lines.size()) { + return; + } + + visibleGeometry.erase(visibleGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id, + visibleGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id + ::ControlLine::OBJ_COUNT); + mouseOverGeometry.erase(mouseOverGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id + 1, + mouseOverGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id + ::ControlLine::OBJ_COUNT + 1); + control_lines.erase(control_lines.begin() + line_id); + + edited = true; + callbacks->lineChanged(); +} + +void ControlLineManager::toControlLines(std::vector& converted) const +{ + converted.clear(); + converted.resize(control_lines.size()); + + for (unsigned int i = 0; i < control_lines.size(); i++) { + converted[i].x1 = control_lines[i]->begin->center.x; + converted[i].y1 = control_lines[i]->begin->center.y; + converted[i].x2 = control_lines[i]->end->center.x; + converted[i].y2 = control_lines[i]->end->center.y; + converted[i].type = control_lines[i]->type; + } +} diff --git a/rtgui/controllines.h b/rtgui/controllines.h new file mode 100644 index 000000000..3743ad5b0 --- /dev/null +++ b/rtgui/controllines.h @@ -0,0 +1,116 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2020 Lawrence Lee + * + * RawTherapee 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 3 of the License, or + * (at your option) any later version. + * + * RawTherapee 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. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#pragma once + +#include + +#include "editcallbacks.h" +#include "../rtengine/perspectivecorrection.h" + +class Circle; +class Line; +class OPIcon; +class Rectangle; +class RTSurface; + +struct ControlLine +{ + static constexpr int OBJ_COUNT = 4; + std::unique_ptr line; + std::shared_ptr icon; + std::shared_ptr icon_h, icon_v; + std::unique_ptr begin, end; + rtengine::ControlLine::Type type; + + ~ControlLine(); +}; + +class ControlLineManager: EditSubscriber +{ + +protected: + /** Hidden object for capturing mouse events. */ + std::unique_ptr canvas_area; + rtengine::Coord drag_delta; + std::vector> control_lines; + CursorShape cursor; + bool draw_mode; + bool drawing_line; + bool edited; + Cairo::RefPtr line_icon_h, line_icon_v; + Cairo::RefPtr line_icon_h_prelight, line_icon_v_prelight; + int prev_obj; + int selected_object; + + void addLine (rtengine::Coord begin, rtengine::Coord end, + rtengine::ControlLine::Type type = rtengine::ControlLine::VERTICAL); + /** + * Set the line type of the line containing the object according to the + * line's angle. + * + * If the line is within 45 degrees of a perfectly vertical + * line, inclusive, the line type is set to vertical. Otherwise, horizontal. + */ + void autoSetLineType(int object_id); + void removeLine (size_t line_id); + +public: + class Callbacks + { + public: + virtual ~Callbacks() {}; + /** Called when a line changed (added, removed, moved, etc.). */ + virtual void lineChanged (void) {}; + /** Called when the EditSubscriber's switchOffEditMode is called. */ + virtual void switchOffEditMode (void) {}; + }; + + /** Callbacks to invoke. */ + std::shared_ptr callbacks; + + ControlLineManager(); + ~ControlLineManager(); + + bool getEdited (void) const; + void removeAll (void); + /** Sets whether or not the lines are visible and interact-able. */ + void setActive (bool active); + /** Set whether or not lines can be drawn and deleted. */ + void setDrawMode (bool draw); + void setEdited (bool edited); + void setEditProvider (EditDataProvider* provider); + void setLines (const std::vector& lines); + /** Returns the number of lines. */ + size_t size (void) const; + /** + * Allocates a new array and populates it with copies of the control lines. + */ + void toControlLines (std::vector& converted) const; + + // EditSubscriber overrides + bool button1Pressed (int modifierKey) override; + bool button1Released (void) override; + bool button3Pressed (int modifierKey) override; + bool pick1 (bool picked) override; + bool pick3 (bool picked) override; + bool drag1 (int modifierKey) override; + CursorShape getCursor (int objectID) const override; + bool mouseOver (int modifierKey) override; + void switchOffEditMode (void) override; +}; diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc index a8a1f73d3..ec86da161 100644 --- a/rtgui/perspective.cc +++ b/rtgui/perspective.cc @@ -782,431 +782,6 @@ void PerspCorrection::setCamBasedEventsActive(bool active) } } -ControlLineManager::ControlLineManager(): - EditSubscriber(ET_OBJECTS), - canvas_area(new Rectangle()), - cursor(CSCrosshair), - draw_mode(false), - drawing_line(false), - edited(false), - prev_obj(-1), - selected_object(-1) -{ - canvas_area->filled = true; - canvas_area->topLeft = Coord(0, 0); - mouseOverGeometry.push_back(canvas_area.get()); - - line_icon_h = Cairo::RefPtr(new RTSurface("bidirectional-arrow-horizontal-hicontrast.png")); - line_icon_v = Cairo::RefPtr(new RTSurface("bidirectional-arrow-vertical-hicontrast.png")); - line_icon_h_prelight = Cairo::RefPtr(new RTSurface("bidirectional-arrow-horizontal-prelight.png")); - line_icon_v_prelight = Cairo::RefPtr(new RTSurface("bidirectional-arrow-vertical-prelight.png")); -} - -void ControlLineManager::setActive(bool active) -{ - EditDataProvider* provider = getEditProvider(); - - if (!provider || (this == provider->getCurrSubscriber()) == active) { - return; - } - - if (active) { - subscribe(); - - int ih, iw; - provider->getImageSize(iw, ih); - canvas_area->bottomRight = Coord(iw, ih); - } else { - unsubscribe(); - } -} - -void ControlLineManager::setDrawMode(bool draw) -{ - draw_mode = draw; -} - -size_t ControlLineManager::size(void) const -{ - return control_lines.size(); -} - -bool ControlLineManager::button1Pressed(int modifierKey) -{ - EditDataProvider* dataProvider = getEditProvider(); - - if (!dataProvider) { - return false; - } - - drag_delta = Coord(0, 0); - - const int object = dataProvider->getObject(); - if (object > 0) { // A control line. - if (object % ::ControlLine::OBJ_COUNT == 2) { // Icon. - action = Action::PICKING; - } else { - selected_object = object; - action = Action::DRAGGING; - } - } else if (draw_mode && (modifierKey & GDK_CONTROL_MASK)) { // Add new line. - addLine(dataProvider->posImage, dataProvider->posImage); - drawing_line = true; - selected_object = mouseOverGeometry.size() - 1; // Select endpoint. - action = Action::DRAGGING; - } - - return true; -} - -bool ControlLineManager::button1Released(void) -{ - action = Action::NONE; - if (selected_object > 0) { - mouseOverGeometry[selected_object]->state = Geometry::NORMAL; - } - edited = true; - callbacks->lineChanged(); - drawing_line = false; - selected_object = -1; - return false; -} - -bool ControlLineManager::button3Pressed(int modifierKey) -{ - EditDataProvider* provider = getEditProvider(); - - action = Action::NONE; - - if (!provider || provider->getObject() < 1) { - return false; - } - - action = Action::PICKING; - return false; -} - -bool ControlLineManager::pick1(bool picked) -{ - action = Action::NONE; - - if (!picked) { - return false; - } - - EditDataProvider* provider = getEditProvider(); - - if (!provider || provider->getObject() % ::ControlLine::OBJ_COUNT != 2) { - return false; - } - - // Change line type. - int object_id = provider->getObject(); - ::ControlLine& line = *control_lines[(object_id - 1) / ::ControlLine::OBJ_COUNT]; - - if (line.type == rtengine::ControlLine::HORIZONTAL) { - line.icon = line.icon_v; - line.type = rtengine::ControlLine::VERTICAL; - } else if (line.type == rtengine::ControlLine::VERTICAL) { - line.icon = line.icon_h; - line.type = rtengine::ControlLine::HORIZONTAL; - } - - visibleGeometry[object_id - 1] = line.icon.get(); - - edited = true; - callbacks->lineChanged(); - - return true; -} - -bool ControlLineManager::pick3(bool picked) -{ - action = Action::NONE; - - if (!picked) { - return false; - } - - EditDataProvider* provider = getEditProvider(); - - if (!provider) { - return false; - } - - removeLine((provider->getObject() - 1) / ::ControlLine::OBJ_COUNT); - prev_obj = -1; - selected_object = -1; - return false; -} - -bool ControlLineManager::drag1(int modifierKey) -{ - EditDataProvider* provider = getEditProvider(); - - if (!provider || selected_object < 1) { - return false; - } - - ::ControlLine& control_line = *control_lines[(selected_object - 1) / ::ControlLine::OBJ_COUNT]; - int component = selected_object % ::ControlLine::OBJ_COUNT; // 0 == end, 1 == line, 2 == icon, 3 == begin - Coord mouse = provider->posImage + provider->deltaImage; - Coord delta = provider->deltaImage - drag_delta; - int ih, iw; - provider->getImageSize(iw, ih); - - switch (component) { - case (0): // end - control_line.end->center = mouse; - control_line.end->center.clip(iw, ih); - control_line.line->end = control_line.end->center; - control_line.end->state = Geometry::DRAGGED; - break; - case (1): { // line - // Constrain delta so the end stays above the image. - Coord new_delta = control_line.end->center + delta; - new_delta.clip(iw, ih); - new_delta -= control_line.end->center; - // Constrain delta so the beginning stays above the image. - new_delta += control_line.begin->center; - new_delta.clip(iw, ih); - new_delta -= control_line.begin->center; - // Move all objects in the control line. - control_line.end->center += new_delta; - control_line.begin->center += new_delta; - control_line.line->end = control_line.end->center; - control_line.line->begin = control_line.begin->center; - drag_delta += new_delta; - control_line.line->state = Geometry::DRAGGED; - break; - } - case (3): // begin - control_line.begin->center = mouse; - control_line.begin->center.clip(iw, ih); - control_line.line->begin = control_line.begin->center; - control_line.begin->state = Geometry::DRAGGED; - break; - } - - control_line.icon_h->position.x = (control_line.begin->center.x + control_line.end->center.x) / 2; - control_line.icon_h->position.y = (control_line.begin->center.y + control_line.end->center.y) / 2; - control_line.icon_v->position.x = control_line.icon_h->position.x; - control_line.icon_v->position.y = control_line.icon_h->position.y; - - if (drawing_line) { - autoSetLineType(selected_object); - } - - return false; -} - -bool ControlLineManager::getEdited(void) const -{ - return edited; -} - -CursorShape ControlLineManager::getCursor(int objectID) const -{ - return cursor; -} - -bool ControlLineManager::mouseOver(int modifierKey) -{ - EditDataProvider* provider = getEditProvider(); - - if (!provider) { - return false; - } - - int cur_obj = provider->getObject(); - - if (cur_obj == 0) { // Canvas - if (draw_mode && modifierKey & GDK_CONTROL_MASK) { - cursor = CSPlus; - } else { - cursor = CSCrosshair; - } - } else if (cur_obj < 0) { // Nothing - cursor = CSArrow; - } else if (cur_obj % ::ControlLine::OBJ_COUNT == 2) { // Icon - visibleGeometry[cur_obj - 1]->state = Geometry::PRELIGHT; - cursor = CSArrow; - } else { // Object - visibleGeometry[cur_obj - 1]->state = Geometry::PRELIGHT; - cursor = CSMove2D; - } - - if (prev_obj != cur_obj && prev_obj > 0) { - visibleGeometry[prev_obj - 1]->state = Geometry::NORMAL; - } - prev_obj = cur_obj; - - return true; -} - -void ControlLineManager::switchOffEditMode(void) -{ - if (callbacks) { - callbacks->switchOffEditMode(); - } -} - -void ControlLineManager::setEdited(bool edited) -{ - this->edited = edited; -} - -void ControlLineManager::setEditProvider(EditDataProvider* provider) -{ - EditSubscriber::setEditProvider(provider); -} - -void ControlLineManager::setLines(const std::vector& lines) -{ - removeAll(); - for (auto&& line : lines) { - Coord start(line.x1, line.y1); - Coord end(line.x2, line.y2); - addLine(start, end, line.type); - } -} - -void ControlLineManager::addLine(Coord begin, Coord end, rtengine::ControlLine::Type type) -{ - constexpr int line_width = 2; - constexpr int handle_radius = 6; - std::unique_ptr line; - std::shared_ptr icon_h, icon_v; - std::unique_ptr begin_c, end_c; - - line = std::unique_ptr(new Line()); - line->datum = Geometry::IMAGE; - line->innerLineWidth = line_width; - line->begin = begin; - line->end = end; - - const Cairo::RefPtr null_surface = Cairo::RefPtr(nullptr); - - icon_h = std::make_shared(line_icon_h, null_surface, line_icon_h_prelight, - null_surface, null_surface, Geometry::DP_CENTERCENTER); - icon_h->position = Coord((begin.x + end.x) / 2, (begin.y + end.y) / 2); - - icon_v = std::make_shared(line_icon_v, null_surface, line_icon_v_prelight, - null_surface, null_surface, Geometry::DP_CENTERCENTER); - icon_v->position = Coord((begin.x + end.x) / 2, (begin.y + end.y) / 2); - - begin_c = std::unique_ptr(new Circle()); - begin_c->datum = Geometry::IMAGE; - begin_c->filled = true; - begin_c->radius = handle_radius; - begin_c->center = begin; - - end_c = std::unique_ptr(new Circle()); - end_c->datum = Geometry::IMAGE; - end_c->filled = true; - end_c->radius = handle_radius; - end_c->center = end; - - std::unique_ptr<::ControlLine> control_line(new ::ControlLine()); - control_line->begin = std::move(begin_c); - control_line->end = std::move(end_c); - control_line->icon_h = icon_h; - control_line->icon_v = icon_v; - if (type == rtengine::ControlLine::HORIZONTAL) { - control_line->icon = icon_h; - } else { - control_line->icon = icon_v; - } - control_line->line = std::move(line); - control_line->type = type; - - EditSubscriber::visibleGeometry.push_back(control_line->line.get()); - EditSubscriber::visibleGeometry.push_back(control_line->icon.get()); - EditSubscriber::visibleGeometry.push_back(control_line->begin.get()); - EditSubscriber::visibleGeometry.push_back(control_line->end.get()); - - EditSubscriber::mouseOverGeometry.push_back(control_line->line.get()); - EditSubscriber::mouseOverGeometry.push_back(control_line->icon.get()); - EditSubscriber::mouseOverGeometry.push_back(control_line->begin.get()); - EditSubscriber::mouseOverGeometry.push_back(control_line->end.get()); - - control_lines.push_back(std::move(control_line)); -} - -void ControlLineManager::autoSetLineType(int object_id) -{ - int line_id = (object_id - 1) / ::ControlLine::OBJ_COUNT; - ::ControlLine& line = *control_lines[line_id]; - - int dx = line.begin->center.x - line.end->center.x; - int dy = line.begin->center.y - line.end->center.y; - - if (dx < 0) { - dx = -dx; - } - if (dy < 0) { - dy = -dy; - } - - rtengine::ControlLine::Type type; - std::shared_ptr icon; - - if (dx > dy) { // More horizontal than vertical. - type = rtengine::ControlLine::HORIZONTAL; - icon = line.icon_h; - } else { - type = rtengine::ControlLine::VERTICAL; - icon = line.icon_v; - } - - if (type != line.type) { // Need to update line type. - line.type = type; - line.icon = icon; - visibleGeometry[line_id * ::ControlLine::OBJ_COUNT + 1] = line.icon.get(); - } -} - -void ControlLineManager::removeAll(void) -{ - visibleGeometry.clear(); - mouseOverGeometry.erase(mouseOverGeometry.begin() + 1, mouseOverGeometry.end()); - control_lines.clear(); - prev_obj = -1; - selected_object = -1; - edited = true; - callbacks->lineChanged(); -} - -void ControlLineManager::removeLine(size_t line_id) -{ - if (line_id >= control_lines.size()) { - return; - } - - visibleGeometry.erase(visibleGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id, - visibleGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id + ::ControlLine::OBJ_COUNT); - mouseOverGeometry.erase(mouseOverGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id + 1, - mouseOverGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id + ::ControlLine::OBJ_COUNT + 1); - control_lines.erase(control_lines.begin() + line_id); - - edited = true; - callbacks->lineChanged(); -} - -void ControlLineManager::toControlLines(std::vector& converted) const -{ - converted.clear(); - converted.resize(control_lines.size()); - - for (unsigned int i = 0; i < control_lines.size(); i++) { - converted[i].x1 = control_lines[i]->begin->center.x; - converted[i].y1 = control_lines[i]->begin->center.y; - converted[i].x2 = control_lines[i]->end->center.x; - converted[i].y2 = control_lines[i]->end->center.y; - converted[i].type = control_lines[i]->type; - } -} - LinesCallbacks::LinesCallbacks(PerspCorrection* tool): tool(tool) { diff --git a/rtgui/perspective.h b/rtgui/perspective.h index c58a9420e..0694ccb4e 100644 --- a/rtgui/perspective.h +++ b/rtgui/perspective.h @@ -21,94 +21,9 @@ #include #include "adjuster.h" -#include "editcallbacks.h" -#include "editwidgets.h" +#include "controllines.h" #include "lensgeomlistener.h" #include "toolpanel.h" -#include "../rtengine/perspectivecorrection.h" - -struct ControlLine -{ - static constexpr int OBJ_COUNT = 4; - std::unique_ptr line; - std::shared_ptr icon; - std::shared_ptr icon_h, icon_v; - std::unique_ptr begin, end; - rtengine::ControlLine::Type type; -}; - -class ControlLineManager: EditSubscriber -{ - -protected: - /** Hidden object for capturing mouse events. */ - std::unique_ptr canvas_area; - rtengine::Coord drag_delta; - std::vector> control_lines; - CursorShape cursor; - bool draw_mode; - bool drawing_line; - bool edited; - Cairo::RefPtr line_icon_h, line_icon_v; - Cairo::RefPtr line_icon_h_prelight, line_icon_v_prelight; - int prev_obj; - int selected_object; - - void addLine (rtengine::Coord begin, rtengine::Coord end, - rtengine::ControlLine::Type type = rtengine::ControlLine::VERTICAL); - /** - * Set the line type of the line containing the object according to the - * line's angle. - * - * If the line is within 45 degrees of a perfectly vertical - * line, inclusive, the line type is set to vertical. Otherwise, horizontal. - */ - void autoSetLineType(int object_id); - void removeLine (size_t line_id); - -public: - class Callbacks - { - public: - virtual ~Callbacks() {}; - /** Called when a line changed (added, removed, moved, etc.). */ - virtual void lineChanged (void) {}; - /** Called when the EditSubscriber's switchOffEditMode is called. */ - virtual void switchOffEditMode (void) {}; - }; - - /** Callbacks to invoke. */ - std::shared_ptr callbacks; - - ControlLineManager(); - - bool getEdited (void) const; - void removeAll (void); - /** Sets whether or not the lines are visible and interact-able. */ - void setActive (bool active); - /** Set whether or not lines can be drawn and deleted. */ - void setDrawMode (bool draw); - void setEdited (bool edited); - void setEditProvider (EditDataProvider* provider); - void setLines (const std::vector& lines); - /** Returns the number of lines. */ - size_t size (void) const; - /** - * Allocates a new array and populates it with copies of the control lines. - */ - void toControlLines (std::vector& converted) const; - - // EditSubscriber overrides - bool button1Pressed (int modifierKey) override; - bool button1Released (void) override; - bool button3Pressed (int modifierKey) override; - bool pick1 (bool picked) override; - bool pick3 (bool picked) override; - bool drag1 (int modifierKey) override; - CursorShape getCursor (int objectID) const override; - bool mouseOver (int modifierKey) override; - void switchOffEditMode (void) override; -}; class PerspCorrection final : public ToolParamBlock,