source: orange/Orange/OrangeCanvas/orngDlgs.py @ 11094:67f486d63faf

Revision 11094:67f486d63faf, 60.9 KB checked in by Ales Erjavec <ales.erjavec@…>, 14 months ago (diff)

Running setup.py script in a subprocess.

If the package uses 'distribute_setup' module the installation fails when
the currently installed (and already imported) version is older then the
requested version.

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