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.

Line 
1# Author: Gregor Leban (gregor.leban@fri.uni-lj.si)
2# Description:
3#    signal dialog, canvas options dialog
4
5import sys
6import os
7import subprocess
8from contextlib import closing
9
10from PyQt4.QtCore import *
11from PyQt4.QtGui import *
12from orngCanvasItems import MyCanvasText
13
14import OWGUI
15
16has_pip = True
17try:
18    import pip.req
19except ImportError:
20    has_pip = False
21
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)
475        self.enableCanvasDropShadowsCB = OWGUI.checkBox(generalBox, self.settings, "enableCanvasDropShadows", "Enable drop shadows in canvas", debuggingEnabled = 0)
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):
689    def __init__(self, add, remove, upgrade, *args):
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 = []
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)
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
732
733class AddOnManagerDialog(QDialog):
734    def __init__(self, canvasDlg, *args):
735        QDialog.__init__(self, *args)
736        self.setModal(True)
737
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 = ""
751        self.to_upgrade = set()
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       
759        repos = OWGUI.widgetBox(mainBox, "Add-ons", orientation = "horizontal", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored))
760        repos.layout().setSizeConstraint(QLayout.SetMinimumSize)
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)
767
768        import Orange.utils.addons
769
770        # Bottom info pane
771       
772        self.infoPane = infoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored))
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)
800        self.docButton = OWGUI.button(pInfoBtns, self, "Open documentation", callback = self.openDocsPage)
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       
816        self.rightPanel = rightPanel = OWGUI.widgetBox(repos, orientation = "vertical", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
817        rightPanel.layout().setSizeConstraint(QLayout.SetMinimumSize)
818        self.reloadRepoButton = OWGUI.button(rightPanel, self, "Refresh list", callback = self.reloadRepo)
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       
826        # Buttons
827        self.hbox = hbox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
828        busyBox = OWGUI.widgetBox(hbox, sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))  # A humble stretch.
829        self.busyLbl = OWGUI.label(busyBox, self, "")
830        self.progress = QProgressBar(hbox, sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
831        hbox.layout().addWidget(self.progress)
832        self.progress.setVisible(False)
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):
845        self.to_upgrade.difference_update(self.to_remove())
846        import Orange.utils.addons
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)
850            if summary.exec_() == QDialog.Rejected:
851                return
852
853        self.busy(True)
854        self.repaint()
855        add, remove, upgrade = self.to_install(), self.to_remove(), self.to_upgrade
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
873        for name in upgrade:
874            try:
875                self.busy("Upgrading %s ..." % name)
876                self.repaint()
877                Orange.utils.addons.upgrade(name, self.pcb)
878            except subprocess.CalledProcessError, ex:
879                errormessage("Error",
880                             "setup.py script exited with error code %i" \
881                             % ex.returncode,
882                             details=ex.output)
883            except Exception, e:
884                errormessage("Error",
885                             "Problem upgrading add-on %s: %s" % (name, e),
886                             exc_info=True)
887
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:
894                errormessage("Error",
895                             "Problem uninstalling add-on %s: %s" % (name, e),
896                             exc_info=True)
897
898        for name in add:
899            try:
900                self.busy("Installing %s ..." % name)
901                self.repaint()
902                Orange.utils.addons.install(name, self.pcb)
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
909            except Exception, e:
910                errormessage("Error",
911                             "Problem installing add-on %s: %s" % (name, e),
912                             exc_info=True)
913
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
919        QDialog.accept(self)
920
921    def busy(self, b=True):
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 "")
926        self.eSearch.setEnabled(not b)
927        self.lst.setEnabled(not b)
928        self.okButton.setEnabled(not b)
929        self.cancelButton.setEnabled(not b)
930        self.rightPanel.setEnabled(not b)
931        self.infoPane.setEnabled(not b)
932
933    def pcb(self, max, val):
934        self.progress.setMaximum(max)
935        self.progress.setValue(val)
936        qApp.processEvents(QEventLoop.ExcludeUserInputEvents)
937
938    def reloadRepo(self):
939        # Reload add-on list.
940        import Orange.utils.addons
941        try:
942            self.busy("Reloading add-on repository ...")
943            self.repaint()
944            Orange.utils.addons.refresh_available_addons(progress_callback = self.pcb)
945        except Exception, e:
946            QMessageBox.critical(self, "Error", "Could not reload repository: %s." % e)
947        finally:
948            self.busy(False)
949        # Finally, refresh the tree on GUI.
950        self.refreshView()
951           
952    def upgradeCandidates(self):
953        result = []
954        import Orange.utils.addons
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)
959        return result
960   
961    def upgradeAll(self):
962        for candidate in self.upgradeCandidates():
963            self.upgrade(candidate, refresh=False)
964        self.refreshInfoPane()
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)
970        if refresh:
971            self.refreshInfoPane()
972
973    def openWebPage(self):
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
985    def listWidgets(self):
986        addOn = self.getAddOnFromItem(self.lst.currentItem())
987        if not addOn: return
988        import Orange.utils.addons
989        if addOn.__class__ is not Orange.utils.addons.OrangeAddOnInRepo: return
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       
994    def donotUpgrade(self):
995        id = self.getAddOnIdFromItem(self.lst.currentItem())
996        self.to_upgrade.remove(id)
997        self.refreshInfoPane()
998
999    def cbToggled(self, item):
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)
1004        self.refreshInfoPane(item)
1005
1006    def lineEditSearch(self, *args, **props):
1007        return OWGUI.lineEdit(*args, **props)
1008
1009    def getAddOnFromItem(self, item):
1010        return getattr(item, "addon", None)
1011
1012    def getAddOnIdFromItem(self, item):
1013        addon = self.getAddOnFromItem(item)
1014        return addon.name if addon else None
1015       
1016    def refreshInfoPane(self, item=None):
1017        if not item:
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:
1025            self.lblDescription.setText((addon.summary.strip() or "") +"\n"+ (addon.description.strip() or ""))
1026            self.lblVerAvailValue.setText(addon.available_version or "")
1027
1028            self.lblVerInstalledValue.setText(addon.installed_version or "-") #TODO Tell whether it's a system-wide installation
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")
1041            else:
1042                self.lblStatus.setText("")
1043
1044            self.infoPane.setVisible(True)
1045        else:
1046            self.infoPane.setVisible(False)
1047        self.enableDisableButtons()
1048
1049    def enableDisableButtons(self):
1050        import Orange.utils.addons
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))
1056       
1057    def currentItemChanged(self, new, previous):
1058        # Refresh info pane & button states
1059        self.refreshInfoPane(new)
1060
1061    def addAddOnsToTree(self, addon_dict, selected=None, to_install=[], to_remove=[]):
1062        # Sort alphabetically
1063        addons = sorted(list(addon_dict.items()),
1064                        key = lambda (name, ao): name)
1065
1066        for (i, (name, ao)) in enumerate(addons):
1067            item = QListWidgetItem()
1068            self.lst.addItem(item)
1069            item.setText(ao.name)
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)
1075
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])
1089
1090    def refreshView(self, selectedRegisteredAddOnId=None):
1091        import Orange
1092        # Save current item selection
1093        selected_addon = self.getAddOnIdFromItem(self.lst.currentItem())
1094        to_install = self.to_install()
1095        to_remove = self.to_remove()
1096        #TODO: Save the next repository selection too, in case the current one was deleted
1097
1098        # Clear the tree
1099        self.lst.clear()
1100       
1101        # Add repositories and add-ons
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()
1108
1109        #TODO Should we somehow show the legacy registered addons?
1110
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):
1187        QDialog.__init__(self, *args)
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
1193        import Orange
1194        import re
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       
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 = ""
1207        try:
1208            import Orange.version as version
1209            short_version = version.short_version
1210            hg_revision = version.hg_revision
1211            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (short_version + built_on_str))
1212            if not version.release:
1213                OWGUI.widgetLabel(self, '<p align="center">(hg revision %s)</p>' % (hg_revision))
1214        except ImportError:
1215            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (default_version_str + built_on_str))
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):
1227        import Orange.utils.addons
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.