source: orange/Orange/OrangeCanvas/orngDlgs.py @ 11073:6c257e7f43bd

Revision 11073:6c257e7f43bd, 59.6 KB checked in by Matija Polajnar <matija.polajnar@…>, 16 months ago (diff)

addon management dialog: make it repaint correctly on Macs (Ales' patch).

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