1 /*
2  *  This file is part of RawTherapee.
3  *
4  *  Copyright (c) 2019 Jean-Christophe FRISCH <natureh.510@gmail.com>
5  *
6  *  RawTherapee is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  RawTherapee is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "edit.h"
21 #include "spot.h"
22 #include "rtimage.h"
23 #include <iomanip>
24 #include "../rtengine/rt_math.h"
25 #include "guiutils.h"
26 #include "eventmapper.h"
27 #include "../rtengine/refreshmap.h"
28 
29 using namespace rtengine;
30 using namespace rtengine::procparams;
31 
32 namespace {
33 
34 constexpr int STATIC_VISIBLE_OBJ_NBR = 6;
35 constexpr int STATIC_MO_OBJ_NBR = 7;
36 
37 constexpr int SOURCE_DISC = 2;
38 constexpr int TARGET_DISC = 1;
39 
40 } // namespace
41 
42 
Spot()43 Spot::Spot() :
44     FoldableToolPanel(this, "spot", M ("TP_SPOT_LABEL"), false, true, true),
45     EditSubscriber(ET_OBJECTS),
46     draggedSide(DraggedSide::NONE),
47     lastObject(-1),
48     activeSpot(-1),
49     sourceIcon("spot-normal.png", "spot-active.png", "spot-prelight.png", "", "", Geometry::DP_CENTERCENTER),
50     targetIcon("spot-normal-target.png", "spot-active-target.png", "spot-prelight-target.png", "", "", Geometry::DP_CENTERCENTER),
51     editedCheckBox(nullptr)
52 {
53     countLabel = Gtk::manage (new Gtk::Label (Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0)));
54 
55     edit = Gtk::manage (new Gtk::ToggleButton());
56     edit->add (*Gtk::manage (new RTImage ("edit-point.png")));
57     editConn = edit->signal_toggled().connect ( sigc::mem_fun (*this, &Spot::editToggled) );
58     edit->set_tooltip_text(M("TP_SPOT_HINT"));
59 
60     reset = Gtk::manage (new Gtk::Button ());
61     reset->add (*Gtk::manage (new RTImage ("undo-small.png")));
62     reset->set_relief (Gtk::RELIEF_NONE);
63     reset->set_border_width (0);
64     reset->signal_clicked().connect ( sigc::mem_fun (*this, &Spot::resetPressed) );
65 
66     labelBox = Gtk::manage (new Gtk::HBox());
67     labelBox->set_spacing (2);
68     labelBox->pack_start (*countLabel, false, false, 0);
69     labelBox->pack_end (*edit, false, false, 0);
70     labelBox->pack_end (*reset, false, false, 0);
71     pack_start (*labelBox);
72 
73     sourceIcon.datum = Geometry::IMAGE;
74     sourceIcon.setActive (false);
75     sourceIcon.state = Geometry::ACTIVE;
76     sourceCircle.datum = Geometry::IMAGE;
77     sourceCircle.setActive (false);
78     sourceCircle.radiusInImageSpace = true;
79     sourceCircle.setDashed(true);
80     sourceMODisc.datum = Geometry::IMAGE;
81     sourceMODisc.setActive (false);
82     sourceMODisc.radiusInImageSpace = true;
83     sourceMODisc.filled = true;
84     sourceMODisc.innerLineWidth = 0.;
85     targetCircle.datum = Geometry::IMAGE;
86     targetCircle.setActive (false);
87     targetCircle.radiusInImageSpace = true;
88     targetMODisc.datum = Geometry::IMAGE;
89     targetMODisc.setActive (false);
90     targetMODisc.radiusInImageSpace = true;
91     targetMODisc.filled = true;
92     targetMODisc.innerLineWidth = 0.;
93     sourceFeatherCircle.datum = Geometry::IMAGE;
94     sourceFeatherCircle.setActive (false);
95     sourceFeatherCircle.radiusInImageSpace = true;
96     sourceFeatherCircle.setDashed(true);
97     sourceFeatherCircle.innerLineWidth = 0.7;
98     targetFeatherCircle.datum = Geometry::IMAGE;
99     targetFeatherCircle.setActive (false);
100     targetFeatherCircle.radiusInImageSpace = true;
101     targetFeatherCircle.innerLineWidth = 0.7;
102     link.datum = Geometry::IMAGE;
103     link.setActive (false);
104 
105     Rectangle *rect = new Rectangle();
106     whole_area_rectangle.reset(rect);
107     rect->filled = true;
108     rect->setActive(true);
109     rect->datum = Geometry::IMAGE;
110 
111     auto m = ProcEventMapper::getInstance();
112     EvSpotEnabled = m->newEvent(ALLNORAW, "TP_SPOT_LABEL");
113     EvSpotEnabledOPA = m->newEvent(SPOTADJUST, "");
114     EvSpotEntry = m->newEvent(SPOTADJUST, "HISTORY_MSG_SPOT_ENTRY");
115     EvSpotEntryOPA = m->newEvent(SPOTADJUST, "HISTORY_MSG_SPOT_ENTRY");
116     EvToolReset.set_action(ALLNORAW);
117 
118     spot_frame = Gtk::manage(new Gtk::Frame(M("TP_SPOT_CUR_SPOT_LABEL")));
119     Gtk::VBox *vb = Gtk::manage(new Gtk::VBox());
120     const auto mkadj =
121         [](const Glib::ustring &lbl, double vmin, double vmax, double vstep, double vdefault) -> Adjuster *
122         {
123             return Gtk::manage(new Adjuster(lbl, vmin, vmax, vstep, vdefault, nullptr, nullptr, nullptr, nullptr, false, true));
124         };
125     source_x = mkadj(M("TP_SPOT_SOURCE_X") + " ", 0, 10000, 1, 0);
126     source_y = mkadj(M("TP_SPOT_SOURCE_Y") + " ", 0, 10000, 1, 0);
127     target_y = mkadj(M("TP_SPOT_TARGET_Y") + " ", 0, 10000, 1, 0);
128     target_x = mkadj(M("TP_SPOT_TARGET_X") + " ", 0, 10000, 1, 0);
129     radius = mkadj(M("TP_SPOT_RADIUS") + " ", SpotParams::minRadius, SpotParams::maxRadius, 1, SpotParams::minRadius);
130     feather = mkadj(M("TP_SPOT_FEATHER") + " ", 0, 1, 0.01, 0);
131     opacity = mkadj(M("TP_SPOT_OPACITY") + " ", 0, 1, 0.01, 0);
132     detail = mkadj(M("TP_SPOT_DETAIL") + " ", 0, 5, 1, 0);
133 
134     spot_adjusters = {
135         source_x,
136         source_y,
137         target_x,
138         target_y,
139         radius,
140         feather,
141         opacity,
142         detail
143     };
144 
145     for (auto a : spot_adjusters) {
146         a->setAdjusterListener(this);
147         vb->pack_start(*a, Gtk::PACK_SHRINK, 2);
148     }
149 
150     spot_frame->add(*vb);
151     pack_start(*spot_frame);
152 
153     getExpander()->signal_button_release_event().connect_notify(sigc::mem_fun(this, &Spot::on_fold));
154     signal_unmap().connect(sigc::mem_fun(*this, &Spot::on_hide));
155 
156     show_all();
157 }
158 
~Spot()159 Spot::~Spot()
160 {
161     // delete all dynamically allocated geometry
162     if (EditSubscriber::visibleGeometry.size()) {
163         for (size_t i = 0; i < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i) { // static visible geometry at the end if the list
164             delete EditSubscriber::visibleGeometry.at (i);
165         }
166     }
167 
168     // We do not delete the mouseOverGeometry, because the referenced objects are either
169     // shared with visibleGeometry or instantiated by the class's ctor
170 }
171 
172 
read(const ProcParams * pp)173 void Spot::read (const ProcParams *pp)
174 {
175     disableListener ();
176 
177     spots = pp->spot.entries;
178     setEnabled (pp->spot.enabled);
179     lastEnabled = pp->spot.enabled;
180     activeSpot = -1;
181     lastObject = -1;
182 
183     createGeometry();
184     updateGeometry();
185 
186     enableListener ();
187 }
188 
189 
write(ProcParams * pp)190 void Spot::write(ProcParams *pp)
191 {
192     pp->spot.enabled = getEnabled();
193     pp->spot.entries = spots;
194 }
195 
196 
resetPressed()197 void Spot::resetPressed()
198 {
199     if (!spots.empty()) {
200         spots.clear();
201         activeSpot = -1;
202         lastObject = -1;
203         createGeometry();
204         updateGeometry();
205 
206         if (listener) {
207             listener->panelChanged (edit->get_active() ? EvSpotEntryOPA : EvSpotEntry, Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), 0));
208         }
209     }
210 }
211 
212 
editedToggled()213 void Spot::editedToggled ()
214 {
215     if (listener) {
216         listener->panelChanged (EvSpotEntry, !editedCheckBox->get_active() ? M ("GENERAL_UNCHANGED") : Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), spots.size()));
217     }
218 }
219 
enabledChanged()220 void Spot::enabledChanged ()
221 {
222     if (listener) {
223         if (get_inconsistent()) {
224             listener->panelChanged (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_UNCHANGED"));
225         } else if (getEnabled()) {
226             listener->panelChanged (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_ENABLED"));
227         } else {
228             listener->panelChanged (edit->get_active() ? EvSpotEnabledOPA : EvSpotEnabled, M ("GENERAL_DISABLED"));
229         }
230     }
231 }
232 
setEditProvider(EditDataProvider * provider)233 void Spot::setEditProvider(EditDataProvider* provider)
234 {
235     EditSubscriber::setEditProvider(provider);
236     if (provider) {
237         int imW, imH;
238         provider->getImageSize(imW, imH);
239 
240         static_cast<Rectangle *>(whole_area_rectangle.get())->bottomRight.x = imW;
241         static_cast<Rectangle *>(whole_area_rectangle.get())->bottomRight.y = imH;
242     }
243 }
244 
editToggled()245 void Spot::editToggled ()
246 {
247     if (listener) {
248         if (edit->get_active()) {
249             listener->setTweakOperator(this);
250             listener->refreshPreview(EvSpotEnabledOPA); // reprocess the preview w/o creating History entry
251             subscribe();
252         } else {
253             reset_adjusters();
254             unsubscribe();
255             listener->unsetTweakOperator(this);
256             listener->refreshPreview(EvSpotEnabled); // reprocess the preview w/o creating History entry
257         }
258     }
259 }
260 
getVisibleGeometryFromMO(int MOID)261 Geometry* Spot::getVisibleGeometryFromMO (int MOID)
262 {
263     if (MOID == -1) {
264         return nullptr;
265     }
266 
267     if (MOID == 1) {
268         return getActiveSpotIcon();
269     }
270 
271     if (MOID == SOURCE_DISC) { // sourceMODisc
272         return &sourceIcon;
273     }
274 
275     if (MOID > STATIC_MO_OBJ_NBR) {
276         return EditSubscriber::visibleGeometry.at(MOID - STATIC_MO_OBJ_NBR);
277     }
278 
279     return EditSubscriber::mouseOverGeometry.at(MOID);
280 }
281 
createGeometry()282 void Spot::createGeometry ()
283 {
284     int nbrEntry = spots.size();
285     countLabel->set_text (Glib::ustring::compose (M ("TP_SPOT_COUNTLABEL"), nbrEntry));
286 
287     //printf("CreateGeometry(%d)\n", nbrEntry);
288     // delete all dynamically allocated geometry
289     if (EditSubscriber::visibleGeometry.size() > STATIC_VISIBLE_OBJ_NBR)
290         for (size_t i = 0; i < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i) { // static visible geometry at the end if the list
291             delete EditSubscriber::visibleGeometry.at (i);
292         }
293 
294     // mouse over geometry starts with the static geometry, then the spot's icon geometry
295     EditSubscriber::mouseOverGeometry.resize (STATIC_MO_OBJ_NBR + nbrEntry);
296     // visible geometry starts with the spot's icon geometry, then the static geometry
297     EditSubscriber::visibleGeometry.resize (nbrEntry + STATIC_VISIBLE_OBJ_NBR);
298 
299     size_t i = 0, j = 0;
300     mouseOverGeometry[i++] = whole_area_rectangle.get();
301     mouseOverGeometry[i++] = &targetMODisc;
302     mouseOverGeometry[i++] = &sourceMODisc;
303     mouseOverGeometry[i++] = &targetCircle;
304     mouseOverGeometry[i++] = &sourceCircle;
305     mouseOverGeometry[i++] = &targetFeatherCircle;
306     mouseOverGeometry[i++] = &sourceFeatherCircle;
307 
308     // recreate all spots geometry
309     Cairo::RefPtr<RTSurface> normalImg   = targetIcon.getNormalImg();
310     Cairo::RefPtr<RTSurface> prelightImg = targetIcon.getPrelightImg();
311     Cairo::RefPtr<RTSurface> activeImg   = targetIcon.getActiveImg();
312 
313     for (; j < EditSubscriber::visibleGeometry.size() - STATIC_VISIBLE_OBJ_NBR; ++i, ++j) {
314         EditSubscriber::mouseOverGeometry.at (i) = EditSubscriber::visibleGeometry.at (j) = new OPIcon (normalImg, activeImg, prelightImg, Cairo::RefPtr<RTSurface> (nullptr), Cairo::RefPtr<RTSurface> (nullptr), Geometry::DP_CENTERCENTER);
315         EditSubscriber::visibleGeometry.at (j)->setActive (true);
316         EditSubscriber::visibleGeometry.at (j)->datum = Geometry::IMAGE;
317         EditSubscriber::visibleGeometry.at (j)->state = Geometry::NORMAL;
318         //printf("mouseOverGeometry.at(%d) = %p\n", (unsigned int)i, (void*)EditSubscriber::mouseOverGeometry.at(i));
319     }
320 
321     visibleGeometry[j++] = &sourceIcon;
322     visibleGeometry[j++] = &sourceFeatherCircle;
323     visibleGeometry[j++] = &link;
324     visibleGeometry[j++] = &sourceCircle;
325     visibleGeometry[j++] = &targetFeatherCircle;
326     visibleGeometry[j++] = &targetCircle;
327 }
328 
updateGeometry()329 void Spot::updateGeometry()
330 {
331     EditDataProvider* dataProvider = getEditProvider();
332 
333     if (dataProvider) {
334         int imW, imH;
335         dataProvider->getImageSize (imW, imH);
336 
337         static_cast<Rectangle *>(whole_area_rectangle.get())->bottomRight.x = imW;
338         static_cast<Rectangle *>(whole_area_rectangle.get())->bottomRight.y = imH;
339         source_x->setLimits(0, imW, 1, 0);
340         target_x->setLimits(0, imW, 1, 0);
341         source_y->setLimits(0, imH, 1, 0);
342         target_y->setLimits(0, imH, 1, 0);
343 
344         if (activeSpot > -1) {
345             // Target point circle
346             targetCircle.center = spots.at (activeSpot).targetPos;
347             targetCircle.radius = spots.at (activeSpot).radius;
348             targetCircle.setActive (true);
349 
350             // Target point Mouse Over disc
351             targetMODisc.center = targetCircle.center;
352             targetMODisc.radius = targetCircle.radius;
353             targetMODisc.setActive (true);
354 
355             // Source point Icon
356             sourceIcon.position = spots.at (activeSpot).sourcePos;
357             sourceIcon.setActive (true);
358 
359             // Source point circle
360             sourceCircle.center = spots.at (activeSpot).sourcePos;
361             sourceCircle.radius = spots.at (activeSpot).radius;
362             sourceCircle.setActive (true);
363 
364             // Source point Mouse Over disc
365             sourceMODisc.center = sourceCircle.center;
366             sourceMODisc.radius = sourceCircle.radius;
367             sourceMODisc.setActive (true);
368 
369             // Target point feather circle
370             targetFeatherCircle.center = spots.at (activeSpot).targetPos;
371             targetFeatherCircle.radius = float (spots.at (activeSpot).radius) * (1.f + spots.at (activeSpot).feather);
372             targetFeatherCircle.radiusInImageSpace = true;
373             targetFeatherCircle.setActive (true);
374 
375             // Source point feather circle
376             sourceFeatherCircle.center = spots.at (activeSpot).sourcePos;
377             sourceFeatherCircle.radius = targetFeatherCircle.radius;
378             sourceFeatherCircle.setActive (true);
379 
380             // Link line
381             PolarCoord p;
382             p = targetCircle.center - sourceCircle.center;
383 
384             if (p.radius > sourceCircle.radius + targetCircle.radius) {
385                 PolarCoord p2 (sourceCircle.radius, p.angle);
386                 Coord p3;
387                 p3 = p2;
388                 link.begin = sourceCircle.center + p3;
389                 p2.set (targetCircle.radius, p.angle + 180);
390                 p3 = p2;
391                 link.end = targetCircle.center + p3;
392                 link.setActive (true);
393             } else {
394                 link.setActive (false);
395             }
396 
397             sourceCircle.setVisible(draggedSide != DraggedSide::SOURCE);
398             targetCircle.setVisible(draggedSide != DraggedSide::TARGET);
399             link.setVisible(draggedSide == DraggedSide::NONE);
400 
401             {
402                 auto &s = spots[activeSpot];
403                 spot_frame->set_sensitive(true);
404                 source_x->setValue(s.sourcePos.x);
405                 source_y->setValue(s.sourcePos.y);
406                 target_x->setValue(s.targetPos.x);
407                 target_y->setValue(s.targetPos.y);
408                 radius->setValue(s.radius);
409                 feather->setValue(s.feather);
410                 opacity->setValue(s.opacity);
411                 detail->setValue(s.detail);
412             }
413         } else {
414             targetCircle.setActive (false);
415             targetMODisc.setActive (false);
416             sourceIcon.setActive (false);
417             sourceCircle.setActive (false);
418             sourceMODisc.setActive (false);
419             targetFeatherCircle.setActive (false);
420             sourceFeatherCircle.setActive (false);
421             link.setActive (false);
422 
423             reset_adjusters();
424         }
425 
426         for (size_t i = 0; i < spots.size(); ++i) {
427             // Target point icon
428             OPIcon* geom = static_cast<OPIcon*> (EditSubscriber::visibleGeometry.at (i));
429             geom->position = spots.at (i).targetPos;
430             geom->setActive (true);
431 
432             if (int (i) == activeSpot) {
433                 geom->setHoverable (false);
434             }
435         }
436     } else {
437         reset_adjusters();
438     }
439 }
440 
441 
getActiveSpotIcon()442 OPIcon *Spot::getActiveSpotIcon()
443 {
444     if (activeSpot > -1) {
445         return static_cast<OPIcon*> (EditSubscriber::visibleGeometry.at (activeSpot));
446     }
447 
448     return nullptr;
449 }
450 
addNewEntry()451 void Spot::addNewEntry()
452 {
453     EditDataProvider* editProvider = getEditProvider();
454     // we create a new entry
455     SpotEntry se;
456     se.targetPos = editProvider->posImage;
457     se.sourcePos = se.targetPos;
458     spots.push_back (se); // this make a copy of se ...
459     activeSpot = spots.size() - 1;
460     lastObject = 1;
461 
462     //printf("ActiveSpot = %d\n", activeSpot);
463 
464     createGeometry();
465     updateGeometry();
466     EditSubscriber::visibleGeometry.at (activeSpot)->state = Geometry::ACTIVE;
467     sourceIcon.state = Geometry::DRAGGED;
468     // TODO: find a way to disable the active spot's Mouse Over geometry but still displaying its location...
469 
470     if (listener) {
471         listener->panelChanged (EvSpotEntryOPA, M ("TP_SPOT_ENTRYCHANGED"));
472     }
473 }
474 
deleteSelectedEntry()475 void Spot::deleteSelectedEntry()
476 {
477     // delete the activeSpot
478     if (activeSpot > -1) {
479         std::vector<rtengine::procparams::SpotEntry>::iterator i = spots.begin();
480 
481         for (int j = 0; j < activeSpot; ++j) {
482             ++i;
483         }
484 
485         spots.erase (i);
486     }
487 
488     lastObject = -1;
489     activeSpot = -1;
490 
491     createGeometry();
492     updateGeometry();
493 
494     if (listener) {
495         listener->panelChanged (EvSpotEntry, M ("TP_SPOT_ENTRYCHANGED"));
496     }
497 }
498 
getCursor(int objectID,int xPos,int yPos)499 CursorShape Spot::getCursor(int objectID, int xPos, int yPos)
500 {
501     EditDataProvider* editProvider = getEditProvider();
502     if (editProvider) {
503         if (draggedSide != DraggedSide::NONE) {
504             return CSEmpty;
505         }
506 
507         if (objectID == TARGET_DISC || objectID == SOURCE_DISC) {
508             return CSMove2D;
509         }
510         if (objectID >= 3 && objectID <= 6 && activeSpot > -1) {
511             Coord delta(Coord(xPos, yPos) - ((objectID == 4 || objectID == 6) ? spots.at(activeSpot).sourcePos : spots.at(activeSpot).targetPos));
512             PolarCoord polarPos(delta);
513             if (polarPos.angle < 0.) {
514                 polarPos.angle += 180.;
515             }
516             if (polarPos.angle < 22.5 || polarPos.angle >= 157.5) {
517                 return CSMove1DH;
518             }
519             if (polarPos.angle < 67.5) {
520                 return CSResizeBottomRight;
521             }
522             if (polarPos.angle < 112.5) {
523                 return CSMove1DV;
524             }
525             return CSResizeBottomLeft;
526         }
527     }
528     return CSCrosshair;
529 }
530 
mouseOver(int modifierKey)531 bool Spot::mouseOver (int modifierKey)
532 {
533     EditDataProvider* editProvider = getEditProvider();
534 
535     if (editProvider && editProvider->getObject() != lastObject) {
536         if (lastObject > -1) {
537             if (EditSubscriber::mouseOverGeometry.at (lastObject) == &targetMODisc) {
538                 getVisibleGeometryFromMO (lastObject)->state = Geometry::ACTIVE;
539             } else {
540                 getVisibleGeometryFromMO (lastObject)->state = Geometry::NORMAL;
541             }
542 
543             sourceIcon.state = Geometry::ACTIVE;
544         }
545 
546         if (editProvider->getObject() > -1) {
547             getVisibleGeometryFromMO (editProvider->getObject())->state = Geometry::PRELIGHT;
548 
549             if (editProvider->getObject() >= STATIC_MO_OBJ_NBR) {
550                 // a Spot is being edited
551                 int oldActiveSpot = activeSpot;
552                 activeSpot = editProvider->getObject() - STATIC_MO_OBJ_NBR;
553 
554                 if (activeSpot != oldActiveSpot) {
555                     if (oldActiveSpot > -1) {
556                         EditSubscriber::visibleGeometry.at (oldActiveSpot)->state = Geometry::NORMAL;
557                         EditSubscriber::mouseOverGeometry.at (oldActiveSpot + STATIC_MO_OBJ_NBR)->state = Geometry::NORMAL;
558                     }
559 
560                     EditSubscriber::visibleGeometry.at (activeSpot)->state = Geometry::PRELIGHT;
561                     EditSubscriber::mouseOverGeometry.at (activeSpot + STATIC_MO_OBJ_NBR)->state = Geometry::PRELIGHT;
562                     //printf("ActiveSpot = %d (was %d before)\n", activeSpot, oldActiveSpot);
563                 }
564             }
565         }
566 
567         lastObject = editProvider->getObject();
568         if (lastObject == 0) {
569             lastObject = -1;
570         }
571 
572         if (lastObject > -1 && EditSubscriber::mouseOverGeometry.at (lastObject) == getActiveSpotIcon()) {
573             lastObject = TARGET_DISC;
574         }
575 
576         updateGeometry();
577         return true;
578     }
579 
580     return false;
581 }
582 
583 // Create a new Target and Source point or start the drag of a Target point under the cursor
button1Pressed(int modifierKey)584 bool Spot::button1Pressed (int modifierKey)
585 {
586     EditDataProvider* editProvider = getEditProvider();
587 
588     if (editProvider) {
589         if (lastObject == -1 && (modifierKey & GDK_CONTROL_MASK)) {
590             draggedSide = DraggedSide::SOURCE;
591             addNewEntry();
592             EditSubscriber::action = ES_ACTION_DRAGGING;
593             return true;
594         } else if (lastObject > -1) {
595             draggedSide = lastObject == TARGET_DISC ? DraggedSide::TARGET : lastObject == SOURCE_DISC ? DraggedSide::SOURCE : DraggedSide::NONE;
596             getVisibleGeometryFromMO (lastObject)->state = Geometry::DRAGGED;
597             EditSubscriber::action = ES_ACTION_DRAGGING;
598             return true;
599         }
600     }
601 
602     return false;
603 }
604 
605 // End the drag of a Target point
button1Released()606 bool Spot::button1Released()
607 {
608     Geometry *loGeom = getVisibleGeometryFromMO (lastObject);
609 
610     if (!loGeom) {
611         EditSubscriber::action = ES_ACTION_NONE;
612         return false;
613     }
614 
615     loGeom->state = Geometry::PRELIGHT;
616     EditSubscriber::action = ES_ACTION_NONE;
617     draggedSide = DraggedSide::NONE;
618     updateGeometry();
619     return true;
620 }
621 
622 // Delete a point
button2Pressed(int modifierKey)623 bool Spot::button2Pressed (int modifierKey)
624 {
625     EditDataProvider* editProvider = getEditProvider();
626 
627     if (!editProvider || lastObject == -1 || activeSpot == -1) {
628         return false;
629     }
630 
631     if (! (modifierKey & (GDK_SHIFT_MASK | GDK_SHIFT_MASK))) {
632         EditSubscriber::action = ES_ACTION_PICKING;
633     }
634 
635     return false;
636 }
637 
638 // Create a new Target and Source point or start the drag of a Target point under the cursor
button3Pressed(int modifierKey)639 bool Spot::button3Pressed (int modifierKey)
640 {
641     EditDataProvider* editProvider = getEditProvider();
642 
643     if (!editProvider || lastObject == -1 || activeSpot == -1) {
644         return false;
645     }
646 
647     if ((modifierKey & GDK_CONTROL_MASK) && (EditSubscriber::mouseOverGeometry.at (lastObject) == &targetMODisc || lastObject >= STATIC_MO_OBJ_NBR)) {
648         lastObject = SOURCE_DISC;
649         sourceIcon.state = Geometry::DRAGGED;
650         EditSubscriber::action = ES_ACTION_DRAGGING;
651         draggedSide = DraggedSide::SOURCE;
652         return true;
653     } else if (! (modifierKey & (GDK_SHIFT_MASK | GDK_SHIFT_MASK))) {
654         EditSubscriber::action = ES_ACTION_PICKING;
655     }
656 
657     return false;
658 }
659 
button3Released()660 bool Spot::button3Released()
661 {
662     Geometry *loGeom = getVisibleGeometryFromMO (lastObject);
663 
664     if (!loGeom) {
665         EditSubscriber::action = ES_ACTION_NONE;
666         return false;
667     }
668 
669     lastObject = -1;
670     sourceIcon.state = Geometry::ACTIVE;
671     draggedSide = DraggedSide::NONE;
672     updateGeometry();
673     EditSubscriber::action = ES_ACTION_NONE;
674     return true;
675 
676     return false;
677 }
678 
drag1(int modifierKey)679 bool Spot::drag1 (int modifierKey)
680 {
681     EditDataProvider *editProvider = getEditProvider();
682     int imW, imH;
683     editProvider->getImageSize (imW, imH);
684     bool modified = false;
685 
686     //printf("Drag1 / LastObject=%d\n", lastObject);
687 
688     Geometry *loGeom = EditSubscriber::mouseOverGeometry.at (lastObject);
689 
690     if (loGeom == &sourceMODisc) {
691         //printf("sourceMODisc / deltaPrevImage = %d / %d\n", editProvider->deltaPrevImage.x, editProvider->deltaPrevImage.y);
692         rtengine::Coord currPos = spots.at (activeSpot).sourcePos;
693         spots.at (activeSpot).sourcePos += editProvider->deltaPrevImage;
694         spots.at (activeSpot).sourcePos.clip (imW, imH);
695 
696         if (spots.at (activeSpot).sourcePos != currPos) {
697             modified = true;
698         }
699 
700         EditSubscriber::mouseOverGeometry.at (activeSpot + STATIC_MO_OBJ_NBR)->state = Geometry::DRAGGED;
701     } else if (loGeom == &targetMODisc || lastObject >= STATIC_MO_OBJ_NBR) {
702         //printf("targetMODisc / deltaPrevImage = %d / %d\n", editProvider->deltaPrevImage.x, editProvider->deltaPrevImage.y);
703         rtengine::Coord currPos = spots.at (activeSpot).targetPos;
704         spots.at (activeSpot).targetPos += editProvider->deltaPrevImage;
705         spots.at (activeSpot).targetPos.clip (imW, imH);
706 
707         if (spots.at (activeSpot).targetPos != currPos) {
708             modified = true;
709         }
710     } else if (loGeom == &sourceCircle) {
711         //printf("sourceCircle / deltaPrevImage = %d / %d\n", editProvider->deltaImage.x, editProvider->deltaImage.y);
712         int lastRadius = spots.at (activeSpot).radius;
713         rtengine::Coord currPos = editProvider->posImage + editProvider->deltaImage;
714         rtengine::PolarCoord currPolar (currPos - spots.at (activeSpot).sourcePos);
715         spots.at (activeSpot).radius = LIM<int> (int (currPolar.radius), SpotParams::minRadius, SpotParams::maxRadius);
716 
717         if (spots.at (activeSpot).radius != lastRadius) {
718             modified = true;
719         }
720     } else if (loGeom == &targetCircle) {
721         //printf("targetCircle / deltaPrevImage = %d / %d\n", editProvider->deltaImage.x, editProvider->deltaImage.y);
722         int lastRadius = spots.at (activeSpot).radius;
723         rtengine::Coord currPos = editProvider->posImage + editProvider->deltaImage;
724         rtengine::PolarCoord currPolar (currPos - spots.at (activeSpot).targetPos);
725         spots.at (activeSpot).radius = LIM<int> (int (currPolar.radius), SpotParams::minRadius, SpotParams::maxRadius);
726 
727         if (spots.at (activeSpot).radius != lastRadius) {
728             modified = true;
729         }
730     } else if (loGeom == &sourceFeatherCircle) {
731         //printf("sourceFeatherCircle / deltaPrevImage = %d / %d\n", editProvider->deltaImage.x, editProvider->deltaImage.y);
732         float currFeather = spots.at (activeSpot).feather;
733         rtengine::Coord currPos = editProvider->posImage + editProvider->deltaImage;
734         rtengine::PolarCoord currPolar (currPos - spots.at (activeSpot).sourcePos);
735         spots.at (activeSpot).feather = LIM01<float> ((currPolar.radius - double (spots.at (activeSpot).radius)) / double (spots.at (activeSpot).radius));
736 
737         if (spots.at (activeSpot).feather != currFeather) {
738             modified = true;
739         }
740     } else if (loGeom == &targetFeatherCircle) {
741         //printf("targetFeatherCircle / deltaPrevImage = %d / %d\n", editProvider->deltaImage.x, editProvider->deltaImage.y);
742         float currFeather = spots.at (activeSpot).feather;
743         rtengine::Coord currPos = editProvider->posImage + editProvider->deltaImage;
744         rtengine::PolarCoord currPolar (currPos - spots.at (activeSpot).targetPos);
745         spots.at (activeSpot).feather = LIM01<float> ((currPolar.radius - double (spots.at (activeSpot).radius)) / double (spots.at (activeSpot).radius));
746 
747         if (spots.at (activeSpot).feather != currFeather) {
748             modified = true;
749         }
750     }
751 
752     if (listener && modified) {
753         updateGeometry();
754         listener->panelChanged (EvSpotEntry, M ("TP_SPOT_ENTRYCHANGED"));
755     }
756 
757     return modified;
758 }
759 
drag3(int modifierKey)760 bool Spot::drag3 (int modifierKey)
761 {
762     EditDataProvider *editProvider = getEditProvider();
763     int imW, imH;
764     editProvider->getImageSize (imW, imH);
765     bool modified = false;
766 
767     Geometry *loGeom = EditSubscriber::mouseOverGeometry.at (lastObject);
768 
769     if (loGeom == &sourceMODisc) {
770         rtengine::Coord currPos = spots.at (activeSpot).sourcePos;
771         spots.at (activeSpot).sourcePos += editProvider->deltaPrevImage;
772         spots.at (activeSpot).sourcePos.clip (imW, imH);
773 
774         if (spots.at (activeSpot).sourcePos != currPos) {
775             modified = true;
776         }
777     }
778 
779     if (listener) {
780         updateGeometry();
781         listener->panelChanged (EvSpotEntry, M ("TP_SPOT_ENTRYCHANGED"));
782     }
783 
784     return modified;
785 }
786 
pick2(bool picked)787 bool Spot::pick2 (bool picked)
788 {
789     return pick3 (picked);
790 }
791 
pick3(bool picked)792 bool Spot::pick3 (bool picked)
793 {
794     EditDataProvider* editProvider = getEditProvider();
795 
796     if (!picked) {
797         if (editProvider->getObject() != lastObject) {
798             return false;
799         }
800     }
801 
802     // Object is picked, we delete it
803     deleteSelectedEntry();
804     EditSubscriber::action = ES_ACTION_NONE;
805     updateGeometry();
806     return true;
807 }
808 
809 
switchOffEditMode()810 void Spot::switchOffEditMode ()
811 {
812     if (edit->get_active()) {
813         // switching off the toggle button
814         bool wasBlocked = editConn.block (true);
815         edit->set_active (false);
816 
817         if (!wasBlocked) {
818             editConn.block (false);
819         }
820     }
821 
822     reset_adjusters();
823 
824     EditSubscriber::switchOffEditMode();  // disconnect
825     listener->unsetTweakOperator(this);
826     listener->refreshPreview(EvSpotEnabled); // reprocess the preview w/o creating History entry
827 }
828 
829 
tweakParams(procparams::ProcParams & pparams)830 void Spot::tweakParams(procparams::ProcParams& pparams)
831 {
832     //params->raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST);
833     //params->raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST);
834 
835     // -> disabling all transform
836     //pparams.coarse = CoarseTransformParams();
837     pparams.lensProf = LensProfParams();
838     pparams.cacorrection = CACorrParams();
839     pparams.distortion = DistortionParams();
840     pparams.rotate = RotateParams();
841     pparams.perspective = PerspectiveParams();
842     pparams.vignetting = VignettingParams();
843 
844     // -> disabling standard crop
845     pparams.crop.enabled = false;
846 
847 //     // -> disabling time consuming and unnecessary tool
848 // //    pparams.sh.enabled = false;
849 //     pparams.blackwhite.enabled = false;
850 //     pparams.dehaze.enabled = false;
851 // //    pparams.wavelet.enabled = false;
852 //     pparams.filmSimulation.enabled = false;
853 // //    pparams.sharpenEdge.enabled = false;
854 // //    pparams.sharpenMicro.enabled = false;
855 //     pparams.sharpening.enabled = false;
856 //     pparams.softlight.enabled = false;
857 //     pparams.gradient.enabled = false;
858 //     pparams.pcvignette.enabled = false;
859 // //    pparams.colorappearance.enabled = false;
860 }
861 
862 
adjusterChanged(Adjuster * a,double newval)863 void Spot::adjusterChanged(Adjuster *a, double newval)
864 {
865     if (activeSpot > -1) {
866         auto &s = spots[activeSpot];
867         s.sourcePos.x = source_x->getValue();
868         s.sourcePos.y = source_y->getValue();
869         s.targetPos.x = target_x->getValue();
870         s.targetPos.y = target_y->getValue();
871         s.radius = radius->getValue();
872         s.feather = feather->getValue();
873         s.opacity = opacity->getValue();
874         s.detail = detail->getValue();
875     }
876 
877     if (listener && getEnabled()) {
878         disableListener();
879         updateGeometry();
880         enableListener();
881 
882         listener->panelChanged(EvSpotEntry, M("TP_SPOT_ENTRYCHANGED"));
883     }
884 }
885 
886 
reset_adjusters()887 void Spot::reset_adjusters()
888 {
889     for (auto a : spot_adjusters) {
890         a->block(true);
891         a->resetValue(true);
892         a->block(false);
893     }
894     spot_frame->set_sensitive(false);
895 }
896 
897 
setDefaults(const ProcParams * def)898 void Spot::setDefaults(const ProcParams *def)
899 {
900     initial_params = def->spot;
901 }
902 
903 
toolReset(bool to_initial)904 void Spot::toolReset(bool to_initial)
905 {
906     ProcParams pp;
907     if (to_initial) {
908         pp.spot = initial_params;
909     }
910     pp.spot.enabled = getEnabled();
911     read(&pp);
912 }
913 
914 
on_fold(GdkEventButton * evt)915 void Spot::on_fold(GdkEventButton *evt)
916 {
917     if (isCurrentSubscriber()) {
918         switchOffEditMode();
919     }
920 }
921 
922 
on_hide()923 void Spot::on_hide()
924 {
925     if (isCurrentSubscriber()) {
926         switchOffEditMode();
927     }
928 }
929