1 import matplotlib.pyplot as plt
2 import matplotlib.colors as colors
4 from CurveView import CurveView
6 from utils import Logger, trQ
7 from PlotWidget import PlotWidget
8 from PlotSettings import PlotSettings
9 from pyqtside import QtGui, QtCore
10 from pyqtside.QtCore import QObject
11 from matplotlib.figure import Figure
12 from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg, NavigationToolbar2QT
14 class EventHandler(QObject):
15 """ Handle the right-click properly so that it only triggers the contextual menu """
16 def __init__(self,parent=None):
17 QObject.__init__(self, parent)
19 def eventFilter(self, obj, event):
20 if event.type() == QtCore.QEvent.MouseButtonPress:
21 if event.button() == 2:
22 # Discarding right button press to only keep context menu display
23 return True # Event handled (and hence not passed to matplotlib)
24 return QObject.eventFilter(self, obj, event)
27 AUTOFIT_MARGIN = 0.03 # 3%
29 # See http://matplotlib.org/api/markers_api.html:
30 CURVE_MARKERS = [ "o" ,# circle
52 _DEFAULT_LEGEND_STATE = False # for test purposes mainly - initial status of the legend
54 def __init__(self, controller):
55 View.__init__(self, controller)
56 self._eventHandler = EventHandler()
58 self._curveViews = {} # key: curve (model) ID, value: CurveView
59 self._salomeViewID = None
60 self._mplFigure = None
62 self._mplCanvas = None
63 self._plotWidget = None
64 self._sgPyQt = self._controller._sgPyQt
66 self._mplNavigationActions = {}
67 self._toobarMPL = None
69 self._currCrv = None # current curve selected in the view
72 self._legendLoc = "right" # "right" or "bottom"
76 self._dragOnDrop = False
82 self._defaultLineStyle = None
83 self._last_point = None
84 self._lastMarkerID = -1
85 self._blockLogSignal = False
87 self._axisXSciNotation = False
88 self._axisYSciNotation = False
89 self._prevTitle = None
91 def __repaintOK(self):
92 """ To be called inside XYView each time a low-level expansive matplotlib methods is to be invoked.
93 @return False if painting is currently locked, in which case it will also register the current XYView
94 as needing a refresh when unlocked
96 ret = self._controller._plotManager.isRepaintLocked()
98 self._controller._plotManager.registerRepaint(self._model)
101 def appendCurve(self, curveID):
102 newC = CurveView(self._controller, self)
103 newC.setModel(self._model._curves[curveID])
104 newC.setMPLAxes(self._mplAxes)
106 newC.setMarker(self.getMarker(go_next=True))
107 self._curveViews[curveID] = newC
109 def removeCurve(self, curveID):
110 v = self._curveViews.pop(curveID)
112 if self._currCrv is not None and self._currCrv.getID() == curveID:
115 def cleanBeforeClose(self):
116 """ Clean some items to avoid accumulating stuff in memory """
117 self._mplFigure.clear()
118 plt.close(self._mplFigure)
119 self._plotWidget.clearAll()
120 # For memory debugging only:
125 if self.__repaintOK():
126 Logger.Debug("XYView::draw")
127 self._mplCanvas.draw()
129 def onXLabelChange(self):
130 if self.__repaintOK():
131 self._mplAxes.set_xlabel(self._model._xlabel)
134 def onYLabelChange(self):
135 if self.__repaintOK():
136 self._mplAxes.set_ylabel(self._model._ylabel)
139 def onTitleChange(self):
140 if self.__repaintOK():
141 self._mplAxes.set_title(self._model._title)
142 self.updateViewTitle()
145 def onCurveTitleChange(self):
146 # Updating the legend should suffice
147 self.showHideLegend()
149 def onClearAll(self):
150 """ Just does an update with a reset of the marker cycle. """
151 if self.__repaintOK():
152 self._lastMarkerID = -1
155 def onPick(self, event):
156 """ MPL callback when picking
158 if event.mouseevent.button == 1:
161 for crv_id, cv in self._curveViews.items():
162 if cv._mplLines[0] is a:
164 # Use the plotmanager so that other plot sets get their current reset:
165 self._controller._plotManager.setCurrentCurve(selected_id)
167 def createAndAddLocalAction(self, icon_file, short_name):
168 return self._toolbar.addAction(self._sgPyQt.loadIcon("CURVEPLOT", icon_file), short_name)
170 def createPlotWidget(self):
171 self._mplFigure = Figure((8.0,5.0), dpi=100)
172 self._mplCanvas = FigureCanvasQTAgg(self._mplFigure)
173 self._mplCanvas.installEventFilter(self._eventHandler)
174 self._mplCanvas.mpl_connect('pick_event', self.onPick)
175 self._mplAxes = self._mplFigure.add_subplot(1, 1, 1)
176 self._plotWidget = PlotWidget()
177 self._toobarMPL = NavigationToolbar2QT(self._mplCanvas, None)
178 for act in self._toobarMPL.actions():
179 actionName = str(act.text()).strip()
180 self._mplNavigationActions[actionName] = act
181 self._plotWidget.setCentralWidget(self._mplCanvas)
182 self._toolbar = self._plotWidget.toolBar
183 self.populateToolbar()
185 self._popupMenu = QtGui.QMenu()
186 self._popupMenu.addAction(self._actionLegend)
188 # Connect evenement for the graphic scene
189 self._mplCanvas.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
190 self._mplCanvas.customContextMenuRequested.connect(self.onContextMenu)
191 self._mplCanvas.mpl_connect('scroll_event', self.onScroll)
192 self._mplCanvas.mpl_connect('button_press_event', self.onMousePress)
194 def populateToolbar(self):
195 # Action to dump view in a file
196 a = self.createAndAddLocalAction("dump_view.png", trQ("DUMP_VIEW_TXT"))
197 a.triggered.connect(self.dumpView)
198 self._toolbar.addSeparator()
199 # Actions to manipulate the scene
200 a = self.createAndAddLocalAction("fit_all.png", trQ("FIT_ALL_TXT"))
201 a.triggered.connect(self.autoFit)
202 # Zoom and pan are mutually exclusive but can be both de-activated:
203 self._zoomAction = self.createAndAddLocalAction("fit_area.png", trQ("FIT_AREA_TXT"))
204 self._zoomAction.triggered.connect(self.zoomArea)
205 self._zoomAction.setCheckable(True)
206 self._panAction = self.createAndAddLocalAction("zoom_pan.png", trQ("ZOOM_PAN_TXT"))
207 self._panAction.triggered.connect(self.pan)
208 self._panAction.setCheckable(True)
209 self._toolbar.addSeparator()
210 # Actions to change the representation of curves
211 self._curveActionGroup = QtGui.QActionGroup(self._plotWidget)
212 self._pointsAction = self.createAndAddLocalAction("draw_points.png", trQ("DRAW_POINTS_TXT"))
213 self._pointsAction.setCheckable(True)
214 self._linesAction = self.createAndAddLocalAction("draw_lines.png", trQ("DRAW_LINES_TXT"))
215 self._linesAction.setCheckable(True)
216 self._curveActionGroup.addAction(self._pointsAction)
217 self._curveActionGroup.addAction(self._linesAction)
218 self._linesAction.setChecked(True)
219 self._curveActionGroup.triggered.connect(self.changeModeCurve)
220 self._curveActionGroup.setExclusive(True)
221 self._toolbar.addSeparator()
222 # Actions to draw horizontal curves as linear or logarithmic
223 self._horActionGroup = QtGui.QActionGroup(self._plotWidget)
224 self._horLinearAction = self.createAndAddLocalAction("hor_linear.png", trQ("HOR_LINEAR_TXT"))
225 self._horLinearAction.setCheckable(True)
226 self._horLogarithmicAction = self.createAndAddLocalAction("hor_logarithmic.png", trQ("HOR_LOGARITHMIC_TXT"))
227 self._horLogarithmicAction.setCheckable(True)
228 self._horActionGroup.addAction(self._horLinearAction)
229 self._horActionGroup.addAction(self._horLogarithmicAction)
230 self._horLinearAction.setChecked(True)
231 self._horActionGroup.triggered.connect(self.onViewHorizontalMode)
232 self._toolbar.addSeparator()
233 # Actions to draw vertical curves as linear or logarithmic
234 self._verActionGroup = QtGui.QActionGroup(self._plotWidget)
235 self._verLinearAction = self.createAndAddLocalAction("ver_linear.png", trQ("VER_LINEAR_TXT"))
236 self._verLinearAction.setCheckable(True)
237 self._verLogarithmicAction = self.createAndAddLocalAction("ver_logarithmic.png", trQ("VER_LOGARITHMIC_TXT"))
238 self._verLogarithmicAction.setCheckable(True)
239 self._verActionGroup.addAction(self._verLinearAction)
240 self._verActionGroup.addAction(self._verLogarithmicAction)
241 self._verLinearAction.setChecked(True)
242 self._verActionGroup.triggered.connect(self.onViewVerticalMode)
243 self._verActionGroup.setExclusive(True)
244 self._toolbar.addSeparator()
245 # Action to show or hide the legend
246 self._actionLegend = self.createAndAddLocalAction("legend.png", trQ("SHOW_LEGEND_TXT"))
247 self._actionLegend.setCheckable(True)
248 self._actionLegend.triggered.connect(self.showHideLegend)
249 if self._DEFAULT_LEGEND_STATE:
250 self._actionLegend.setChecked(True)
251 self._toolbar.addSeparator()
252 # Action to set the preferences
253 a = self.createAndAddLocalAction("settings.png", trQ("SETTINGS_TXT"))
254 a.triggered.connect(self.onSettings)
258 # Choice of the view backup file
260 for form in ["IMAGES_FILES", "PDF_FILES", "POSTSCRIPT_FILES", "ENCAPSULATED_POSTSCRIPT_FILES"]:
261 filters.append(trQ(form))
262 fileName = self._sgPyQt.getFileName(self._sgPyQt.getDesktop(),
265 trQ("DUMP_VIEW_FILE"),
267 if not fileName.isEmpty():
269 self._mplAxes.figure.savefig(name)
272 def autoFit(self, check=True, repaint=True):
273 if self.__repaintOK():
274 self._mplAxes.relim()
275 xm, xM = self._mplAxes.xaxis.get_data_interval()
276 ym, yM = self._mplAxes.yaxis.get_data_interval()
278 self._mplAxes.axis([xm, xM, ym-i*self.AUTOFIT_MARGIN, yM+i*self.AUTOFIT_MARGIN])
283 if self._panAction.isChecked() and self._zoomAction.isChecked():
284 self._panAction.setChecked(False)
285 # Trigger underlying matplotlib action:
286 self._mplNavigationActions["Zoom"].trigger()
289 if self._panAction.isChecked() and self._zoomAction.isChecked():
290 self._zoomAction.setChecked(False)
291 # Trigger underlying matplotlib action:
292 self._mplNavigationActions["Pan"].trigger()
294 def getMarker(self, go_next=False):
296 self._lastMarkerID = (self._lastMarkerID+1) % len(self.CURVE_MARKERS)
297 return self.CURVE_MARKERS[self._lastMarkerID]
299 def changeModeCurve(self, repaint=True):
300 if not self.__repaintOK():
302 action = self._curveActionGroup.checkedAction()
303 if action is self._pointsAction :
304 for crv_view in self._curveViews.values():
305 crv_view.setLineStyle("None")
306 elif action is self._linesAction :
307 for crv_view in self._curveViews.values():
308 crv_view.setLineStyle("-")
310 raise NotImplementedError
314 def setXLog(self, log, repaint=True):
315 if not self.__repaintOK():
317 self._blockLogSignal = True
319 self._mplAxes.set_xscale('log')
320 self._horLogarithmicAction.setChecked(True)
322 self._mplAxes.set_xscale('linear')
323 self._horLinearAction.setChecked(True)
327 self._blockLogSignal = False
329 def setYLog(self, log, repaint=True):
330 if not self.__repaintOK():
332 self._blockLogSignal = True
334 self._mplAxes.set_yscale('log')
335 self._verLogarithmicAction.setChecked(True)
337 self._mplAxes.set_yscale('linear')
338 self._verLinearAction.setChecked(True)
342 self._blockLogSignal = False
344 def setXSciNotation(self, sciNotation, repaint=True):
345 self._axisXSciNotation = sciNotation
346 self.changeFormatAxis()
350 def setYSciNotation(self, sciNotation, repaint=True):
351 self._axisYSciNotation = sciNotation
352 self.changeFormatAxis()
356 def onViewHorizontalMode(self, checked=True, repaint=True):
357 if self._blockLogSignal:
359 action = self._horActionGroup.checkedAction()
360 if action is self._horLinearAction:
361 self.setXLog(False, repaint)
362 elif action is self._horLogarithmicAction:
363 self.setXLog(True, repaint)
365 raise NotImplementedError
367 def onViewVerticalMode(self, checked=True, repaint=True):
368 if self._blockLogSignal:
370 action = self._verActionGroup.checkedAction()
371 if action is self._verLinearAction:
372 self.setYLog(False, repaint)
373 elif action is self._verLogarithmicAction:
374 self.setYLog(True, repaint)
376 raise NotImplementedError
380 def __adjustFigureMargins(self, withLegend):
381 """ Adjust figure margins to make room for the legend """
384 bbox = leg.get_window_extent()
385 # In axes coordinates:
386 bbox2 = bbox.transformed(leg.figure.transFigure.inverted())
387 if self._legendLoc == "right":
388 self._mplFigure.subplots_adjust(right=1.0-(bbox2.width+0.02))
389 elif self._legendLoc == "bottom":
390 self._mplFigure.subplots_adjust(bottom=bbox2.height+0.1)
392 # Reset to default (rc) values
393 self._mplFigure.subplots_adjust(bottom=0.1, right=0.9)
395 def setLegendVisible(self, visible, repaint=True):
396 if visible and not self._actionLegend.isChecked():
397 self._actionLegend.setChecked(True)
398 self.showHideLegend(repaint=repaint)
399 if not visible and self._actionLegend.isChecked():
400 self._actionLegend.setChecked(False)
401 self.showHideLegend(repaint=repaint)
403 def showHideLegend(self, actionChecked=None, repaint=True):
404 if not self.__repaintOK(): # Show/hide legend is extremely costly
407 show = self._actionLegend.isChecked()
408 nCurves = len(self._curveViews)
409 if nCurves > 10: fontSize = 'x-small'
410 else: fontSize = None
414 leg = self._mplAxes.legend()
415 if leg is not None: leg.remove()
416 if show and nCurves > 0:
417 # Recreate legend from scratch
418 if self._legend is not None:
420 self._mplAxes._legend = None
421 if self._legendLoc == "bottom":
422 self._legend = self._mplAxes.legend(loc="upper left", bbox_to_anchor=(0.0, -0.05, 1.0, -0.05),
423 borderaxespad=0.0, mode="expand", fancybox=True,
424 shadow=True, ncol=3, prop={'size':fontSize, 'style': 'italic'})
425 elif self._legendLoc == "right":
426 self._legend = self._mplAxes.legend(loc="upper left", bbox_to_anchor=(1.02,1.0), borderaxespad=0.0,
427 ncol=1, fancybox=True, shadow=True, prop={'size':fontSize, 'style': 'italic'})
429 raise Exception("Invalid legend placement! Must be 'bottom' or 'right'")
430 # Canvas must be drawn so we can adjust the figure placement:
431 self._mplCanvas.draw()
432 self.__adjustFigureMargins(withLegend=True)
434 if self._legend is None:
438 self._legend.set_visible(False)
440 self._mplAxes._legend = None
441 self._mplCanvas.draw()
442 self.__adjustFigureMargins(withLegend=False)
444 curr_crv = self._model._currentCurve
445 if curr_crv is None: curr_title = None
446 else: curr_title = curr_crv.getTitle()
447 if self._legend is not None:
448 for label in self._legend.get_texts() :
449 text = label.get_text()
450 if (text == curr_title):
451 label.set_backgroundcolor('0.85')
453 label.set_backgroundcolor('white')
458 def onSettings(self, trigger=False, dlg_test=None):
459 dlg = dlg_test or PlotSettings()
460 dlg.titleEdit.setText(self._mplAxes.get_title())
461 dlg.axisXTitleEdit.setText(self._mplAxes.get_xlabel())
462 dlg.axisYTitleEdit.setText(self._mplAxes.get_ylabel())
463 dlg.gridCheckBox.setChecked(self._mplAxes.xaxis._gridOnMajor) # could not find a relevant API to check this
464 dlg.axisXSciCheckBox.setChecked(self._axisXSciNotation)
465 dlg.axisYSciCheckBox.setChecked(self._axisYSciNotation)
466 xmin, xmax = self._mplAxes.get_xlim()
467 ymin, ymax = self._mplAxes.get_ylim()
468 xminText = "%g" %xmin
469 xmaxText = "%g" %xmax
470 yminText = "%g" %ymin
471 ymaxText = "%g" %ymax
472 dlg.axisXMinEdit.setText(xminText)
473 dlg.axisXMaxEdit.setText(xmaxText)
474 dlg.axisYMinEdit.setText(yminText)
475 dlg.axisYMaxEdit.setText(ymaxText)
477 dlg.markerCurve.clear()
478 for marker in self.CURVE_MARKERS :
479 dlg.markerCurve.addItem(marker)
480 curr_crv = self._model.getCurrentCurve()
481 if not curr_crv is None:
482 dlg.colorCurve.setEnabled(True)
483 dlg.markerCurve.setEnabled(True)
484 name = curr_crv.getTitle()
485 dlg.nameCurve.setText(name)
486 view = self._curveViews[curr_crv.getID()]
487 marker = view.getMarker()
488 color = view.getColor()
489 index = dlg.markerCurve.findText(marker)
490 dlg.markerCurve.setCurrentIndex(index)
491 rgb = colors.colorConverter.to_rgb(color)
492 dlg.setRGB(rgb[0],rgb[1],rgb[2])
494 dlg.colorCurve.setEnabled(False)
495 dlg.markerCurve.setEnabled(False)
496 dlg.nameCurve.setText("")
498 if self._legend is None:
499 dlg.showLegendCheckBox.setChecked(False)
500 dlg.legendPositionComboBox.setEnabled(False)
502 if self._legend.get_visible():
503 dlg.showLegendCheckBox.setChecked(True)
504 dlg.legendPositionComboBox.setEnabled(True)
505 if self._legendLoc == "bottom":
506 dlg.legendPositionComboBox.setCurrentIndex(0)
507 elif self._legendLoc == "right" :
508 dlg.legendPositionComboBox.setCurrentIndex(1)
510 dlg.showLegendCheckBox.setChecked(False)
511 dlg.legendPositionComboBox.setEnabled(False)
515 self._model.setTitle(dlg.titleEdit.text())
517 self._model.setXLabel(dlg.axisXTitleEdit.text())
518 self._model.setYLabel(dlg.axisYTitleEdit.text())
520 if dlg.gridCheckBox.isChecked() :
521 self._mplAxes.grid(True)
523 self._mplAxes.grid(False)
525 if dlg.showLegendCheckBox.isChecked():
526 self._actionLegend.setChecked(True)
527 if dlg.legendPositionComboBox.currentIndex() == 0 :
528 self._legendLoc = "bottom"
529 elif dlg.legendPositionComboBox.currentIndex() == 1 :
530 self._legendLoc = "right"
532 self._actionLegend.setChecked(False)
533 xminText = dlg.axisXMinEdit.text()
534 xmaxText = dlg.axisXMaxEdit.text()
535 yminText = dlg.axisYMinEdit.text()
536 ymaxText = dlg.axisYMaxEdit.text()
537 self._mplAxes.axis([float(xminText), float(xmaxText), float(yminText), float(ymaxText)] )
538 self._axisXSciNotation = dlg.axisXSciCheckBox.isChecked()
539 self._axisYSciNotation = dlg.axisYSciCheckBox.isChecked()
540 self.changeFormatAxis()
541 # Color and marker of the curve
543 view.setColor(dlg.getRGB())
544 view.setMarker(self.CURVE_MARKERS[dlg.markerCurve.currentIndex()])
545 self.showHideLegend(repaint=True)
546 self._mplCanvas.draw()
549 def updateViewTitle(self):
551 if self._model._title != "":
552 s = " - %s" % self._model._title
553 title = "CurvePlot (%d)%s" % (self._model.getID(), s)
554 self._sgPyQt.setViewTitle(self._salomeViewID, title)
556 def onCurrentPlotSetChange(self):
557 """ Avoid a unnecessary call to update() when just switching current plot set! """
560 def onCurrentCurveChange(self):
561 curr_crv2 = self._model.getCurrentCurve()
562 if curr_crv2 != self._currCrv:
563 if self._currCrv is not None:
564 view = self._curveViews[self._currCrv.getID()]
565 view.toggleHighlight(False)
566 if not curr_crv2 is None:
567 view = self._curveViews[curr_crv2.getID()]
568 view.toggleHighlight(True)
569 self._currCrv = curr_crv2
570 self.showHideLegend(repaint=False) # redo legend
573 def changeFormatAxis(self) :
574 if not self.__repaintOK():
577 # don't try to switch to sci notation if we are not using the
578 # matplotlib.ticker.ScalarFormatter (i.e. if in Log for ex.)
579 if self._horLinearAction.isChecked():
580 if self._axisXSciNotation :
581 self._mplAxes.ticklabel_format(style='sci',scilimits=(0,0), axis='x')
583 self._mplAxes.ticklabel_format(style='plain',axis='x')
584 if self._verLinearAction.isChecked():
585 if self._axisYSciNotation :
586 self._mplAxes.ticklabel_format(style='sci',scilimits=(0,0), axis='y')
588 self._mplAxes.ticklabel_format(style='plain',axis='y')
591 if self._salomeViewID is None:
592 self.createPlotWidget()
593 self._salomeViewID = self._sgPyQt.createView("CurvePlot", self._plotWidget)
594 Logger.Debug("Creating SALOME view ID=%d" % self._salomeViewID)
595 self._sgPyQt.setViewVisible(self._salomeViewID, True)
597 self.updateViewTitle()
599 # Check list of curve views:
600 set_mod = set(self._model._curves.keys())
601 set_view = set(self._curveViews.keys())
603 # Deleted/Added curves:
604 dels = set_view - set_mod
605 added = set_mod - set_view
610 if not len(self._curveViews):
612 self._mplAxes.set_color_cycle(None)
617 # Axes labels and title
618 self._mplAxes.set_xlabel(self._model._xlabel)
619 self._mplAxes.set_ylabel(self._model._ylabel)
620 self._mplAxes.set_title(self._model._title)
622 self.onViewHorizontalMode(repaint=False)
623 self.onViewVerticalMode(repaint=False)
624 self.changeModeCurve(repaint=False)
625 self.showHideLegend(repaint=False) # The canvas is repainted anyway (needed to get legend bounding box)
626 self.changeFormatAxis()
629 self.autoFit(repaint=False)
632 def onDataChange(self):
633 # the rest is done in the CurveView:
634 self.autoFit(repaint=True)
636 def onMousePress(self, event):
637 if event.button == 3 :
638 if self._panAction.isChecked():
639 self._panAction.setChecked(False)
640 if self._zoomAction.isChecked():
641 self._zoomAction.setChecked(False)
643 def onContextMenu(self, position):
644 pos = self._mplCanvas.mapToGlobal(QtCore.QPoint(position.x(),position.y()))
645 self._popupMenu.exec_(pos)
647 def onScroll(self, event):
648 # Event location (x and y)
652 cur_xlim = self._mplAxes.get_xlim()
653 cur_ylim = self._mplAxes.get_ylim()
656 if event.button == 'down':
658 scale_factor = 1 / base_scale
659 elif event.button == 'up':
661 scale_factor = base_scale
663 # deal with something that should never happen
666 new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor
667 new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor
669 relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0])
670 rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0])
672 self._mplAxes.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)])
673 self._mplAxes.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)])
678 def onPressEvent(self, event):
679 if event.button == 3 :
680 #self._mplCanvas.emit(QtCore.SIGNAL("button_release_event()"))
681 canvasSize = event.canvas.geometry()
682 point = event.canvas.mapToGlobal(QtCore.QPoint(event.x,canvasSize.height()-event.y))
683 self._popupMenu.exec_(point)
685 print "Press event on the other button"
686 #if event.button == 3 :
687 # canvasSize = event.canvas.geometry()
688 # point = event.canvas.mapToGlobal(QtCore.QPoint(event.x,canvasSize.height()-event.y))
689 # self._popupMenu.move(point)
690 # self._popupMenu.show()
692 def onMotionEvent(self, event):
693 print "OnMotionEvent ",event.button
694 #if event.button == 3 :
695 # event.button = None
698 def onReleaseEvent(self, event):
699 print "OnReleaseEvent ",event.button
700 #if event.button == 3 :
701 # event.button = None