source: orange/Orange/OrangeCanvas/orngView.py @ 10731:abfeb5062e17

Revision 10731:abfeb5062e17, 20.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Don't call QGraphicsView.mousePressEvent when doing manual widget selection (it re-toggles selected widget when control modifier key is pressed).

Line 
1# Author: Gregor Leban (gregor.leban@fri.uni-lj.si)
2# Description:
3#    handling the mouse events inside documents
4#
5import orngCanvasItems
6from PyQt4.QtCore import *
7from PyQt4.QtGui import *
8import orngHistory, orngTabs
9
10       
11class SchemaView(QGraphicsView):
12    def __init__(self, doc, *args):
13        apply(QGraphicsView.__init__,(self,) + args)
14        self.doc = doc
15        self.bWidgetDragging = False               # are we currently dragging a widget
16        self.movingWidget = None
17        self.mouseDownPosition = QPointF(0,0)
18        self.tempLine = None
19        self.widgetSelectionRect = None
20        self.selectedLine = None
21        self.tempWidget = None
22        self.setRenderHint(QPainter.Antialiasing)
23        self.setAlignment(Qt.AlignLeft | Qt.AlignTop)
24        self.ensureVisible(0,0,1,1)
25
26        # create popup menus
27        self.linePopup = QMenu("Link", self)
28        self.lineEnabledAction = self.menupopupLinkEnabledID = self.linePopup.addAction( "Enabled",  self.toggleEnabledLink)
29        self.lineEnabledAction.setCheckable(1)
30        self.linePopup.addSeparator()
31        self.linePopup.addAction("Reset Signals", self.resetLineSignals)
32        self.linePopup.addAction("Remove", self.deleteSelectedLine)
33        self.linePopup.addSeparator()
34        self.setAcceptDrops(1)
35        self.viewport().setMouseTracking(True)
36        self.connect(self.scene(), SIGNAL("selectionChanged()"), self.onSelectionChanged)
37
38    # ###########################################
39    # drag and drop events. You can open a document by dropping it on the canvas
40    # ###########################################
41    def containsOWSFile(self, name):
42        name = name.strip("\x00")
43        return name.lower().endswith(".ows")
44
45    def dragEnterEvent(self, ev):
46        if self.containsOWSFile(str(ev.mimeData().data("FileName"))):
47            ev.accept()
48        else: ev.ignore()
49               
50    def dragMoveEvent(self, ev):
51        if self.containsOWSFile(str(ev.mimeData().data("FileName"))):
52            ev.setDropAction(Qt.MoveAction)
53            ev.accept()
54        else:
55            ev.ignore()
56
57    def dropEvent(self, ev):
58        name = str(ev.mimeData().data("FileName"))
59        if self.containsOWSFile(name):
60            name = name.strip("\x00")
61            self.doc.loadDocument(name)
62            ev.accept()
63        else:
64            ev.ignore()
65
66    def onSelectionChanged(self):
67        selected = self.scene().selectedItems()
68        one_selected = len(selected) == 1
69        n_selected = len(selected) > 0
70        self.doc.canvasDlg.widgetPopup.setEnabled(n_selected)
71        self.doc.canvasDlg.openActiveWidgetAction.setEnabled(one_selected)
72        self.doc.canvasDlg.renameActiveWidgetAction.setEnabled(one_selected)
73        self.doc.canvasDlg.removeActiveWidgetAction.setEnabled(n_selected)
74        self.doc.canvasDlg.helpActiveWidgetAction.setEnabled(one_selected)
75       
76    # ###########################################
77    # POPUP MENU - WIDGET actions
78    # ###########################################
79
80    # popMenuAction - user selected to show active widget
81    def openActiveWidget(self):
82        #if not self.tempWidget or self.tempWidget.instance == None: return
83        widgets = self.getSelectedWidgets()
84        if len(widgets) != 1: return
85        widget = widgets[0]
86        widget.instance.reshow()
87        if widget.instance.isMinimized():  # if widget is minimized, show its normal size
88            widget.instance.showNormal()
89
90    def helpOnActiveWidget(self):
91        #if not self.tempWidget or self.tempWidget.instance == None: return
92        widgets = self.getSelectedWidgets()
93        if len(widgets) != 1: return
94        widget = widgets[0]
95        widget.instance.openWidgetHelp()
96
97    # popMenuAction - user selected to rename active widget
98    def renameActiveWidget(self):
99        widgets = self.getSelectedWidgets()
100        if len(widgets) != 1: return
101        widget = widgets[0]
102
103        exName = str(widget.caption)
104        (newName, ok) = QInputDialog.getText(self, "Rename Widget", "Enter new name for the '" + exName + "' widget:", QLineEdit.Normal, exName)
105        newName = str(newName)
106        if ok and newName != exName:
107            for w in self.doc.widgets:
108                if w != widget and w.caption == newName:
109                    QMessageBox.information(self, 'Orange Canvas', 'Unable to rename widget. An instance with that name already exists.')
110                    return
111            widget.updateText(newName)
112            widget.instance.setCaption(newName)
113
114    # popMenuAction - user selected to delete active widget
115    def removeActiveWidget(self):
116        if self.doc.signalManager.signalProcessingInProgress:
117            QMessageBox.information( self, "Orange Canvas", "Unable to remove widgets while signal processing is in progress. Please wait.")
118            return
119
120        selectedWidgets = self.getSelectedWidgets()
121        if selectedWidgets == []:
122            selectedWidgets = [self.tempWidget]
123
124        for item in selectedWidgets:
125            self.doc.removeWidget(item)
126
127#        self.scene().update()
128        self.tempWidget = None
129#        self.doc.canvasDlg.widgetPopup.setEnabled(len(self.getSelectedWidgets()) == 1)
130
131    # ###########################################
132    # POPUP MENU - LINKS actions
133    # ###########################################
134
135    # popMenuAction - enable/disable link between two widgets
136    def toggleEnabledLink(self):
137        if self.selectedLine != None:
138            oldEnabled = self.doc.signalManager.getLinkEnabled(self.selectedLine.outWidget.instance, self.selectedLine.inWidget.instance)
139            self.doc.signalManager.setLinkEnabled(self.selectedLine.outWidget.instance, self.selectedLine.inWidget.instance, not oldEnabled)
140            self.selectedLine.updateTooltip()
141            self.selectedLine.inWidget.updateTooltip()
142            self.selectedLine.outWidget.updateTooltip()
143
144    # popMenuAction - delete selected link
145    def deleteSelectedLine(self):
146        if not self.selectedLine: return
147        if self.doc.signalManager.signalProcessingInProgress:
148             QMessageBox.information( self, "Orange Canvas", "Unable to remove connection while signal processing is in progress. Please wait.")
149             return
150        self.deleteLine(self.selectedLine)
151        self.selectedLine = None
152#        self.scene().update()
153
154    def deleteLine(self, line):
155        if line != None:
156            self.doc.removeLine1(line)
157
158    # resend signals between two widgets. receiving widget will process the received data
159    def resendSignals(self):
160        if self.selectedLine != None:
161            self.doc.signalManager.setLinkEnabled(self.selectedLine.outWidget.instance, self.selectedLine.inWidget.instance, 1, justSend = 1)
162
163    def resetLineSignals(self):
164        if self.selectedLine:
165            outWidget, inWidget = self.selectedLine.outWidget, self.selectedLine.inWidget
166            self.doc.resetActiveSignals(outWidget, inWidget, enabled = self.doc.signalManager.getLinkEnabled(outWidget.instance, inWidget.instance))
167            inWidget.updateTooltip()
168            outWidget.updateTooltip()
169            self.selectedLine.updateTooltip()
170
171    def unselectAllWidgets(self):
172        for item in self.doc.widgets:
173            item.setSelected(0)
174
175    def getItemsAtPos(self, pos, itemType = None):
176        if isinstance(pos, QGraphicsItem):
177            items = self.scene().collidingItems(pos)
178        else:
179            items = self.scene().items(pos)
180#        if type(pos) == QPointF:
181#            pos = QGraphicsRectItem(QRectF(pos, QSizeF(1,1)))
182#        items = self.scene().collidingItems(pos)
183        if itemType is not None:
184            items = [item for item in items if type(item) == itemType]
185        return items
186
187    # ###########################################
188    # MOUSE events
189    # ###########################################
190
191    # mouse button was pressed
192    def mousePressEvent(self, ev):
193        self.mouseDownPosition = self.mapToScene(ev.pos())
194
195        if self.widgetSelectionRect:
196            self.widgetSelectionRect.hide()
197            self.widgetSelectionRect = None
198
199        # do we start drawing a connection line
200        if ev.button() == Qt.LeftButton:
201            widgets = [item for item in self.doc.widgets if item.mouseInsideRightChannel(self.mouseDownPosition) or item.mouseInsideLeftChannel(self.mouseDownPosition)]# + [item for item in self.doc.widgets if item.mouseInsideLeftChannel(self.mouseDownPosition)]           
202            if widgets:
203                self.tempWidget = widgets[0]
204                if not self.doc.signalManager.signalProcessingInProgress:   # if we are processing some signals, don't allow to add lines
205                    self.unselectAllWidgets()
206                    self.tempLine = orngCanvasItems.TempCanvasLine(self.doc.canvasDlg, self.scene())
207                    if self.tempWidget.getDistToLeftEdgePoint(self.mouseDownPosition) < self.tempWidget.getDistToRightEdgePoint(self.mouseDownPosition):
208                        self.tempLine.setEndWidget(self.tempWidget)
209                        for widget in self.doc.widgets:
210                            widget.canConnect(widget, self.tempWidget, dynamic=True)
211                    else:
212                        self.tempLine.setStartWidget(self.tempWidget)
213                        for widget in self.doc.widgets:
214                            widget.canConnect(self.tempWidget, widget, dynamic=True)
215                                                       
216#                self.scene().update()
217#                self.doc.canvasDlg.widgetPopup.setEnabled(len(self.getSelectedWidgets()) == 1)
218                return QGraphicsView.mousePressEvent(self, ev)
219           
220        items = self.scene().items(QRectF(self.mouseDownPosition, QSizeF(0 ,0)).adjusted(-2, -2, 2, 2))#, At(self.mouseDownPosition)
221        items = [item for item in items if type(item) in [orngCanvasItems.CanvasWidget, orngCanvasItems.CanvasLine]]
222        activeItem = items[0] if items else None
223        if not activeItem:
224            self.tempWidget = None
225            rect = self.maxSelectionRect(QRectF(self.mouseDownPosition, self.mouseDownPosition))
226            self.widgetSelectionRect = QGraphicsRectItem(rect, None, self.scene())
227            self.widgetSelectionRect.setPen(QPen(QBrush(QColor(51, 153, 255, 192)), 1, Qt.SolidLine, Qt.RoundCap))
228            self.widgetSelectionRect.setBrush(QBrush(QColor(168, 202, 236, 192)))
229            self.widgetSelectionRect.setZValue(-100)
230            self.widgetSelectionRect.show()
231            self.unselectAllWidgets()
232
233        # we clicked on a widget or on a line
234        else:
235            if type(activeItem) == orngCanvasItems.CanvasWidget:
236                # if we clicked on a widget
237                self.tempWidget = activeItem
238
239                if ev.button() == Qt.LeftButton:
240                    self.bWidgetDragging = True
241                    if ev.modifiers() & Qt.ControlModifier:
242                        activeItem.setSelected(not activeItem.isSelected())
243                    elif activeItem.isSelected() == 0:
244                        self.unselectAllWidgets()
245                        activeItem.setSelected(True)
246
247                    for w in self.getSelectedWidgets():
248                        w.savePosition()
249                        w.setAllLinesFinished(False)
250
251                # is we clicked the right mouse button we show the popup menu for widgets
252                elif ev.button() == Qt.RightButton:
253                    if not ev.modifiers() & Qt.ControlModifier:
254                        self.unselectAllWidgets() 
255                    activeItem.setSelected(True)
256                    self.doc.canvasDlg.widgetPopup.popup(ev.globalPos())
257                else:
258                    self.unselectAllWidgets()
259                return # Don't call QGraphicsView.mousePressEvent. It unselects the active item
260
261            # if we right clicked on a line we show a popup menu
262            elif type(activeItem) == orngCanvasItems.CanvasLine and ev.button() == Qt.RightButton:
263                self.unselectAllWidgets()
264                self.selectedLine = activeItem
265                self.lineEnabledAction.setChecked(self.selectedLine.getEnabled())
266                self.linePopup.popup(ev.globalPos())
267            else:
268                self.unselectAllWidgets()
269
270        return QGraphicsView.mousePressEvent(self, ev)
271
272
273    # mouse button was pressed and mouse is moving ######################
274    def mouseMoveEvent(self, ev):
275        point = self.mapToScene(ev.pos())
276        if self.bWidgetDragging:
277            for item in self.getSelectedWidgets():
278                newPos = item.oldPos + (point-self.mouseDownPosition)
279                item.setCoords(newPos.x(), newPos.y())
280            self.ensureVisible(QRectF(point, point + QPointF(1, 1)))
281
282        elif self.tempLine:
283            self.tempLine.updateLinePos(point)
284            self.ensureVisible(QRectF(point, point + QPointF(1, 1)))
285
286        elif self.widgetSelectionRect:
287            selectionRect = self.maxSelectionRect(QRectF(self.mouseDownPosition, point).normalized())
288            self.widgetSelectionRect.setRect(selectionRect)
289            self.ensureVisible(QRectF(point, point + QPointF(1, 1)))
290
291            # select widgets in rectangle
292            widgets = self.getItemsAtPos(self.widgetSelectionRect, orngCanvasItems.CanvasWidget)
293            for widget in self.doc.widgets:
294                widget.setSelected(widget in widgets)
295
296#        self.scene().update()
297        return QGraphicsView.mouseMoveEvent(self, ev)
298
299
300    # mouse button was released #########################################
301    def mouseReleaseEvent(self, ev):
302        point = self.mapToScene(ev.pos())
303        if self.widgetSelectionRect:
304            self.widgetSelectionRect.hide()
305            self.scene().removeItem(self.widgetSelectionRect)
306            self.widgetSelectionRect = None
307
308        # if we are moving a widget
309        if self.bWidgetDragging:
310            validPos = True
311            for item in self.getSelectedWidgets():
312                items = self.scene().collidingItems(item)
313                validPos = validPos and (self.findItemTypeCount(items, orngCanvasItems.CanvasWidget) == 0)
314
315            for item in self.getSelectedWidgets():
316                if not validPos:
317                    item.restorePosition()
318                item.updateTooltip()
319                item.setAllLinesFinished(True)
320                orngHistory.logChangeWidgetPosition(self.doc.schemaID, id(item), (item.widgetInfo.category, item.widgetInfo.name), item.x(), item.y())
321
322        # if we are drawing line
323        elif self.tempLine:
324            # show again the empty input/output boxes
325            for widget in self.doc.widgets:
326                widget.resetLeftRightEdges()
327           
328            start = self.tempLine.startWidget or self.tempLine.widget
329            end = self.tempLine.endWidget or self.tempLine.widget
330#            self.tempLine.hide()
331            self.tempLine.remove()
332            self.tempLine = None
333
334            # we must check if we have really connected some output to input
335            if start and end and start != end:
336                if self.doc.signalManager.signalProcessingInProgress: # TODO: Remove this check when signal manager handles out of sync signals
337                    QMessageBox.information( self, "Orange Canvas", "Unable to connect widgets while signal processing is in progress. Please wait.")
338                else:
339                    self.doc.addLine(start, end)
340            else:
341                state = [self.doc.widgets[i].widgetInfo.name for i in range(min(len(self.doc.widgets), 5))]
342                predictedWidgets = orngHistory.predictWidgets(state, 20)
343                if start:
344                    orngTabs.categoriesPopup.updatePredictedWidgets(predictedWidgets, 'inputClasses', start.widgetInfo.outputClasses)
345                    orngTabs.categoriesPopup.updateWidgetsByInputs(start.widgetInfo)
346                else:
347                    orngTabs.categoriesPopup.updatePredictedWidgets(predictedWidgets, 'outputClasses', end.widgetInfo.inputClasses)
348                    orngTabs.categoriesPopup.updateWidgesByOutputs(end.widgetInfo)
349                   
350                newCoords = QPoint(ev.globalPos())
351                orngTabs.categoriesPopup.updateMenu()
352                action = orngTabs.categoriesPopup.exec_(newCoords- QPoint(0, orngTabs.categoriesPopup.categoriesYOffset))
353                if action and hasattr(action, "widgetInfo"):
354                    xOff = -48 * bool(end)
355                    newWidget = self.doc.addWidget(action.widgetInfo, point.x()+xOff, point.y()-24)
356                    if newWidget != None:
357                        if self.doc.signalManager.signalProcessingInProgress:
358                            QMessageBox.information( self, "Orange Canvas", "Unable to connect widgets while signal processing is in progress. Please wait.")
359                        else:
360                            self.doc.addLine(start or newWidget, end or newWidget)
361
362        elif ev.button() == Qt.RightButton:
363            activeItem = self.scene().itemAt(point)
364            diff = self.mouseDownPosition - point
365            if not activeItem and (diff.x()**2 + diff.y()**2) < 25:     # if no active widgets and we pressed and released mouse at approx same position
366                newCoords = QPoint(ev.globalPos())
367                orngTabs.categoriesPopup.showAllWidgets()
368                state = [self.doc.widgets[i].widgetInfo.name for i in range(min(len(self.doc.widgets), 5))]
369                predictedWidgets = orngHistory.predictWidgets(state, 20)
370                orngTabs.categoriesPopup.updatePredictedWidgets(predictedWidgets, 'inputClasses')
371                orngTabs.categoriesPopup.updateMenu()
372                height = sum([orngTabs.categoriesPopup.actionGeometry(action).height() for action in orngTabs.categoriesPopup.actions()])
373                action = orngTabs.categoriesPopup.exec_(newCoords - QPoint(0, orngTabs.categoriesPopup.categoriesYOffset))
374                if action and hasattr(action, "widgetInfo"):
375                    newWidget = self.doc.addWidget(action.widgetInfo, point.x(), point.y())
376                   
377
378        self.scene().update()
379        self.bWidgetDragging = False
380#        self.doc.canvasDlg.widgetPopup.setEnabled(len(self.getSelectedWidgets()) == 1)
381        return QGraphicsView.mouseReleaseEvent(self, ev)
382
383    def mouseDoubleClickEvent(self, ev):
384        point = self.mapToScene(ev.pos())
385        items = self.scene().items(QRectF(point, QSizeF(0.0, 0.0)).adjusted(-2, -2, 2, 2))
386        items = [item for item in items if type(item) in [orngCanvasItems.CanvasWidget, orngCanvasItems.CanvasLine]]
387        activeItem = items[0] if items else None
388        if type(activeItem) == orngCanvasItems.CanvasWidget:        # if we clicked on a widget
389            self.tempWidget = activeItem
390            self.openActiveWidget()
391        elif type(activeItem) == orngCanvasItems.CanvasLine:
392            if self.doc.signalManager.signalProcessingInProgress:
393                QMessageBox.information( self, "Orange Canvas", "Please wait until Orange finishes processing signals.")
394                return
395            inWidget, outWidget = activeItem.inWidget, activeItem.outWidget
396            self.doc.resetActiveSignals(outWidget, inWidget, enabled = self.doc.signalManager.getLinkEnabled(outWidget.instance, inWidget.instance))
397            inWidget.updateTooltip()
398            outWidget.updateTooltip()
399            activeItem.updateTooltip()
400           
401        return QGraphicsView.mouseDoubleClickEvent(self, ev)
402
403    # ###########################################
404    # Functions for processing events
405    # ###########################################
406
407    def progressBarHandler(self, widgetInstance, value):
408        for widget in self.doc.widgets:
409            if widget.instance == widgetInstance:
410                widget.setProgressBarValue(value)
411                qApp.processEvents()        # allow processing of other events
412                return
413
414    def processingHandler(self, widgetInstance, value):
415        for widget in self.doc.widgets:
416            if widget.instance == widgetInstance:
417                widget.setProcessing(value)
418#                self.repaint()
419#                widget.update()
420                return
421
422    # ###########################################
423    # misc functions regarding item selection
424    # ###########################################
425
426    # return a list of widgets that are currently selected
427    def getSelectedWidgets(self):
428        return [widget for widget in self.doc.widgets if widget.isSelected()]
429
430    # return number of items in "items" of type "type"
431    def findItemTypeCount(self, items, Type):
432        return sum([type(item) == Type for item in items])
433   
434    def maxSelectionRect(self, rect, penWidth=1):
435        b_rect = self.scene().sceneRect() #.adjusted(-5, -5, 5, 5)
436        minSize = self.viewport().size()
437        minGeom = self.mapToScene(QRect(QPoint(0, 0), minSize)).boundingRect()
438        sceneRect = minGeom.united(b_rect)
439        return rect.intersected(sceneRect).adjusted(penWidth, penWidth, -penWidth, -penWidth)
440#       
441#    def resizeEvent(self, event):
442#        self.updateSceneRect()
443
Note: See TracBrowser for help on using the repository browser.