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;