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