source: orange/Orange/OrangeCanvas/orngDlgs.py @ 11026:1394738986c7

Revision 11026:1394738986c7, 59.3 KB checked in by Matija Polajnar <matija.polajnar@…>, 17 months ago (diff)

Add-ons: warn users that Orange should be restarted after updating or removal of add-ons.

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        if len(upgrade) > 0:
870            QMessageBox.warning(self, "Restart Orange", "After upgrading add-ons, it is very important to restart Orange to make sure the changes have been applied.")
871        elif len(remove) > 0:  # Don't bother with this if there has already been one (more important) warning.
872            QMessageBox.warning(self, "Restart Orange", "After removal of add-ons, it is suggested that you restart Orange for the changes to become effective.")
873
874        QDialog.accept(self)
875
876    def busy(self, b=True):
877        self.progress.setMaximum(1)
878        self.progress.setValue(0)
879        self.progress.setVisible(bool(b))
880        self.busyLbl.setText(b if isinstance(b, str) else "")
881        self.eSearch.setEnabled(not b)
882        self.lst.setEnabled(not b)
883        self.okButton.setEnabled(not b)
884        self.cancelButton.setEnabled(not b)
885        self.rightPanel.setEnabled(not b)
886        self.infoPane.setEnabled(not b)
887
888    def pcb(self, max, val):
889        self.progress.setMaximum(max)
890        self.progress.setValue(val)
891        self.progress.repaint()
892
893    def reloadRepo(self):
894        # Reload add-on list.
895        import Orange.utils.addons
896        try:
897            self.busy("Reloading add-on repository ...")
898            self.repaint()
899            Orange.utils.addons.refresh_available_addons(progress_callback = self.pcb)
900        except Exception, e:
901            QMessageBox.critical(self, "Error", "Could not reload repository: %s." % e)
902        finally:
903            self.busy(False)
904        # Finally, refresh the tree on GUI.
905        self.refreshView()
906           
907    def upgradeCandidates(self):
908        result = []
909        import Orange.utils.addons
910        for ao in Orange.utils.addons.addons.values():
911            if ao.installed_version and ao.available_version and ao.installed_version != ao.available_version:
912                result.append(ao.name)
913        return result
914   
915    def upgradeAll(self):
916        for candidate in self.upgradeCandidates():
917            self.upgrade(candidate, refresh=False)
918        self.refreshInfoPane()
919
920    def upgrade(self, name=None, refresh=True):
921        if not name:
922            name = self.getAddOnIdFromItem(self.lst.currentItem())
923        self.to_upgrade.add(name)
924        if refresh:
925            self.refreshInfoPane()
926
927    def openWebPage(self):
928        addon = self.getAddOnFromItem(self.lst.currentItem())
929        if addon and addon.homepage:
930            import webbrowser
931            webbrowser.open(addon.homepage)
932
933    def openDocsPage(self):
934        addon = self.getAddOnFromItem(self.lst.currentItem())
935        if addon and addon.docs_url:
936            import webbrowser
937            webbrowser.open(addon.docs_url)
938
939    def listWidgets(self):
940        addOn = self.getAddOnFromItem(self.lst.currentItem())
941        if not addOn: return
942        import Orange.utils.addons
943        if addOn.__class__ is not Orange.utils.addons.OrangeAddOnInRepo: return
944        if not addOn.repository.has_web_script: return
945        self.canvasDlg.helpWindow.open("%s/addOnServer.py/%s/doc/widgets/" % (addOn.repository.url, addOn.filename), modal=True)
946       
947       
948    def donotUpgrade(self):
949        id = self.getAddOnIdFromItem(self.lst.currentItem())
950        self.to_upgrade.remove(id)
951        self.refreshInfoPane()
952
953    def cbToggled(self, item):
954        ao = self.getAddOnFromItem(item)
955        if ao and not has_pip and ao.installed_version and item.checkState()==Qt.Unchecked:
956            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.")
957            item.setCheckState(Qt.Checked)
958        self.refreshInfoPane(item)
959
960    def lineEditSearch(self, *args, **props):
961        return OWGUI.lineEdit(*args, **props)
962
963    def getAddOnFromItem(self, item):
964        return getattr(item, "addon", None)
965
966    def getAddOnIdFromItem(self, item):
967        addon = self.getAddOnFromItem(item)
968        return addon.name if addon else None
969       
970    def refreshInfoPane(self, item=None):
971        if not item:
972            item = self.lst.currentItem()
973        addon = None
974        if item:
975            import Orange.utils.addons
976            import orngEnviron
977            addon = self.getAddOnFromItem(item)
978        if addon:
979            self.lblDescription.setText(addon.summary.strip() +"\n"+ addon.description.strip())
980            self.lblVerAvailValue.setText(addon.available_version)
981
982            self.lblVerInstalledValue.setText(addon.installed_version if addon.installed_version else "-") #TODO Tell whether it's a system-wide installation
983            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
984            self.donotUpgradeButton.setVisible(addon.name in self.to_upgrade)
985            self.webButton.setVisible(bool(addon.homepage))
986            self.docButton.setVisible(bool(addon.docs_url))
987            self.listWidgetsButton.setVisible(False) #TODO A list of widgets is not available
988
989            if not addon.installed_version and item.checkState()==Qt.Checked:
990                self.lblStatus.setText("marked for installation")
991            elif addon.installed_version and item.checkState()!=Qt.Checked:
992                self.lblStatus.setText("marked for removal")
993            elif addon.name in self.to_upgrade:
994                self.lblStatus.setText("marked for upgrade")
995            else:
996                self.lblStatus.setText("")
997
998            self.infoPane.setVisible(True)
999        else:
1000            self.infoPane.setVisible(False)
1001        self.enableDisableButtons()
1002
1003    def enableDisableButtons(self):
1004        import Orange.utils.addons
1005        aos = Orange.utils.addons.addons.values()
1006        self.upgradeAllButton.setEnabled(any(ao.installed_version and ao.available_version and
1007                                             ao.installed_version != ao.available_version and
1008                                             ao.name not in self.to_upgrade for ao in aos))
1009       
1010    def currentItemChanged(self, new, previous):
1011        # Refresh info pane & button states
1012        self.refreshInfoPane(new)
1013
1014    def addAddOnsToTree(self, addon_dict, selected=None, to_install=[], to_remove=[]):
1015        # Sort alphabetically
1016        addons = sorted(list(addon_dict.items()),
1017                        key = lambda (name, ao): name)
1018
1019        for (i, (name, ao)) in enumerate(addons):
1020            item = QListWidgetItem()
1021            self.lst.addItem(item)
1022            item.setText(name)
1023            item.setCheckState(Qt.Checked if ao.installed_version and not name in to_remove or name in to_install else Qt.Unchecked)
1024            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled)
1025            item.addon = ao
1026            if name == selected:
1027                self.lst.setCurrentItem(item)
1028
1029            item.disableToggleSignal = False
1030
1031    def lst_items(self):
1032        for i in xrange(self.lst.count()):
1033            yield self.lst.item(i)
1034
1035    def to_install(self):
1036        return set([item.addon.name for item in self.lst_items()
1037                    if item.checkState()==Qt.Checked and not item.addon.installed_version])
1038
1039    def to_remove(self):
1040        return set([item.addon.name for item in self.lst_items()
1041                    if item.checkState()!=Qt.Checked and item.addon.installed_version])
1042
1043    def refreshView(self, selectedRegisteredAddOnId=None):
1044        import Orange
1045        # Save current item selection
1046        selected_addon = self.getAddOnIdFromItem(self.lst.currentItem())
1047        to_install = self.to_install()
1048        to_remove = self.to_remove()
1049        #TODO: Save the next repository selection too, in case the current one was deleted
1050
1051        # Clear the tree
1052        self.lst.clear()
1053       
1054        # Add repositories and add-ons
1055        addons = {}
1056        for name in Orange.utils.addons.search_index(self.searchStr):
1057            addons[name] = Orange.utils.addons.addons[name]
1058        self.addAddOnsToTree(addons, selected = selected_addon, to_install=to_install, to_remove=to_remove)
1059        self.refreshInfoPane()
1060
1061        #TODO Should we somehow show the legacy registered addons?
1062
1063    def searchCallback(self):
1064        self.refreshView()
1065   
1066# widget shortcuts dialog
1067class WidgetShortcutDlg(QDialog):
1068    def __init__(self, canvasDlg, *args):
1069        import orngTabs
1070
1071        apply(QDialog.__init__,(self,) + args)
1072        self.canvasDlg = canvasDlg
1073        self.setWindowTitle("Widget Shortcuts")
1074        self.setLayout(QVBoxLayout())
1075        self.layout().setSpacing(10)
1076        self.resize(700,500)
1077
1078        self.invDict = dict([(y, x) for x, y in canvasDlg.widgetShortcuts.items()])
1079        invInvDict = {}
1080
1081        self.tabs = QTabWidget(self)
1082       
1083        extraTabs = [(name, 1) for name in canvasDlg.widgetRegistry.keys() if name not in [tab for (tab, s) in canvasDlg.settings["WidgetTabs"]]]
1084        for tabName, show in canvasDlg.settings["WidgetTabs"] + extraTabs:
1085            if not canvasDlg.widgetRegistry.has_key(tabName):
1086                continue
1087            scrollArea = QScrollArea()
1088            self.tabs.addTab(scrollArea, tabName)
1089            #scrollArea.setWidgetResizable(1)       # you have to use this or set size to wtab manually - otherwise nothing gets shown
1090
1091            wtab = QWidget(self.tabs)
1092            scrollArea.setWidget(wtab)
1093
1094            widgets = [(int(widgetInfo.priority), name, widgetInfo) for (name, widgetInfo) in canvasDlg.widgetRegistry[tabName].items()]
1095            widgets.sort()
1096            rows = (len(widgets)+2) / 3
1097            layout = QGridLayout(wtab)
1098
1099            for i, (priority, name, widgetInfo) in enumerate(widgets):
1100                x = i / rows
1101                y = i % rows
1102
1103                hlayout = QHBoxLayout()
1104                mainBox = QWidget(wtab)
1105                mainBox.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
1106                mainBox.setLayout(hlayout)
1107                layout.addWidget(mainBox, y, x, Qt.AlignTop | Qt.AlignLeft)
1108                label = QLabel(wtab)
1109                label.setPixmap(canvasDlg.getWidgetIcon(widgetInfo).pixmap(40))
1110                hlayout.addWidget(label)
1111
1112                optionsw = QWidget(self)
1113                optionsw.setLayout(QVBoxLayout())
1114                hlayout.addWidget(optionsw)
1115                optionsw.layout().addStretch(1)
1116
1117                OWGUI.widgetLabel(optionsw, name)
1118                key = self.invDict.get(widgetInfo, "<none>")
1119                le = KeyEdit(optionsw, key, self.invDict, widgetInfo, invInvDict)
1120                optionsw.layout().addWidget(le)
1121                invInvDict[key] = le
1122                le.setFixedWidth(60)
1123
1124            wtab.resize(wtab.sizeHint())
1125
1126        # OK, Cancel buttons
1127        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1128        hbox.layout().addStretch(1)
1129        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
1130        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
1131        self.okButton.setDefault(True)
1132
1133        self.layout().addWidget(self.tabs)
1134        self.layout().addWidget(hbox)
1135
1136
1137class AboutDlg(QDialog):
1138    def __init__(self, *args):
1139        QDialog.__init__(self, *args)
1140        self.topLayout = QVBoxLayout(self)
1141#        self.setWindowFlags(Qt.Popup)       # Commented out, because it makes the window appear in the top-left corner on Linux
1142        self.setWindowTitle("About Orange")
1143       
1144        import orngEnviron
1145        import Orange
1146        import re
1147        logoImage = QPixmap(os.path.join(orngEnviron.directoryNames["canvasDir"], "icons", "splash.png"))
1148        logo = OWGUI.widgetLabel(self, "")
1149        logo.setPixmap(logoImage)
1150       
1151        OWGUI.widgetLabel(self, '<p align="center"><h2>Orange</h2></p>') 
1152       
1153        default_version_str = Orange.__version__
1154        built_on = re.findall("\((.*?)\)", Orange.orange.version)
1155        if built_on:
1156            built_on_str = " (built on " + built_on[0].split(",")[-1] + ")"
1157        else:
1158            built_on_str = ""
1159        try:
1160            import Orange.version as version
1161            short_version = version.short_version
1162            hg_revision = version.hg_revision
1163            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (short_version + built_on_str))
1164            if not version.release:
1165                OWGUI.widgetLabel(self, '<p align="center">(hg revision %s)</p>' % (hg_revision))
1166        except ImportError:
1167            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (default_version_str + built_on_str))
1168        OWGUI.widgetLabel(self, "" )
1169        #OWGUI.button(self, self, "Close", callback = self.accept)
1170        b = QDialogButtonBox(self)
1171        b.setCenterButtons(1)
1172        self.layout().addWidget(b)
1173        butt = b.addButton(QDialogButtonBox.Close)
1174        self.connect(butt, SIGNAL("clicked()"), self.accept)
1175       
1176
1177class saveApplicationDlg(QDialog):
1178    def __init__(self, *args):
1179        import Orange.utils.addons
1180       
1181        apply(QDialog.__init__,(self,) + args)
1182        self.setWindowTitle("Set Widget Order")
1183        self.setLayout(QVBoxLayout())
1184
1185        listbox = OWGUI.widgetBox(self, 1, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
1186        self.tabOrderList = QListWidget(listbox)
1187        self.tabOrderList.setSelectionMode(QListWidget.SingleSelection)
1188        listbox.layout().addWidget(self.tabOrderList)
1189
1190        w = OWGUI.widgetBox(listbox, sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
1191        self.upButton = OWGUI.button(w, self, "Up", callback = self.moveUp)
1192        self.downButton = OWGUI.button(w, self, "Down", callback = self.moveDown)
1193        w.layout().addStretch(1)
1194
1195        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1196        OWGUI.button(hbox, self, "Add Separator", callback = self.insertSeparator)
1197        hbox.layout().addStretch(1)
1198        OWGUI.button(hbox, self, "&OK", callback = self.accept)
1199        OWGUI.button(hbox, self, "&Cancel", callback = self.reject)
1200
1201        self.resize(200,250)
1202
1203    def accept(self):
1204        self.shownWidgetList = [str(self.tabOrderList.item(i).text()) for i in range(self.tabOrderList.count())]
1205        QDialog.accept(self)
1206
1207    def insertSeparator(self):
1208        curr = self.tabOrderList.indexFromItem(self.tabOrderList.currentItem()).row()
1209        self.insertWidgetName("[Separator]", curr)
1210
1211    def insertWidgetName(self, name, index = -1):
1212        if index == -1:
1213            self.tabOrderList.addItem(name)
1214        else:
1215            self.tabOrderList.insertItem(index, name)
1216
1217    # move selected widget category up
1218    def moveUp(self):
1219        for i in range(1, self.tabOrderList.count()):
1220            if self.tabOrderList.item(i).isSelected():
1221                item = self.tabOrderList.takeItem(i)
1222                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
1223                self.tabOrderList.insertItem(i-1, item)
1224                item.setSelected(1)
1225
1226
1227    # move selected widget category down
1228    def moveDown(self):
1229        for i in range(self.tabOrderList.count()-2,-1,-1):
1230            if self.tabOrderList.item(i).isSelected():
1231                item = self.tabOrderList.takeItem(i)
1232                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
1233                self.tabOrderList.insertItem(i+1, item)
1234                item.setSelected(1)
1235
1236
1237if __name__=="__main__":
1238    import sys
1239    app = QApplication(sys.argv)
1240    #dlg = saveApplicationDlg(None)
1241    dlg = AboutDlg(None)
1242    dlg.show()
1243    app.exec_()
Note: See TracBrowser for help on using the repository browser.