1 /*
2     Scan Tailor - Interactive post-processing tool for scanned pages.
3     Copyright (C) 2007-2009  Joseph Artsimovich <joseph_a@mail.ru>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "ZoneDefaultInteraction.h"
20 #include <QMouseEvent>
21 #include <QPainter>
22 #include <QPainterPath>
23 #include "ImageViewBase.h"
24 #include "SerializableSpline.h"
25 #include "ZoneInteractionContext.h"
26 
ZoneDefaultInteraction(ZoneInteractionContext & context)27 ZoneDefaultInteraction::ZoneDefaultInteraction(ZoneInteractionContext& context)
28     : m_context(context), m_dragHandler(context.imageView()), m_dragWatcher(m_dragHandler) {
29   makeLastFollower(m_dragHandler);
30   m_dragHandler.makeFirstFollower(m_dragWatcher);
31 
32   m_vertexProximity.setProximityStatusTip(tr("Drag the vertex. Hold Ctrl to make the vertex angle right."));
33   m_segmentProximity.setProximityStatusTip(tr("Click to create a new vertex here."));
34   m_zoneAreaProximity.setProximityStatusTip(
35       tr("Right click to edit zone properties. Hold Shift to drag the zone or "
36          "Shift+Ctrl to copy. Press Del to delete this zone."));
37   m_zoneAreaDragProximity.setProximityStatusTip("Hold left mouse button to drag the zone.");
38   m_zoneAreaDragProximity.setProximityCursor(Qt::DragMoveCursor);
39   m_zoneAreaDragCopyProximity.setProximityStatusTip("Hold left mouse button to copy and drag the zone.");
40   m_zoneAreaDragCopyProximity.setProximityCursor(Qt::DragCopyCursor);
41   m_context.imageView().interactionState().setDefaultStatusTip(
42       tr("Click to start creating a new zone. Use Ctrl+Alt+Click to copy the latest created zone."));
43 }
44 
onPaint(QPainter & painter,const InteractionState & interaction)45 void ZoneDefaultInteraction::onPaint(QPainter& painter, const InteractionState& interaction) {
46   painter.setWorldMatrixEnabled(false);
47   painter.setRenderHint(QPainter::Antialiasing);
48 
49   const QTransform to_screen(m_context.imageView().imageToWidget());
50 
51   for (const EditableZoneSet::Zone& zone : m_context.zones()) {
52     const EditableSpline::Ptr& spline = zone.spline();
53     m_visualizer.prepareForSpline(painter, spline);
54     QPolygonF points;
55 
56     if (!interaction.captured() && interaction.proximityLeader(m_vertexProximity)
57         && (spline == m_nearestVertexSpline)) {
58       SplineVertex::Ptr vertex(m_nearestVertex->next(SplineVertex::LOOP));
59       for (; vertex != m_nearestVertex; vertex = vertex->next(SplineVertex::LOOP)) {
60         points.push_back(to_screen.map(vertex->point()));
61       }
62       painter.drawPolyline(points);
63     } else if (!interaction.captured() && interaction.proximityLeader(m_segmentProximity)
64                && (spline == m_nearestSegmentSpline)) {
65       SplineVertex::Ptr vertex(m_nearestSegment.prev);
66       do {
67         vertex = vertex->next(SplineVertex::LOOP);
68         points.push_back(to_screen.map(vertex->point()));
69       } while (vertex != m_nearestSegment.prev);
70       painter.drawPolyline(points);
71     } else {
72       m_visualizer.drawSpline(painter, to_screen, spline);
73     }
74   }
75 
76   if (interaction.proximityLeader(m_vertexProximity)) {
77     // Draw the two adjacent edges in gradient red-to-orange.
78     QLinearGradient gradient;  // From inactive to active point.
79     gradient.setColorAt(0.0, m_visualizer.solidColor());
80     gradient.setColorAt(1.0, m_visualizer.highlightDarkColor());
81 
82     QPen pen(painter.pen());
83 
84     const QPointF prev(to_screen.map(m_nearestVertex->prev(SplineVertex::LOOP)->point()));
85     const QPointF pt(to_screen.map(m_nearestVertex->point()));
86     const QPointF next(to_screen.map(m_nearestVertex->next(SplineVertex::LOOP)->point()));
87 
88     gradient.setStart(prev);
89     gradient.setFinalStop(pt);
90     pen.setBrush(gradient);
91     painter.setPen(pen);
92     painter.drawLine(prev, pt);
93 
94     gradient.setStart(next);
95     pen.setBrush(gradient);
96     painter.setPen(pen);
97     painter.drawLine(next, pt);
98 
99     // Visualize the highlighted vertex.
100     const QPointF screen_vertex(to_screen.map(m_nearestVertex->point()));
101     m_visualizer.drawVertex(painter, screen_vertex, m_visualizer.highlightBrightColor());
102   } else if (interaction.proximityLeader(m_segmentProximity)) {
103     const QLineF line(to_screen.map(m_nearestSegment.toLine()));
104     // Draw the highglighed edge in orange.
105     QPen pen(painter.pen());
106     pen.setColor(m_visualizer.highlightDarkColor());
107     painter.setPen(pen);
108     painter.drawLine(line);
109 
110     m_visualizer.drawVertex(painter, m_screenPointOnSegment, m_visualizer.highlightBrightColor());
111   } else if (!interaction.captured()) {
112     m_visualizer.drawVertex(painter, m_screenMousePos, m_visualizer.solidColor());
113   }
114 }  // ZoneDefaultInteraction::onPaint
115 
onProximityUpdate(const QPointF & mouse_pos,InteractionState & interaction)116 void ZoneDefaultInteraction::onProximityUpdate(const QPointF& mouse_pos, InteractionState& interaction) {
117   m_screenMousePos = mouse_pos;
118 
119   const QTransform to_screen(m_context.imageView().imageToWidget());
120   const QTransform from_screen(m_context.imageView().widgetToImage());
121   const QPointF image_mouse_pos(from_screen.map(mouse_pos));
122 
123   m_nearestVertex.reset();
124   m_nearestVertexSpline.reset();
125   m_nearestSegment = SplineSegment();
126   m_nearestSegmentSpline.reset();
127   m_underCursorSpline.reset();
128 
129   Proximity best_vertex_proximity;
130   Proximity best_segment_proximity;
131 
132   bool has_zone_under_mouse = false;
133 
134   for (const EditableZoneSet::Zone& zone : m_context.zones()) {
135     const EditableSpline::Ptr& spline = zone.spline();
136 
137     {
138       QPainterPath path;
139       path.setFillRule(Qt::WindingFill);
140       path.addPolygon(spline->toPolygon());
141       if (path.contains(image_mouse_pos)) {
142         m_underCursorSpline = spline;
143 
144         has_zone_under_mouse = true;
145       }
146     }
147 
148     // Process vertices.
149     for (SplineVertex::Ptr vert(spline->firstVertex()); vert; vert = vert->next(SplineVertex::NO_LOOP)) {
150       const Proximity proximity(mouse_pos, to_screen.map(vert->point()));
151       if (proximity < best_vertex_proximity) {
152         m_nearestVertex = vert;
153         m_nearestVertexSpline = spline;
154         best_vertex_proximity = proximity;
155       }
156     }
157     // Process segments.
158     for (EditableSpline::SegmentIterator it(*spline); it.hasNext();) {
159       const SplineSegment segment(it.next());
160       const QLineF line(to_screen.map(segment.toLine()));
161       QPointF point_on_segment;
162       const Proximity proximity(Proximity::pointAndLineSegment(mouse_pos, line, &point_on_segment));
163       if (proximity < best_segment_proximity) {
164         m_nearestSegment = segment;
165         m_nearestSegmentSpline = spline;
166         best_segment_proximity = proximity;
167         m_screenPointOnSegment = point_on_segment;
168       }
169     }
170   }
171 
172   interaction.updateProximity(m_vertexProximity, best_vertex_proximity, 2);
173   interaction.updateProximity(m_segmentProximity, best_segment_proximity, 1);
174 
175   if (has_zone_under_mouse) {
176     const Proximity zone_area_proximity(std::min(best_vertex_proximity, best_segment_proximity));
177     interaction.updateProximity(m_zoneAreaProximity, zone_area_proximity, -1, zone_area_proximity);
178     if (m_activeKeyboardModifiers == Qt::ShiftModifier) {
179       interaction.updateProximity(m_zoneAreaDragProximity, Proximity::fromSqDist(0), 0);
180     } else if (m_activeKeyboardModifiers == (Qt::ShiftModifier | Qt::ControlModifier)) {
181       interaction.updateProximity(m_zoneAreaDragCopyProximity, Proximity::fromSqDist(0), 0);
182     }
183   }
184 }  // ZoneDefaultInteraction::onProximityUpdate
185 
onMousePressEvent(QMouseEvent * event,InteractionState & interaction)186 void ZoneDefaultInteraction::onMousePressEvent(QMouseEvent* event, InteractionState& interaction) {
187   if (interaction.captured()) {
188     return;
189   }
190   if (event->button() != Qt::LeftButton) {
191     return;
192   }
193 
194   if (interaction.proximityLeader(m_vertexProximity)) {
195     makePeerPreceeder(*m_context.createVertexDragInteraction(interaction, m_nearestVertexSpline, m_nearestVertex));
196     delete this;
197     event->accept();
198   } else if (interaction.proximityLeader(m_segmentProximity)) {
199     const QTransform from_screen(m_context.imageView().widgetToImage());
200     SplineVertex::Ptr vertex(m_nearestSegment.splitAt(from_screen.map(m_screenPointOnSegment)));
201     makePeerPreceeder(*m_context.createVertexDragInteraction(interaction, m_nearestSegmentSpline, vertex));
202     delete this;
203     event->accept();
204   } else if (interaction.proximityLeader(m_zoneAreaDragProximity)) {
205     makePeerPreceeder(*m_context.createZoneDragInteraction(interaction, m_underCursorSpline));
206     delete this;
207     event->accept();
208   } else if (interaction.proximityLeader(m_zoneAreaDragCopyProximity)) {
209     auto new_spline = make_intrusive<EditableSpline>(SerializableSpline(*m_underCursorSpline));
210     m_context.zones().addZone(new_spline, *m_context.zones().propertiesFor(m_underCursorSpline));
211     makePeerPreceeder(*m_context.createZoneDragInteraction(interaction, new_spline));
212     delete this;
213     event->accept();
214   }
215 }
216 
onMouseReleaseEvent(QMouseEvent * event,InteractionState & interaction)217 void ZoneDefaultInteraction::onMouseReleaseEvent(QMouseEvent* event, InteractionState& interaction) {
218   if (event->button() != Qt::LeftButton) {
219     return;
220   }
221 
222   if (!interaction.captured()) {
223     return;
224   }
225   if (!m_dragHandler.isActive() || m_dragWatcher.haveSignificantDrag()) {
226     return;
227   }
228 
229   if (m_activeKeyboardModifiers == (Qt::ControlModifier | Qt::AltModifier)) {
230     const QTransform from_screen(m_context.imageView().widgetToImage());
231 
232     EditableZoneSet::const_iterator latest_zone = --m_context.zones().end();
233     if (latest_zone != m_context.zones().end()) {
234       SerializableSpline serializable_spline(*(*latest_zone).spline());
235 
236       const QPointF old_center = serializable_spline.toPolygon().boundingRect().center();
237       const QPointF new_center = from_screen.map(event->pos() + QPointF(0.5, 0.5));
238       const QPointF shift = new_center - old_center;
239 
240       serializable_spline = serializable_spline.transformed(QTransform().translate(shift.x(), shift.y()));
241 
242       auto new_spline = make_intrusive<EditableSpline>(serializable_spline);
243       m_context.zones().addZone(new_spline, *(*latest_zone).properties());
244       m_context.zones().commit();
245     }
246 
247     m_context.imageView().update();
248     return;
249   }
250 
251   makePeerPreceeder(*m_context.createZoneCreationInteraction(interaction));
252   delete this;
253   event->accept();
254 }
255 
onMouseMoveEvent(QMouseEvent * event,InteractionState & interaction)256 void ZoneDefaultInteraction::onMouseMoveEvent(QMouseEvent* event, InteractionState& interaction) {
257   const QTransform to_screen(m_context.imageView().imageToWidget());
258 
259   m_screenMousePos = to_screen.map(event->pos() + QPointF(0.5, 0.5));
260   m_context.imageView().update();
261 }
262 
onKeyPressEvent(QKeyEvent * event,InteractionState & interaction)263 void ZoneDefaultInteraction::onKeyPressEvent(QKeyEvent* event, InteractionState& interaction) {
264   m_activeKeyboardModifiers = event->modifiers();
265 }
266 
onKeyReleaseEvent(QKeyEvent * event,InteractionState & interaction)267 void ZoneDefaultInteraction::onKeyReleaseEvent(QKeyEvent* event, InteractionState& interaction) {
268   m_activeKeyboardModifiers = event->modifiers();
269 
270   if (event->key() == Qt::Key_Delete) {
271     if (m_underCursorSpline != nullptr) {
272       m_context.zones().removeZone(m_underCursorSpline);
273       m_context.zones().commit();
274     }
275 
276     m_context.imageView().update();
277   }
278 }
279 
onContextMenuEvent(QContextMenuEvent * event,InteractionState & interaction)280 void ZoneDefaultInteraction::onContextMenuEvent(QContextMenuEvent* event, InteractionState& interaction) {
281   event->accept();
282 
283   InteractionHandler* cm_interaction = m_context.createContextMenuInteraction(interaction);
284   if (!cm_interaction) {
285     return;
286   }
287 
288   makePeerPreceeder(*cm_interaction);
289   delete this;
290 }
291