Engauge Digitizer  2
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 
25 extern const QString AXIS_CURVE_NAME;
26 
27 GraphicsView::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 
66 GraphicsView::~GraphicsView()
67 {
68 }
69 
70 void 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 
97 void 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 
107 void 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 
117 void 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 
174 bool 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 
184 void 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 
208 void GraphicsView::leaveEvent (QEvent *event)
209 {
210  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::leaveEvent";
211 
212  emit signalLeave ();
213 
214  QGraphicsView::leaveEvent (event);
215 }
216 
217 void 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 
235 void 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 
252 void 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 
280 void 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 }
void signalMouseMove(QPointF)
Send mouse move to MainWindow for eventual display of cursor coordinates in StatusBar.
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
Returns information about files.
Definition: LoadFileInfo.h:13
virtual void keyPressEvent(QKeyEvent *event)
Intercept key press events to handle left/right/up/down moving.
virtual void wheelEvent(QWheelEvent *event)
Convert wheel events into zoom in/out.
virtual void dragMoveEvent(QDragMoveEvent *event)
Intercept mouse move event to support drag-and-drop.
virtual void dropEvent(QDropEvent *event)
Intercept mouse drop event to support drag-and-drop. This initiates asynchronous loading of the dragg...
void contextMenuEvent(QContextMenuEvent *event)
Intercept right click to support point editing.
virtual void mouseMoveEvent(QMouseEvent *event)
Intercept mouse move events to populate the current cursor position in StatusBar. ...
GraphicsView(QGraphicsScene *scene, MainWindow &mainWindow)
Single constructor.
void signalMousePress(QPointF)
Send mouse press to MainWindow for creating one or more Points.
void signalKeyPress(Qt::Key, bool atLeastOneSelectedItem)
Send keypress to MainWindow for eventual processing by DigitizeStateAbstractBase subclasses.
virtual void mousePressEvent(QMouseEvent *event)
Intercept mouse press events to create one or more Points.
void signalDraggedImage(QImage)
Send dragged image to MainWindow for import. This typically comes from dragging a file...
virtual void leaveEvent(QEvent *event)
Intercept leave events to manage override cursor.
bool loadsAsDigFile(const QString &urlString) const
Returns true if specified file name can be loaded as a DIG file.
void signalViewZoomIn()
Send wheel event to MainWindow for zooming in.
void signalDraggedDigFile(QString)
Send dragged dig file to MainWindow for import. This comes from dragging an engauge dig file...
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.
virtual void mouseReleaseEvent(QMouseEvent *event)
Intercept mouse release events to move 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 signalViewZoomOut()
Send wheel event to MainWindow for zooming out.
Main window consisting of menu, graphics scene, status bar and optional toolbars as a Single Document...
Definition: MainWindow.h:82
void signalLeave()
Send leave to MainWindow for managing the override cursor.
void signalContextMenuEvent(QString pointIdentifier)
Send right click on axis point to MainWindow for editing.