source: orange/Orange/OrangeCanvas/orngDlgs.py @ 11021:258cd74e0722

Revision 11021:258cd74e0722, 58.2 KB checked in by Matija Polajnar <matija.polajnar@…>, 17 months ago (diff)

Show progress during installation of add-ons.

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