source: orange/Orange/OrangeCanvas/orngDlgs.py @ 11072:0c1237ec0883

Revision 11072:0c1237ec0883, 59.5 KB checked in by Matija Polajnar <matija.polajnar@…>, 16 months ago (diff)

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