Engauge Digitizer  2
 All Classes Functions Variables Typedefs Enumerations Friends Pages
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  const QString MIME_FORMAT_TEXT_PLAIN ("text/plain");
125 
126  // Urls from text/uri-list
127  QList<QUrl> urlList = event->mimeData ()->urls ();
128  QString urls;
129  QTextStream str (&urls);
130  QList<QUrl>::const_iterator itr;
131  for (itr = urlList.begin (); itr != urlList.end (); itr++) {
132  QUrl url = *itr;
133  str << " url=" << url.toString () << " ";
134  }
135 
136  QString textPlain (event->mimeData()->data (MIME_FORMAT_TEXT_PLAIN));
137 
138  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent"
139  << " formats=(" << event->mimeData()->formats().join (", ").toLatin1().data() << ")"
140  << " hasUrls=" << (event->mimeData()->hasUrls() ? "yes" : "no")
141  << " urlCount=" << urlList.count()
142  << " urls=(" << urls.toLatin1().data() << ")"
143  << " text=" << textPlain.toLatin1().data()
144  << " hasImage=" << (event->mimeData()->hasImage() ? "yes" : "no");
145 
146  LoadFileInfo loadFileInfo;
147  if (loadFileInfo.loadsAsDigFile (textPlain)) {
148 
149  LOG4CPP_INFO_S ((*mainCat)) << "QGraphicsView::dropEvent dig file";
150  QUrl url (textPlain);
151  emit signalDraggedDigFile (url.toLocalFile());
152  event->acceptProposedAction();
153 
154  } else if (event->mimeData ()->hasImage ()) {
155 
156  // This branch never seems to get executed, but will be kept in case it ever applies (since hasUrls branch is messier and less reliable)
157  QImage image = qvariant_cast<QImage> (event->mimeData ()->imageData ());
158  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent image";
159  emit signalDraggedImage (image);
160 
161  } else if (event->mimeData ()->hasUrls () &&
162  urlList.count () > 0) {
163 
164  // Sometimes images can be dragged in, but sometimes the url points to an html page that
165  // contains just the image, in which case importing will fail
166  QUrl url = urlList.at(0);
167  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent url=" << url.toString ().toLatin1 ().data ();
168  emit signalDraggedImageUrl (url);
169  event->acceptProposedAction();
170 
171  } else {
172 
173  LOG4CPP_INFO_S ((*mainCat)) << "GraphicsView::dropEvent dropped";
174  QGraphicsView::dropEvent (event);
175 
176  }
177 }
178 
179 bool GraphicsView::inBounds (const QPointF &posScreen)
180 {
181  QRectF boundingRect = scene()->sceneRect();
182 
183  return 0 <= posScreen.x () &&
184  0 <= posScreen.y () &&
185  posScreen.x () < boundingRect.width() &&
186  posScreen.y () < boundingRect.height();
187 }
188 
189 void GraphicsView::keyPressEvent (QKeyEvent *event)
190 {
191  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::keyPressEvent";
192 
193  // Intercept up/down/left/right if any items are selected
194  Qt::Key key = (Qt::Key) event->key();
195 
196  bool atLeastOneSelectedItem = (scene ()->selectedItems ().count () > 0);
197 
198  if (key == Qt::Key_Down ||
199  key == Qt::Key_Left ||
200  key == Qt::Key_Right ||
201  key == Qt::Key_Up) {
202 
203  emit signalKeyPress (key, atLeastOneSelectedItem);
204  event->accept();
205 
206  } else {
207 
208  QGraphicsView::keyPressEvent (event);
209 
210  }
211 }
212 
213 void GraphicsView::mouseMoveEvent (QMouseEvent *event)
214 {
215 // LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mouseMoveEvent cursor="
216 // << QtCursorToString (cursor().shape()).toLatin1 ().data ();
217 
218  QPointF posScreen = mapToScene (event->pos ());
219 
220  if (!inBounds (posScreen)) {
221 
222  // Set to out-of-bounds value
223  posScreen = QPointF (-1.0, -1.0);
224  }
225 
226  emit signalMouseMove (posScreen);
227 
228  QGraphicsView::mouseMoveEvent (event);
229 }
230 
231 void GraphicsView::mousePressEvent (QMouseEvent *event)
232 {
233  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mousePressEvent";
234 
235  QPointF posScreen = mapToScene (event->pos ());
236 
237  if (!inBounds (posScreen)) {
238 
239  // Set to out-of-bounds value
240  posScreen = QPointF (-1.0, -1.0);
241  }
242 
243  emit signalMousePress (posScreen);
244 
245  QGraphicsView::mousePressEvent (event);
246 }
247 
248 void GraphicsView::mouseReleaseEvent (QMouseEvent *event)
249 {
250  LOG4CPP_DEBUG_S ((*mainCat)) << "GraphicsView::mouseReleaseEvent signalMouseRelease";
251 
252  QPointF posScreen = mapToScene (event->pos ());
253 
254  if (!inBounds (posScreen)) {
255 
256  // Set to out-of-bounds value
257  posScreen = QPointF (-1.0, -1.0);
258  }
259 
260  // Send a signal, unless this is a right click. We still send if out of bounds since
261  // a click-and-drag often ends out of bounds (and user is unlikely to expect different
262  // behavior when endpoint is outside, versus inside, the image boundary)
263  int bitFlag = (event->buttons () & Qt::RightButton);
264  bool isRightClick = (bitFlag != 0);
265 
266  if (!isRightClick) {
267 
268  emit signalMouseRelease (posScreen);
269 
270  }
271 
272  QGraphicsView::mouseReleaseEvent (event);
273 }
274 
275 QStringList GraphicsView::pointIdentifiersFromSelection (const QList<QGraphicsItem*> &items) const
276 {
277  // This method assumes that all specified items are points
278 
279  QStringList pointIdentifiers;
280 
281  QList<QGraphicsItem*>::const_iterator itr;
282  for (itr = items.begin(); itr != items.end(); itr++) {
283 
284  QGraphicsItem *item = *itr;
285  GraphicsItemType type = (GraphicsItemType) item->data (DATA_KEY_GRAPHICS_ITEM_TYPE).toInt ();
286  ENGAUGE_ASSERT (type == GRAPHICS_ITEM_TYPE_POINT);
287 
288  QString pointIdentifier = item->data (DATA_KEY_IDENTIFIER).toString ();
289  pointIdentifiers << pointIdentifier;
290  }
291 
292  return pointIdentifiers;
293 }
294 
295 void GraphicsView::wheelEvent(QWheelEvent *event)
296 {
297  const int ANGLE_THRESHOLD = 15; // From QWheelEvent documentation
298  const int DELTAS_PER_DEGREE = 8; // From QWheelEvent documentation
299 
300  QPoint numDegrees = event->angleDelta() / DELTAS_PER_DEGREE;
301 
302  LOG4CPP_INFO_S ((*mainCat)) << "MainWindow::wheelEvent"
303  << " degrees=" << numDegrees.y()
304  << " phase=" << event->phase();
305 
306  // Criteria:
307  // 1) User has enabled wheel zoom control (but that is not known here so MainWindow will handle that part)
308  // in slotViewZoomInFromWheelEvent and slotViewZoomOutFromWheelEvent
309  // 2) Angle is over a threshold to eliminate false events from just touching wheel
310  if ((event->modifiers() & Qt::ControlModifier) != 0) {
311 
312  if (numDegrees.y() >= ANGLE_THRESHOLD) {
313 
314  // Rotated backwards towards the user, which means zoom in
315  emit signalViewZoomIn();
316 
317  } else if (numDegrees.y() <= -ANGLE_THRESHOLD) {
318 
319  // Rotated forwards away from the user, which means zoom out
320  emit signalViewZoomOut();
321 
322  }
323 
324  // Accept the event as long as Control key was used and we are capturing wheel event
325  event->accept();
326 
327  } else {
328 
329  // Let non-Control events manage scrolling
330  QGraphicsView::wheelEvent (event);
331 
332  }
333 }
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.
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.
QStringList selectedPointIdentifiers(const QList< QGraphicsItem * > &items) const
Return list of selected point identifiers.
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...
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.
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.
Main window consisting of menu, graphics scene, status bar and optional toolbars as a Single Document...
Definition: MainWindow.h:91