source: orange/orange/OrangeCanvas/orngView.py @ 8124:2fb0b52428b4

Revision 8124:2fb0b52428b4, 20.9 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Fixed an error when reseting the widget signals (a line was deleted but its in/outWidget attr was still accessed).

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:        # if we clicked on a widget
236                self.tempWidget = activeItem
237
238                if ev.button() == Qt.LeftButton:
239                    self.bWidgetDragging = True
240                    if ev.modifiers() & Qt.ControlModifier: #self.doc.ctrlPressed:
241                        activeItem.setSelected(not activeItem.isSelected())
242                    elif activeItem.isSelected() == 0:
243                        self.unselectAllWidgets()
244                        activeItem.setSelected(True)
245
246                    for w in self.getSelectedWidgets():
247                        w.savePosition()
248                        w.setAllLinesFinished(False)
249
250                # is we clicked the right mouse button we show the popup menu for widgets
251                elif ev.button() == Qt.RightButton:
252                    if not ev.modifiers() & Qt.ControlModifier:
253                        self.unselectAllWidgets() 
254                    activeItem.setSelected(True)
255                    self.doc.canvasDlg.widgetPopup.popup(ev.globalPos())
256                    return # Don't call QGraphicsView.mousePressEvent. It unselects the active item
257                else:
258                    self.unselectAllWidgets()
259
260            # if we right clicked on a line we show a popup menu
261            elif type(activeItem) == orngCanvasItems.CanvasLine and ev.button() == Qt.RightButton:
262                self.unselectAllWidgets()
263                self.selectedLine = activeItem
264                self.lineEnabledAction.setChecked(self.selectedLine.getEnabled())
265                self.linePopup.popup(ev.globalPos())
266            else:
267                self.unselectAllWidgets()
268
269        return QGraphicsView.mousePressEvent(self, ev)
270
271
272    # mouse button was pressed and mouse is moving ######################
273    def mouseMoveEvent(self, ev):
274        point = self.mapToScene(ev.pos())
275        if self.bWidgetDragging:
276            for item in self.getSelectedWidgets():
277                newPos = item.oldPos + (point-self.mouseDownPosition)
278                item.setCoords(newPos.x(), newPos.y())
279            self.ensureVisible(QRectF(point, point + QPointF(1, 1)))
280
281        elif self.tempLine:
282            self.tempLine.updateLinePos(point)
283            self.ensureVisible(QRectF(point, point + QPointF(1, 1)))
284
285        elif self.widgetSelectionRect:
286            selectionRect = self.maxSelectionRect(QRectF(self.mouseDownPosition, point).normalized())
287            self.widgetSelectionRect.setRect(selectionRect)
288            self.ensureVisible(QRectF(point, point + QPointF(1, 1)))
289
290            # select widgets in rectangle
291            widgets = self.getItemsAtPos(self.widgetSelectionRect, orngCanvasItems.CanvasWidget)
292            for widget in self.doc.widgets:
293                widget.setSelected(widget in widgets)
294
295#        self.scene().update()
296        return QGraphicsView.mouseMoveEvent(self, ev)
297
298
299    # mouse button was released #########################################
300    def mouseReleaseEvent(self, ev):
301        point = self.mapToScene(ev.pos())
302        if self.widgetSelectionRect:
303            self.widgetSelectionRect.hide()
304            self.scene().removeItem(self.widgetSelectionRect)
305            self.widgetSelectionRect = None
306
307        # if we are moving a widget
308        if self.bWidgetDragging:
309            validPos = True
310            for item in self.getSelectedWidgets():
311                items = self.scene().collidingItems(item)
312                validPos = validPos and (self.findItemTypeCount(items, orngCanvasItems.CanvasWidget) == 0)
313
314            for item in self.getSelectedWidgets():
315                if not validPos:
316                    item.restorePosition()
317                item.updateTooltip()
318                item.setAllLinesFinished(True)
319                orngHistory.logChangeWidgetPosition(self.doc.schemaID, id(item), (item.widgetInfo.category, item.widgetInfo.name), item.x(), item.y())
320
321        # if we are drawing line
322        elif self.tempLine:
323            # show again the empty input/output boxes
324            for widget in self.doc.widgets:
325                widget.resetLeftRightEdges()
326           
327            start = self.tempLine.startWidget or self.tempLine.widget
328            end = self.tempLine.endWidget or self.tempLine.widget
329#            self.tempLine.hide()
330            self.tempLine.remove()
331            self.tempLine = None
332
333            # we must check if we have really connected some output to input
334            if start and end and start != end:
335                if self.doc.signalManager.signalProcessingInProgress: # TODO: Remove this check when signal manager handles out of sync signals
336                    QMessageBox.information( self, "Orange Canvas", "Unable to connect widgets while signal processing is in progress. Please wait.")
337                else:
338                    self.doc.addLine(start, end)
339            else:
340                state = [self.doc.widgets[i].widgetInfo.name for i in range(min(len(self.doc.widgets), 5))]
341                predictedWidgets = orngHistory.predictWidgets(state, 20)
342                if start:
343                    orngTabs.categoriesPopup.updatePredictedWidgets(predictedWidgets, 'inputClasses', start.widgetInfo.outputClasses)
344                    orngTabs.categoriesPopup.updateWidgetsByInputs(start.widgetInfo)
345                else:
346                    orngTabs.categoriesPopup.updatePredictedWidgets(predictedWidgets, 'outputClasses', end.widgetInfo.inputClasses)
347                    orngTabs.categoriesPopup.updateWidgesByOutputs(end.widgetInfo)
348                   
349                newCoords = QPoint(ev.globalPos())
350                orngTabs.categoriesPopup.updateMenu()
351                action = orngTabs.categoriesPopup.exec_(newCoords- QPoint(0, orngTabs.categoriesPopup.categoriesYOffset))
352                if action and hasattr(action, "widgetInfo"):
353                    xOff = -48 * bool(end)
354                    newWidget = self.doc.addWidget(action.widgetInfo, point.x()+xOff, point.y()-24)
355                    if newWidget != None:
356                        if self.doc.signalManager.signalProcessingInProgress:
357                            QMessageBox.information( self, "Orange Canvas", "Unable to connect widgets while signal processing is in progress. Please wait.")
358                        else:
359                            self.doc.addLine(start or newWidget, end or newWidget)
360
361        elif ev.button() == Qt.RightButton:
362            activeItem = self.scene().itemAt(point)
363            diff = self.mouseDownPosition - point
364            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
365                newCoords = QPoint(ev.globalPos())
366                orngTabs.categoriesPopup.showAllWidgets()
367                state = [self.doc.widgets[i].widgetInfo.name for i in range(min(len(self.doc.widgets), 5))]
368                predictedWidgets = orngHistory.predictWidgets(state, 20)
369                orngTabs.categoriesPopup.updatePredictedWidgets(predictedWidgets, 'inputClasses')
370                orngTabs.categoriesPopup.updateMenu()
371                height = sum([orngTabs.categoriesPopup.actionGeometry(action).height() for action in orngTabs.categoriesPopup.actions()])
372                action = orngTabs.categoriesPopup.exec_(newCoords - QPoint(0, orngTabs.categoriesPopup.categoriesYOffset))
373                if action and hasattr(action, "widgetInfo"):
374                    newWidget = self.doc.addWidget(action.widgetInfo, point.x(), point.y())
375                   
376
377        self.scene().update()
378        self.bWidgetDragging = False
379#        self.doc.canvasDlg.widgetPopup.setEnabled(len(self.getSelectedWidgets()) == 1)
380        return QGraphicsView.mouseReleaseEvent(self, ev)
381
382    def mouseDoubleClickEvent(self, ev):
383        point = self.mapToScene(ev.pos())
384        items = self.scene().items(QRectF(point, QSizeF(0.0, 0.0)).adjusted(-2, -2, 2, 2))
385        items = [item for item in items if type(item) in [orngCanvasItems.CanvasWidget, orngCanvasItems.CanvasLine]]
386        activeItem = items[0] if items else None
387        if type(activeItem) == orngCanvasItems.CanvasWidget:        # if we clicked on a widget
388            self.tempWidget = activeItem
389            self.openActiveWidget()
390        elif type(activeItem) == orngCanvasItems.CanvasLine:
391            if self.doc.signalManager.signalProcessingInProgress:
392                QMessageBox.information( self, "Orange Canvas", "Please wait until Orange finishes processing signals.")
393                return
394            inWidget, outWidget = activeItem.inWidget, activeItem.outWidget
395            self.doc.resetActiveSignals(outWidget, inWidget, enabled = self.doc.signalManager.getLinkEnabled(outWidget.instance, inWidget.instance))
396            inWidget.updateTooltip()
397            outWidget.updateTooltip()
398            activeItem.updateTooltip()
399           
400        return QGraphicsView.mouseDoubleClickEvent(self, ev)
401
402    # ###########################################
403    # Functions for processing events
404    # ###########################################
405
406    def progressBarHandler(self, widgetInstance, value):
407        for widget in self.doc.widgets:
408            if widget.instance == widgetInstance:
409                widget.setProgressBarValue(value)
410                qApp.processEvents()        # allow processing of other events
411                return
412
413    def processingHandler(self, widgetInstance, value):
414        for widget in self.doc.widgets:
415            if widget.instance == widgetInstance:
416                widget.setProcessing(value)
417#                self.repaint()
418#                widget.update()
419                return
420
421    # ###########################################
422    # misc functions regarding item selection
423    # ###########################################
424
425    # return a list of widgets that are currently selected
426    def getSelectedWidgets(self):
427        return [widget for widget in self.doc.widgets if widget.isSelected()]
428
429    # return number of items in "items" of type "type"
430    def findItemTypeCount(self, items, Type):
431        return sum([type(item) == Type for item in items])
432   
433    def maxSelectionRect(self, rect, penWidth=1):
434        b_rect = self.scene().sceneRect() #.adjusted(-5, -5, 5, 5)
435        minSize = self.viewport().size()
436        minGeom = self.mapToScene(QRect(QPoint(0, 0), minSize)).boundingRect()
437        sceneRect = minGeom.united(b_rect)
438        return rect.intersected(sceneRect).adjusted(penWidth, penWidth, -penWidth, -penWidth)
439#       
440#    def resizeEvent(self, event):
441#        self.updateSceneRect()
442
Note: See TracBrowser for help on using the repository browser.