source: orange/Orange/OrangeCanvas/orngDlgs.py @ 11025:b69249383d87

Revision 11025:b69249383d87, 58.8 KB checked in by Matija Polajnar <matija.polajnar@…>, 18 months ago (diff)

Add-ons: enable uninstallation on systems with pip installed.

Line 
1# Author: Gregor Leban (gregor.leban@fri.uni-lj.si)
2# Description:
3#    signal dialog, canvas options dialog
4
5from PyQt4.QtCore import *
6from PyQt4.QtGui import *
7from orngCanvasItems import MyCanvasText
8import OWGUI, sys, os
9
10has_pip = True
11try:
12    import pip.req
13except ImportError:
14    has_pip = False
15
16# this class is needed by signalDialog to show widgets and lines
17class SignalCanvasView(QGraphicsView):
18    def __init__(self, dlg, canvasDlg, *args):
19        apply(QGraphicsView.__init__,(self,) + args)
20        self.dlg = dlg
21        self.canvasDlg = canvasDlg
22        self.bMouseDown = False
23        self.tempLine = None
24        self.inWidget = None
25        self.outWidget = None
26        self.inWidgetIcon = None
27        self.outWidgetIcon = None
28        self.lines = []
29        self.outBoxes = []
30        self.inBoxes = []
31        self.texts = []
32        self.ensureVisible(0,0,1,1)
33        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
34        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
35        self.setAlignment(Qt.AlignLeft | Qt.AlignTop)
36        self.setRenderHint(QPainter.Antialiasing)
37
38    def addSignalList(self, outWidget, inWidget):
39        self.scene().clear()
40        outputs, inputs = outWidget.widgetInfo.outputs, inWidget.widgetInfo.inputs
41        outIcon, inIcon = self.canvasDlg.getWidgetIcon(outWidget.widgetInfo), self.canvasDlg.getWidgetIcon(inWidget.widgetInfo)
42        self.lines = []
43        self.outBoxes = []
44        self.inBoxes = []
45        self.texts = []
46        xSpaceBetweenWidgets = 100  # space between widgets
47        xWidgetOff = 10             # offset for widget position
48        yWidgetOffTop = 10          # offset for widget position
49        yWidgetOffBottom = 30       # offset for widget position
50        ySignalOff = 10             # space between the top of the widget and first signal
51        ySignalSpace = 50           # space between two neighbouring signals
52        ySignalSize = 20            # height of the signal box
53        xSignalSize = 20            # width of the signal box
54        xIconOff = 10
55        iconSize = 48
56
57        count = max(len(inputs), len(outputs))
58        height = max ((count)*ySignalSpace, 70)
59
60        # calculate needed sizes of boxes to show text
61        maxLeft = 0
62        for i in range(len(inputs)):
63            maxLeft = max(maxLeft, self.getTextWidth("("+inputs[i].name+")", 1))
64            maxLeft = max(maxLeft, self.getTextWidth(inputs[i].type))
65
66        maxRight = 0
67        for i in range(len(outputs)):
68            maxRight = max(maxRight, self.getTextWidth("("+outputs[i].name+")", 1))
69            maxRight = max(maxRight, self.getTextWidth(outputs[i].type))
70
71        width = max(maxLeft, maxRight) + 70 # we add 70 to show icons beside signal names
72
73        # show boxes
74        pen = QPen(QBrush(QColor(125, 162, 206, 192)), 1, Qt.SolidLine, Qt.RoundCap)
75        brush = QBrush(QColor(217, 232, 252, 192))
76        self.outWidget = QGraphicsRectItem(xWidgetOff, yWidgetOffTop, width, height, None, self.dlg.canvas)
77        self.outWidget.setBrush(brush)
78        self.outWidget.setPen(pen)
79        self.outWidget.setZValue(-100)
80
81        self.inWidget = QGraphicsRectItem(xWidgetOff + width + xSpaceBetweenWidgets, yWidgetOffTop, width, height, None, self.dlg.canvas)
82        self.inWidget.setBrush(brush)
83        self.inWidget.setPen(pen)
84        self.inWidget.setZValue(-100)
85       
86        canvasPicsDir  = os.path.join(self.canvasDlg.canvasDir, "icons")
87        if os.path.exists(os.path.join(canvasPicsDir, "frame.png")):
88            widgetBack = QPixmap(os.path.join(canvasPicsDir, "frame.png"))
89        else:
90            widgetBack = outWidget.imageFrame
91
92        # if icons -> show them
93        if outIcon:
94#            frame = QGraphicsPixmapItem(widgetBack, None, self.dlg.canvas)
95#            frame.setPos(xWidgetOff + xIconOff, yWidgetOffTop + height/2.0 - frame.pixmap().width()/2.0)
96            self.outWidgetIcon = QGraphicsPixmapItem(outIcon.pixmap(iconSize, iconSize), None, self.dlg.canvas)
97#            self.outWidgetIcon.setPos(xWidgetOff + xIconOff, yWidgetOffTop + height/2.0 - self.outWidgetIcon.pixmap().width()/2.0)
98            self.outWidgetIcon.setPos(xWidgetOff + xIconOff, yWidgetOffTop + xIconOff)
99       
100        if inIcon:
101#            frame = QGraphicsPixmapItem(widgetBack, None, self.dlg.canvas)
102#            frame.setPos(xWidgetOff + xSpaceBetweenWidgets + 2*width - xIconOff - frame.pixmap().width(), yWidgetOffTop + height/2.0 - frame.pixmap().width()/2.0)
103            self.inWidgetIcon = QGraphicsPixmapItem(inIcon.pixmap(iconSize, iconSize), None, self.dlg.canvas)
104#            self.inWidgetIcon.setPos(xWidgetOff + xSpaceBetweenWidgets + 2*width - xIconOff - self.inWidgetIcon.pixmap().width(), yWidgetOffTop + height/2.0 - self.inWidgetIcon.pixmap().width()/2.0)
105            self.inWidgetIcon.setPos(xWidgetOff + xSpaceBetweenWidgets + 2*width - xIconOff - self.inWidgetIcon.pixmap().width(), yWidgetOffTop + xIconOff)
106
107        # show signal boxes and text labels
108        #signalSpace = (count)*ySignalSpace
109        signalSpace = height
110        for i in range(len(outputs)):
111            y = yWidgetOffTop + ((i+1)*signalSpace)/float(len(outputs)+1)
112            box = QGraphicsRectItem(xWidgetOff + width, y - ySignalSize/2.0, xSignalSize, ySignalSize, None, self.dlg.canvas)
113            box.setBrush(QBrush(QColor(0,0,255)))
114            box.setZValue(200)
115            self.outBoxes.append((outputs[i].name, box))
116
117            self.texts.append(MyCanvasText(self.dlg.canvas, outputs[i].name, xWidgetOff + width - 8, y - 7, Qt.AlignRight | Qt.AlignVCenter, bold =1, show=1))
118            self.texts.append(MyCanvasText(self.dlg.canvas, outputs[i].type, xWidgetOff + width - 8, y + 7, Qt.AlignRight | Qt.AlignVCenter, bold =0, show=1))
119
120        for i in range(len(inputs)):
121            y = yWidgetOffTop + ((i+1)*signalSpace)/float(len(inputs)+1)
122            box = QGraphicsRectItem(xWidgetOff + width + xSpaceBetweenWidgets - xSignalSize, y - ySignalSize/2.0, xSignalSize, ySignalSize, None, self.dlg.canvas)
123            box.setBrush(QBrush(QColor(0,0,255)))
124            box.setZValue(200)
125            self.inBoxes.append((inputs[i].name, box))
126
127            self.texts.append(MyCanvasText(self.dlg.canvas, inputs[i].name, xWidgetOff + width + xSpaceBetweenWidgets + 8, y - 7, Qt.AlignLeft | Qt.AlignVCenter, bold =1, show=1))
128            self.texts.append(MyCanvasText(self.dlg.canvas, inputs[i].type, xWidgetOff + width + xSpaceBetweenWidgets + 8, y + 7, Qt.AlignLeft | Qt.AlignVCenter, bold =0, show=1))
129
130        self.texts.append(MyCanvasText(self.dlg.canvas, outWidget.caption, xWidgetOff + width/2.0, yWidgetOffTop + height + 5, Qt.AlignHCenter | Qt.AlignTop, bold =1, show=1))
131        self.texts.append(MyCanvasText(self.dlg.canvas, inWidget.caption, xWidgetOff + width* 1.5 + xSpaceBetweenWidgets, yWidgetOffTop + height + 5, Qt.AlignHCenter | Qt.AlignTop, bold =1, show=1))
132
133        return (2*xWidgetOff + 2*width + xSpaceBetweenWidgets, yWidgetOffTop + height + yWidgetOffBottom)
134
135    def getTextWidth(self, text, bold = 0):
136        temp = QGraphicsSimpleTextItem(text, None, self.dlg.canvas)
137        if bold:
138            font = temp.font()
139            font.setBold(1)
140            temp.setFont(font)
141        temp.hide()
142        return temp.boundingRect().width()
143
144    # ###################################################################
145    # mouse button was pressed
146    def mousePressEvent(self, ev):
147        self.bMouseDown = 1
148        point = self.mapToScene(ev.pos())
149        activeItem = self.scene().itemAt(QPointF(ev.pos()))
150        if type(activeItem) == QGraphicsRectItem and activeItem not in [self.outWidget, self.inWidget]:
151            self.tempLine = QGraphicsLineItem(None, self.dlg.canvas)
152            self.tempLine.setLine(point.x(), point.y(), point.x(), point.y())
153            self.tempLine.setPen(QPen(QColor(200, 200, 200), 1))
154            self.tempLine.setZValue(-300)
155           
156        elif type(activeItem) == QGraphicsLineItem:
157            for (line, outName, inName, outBox, inBox) in self.lines:
158                if line == activeItem:
159                    self.dlg.removeLink(outName, inName)
160                    return
161
162    # ###################################################################
163    # mouse button was released #########################################
164    def mouseMoveEvent(self, ev):
165        if self.tempLine:
166            curr = self.mapToScene(ev.pos())
167            start = self.tempLine.line().p1()
168            self.tempLine.setLine(start.x(), start.y(), curr.x(), curr.y())
169            self.scene().update()
170
171    # ###################################################################
172    # mouse button was released #########################################
173    def mouseReleaseEvent(self, ev):
174        if self.tempLine:
175            activeItem = self.scene().itemAt(QPointF(ev.pos()))
176            if type(activeItem) == QGraphicsRectItem:
177                activeItem2 = self.scene().itemAt(self.tempLine.line().p1())
178                if activeItem.x() < activeItem2.x(): outBox = activeItem; inBox = activeItem2
179                else:                                outBox = activeItem2; inBox = activeItem
180                outName = None; inName = None
181                for (name, box) in self.outBoxes:
182                    if box == outBox: outName = name
183                for (name, box) in self.inBoxes:
184                    if box == inBox: inName = name
185                if outName != None and inName != None:
186                    self.dlg.addLink(outName, inName)
187
188            self.tempLine.hide()
189            self.tempLine = None
190            self.scene().update()
191
192
193    def addLink(self, outName, inName):
194        outBox = None; inBox = None
195        for (name, box) in self.outBoxes:
196            if name == outName: outBox = box
197        for (name, box) in self.inBoxes:
198            if name == inName : inBox  = box
199        if outBox == None or inBox == None:
200            print "error adding link. Data = ", outName, inName
201            return
202        line = QGraphicsLineItem(None, self.dlg.canvas)
203        outRect = outBox.rect()
204        inRect = inBox.rect()
205        line.setLine(outRect.x() + outRect.width()-2, outRect.y() + outRect.height()/2.0, inRect.x()+2, inRect.y() + inRect.height()/2.0)
206        line.setPen(QPen(QColor(160, 160, 160), 5))
207        line.setZValue(100)
208        self.scene().update()
209        self.lines.append((line, outName, inName, outBox, inBox))
210
211
212    def removeLink(self, outName, inName):
213        for (line, outN, inN, outBox, inBox) in self.lines:
214            if outN == outName and inN == inName:
215                line.hide()
216                self.lines.remove((line, outN, inN, outBox, inBox))
217                self.scene().update()
218                return
219
220
221# #######################################
222# # Signal dialog - let the user select active signals between two widgets
223# #######################################
224class SignalDialog(QDialog):
225    def __init__(self, canvasDlg, *args):
226        QDialog.__init__(self, *args)
227        self.setAttribute(Qt.WA_DeleteOnClose, True)
228        self.canvasDlg = canvasDlg
229
230        self.signals = []
231        self._links = []
232        self.allSignalsTaken = 0
233        self.signalManager = self.canvasDlg.schema.signalManager
234
235        # GUI
236        self.setWindowTitle('Connect Signals')
237        self.setLayout(QVBoxLayout())
238
239        self.canvasGroup = OWGUI.widgetBox(self, 1)
240        self.canvas = QGraphicsScene(0,0,1000,1000)
241        self.canvasView = SignalCanvasView(self, self.canvasDlg, self.canvas, self.canvasGroup)
242        self.canvasGroup.layout().addWidget(self.canvasView)
243
244        buttons = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
245
246        self.buttonHelp = OWGUI.button(buttons, self, "&Help")
247        buttons.layout().addStretch(1)
248        self.buttonClearAll = OWGUI.button(buttons, self, "Clear &All", callback = self.clearAll)
249        self.buttonOk = OWGUI.button(buttons, self, "&OK", callback = self.accept)
250        self.buttonOk.setAutoDefault(1)
251        self.buttonOk.setDefault(1)
252        self.buttonCancel = OWGUI.button(buttons, self, "&Cancel", callback = self.reject)
253
254    def clearAll(self):
255        while self._links != []:
256            self.removeLink(self._links[0][0], self._links[0][1])
257
258    def setOutInWidgets(self, outWidget, inWidget):
259        self.outWidget = outWidget
260        self.inWidget = inWidget
261        (width, height) = self.canvasView.addSignalList(outWidget, inWidget)
262        self.canvas.setSceneRect(0, 0, width, height)
263        self.resize(width+50, height+80)
264
265    def countCompatibleConnections(self, outputs, inputs, outInstance, inInstance, outType, inType):
266        count = 0
267        for outS in outputs:
268            if outInstance.getOutputType(outS.name) == None: continue  # ignore if some signals don't exist any more, since we will report error somewhere else
269            if not issubclass(outInstance.getOutputType(outS.name), outType): continue
270            for inS in inputs:
271                if inInstance.getOutputType(inS.name) == None: continue  # ignore if some signals don't exist any more, since we will report error somewhere else
272                if not issubclass(inType, inInstance.getInputType(inS.name)): continue
273                if issubclass(outInstance.getOutputType(outS.name), inInstance.getInputType(inS.name)): count+= 1
274
275        return count
276
277    def existsABetterLink(self, outSignal, inSignal, outSignals, inSignals):
278        existsBetter = 0
279
280        betterOutSignal = None; betterInSignal = None
281        for outS in outSignals:
282            for inS in inSignals:
283                if (outS.name != outSignal.name and outS.name == inSignal.name and outS.type == inSignal.type) or (inS.name != inSignal.name and inS.name == outSignal.name and inS.type == outSignal.type):
284                    existsBetter = 1
285                    betterOutSignal = outS
286                    betterInSignal = inS
287        return existsBetter, betterOutSignal, betterInSignal
288
289
290    def getPossibleConnections(self, outputs, inputs):
291        possibleLinks = []
292        for outS in outputs:
293            outType = self.outWidget.instance.getOutputType(outS.name)
294            if outType == None:     #print "Unable to find signal type for signal %s. Check the definition of the widget." % (outS.name)
295                continue
296            for inS in inputs:
297                inType = self.inWidget.instance.getInputType(inS.name)
298                if inType == None:
299                    continue        #print "Unable to find signal type for signal %s. Check the definition of the widget." % (inS.name)
300                if issubclass(outType, inType):
301                    possibleLinks.append((outS.name, inS.name))
302                if issubclass(inType, outType):
303                    possibleLinks.append((outS.name, inS.name))
304        return possibleLinks
305
306    def addDefaultLinks(self):
307        canConnect = 0
308        addedInLinks = []
309        addedOutLinks = []
310        self.multiplePossibleConnections = 0    # can we connect some signal with more than one widget
311
312        minorInputs = [signal for signal in self.inWidget.widgetInfo.inputs if not signal.default]
313        majorInputs = [signal for signal in self.inWidget.widgetInfo.inputs if signal.default]
314        minorOutputs = [signal for signal in self.outWidget.widgetInfo.outputs if not signal.default]
315        majorOutputs = [signal for signal in self.outWidget.widgetInfo.outputs if signal.default]
316
317        inConnected = self.inWidget.getInConnectedSignalNames()
318        outConnected = self.outWidget.getOutConnectedSignalNames()
319
320        # input connections that can be simultaneously connected to multiple outputs are not to be considered as already connected
321        for i in inConnected[::-1]:
322            if not self.inWidget.instance.signalIsOnlySingleConnection(i):
323                inConnected.remove(i)
324
325        for s in majorInputs + minorInputs:
326            if not self.inWidget.instance.hasInputName(s.name):
327                return -1
328        for s in majorOutputs + minorOutputs:
329            if not self.outWidget.instance.hasOutputName(s.name):
330                return -1
331
332        pl1 = self.getPossibleConnections(majorOutputs, majorInputs)
333        pl2 = self.getPossibleConnections(majorOutputs, minorInputs)
334        pl3 = self.getPossibleConnections(minorOutputs, majorInputs)
335        pl4 = self.getPossibleConnections(minorOutputs, minorInputs)
336
337        all = pl1 + pl2 + pl3 + pl4
338
339        if not all: return 0
340
341        # try to find a link to any inputs that hasn't been previously connected
342        self.allSignalsTaken = 1
343        for (o,i) in all:
344            if i not in inConnected:
345                all.remove((o,i))
346                all.insert(0, (o,i))
347                self.allSignalsTaken = 0       # we found an unconnected link. no need to show the signal dialog
348                break
349        self.addLink(all[0][0], all[0][1])  # add only the best link
350
351        # there are multiple possible connections if we have in the same priority class more than one possible unconnected link
352        for pl in [pl1, pl2, pl3, pl4]:
353            #if len(pl) > 1 and sum([i not in inConnected for (o,i) in pl]) > 1: # if we have more than one valid
354            if len(pl) > 1:     # if we have more than one valid
355                self.multiplePossibleConnections = 1
356            if len(pl) > 0:     # when we find a first non-empty list we stop searching
357                break
358        return len(all) > 0
359   
360    def addDefaultLinks(self):
361        self.multiplePossibleConnections = 0
362        candidates = self.signalManager.proposePossibleLinks(self.outWidget.instance, self.inWidget.instance)
363        if candidates:
364            ## If there are more candidates with max weights
365            maxW = max([w for _,_,w in candidates])
366            maxCandidates = [c for c in candidates if c[-1] == maxW]
367            if len(maxCandidates) > 1:
368                self.multiplePossibleConnections = 1
369            best = maxCandidates[0]
370            self.addLink(best[0].name, best[1].name) # add the best to the view
371            return True
372        else:
373            return 0
374           
375
376    def addLink(self, outName, inName):
377        if (outName, inName) in self._links: return 1
378
379        # check if correct types
380        outType = self.outWidget.instance.getOutputType(outName)
381        inType = self.inWidget.instance.getInputType(inName)
382        if not issubclass(outType, inType) and not issubclass(inType, outType): return 0 #TODO check this with signalManager.canConnect
383
384        inSignal = None
385        inputs = self.inWidget.widgetInfo.inputs
386        for i in range(len(inputs)):
387            if inputs[i].name == inName: inSignal = inputs[i]
388
389        # if inName is a single signal and connection already exists -> delete it
390        for (outN, inN) in self._links:
391            if inN == inName and inSignal.single:
392                self.removeLink(outN, inN)
393
394        self._links.append((outName, inName))
395        self.canvasView.addLink(outName, inName)
396        return 1
397
398
399    def removeLink(self, outName, inName):
400        if (outName, inName) in self._links:
401            self._links.remove((outName, inName))
402            self.canvasView.removeLink(outName, inName)
403
404    def getLinks(self):
405        return self._links
406
407
408class ColorIcon(QToolButton):
409    def __init__(self, parent, color):
410        QToolButton.__init__(self, parent)
411        self.color = color
412        self.setMaximumSize(20,20)
413        self.connect(self, SIGNAL("clicked()"), self.showColorDialog)
414        self.updateColor()
415
416    def updateColor(self):
417        pixmap = QPixmap(16,16)
418        painter = QPainter()
419        painter.begin(pixmap)
420        painter.setPen(QPen(self.color))
421        painter.setBrush(QBrush(self.color))
422        painter.drawRect(0, 0, 16, 16);
423        painter.end()
424        self.setIcon(QIcon(pixmap))
425        self.setIconSize(QSize(16,16))
426
427
428    def drawButtonLabel(self, painter):
429        painter.setBrush(QBrush(self.color))
430        painter.setPen(QPen(self.color))
431        painter.drawRect(3, 3, self.width()-6, self.height()-6)
432
433    def showColorDialog(self):
434        color = QColorDialog.getColor(self.color, self)
435        if color.isValid():
436            self.color = color
437            self.updateColor()
438            self.repaint()
439
440# canvas dialog
441class CanvasOptionsDlg(QDialog):
442    def __init__(self, canvasDlg, *args):
443        apply(QDialog.__init__,(self,) + args)
444        self.canvasDlg = canvasDlg
445        self.settings = dict(canvasDlg.settings)        # create a copy of the settings dict. in case we accept the dialog, we update the canvasDlg.settings with this dict
446        if sys.platform == "darwin":
447            self.setWindowTitle("Preferences")
448        else:
449            self.setWindowTitle("Canvas Options")
450        self.topLayout = QVBoxLayout(self)
451        self.topLayout.setSpacing(0)
452        self.resize(300,300)
453#        self.toAdd = []
454#        self.toRemove = []
455
456        self.tabs = QTabWidget(self)
457        GeneralTab = OWGUI.widgetBox(self.tabs, margin = 4)
458        ExceptionsTab = OWGUI.widgetBox(self.tabs, margin = 4)
459        TabOrderTab = OWGUI.widgetBox(self.tabs, margin = 4)
460
461        self.tabs.addTab(GeneralTab, "General")
462        self.tabs.addTab(ExceptionsTab, "Exception handling")
463        self.tabs.addTab(TabOrderTab, "Widget tab order")
464
465        # #################################################################
466        # GENERAL TAB
467        generalBox = OWGUI.widgetBox(GeneralTab, "General Options")
468        self.snapToGridCB = OWGUI.checkBox(generalBox, self.settings, "snapToGrid", "Snap widgets to grid", debuggingEnabled = 0)
469        self.enableCanvasDropShadowsCB = OWGUI.checkBox(generalBox, self.settings, "enableCanvasDropShadows", "Enable drop shadows in canvas", debuggingEnabled = 0)
470        self.writeLogFileCB  = OWGUI.checkBox(generalBox, self.settings, "writeLogFile", "Save content of the Output window to a log file", debuggingEnabled = 0)
471        self.showSignalNamesCB = OWGUI.checkBox(generalBox, self.settings, "showSignalNames", "Show signal names between widgets", debuggingEnabled = 0)
472        self.dontAskBeforeCloseCB= OWGUI.checkBox(generalBox, self.settings, "dontAskBeforeClose", "Don't ask to save schema before closing", debuggingEnabled = 0)
473        self.saveWidgetsPositionCB = OWGUI.checkBox(generalBox, self.settings, "saveWidgetsPosition", "Save size and position of widgets", debuggingEnabled = 0)
474        self.useContextsCB = OWGUI.checkBox(generalBox, self.settings, "useContexts", "Use context settings")
475
476        validator = QIntValidator(self)
477        validator.setRange(0,10000)
478
479        hbox1 = OWGUI.widgetBox(GeneralTab, orientation = "horizontal")
480        hbox2 = OWGUI.widgetBox(GeneralTab, orientation = "horizontal")
481        canvasDlgSettings = OWGUI.widgetBox(hbox1, "Canvas Dialog Settings")
482#        schemeSettings = OWGUI.widgetBox(hbox1, "Scheme Settings")
483         
484#        self.widthSlider = OWGUI.qwtHSlider(canvasDlgSettings, self.settings, "canvasWidth", minValue = 300, maxValue = 1200, label = "Canvas width:  ", step = 50, precision = " %.0f px", debuggingEnabled = 0)
485#        self.heightSlider = OWGUI.qwtHSlider(canvasDlgSettings, self.settings, "canvasHeight", minValue = 300, maxValue = 1200, label = "Canvas height:  ", step = 50, precision = " %.0f px", debuggingEnabled = 0)
486#        OWGUI.separator(canvasDlgSettings)
487       
488        items = [str(n) for n in QStyleFactory.keys()]
489        itemsLower = [s.lower() for s in items]
490        ind = itemsLower.index(self.settings.get("style", "Windows").lower())
491        self.settings["style"] = items[ind]
492        OWGUI.comboBox(canvasDlgSettings, self.settings, "style", label = "Window style:", orientation = "horizontal", items = [str(n) for n in QStyleFactory.keys()], sendSelectedValue = 1, debuggingEnabled = 0)
493        OWGUI.checkBox(canvasDlgSettings, self.settings, "useDefaultPalette", "Use style's standard palette", debuggingEnabled = 0)
494       
495        OWGUI.separator(canvasDlgSettings)
496        OWGUI.comboBox(canvasDlgSettings, self.settings, "widgetListType", label="Toolbox style:", orientation="horizontal",
497                       items = ["Tool box", "Tree view", "Tree view (no icons)", "Tabs without labels", "Tabs with labels"], debuggingEnabled=0)
498       
499#        if canvasDlg:
500#            selectedWidgetBox = OWGUI.widgetBox(schemeSettings, orientation = "horizontal")
501#            self.selectedWidgetIcon = ColorIcon(selectedWidgetBox, canvasDlg.widgetSelectedColor)
502#            selectedWidgetBox.layout().addWidget(self.selectedWidgetIcon)
503#            selectedWidgetLabel = OWGUI.widgetLabel(selectedWidgetBox, " Selected widget")
504#
505#            activeWidgetBox = OWGUI.widgetBox(schemeSettings, orientation = "horizontal")
506#            self.activeWidgetIcon = ColorIcon(activeWidgetBox, canvasDlg.widgetActiveColor)
507#            activeWidgetBox.layout().addWidget(self.activeWidgetIcon)
508#            selectedWidgetLabel = OWGUI.widgetLabel(activeWidgetBox, " Active widget")
509#
510#            lineBox = OWGUI.widgetBox(schemeSettings, orientation = "horizontal")
511#            self.lineIcon = ColorIcon(lineBox, canvasDlg.lineColor)
512#            lineBox.layout().addWidget(self.lineIcon)
513#            selectedWidgetLabel = OWGUI.widgetLabel(lineBox, " Lines")
514#           
515#        OWGUI.separator(schemeSettings)
516#        items = ["%d x %d" % (v,v) for v in self.canvasDlg.schemeIconSizeList]
517#        val = min(len(items)-1, self.settings['schemeIconSize'])
518#        self.schemeIconSizeCombo = OWGUI.comboBoxWithCaption(schemeSettings, self.settings, 'schemeIconSize', "Scheme icon size:", items = items, tooltip = "Set the size of the widget icons on the scheme", debuggingEnabled = 0)
519       
520        GeneralTab.layout().addStretch(1)
521
522        # #################################################################
523        # EXCEPTION TAB
524        exceptions = OWGUI.widgetBox(ExceptionsTab, "Exceptions")
525        #self.catchExceptionCB = QCheckBox('Catch exceptions', exceptions)
526        self.focusOnCatchExceptionCB = OWGUI.checkBox(exceptions, self.settings, "focusOnCatchException", 'Show output window on exception')
527        self.printExceptionInStatusBarCB = OWGUI.checkBox(exceptions, self.settings, "printExceptionInStatusBar", 'Print last exception in status bar')
528
529        output = OWGUI.widgetBox(ExceptionsTab, "System output")
530        #self.catchOutputCB = QCheckBox('Catch system output', output)
531        self.focusOnCatchOutputCB = OWGUI.checkBox(output, self.settings, "focusOnCatchOutput", 'Focus output window on system output')
532        self.printOutputInStatusBarCB = OWGUI.checkBox(output, self.settings, "printOutputInStatusBar", 'Print last system output in status bar')
533
534        hboxExc = OWGUI.widgetBox(ExceptionsTab, orientation="horizontal")
535        outputCanvas = OWGUI.widgetBox(hboxExc, "Canvas Info Handling")
536        outputWidgets = OWGUI.widgetBox(hboxExc, "Widget Info Handling")
537        self.ocShow = OWGUI.checkBox(outputCanvas, self.settings, "ocShow", 'Show icon above widget for...')
538        indent = OWGUI.checkButtonOffsetHint(self.ocShow)
539        self.ocInfo = OWGUI.checkBox(OWGUI.indentedBox(outputCanvas, indent), self.settings, "ocInfo", 'Information')
540        self.ocWarning = OWGUI.checkBox(OWGUI.indentedBox(outputCanvas, indent), self.settings, "ocWarning", 'Warnings')
541        self.ocError = OWGUI.checkBox(OWGUI.indentedBox(outputCanvas, indent), self.settings, "ocError", 'Errors')
542
543        self.owShow = OWGUI.checkBox(outputWidgets, self.settings, "owShow", 'Show statusbar info for...')
544        self.owInfo = OWGUI.checkBox(OWGUI.indentedBox(outputWidgets, indent), self.settings, "owInfo", 'Information')
545        self.owWarning = OWGUI.checkBox(OWGUI.indentedBox(outputWidgets, indent), self.settings, "owWarning", 'Warnings')
546        self.owError = OWGUI.checkBox(OWGUI.indentedBox(outputWidgets, indent), self.settings, "owError", 'Errors')
547
548        verbosityBox = OWGUI.widgetBox(ExceptionsTab, "Verbosity", orientation = "horizontal")
549        verbosityBox.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))
550        self.verbosityCombo = OWGUI.comboBox(verbosityBox, self.settings, "outputVerbosity", label = "Set level of widget output: ", orientation='horizontal', items=["Small", "Medium", "High"])
551        ExceptionsTab.layout().addStretch(1)
552
553        # #################################################################
554        # TAB ORDER TAB
555        tabOrderBox = OWGUI.widgetBox(TabOrderTab, "Set Order of Widget Categories", orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
556        self.tabOrderList = QListWidget(tabOrderBox)
557        self.tabOrderList.setAcceptDrops(True)
558
559        tabOrderBox.layout().addWidget(self.tabOrderList)
560        self.tabOrderList.setSelectionMode(QListWidget.SingleSelection)
561       
562        ind = 0
563        for (name, show) in self.settings["WidgetTabs"]:
564            if self.canvasDlg.widgetRegistry.has_key(name):
565                self.tabOrderList.addItem(name)
566                self.tabOrderList.item(ind).setCheckState(show and Qt.Checked or Qt.Unchecked)
567                ind+=1
568
569        w = OWGUI.widgetBox(tabOrderBox, sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
570        self.upButton = OWGUI.button(w, self, "Up", callback = self.moveUp)
571        self.downButton = OWGUI.button(w, self, "Down", callback = self.moveDown)
572#        w.layout().addSpacing(20)
573#        self.addButton = OWGUI.button(w, self, "Add", callback = self.addCategory)
574#        self.removeButton = OWGUI.button(w, self, "Remove", callback = self.removeCategory)
575#        self.removeButton.setEnabled(0)
576        w.layout().addStretch(1)
577
578        # OK, Cancel buttons
579        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
580        hbox.layout().addStretch(1)
581        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
582        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
583        self.connect(self.tabOrderList, SIGNAL("currentRowChanged(int)"), self.enableDisableButtons)
584
585        self.topLayout.addWidget(self.tabs)
586        self.topLayout.addWidget(hbox)
587
588
589    def accept(self):
590#        self.settings["widgetSelectedColor"] = self.selectedWidgetIcon.color.getRgb()
591#        self.settings["widgetActiveColor"]   = self.activeWidgetIcon.color.getRgb()
592#        self.settings["lineColor"]           = self.lineIcon.color.getRgb()
593        QDialog.accept(self)
594       
595       
596
597    # move selected widget category up
598    def moveUp(self):
599        for i in range(1, self.tabOrderList.count()):
600            if self.tabOrderList.item(i).isSelected():
601                item = self.tabOrderList.takeItem(i)
602                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
603                self.tabOrderList.insertItem(i-1, item)
604                item.setSelected(1)
605
606    # move selected widget category down
607    def moveDown(self):
608        for i in range(self.tabOrderList.count()-2,-1,-1):
609            if self.tabOrderList.item(i).isSelected():
610                item = self.tabOrderList.takeItem(i)
611                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
612                self.tabOrderList.insertItem(i+1, item)
613                item.setSelected(1)
614
615    def enableDisableButtons(self, itemIndex):
616        self.upButton.setEnabled(itemIndex > 0)
617        self.downButton.setEnabled(itemIndex < self.tabOrderList.count()-1)
618        catName = str(self.tabOrderList.currentItem().text())
619        if not self.canvasDlg.widgetRegistry.has_key(catName): return
620#        self.removeButton.setEnabled( all([os.path.normpath(self.canvasDlg.widgetDir) not in os.path.normpath(x.directory) and
621#                                           os.path.normpath(self.canvasDlg.addOnsDir) not in os.path.normpath(x.directory)
622#                                             for x in self.canvasDlg.widgetRegistry[catName].values()]))
623        #self.removeButton.setEnabled(1)
624
625#    def addCategory(self):
626#        dir = str(QFileDialog.getExistingDirectory(self, "Select the folder that contains the add-on:"))
627#        if dir != "":
628#            if os.path.split(dir)[1] == "widgets":     # register a dir above the dir that contains the widget folder
629#                dir = os.path.split(dir)[0]
630#            if os.path.exists(os.path.join(dir, "widgets")):
631#                name = os.path.split(dir)[1]
632#                self.toAdd.append((name, dir))
633#                self.tabOrderList.addItem(name)
634#                self.tabOrderList.item(self.tabOrderList.count()-1).setCheckState(Qt.Checked)
635#            else:
636#                QMessageBox.information( None, "Information", 'The specified folder does not seem to contain an Orange add-on.', QMessageBox.Ok + QMessageBox.Default)
637#           
638#       
639#    def removeCategory(self):
640#        curCat = str(self.tabOrderList.item(self.tabOrderList.currentRow()).text())
641#        if QMessageBox.warning(self,'Orange Canvas', "Unregister widget category '%s' from Orange canvas?\nThis will not remove any files." % curCat, QMessageBox.Ok , QMessageBox.Cancel | QMessageBox.Default | QMessageBox.Escape) == QMessageBox.Ok:
642#            self.toRemove.append((curCat, self.canvasDlg.widgetRegistry[curCat]))
643#            item = self.tabOrderList.takeItem(self.tabOrderList.row(self.tabOrderList.currentItem()))
644#            #if item: item.setHidden(1)
645
646class KeyEdit(QLineEdit):
647    def __init__(self, parent, key, invdict, widget, invInvDict):
648        QLineEdit.__init__(self, parent)
649        self.setText(key)
650        #self.setReadOnly(True)
651        self.invdict = invdict
652        self.widget = widget
653        self.invInvDict = invInvDict
654
655    def keyPressEvent(self, e):
656        if e.key() == Qt.Key_Delete or e.key() == Qt.Key_Backspace:
657            pressed = "<none>"
658            self.setText(pressed)
659            prevkey = self.invdict.get(self.widget)
660            if prevkey:
661                del self.invdict[self.widget]
662                del self.invInvDict[prevkey]
663            return
664
665        if e.key() not in range(32, 128): # + range(Qt.Key_F1, Qt.Key_F35+1): -- this wouldn't work, see the line below, and also writing to file etc.
666            e.ignore()
667            return
668
669        pressed = "-".join(filter(None, [e.modifiers() & x and y for x, y in [(Qt.ControlModifier, "Ctrl"), (Qt.AltModifier, "Alt")]]) + [chr(e.key())])
670
671        assigned = self.invInvDict.get(pressed, None)
672        if assigned and assigned != self and QMessageBox.question(self, "Confirmation", "'%(pressed)s' is already assigned to '%(assigned)s'. Override?" % {"pressed": pressed, "assigned": assigned.widget.name}, QMessageBox.Yes | QMessageBox.Default, QMessageBox.No | QMessageBox.Escape) == QMessageBox.No:
673            return
674       
675        if assigned:
676            assigned.setText("<none>")
677            del self.invdict[assigned.widget]
678        self.setText(pressed)
679        self.invdict[self.widget] = pressed
680        self.invInvDict[pressed] = self
681
682class AddOnManagerSummary(QDialog):
683    def __init__(self, add, remove, upgrade, *args):
684        apply(QDialog.__init__,(self,) + args)
685        self.setWindowTitle("Pending Actions")
686        self.topLayout = QVBoxLayout(self)
687        self.topLayout.setSpacing(10)
688        self.resize(200, 0)
689       
690        OWGUI.label(self, self, "If you confirm, the following actions will take place:")
691
692        self.memo = memo = QTextEdit(self)
693        self.layout().addWidget(memo)
694        memo.setReadOnly(True)
695        memo.setFrameStyle(QFrame.NoFrame)
696        pal = QPalette()
697        pal.setColor(QPalette.Base, Qt.transparent)
698        memo.setPalette(pal)
699        memo.setLineWrapMode(QTextEdit.WidgetWidth)
700        memo.setWordWrapMode(QTextOption.WordWrap)
701        QObject.connect(memo.document().documentLayout(),
702                       SIGNAL("documentSizeChanged(const QSizeF &)"),
703                        lambda docSize: self.updateMinSize(docSize))
704        actions = []
705        for ao in add:
706            actions.append("Install %s." % ao)
707        for ao in remove:
708            actions.append("Remove %s." % ao)
709        for ao in upgrade:
710            actions.append("Upgrade %s." % ao)
711        memo.setText("\n".join(actions))
712       
713        self.layout().addStretch(1)
714        self.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) )
715
716        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
717        hbox.layout().addStretch(1)
718        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
719        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
720        self.okButton.setDefault(True)
721
722    def updateMinSize(self, documentSize):
723        self.memo.update()
724        self.memo.setMinimumHeight(min(300, documentSize.height() + 2 * self.memo.frameWidth()))
725
726class AddOnManagerDialog(QDialog):
727    def __init__(self, canvasDlg, *args):
728        apply(QDialog.__init__,(self,) + args)
729        self.canvasDlg = canvasDlg
730        self.setWindowTitle("Add-on Management")
731        self.topLayout = QVBoxLayout(self)
732        self.topLayout.setSpacing(0)
733        self.resize(600,500)
734        self.layout().setSizeConstraint(QLayout.SetMinimumSize)
735       
736        mainBox = OWGUI.widgetBox(self, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
737       
738        # View settings & Search
739       
740        self.groupByRepo = True
741        self.searchStr = ""
742        self.to_upgrade = set()
743
744        searchBox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
745
746        self.eSearch = self.lineEditSearch(searchBox, self, "searchStr", None, 0, tooltip = "Type in to filter (search) add-ons.", callbackOnType=True, callback=self.searchCallback)
747       
748        # Repository & Add-on tree
749       
750        repos = OWGUI.widgetBox(mainBox, "Add-ons", orientation = "horizontal", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored))
751        repos.layout().setSizeConstraint(QLayout.SetMinimumSize)
752        self.lst = lst = QListWidget(repos)
753        lst.setMinimumWidth(200)
754        lst.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred))
755        repos.layout().addWidget(lst)
756        QObject.connect(lst, SIGNAL("itemChanged(QListWidgetItem *)"), self.cbToggled)
757        QObject.connect(lst, SIGNAL("currentItemChanged(QListWidgetItem *, QListWidgetItem *)"), self.currentItemChanged)
758
759        import Orange.utils.addons
760
761        # Bottom info pane
762       
763        self.infoPane = infoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored))
764        infoPane.layout().setSizeConstraint(QLayout.SetMinimumSize)
765
766        pVerInstalled = OWGUI.widgetBox(infoPane, orientation="horizontal")
767        lblVerInstalled = OWGUI.label(pVerInstalled, self, "Installed version:", 150)
768        boldFont = lblVerInstalled.font()
769        boldFont.setWeight(QFont.Bold)
770        lblVerInstalled.setFont(boldFont)
771        self.lblVerInstalledValue = OWGUI.label(pVerInstalled, self, "-")
772        self.lblVerInstalledValue.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
773        pVerInstalled.layout().addSpacing(10)
774        self.lblStatus = OWGUI.label(pVerInstalled, self, "")
775        self.lblStatus.setFont(boldFont)
776        pVerInstalled.layout().addStretch(1)
777
778        pVerAvail = OWGUI.widgetBox(infoPane, orientation="horizontal")
779        lblVerAvail = OWGUI.label(pVerAvail, self, "Available version:", 150)
780        lblVerAvail.setFont(boldFont)
781        self.lblVerAvailValue = OWGUI.label(pVerAvail, self, "")
782        pVerAvail.layout().addSpacing(10)
783        self.upgradeButton = OWGUI.button(pVerAvail, self, "Upgrade", callback = self.upgrade)
784        self.upgradeButton.setFixedHeight(lblVerAvail.height())
785        self.donotUpgradeButton = OWGUI.button(pVerAvail, self, "Do not upgrade", callback = self.donotUpgrade)
786        self.donotUpgradeButton.setFixedHeight(lblVerAvail.height())
787        pVerAvail.layout().addStretch(1)
788       
789        pInfoBtns = OWGUI.widgetBox(infoPane, orientation="horizontal", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
790        self.webButton = OWGUI.button(pInfoBtns, self, "Open webpage", callback = self.openWebPage)
791        self.docButton = OWGUI.button(pInfoBtns, self, "Open documentation", callback = self.openDocsPage)
792        self.listWidgetsButton = OWGUI.button(pInfoBtns, self, "List widgets", callback = self.listWidgets)
793        pInfoBtns.layout().addStretch(1)
794
795        self.lblDescription = lblDescription = QTextEdit(infoPane)
796        infoPane.layout().addWidget(lblDescription)
797        lblDescription.setReadOnly(True)
798        lblDescription.setFrameStyle(QFrame.NoFrame)
799        pal = QPalette()
800        pal.setColor(QPalette.Base, Qt.transparent)
801        lblDescription.setPalette(pal)
802        lblDescription.setLineWrapMode(QTextEdit.WidgetWidth)
803        lblDescription.setWordWrapMode(QTextOption.WordWrap)
804
805        # Right panel
806       
807        self.rightPanel = rightPanel = OWGUI.widgetBox(repos, orientation = "vertical", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
808        rightPanel.layout().setSizeConstraint(QLayout.SetMinimumSize)
809        self.reloadRepoButton = OWGUI.button(rightPanel, self, "Refresh list", callback = self.reloadRepo)
810        rightPanel.layout().addSpacing(15)
811        self.upgradeAllButton = OWGUI.button(rightPanel, self, "Upgrade All", callback = self.upgradeAll)
812        rightPanel.layout().addStretch(1)
813        for btn in rightPanel.children():
814            if btn.__class__ is QPushButton:
815                btn.setMinimumHeight(btn.height())
816       
817        # Buttons
818        self.hbox = hbox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
819        busyBox = OWGUI.widgetBox(hbox, sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))  # A humble stretch.
820        self.busyLbl = OWGUI.label(busyBox, self, "")
821        self.progress = QProgressBar(hbox, sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
822        hbox.layout().addWidget(self.progress)
823        self.progress.setVisible(False)
824        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
825        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
826        self.okButton.setDefault(True)
827
828        self.refreshView()
829       
830    def updateDescMinSize(self, documentSize):
831        self.lblDescription.update()
832        self.lblDescription.setMinimumHeight(min(300, documentSize.height() + 2 * self.lblDescription.frameWidth()))
833        pass
834   
835    def accept(self):
836        self.to_upgrade.difference_update(self.to_remove())
837        import Orange.utils.addons
838        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade
839        if len(add) + len(remove) + len(upgrade) > 0:
840            summary = AddOnManagerSummary(add, remove, upgrade, self)
841            if summary.exec_() == QDialog.Rejected:
842                return
843
844        self.busy(True)
845        self.repaint()
846        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade
847        for name in upgrade:
848            try:
849                self.busy("Upgrading %s ..." % name)
850                self.repaint()
851                Orange.utils.addons.upgrade(name, self.pcb)
852            except Exception, e:
853                QMessageBox.critical(self, "Error", "Problem upgrading add-on %s: %s" % (name, e))
854        for name in remove:
855            try:
856                self.busy("Uninstalling %s ..." % name)
857                self.repaint()
858                Orange.utils.addons.uninstall(name, self.pcb)
859            except Exception, e:
860                QMessageBox.critical(self, "Error", "Problem uninstalling add-on %s: %s" % (name, e))
861        for name in add:
862            try:
863                self.busy("Installing %s ..." % name)
864                self.repaint()
865                Orange.utils.addons.install(name, self.pcb)
866            except Exception, e:
867                QMessageBox.critical(self, "Error", "Problem installing add-on %s: %s" % (name, e))
868
869        QDialog.accept(self)
870
871    def busy(self, b=True):
872        self.progress.setMaximum(1)
873        self.progress.setValue(0)
874        self.progress.setVisible(bool(b))
875        self.busyLbl.setText(b if isinstance(b, str) else "")
876        self.eSearch.setEnabled(not b)
877        self.lst.setEnabled(not b)
878        self.okButton.setEnabled(not b)
879        self.cancelButton.setEnabled(not b)
880        self.rightPanel.setEnabled(not b)
881        self.infoPane.setEnabled(not b)
882
883    def pcb(self, max, val):
884        self.progress.setMaximum(max)
885        self.progress.setValue(val)
886        self.progress.repaint()
887
888    def reloadRepo(self):
889        # Reload add-on list.
890        import Orange.utils.addons
891        try:
892            self.busy("Reloading add-on repository ...")
893            self.repaint()
894            Orange.utils.addons.refresh_available_addons(progress_callback = self.pcb)
895        except Exception, e:
896            QMessageBox.critical(self, "Error", "Could not reload repository: %s." % e)
897        finally:
898            self.busy(False)
899        # Finally, refresh the tree on GUI.
900        self.refreshView()
901           
902    def upgradeCandidates(self):
903        result = []
904        import Orange.utils.addons
905        for ao in Orange.utils.addons.addons.values():
906            if ao.installed_version and ao.available_version and ao.installed_version != ao.available_version:
907                result.append(ao.name)
908        return result
909   
910    def upgradeAll(self):
911        for candidate in self.upgradeCandidates():
912            self.upgrade(candidate, refresh=False)
913        self.refreshInfoPane()
914
915    def upgrade(self, name=None, refresh=True):
916        if not name:
917            name = self.getAddOnIdFromItem(self.lst.currentItem())
918        self.to_upgrade.add(name)
919        if refresh:
920            self.refreshInfoPane()
921
922    def openWebPage(self):
923        addon = self.getAddOnFromItem(self.lst.currentItem())
924        if addon and addon.homepage:
925            import webbrowser
926            webbrowser.open(addon.homepage)
927
928    def openDocsPage(self):
929        addon = self.getAddOnFromItem(self.lst.currentItem())
930        if addon and addon.docs_url:
931            import webbrowser
932            webbrowser.open(addon.docs_url)
933
934    def listWidgets(self):
935        addOn = self.getAddOnFromItem(self.lst.currentItem())
936        if not addOn: return
937        import Orange.utils.addons
938        if addOn.__class__ is not Orange.utils.addons.OrangeAddOnInRepo: return
939        if not addOn.repository.has_web_script: return
940        self.canvasDlg.helpWindow.open("%s/addOnServer.py/%s/doc/widgets/" % (addOn.repository.url, addOn.filename), modal=True)
941       
942       
943    def donotUpgrade(self):
944        id = self.getAddOnIdFromItem(self.lst.currentItem())
945        self.to_upgrade.remove(id)
946        self.refreshInfoPane()
947
948    def cbToggled(self, item):
949        ao = self.getAddOnFromItem(item)
950        if ao and not has_pip and ao.installed_version and item.checkState()==Qt.Unchecked:
951            QMessageBox.warning(self, "Unable to uninstall", "Pip is not installed on your system. Without it, automated removal of add-ons is not possible.\n\nInstall pip (try 'easy_install --user pip') and restart Orange to make this action possible.")
952            item.setCheckState(Qt.Checked)
953        self.refreshInfoPane(item)
954
955    def lineEditSearch(self, *args, **props):
956        return OWGUI.lineEdit(*args, **props)
957
958    def getAddOnFromItem(self, item):
959        return getattr(item, "addon", None)
960
961    def getAddOnIdFromItem(self, item):
962        addon = self.getAddOnFromItem(item)
963        return addon.name if addon else None
964       
965    def refreshInfoPane(self, item=None):
966        if not item:
967            item = self.lst.currentItem()
968        addon = None
969        if item:
970            import Orange.utils.addons
971            import orngEnviron
972            addon = self.getAddOnFromItem(item)
973        if addon:
974            self.lblDescription.setText(addon.summary.strip() +"\n"+ addon.description.strip())
975            self.lblVerAvailValue.setText(addon.available_version)
976
977            self.lblVerInstalledValue.setText(addon.installed_version if addon.installed_version else "-") #TODO Tell whether it's a system-wide installation
978            self.upgradeButton.setVisible(bool(addon.installed_version and addon.installed_version!=addon.available_version) and addon.name not in self.to_upgrade) #TODO Disable if it's a system-wide installation
979            self.donotUpgradeButton.setVisible(addon.name in self.to_upgrade)
980            self.webButton.setVisible(bool(addon.homepage))
981            self.docButton.setVisible(bool(addon.docs_url))
982            self.listWidgetsButton.setVisible(False) #TODO A list of widgets is not available
983
984            if not addon.installed_version and item.checkState()==Qt.Checked:
985                self.lblStatus.setText("marked for installation")
986            elif addon.installed_version and item.checkState()!=Qt.Checked:
987                self.lblStatus.setText("marked for removal")
988            elif addon.name in self.to_upgrade:
989                self.lblStatus.setText("marked for upgrade")
990            else:
991                self.lblStatus.setText("")
992
993            self.infoPane.setVisible(True)
994        else:
995            self.infoPane.setVisible(False)
996        self.enableDisableButtons()
997
998    def enableDisableButtons(self):
999        import Orange.utils.addons
1000        aos = Orange.utils.addons.addons.values()
1001        self.upgradeAllButton.setEnabled(any(ao.installed_version and ao.available_version and
1002                                             ao.installed_version != ao.available_version and
1003                                             ao.name not in self.to_upgrade for ao in aos))
1004       
1005    def currentItemChanged(self, new, previous):
1006        # Refresh info pane & button states
1007        self.refreshInfoPane(new)
1008
1009    def addAddOnsToTree(self, addon_dict, selected=None, to_install=[], to_remove=[]):
1010        # Sort alphabetically
1011        addons = sorted(list(addon_dict.items()),
1012                        key = lambda (name, ao): name)
1013
1014        for (i, (name, ao)) in enumerate(addons):
1015            item = QListWidgetItem()
1016            self.lst.addItem(item)
1017            item.setText(name)
1018            item.setCheckState(Qt.Checked if ao.installed_version and not name in to_remove or name in to_install else Qt.Unchecked)
1019            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled)
1020            item.addon = ao
1021            if name == selected:
1022                self.lst.setCurrentItem(item)
1023
1024            item.disableToggleSignal = False
1025
1026    def lst_items(self):
1027        for i in xrange(self.lst.count()):
1028            yield self.lst.item(i)
1029
1030    def to_install(self):
1031        return set([item.addon.name for item in self.lst_items()
1032                    if item.checkState()==Qt.Checked and not item.addon.installed_version])
1033
1034    def to_remove(self):
1035        return set([item.addon.name for item in self.lst_items()
1036                    if item.checkState()!=Qt.Checked and item.addon.installed_version])
1037
1038    def refreshView(self, selectedRegisteredAddOnId=None):
1039        import Orange
1040        # Save current item selection
1041        selected_addon = self.getAddOnIdFromItem(self.lst.currentItem())
1042        to_install = self.to_install()
1043        to_remove = self.to_remove()
1044        #TODO: Save the next repository selection too, in case the current one was deleted
1045
1046        # Clear the tree
1047        self.lst.clear()
1048       
1049        # Add repositories and add-ons
1050        addons = {}
1051        for name in Orange.utils.addons.search_index(self.searchStr):
1052            addons[name] = Orange.utils.addons.addons[name]
1053        self.addAddOnsToTree(addons, selected = selected_addon, to_install=to_install, to_remove=to_remove)
1054        self.refreshInfoPane()
1055
1056        #TODO Should we somehow show the legacy registered addons?
1057
1058    def searchCallback(self):
1059        self.refreshView()
1060   
1061# widget shortcuts dialog
1062class WidgetShortcutDlg(QDialog):
1063    def __init__(self, canvasDlg, *args):
1064        import orngTabs
1065
1066        apply(QDialog.__init__,(self,) + args)
1067        self.canvasDlg = canvasDlg
1068        self.setWindowTitle("Widget Shortcuts")
1069        self.setLayout(QVBoxLayout())
1070        self.layout().setSpacing(10)
1071        self.resize(700,500)
1072
1073        self.invDict = dict([(y, x) for x, y in canvasDlg.widgetShortcuts.items()])
1074        invInvDict = {}
1075
1076        self.tabs = QTabWidget(self)
1077       
1078        extraTabs = [(name, 1) for name in canvasDlg.widgetRegistry.keys() if name not in [tab for (tab, s) in canvasDlg.settings["WidgetTabs"]]]
1079        for tabName, show in canvasDlg.settings["WidgetTabs"] + extraTabs:
1080            if not canvasDlg.widgetRegistry.has_key(tabName):
1081                continue
1082            scrollArea = QScrollArea()
1083            self.tabs.addTab(scrollArea, tabName)
1084            #scrollArea.setWidgetResizable(1)       # you have to use this or set size to wtab manually - otherwise nothing gets shown
1085
1086            wtab = QWidget(self.tabs)
1087            scrollArea.setWidget(wtab)
1088
1089            widgets = [(int(widgetInfo.priority), name, widgetInfo) for (name, widgetInfo) in canvasDlg.widgetRegistry[tabName].items()]
1090            widgets.sort()
1091            rows = (len(widgets)+2) / 3
1092            layout = QGridLayout(wtab)
1093
1094            for i, (priority, name, widgetInfo) in enumerate(widgets):
1095                x = i / rows
1096                y = i % rows
1097
1098                hlayout = QHBoxLayout()
1099                mainBox = QWidget(wtab)
1100                mainBox.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
1101                mainBox.setLayout(hlayout)
1102                layout.addWidget(mainBox, y, x, Qt.AlignTop | Qt.AlignLeft)
1103                label = QLabel(wtab)
1104                label.setPixmap(canvasDlg.getWidgetIcon(widgetInfo).pixmap(40))
1105                hlayout.addWidget(label)
1106
1107                optionsw = QWidget(self)
1108                optionsw.setLayout(QVBoxLayout())
1109                hlayout.addWidget(optionsw)
1110                optionsw.layout().addStretch(1)
1111
1112                OWGUI.widgetLabel(optionsw, name)
1113                key = self.invDict.get(widgetInfo, "<none>")
1114                le = KeyEdit(optionsw, key, self.invDict, widgetInfo, invInvDict)
1115                optionsw.layout().addWidget(le)
1116                invInvDict[key] = le
1117                le.setFixedWidth(60)
1118
1119            wtab.resize(wtab.sizeHint())
1120
1121        # OK, Cancel buttons
1122        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1123        hbox.layout().addStretch(1)
1124        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
1125        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
1126        self.okButton.setDefault(True)
1127
1128        self.layout().addWidget(self.tabs)
1129        self.layout().addWidget(hbox)
1130
1131
1132class AboutDlg(QDialog):
1133    def __init__(self, *args):
1134        QDialog.__init__(self, *args)
1135        self.topLayout = QVBoxLayout(self)
1136#        self.setWindowFlags(Qt.Popup)       # Commented out, because it makes the window appear in the top-left corner on Linux
1137        self.setWindowTitle("About Orange")
1138       
1139        import orngEnviron
1140        import Orange
1141        import re
1142        logoImage = QPixmap(os.path.join(orngEnviron.directoryNames["canvasDir"], "icons", "splash.png"))
1143        logo = OWGUI.widgetLabel(self, "")
1144        logo.setPixmap(logoImage)
1145       
1146        OWGUI.widgetLabel(self, '<p align="center"><h2>Orange</h2></p>') 
1147       
1148        default_version_str = Orange.__version__
1149        built_on = re.findall("\((.*?)\)", Orange.orange.version)
1150        if built_on:
1151            built_on_str = " (built on " + built_on[0].split(",")[-1] + ")"
1152        else:
1153            built_on_str = ""
1154        try:
1155            import Orange.version as version
1156            short_version = version.short_version
1157            hg_revision = version.hg_revision
1158            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (short_version + built_on_str))
1159            if not version.release:
1160                OWGUI.widgetLabel(self, '<p align="center">(hg revision %s)</p>' % (hg_revision))
1161        except ImportError:
1162            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (default_version_str + built_on_str))
1163        OWGUI.widgetLabel(self, "" )
1164        #OWGUI.button(self, self, "Close", callback = self.accept)
1165        b = QDialogButtonBox(self)
1166        b.setCenterButtons(1)
1167        self.layout().addWidget(b)
1168        butt = b.addButton(QDialogButtonBox.Close)
1169        self.connect(butt, SIGNAL("clicked()"), self.accept)
1170       
1171
1172class saveApplicationDlg(QDialog):
1173    def __init__(self, *args):
1174        import Orange.utils.addons
1175       
1176        apply(QDialog.__init__,(self,) + args)
1177        self.setWindowTitle("Set Widget Order")
1178        self.setLayout(QVBoxLayout())
1179
1180        listbox = OWGUI.widgetBox(self, 1, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
1181        self.tabOrderList = QListWidget(listbox)
1182        self.tabOrderList.setSelectionMode(QListWidget.SingleSelection)
1183        listbox.layout().addWidget(self.tabOrderList)
1184
1185        w = OWGUI.widgetBox(listbox, sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
1186        self.upButton = OWGUI.button(w, self, "Up", callback = self.moveUp)
1187        self.downButton = OWGUI.button(w, self, "Down", callback = self.moveDown)
1188        w.layout().addStretch(1)
1189
1190        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1191        OWGUI.button(hbox, self, "Add Separator", callback = self.insertSeparator)
1192        hbox.layout().addStretch(1)
1193        OWGUI.button(hbox, self, "&OK", callback = self.accept)
1194        OWGUI.button(hbox, self, "&Cancel", callback = self.reject)
1195
1196        self.resize(200,250)
1197
1198    def accept(self):
1199        self.shownWidgetList = [str(self.tabOrderList.item(i).text()) for i in range(self.tabOrderList.count())]
1200        QDialog.accept(self)
1201
1202    def insertSeparator(self):
1203        curr = self.tabOrderList.indexFromItem(self.tabOrderList.currentItem()).row()
1204        self.insertWidgetName("[Separator]", curr)
1205
1206    def insertWidgetName(self, name, index = -1):
1207        if index == -1:
1208            self.tabOrderList.addItem(name)
1209        else:
1210            self.tabOrderList.insertItem(index, name)
1211
1212    # move selected widget category up
1213    def moveUp(self):
1214        for i in range(1, self.tabOrderList.count()):
1215            if self.tabOrderList.item(i).isSelected():
1216                item = self.tabOrderList.takeItem(i)
1217                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
1218                self.tabOrderList.insertItem(i-1, item)
1219                item.setSelected(1)
1220
1221
1222    # move selected widget category down
1223    def moveDown(self):
1224        for i in range(self.tabOrderList.count()-2,-1,-1):
1225            if self.tabOrderList.item(i).isSelected():
1226                item = self.tabOrderList.takeItem(i)
1227                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
1228                self.tabOrderList.insertItem(i+1, item)
1229                item.setSelected(1)
1230
1231
1232if __name__=="__main__":
1233    import sys
1234    app = QApplication(sys.argv)
1235    #dlg = saveApplicationDlg(None)
1236    dlg = AboutDlg(None)
1237    dlg.show()
1238    app.exec_()
Note: See TracBrowser for help on using the repository browser.