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