Engauge Digitizer 2
Loading...
Searching...
No Matches
GraphicsView.cpp
1/******************************************************************************************************
2 * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3 * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4 * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5 ******************************************************************************************************/
6
7#include "DataKey.h"
8#include "GraphicsItemType.h"
9#include "GraphicsView.h"
10#include "LoadFileInfo.h"
11#include "Logger.h"
12#include "MainWindow.h"
13#include "Point.h"
14#include <QApplication>
15#include <QDebug>
16#include <QDropEvent>
17#include <QGraphicsPixmapItem>
18#include <QGraphicsPolygonItem>
19#include <QGraphicsScene>
20#include <QMimeData>
21#include <QMouseEvent>
22#include <QScrollBar>
23#include "QtToString.h"
24
25extern const QString AXIS_CURVE_NAME;
26
27GraphicsView::GraphicsView(QGraphicsScene *scene,
28 MainWindow &mainWindow) :
29 QGraphicsView (scene)
30{
31 connect (this, SIGNAL (signalContextMenuEvent (QString)), &mainWindow, SLOT (slotContextMenuEvent (QString)));
32 connect (this, SIGNAL (signalDraggedDigFile (QString)), &mainWindow, SLOT (slotFileOpenDraggedDigFile (QString)));
33 connect (this, SIGNAL (signalDraggedImage (QImage)), &mainWindow, SLOT (slotFileImportDraggedImage (QImage)));
34 connect (this, SIGNAL (signalDraggedImageUrl (QUrl)), &mainWindow, SLOT (slotFileImportDraggedImageUrl (QUrl)));
35 connect (this, SIGNAL (signalKeyPress (Qt::Key, bool)), &mainWindow, SLOT (slotKeyPress (Qt::Key, bool)));
36 connect (this, SIGNAL (signalLeave ()), &mainWindow, SLOT (slotLeave ()));
37 connect (this, SIGNAL (signalMouseMove(QPointF)), &mainWindow, SLOT (slotMouseMove (QPointF)));
38 connect (this, SIGNAL (signalMousePress (QPointF)), &mainWindow, SLOT (slotMousePress (QPointF)));
39 connect (this, SIGNAL (signalMouseRelease (QPointF)), &mainWindow, SLOT (slotMouseRelease (QPointF)));
40 connect (this, SIGNAL (signalViewZoomIn ()), &mainWindow, SLOT (slotViewZoomInFromWheelEvent ()));
41 connect (this, SIGNAL (signalViewZoomOut ()), &mainWindow, SLOT (slotViewZoomOutFromWheelEvent ()));
42
43 setMouseTracking (true);
44 setAcceptDrops (true);
45 setEnabled (true);
46 setRenderHints(QPainter::Antialiasing);
47 setBackgroundBrush (QBrush (QColor (Qt::gray)));
48 verticalScrollBar()->setCursor (QCursor (Qt::ArrowCursor));
49 horizontalScrollBar()->setCursor (QCursor (Qt::ArrowCursor));
50
51 // Skip setStatusTip here since that will overwrite much more important messages, and trigger gratuitous showing of status bar
52 setWhatsThis (tr ("Main Window\n\n"
53 "After an image file is imported, or an Engauge Document opened, an image appears in this area. "
54 "Points are added to the image.\n\n"
55 "If the image is a graph with two axes and one or more curves, then three axis points must be "
56 "created along those axes. Just put two axis points on one axis and a third axis point on the other "
57 "axis, as far apart as possible for higher accuracy. Then curve points can be added along the curves.\n\n"
58 "If the image is a map with a scale to define length, then two axis points must be "
59 "created at either end of the scale. Then curve points can be added.\n\n"
60 "Zooming the image in or out is performed using any of several methods:\n"
61 "1) rotating the mouse wheel when the cursor is outside of the image\n"
62 "2) pressing the minus or plus keys\n"
63 "3) selecting a new zoom setting from the View/Zoom menu"));
64}
65
66GraphicsView::~GraphicsView()
67{
68}
69
70void GraphicsView::contextMenuEvent (QContextMenuEvent *event)
71{
72 LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::contextMenuEvent";
73
74 QList<QGraphicsItem*> items = scene()->selectedItems ();
75
76 if (items.count () == 1) {
77
78 QGraphicsItem *item = items.first ();
79 QString pointIdentifier = item->data (DATA_KEY_IDENTIFIER).toString ();
80 GraphicsItemType type = (GraphicsItemType) item->data (DATA_KEY_GRAPHICS_ITEM_TYPE).toInt ();
81 QString curveName = Point::curveNameFromPointIdentifier (pointIdentifier);
82
83 if ((type == GRAPHICS_ITEM_TYPE_POINT) &&
84 (curveName == AXIS_CURVE_NAME)) {
85
86 // A single axis point is selected so edit it
87 emit signalContextMenuEvent (pointIdentifier);
88 event->accept ();
89
90 return;
91 }
92 }
93
94 QGraphicsView::contextMenuEvent (event);
95}
96
97void GraphicsView::dragEnterEvent (QDragEnterEvent *event)
98{
99 LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dragEnterEvent " << (event->mimeData ()->hasUrls () ? "urls" : "non-urls");
100
101 if (event->mimeData ()->hasImage () ||
102 event->mimeData ()->hasUrls ()) {
103 event->acceptProposedAction();
104 }
105}
106
107void GraphicsView::dragMoveEvent (QDragMoveEvent *event)
108{
109 LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dragMoveEvent";
110
111 if (event->mimeData ()->hasImage () ||
112 event->mimeData ()->hasUrls ()) {
113 event->acceptProposedAction();
114 }
115}
116
117void GraphicsView::dropEvent (QDropEvent *event)
118{
119 const QString MIME_FORMAT_TEXT_PLAIN ("text/plain");
120
121 // Urls from text/uri-list
122 QList<QUrl> urlList = event->mimeData ()->urls ();
123 QString urls;
124 QTextStream str (&urls);
125 QList<QUrl>::const_iterator itr;
126 for (itr = urlList.begin (); itr != urlList.end (); itr++) {
127 QUrl url = *itr;
128 str << " url=" << url.toString () << " ";
129 }
130
131 QString textPlain (event->mimeData()->data (MIME_FORMAT_TEXT_PLAIN));
132
133 LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent"
134 << " formats=(" << event->mimeData()->formats().join (", ").toLatin1().data() << ")"
135 << " hasUrls=" << (event->mimeData()->hasUrls() ? "yes" : "no")
136 << " urlCount=" << urlList.count()
137 << " urls=(" << urls.toLatin1().data() << ")"
138 << " text=" << textPlain.toLatin1().data()
139 << " hasImage=" << (event->mimeData()->hasImage() ? "yes" : "no");
140
141 LoadFileInfo loadFileInfo;
142 if (loadFileInfo.loadsAsDigFile (textPlain)) {
143
144 LOG4CPP_INFO_S ((*mainCat)) << "QGraphicsView::dropEvent dig file";
145 QUrl url (textPlain);
146 emit signalDraggedDigFile (url.toLocalFile());
147 event->acceptProposedAction();
148
149 } else if (event->mimeData ()->hasImage ()) {
150
151 // This branch never seems to get executed, but will be kept in case it ever applies (since hasUrls branch is messier and less reliable)
152 QImage image = qvariant_cast<QImage> (event->mimeData ()->imageData ());
153 LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent image";
154 emit signalDraggedImage (image);
155
156 } else if (event->mimeData ()->hasUrls () &&
157 urlList.count () > 0) {
158
159 // Sometimes images can be dragged in, but sometimes the url points to an html page that
160 // contains just the image, in which case importing will fail
161 QUrl url = urlList.at(0);
162 LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent url=" << url.toString ().toLatin1 ().data ();
163 emit signalDraggedImageUrl (url);
164 event->acceptProposedAction();
165
166 } else {
167
168 LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent dropped";
169 QGraphicsView::dropEvent (event);
170
171 }
172}
173
174bool GraphicsView::inBounds (const QPointF &posScreen)
175{
176 QRectF boundingRect = scene()->sceneRect();
177
178 return 0 <= posScreen.x () &&
179 0 <= posScreen.y () &&
180 posScreen.x () < boundingRect.width() &&
181 posScreen.y () < boundingRect.height();
182}
183
184void GraphicsView::keyPressEvent (QKeyEvent *event)
185{
186 LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::keyPressEvent";
187
188 // Intercept up/down/left/right if any items are selected
189 Qt::Key key = (Qt::Key) event->key();
190
191 bool atLeastOneSelectedItem = (scene ()->selectedItems ().count () > 0);
192
193 if (key == Qt::Key_Down ||
194 key == Qt::Key_Left ||
195 key == Qt::Key_Right ||
196 key == Qt::Key_Up) {
197
198 emit signalKeyPress (key, atLeastOneSelectedItem);
199 event->accept();
200
201 } else {
202
203 QGraphicsView::keyPressEvent (event);
204
205 }
206}
207
208void GraphicsView::leaveEvent (QEvent *event)
209{
210 LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::leaveEvent";
211
212 emit signalLeave ();
213
214 QGraphicsView::leaveEvent (event);
215}
216
217void GraphicsView::mouseMoveEvent (QMouseEvent *event)
218{
219// LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mouseMoveEvent cursor="
220// << QtCursorToString (cursor().shape()).toLatin1 ().data ();
221
222 QPointF posScreen = mapToScene (event->pos ());
223
224 if (!inBounds (posScreen)) {
225
226 // Set to out-of-bounds value
227 posScreen = QPointF (-1.0, -1.0);
228 }
229
230 emit signalMouseMove (posScreen);
231
232 QGraphicsView::mouseMoveEvent (event);
233}
234
235void GraphicsView::mousePressEvent (QMouseEvent *event)
236{
237 LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mousePressEvent";
238
239 QPointF posScreen = mapToScene (event->pos ());
240
241 if (!inBounds (posScreen)) {
242
243 // Set to out-of-bounds value
244 posScreen = QPointF (-1.0, -1.0);
245 }
246
247 emit signalMousePress (posScreen);
248
249 QGraphicsView::mousePressEvent (event);
250}
251
252void GraphicsView::mouseReleaseEvent (QMouseEvent *event)
253{
254 LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mouseReleaseEvent signalMouseRelease";
255
256 QPointF posScreen = mapToScene (event->pos ());
257
258 if (!inBounds (posScreen)) {
259
260 // Set to out-of-bounds value
261 posScreen = QPointF (-1.0, -1.0);
262 }
263
264 // Send a signal, unless this is a right click. We still send if out of bounds since
265 // a click-and-drag often ends out of bounds (and user is unlikely to expect different
266 // behavior when endpoint is outside, versus inside, the image boundary)
267 int bitFlag = (event->buttons () & Qt::RightButton);
268 bool isRightClick = (bitFlag != 0);
269
270 if (!isRightClick) {
271
272
273 emit signalMouseRelease (posScreen);
274
275 }
276
277 QGraphicsView::mouseReleaseEvent (event);
278}
279
280void GraphicsView::wheelEvent(QWheelEvent *event)
281{
282 const int ANGLE_THRESHOLD = 15; // From QWheelEvent documentation
283 const int DELTAS_PER_DEGREE = 8; // From QWheelEvent documentation
284
285 QPoint numDegrees = event->angleDelta() / DELTAS_PER_DEGREE;
286
287 LOG4CPP_INFO_S ((*mainCat)) << "MainWindow::wheelEvent"
288 << " degrees=" << numDegrees.y()
289 << " phase=" << event->phase();
290
291 // Criteria:
292 // 1) User has enabled wheel zoom control (but that is not known here so MainWindow will handle that part)
293 // in slotViewZoomInFromWheelEvent and slotViewZoomOutFromWheelEvent
294 // 2) Angle is over a threshold to eliminate false events from just touching wheel
295 if ((event->modifiers() & Qt::ControlModifier) != 0) {
296
297 if (numDegrees.y() >= ANGLE_THRESHOLD) {
298
299 // Rotated backwards towards the user, which means zoom in
300 emit signalViewZoomIn();
301
302 } else if (numDegrees.y() <= -ANGLE_THRESHOLD) {
303
304 // Rotated forwards away from the user, which means zoom out
305 emit signalViewZoomOut();
306
307 }
308
309 // Accept the event as long as Control key was used and we are capturing wheel event
310 event->accept();
311
312 } else {
313
314 // Let non-Control events manage scrolling
315 QGraphicsView::wheelEvent (event);
316
317 }
318}
virtual void mouseReleaseEvent(QMouseEvent *event)
Intercept mouse release events to move one or more Points.
void signalDraggedImage(QImage)
Send dragged image to MainWindow for import. This typically comes from dragging a file.
virtual void wheelEvent(QWheelEvent *event)
Convert wheel events into zoom in/out.
virtual void dropEvent(QDropEvent *event)
Intercept mouse drop event to support drag-and-drop. This initiates asynchronous loading of the dragg...
void signalKeyPress(Qt::Key, bool atLeastOneSelectedItem)
Send keypress to MainWindow for eventual processing by DigitizeStateAbstractBase subclasses.
void signalMouseRelease(QPointF)
Send mouse release to MainWindow for moving Points.
virtual void dragEnterEvent(QDragEnterEvent *event)
Intercept mouse drag event to support drag-and-drop.
void signalContextMenuEvent(QString pointIdentifier)
Send right click on axis point to MainWindow for editing.
virtual void keyPressEvent(QKeyEvent *event)
Intercept key press events to handle left/right/up/down moving.
void signalViewZoomOut()
Send wheel event to MainWindow for zooming out.
void signalLeave()
Send leave to MainWindow for managing the override cursor.
virtual void dragMoveEvent(QDragMoveEvent *event)
Intercept mouse move event to support drag-and-drop.
void signalDraggedDigFile(QString)
Send dragged dig file to MainWindow for import. This comes from dragging an engauge dig file.
virtual void mousePressEvent(QMouseEvent *event)
Intercept mouse press events to create one or more Points.
void signalViewZoomIn()
Send wheel event to MainWindow for zooming in.
GraphicsView(QGraphicsScene *scene, MainWindow &mainWindow)
Single constructor.
virtual void leaveEvent(QEvent *event)
Intercept leave events to manage override cursor.
virtual void mouseMoveEvent(QMouseEvent *event)
Intercept mouse move events to populate the current cursor position in StatusBar.
void signalMousePress(QPointF)
Send mouse press to MainWindow for creating one or more Points.
void signalDraggedImageUrl(QUrl)
Send dragged url to MainWindow for import. This typically comes from dragging an image from a browser...
void signalMouseMove(QPointF)
Send mouse move to MainWindow for eventual display of cursor coordinates in StatusBar.
void contextMenuEvent(QContextMenuEvent *event)
Intercept right click to support point editing.
Returns information about files.
bool loadsAsDigFile(const QString &urlString) const
Returns true if specified file name can be loaded as a DIG file.
Main window consisting of menu, graphics scene, status bar and optional toolbars as a Single Document...
Definition MainWindow.h:78
static QString curveNameFromPointIdentifier(const QString &pointIdentifier)
Parse the curve name from the specified point identifier. This does the opposite of uniqueIdentifierG...
Definition Point.cpp:227