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 "EngaugeAssert.h"
9 #include "GraphicsItemsExtractor.h"
10 #include "GraphicsItemType.h"
11 #include "GraphicsView.h"
12 #include "LoadFileInfo.h"
13 #include "Logger.h"
14 #include "MainWindow.h"
15 #include "Point.h"
16 #include <QApplication>
17 #include <QContextMenuEvent>
18 #include <QDebug>
19 #include <QDropEvent>
20 #include <QGraphicsPixmapItem>
21 #include <QGraphicsPolygonItem>
22 #include <QGraphicsScene>
23 #include <QMimeData>
24 #include <QMouseEvent>
25 #include <QScrollBar>
26 #include "QtToString.h"
27 
28 extern const QString AXIS_CURVE_NAME;
29 
30 GraphicsView::GraphicsView(QGraphicsScene *scene,
31  MainWindow &mainWindow) :
32  QGraphicsView (scene)
33 {
34  connect (this, SIGNAL (signalContextMenuEventAxis (QString)), &mainWindow, SLOT (slotContextMenuEventAxis (QString)));
35  connect (this, SIGNAL (signalContextMenuEventGraph (QStringList)), &mainWindow, SLOT (slotContextMenuEventGraph (QStringList)));
36  connect (this, SIGNAL (signalDraggedDigFile (QString)), &mainWindow, SLOT (slotFileOpenDraggedDigFile (QString)));
37  connect (this, SIGNAL (signalDraggedImage (QImage)), &mainWindow, SLOT (slotFileImportDraggedImage (QImage)));
38  connect (this, SIGNAL (signalDraggedImageUrl (QUrl)), &mainWindow, SLOT (slotFileImportDraggedImageUrl (QUrl)));
39  connect (this, SIGNAL (signalKeyPress (Qt::Key, bool)), &mainWindow, SLOT (slotKeyPress (Qt::Key, bool)));
40  connect (this, SIGNAL (signalMouseMove(QPointF)), &mainWindow, SLOT (slotMouseMove (QPointF)));
41  connect (this, SIGNAL (signalMousePress (QPointF)), &mainWindow, SLOT (slotMousePress (QPointF)));
42  connect (this, SIGNAL (signalMouseRelease (QPointF)), &mainWindow, SLOT (slotMouseRelease (QPointF)));
43  connect (this, SIGNAL (signalViewZoomIn ()), &mainWindow, SLOT (slotViewZoomInFromWheelEvent ()));
44  connect (this, SIGNAL (signalViewZoomOut ()), &mainWindow, SLOT (slotViewZoomOutFromWheelEvent ()));
45 
46  setMouseTracking (true);
47  setAcceptDrops (true);
48  setEnabled (true);
49  setRenderHints(QPainter::Antialiasing);
50  setBackgroundBrush (QBrush (QColor (Qt::gray)));
51  verticalScrollBar()->setCursor (QCursor (Qt::ArrowCursor));
52  horizontalScrollBar()->setCursor (QCursor (Qt::ArrowCursor));
53 
54  // Skip setStatusTip here since that will overwrite much more important messages, and trigger gratuitous showing of status bar
55  setWhatsThis (tr ("Main Window\n\n"
56  "After an image file is imported, or an Engauge Document opened, an image appears in this area. "
57  "Points are added to the image.\n\n"
58  "If the image is a graph with two axes and one or more curves, then three axis points must be "
59  "created along those axes. Just put two axis points on one axis and a third axis point on the other "
60  "axis, as far apart as possible for higher accuracy. Then curve points can be added along the curves.\n\n"
61  "If the image is a map with a scale to define length, then two axis points must be "
62  "created at either end of the scale. Then curve points can be added.\n\n"
63  "Zooming the image in or out is performed using any of several methods:\n"
64  "1) rotating the mouse wheel when the cursor is outside of the image\n"
65  "2) pressing the minus or plus keys\n"
66  "3) selecting a new zoom setting from the View/Zoom menu"));
67 }
68 
69 GraphicsView::~GraphicsView()
70 {
71 }
72 
73 void GraphicsView::contextMenuEvent(QContextMenuEvent *event)
74 {
75  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::contextMenuEvent"
76  << " selectedCount=" << scene()->selectedItems().count();
77 
78  GraphicsItemsExtractor graphicsItemsExtractor;
79  const QList<QGraphicsItem*> &items = scene()->selectedItems();
80  QStringList pointIdentifiers = graphicsItemsExtractor.selectedPointIdentifiers(items);
81 
82  if (pointIdentifiers.count() > 0) {
83 
84  if (graphicsItemsExtractor.allSelectedItemsAreEitherAxisOrGraph (items,
85  GRAPH_POINTS)) {
86 
87  // One or more graph points are selected so edit their coordinates
88  emit signalContextMenuEventGraph (pointIdentifiers);
89 
90  } else if (graphicsItemsExtractor.allSelectedItemsAreEitherAxisOrGraph (items,
91  AXIS_POINTS) && pointIdentifiers.count() == 1) {
92 
93  // A single axis point is selected so edit it
94  emit signalContextMenuEventAxis (pointIdentifiers.first());
95 
96  }
97  }
98 
99  QGraphicsView::contextMenuEvent (event);
100 }
101 
102 void GraphicsView::dragEnterEvent (QDragEnterEvent *event)
103 {
104  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dragEnterEvent " << (event->mimeData ()->hasUrls () ? "urls" : "non-urls");
105 
106  if (event->mimeData ()->hasImage () ||
107  event->mimeData ()->hasUrls ()) {
108  event->acceptProposedAction();
109  }
110 }
111 
112 void GraphicsView::dragMoveEvent (QDragMoveEvent *event)
113 {
114  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dragMoveEvent";
115 
116  if (event->mimeData ()->hasImage () ||
117  event->mimeData ()->hasUrls ()) {
118  event->acceptProposedAction();
119  }
120 }
121 
122 void GraphicsView::dropEvent (QDropEvent *event)
123 {
124  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent";
125 
126  // Urls from text/uri-list
127  QList<QUrl> urlList = event->mimeData ()->urls ();
128 
129  const QString MIME_FORMAT_TEXT_PLAIN ("text/plain");
130  QString textPlain (event->mimeData()->data (MIME_FORMAT_TEXT_PLAIN));
131 
132  QUrl urlFirst;
133  if (event->mimeData ()->hasUrls () &&
134  urlList.count () > 0) {
135  urlFirst = urlList.at (0);
136  }
137 
138  QImage image;
139  if (event->mimeData()->hasImage()) {
140  image = qvariant_cast<QImage> (event->mimeData ()->imageData ());
141  }
142 
143  if (handleDropEvent (textPlain,
144  event->mimeData ()->hasUrls (),
145  urlFirst,
146  event->mimeData ()->hasImage (),
147  image)) {
148 
149  event->acceptProposedAction();
150 
151  } else {
152 
153  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent dropped";
154  QGraphicsView::dropEvent (event);
155  }
156 }
157 
158 bool GraphicsView::handleDropEvent (const QString &possibleDigFileName,
159  bool hasUrl,
160  const QUrl &urlFirst,
161  bool hasImage,
162  const QImage &image)
163 {
164  bool willAccept = false;
165 
166  LoadFileInfo loadFileInfo;
167  if (loadFileInfo.loadsAsDigFile (possibleDigFileName)) {
168 
169  // Branch that applies when a dig file name has been dropped
170  LOG4CPP_INFO_S ((*mainCat)) << "QGraphicsView::handleDropEvent dig file";
171  QUrl url (possibleDigFileName);
172  emit signalDraggedDigFile (url.toLocalFile());
173  willAccept = true;
174 
175  } else if (hasImage) {
176 
177  // Branch that applies when an image selected within another application (e.g. LibreOffice Draw) has been dropped
178  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::handleDropEvent image";
179  emit signalDraggedImage (image);
180  willAccept = true;
181 
182  } else if (hasUrl) {
183 
184  // Branch that applies when a local file name or internet url has been dropped
185  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::handleDropEvent url=" << urlFirst.toString ().toLatin1 ().data ();
186  emit signalDraggedImageUrl (urlFirst);
187  willAccept = true;
188  }
189 
190  return willAccept;
191 }
192 
193 bool GraphicsView::inBounds (const QPointF &posScreen)
194 {
195  QRectF boundingRect = scene()->sceneRect();
196 
197  return 0 <= posScreen.x () &&
198  0 <= posScreen.y () &&
199  posScreen.x () < boundingRect.width() &&
200  posScreen.y () < boundingRect.height();
201 }
202 
203 void GraphicsView::keyPressEvent (QKeyEvent *event)
204 {
205  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::keyPressEvent";
206 
207  // Intercept up/down/left/right if any items are selected
208  Qt::Key key = static_cast<Qt::Key> (event->key());
209 
210  bool atLeastOneSelectedItem = (scene ()->selectedItems ().count () > 0);
211 
212  if (key == Qt::Key_Down ||
213  key == Qt::Key_Left ||
214  key == Qt::Key_Right ||
215  key == Qt::Key_Up) {
216 
217  emit signalKeyPress (key, atLeastOneSelectedItem);
218  event->accept();
219 
220  } else {
221 
222  QGraphicsView::keyPressEvent (event);
223 
224  }
225 }
226 
227 void GraphicsView::mouseMoveEvent (QMouseEvent *event)
228 {
229 // LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mouseMoveEvent cursor="
230 // << QtCursorToString (cursor().shape()).toLatin1 ().data ();
231 
232  QPointF posScreen = mapToScene (event->pos ());
233 
234  if (!inBounds (posScreen)) {
235 
236  // Set to out-of-bounds value
237  posScreen = QPointF (-1.0, -1.0);
238  }
239 
240  emit signalMouseMove (posScreen);
241 
242  QGraphicsView::mouseMoveEvent (event);
243 }
244 
245 void GraphicsView::mousePressEvent (QMouseEvent *event)
246 {
247  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mousePressEvent";
248 
249  QPointF posScreen = mapToScene (event->pos ());
250 
251  if (!inBounds (posScreen)) {
252 
253  // Set to out-of-bounds value
254  posScreen = QPointF (-1.0, -1.0);
255  }
256 
257  emit signalMousePress (posScreen);
258 
259  QGraphicsView::mousePressEvent (event);
260 }
261 
262 void GraphicsView::mouseReleaseEvent (QMouseEvent *event)
263 {
264  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mouseReleaseEvent signalMouseRelease";
265 
266  QPointF posScreen = mapToScene (event->pos ());
267 
268  if (!inBounds (posScreen)) {
269 
270  // Set to out-of-bounds value
271  posScreen = QPointF (-1.0, -1.0);
272  }
273 
274  // Send a signal, unless this is a right click. We still send if out of bounds since
275  // a click-and-drag often ends out of bounds (and user is unlikely to expect different
276  // behavior when endpoint is outside, versus inside, the image boundary)
277  int bitFlag = (unsigned (event->buttons ()) & Qt::RightButton);
278  bool isRightClick = (bitFlag != 0);
279 
280  if (!isRightClick) {
281 
282  emit signalMouseRelease (posScreen);
283 
284  }
285 
286  QGraphicsView::mouseReleaseEvent (event);
287 }
288 
289 QStringList GraphicsView::pointIdentifiersFromSelection (const QList<QGraphicsItem*> &items) const
290 {
291  // This method assumes that all specified items are points
292 
293  QStringList pointIdentifiers;
294 
295  QList<QGraphicsItem*>::const_iterator itr;
296  for (itr = items.begin(); itr != items.end(); itr++) {
297 
298  QGraphicsItem *item = *itr;
299  GraphicsItemType type = static_cast<GraphicsItemType> (item->data (DATA_KEY_GRAPHICS_ITEM_TYPE).toInt ());
300  ENGAUGE_ASSERT (type == GRAPHICS_ITEM_TYPE_POINT);
301 
302  QString pointIdentifier = item->data (DATA_KEY_IDENTIFIER).toString ();
303  pointIdentifiers << pointIdentifier;
304  }
305 
306  return pointIdentifiers;
307 }
308 
309 void GraphicsView::slotDropRegression (QString urlText)
310 {
311  // Regression test with local file or internet url specified
312  QString emptyDigFileName;
313  bool hasUrl = true;
314  QUrl url (urlText); // Works as is for internet url
315  if (!urlText.contains ("http")) {
316  url = QUrl::fromLocalFile (urlText);
317  }
318  bool hasImage = false;
319  QImage emptyImage;
320 
321  handleDropEvent (emptyDigFileName,
322  hasUrl,
323  url,
324  hasImage,
325  emptyImage);
326 }
327 
328 void GraphicsView::wheelEvent(QWheelEvent *event)
329 {
330  const int ANGLE_THRESHOLD = 15; // From QWheelEvent documentation
331  const int DELTAS_PER_DEGREE = 8; // From QWheelEvent documentation
332 
333  QPoint numDegrees = event->angleDelta() / DELTAS_PER_DEGREE;
334 
335  LOG4CPP_INFO_S ((*mainCat)) << "MainWindow::wheelEvent"
336  << " degrees=" << numDegrees.y()
337  << " phase=" << event->phase();
338 
339  // Criteria:
340  // 1) User has enabled wheel zoom control (but that is not known here so MainWindow will handle that part)
341  // in slotViewZoomInFromWheelEvent and slotViewZoomOutFromWheelEvent
342  // 2) Angle is over a threshold to eliminate false events from just touching wheel
343  if ((event->modifiers() & Qt::ControlModifier) != 0) {
344 
345  if (numDegrees.y() >= ANGLE_THRESHOLD) {
346 
347  // Rotated backwards towards the user, which means zoom in
348  emit signalViewZoomIn();
349 
350  } else if (numDegrees.y() <= -ANGLE_THRESHOLD) {
351 
352  // Rotated forwards away from the user, which means zoom out
353  emit signalViewZoomOut();
354 
355  }
356 
357  // Accept the event as long as Control key was used and we are capturing wheel event
358  event->accept();
359 
360  } else {
361 
362  // Let non-Control events manage scrolling
363  QGraphicsView::wheelEvent (event);
364 
365  }
366 }
void signalMouseMove(QPointF)
Send mouse move to MainWindow for eventual display of cursor coordinates in StatusBar.
Returns information about files.
Definition: LoadFileInfo.h:13
QStringList selectedPointIdentifiers(const QList< QGraphicsItem * > &items) const
Return list of selected point identifiers.
virtual void keyPressEvent(QKeyEvent *event)
Intercept key press events to handle left/right/up/down moving.
bool loadsAsDigFile(const QString &urlString) const
Returns true if specified file name can be loaded as a DIG file.
virtual void wheelEvent(QWheelEvent *event)
Convert wheel events into zoom in/out.
bool allSelectedItemsAreEitherAxisOrGraph(const QList< QGraphicsItem * > &items, AxisOrGraph axisOrGraph) const
Return true if all selected points are of the specified axis or graph type.
This class consolidates utility routines that deal with graphics items that are getting extracted fro...
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...
virtual void contextMenuEvent(QContextMenuEvent *event)
Intercept context event 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.
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.
void signalContextMenuEventAxis(QString pointIdentifier)
Send right click on axis point to MainWindow for editing.
void signalContextMenuEventGraph(QStringList pointIdentifiers)
Send right click on graph point(s) to MainWindow for editing.
void slotDropRegression(QString)
Receive drag and drop regression test url.
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:91