source: orange/Orange/OrangeCanvas/orngDlgs.py @ 11075:3a5d0fb34651

Revision 11075:3a5d0fb34651, 60.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 15 months ago (diff)

Detailed error messages on install/update failure, handle possible SystemExit exceptions.

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        QDialog.__init__(self, *args)
730        self.setModal(True)
731
732        self.canvasDlg = canvasDlg
733        self.setWindowTitle("Add-on Management")
734        self.topLayout = QVBoxLayout(self)
735        self.topLayout.setSpacing(0)
736        self.resize(600,500)
737        self.layout().setSizeConstraint(QLayout.SetMinimumSize)
738       
739        mainBox = OWGUI.widgetBox(self, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
740       
741        # View settings & Search
742       
743        self.groupByRepo = True
744        self.searchStr = ""
745        self.to_upgrade = set()
746
747        searchBox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
748
749        self.eSearch = self.lineEditSearch(searchBox, self, "searchStr", None, 0, tooltip = "Type in to filter (search) add-ons.", callbackOnType=True, callback=self.searchCallback)
750       
751        # Repository & Add-on tree
752       
753        repos = OWGUI.widgetBox(mainBox, "Add-ons", orientation = "horizontal", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored))
754        repos.layout().setSizeConstraint(QLayout.SetMinimumSize)
755        self.lst = lst = QListWidget(repos)
756        lst.setMinimumWidth(200)
757        lst.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred))
758        repos.layout().addWidget(lst)
759        QObject.connect(lst, SIGNAL("itemChanged(QListWidgetItem *)"), self.cbToggled)
760        QObject.connect(lst, SIGNAL("currentItemChanged(QListWidgetItem *, QListWidgetItem *)"), self.currentItemChanged)
761
762        import Orange.utils.addons
763
764        # Bottom info pane
765       
766        self.infoPane = infoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored))
767        infoPane.layout().setSizeConstraint(QLayout.SetMinimumSize)
768
769        pVerInstalled = OWGUI.widgetBox(infoPane, orientation="horizontal")
770        lblVerInstalled = OWGUI.label(pVerInstalled, self, "Installed version:", 150)
771        boldFont = lblVerInstalled.font()
772        boldFont.setWeight(QFont.Bold)
773        lblVerInstalled.setFont(boldFont)
774        self.lblVerInstalledValue = OWGUI.label(pVerInstalled, self, "-")
775        self.lblVerInstalledValue.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
776        pVerInstalled.layout().addSpacing(10)
777        self.lblStatus = OWGUI.label(pVerInstalled, self, "")
778        self.lblStatus.setFont(boldFont)
779        pVerInstalled.layout().addStretch(1)
780
781        pVerAvail = OWGUI.widgetBox(infoPane, orientation="horizontal")
782        lblVerAvail = OWGUI.label(pVerAvail, self, "Available version:", 150)
783        lblVerAvail.setFont(boldFont)
784        self.lblVerAvailValue = OWGUI.label(pVerAvail, self, "")
785        pVerAvail.layout().addSpacing(10)
786        self.upgradeButton = OWGUI.button(pVerAvail, self, "Upgrade", callback = self.upgrade)
787        self.upgradeButton.setFixedHeight(lblVerAvail.height())
788        self.donotUpgradeButton = OWGUI.button(pVerAvail, self, "Do not upgrade", callback = self.donotUpgrade)
789        self.donotUpgradeButton.setFixedHeight(lblVerAvail.height())
790        pVerAvail.layout().addStretch(1)
791       
792        pInfoBtns = OWGUI.widgetBox(infoPane, orientation="horizontal", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
793        self.webButton = OWGUI.button(pInfoBtns, self, "Open webpage", callback = self.openWebPage)
794        self.docButton = OWGUI.button(pInfoBtns, self, "Open documentation", callback = self.openDocsPage)
795        self.listWidgetsButton = OWGUI.button(pInfoBtns, self, "List widgets", callback = self.listWidgets)
796        pInfoBtns.layout().addStretch(1)
797
798        self.lblDescription = lblDescription = QTextEdit(infoPane)
799        infoPane.layout().addWidget(lblDescription)
800        lblDescription.setReadOnly(True)
801        lblDescription.setFrameStyle(QFrame.NoFrame)
802        pal = QPalette()
803        pal.setColor(QPalette.Base, Qt.transparent)
804        lblDescription.setPalette(pal)
805        lblDescription.setLineWrapMode(QTextEdit.WidgetWidth)
806        lblDescription.setWordWrapMode(QTextOption.WordWrap)
807
808        # Right panel
809       
810        self.rightPanel = rightPanel = OWGUI.widgetBox(repos, orientation = "vertical", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
811        rightPanel.layout().setSizeConstraint(QLayout.SetMinimumSize)
812        self.reloadRepoButton = OWGUI.button(rightPanel, self, "Refresh list", callback = self.reloadRepo)
813        rightPanel.layout().addSpacing(15)
814        self.upgradeAllButton = OWGUI.button(rightPanel, self, "Upgrade All", callback = self.upgradeAll)
815        rightPanel.layout().addStretch(1)
816        for btn in rightPanel.children():
817            if btn.__class__ is QPushButton:
818                btn.setMinimumHeight(btn.height())
819       
820        # Buttons
821        self.hbox = hbox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
822        busyBox = OWGUI.widgetBox(hbox, sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))  # A humble stretch.
823        self.busyLbl = OWGUI.label(busyBox, self, "")
824        self.progress = QProgressBar(hbox, sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
825        hbox.layout().addWidget(self.progress)
826        self.progress.setVisible(False)
827        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
828        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
829        self.okButton.setDefault(True)
830
831        self.refreshView()
832       
833    def updateDescMinSize(self, documentSize):
834        self.lblDescription.update()
835        self.lblDescription.setMinimumHeight(min(300, documentSize.height() + 2 * self.lblDescription.frameWidth()))
836        pass
837   
838    def accept(self):
839        self.to_upgrade.difference_update(self.to_remove())
840        import Orange.utils.addons
841        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade
842        if len(add) + len(remove) + len(upgrade) > 0:
843            summary = AddOnManagerSummary(add, remove, upgrade, self)
844            if summary.exec_() == QDialog.Rejected:
845                return
846
847        self.busy(True)
848        self.repaint()
849        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade
850
851        def errormessage(title, message, details=None, exc_info=None):
852            box = QMessageBox(QMessageBox.Critical, title, message,
853                              parent=self)
854
855            if details is not None:
856                box.setDetailedText(details)
857            elif exc_info:
858                import traceback
859                if isinstance(exc_info, tuple):
860                    details = traceback.format_exception(*(exc_info + (10,)))
861                else:
862                    details = traceback.format_exc(10)
863                box.setDetailedText(details)
864
865            return box.exec_()
866
867        for name in upgrade:
868            try:
869                self.busy("Upgrading %s ..." % name)
870                self.repaint()
871                Orange.utils.addons.upgrade(name, self.pcb)
872            except Exception, e:
873                errormessage("Error",
874                             "Problem upgrading add-on %s: %s" % (name, e),
875                             exc_info=True)
876            except SystemExit, e:
877                errormessage("Error", "Abnormal exit", exc_info=True)
878
879        for name in remove:
880            try:
881                self.busy("Uninstalling %s ..." % name)
882                self.repaint()
883                Orange.utils.addons.uninstall(name, self.pcb)
884            except Exception, e:
885                errormessage("Error",
886                             "Problem uninstalling add-on %s: %s" % (name, e),
887                             exc_info=True)
888
889        for name in add:
890            try:
891                self.busy("Installing %s ..." % name)
892                self.repaint()
893                Orange.utils.addons.install(name, self.pcb)
894            except Exception, e:
895                errormessage("Error",
896                             "Problem installing add-on %s: %s" % (name, e),
897                             exc_info=True)
898            except SystemExit, e:
899                errormessage("Error", "Abnormal exit", exc_info=True)
900
901        if len(upgrade) > 0:
902            QMessageBox.warning(self, "Restart Orange", "After upgrading add-ons, it is very important to restart Orange to make sure the changes have been applied.")
903        elif len(remove) > 0:  # Don't bother with this if there has already been one (more important) warning.
904            QMessageBox.warning(self, "Restart Orange", "After removal of add-ons, it is suggested that you restart Orange for the changes to become effective.")
905
906        QDialog.accept(self)
907
908    def busy(self, b=True):
909        self.progress.setMaximum(1)
910        self.progress.setValue(0)
911        self.progress.setVisible(bool(b))
912        self.busyLbl.setText(b if isinstance(b, str) else "")
913        self.eSearch.setEnabled(not b)
914        self.lst.setEnabled(not b)
915        self.okButton.setEnabled(not b)
916        self.cancelButton.setEnabled(not b)
917        self.rightPanel.setEnabled(not b)
918        self.infoPane.setEnabled(not b)
919
920    def pcb(self, max, val):
921        self.progress.setMaximum(max)
922        self.progress.setValue(val)
923        qApp.processEvents(QEventLoop.ExcludeUserInputEvents)
924
925    def reloadRepo(self):
926        # Reload add-on list.
927        import Orange.utils.addons
928        try:
929            self.busy("Reloading add-on repository ...")
930            self.repaint()
931            Orange.utils.addons.refresh_available_addons(progress_callback = self.pcb)
932        except Exception, e:
933            QMessageBox.critical(self, "Error", "Could not reload repository: %s." % e)
934        finally:
935            self.busy(False)
936        # Finally, refresh the tree on GUI.
937        self.refreshView()
938           
939    def upgradeCandidates(self):
940        result = []
941        import Orange.utils.addons
942        with closing(Orange.utils.addons.open_addons()) as addons:
943            for ao in addons.values():
944                if ao.installed_version and ao.available_version and ao.installed_version != ao.available_version:
945                    result.append(ao.name)
946        return result
947   
948    def upgradeAll(self):
949        for candidate in self.upgradeCandidates():
950            self.upgrade(candidate, refresh=False)
951        self.refreshInfoPane()
952
953    def upgrade(self, name=None, refresh=True):
954        if not name:
955            name = self.getAddOnIdFromItem(self.lst.currentItem())
956        self.to_upgrade.add(name)
957        if refresh:
958            self.refreshInfoPane()
959
960    def openWebPage(self):
961        addon = self.getAddOnFromItem(self.lst.currentItem())
962        if addon and addon.homepage:
963            import webbrowser
964            webbrowser.open(addon.homepage)
965
966    def openDocsPage(self):
967        addon = self.getAddOnFromItem(self.lst.currentItem())
968        if addon and addon.docs_url:
969            import webbrowser
970            webbrowser.open(addon.docs_url)
971
972    def listWidgets(self):
973        addOn = self.getAddOnFromItem(self.lst.currentItem())
974        if not addOn: return
975        import Orange.utils.addons
976        if addOn.__class__ is not Orange.utils.addons.OrangeAddOnInRepo: return
977        if not addOn.repository.has_web_script: return
978        self.canvasDlg.helpWindow.open("%s/addOnServer.py/%s/doc/widgets/" % (addOn.repository.url, addOn.filename), modal=True)
979       
980       
981    def donotUpgrade(self):
982        id = self.getAddOnIdFromItem(self.lst.currentItem())
983        self.to_upgrade.remove(id)
984        self.refreshInfoPane()
985
986    def cbToggled(self, item):
987        ao = self.getAddOnFromItem(item)
988        if ao and not has_pip and ao.installed_version and item.checkState()==Qt.Unchecked:
989            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.")
990            item.setCheckState(Qt.Checked)
991        self.refreshInfoPane(item)
992
993    def lineEditSearch(self, *args, **props):
994        return OWGUI.lineEdit(*args, **props)
995
996    def getAddOnFromItem(self, item):
997        return getattr(item, "addon", None)
998
999    def getAddOnIdFromItem(self, item):
1000        addon = self.getAddOnFromItem(item)
1001        return addon.name if addon else None
1002       
1003    def refreshInfoPane(self, item=None):
1004        if not item:
1005            item = self.lst.currentItem()
1006        addon = None
1007        if item:
1008            import Orange.utils.addons
1009            import orngEnviron
1010            addon = self.getAddOnFromItem(item)
1011        if addon:
1012            self.lblDescription.setText((addon.summary.strip() or "") +"\n"+ (addon.description.strip() or ""))
1013            self.lblVerAvailValue.setText(addon.available_version or "")
1014
1015            self.lblVerInstalledValue.setText(addon.installed_version or "-") #TODO Tell whether it's a system-wide installation
1016            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
1017            self.donotUpgradeButton.setVisible(addon.name in self.to_upgrade)
1018            self.webButton.setVisible(bool(addon.homepage))
1019            self.docButton.setVisible(bool(addon.docs_url))
1020            self.listWidgetsButton.setVisible(False) #TODO A list of widgets is not available
1021
1022            if not addon.installed_version and item.checkState()==Qt.Checked:
1023                self.lblStatus.setText("marked for installation")
1024            elif addon.installed_version and item.checkState()!=Qt.Checked:
1025                self.lblStatus.setText("marked for removal")
1026            elif addon.name in self.to_upgrade:
1027                self.lblStatus.setText("marked for upgrade")
1028            else:
1029                self.lblStatus.setText("")
1030
1031            self.infoPane.setVisible(True)
1032        else:
1033            self.infoPane.setVisible(False)
1034        self.enableDisableButtons()
1035
1036    def enableDisableButtons(self):
1037        import Orange.utils.addons
1038        with closing(Orange.utils.addons.open_addons()) as addons:
1039            aos = addons.values()
1040            self.upgradeAllButton.setEnabled(any(ao.installed_version and ao.available_version and
1041                                                 ao.installed_version != ao.available_version and
1042                                                 ao.name not in self.to_upgrade for ao in aos))
1043       
1044    def currentItemChanged(self, new, previous):
1045        # Refresh info pane & button states
1046        self.refreshInfoPane(new)
1047
1048    def addAddOnsToTree(self, addon_dict, selected=None, to_install=[], to_remove=[]):
1049        # Sort alphabetically
1050        addons = sorted(list(addon_dict.items()),
1051                        key = lambda (name, ao): name)
1052
1053        for (i, (name, ao)) in enumerate(addons):
1054            item = QListWidgetItem()
1055            self.lst.addItem(item)
1056            item.setText(ao.name)
1057            item.setCheckState(Qt.Checked if ao.installed_version and not name in to_remove or name in to_install else Qt.Unchecked)
1058            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled)
1059            item.addon = ao
1060            if name == selected:
1061                self.lst.setCurrentItem(item)
1062
1063            item.disableToggleSignal = False
1064
1065    def lst_items(self):
1066        for i in xrange(self.lst.count()):
1067            yield self.lst.item(i)
1068
1069    def to_install(self):
1070        return set([item.addon.name for item in self.lst_items()
1071                    if item.checkState()==Qt.Checked and not item.addon.installed_version])
1072
1073    def to_remove(self):
1074        return set([item.addon.name for item in self.lst_items()
1075                    if item.checkState()!=Qt.Checked and item.addon.installed_version])
1076
1077    def refreshView(self, selectedRegisteredAddOnId=None):
1078        import Orange
1079        # Save current item selection
1080        selected_addon = self.getAddOnIdFromItem(self.lst.currentItem())
1081        to_install = self.to_install()
1082        to_remove = self.to_remove()
1083        #TODO: Save the next repository selection too, in case the current one was deleted
1084
1085        # Clear the tree
1086        self.lst.clear()
1087       
1088        # Add repositories and add-ons
1089        with closing(Orange.utils.addons.open_addons()) as global_addons:
1090            addons = {}
1091            for name in Orange.utils.addons.search_index(self.searchStr):
1092                addons[name.lower()] = global_addons[name.lower()]
1093            self.addAddOnsToTree(addons, selected = selected_addon, to_install=to_install, to_remove=to_remove)
1094            self.refreshInfoPane()
1095
1096        #TODO Should we somehow show the legacy registered addons?
1097
1098    def searchCallback(self):
1099        self.refreshView()
1100   
1101# widget shortcuts dialog
1102class WidgetShortcutDlg(QDialog):
1103    def __init__(self, canvasDlg, *args):
1104        import orngTabs
1105
1106        apply(QDialog.__init__,(self,) + args)
1107        self.canvasDlg = canvasDlg
1108        self.setWindowTitle("Widget Shortcuts")
1109        self.setLayout(QVBoxLayout())
1110        self.layout().setSpacing(10)
1111        self.resize(700,500)
1112
1113        self.invDict = dict([(y, x) for x, y in canvasDlg.widgetShortcuts.items()])
1114        invInvDict = {}
1115
1116        self.tabs = QTabWidget(self)
1117       
1118        extraTabs = [(name, 1) for name in canvasDlg.widgetRegistry.keys() if name not in [tab for (tab, s) in canvasDlg.settings["WidgetTabs"]]]
1119        for tabName, show in canvasDlg.settings["WidgetTabs"] + extraTabs:
1120            if not canvasDlg.widgetRegistry.has_key(tabName):
1121                continue
1122            scrollArea = QScrollArea()
1123            self.tabs.addTab(scrollArea, tabName)
1124            #scrollArea.setWidgetResizable(1)       # you have to use this or set size to wtab manually - otherwise nothing gets shown
1125
1126            wtab = QWidget(self.tabs)
1127            scrollArea.setWidget(wtab)
1128
1129            widgets = [(int(widgetInfo.priority), name, widgetInfo) for (name, widgetInfo) in canvasDlg.widgetRegistry[tabName].items()]
1130            widgets.sort()
1131            rows = (len(widgets)+2) / 3
1132            layout = QGridLayout(wtab)
1133
1134            for i, (priority, name, widgetInfo) in enumerate(widgets):
1135                x = i / rows
1136                y = i % rows
1137
1138                hlayout = QHBoxLayout()
1139                mainBox = QWidget(wtab)
1140                mainBox.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
1141                mainBox.setLayout(hlayout)
1142                layout.addWidget(mainBox, y, x, Qt.AlignTop | Qt.AlignLeft)
1143                label = QLabel(wtab)
1144                label.setPixmap(canvasDlg.getWidgetIcon(widgetInfo).pixmap(40))
1145                hlayout.addWidget(label)
1146
1147                optionsw = QWidget(self)
1148                optionsw.setLayout(QVBoxLayout())
1149                hlayout.addWidget(optionsw)
1150                optionsw.layout().addStretch(1)
1151
1152                OWGUI.widgetLabel(optionsw, name)
1153                key = self.invDict.get(widgetInfo, "<none>")
1154                le = KeyEdit(optionsw, key, self.invDict, widgetInfo, invInvDict)
1155                optionsw.layout().addWidget(le)
1156                invInvDict[key] = le
1157                le.setFixedWidth(60)
1158
1159            wtab.resize(wtab.sizeHint())
1160
1161        # OK, Cancel buttons
1162        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1163        hbox.layout().addStretch(1)
1164        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
1165        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
1166        self.okButton.setDefault(True)
1167
1168        self.layout().addWidget(self.tabs)
1169        self.layout().addWidget(hbox)
1170
1171
1172class AboutDlg(QDialog):
1173    def __init__(self, *args):
1174        QDialog.__init__(self, *args)
1175        self.topLayout = QVBoxLayout(self)
1176#        self.setWindowFlags(Qt.Popup)       # Commented out, because it makes the window appear in the top-left corner on Linux
1177        self.setWindowTitle("About Orange")
1178       
1179        import orngEnviron
1180        import Orange
1181        import re
1182        logoImage = QPixmap(os.path.join(orngEnviron.directoryNames["canvasDir"], "icons", "splash.png"))
1183        logo = OWGUI.widgetLabel(self, "")
1184        logo.setPixmap(logoImage)
1185       
1186        OWGUI.widgetLabel(self, '<p align="center"><h2>Orange</h2></p>') 
1187       
1188        default_version_str = Orange.__version__
1189        built_on = re.findall("\((.*?)\)", Orange.orange.version)
1190        if built_on:
1191            built_on_str = " (built on " + built_on[0].split(",")[-1] + ")"
1192        else:
1193            built_on_str = ""
1194        try:
1195            import Orange.version as version
1196            short_version = version.short_version
1197            hg_revision = version.hg_revision
1198            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (short_version + built_on_str))
1199            if not version.release:
1200                OWGUI.widgetLabel(self, '<p align="center">(hg revision %s)</p>' % (hg_revision))
1201        except ImportError:
1202            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (default_version_str + built_on_str))
1203        OWGUI.widgetLabel(self, "" )
1204        #OWGUI.button(self, self, "Close", callback = self.accept)
1205        b = QDialogButtonBox(self)
1206        b.setCenterButtons(1)
1207        self.layout().addWidget(b)
1208        butt = b.addButton(QDialogButtonBox.Close)
1209        self.connect(butt, SIGNAL("clicked()"), self.accept)
1210       
1211
1212class saveApplicationDlg(QDialog):
1213    def __init__(self, *args):
1214        import Orange.utils.addons
1215       
1216        apply(QDialog.__init__,(self,) + args)
1217        self.setWindowTitle("Set Widget Order")
1218        self.setLayout(QVBoxLayout())
1219
1220        listbox = OWGUI.widgetBox(self, 1, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
1221        self.tabOrderList = QListWidget(listbox)
1222        self.tabOrderList.setSelectionMode(QListWidget.SingleSelection)
1223        listbox.layout().addWidget(self.tabOrderList)
1224
1225        w = OWGUI.widgetBox(listbox, sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
1226        self.upButton = OWGUI.button(w, self, "Up", callback = self.moveUp)
1227        self.downButton = OWGUI.button(w, self, "Down", callback = self.moveDown)
1228        w.layout().addStretch(1)
1229
1230        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1231        OWGUI.button(hbox, self, "Add Separator", callback = self.insertSeparator)
1232        hbox.layout().addStretch(1)
1233        OWGUI.button(hbox, self, "&OK", callback = self.accept)
1234        OWGUI.button(hbox, self, "&Cancel", callback = self.reject)
1235
1236        self.resize(200,250)
1237
1238    def accept(self):
1239        self.shownWidgetList = [str(self.tabOrderList.item(i).text()) for i in range(self.tabOrderList.count())]
1240        QDialog.accept(self)
1241
1242    def insertSeparator(self):
1243        curr = self.tabOrderList.indexFromItem(self.tabOrderList.currentItem()).row()
1244        self.insertWidgetName("[Separator]", curr)
1245
1246    def insertWidgetName(self, name, index = -1):
1247        if index == -1:
1248            self.tabOrderList.addItem(name)
1249        else:
1250            self.tabOrderList.insertItem(index, name)
1251
1252    # move selected widget category up
1253    def moveUp(self):
1254        for i in range(1, self.tabOrderList.count()):
1255            if self.tabOrderList.item(i).isSelected():
1256                item = self.tabOrderList.takeItem(i)
1257                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
1258                self.tabOrderList.insertItem(i-1, item)
1259                item.setSelected(1)
1260
1261
1262    # move selected widget category down
1263    def moveDown(self):
1264        for i in range(self.tabOrderList.count()-2,-1,-1):
1265            if self.tabOrderList.item(i).isSelected():
1266                item = self.tabOrderList.takeItem(i)
1267                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
1268                self.tabOrderList.insertItem(i+1, item)
1269                item.setSelected(1)
1270
1271
1272if __name__=="__main__":
1273    import sys
1274    app = QApplication(sys.argv)
1275    #dlg = saveApplicationDlg(None)
1276    dlg = AboutDlg(None)
1277    dlg.show()
1278    app.exec_()
Note: See TracBrowser for help on using the repository browser.