source: orange/Orange/OrangeCanvas/orngView.py @ 11482:30188643fe5b

Revision 11482:30188643fe5b, 21.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Added 'progressEventsFlags' argument to 'progressBarSet' method.

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