diff --git a/rtdata/languages/default b/rtdata/languages/default
index 821a6fb60..69cc6adc3 100644
--- a/rtdata/languages/default
+++ b/rtdata/languages/default
@@ -1483,6 +1483,7 @@ ICCPROFCREATOR_PROF_V4;ICC v4
ICCPROFCREATOR_SAVEDIALOG_TITLE;Save ICC profile as...
ICCPROFCREATOR_SLOPE;Slope
ICCPROFCREATOR_TRC_PRESET;Tone response curve
+INSPECTOR_WINDOW_TITLE;Inspector
IPTCPANEL_CATEGORY;Category
IPTCPANEL_CATEGORYHINT;Identifies the subject of the image in the opinion of the provider.
IPTCPANEL_CITY;City
diff --git a/rtgui/controllines.cc b/rtgui/controllines.cc
index 84175af28..65106f375 100644
--- a/rtgui/controllines.cc
+++ b/rtgui/controllines.cc
@@ -16,6 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with RawTherapee. If not, see .
*/
+#include
#include
#include "controllines.h"
@@ -27,6 +28,53 @@
using namespace rtengine;
+enum GeometryIndex {
+ MO_CANVAS,
+ MO_OBJECT_COUNT,
+
+ VISIBLE_OBJECT_COUNT = 0
+};
+
+/**
+ * Offsets for mouse-over geometry that can be compared to the mouse-over object
+ * ID modded with the control line object count.
+ */
+enum GeometryOffset {
+ OFFSET_LINE = (MO_OBJECT_COUNT + ::ControlLine::LINE) % ::ControlLine::OBJECT_COUNT,
+ OFFSET_ICON = (MO_OBJECT_COUNT + ::ControlLine::ICON) % ::ControlLine::OBJECT_COUNT,
+ OFFSET_BEGIN = (MO_OBJECT_COUNT + ::ControlLine::BEGIN) % ::ControlLine::OBJECT_COUNT,
+ OFFSET_END = (MO_OBJECT_COUNT + ::ControlLine::END) % ::ControlLine::OBJECT_COUNT,
+};
+
+namespace
+{
+
+/**
+ * Returns true if the object matches the offset.
+ */
+constexpr bool checkOffset(int object_id, enum GeometryOffset offset)
+{
+ return object_id % ::ControlLine::OBJECT_COUNT == offset;
+}
+
+/**
+ * Converts a control line mouse-over geometry ID to the visible geometry ID.
+ */
+constexpr int mouseOverIdToVisibleId(int mouse_over_id)
+{
+ return mouse_over_id - MO_OBJECT_COUNT + VISIBLE_OBJECT_COUNT;
+}
+
+/**
+ * Converts a control line mouse-over geometry ID to the control line ID.
+ */
+constexpr int mouseOverIdToLineId(int mouse_over_id)
+{
+ return (mouse_over_id - MO_OBJECT_COUNT) / ::ControlLine::OBJECT_COUNT;
+}
+
+}
+
::ControlLine::~ControlLine() = default;
ControlLineManager::ControlLineManager():
@@ -107,17 +155,20 @@ bool ControlLineManager::button1Pressed(int modifierKey)
const int object = dataProvider->getObject();
- if (object > 0) { // A control line.
- if (object % ::ControlLine::OBJ_COUNT == 2) { // Icon.
+ if (object >= MO_OBJECT_COUNT) { // A control line.
+ if (checkOffset(object, OFFSET_ICON)) { // Icon.
action = Action::PICKING;
} else {
selected_object = object;
action = Action::DRAGGING;
}
} else if (draw_mode && (modifierKey & GDK_CONTROL_MASK)) { // Add new line.
+ if (object < 0) {
+ return false;
+ }
addLine(dataProvider->posImage, dataProvider->posImage);
drawing_line = true;
- selected_object = mouseOverGeometry.size() - 1; // Select endpoint.
+ selected_object = mouseOverGeometry.size() - ::ControlLine::OBJECT_COUNT + ::ControlLine::END; // Select endpoint.
action = Action::DRAGGING;
}
@@ -128,7 +179,7 @@ bool ControlLineManager::button1Released(void)
{
action = Action::NONE;
- if (selected_object > 0) {
+ if (selected_object >= MO_OBJECT_COUNT) {
mouseOverGeometry[selected_object]->state = Geometry::NORMAL;
}
@@ -136,7 +187,7 @@ bool ControlLineManager::button1Released(void)
callbacks->lineChanged();
drawing_line = false;
selected_object = -1;
- return false;
+ return true;
}
bool ControlLineManager::button3Pressed(int modifierKey)
@@ -145,12 +196,12 @@ bool ControlLineManager::button3Pressed(int modifierKey)
action = Action::NONE;
- if (!provider || provider->getObject() < 1) {
+ if (!provider || provider->getObject() < MO_OBJECT_COUNT) {
return false;
}
action = Action::PICKING;
- return false;
+ return true;
}
bool ControlLineManager::pick1(bool picked)
@@ -163,14 +214,13 @@ bool ControlLineManager::pick1(bool picked)
EditDataProvider* provider = getEditProvider();
- if (!provider || provider->getObject() % ::ControlLine::OBJ_COUNT != 2) {
+ if (!provider || !checkOffset(provider->getObject(), OFFSET_ICON)) {
return false;
}
// Change line type.
int object_id = provider->getObject();
- ::ControlLine& line =
- *control_lines[(object_id - 1) / ::ControlLine::OBJ_COUNT];
+ ::ControlLine& line = *control_lines[mouseOverIdToLineId(object_id)];
if (line.type == rtengine::ControlLine::HORIZONTAL) {
line.icon = line.icon_v;
@@ -184,7 +234,7 @@ bool ControlLineManager::pick1(bool picked)
verticalCount--;
}
- visibleGeometry[object_id - 1] = line.icon.get();
+ visibleGeometry[mouseOverIdToVisibleId(object_id)] = line.icon.get();
edited = true;
callbacks->lineChanged();
@@ -206,38 +256,42 @@ bool ControlLineManager::pick3(bool picked)
return false;
}
- removeLine((provider->getObject() - 1) / ::ControlLine::OBJ_COUNT);
+ removeLine(mouseOverIdToLineId(provider->getObject()));
prev_obj = -1;
selected_object = -1;
- return false;
+ return true;
}
bool ControlLineManager::drag1(int modifierKey)
{
+ if (action != Action::DRAGGING) {
+ return false;
+ }
+
EditDataProvider* provider = getEditProvider();
- if (!provider || selected_object < 1) {
+ if (!provider || selected_object < MO_OBJECT_COUNT) {
return false;
}
::ControlLine& control_line =
- *control_lines[(selected_object - 1) / ::ControlLine::OBJ_COUNT];
+ *control_lines[mouseOverIdToLineId(selected_object)];
// 0 == end, 1 == line, 2 == icon, 3 == begin
- int component = selected_object % ::ControlLine::OBJ_COUNT;
+ int component = selected_object % ::ControlLine::OBJECT_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
+ case (OFFSET_END): // 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
+ case (OFFSET_LINE): { // line
// Constrain delta so the end stays above the image.
Coord new_delta = control_line.end->center + delta;
new_delta.clip(iw, ih);
@@ -256,7 +310,7 @@ bool ControlLineManager::drag1(int modifierKey)
break;
}
- case (3): // begin
+ case (OFFSET_BEGIN): // begin
control_line.begin->center = mouse;
control_line.begin->center.clip(iw, ih);
control_line.line->begin = control_line.begin->center;
@@ -275,7 +329,24 @@ bool ControlLineManager::drag1(int modifierKey)
autoSetLineType(selected_object);
}
- return false;
+ return true;
+}
+
+void ControlLineManager::releaseEdit(void)
+{
+ action = Action::NONE;
+
+ if (selected_object >= MO_OBJECT_COUNT) {
+ mouseOverGeometry[selected_object]->state = Geometry::NORMAL;
+ }
+ if (prev_obj >= MO_OBJECT_COUNT) {
+ visibleGeometry[mouseOverIdToVisibleId(prev_obj)]->state = Geometry::NORMAL;
+ }
+
+ edited = true;
+ callbacks->lineChanged();
+ drawing_line = false;
+ selected_object = -1;
}
bool ControlLineManager::getEdited(void) const
@@ -298,7 +369,7 @@ bool ControlLineManager::mouseOver(int modifierKey)
int cur_obj = provider->getObject();
- if (cur_obj == 0) { // Canvas
+ if (cur_obj == MO_CANVAS) { // Canvas
if (draw_mode && modifierKey & GDK_CONTROL_MASK) {
cursor = CSCrosshair;
} else {
@@ -306,16 +377,16 @@ bool ControlLineManager::mouseOver(int modifierKey)
}
} else if (cur_obj < 0) { // Nothing
cursor = CSArrow;
- } else if (cur_obj % ::ControlLine::OBJ_COUNT == 2) { // Icon
- visibleGeometry[cur_obj - 1]->state = Geometry::PRELIGHT;
+ } else if (checkOffset(cur_obj, OFFSET_ICON)) { // Icon
+ visibleGeometry[mouseOverIdToVisibleId(cur_obj)]->state = Geometry::PRELIGHT;
cursor = CSArrow;
} else { // Object
- visibleGeometry[cur_obj - 1]->state = Geometry::PRELIGHT;
+ visibleGeometry[mouseOverIdToVisibleId(cur_obj)]->state = Geometry::PRELIGHT;
cursor = CSMove2D;
}
- if (prev_obj != cur_obj && prev_obj > 0) {
- visibleGeometry[prev_obj - 1]->state = Geometry::NORMAL;
+ if (prev_obj != cur_obj && prev_obj >= MO_OBJECT_COUNT) {
+ visibleGeometry[mouseOverIdToVisibleId(prev_obj)]->state = Geometry::NORMAL;
}
prev_obj = cur_obj;
@@ -409,14 +480,29 @@ void ControlLineManager::addLine(Coord begin, Coord end,
control_line->line = std::move(line);
control_line->type = type;
+ auto assertEqual = [](size_t a, int b) {
+ assert(b >= 0);
+ assert(a == static_cast(b));
+ };
+
+ const int base_visible_offset = VISIBLE_OBJECT_COUNT + ::ControlLine::OBJECT_COUNT * control_lines.size();
+ assertEqual(visibleGeometry.size(), base_visible_offset + ::ControlLine::LINE);
EditSubscriber::visibleGeometry.push_back(control_line->line.get());
+ assertEqual(visibleGeometry.size(), base_visible_offset + ::ControlLine::ICON);
EditSubscriber::visibleGeometry.push_back(control_line->icon.get());
+ assertEqual(visibleGeometry.size(), base_visible_offset + ::ControlLine::BEGIN);
EditSubscriber::visibleGeometry.push_back(control_line->begin.get());
+ assertEqual(visibleGeometry.size(), base_visible_offset + ::ControlLine::END);
EditSubscriber::visibleGeometry.push_back(control_line->end.get());
+ const int base_mo_count = MO_OBJECT_COUNT + ::ControlLine::OBJECT_COUNT * control_lines.size();
+ assertEqual(mouseOverGeometry.size(), base_mo_count + ::ControlLine::LINE);
EditSubscriber::mouseOverGeometry.push_back(control_line->line.get());
+ assertEqual(mouseOverGeometry.size(), base_mo_count + ::ControlLine::ICON);
EditSubscriber::mouseOverGeometry.push_back(control_line->icon.get());
+ assertEqual(mouseOverGeometry.size(), base_mo_count + ::ControlLine::BEGIN);
EditSubscriber::mouseOverGeometry.push_back(control_line->begin.get());
+ assertEqual(mouseOverGeometry.size(), base_mo_count + ::ControlLine::END);
EditSubscriber::mouseOverGeometry.push_back(control_line->end.get());
control_lines.push_back(std::move(control_line));
@@ -429,7 +515,7 @@ void ControlLineManager::addLine(Coord begin, Coord end,
void ControlLineManager::autoSetLineType(int object_id)
{
- int line_id = (object_id - 1) / ::ControlLine::OBJ_COUNT;
+ int line_id = mouseOverIdToLineId(object_id);
::ControlLine& line = *control_lines[line_id];
int dx = line.begin->center.x - line.end->center.x;
@@ -464,7 +550,8 @@ void ControlLineManager::autoSetLineType(int object_id)
horizontalCount--;
verticalCount++;
}
- visibleGeometry[line_id * ::ControlLine::OBJ_COUNT + 1] =
+ visibleGeometry[line_id * ::ControlLine::OBJECT_COUNT
++ VISIBLE_OBJECT_COUNT + ::ControlLine::ICON] =
line.icon.get();
}
}
@@ -472,7 +559,7 @@ void ControlLineManager::autoSetLineType(int object_id)
void ControlLineManager::removeAll(void)
{
visibleGeometry.clear();
- mouseOverGeometry.erase(mouseOverGeometry.begin() + 1,
+ mouseOverGeometry.erase(mouseOverGeometry.begin() + MO_OBJECT_COUNT,
mouseOverGeometry.end());
control_lines.clear();
horizontalCount = verticalCount = 0;
@@ -489,14 +576,13 @@ void ControlLineManager::removeLine(std::size_t line_id)
}
visibleGeometry.erase(
- visibleGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id,
- visibleGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id
- + ::ControlLine::OBJ_COUNT
+ visibleGeometry.begin() + ::ControlLine::OBJECT_COUNT * line_id + VISIBLE_OBJECT_COUNT,
+ visibleGeometry.begin() + ::ControlLine::OBJECT_COUNT * line_id + VISIBLE_OBJECT_COUNT
+ + ::ControlLine::OBJECT_COUNT
);
mouseOverGeometry.erase(
- mouseOverGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id + 1,
- mouseOverGeometry.begin() + ::ControlLine::OBJ_COUNT * line_id
- + ::ControlLine::OBJ_COUNT + 1
+ mouseOverGeometry.begin() + ::ControlLine::OBJECT_COUNT * line_id + MO_OBJECT_COUNT,
+ mouseOverGeometry.begin() + ::ControlLine::OBJECT_COUNT * line_id + MO_OBJECT_COUNT + ::ControlLine::OBJECT_COUNT
);
if (control_lines[line_id]->type == rtengine::ControlLine::HORIZONTAL) {
horizontalCount--;
diff --git a/rtgui/controllines.h b/rtgui/controllines.h
index 81c4c7b8a..9e850da1c 100644
--- a/rtgui/controllines.h
+++ b/rtgui/controllines.h
@@ -30,7 +30,14 @@ class Rectangle;
class RTSurface;
struct ControlLine {
- static constexpr int OBJ_COUNT = 4;
+ enum ObjectIndex {
+ LINE,
+ ICON,
+ BEGIN,
+ END,
+ OBJECT_COUNT
+ };
+
std::unique_ptr line;
std::shared_ptr icon;
std::shared_ptr icon_h, icon_v;
@@ -92,6 +99,8 @@ public:
std::size_t getHorizontalCount() const;
/** Returns the number of vertical control lines. */
std::size_t getVerticalCount() const;
+ /** Release anything that is currently being dragged. */
+ void releaseEdit(void);
void removeAll(void);
/** Sets whether or not the lines are visible and interact-able. */
void setActive(bool active);
diff --git a/rtgui/cropwindow.cc b/rtgui/cropwindow.cc
index 5a1debb23..b612de2e7 100644
--- a/rtgui/cropwindow.cc
+++ b/rtgui/cropwindow.cc
@@ -540,7 +540,7 @@ void CropWindow::buttonPress (int button, int type, int bstate, int x, int y)
action_y = 0;
}
- } else if (iarea->getToolMode () == TMHand) { // events outside of the image domain
+ } else if (iarea->getToolMode () == TMHand || iarea->getToolMode() == TMPerspective) { // events outside of the image domain
EditSubscriber *editSubscriber = iarea->getCurrSubscriber();
if (editSubscriber && editSubscriber->getEditingType() == ET_OBJECTS) {
diff --git a/rtgui/filebrowser.cc b/rtgui/filebrowser.cc
index c3b4a6353..b96a5ecd1 100644
--- a/rtgui/filebrowser.cc
+++ b/rtgui/filebrowser.cc
@@ -411,7 +411,7 @@ FileBrowser::FileBrowser () :
untrash->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_Delete, Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
open->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_Return, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
if (options.inspectorWindow)
- inspect->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_F, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
+ inspect->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_f, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
develop->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_B, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
developfast->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_B, Gdk::CONTROL_MASK | Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE);
copyprof->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_C, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE);
@@ -1374,6 +1374,19 @@ int FileBrowser::getThumbnailHeight ()
}
}
+void FileBrowser::enableTabMode(bool enable)
+{
+ ThumbBrowserBase::enableTabMode(enable);
+ if (options.inspectorWindow) {
+ if (enable) {
+ inspect->remove_accelerator(pmenu->get_accel_group(), GDK_KEY_f, (Gdk::ModifierType)0);
+ }
+ else {
+ inspect->add_accelerator ("activate", pmenu->get_accel_group(), GDK_KEY_f, (Gdk::ModifierType)0, Gtk::ACCEL_VISIBLE);
+ }
+ }
+}
+
void FileBrowser::applyMenuItemActivated (ProfileStoreLabel *label)
{
MYREADERLOCK(l, entryRW);
@@ -2100,5 +2113,5 @@ void FileBrowser::openRequested( std::vector mselected)
void FileBrowser::inspectRequested(std::vector mselected)
{
- getInspector()->showWindow(false, false);
+ getInspector()->showWindow(true);
}
diff --git a/rtgui/filebrowser.h b/rtgui/filebrowser.h
index 03a8636e5..4602ba9bb 100644
--- a/rtgui/filebrowser.h
+++ b/rtgui/filebrowser.h
@@ -182,6 +182,8 @@ public:
void saveThumbnailHeight (int height) override;
int getThumbnailHeight () override;
+
+ void enableTabMode(bool enable);
bool isInTabMode() override
{
return tbl ? tbl->isInTabMode() : false;
diff --git a/rtgui/filebrowserentry.cc b/rtgui/filebrowserentry.cc
index 432296f38..5e8c730aa 100644
--- a/rtgui/filebrowserentry.cc
+++ b/rtgui/filebrowserentry.cc
@@ -294,7 +294,7 @@ bool FileBrowserEntry::motionNotify (int x, int y)
Inspector* inspector = parent->getInspector();
- if (inspector && inspector->isActive() && !parent->isInTabMode()) {
+ if (inspector && inspector->isActive() && (!parent->isInTabMode() || options.inspectorWindow)) {
const rtengine::Coord2D coord(getPosInImgSpace(x, y));
if (coord.x != -1.) {
diff --git a/rtgui/filecatalog.cc b/rtgui/filecatalog.cc
index dbea4ade9..c45fc154f 100644
--- a/rtgui/filecatalog.cc
+++ b/rtgui/filecatalog.cc
@@ -2515,8 +2515,10 @@ bool FileCatalog::handleShortcutKey (GdkEventKey* event)
if (!ctrl && !alt) {
switch (event->keyval) {
case GDK_KEY_f:
+ fileBrowser->getInspector()->showWindow(false, true);
+ return true;
case GDK_KEY_F:
- fileBrowser->getInspector()->showWindow(!shift);
+ fileBrowser->getInspector()->showWindow(false, false);
return true;
}
}
@@ -2524,6 +2526,23 @@ bool FileCatalog::handleShortcutKey (GdkEventKey* event)
return fileBrowser->keyPressed(event);
}
+bool FileCatalog::handleShortcutKeyRelease(GdkEventKey* event)
+{
+ bool ctrl = event->state & GDK_CONTROL_MASK;
+ bool alt = event->state & GDK_MOD1_MASK;
+
+ if (!ctrl && !alt) {
+ switch (event->keyval) {
+ case GDK_KEY_f:
+ case GDK_KEY_F:
+ fileBrowser->getInspector()->hideWindow();
+ return true;
+ }
+ }
+
+ return false;
+}
+
void FileCatalog::showToolBar()
{
if (hbToolBar1STB) {
diff --git a/rtgui/filecatalog.h b/rtgui/filecatalog.h
index c7c4f3155..23d56af73 100644
--- a/rtgui/filecatalog.h
+++ b/rtgui/filecatalog.h
@@ -276,6 +276,7 @@ public:
void openNextPreviousEditorImage (Glib::ustring fname, bool clearFilters, eRTNav nextPrevious);
bool handleShortcutKey (GdkEventKey* event);
+ bool handleShortcutKeyRelease(GdkEventKey *event);
bool CheckSidePanelsVisibility();
void toggleSidePanels();
diff --git a/rtgui/filepanel.cc b/rtgui/filepanel.cc
index a09a82597..974482f41 100644
--- a/rtgui/filepanel.cc
+++ b/rtgui/filepanel.cc
@@ -412,6 +412,15 @@ bool FilePanel::handleShortcutKey (GdkEventKey* event)
return false;
}
+bool FilePanel::handleShortcutKeyRelease(GdkEventKey *event)
+{
+ if(fileCatalog->handleShortcutKeyRelease(event)) {
+ return true;
+ }
+
+ return false;
+}
+
void FilePanel::loadingThumbs(Glib::ustring str, double rate)
{
GThreadLock lock; // All GUI access from idle_add callbacks or separate thread HAVE to be protected
diff --git a/rtgui/filepanel.h b/rtgui/filepanel.h
index 65e1ea548..ba5dfa7c9 100644
--- a/rtgui/filepanel.h
+++ b/rtgui/filepanel.h
@@ -81,6 +81,7 @@ public:
bool imageLoaded( Thumbnail* thm, ProgressConnector * );
bool handleShortcutKey (GdkEventKey* event);
+ bool handleShortcutKeyRelease(GdkEventKey *event);
void updateTPVScrollbar (bool hide);
private:
diff --git a/rtgui/gradient.cc b/rtgui/gradient.cc
index 972105dd0..af6503b45 100644
--- a/rtgui/gradient.cc
+++ b/rtgui/gradient.cc
@@ -12,6 +12,14 @@
using namespace rtengine;
using namespace rtengine::procparams;
+enum GeometryIndex {
+ H_LINE,
+ V_LINE,
+ FEATHER_LINE_1,
+ FEATHER_LINE_2,
+ CENTER_CIRCLE,
+};
+
Gradient::Gradient () : FoldableToolPanel(this, "gradient", M("TP_GRADIENT_LABEL"), false, true), EditSubscriber(ET_OBJECTS), lastObject(-1), draggedPointOldAngle(-1000.)
{
@@ -191,24 +199,24 @@ void Gradient::updateGeometry(const int centerX, const int centerY, const double
};
// update horizontal line
- updateLine (visibleGeometry.at(0), 1500., 0., 180.);
- updateLine (mouseOverGeometry.at(0), 1500., 0., 180.);
+ updateLine (visibleGeometry.at(H_LINE), 1500., 0., 180.);
+ updateLine (mouseOverGeometry.at(H_LINE), 1500., 0., 180.);
// update vertical line
- updateLine (visibleGeometry.at(1), 700., 90., 270.);
- updateLine (mouseOverGeometry.at(1), 700., 90., 270.);
+ updateLine (visibleGeometry.at(V_LINE), 700., 90., 270.);
+ updateLine (mouseOverGeometry.at(V_LINE), 700., 90., 270.);
// update upper feather line
- updateLineWithDecay (visibleGeometry.at(2), 350., 270.);
- updateLineWithDecay (mouseOverGeometry.at(2), 350., 270.);
+ updateLineWithDecay (visibleGeometry.at(FEATHER_LINE_1), 350., 270.);
+ updateLineWithDecay (mouseOverGeometry.at(FEATHER_LINE_1), 350., 270.);
// update lower feather line
- updateLineWithDecay (visibleGeometry.at(3), 350., 90.);
- updateLineWithDecay (mouseOverGeometry.at(3), 350., 90.);
+ updateLineWithDecay (visibleGeometry.at(FEATHER_LINE_2), 350., 90.);
+ updateLineWithDecay (mouseOverGeometry.at(FEATHER_LINE_2), 350., 90.);
// update circle's position
- updateCircle (visibleGeometry.at(4));
- updateCircle (mouseOverGeometry.at(4));
+ updateCircle (visibleGeometry.at(CENTER_CIRCLE));
+ updateCircle (mouseOverGeometry.at(CENTER_CIRCLE));
}
void Gradient::write (ProcParams* pp, ParamsEdited* pedited)
@@ -325,6 +333,7 @@ void Gradient::editToggled ()
if (edit->get_active()) {
subscribe();
} else {
+ releaseEdit();
unsubscribe();
}
}
@@ -332,12 +341,12 @@ void Gradient::editToggled ()
CursorShape Gradient::getCursor(int objectID, int xPos, int yPos) const
{
switch (objectID) {
- case (0):
- case (1):
+ case (H_LINE):
+ case (V_LINE):
return CSMoveRotate;
- case (2):
- case (3): {
+ case (FEATHER_LINE_1):
+ case (FEATHER_LINE_2): {
int angle = degree->getIntValue();
if (angle < -135 || (angle >= -45 && angle <= 45) || angle > 135) {
@@ -347,7 +356,7 @@ CursorShape Gradient::getCursor(int objectID, int xPos, int yPos) const
return CSMove1DH;
}
- case (4):
+ case (CENTER_CIRCLE):
return CSMove2D;
default:
@@ -361,18 +370,18 @@ bool Gradient::mouseOver(int modifierKey)
if (editProvider && editProvider->getObject() != lastObject) {
if (lastObject > -1) {
- if (lastObject == 2 || lastObject == 3) {
- EditSubscriber::visibleGeometry.at(2)->state = Geometry::NORMAL;
- EditSubscriber::visibleGeometry.at(3)->state = Geometry::NORMAL;
+ if (lastObject == FEATHER_LINE_1 || lastObject == FEATHER_LINE_2) {
+ EditSubscriber::visibleGeometry.at(FEATHER_LINE_1)->state = Geometry::NORMAL;
+ EditSubscriber::visibleGeometry.at(FEATHER_LINE_2)->state = Geometry::NORMAL;
} else {
EditSubscriber::visibleGeometry.at(lastObject)->state = Geometry::NORMAL;
}
}
if (editProvider->getObject() > -1) {
- if (editProvider->getObject() == 2 || editProvider->getObject() == 3) {
- EditSubscriber::visibleGeometry.at(2)->state = Geometry::PRELIGHT;
- EditSubscriber::visibleGeometry.at(3)->state = Geometry::PRELIGHT;
+ if (editProvider->getObject() == FEATHER_LINE_1 || editProvider->getObject() == FEATHER_LINE_2) {
+ EditSubscriber::visibleGeometry.at(FEATHER_LINE_1)->state = Geometry::PRELIGHT;
+ EditSubscriber::visibleGeometry.at(FEATHER_LINE_2)->state = Geometry::PRELIGHT;
} else {
EditSubscriber::visibleGeometry.at(editProvider->getObject())->state = Geometry::PRELIGHT;
}
@@ -414,7 +423,7 @@ bool Gradient::button1Pressed(int modifierKey)
//printf("\ndraggedPointOldAngle=%.3f\n\n", draggedPointOldAngle);
draggedPointAdjusterAngle = degree->getValue();
- if (lastObject == 2 || lastObject == 3) {
+ if (lastObject == FEATHER_LINE_1 || lastObject == FEATHER_LINE_2) {
// Dragging a line to change the angle
PolarCoord draggedPoint;
rtengine::Coord currPos;
@@ -430,7 +439,7 @@ bool Gradient::button1Pressed(int modifierKey)
// compute the projected value of the dragged point
draggedFeatherOffset = draggedPoint.radius * sin((draggedPoint.angle - degree->getValue()) / 180.*rtengine::RT_PI);
- if (lastObject == 3) {
+ if (lastObject == FEATHER_LINE_2) {
draggedFeatherOffset = -draggedFeatherOffset;
}
@@ -441,9 +450,9 @@ bool Gradient::button1Pressed(int modifierKey)
return false;
} else { // should theoretically always be true
// this will let this class ignore further drag events
- if (lastObject == 2 || lastObject == 3) {
- EditSubscriber::visibleGeometry.at(2)->state = Geometry::NORMAL;
- EditSubscriber::visibleGeometry.at(3)->state = Geometry::NORMAL;
+ if (lastObject == FEATHER_LINE_1 || lastObject == FEATHER_LINE_2) {
+ EditSubscriber::visibleGeometry.at(FEATHER_LINE_1)->state = Geometry::NORMAL;
+ EditSubscriber::visibleGeometry.at(FEATHER_LINE_2)->state = Geometry::NORMAL;
} else {
EditSubscriber::visibleGeometry.at(lastObject)->state = Geometry::NORMAL;
}
@@ -471,7 +480,7 @@ bool Gradient::drag1(int modifierKey)
double halfSizeW = imW / 2.;
double halfSizeH = imH / 2.;
- if (lastObject == 0 || lastObject == 1) {
+ if (lastObject == H_LINE || lastObject == V_LINE) {
// Dragging a line to change the angle
PolarCoord draggedPoint;
@@ -515,7 +524,7 @@ bool Gradient::drag1(int modifierKey)
return true;
}
- } else if (lastObject == 2 || lastObject == 3) {
+ } else if (lastObject == FEATHER_LINE_1 || lastObject == FEATHER_LINE_2) {
// Dragging the upper or lower feather bar
PolarCoord draggedPoint;
rtengine::Coord currPos;
@@ -532,11 +541,11 @@ bool Gradient::drag1(int modifierKey)
draggedPoint = currPos - centerPos;
double currDraggedFeatherOffset = draggedPoint.radius * sin((draggedPoint.angle - degree->getValue()) / 180.*rtengine::RT_PI);
- if (lastObject == 2)
+ if (lastObject == FEATHER_LINE_1)
// Dragging the upper feather bar
{
currDraggedFeatherOffset -= draggedFeatherOffset;
- } else if (lastObject == 3)
+ } else if (lastObject == FEATHER_LINE_2)
// Dragging the lower feather bar
{
currDraggedFeatherOffset = -currDraggedFeatherOffset + draggedFeatherOffset;
@@ -554,7 +563,7 @@ bool Gradient::drag1(int modifierKey)
return true;
}
- } else if (lastObject == 4) {
+ } else if (lastObject == CENTER_CIRCLE) {
// Dragging the circle to change the center
rtengine::Coord currPos;
draggedCenter += provider->deltaPrevImage;
@@ -579,6 +588,19 @@ bool Gradient::drag1(int modifierKey)
return false;
}
+void Gradient::releaseEdit()
+{
+ if (lastObject >= 0) {
+ if (lastObject == FEATHER_LINE_1 || lastObject == FEATHER_LINE_2) {
+ EditSubscriber::visibleGeometry.at(FEATHER_LINE_1)->state = Geometry::NORMAL;
+ EditSubscriber::visibleGeometry.at(FEATHER_LINE_2)->state = Geometry::NORMAL;
+ } else {
+ EditSubscriber::visibleGeometry.at(lastObject)->state = Geometry::NORMAL;
+ }
+ }
+ action = Action::NONE;
+}
+
void Gradient::switchOffEditMode ()
{
if (edit->get_active()) {
diff --git a/rtgui/gradient.h b/rtgui/gradient.h
index 139b281a8..dc0371932 100644
--- a/rtgui/gradient.h
+++ b/rtgui/gradient.h
@@ -35,6 +35,7 @@ protected:
sigc::connection editConn;
void editToggled ();
+ void releaseEdit();
public:
diff --git a/rtgui/inspector.cc b/rtgui/inspector.cc
index 37ed20207..675da51c6 100644
--- a/rtgui/inspector.cc
+++ b/rtgui/inspector.cc
@@ -21,6 +21,7 @@
#include
#include "cursormanager.h"
#include "guiutils.h"
+#include "multilangmgr.h"
#include "options.h"
#include "pathutils.h"
#include "rtscalable.h"
@@ -82,7 +83,7 @@ InspectorBuffer::~InspectorBuffer() {
// return deg;
//}
-Inspector::Inspector () : currImage(nullptr), scaled(false), scale(1.0), zoomScale(1.0), zoomScaleBegin(1.0), active(false), pinned(false), dirty(false)
+Inspector::Inspector () : currImage(nullptr), scaled(false), scale(1.0), zoomScale(1.0), zoomScaleBegin(1.0), active(false), pinned(false), dirty(false), fullscreen(true), keyDown(false), windowShowing(false)
{
set_name("Inspector");
@@ -91,11 +92,14 @@ Inspector::Inspector () : currImage(nullptr), scaled(false), scale(1.0), zoomSca
}
else {
window = new Gtk::Window();
- window->set_title("RawTherapee Inspector");
+ window->set_name("InspectorWindow");
+ window->set_title("RawTherapee " + M("INSPECTOR_WINDOW_TITLE"));
window->set_visible(false);
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));
+ window->signal_hide().connect(sigc::mem_fun(*this, &Inspector::on_window_hide));
+ window->signal_window_state_event().connect(sigc::mem_fun(*this, &Inspector::on_inspector_window_state_event));
add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
gestureZoom = Gtk::GestureZoom::create(*this);
@@ -104,6 +108,7 @@ Inspector::Inspector () : currImage(nullptr), scaled(false), scale(1.0), zoomSca
window->add(*this);
window->set_size_request(500, 500);
+ window->fullscreen();
initialized = false; // delay init to avoid flickering on some systems
active = true; // always track inspected thumbnails
}
@@ -116,35 +121,40 @@ Inspector::~Inspector()
delete window;
}
-void Inspector::showWindow(bool scaled, bool fullscreen)
+void Inspector::showWindow(bool pinned, bool scaled)
{
- if (!window)
+ if (!window || windowShowing)
return;
// initialize when shown first
if (!initialized) {
window->show_all();
- window->set_visible(false);
initialized = true;
}
// show inspector window
this->scaled = scaled;
- if (fullscreen)
- window->fullscreen();
- else
- window->unfullscreen();
- this->fullscreen = fullscreen;
window->set_visible(true);
- pinned = false;
+ this->pinned = pinned;
+ windowShowing = true;
// update content when becoming visible
switchImage(next_image_path);
mouseMove(next_image_pos, 0);
}
+void Inspector::hideWindow()
+{
+ if (!window) {
+ return;
+ }
+ window->set_visible(false);
+}
+
bool Inspector::on_key_release(GdkEventKey *event)
{
+ keyDown = false;
+
if (!window)
return false;
@@ -165,6 +175,12 @@ bool Inspector::on_key_press(GdkEventKey *event)
if (!window)
return false;
+ if (keyDown) {
+ return true;
+ }
+
+ keyDown = true;
+
switch (event->keyval) {
case GDK_KEY_z:
case GDK_KEY_F:
@@ -196,9 +212,27 @@ bool Inspector::on_key_press(GdkEventKey *event)
return true;
}
+ keyDown = false;
+
return false;
}
+void Inspector::on_window_hide()
+{
+ windowShowing = false;
+}
+
+bool Inspector::on_inspector_window_state_event(GdkEventWindowState *event)
+{
+ if (!window->get_window() || window->get_window()->gobj() != event->window) {
+ return false;
+ }
+
+ fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
+
+ return true;
+}
+
bool Inspector::on_button_press_event(GdkEventButton *event)
{
if (!window)
@@ -220,13 +254,15 @@ bool Inspector::on_motion_notify_event(GdkEventMotion *event)
return false;
int deviceScale = get_scale_factor();
- int delta_x = (button_pos.x - event->x)*deviceScale;
- int delta_y = (button_pos.y - event->y)*deviceScale;
+ int event_x = round(event->x);
+ int event_y = round(event->y);
+ int delta_x = (button_pos.x - event_x) * deviceScale;
+ int delta_y = (button_pos.y - event_y) * deviceScale;
int imW = currImage->imgBuffer.getWidth();
int imH = currImage->imgBuffer.getHeight();
moveCenter(delta_x, delta_y, imW, imH, deviceScale);
- button_pos.set(event->x, event->y);
+ button_pos.set(event_x, event_y);
if (!dirty) {
dirty = true;
@@ -241,6 +277,8 @@ bool Inspector::on_scroll_event(GdkEventScroll *event)
if (!currImage || !window)
return false;
+ pinned = true;
+
bool alt = event->state & GDK_MOD1_MASK;
int deviceScale = get_scale_factor();
int imW = currImage->imgBuffer.getWidth();
@@ -309,8 +347,8 @@ void Inspector::moveCenter(int delta_x, int delta_y, int imW, int imH, int devic
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));
+ center.set(rtengine::LIM(center.x + delta_x / scale, margin.x, imW - margin.x),
+ rtengine::LIM(center.y + delta_y / scale, margin.y, imH - margin.y));
}
void Inspector::beginZoom(double x, double y)
@@ -326,8 +364,17 @@ void Inspector::beginZoom(double x, double y)
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;
+ double cur_scale = zoomScale;
+ if (scaled) {
+ Glib::RefPtr win = get_window();
+ double winW = win->get_width() * deviceScale;
+ double winH = win->get_height() * deviceScale;
+ int imW = rtengine::max(currImage->imgBuffer.getWidth(), 1);
+ int imH = rtengine::max(currImage->imgBuffer.getHeight(), 1);
+ cur_scale *= rtengine::min(winW / imW, winH / imH);
+ }
+ dcenterBegin.x = (x - window->get_width() / 2.) / cur_scale * deviceScale;
+ dcenterBegin.y = (y - window->get_height() / 2.) / cur_scale * deviceScale;
centerBegin = center;
zoomScaleBegin = zoomScale;
@@ -336,6 +383,7 @@ void Inspector::beginZoom(double x, double y)
void Inspector::on_zoom_begin(GdkEventSequence *s)
{
double x, y;
+ pinned = true;
if (gestureZoom->get_point(s, x, y))
beginZoom(x, y);
}
@@ -378,15 +426,16 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
// this will eventually create/update the off-screen pixmap
// compute the displayed area
- rtengine::Coord availableSize;
- rtengine::Coord topLeft;
- rtengine::Coord dest(0, 0);
+ rtengine::Coord2D availableSize;
+ rtengine::Coord2D topLeft;
+ rtengine::Coord topLeftInt;
+ rtengine::Coord2D dest(0, 0);
int deviceScale = window? get_scale_factor(): 1;
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);
+ scale = rtengine::min(1., rtengine::min(availableSize.x / imW, availableSize.y / imH));
if (scaled) {
// reduce size of image to fit into window, no further zoom down
zoomScale = rtengine::max(zoomScale, 1.0);
@@ -403,33 +452,36 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
if (imW < availableSize.x) {
// center the image in the available space along X
topLeft.x = 0;
- dest.x = (availableSize.x - imW) / 2;
+ dest.x = (availableSize.x - imW) / 2.;
} else {
// partial image display
// double clamp
- topLeft.x = center.x + availableSize.x / 2;
- topLeft.x = rtengine::min(topLeft.x, imW);
+ topLeft.x = center.x + availableSize.x / 2.;
+ topLeft.x = rtengine::min(topLeft.x, imW);
topLeft.x -= availableSize.x;
- topLeft.x = rtengine::max(topLeft.x, 0);
+ topLeft.x = rtengine::max(topLeft.x, 0);
}
if (imH < availableSize.y) {
// center the image in the available space along Y
topLeft.y = 0;
- dest.y = (availableSize.y - imH) / 2;
+ dest.y = (availableSize.y - imH) / 2.;
} else {
// partial image display
// double clamp
- topLeft.y = center.y + availableSize.y / 2;
- topLeft.y = rtengine::min(topLeft.y, imH);
+ topLeft.y = center.y + availableSize.y / 2.;
+ topLeft.y = rtengine::min(topLeft.y, imH);
topLeft.y -= availableSize.y;
- topLeft.y = rtengine::max(topLeft.y, 0);
+ 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);
+ topLeftInt.x = floor(topLeft.x);
+ topLeftInt.y = floor(topLeft.y);
+
// define the destination area
- currImage->imgBuffer.setDrawRectangle(win, dest.x, dest.y, rtengine::min(availableSize.x - dest.x, imW), rtengine::min(availableSize.y - dest.y, imH), false);
- currImage->imgBuffer.setSrcOffset(topLeft.x, topLeft.y);
+ currImage->imgBuffer.setDrawRectangle(win, dest.x, dest.y, rtengine::min(ceil(availableSize.x + (topLeft.x - topLeftInt.x) - 2 * dest.x), imW), rtengine::min(ceil(availableSize.y + (topLeft.y - topLeftInt.y) - 2 * dest.y), imH), false);
+ currImage->imgBuffer.setSrcOffset(topLeftInt.x, topLeftInt.y);
if (!currImage->imgBuffer.surfaceCreated()) {
return false;
@@ -444,15 +496,6 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
// draw the background
style->render_background(cr, 0, 0, get_width(), get_height());
}
- else {
- ///* --- 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 ();
- //*/
- }
bool scaledImage = scale != 1.0;
if (!window || (deviceScale == 1 && !scaledImage)) {
@@ -462,20 +505,26 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
else {
// consider device scale and image scale
if (deviceScale > 1) {
+#ifdef __APPLE__
// use full device resolution and let it scale the image (macOS)
cairo_surface_set_device_scale(cr->get_target()->cobj(), scale, scale);
scaledImage = false;
+#else
+ cr->scale(1. / deviceScale, 1. / deviceScale);
+#endif
}
- 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);
+ int viewW = rtengine::min(imW, ceil(availableSize.x + (topLeft.x - topLeftInt.x)));
+ int viewH = rtengine::min(imH, ceil(availableSize.y + (topLeft.y - topLeftInt.y)));
+ Glib::RefPtr crop = Gdk::Pixbuf::create(currImage->imgBuffer.getSurface(), topLeftInt.x, topLeftInt.y, viewW, viewH);
if (!scaledImage) {
Gdk::Cairo::set_source_pixbuf(cr, crop, dest.x, dest.y);
}
else {
+ double dx = scale * (dest.x + topLeftInt.x - topLeft.x);
+ double dy = scale * (dest.y + topLeftInt.y - topLeft.y);
// 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);
+ crop = crop->scale_simple(round(viewW*scale), round(viewH*scale), Gdk::INTERP_BILINEAR);
+ Gdk::Cairo::set_source_pixbuf(cr, crop, dx, dy);
}
cr->paint();
}
@@ -506,7 +555,7 @@ void Inspector::mouseMove (rtengine::Coord2D pos, int transform)
return;
if (currImage) {
- center.set(int(rtengine::LIM01(pos.x)*double(currImage->imgBuffer.getWidth())), int(rtengine::LIM01(pos.y)*double(currImage->imgBuffer.getHeight())));
+ center.set(rtengine::LIM01(pos.x)*double(currImage->imgBuffer.getWidth()), rtengine::LIM01(pos.y)*double(currImage->imgBuffer.getHeight()));
} else {
center.set(0, 0);
}
diff --git a/rtgui/inspector.h b/rtgui/inspector.h
index 726bc947c..5577bfb45 100644
--- a/rtgui/inspector.h
+++ b/rtgui/inspector.h
@@ -22,7 +22,6 @@
#include "guiutils.h"
-#include "../rtengine/coord.h"
#include "../rtengine/coord2d.h"
class InspectorBuffer
@@ -44,18 +43,20 @@ class Inspector final : public Gtk::DrawingArea
{
private:
- rtengine::Coord center;
+ rtengine::Coord2D center;
std::vector images;
InspectorBuffer* currImage;
bool scaled; // fit image into window
double scale; // current scale
double zoomScale, zoomScaleBegin; // scale during zoom
- rtengine::Coord centerBegin, dcenterBegin; // center during zoom
+ rtengine::Coord2D centerBegin, dcenterBegin; // center during zoom
bool active;
bool pinned;
bool dirty;
bool initialized;
bool fullscreen; // window is shown in fullscreen mode
+ bool keyDown;
+ bool windowShowing;
sigc::connection delayconn;
Glib::ustring next_image_path;
@@ -65,6 +66,9 @@ private:
bool on_key_release(GdkEventKey *event);
bool on_key_press(GdkEventKey *event);
+ void on_window_hide();
+ bool on_inspector_window_state_event(GdkEventWindowState *event);
+
rtengine::Coord button_pos;
bool on_button_press_event(GdkEventButton *event) override;
bool on_motion_notify_event(GdkEventMotion *event) override;
@@ -87,9 +91,15 @@ public:
~Inspector() override;
/** @brief Show or hide window
+ * @param pinned pin window
* @param scaled fit image into window
*/
- void showWindow(bool scaled, bool fullscreen = true);
+ void showWindow(bool pinned, bool scaled = true);
+
+ /**
+ * Hide the window.
+ */
+ void hideWindow();
/** @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
diff --git a/rtgui/perspective.cc b/rtgui/perspective.cc
index c845c272b..d06243524 100644
--- a/rtgui/perspective.cc
+++ b/rtgui/perspective.cc
@@ -792,6 +792,7 @@ void PerspCorrection::linesEditButtonPressed(void)
listener->unsetTweakOperator(this);
listener->refreshPreview(EvPerspRender);
}
+ lines->releaseEdit();
lines->setDrawMode(false);
lines->setActive(false);
if (panel_listener) {
diff --git a/rtgui/rtwindow.cc b/rtgui/rtwindow.cc
index c0042f949..cf77374ce 100644
--- a/rtgui/rtwindow.cc
+++ b/rtgui/rtwindow.cc
@@ -285,6 +285,7 @@ RTWindow::RTWindow ()
property_destroy_with_parent().set_value (false);
signal_window_state_event().connect ( sigc::mem_fun (*this, &RTWindow::on_window_state_event) );
signal_key_press_event().connect ( sigc::mem_fun (*this, &RTWindow::keyPressed) );
+ signal_key_release_event().connect(sigc::mem_fun(*this, &RTWindow::keyReleased));
if (simpleEditor) {
epanel = Gtk::manage ( new EditorPanel (nullptr) );
@@ -756,6 +757,14 @@ bool RTWindow::keyPressed (GdkEventKey* event)
return false;
}
+bool RTWindow::keyReleased(GdkEventKey *event)
+{
+ if (mainNB->get_current_page() == mainNB->page_num(*fpanel)) {
+ return fpanel->handleShortcutKeyRelease(event);
+ }
+ return false;
+}
+
void RTWindow::addBatchQueueJob (BatchQueueEntry* bqe, bool head)
{
diff --git a/rtgui/rtwindow.h b/rtgui/rtwindow.h
index e5e180747..aa1830d89 100644
--- a/rtgui/rtwindow.h
+++ b/rtgui/rtwindow.h
@@ -85,6 +85,7 @@ public:
void addBatchQueueJobs (const std::vector& entries);
bool keyPressed (GdkEventKey* event);
+ bool keyReleased(GdkEventKey *event);
bool on_configure_event (GdkEventConfigure* event) override;
bool on_delete_event (GdkEventAny* event) override;
bool on_window_state_event (GdkEventWindowState* event) override;
diff --git a/rtgui/spot.cc b/rtgui/spot.cc
index 322ffe106..d9a6be6b7 100644
--- a/rtgui/spot.cc
+++ b/rtgui/spot.cc
@@ -29,8 +29,23 @@
using namespace rtengine;
using namespace rtengine::procparams;
-#define STATIC_VISIBLE_OBJ_NBR 6
-#define STATIC_MO_OBJ_NBR 6
+enum GeometryIndex {
+ MO_TARGET_DISK,
+ MO_SOURCE_DISC,
+ MO_TARGET_CIRCLE,
+ MO_SOURCE_CIRCLE,
+ MO_TARGET_FEATHER_CIRCLE,
+ MO_SOURCE_FEATHER_CIRCLE,
+ MO_OBJECT_COUNT,
+
+ VISIBLE_SOURCE_ICON = 0,
+ VISIBLE_SOURCE_FEATHER_CIRCLE,
+ VISIBLE_LINK,
+ VISIBLE_SOURCE_CIRCLE,
+ VISIBLE_TARGET_FEATHER_CIRCLE,
+ VISIBLE_TARGET_CIRCLE,
+ VISIBLE_OBJECT_COUNT
+};
Spot::Spot() :
FoldableToolPanel(this, "spot", M ("TP_SPOT_LABEL"), true, true),
@@ -106,7 +121,7 @@ Spot::~Spot()
{
// delete all dynamically allocated geometry
if (EditSubscriber::visibleGeometry.size()) {
- for (size_t i = 0; i < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i) { // static visible geometry at the end if the list
+ for (size_t i = 0; i < EditSubscriber::visibleGeometry.size() - VISIBLE_OBJECT_COUNT; ++i) { // static visible geometry at the end of the list
delete EditSubscriber::visibleGeometry.at (i);
}
}
@@ -172,6 +187,7 @@ void Spot::resetPressed()
}
} else {
if (!spots.empty()) {
+ EditSubscriber::action = EditSubscriber::Action::NONE;
spots.clear();
activeSpot = -1;
lastObject = -1;
@@ -185,6 +201,25 @@ void Spot::resetPressed()
}
}
+/**
+ * Release anything that's currently being dragged.
+ */
+void Spot::releaseEdit()
+{
+ Geometry *loGeom = getVisibleGeometryFromMO (lastObject);
+
+ EditSubscriber::action = EditSubscriber::Action::NONE;
+
+ if (!loGeom) {
+ return;
+ }
+
+ loGeom->state = Geometry::NORMAL;
+ sourceIcon.state = Geometry::NORMAL;
+ draggedSide = DraggedSide::NONE;
+ updateGeometry();
+}
+
void Spot::setBatchMode (bool batchMode)
{
ToolPanel::setBatchMode (batchMode);
@@ -237,6 +272,7 @@ void Spot::editToggled ()
listener->refreshPreview(EvSpotEnabledOPA); // reprocess the preview w/o creating History entry
subscribe();
} else {
+ releaseEdit();
unsubscribe();
listener->unsetTweakOperator(this);
listener->refreshPreview(EvSpotEnabled); // reprocess the preview w/o creating History entry
@@ -250,16 +286,16 @@ Geometry* Spot::getVisibleGeometryFromMO (int MOID)
return nullptr;
}
- if (MOID == 0) {
+ if (MOID == MO_TARGET_DISK) {
return getActiveSpotIcon();
}
- if (MOID == 1) { // sourceMODisc
+ if (MOID == MO_SOURCE_DISC) {
return &sourceIcon;
}
- if (MOID > STATIC_MO_OBJ_NBR) {
- return EditSubscriber::visibleGeometry.at(MOID - STATIC_MO_OBJ_NBR);
+ if (MOID > MO_OBJECT_COUNT) {
+ return EditSubscriber::visibleGeometry.at(MOID - MO_OBJECT_COUNT);
}
return EditSubscriber::mouseOverGeometry.at (MOID);
@@ -275,30 +311,36 @@ void Spot::createGeometry ()
//printf("CreateGeometry(%d)\n", nbrEntry);
// delete all dynamically allocated geometry
- if (EditSubscriber::visibleGeometry.size() > STATIC_VISIBLE_OBJ_NBR)
- for (size_t i = 0; i < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i) { // static visible geometry at the end if the list
+ if (EditSubscriber::visibleGeometry.size() > VISIBLE_OBJECT_COUNT)
+ for (size_t i = 0; i < EditSubscriber::visibleGeometry.size() - VISIBLE_OBJECT_COUNT; ++i) { // static visible geometry at the end of the list
delete EditSubscriber::visibleGeometry.at (i);
}
// mouse over geometry starts with the static geometry, then the spot's icon geometry
- EditSubscriber::mouseOverGeometry.resize (STATIC_MO_OBJ_NBR + nbrEntry);
+ EditSubscriber::mouseOverGeometry.resize (MO_OBJECT_COUNT + nbrEntry);
// visible geometry starts with the spot's icon geometry, then the static geometry
- EditSubscriber::visibleGeometry.resize (nbrEntry + STATIC_VISIBLE_OBJ_NBR);
+ EditSubscriber::visibleGeometry.resize (nbrEntry + VISIBLE_OBJECT_COUNT);
size_t i = 0, j = 0;
- EditSubscriber::mouseOverGeometry.at (i++) = &targetMODisc; // STATIC_MO_OBJ_NBR + 0
- EditSubscriber::mouseOverGeometry.at (i++) = &sourceMODisc; // STATIC_MO_OBJ_NBR + 1
- EditSubscriber::mouseOverGeometry.at (i++) = &targetCircle; // STATIC_MO_OBJ_NBR + 2
- EditSubscriber::mouseOverGeometry.at (i++) = &sourceCircle; // STATIC_MO_OBJ_NBR + 3
- EditSubscriber::mouseOverGeometry.at (i++) = &targetFeatherCircle; // STATIC_MO_OBJ_NBR + 4
- EditSubscriber::mouseOverGeometry.at (i++) = &sourceFeatherCircle; // STATIC_MO_OBJ_NBR + 5
+ assert(i == MO_TARGET_DISK);
+ EditSubscriber::mouseOverGeometry.at (i++) = &targetMODisc; // MO_OBJECT_COUNT + 0
+ assert(i == MO_SOURCE_DISC);
+ EditSubscriber::mouseOverGeometry.at (i++) = &sourceMODisc; // MO_OBJECT_COUNT + 1
+ assert(i == MO_TARGET_CIRCLE);
+ EditSubscriber::mouseOverGeometry.at (i++) = &targetCircle; // MO_OBJECT_COUNT + 2
+ assert(i == MO_SOURCE_CIRCLE);
+ EditSubscriber::mouseOverGeometry.at (i++) = &sourceCircle; // MO_OBJECT_COUNT + 3
+ assert(i == MO_TARGET_FEATHER_CIRCLE);
+ EditSubscriber::mouseOverGeometry.at (i++) = &targetFeatherCircle; // MO_OBJECT_COUNT + 4
+ assert(i == MO_SOURCE_FEATHER_CIRCLE);
+ EditSubscriber::mouseOverGeometry.at (i++) = &sourceFeatherCircle; // MO_OBJECT_COUNT + 5
// recreate all spots geometry
Cairo::RefPtr normalImg = sourceIcon.getNormalImg();
Cairo::RefPtr prelightImg = sourceIcon.getPrelightImg();
Cairo::RefPtr activeImg = sourceIcon.getActiveImg();
- for (; j < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i, ++j) {
+ for (; j < EditSubscriber::visibleGeometry.size() - VISIBLE_OBJECT_COUNT; ++i, ++j) {
EditSubscriber::mouseOverGeometry.at (i) = EditSubscriber::visibleGeometry.at (j) = new OPIcon (normalImg, activeImg, prelightImg, Cairo::RefPtr (nullptr), Cairo::RefPtr (nullptr), Geometry::DP_CENTERCENTER);
EditSubscriber::visibleGeometry.at (j)->setActive (true);
EditSubscriber::visibleGeometry.at (j)->datum = Geometry::IMAGE;
@@ -306,12 +348,19 @@ void Spot::createGeometry ()
//printf("mouseOverGeometry.at(%d) = %p\n", (unsigned int)i, (void*)EditSubscriber::mouseOverGeometry.at(i));
}
- EditSubscriber::visibleGeometry.at (j++) = &sourceIcon; // STATIC_VISIBLE_OBJ_NBR + 0
- EditSubscriber::visibleGeometry.at (j++) = &sourceFeatherCircle; // STATIC_VISIBLE_OBJ_NBR + 1
- EditSubscriber::visibleGeometry.at (j++) = &link; // STATIC_VISIBLE_OBJ_NBR + 2
- EditSubscriber::visibleGeometry.at (j++) = &sourceCircle; // STATIC_VISIBLE_OBJ_NBR + 3
- EditSubscriber::visibleGeometry.at (j++) = &targetFeatherCircle; // STATIC_VISIBLE_OBJ_NBR + 4
- EditSubscriber::visibleGeometry.at (j++) = &targetCircle; // STATIC_VISIBLE_OBJ_NBR + 5
+ int visibleOffset = j;
+ assert(j - visibleOffset == VISIBLE_SOURCE_ICON);
+ EditSubscriber::visibleGeometry.at (j++) = &sourceIcon; // VISIBLE_OBJECT_COUNT + 0
+ assert(j - visibleOffset == VISIBLE_SOURCE_FEATHER_CIRCLE);
+ EditSubscriber::visibleGeometry.at (j++) = &sourceFeatherCircle; // VISIBLE_OBJECT_COUNT + 1
+ assert(j - visibleOffset == VISIBLE_LINK);
+ EditSubscriber::visibleGeometry.at (j++) = &link; // VISIBLE_OBJECT_COUNT + 2
+ assert(j - visibleOffset == VISIBLE_SOURCE_CIRCLE);
+ EditSubscriber::visibleGeometry.at (j++) = &sourceCircle; // VISIBLE_OBJECT_COUNT + 3
+ assert(j - visibleOffset == VISIBLE_TARGET_FEATHER_CIRCLE);
+ EditSubscriber::visibleGeometry.at (j++) = &targetFeatherCircle; // VISIBLE_OBJECT_COUNT + 4
+ assert(j - visibleOffset == VISIBLE_TARGET_CIRCLE);
+ EditSubscriber::visibleGeometry.at (j++) = &targetCircle; // VISIBLE_OBJECT_COUNT + 5
}
void Spot::updateGeometry()
@@ -419,7 +468,7 @@ void Spot::addNewEntry()
se.sourcePos = se.targetPos;
spots.push_back (se); // this make a copy of se ...
activeSpot = spots.size() - 1;
- lastObject = 1;
+ lastObject = MO_SOURCE_DISC;
//printf("ActiveSpot = %d\n", activeSpot);
@@ -466,11 +515,11 @@ CursorShape Spot::getCursor (int objectID, int xPos, int yPos) const
return CSEmpty;
}
- if (objectID == 0 || objectID == 1) {
+ if (objectID == MO_TARGET_DISK || objectID == MO_SOURCE_DISC) {
return CSMove2D;
}
- if (objectID >= 2 && objectID <= 5) {
- Coord delta(Coord(xPos, yPos) - ((objectID == 3 || objectID == 5) ? spots.at(activeSpot).sourcePos : spots.at(activeSpot).targetPos));
+ if (objectID >= MO_TARGET_CIRCLE && objectID <= MO_SOURCE_FEATHER_CIRCLE) {
+ Coord delta(Coord(xPos, yPos) - ((objectID == MO_SOURCE_CIRCLE || objectID == MO_SOURCE_FEATHER_CIRCLE) ? spots.at(activeSpot).sourcePos : spots.at(activeSpot).targetPos));
PolarCoord polarPos(delta);
if (polarPos.angle < 0.) {
polarPos.angle += 180.;
@@ -508,19 +557,19 @@ bool Spot::mouseOver (int modifierKey)
if (editProvider->getObject() > -1) {
getVisibleGeometryFromMO (editProvider->getObject())->state = Geometry::PRELIGHT;
- if (editProvider->getObject() >= STATIC_MO_OBJ_NBR) {
+ if (editProvider->getObject() >= MO_OBJECT_COUNT) {
// a Spot is being edited
int oldActiveSpot = activeSpot;
- activeSpot = editProvider->getObject() - STATIC_MO_OBJ_NBR;
+ activeSpot = editProvider->getObject() - MO_OBJECT_COUNT;
if (activeSpot != oldActiveSpot) {
if (oldActiveSpot > -1) {
EditSubscriber::visibleGeometry.at (oldActiveSpot)->state = Geometry::NORMAL;
- EditSubscriber::mouseOverGeometry.at (oldActiveSpot + STATIC_MO_OBJ_NBR)->state = Geometry::NORMAL;
+ EditSubscriber::mouseOverGeometry.at (oldActiveSpot + MO_OBJECT_COUNT)->state = Geometry::NORMAL;
}
EditSubscriber::visibleGeometry.at (activeSpot)->state = Geometry::PRELIGHT;
- EditSubscriber::mouseOverGeometry.at (activeSpot + STATIC_MO_OBJ_NBR)->state = Geometry::PRELIGHT;
+ EditSubscriber::mouseOverGeometry.at (activeSpot + MO_OBJECT_COUNT)->state = Geometry::PRELIGHT;
//printf("ActiveSpot = %d (was %d before)\n", activeSpot, oldActiveSpot);
}
}
@@ -529,7 +578,7 @@ bool Spot::mouseOver (int modifierKey)
lastObject = editProvider->getObject();
if (lastObject > -1 && EditSubscriber::mouseOverGeometry.at (lastObject) == getActiveSpotIcon()) {
- lastObject = 0; // targetMODisc
+ lastObject = MO_TARGET_DISK;
}
updateGeometry();
@@ -546,12 +595,18 @@ bool Spot::button1Pressed (int modifierKey)
if (editProvider) {
if (lastObject == -1 && (modifierKey & GDK_CONTROL_MASK)) {
+ int imW, imH;
+ const auto startPos = editProvider->posImage;
+ editProvider->getImageSize(imW, imH);
+ if (startPos.x < 0 || startPos.y < 0 || startPos.x > imW || startPos.y > imH) {
+ return false; // Outside of image area.
+ }
draggedSide = DraggedSide::SOURCE;
addNewEntry();
EditSubscriber::action = EditSubscriber::Action::DRAGGING;
return true;
} else if (lastObject > -1) {
- draggedSide = lastObject == 0 ? DraggedSide::TARGET : lastObject == 1 ? DraggedSide::SOURCE : DraggedSide::NONE;
+ draggedSide = lastObject == MO_TARGET_DISK ? DraggedSide::TARGET : lastObject == MO_SOURCE_DISC ? DraggedSide::SOURCE : DraggedSide::NONE;
getVisibleGeometryFromMO (lastObject)->state = Geometry::DRAGGED;
EditSubscriber::action = EditSubscriber::Action::DRAGGING;
return true;
@@ -603,8 +658,8 @@ bool Spot::button3Pressed (int modifierKey)
return false;
}
- if ((modifierKey & GDK_CONTROL_MASK) && (EditSubscriber::mouseOverGeometry.at (lastObject) == &targetMODisc || lastObject >= STATIC_MO_OBJ_NBR)) {
- lastObject = 1; // sourceMODisc
+ if ((modifierKey & GDK_CONTROL_MASK) && (EditSubscriber::mouseOverGeometry.at (lastObject) == &targetMODisc || lastObject >= MO_OBJECT_COUNT)) {
+ lastObject = MO_SOURCE_DISC;
sourceIcon.state = Geometry::DRAGGED;
EditSubscriber::action = EditSubscriber::Action::DRAGGING;
draggedSide = DraggedSide::SOURCE;
@@ -631,12 +686,14 @@ bool Spot::button3Released()
updateGeometry();
EditSubscriber::action = EditSubscriber::Action::NONE;
return true;
-
- return false;
}
bool Spot::drag1 (int modifierKey)
{
+ if (EditSubscriber::action != EditSubscriber::Action::DRAGGING) {
+ return false;
+ }
+
EditDataProvider *editProvider = getEditProvider();
int imW, imH;
editProvider->getImageSize (imW, imH);
@@ -656,8 +713,8 @@ bool Spot::drag1 (int modifierKey)
modified = true;
}
- EditSubscriber::mouseOverGeometry.at (activeSpot + STATIC_MO_OBJ_NBR)->state = Geometry::DRAGGED;
- } else if (loGeom == &targetMODisc || lastObject >= STATIC_MO_OBJ_NBR) {
+ EditSubscriber::mouseOverGeometry.at (activeSpot + MO_OBJECT_COUNT)->state = Geometry::DRAGGED;
+ } else if (loGeom == &targetMODisc || lastObject >= MO_OBJECT_COUNT) {
//printf("targetMODisc / deltaPrevImage = %d / %d\n", editProvider->deltaPrevImage.x, editProvider->deltaPrevImage.y);
rtengine::Coord currPos = spots.at (activeSpot).targetPos;
spots.at (activeSpot).targetPos += editProvider->deltaPrevImage;
@@ -718,6 +775,10 @@ bool Spot::drag1 (int modifierKey)
bool Spot::drag3 (int modifierKey)
{
+ if (EditSubscriber::action != EditSubscriber::Action::DRAGGING) {
+ return false;
+ }
+
EditDataProvider *editProvider = getEditProvider();
int imW, imH;
editProvider->getImageSize (imW, imH);
diff --git a/rtgui/spot.h b/rtgui/spot.h
index db1fdac05..85cefa4c2 100644
--- a/rtgui/spot.h
+++ b/rtgui/spot.h
@@ -82,6 +82,7 @@ private:
void addNewEntry ();
void deleteSelectedEntry ();
void resetPressed ();
+ void releaseEdit();
protected:
Gtk::Box* labelBox;