Set the "apply" and "delete all" button sensitivity based on the number of control lines. Add extra tooltip text to the apply button when editing and there is not enough lines. The text explains there must be enough vertical or horizontal control lines.
526 lines
15 KiB
C++
526 lines
15 KiB
C++
/*
|
|
* This file is part of RawTherapee.
|
|
*
|
|
* Copyright (c) 2020 Lawrence Lee <billee@ucdavis.edu>
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <memory>
|
|
|
|
#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(CSHandOpen),
|
|
draw_mode(false),
|
|
drawing_line(false),
|
|
edited(false),
|
|
horizontalCount(0), verticalCount(0),
|
|
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<RTSurface>(new RTSurface(
|
|
"bidirectional-arrow-horizontal-hicontrast.png"));
|
|
line_icon_v = Cairo::RefPtr<RTSurface>(new RTSurface(
|
|
"bidirectional-arrow-vertical-hicontrast.png"));
|
|
line_icon_h_prelight = Cairo::RefPtr<RTSurface>(new RTSurface(
|
|
"bidirectional-arrow-horizontal-prelight.png"));
|
|
line_icon_v_prelight = Cairo::RefPtr<RTSurface>(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();
|
|
}
|
|
|
|
size_t ControlLineManager::getHorizontalCount(void) const
|
|
{
|
|
return horizontalCount;
|
|
}
|
|
|
|
size_t ControlLineManager::getVerticalCount(void) const
|
|
{
|
|
return verticalCount;
|
|
}
|
|
|
|
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;
|
|
horizontalCount--;
|
|
verticalCount++;
|
|
} else if (line.type == rtengine::ControlLine::VERTICAL) {
|
|
line.icon = line.icon_h;
|
|
line.type = rtengine::ControlLine::HORIZONTAL;
|
|
horizontalCount++;
|
|
verticalCount--;
|
|
}
|
|
|
|
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];
|
|
// 0 == end, 1 == line, 2 == icon, 3 == begin
|
|
int component = selected_object % ::ControlLine::OBJ_COUNT;
|
|
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, int xPos, int yPos) 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 = CSCrosshair;
|
|
} else {
|
|
cursor = CSHandOpen;
|
|
}
|
|
} 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<rtengine::ControlLine>&
|
|
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> line;
|
|
std::shared_ptr<OPIcon> icon_h, icon_v;
|
|
std::unique_ptr<Circle> begin_c, end_c;
|
|
|
|
line = std::unique_ptr<Line>(new Line());
|
|
line->datum = Geometry::IMAGE;
|
|
line->innerLineWidth = line_width;
|
|
line->begin = begin;
|
|
line->end = end;
|
|
|
|
const Cairo::RefPtr<RTSurface> null_surface =
|
|
Cairo::RefPtr<RTSurface>(nullptr);
|
|
|
|
icon_h = std::make_shared<OPIcon>(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<OPIcon>(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<Circle>(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<Circle>(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));
|
|
if (type == rtengine::ControlLine::HORIZONTAL) {
|
|
horizontalCount++;
|
|
} else {
|
|
verticalCount++;
|
|
}
|
|
}
|
|
|
|
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<OPIcon> 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;
|
|
if (type == rtengine::ControlLine::HORIZONTAL) {
|
|
horizontalCount++;
|
|
verticalCount--;
|
|
} else {
|
|
horizontalCount--;
|
|
verticalCount++;
|
|
}
|
|
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();
|
|
horizontalCount = verticalCount = 0;
|
|
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
|
|
);
|
|
if (control_lines[line_id]->type == rtengine::ControlLine::HORIZONTAL) {
|
|
horizontalCount--;
|
|
} else {
|
|
verticalCount--;
|
|
}
|
|
control_lines.erase(control_lines.begin() + line_id);
|
|
|
|
edited = true;
|
|
callbacks->lineChanged();
|
|
}
|
|
|
|
void ControlLineManager::toControlLines(std::vector<rtengine::ControlLine>&
|
|
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;
|
|
}
|
|
}
|