Add some new features to the On Preview Objects mechanism

This commit is contained in:
Hombre
2015-08-20 02:33:46 +02:00
parent 60ddd563b8
commit c351be41b8
15 changed files with 618 additions and 228 deletions

View File

@@ -24,7 +24,9 @@
#include "editid.h"
#include "cursormanager.h"
#include "../rtengine/rt_math.h"
#include "coord.h"
#include "../rtengine/coord.h"
#include "guiutils.h"
#include "options.h"
class EditDataProvider;
@@ -39,30 +41,117 @@ class EditBuffer;
* Subscribers will be tools that need to create some graphics in the preview area, to let the user interact
* with it in a more user friendly way.
*
* Do not confuse with a _local_ editing, which is another topic implemented in another class. The Edit feature
* Subscribers will be tools that need to create some graphics in the preview area, to let the user interact
* with it in a more user friendly way.
*
* Do not confuse with _local_ editing, which is another topic implemented in another class. The Edit feature
* is also not supported in batch editing from the File Browser.
*
* Each Edit tool must have a unique ID, that will identify them, and which let the ImProcCoordinator or other
* mechanism act as appropriated. They are all defined in rtgui/editid.h.
* Edit tool can be of 2 types: pipette editing and object editing.
*
* ## How does it works?
* ## Pipette edition
*
* When a tool is being constructed, unique IDs are affected to the EditSubscribers. Then the EditorPanel class
* will ask all ToolPanel to register the 'after' preview ImageArea object as data provider. The Subscribers
* have now to provide a toggle button to click on to start the Edit listening. When toggling on, the Subscriber
* By using this class, a pipette mechanism can be handled on the preview.
*
* Each pipette Edit tool must have a unique ID, that will identify them, and which let the ImProcCoordinator
* or other mechanism act as appropriated. They are all defined in rtgui/editid.h. A buffer type has to be given
* too, to know which kind of buffer to allocate (see EditSubscriber::BufferType).
*
* Only the first mouse button can be used to manipulate the pipette on the Preview, that's why the developer has
* to implement at least the following 4 methods:
* - mouseOver
* - button1Pressed
* - drag1
* - button1Released
*
* Actually, only curves does use this class, and everything is handled for curve implementor (as much as possible).
* See the curve's class documentation to see how to implement the curve's pipette feature.
*
* ### Event handling
*
* The mouseOver method is called on each mouse movement, excepted when dragging a point. This method can then access
* the pipetteVal array values, which contain the mean of the pixel read in the buffer, or -1 of the cursor is outside
* of the image. In this case, EditDataProvider::object is also set to 0 (and 1 if over the image).
*
* When the user will click on the left mouse button while pressing the CTRL key, the button1Pressed will be called.
* Setting "dragging" to true (or false) is not required for the pipette type editing.
*
* The drag1 method will be called on all subsequent mouse move. The pipetteVal[3] array will already be filled with
* the mean of the read values under the cursor (actually a fixed square of 8px). If the BufferType is BT_SINGLEPLANE_FLOAT,
* only the first array value will be filled.
*
* Then the button1Released will be called to stop the dragging.
*
* ## Object edition
*
* By using this class, object can be drawn and manipulated on the preview.
*
* The developer has to handle the buttonPress, buttonRelease, drag and mouseOver method that he needs. There
* are buttonPress, buttonRelease and drag methods dedicated to each mouse button, for better flexibility
* (e.g.button2Press, button2Release, drag2 will handle event when mouse button 2 is used first). RT actually
* does not handle multiple mouse button event (e.g. button1 + button2), only one at a time. The first button pressed
* set the mechanism, all other combined button press are ignored.
*
* The developer also have to fill 2 display list with object of the Geometry subclass. Each geometrical shape
* _can_ be used in one or the other, or both list at a time.
*
* The first list (visibleGeometry) is used to be displayed on the preview. The developer will have to set their state
* manually (see Geometry::State), but the display shape, line width and color can be handled automatically, or with
* specific values. To be displayed, the F_VISIBLE flag has to be set through the setActive or setVisible methods.
*
* The second list (mouseOverGeometry) is used in a backbuffer, the color used to draw the shape being the id of the
* mouseOverGeometry. As an example, you could use a circle line to represent the line to the user, but use another
* Circle object, filled, to be used as mouseOver detection. The association between both shape (visible and mouseOver)
* is handled by the developer. To be displayed on this backbuffer, the F_HOVERABLE flag has to be set through the
* setActive or setHoverable methods.
*
*
* ### Event handling
*
* RT will draw in the back buffer all mouseOverGeometry set by the developer once the Edit button is pressed, and handle
* the events automatically.
*
* RT will call the mouseOver method on each mouse movement where no mouse button is pressed.
*
* On mouse button press over a mouseOverGeometry, it will call the button press method corresponding to the button
* (e.g. button1Pressed for mouse button 1), with the modifier key as parameter. Any other mouse button pressed at
* the same time will be ignored. It's up to the developer to decide whether it leads to a drag movement or not,
* by setting the "dragging" boolean to true.
*
* In this case, RT will then sent drag1 event (to stay with our button 1 pressed example) on each mouse movement. It's up
* to the developer of the tool to handle the dragging. The EditProvider class will help you in this by handling the actual
* position in various coordinate system and ways.
*
* When the user will release the mouse button, RT will call the button1Release event (in our example). The developer have
* then to set the "dragging" flag to false.
*
* Each of these methods have to returns a boolean value saying that the preview has to be refreshed or not (i.e. the displayed
* geometry).
*
* ## Other general internal implementation notes
*
* When a tool is being constructed, unique IDs are affected to the EditSubscribers of the Pipette type.
* Then the EditorPanel class will ask all ToolPanel to register the 'after' preview ImageArea object as data provider.
* The Subscribers have now to provide a toggle button to click on to start the Edit listening. When toggling on, the Subscriber
* register itself to the DataProvider, then an event is thrown through the standard ToolPanelListener::panelChanged
* method to update the preview with new graphics to be displayed, and eventually buffers to create and populate.
* method to update the preview with new graphics to be displayed. If a previous Edit button was active, it will be deactivated
* (the Edit buttons are mutually exclusive). For the Pipette type, a buffer will be created and has to be populated
* by the developer in rtengine's pipeline. The unique pipette ID will be used to know where to fill the buffer, as each pipette
* will need different data, corresponding to the state of the image right before the tool that needs pipette values. E.g for
* the HSV tool, the Hue and Saturation and Value curves are applied on the current state of the image. That's why the pipette
* of the H, S and V curve will share the same data of this "current state", otherwise the read value would be wrong.
*
* When the Edit process stops, the Subscriber is removed from the DataProvider, so buffer can be freed up.
* When the mouse button 1 is pressed while pressing the CTRL key, the button1Pressed method will be called.
*
* When the Edit process stops, the Subscriber is removed from the DataProvider, so buffers can be freed up.
* A new ToolPanelListener::panelChanged event is also thrown to update the preview again, without the tool's
* graphical objects. The Edit button is also toggled off (by the user or programmatically).
*
* It means that each Edit buttons toggled on will start an update of the preview which might or might not create
* a new History entry, depending on the event.
* a new History entry, depending on the ProcEvent used.
*
*/
/** @brief Coordinate system where the widgets will be drawn
*
* The EditCoordSystem is used to define a screen and an image coordinate system.
@@ -133,22 +222,69 @@ public:
}
};
class RGBAColor : public RGBColor
{
double a;
public:
RGBAColor () : RGBColor(0., 0., 0.), a(0.) {}
explicit RGBAColor (double r, double g, double b, double a) : RGBColor(r, g, b), a(a) {}
explicit RGBAColor (char r, char g, char b, char a) : RGBColor(r, g, b), a(double(a) / 255.) {}
void setColor(double r, double g, double b, double a)
{
RGBColor::setColor(r, g, b);
this->a = a;
}
void setColor(char r, char g, char b, char a)
{
RGBColor::setColor(r, g, b);
this->a = double(a) / 255.;
}
double getA()
{
return a;
}
};
/// @brief Displayable and MouseOver geometry base class
class Geometry
{
public:
/// @brief Graphical state of the element
enum State {
NORMAL,
PRELIGHT,
DRAGGED
NORMAL, /// Default state
ACTIVE, /// Focused state
PRELIGHT, /// Hovered state
DRAGGED, /// When being dragged
INSENSITIVE /// Displayed but insensitive
};
/// @brief Coordinate space and origin of the point
enum Datum {
IMAGE,
CLICKED_POINT,
CURSOR
IMAGE, /// Image coordinate system with image's top left corner as origin
CLICKED_POINT, /// Screen coordinate system with clicked point as origin
CURSOR /// Screen coordinate system with actual cursor position as origin
};
enum Flags {
ACTIVE = 1 << 0, // true if the geometry is active and have to be drawn
AUTO_COLOR = 1 << 1, // true if the color depend on the state value, not the color field above
F_VISIBLE = 1 << 0, /// true if the geometry have to be drawn on the visible layer
F_HOVERABLE = 1 << 1, /// true if the geometry have to be drawn on the "mouse over" layer
F_AUTO_COLOR = 1 << 2, /// true if the color depend on the state value, not the color field above
};
/// @brief Key point of the image's rectangle that is used to locate the icon copy to the target point:
enum DrivenPoint {
DP_CENTERCENTER,
DP_TOPLEFT,
DP_TOPCENTER,
DP_TOPRIGHT,
DP_CENTERRIGHT,
DP_BOTTOMRIGHT,
DP_BOTTOMCENTER,
DP_BOTTOMLEFT,
DP_CENTERLEFT
};
protected:
@@ -161,29 +297,29 @@ public:
Datum datum;
State state; // set by the Subscriber
Geometry () : innerLineColor(char(255), char(255), char(255)), outerLineColor(char(0), char(0), char(0)), flags(ACTIVE | AUTO_COLOR), innerLineWidth(1.f), datum(IMAGE), state(NORMAL) {}
Geometry () : innerLineColor(char(255), char(255), char(255)), outerLineColor(char(0), char(0), char(0)), flags(F_VISIBLE | F_HOVERABLE | F_AUTO_COLOR), innerLineWidth(1.5f), datum(IMAGE), state(NORMAL) {}
virtual ~Geometry() {}
void setInnerLineColor (double r, double g, double b)
{
innerLineColor.setColor(r, g, b);
flags &= ~AUTO_COLOR;
flags &= ~F_AUTO_COLOR;
}
void setInnerLineColor (char r, char g, char b)
{
innerLineColor.setColor(r, g, b);
flags &= ~AUTO_COLOR;
flags &= ~F_AUTO_COLOR;
}
RGBColor getInnerLineColor ();
void setOuterLineColor (double r, double g, double b)
{
outerLineColor.setColor(r, g, b);
flags &= ~AUTO_COLOR;
flags &= ~F_AUTO_COLOR;
}
void setOuterLineColor (char r, char g, char b)
{
outerLineColor.setColor(r, g, b);
flags &= ~AUTO_COLOR;
flags &= ~F_AUTO_COLOR;
}
RGBColor getOuterLineColor ();
double getOuterLineWidth ()
@@ -197,17 +333,43 @@ public:
void setAutoColor (bool aColor)
{
if (aColor) {
flags |= AUTO_COLOR;
flags |= F_AUTO_COLOR;
} else {
flags &= ~AUTO_COLOR;
flags &= ~F_AUTO_COLOR;
}
}
bool isVisible ()
{
return flags & F_VISIBLE;
}
void setVisible (bool visible)
{
if (visible) {
flags |= F_VISIBLE;
} else {
flags &= ~F_VISIBLE;
}
}
bool isHoverable ()
{
return flags & F_HOVERABLE;
}
void setHoverable (bool visible)
{
if (visible) {
flags |= F_HOVERABLE;
} else {
flags &= ~F_HOVERABLE;
}
}
// setActive will enable/disable the visible and hoverable flags in one shot!
void setActive (bool active)
{
if (active) {
flags |= ACTIVE;
flags |= (F_VISIBLE | F_HOVERABLE);
} else {
flags &= ~ACTIVE;
flags &= ~(F_VISIBLE | F_HOVERABLE);
}
}
@@ -219,13 +381,13 @@ public:
class Circle : public Geometry
{
public:
Coord center;
rtengine::Coord center;
int radius;
bool filled;
bool radiusInImageSpace; /// If true, the radius depend on the image scale; if false, it is a fixed 'screen' size
Circle () : center(100, 100), radius(10), filled(false), radiusInImageSpace(false) {}
Circle (Coord &center, int radius, bool filled = false, bool radiusInImageSpace = false) : center(center), radius(radius), filled(filled), radiusInImageSpace(radiusInImageSpace) {}
Circle (rtengine::Coord &center, int radius, bool filled = false, bool radiusInImageSpace = false) : center(center), radius(radius), filled(filled), radiusInImageSpace(radiusInImageSpace) {}
Circle (int centerX, int centerY, int radius, bool filled = false, bool radiusInImageSpace = false) : center(centerX, centerY), radius(radius), filled(filled), radiusInImageSpace(radiusInImageSpace) {}
void drawOuterGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
@@ -236,11 +398,11 @@ public:
class Line : public Geometry
{
public:
Coord begin;
Coord end;
rtengine::Coord begin;
rtengine::Coord end;
Line () : begin(10, 10), end(100, 100) {}
Line (Coord &begin, Coord &end) : begin(begin), end(end) {}
Line (rtengine::Coord &begin, rtengine::Coord &end) : begin(begin), end(end) {}
Line (int beginX, int beginY, int endX, int endY) : begin(beginX, beginY), end(endX, endY) {}
void drawOuterGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
@@ -251,7 +413,7 @@ public:
class Polyline : public Geometry
{
public:
std::vector<Coord> points;
std::vector<rtengine::Coord> points;
bool filled;
Polyline() : filled(false) {}
@@ -264,16 +426,16 @@ public:
class Rectangle : public Geometry
{
public:
Coord topLeft;
Coord bottomRight;
rtengine::Coord topLeft;
rtengine::Coord bottomRight;
bool filled;
Rectangle() : topLeft(0, 0), bottomRight(10, 10), filled(false) {}
void setXYWH(int left, int top, int width, int height);
void setXYXY(int left, int top, int right, int bottom);
void setXYWH(Coord topLeft, Coord widthHeight);
void setXYXY(Coord topLeft, Coord bottomRight);
void setXYWH(rtengine::Coord topLeft, rtengine::Coord widthHeight);
void setXYXY(rtengine::Coord topLeft, rtengine::Coord bottomRight);
void drawOuterGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
void drawInnerGeometry (Cairo::RefPtr<Cairo::Context> &cr, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
void drawToMOChannel (Cairo::RefPtr<Cairo::Context> &cr, Cairo::RefPtr<Cairo::Context> &cr2, unsigned short id, rtengine::EditBuffer *editBuffer, EditCoordSystem &coordSystem);
@@ -292,8 +454,9 @@ private:
EditDataProvider *provider;
protected:
std::vector<Geometry*> visibleGeometry;
std::vector<Geometry*> mouseOverGeometry;
std::vector<Geometry*> visibleGeometry; /// displayed geometry
std::vector<Geometry*> mouseOverGeometry; /// mouseOver geometry, drawn in a hidden buffer
bool dragging; /// in object mode, set this to true in buttonPressed events to start dragging and ask for drag event (ignored in pipette mode)
public:
EditSubscriber (EditType editType);
@@ -312,6 +475,7 @@ public:
EditUniqueID getEditID();
EditType getEditingType();
BufferType getEditBufferType();
bool isDragging(); /// Returns true if something is being dragged and drag events has to be sent (object mode only)
/** @brief Get the cursor to be displayed when above handles
@param objectID object currently "hovered" */
@@ -331,6 +495,7 @@ public:
}
/** @brief Triggered when mouse button 1 is pressed, together with the CTRL modifier key if the subscriber is of type ET_PIPETTE
Once the key is pressed, RT will enter in drag1 mode on subsequent mouse movements
@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
@return true if the preview has to be redrawn, false otherwise */
virtual bool button1Pressed(int modifierKey)
@@ -345,10 +510,58 @@ public:
return false;
}
/** @brief Triggered when mouse button 2 is pressed (middle button)
Once the key is pressed, RT will enter in drag2 mode on subsequent mouse movements
@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
@return true if the preview has to be redrawn, false otherwise */
virtual bool button2Pressed(int modifierKey)
{
return false;
}
/** @brief Triggered when mouse button 2 is released (middle button)
@return true if the preview has to be redrawn, false otherwise */
virtual bool button2Released()
{
return false;
}
/** @brief Triggered when mouse button 3 is pressed (right button)
Once the key is pressed, RT will enter in drag3 mode on subsequent mouse movements
@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
@return true if the preview has to be redrawn, false otherwise */
virtual bool button3Pressed(int modifierKey)
{
return false;
}
/** @brief Triggered when mouse button 3 is released (right button)
@return true if the preview has to be redrawn, false otherwise */
virtual bool button3Released()
{
return false;
}
/** @brief Triggered when the user is moving while holding down mouse button 1
@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
@return true if the preview has to be redrawn, false otherwise */
virtual bool drag(int modifierKey)
virtual bool drag1(int modifierKey)
{
return false;
}
/** @brief Triggered when the user is moving while holding down mouse button 2
@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
@return true if the preview has to be redrawn, false otherwise */
virtual bool drag2(int modifierKey)
{
return false;
}
/** @brief Triggered when the user is moving while holding down mouse button 3
@param modifierKey Gtk's event modifier key (GDK_CONTROL_MASK | GDK_SHIFT_MASK | ...)
@return true if the preview has to be redrawn, false otherwise */
virtual bool drag3(int modifierKey)
{
return false;
}
@@ -381,12 +594,12 @@ public:
int object; /// ET_OBJECTS mode: Object detected under the cursor, 0 otherwise; ET_PIPETTE mode: 1 if above the image, 0 otherwise
float pipetteVal[3]; /// Current pipette values; if bufferType==BT_SINGLEPLANE_FLOAT, #2 & #3 will be set to 0
Coord posScreen; /// Location of the mouse button press, in preview image space
Coord posImage; /// Location of the mouse button press, in the full image space
Coord deltaScreen; /// Delta relative to posScreen
Coord deltaImage; /// Delta relative to posImage
Coord deltaPrevScreen; /// Delta relative to the previous mouse location, in preview image space
Coord deltaPrevImage; /// Delta relative to the previous mouse location, in the full image space
rtengine::Coord posScreen; /// Location of the mouse button press, in preview image space
rtengine::Coord posImage; /// Location of the mouse button press, in the full image space
rtengine::Coord deltaScreen; /// Delta relative to posScreen
rtengine::Coord deltaImage; /// Delta relative to posImage
rtengine::Coord deltaPrevScreen; /// Delta relative to the previous mouse location, in preview image space
rtengine::Coord deltaPrevImage; /// Delta relative to the previous mouse location, in the full image space
EditDataProvider();
virtual ~EditDataProvider() {}