source: orange/Orange/OrangeCanvas/orngDlgs.py @ 10581:94a831b04ec3

Revision 10581:94a831b04ec3, 72.6 KB checked in by markotoplak, 2 years ago (diff)

Moved some other scripts from misc to utils and Orange imports and canvas not works, although not systematically tested.

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, *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 addOnId in add:
700            if addOnId in remove:
701                actions.append("Upgrade %s." % add[addOnId].name)
702            elif addOnId.startswith("registered:"):
703                actions.append("Register %s." % add[addOnId].name)
704            else:
705                actions.append("Install %s." % add[addOnId].name)
706        for addOnId in remove:
707            if not addOnId in add:
708                if addOnId.startswith("registered:"):
709                    actions.append("Unregister %s." % remove[addOnId].name)
710                else:
711                    actions.append("Remove %s." % remove[addOnId].name)
712        actions.sort()
713        memo.setText("\n".join(actions))
714       
715        self.layout().addStretch(1)
716        self.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) )
717
718        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
719        hbox.layout().addStretch(1)
720        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
721        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
722        self.okButton.setDefault(True)
723
724    def updateMinSize(self, documentSize):
725        self.memo.update()
726        self.memo.setMinimumHeight(min(300, documentSize.height() + 2 * self.memo.frameWidth()))
727
728
729class AddOnRepositoryData(QDialog):
730    def __init__(self, name="", url="", *args):
731        apply(QDialog.__init__,(self,) + args)
732        self.setWindowTitle("Add-on Repository")
733        self.topLayout = QVBoxLayout(self)
734        self.topLayout.setSpacing(0)
735       
736        self.name = name
737        self.url = url     
738       
739        eName = OWGUI.lineEdit(self, self, "name", "Display name:", orientation="horizontal", controlWidth=150)
740        eName.parent().layout().addStretch(1)
741        eURL = OWGUI.lineEdit(self, self, "url", "URL:", orientation="horizontal", controlWidth=250)
742        eURL.parent().layout().addStretch(1)
743        self.layout().addSpacing(15)
744        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
745        hbox.layout().addStretch(1)
746        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
747        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
748        self.okButton.setDefault(True)
749       
750    def accept(self):
751        if self.name.strip() == "":
752            QMessageBox.warning(self, "Incorrect Input", "Name cannot be empty")
753            return
754        if self.url.strip() == "":
755            QMessageBox.warning(self, "Incorrect Input", "URL cannot be empty")
756            return
757        QDialog.accept(self)
758       
759       
760class AddOnManagerDialog(QDialog):
761    def __init__(self, canvasDlg, *args):
762        apply(QDialog.__init__,(self,) + args)
763        self.canvasDlg = canvasDlg
764        self.setWindowTitle("Add-on Management")
765        self.topLayout = QVBoxLayout(self)
766        self.topLayout.setSpacing(0)
767        self.resize(600,500)
768        self.layout().setSizeConstraint(QLayout.SetMinimumSize)
769       
770        mainBox = OWGUI.widgetBox(self, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
771       
772        # View settings & Search
773       
774        self.groupByRepo = True
775        self.sortInstalledFirst = True
776        self.sortSingleLast = True
777        self.searchStr = ""
778
779        searchBox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
780
781        self.viewBox = viewBox = OWGUI.SmallWidgetLabel(searchBox, pixmap = 1, box = "Grouping and Order", tooltip = "Adjust the order of add-ons in the list")
782        cGroupByRepo        = OWGUI.checkBox(viewBox.widget, self, "groupByRepo", "&Group by repository", callback = self.refreshView)
783        cSortInstalledFirst = OWGUI.checkBox(viewBox.widget, self, "sortInstalledFirst", "&Installed first", callback = self.refreshView)
784        cSortSingleLast     = OWGUI.checkBox(viewBox.widget, self, "sortSingleLast", "&Single widgets last", callback = self.refreshView)
785
786        self.eSearch = self.lineEditSearch(searchBox, self, "searchStr", None, 0, tooltip = "Type in to filter (search) add-ons.", callbackOnType=True, callback=self.searchCallback)
787       
788        # Repository & Add-on tree
789       
790        repos = OWGUI.widgetBox(mainBox, "Add-ons", orientation = "horizontal", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
791        repos.layout().setSizeConstraint(QLayout.SetMinimumSize)
792        self.tree = tree = QTreeWidget(repos)
793        self.tree.setMinimumWidth(200)
794        self.tree.repoItems = {}
795        tree.header().hide()
796        repos.layout().addWidget(tree)
797        QObject.connect(tree, SIGNAL("itemChanged(QTreeWidgetItem *, int)"), self.cbToggled)
798        QObject.connect(tree, SIGNAL("currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)"), self.currentItemChanged)
799
800        self.addOnsToAdd = {}
801        self.addOnsToRemove = {}
802        import Orange.utils.addons
803        self.repositories = [repo.clone() for repo in Orange.utils.addons.available_repositories]
804       
805        # Bottom info pane
806       
807        self.infoPane = infoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))
808        infoPane.layout().setSizeConstraint(QLayout.SetMinimumSize)
809
810        pVerInstalled = OWGUI.widgetBox(infoPane, orientation="horizontal")
811        lblVerInstalled = OWGUI.label(pVerInstalled, self, "Installed version:", 150)
812        boldFont = lblVerInstalled.font()
813        boldFont.setWeight(QFont.Bold)
814        lblVerInstalled.setFont(boldFont)
815        self.lblVerInstalledValue = OWGUI.label(pVerInstalled, self, "-")
816        self.lblVerInstalledValue.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
817        pVerInstalled.layout().addSpacing(10)
818        self.lblStatus = OWGUI.label(pVerInstalled, self, "")
819        self.lblStatus.setFont(boldFont)
820        pVerInstalled.layout().addStretch(1)
821
822        pVerAvail = OWGUI.widgetBox(infoPane, orientation="horizontal")
823        lblVerAvail = OWGUI.label(pVerAvail, self, "Available version:", 150)
824        lblVerAvail.setFont(boldFont)
825        self.lblVerAvailValue = OWGUI.label(pVerAvail, self, "")
826        pVerAvail.layout().addSpacing(10)
827        self.upgradeButton = OWGUI.button(pVerAvail, self, "Upgrade", callback = self.upgrade)
828        self.upgradeButton.setFixedHeight(lblVerAvail.height())
829        self.donotUpgradeButton = OWGUI.button(pVerAvail, self, "Do not upgrade", callback = self.donotUpgrade)
830        self.donotUpgradeButton.setFixedHeight(lblVerAvail.height())
831        pVerAvail.layout().addStretch(1)
832       
833        pInfoBtns = OWGUI.widgetBox(infoPane, orientation="horizontal", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
834        self.webButton = OWGUI.button(pInfoBtns, self, "Open webpage", callback = self.openWebPage)
835        self.listWidgetsButton = OWGUI.button(pInfoBtns, self, "List widgets", callback = self.listWidgets)
836        pInfoBtns.layout().addStretch(1)
837
838        self.lblDescription = lblDescription = QTextEdit(infoPane)
839        infoPane.layout().addWidget(lblDescription)
840        lblDescription.setReadOnly(True)
841        lblDescription.setFrameStyle(QFrame.NoFrame)
842        pal = QPalette()
843        pal.setColor(QPalette.Base, Qt.transparent)
844        lblDescription.setPalette(pal)
845        lblDescription.setLineWrapMode(QTextEdit.WidgetWidth)
846        lblDescription.setWordWrapMode(QTextOption.WordWrap)
847        QObject.connect(lblDescription.document().documentLayout(),
848                        SIGNAL("documentSizeChanged(const QSizeF &)"),
849                        lambda docSize: self.updateDescMinSize(docSize))
850
851        # Bottom info pane for registered add-ons
852        self.regiInfoPane = regiInfoPane = OWGUI.widgetBox(mainBox, orientation="vertical", sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum))
853        regiInfoPane.layout().setSizeConstraint(QLayout.SetMinimumSize)
854        self.lblRegisteredAddOnInfo = OWGUI.label(regiInfoPane, self, "-")
855        self.lblRegisteredAddOnInfo.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
856
857        # Right panel
858       
859        rightPanel = OWGUI.widgetBox(repos, orientation = "vertical", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
860        rightPanel.layout().setSizeConstraint(QLayout.SetMinimumSize)
861        self.addRepoButton = OWGUI.button(rightPanel, self, "Add Repository...", callback = self.addRepo)
862        self.editRepoButton = OWGUI.button(rightPanel, self, "Edit Repository...", callback = self.editRepo)
863        self.delRepoButton = OWGUI.button(rightPanel, self, "Remove Repository", callback = self.delSelectedRepo)
864        self.reloadRepoButton = OWGUI.button(rightPanel, self, "Refresh lists", callback = self.reloadRepos)
865        rightPanel.layout().addSpacing(15)
866        self.upgradeAllButton = OWGUI.button(rightPanel, self, "Upgrade All", callback = self.upgradeAll)
867        rightPanel.layout().addSpacing(15)
868        self.registerButton = OWGUI.button(rightPanel, self, "Register Add-on...", callback = self.registerAddOn)
869        rightPanel.layout().addStretch(1)
870        for btn in rightPanel.children():
871            if btn.__class__ is QPushButton:
872                btn.setMinimumHeight(btn.height())
873       
874        # Close button
875        hbox = OWGUI.widgetBox(mainBox, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
876        hbox.layout().addStretch(1)
877        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
878        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
879        self.okButton.setDefault(True)
880
881        self.refreshView()
882       
883    def updateDescMinSize(self, documentSize):
884        self.lblDescription.update()
885        self.lblDescription.setMinimumHeight(min(300, documentSize.height() + 2 * self.lblDescription.frameWidth()))
886        pass
887   
888    def accept(self):
889        import Orange.utils.addons
890        if len(self.addOnsToAdd) + len(self.addOnsToRemove) > 0:
891            summary = AddOnManagerSummary(self.addOnsToAdd, self.addOnsToRemove, self)
892            if summary.exec_() == QDialog.Rejected:
893                return
894        Orange.utils.addons.available_repositories = self.repositories
895        Orange.utils.addons.save_repositories()
896        QDialog.accept(self)
897       
898    def addRepo(self):
899        dlg = AddOnRepositoryData()
900        while dlg.exec_() == QDialog.Accepted:
901            import Orange.utils.addons
902            try:
903                repo = Orange.utils.addons.OrangeAddOnRepository(dlg.name, dlg.url)   #TODO: This can take some time - inform the user!
904                self.repositories.append(repo)
905            except Exception, e:
906                QMessageBox.critical(self, "Error", "Could not add this repository: %s"%e)
907                continue
908            break
909        self.refreshView()
910
911    def editRepo(self, repo=None):
912        if not repo:
913            repo = self.getRepoFromItem(self.tree.currentItem())
914        if not repo:
915            return
916        dlg = AddOnRepositoryData(name=repo.name, url=repo.url)
917        while dlg.exec_() == QDialog.Accepted:
918            import Orange.utils.addons
919            try:
920                oldname, oldurl = repo.name, repo.url
921                repo.name, repo.url = dlg.name, dlg.url
922                if oldurl != repo.url:
923                    repo.refreshdata(force=True)  #TODO: This can take some time - inform the user!
924            except Exception, e:
925                repo.name, repo.url = oldname, oldurl
926                QMessageBox.critical(self, "Error", "Could not load repository %s."%e)
927                continue
928            break
929        self.refreshView()
930
931    def delSelectedRepo(self):
932        repo = self.getRepoFromItem(self.tree.currentItem())
933        if repo==None:
934            return
935        # Is it a default repository? We cannot delete it!
936        import Orange.utils.addons
937        if repo.__class__ is Orange.utils.addons.OrangeDefaultAddOnRepository:
938            return
939       
940        # Are there add-ons selected for installation from this repository? We remove the installation requests.
941        for (id, addOn) in self.addOnsToAdd.items():
942            if addOn.repository == repo:
943                del self.addOnsToAdd[id]
944       
945        # Remove the repository and refresh tree.
946        self.repositories.remove(repo)
947        self.refreshView()
948   
949    def reloadRepos(self):
950        # Reload add-on list for all repositories.
951        # TODO: This can take some time - show some progress to user!
952        for repo in self.repositories:
953            try:
954                repo.refreshdata(force=True)
955            except Exception, e:  # Maybe gather all exceptions (for all repositories) and show them in the end?
956                QMessageBox.critical(self, "Error", "Could not reload repository '%s': %s." % (repo.name, e))
957        # Were any installation-pending add-ons removed from repositories?
958        for (id, addOn) in self.addOnsToAdd.items():
959            if id in addOn.repository.addons:
960                newObject = [version for version in addOn.repository.addons[id] if version.version == addOn.version]
961                if newObject != []:
962                    self.addOnsToAdd[id] = newObject[0]
963                    continue
964            del self.addOnsToAdd[id]
965            if id in self.addOnsToRemove:    # If true, it was a request for upgrade, not installation -- do not remove the installed version!
966                del self.addOnsToRemove[id]
967        # Finally, refresh the tree on GUI.
968        self.refreshView()
969           
970    def upgradeCandidates(self):
971        result = []
972        import orngEnviron, Orange.utils.addons
973        for item in self.tree.addOnItems:
974            id = item.newest.id
975            if id.startswith("registered:"): continue
976            installedAo = Orange.utils.addons.installed_addons[id] if id in Orange.utils.addons.installed_addons else None
977            installed = installedAo.version if installedAo else None 
978            selected = self.addOnsToAdd[id].version if id in self.addOnsToAdd else None
979            if installed:
980                if installedAo.directory.startswith(orngEnviron.addOnsDirUser):
981                    if installed < item.newest.version:
982                        if selected:
983                            if selected >= item.newest.version:
984                                continue
985                        result.append(item.newest)
986        return result
987   
988    def upgradeAll(self):
989        for candidate in self.upgradeCandidates():
990            self.upgrade(candidate, refresh=False)
991        self.refreshInfoPane()
992        self.enableDisableButtons()
993       
994    def upgrade(self, newAddOn=None, refresh=True):
995        if not newAddOn:
996            newAddOn = self.getAddOnFromItem(self.tree.currentItem())
997        if not newAddOn:
998            return
999        import Orange.utils.addons
1000        self.addOnsToRemove[newAddOn.id] = Orange.utils.addons.installed_addons[newAddOn.id]
1001        self.addOnsToAdd[newAddOn.id] = newAddOn
1002        if refresh:
1003            self.refreshInfoPane()
1004            self.enableDisableButtons()
1005
1006    def registerAddOn(self):
1007        dir = str(QFileDialog.getExistingDirectory(self, "Select the folder that contains the add-on:"))
1008        if dir != "":
1009            if os.path.split(dir)[1] == "widgets":     # register a dir above the dir that contains the widget folder
1010                dir = os.path.split(dir)[0]
1011            if os.path.exists(os.path.join(dir, "widgets")):
1012                name = os.path.split(dir)[1]
1013                import Orange.utils.addons
1014                id = "registered:"+dir
1015                self.addOnsToAdd[id] = Orange.utils.addons.OrangeRegisteredAddOn(name, dir, systemwide=False)
1016                self.refreshView(id)
1017            else:
1018                QMessageBox.information( None, "Information", 'The specified folder does not seem to contain an Orange add-on.', QMessageBox.Ok + QMessageBox.Default)
1019
1020    def openWebPage(self):
1021        addOn = self.getAddOnFromItem(self.tree.currentItem())
1022        if not addOn: return
1023        if not addOn.homepage: return
1024        import webbrowser
1025        webbrowser.open(addOn.homepage)
1026       
1027    def listWidgets(self):
1028        addOn = self.getAddOnFromItem(self.tree.currentItem())
1029        if not addOn: return
1030        import Orange.utils.addons
1031        if addOn.__class__ is not Orange.utils.addons.OrangeAddOnInRepo: return
1032        if not addOn.repository.has_web_script: return
1033        self.canvasDlg.helpWindow.open("%s/addOnServer.py/%s/doc/widgets/" % (addOn.repository.url, addOn.filename), modal=True)
1034       
1035       
1036    def donotUpgrade(self, newAddOn=None):
1037        if not newAddOn:
1038            newAddOn = self.getAddOnFromItem(self.tree.currentItem())
1039        if not newAddOn:
1040            return
1041        del self.addOnsToAdd[newAddOn.id]
1042        del self.addOnsToRemove[newAddOn.id]
1043        self.refreshInfoPane()
1044       
1045    def lineEditSearch(self, *args, **props):
1046        return OWGUI.lineEdit(*args, **props)
1047
1048    def cbToggled(self, item, column):
1049        # Not a request from an add-on item in tree?
1050        if (column != 0) or "disableToggleSignal" not in item.__dict__:
1051            return
1052        # Toggle signal currently disabled?
1053        if item.disableToggleSignal:
1054            return
1055       
1056        addOn = item.newest
1057        id = addOn.id
1058        if item.checkState(0) == Qt.Checked:  # Mark for installation (or delete removal request)
1059            if id not in self.addOnsToAdd:
1060                if id in self.addOnsToRemove:
1061                    del self.addOnsToRemove[id]
1062                else:
1063                    self.addOnsToAdd[id] = addOn
1064        else:                                 # Mark for removal (or delete installation request)
1065            import Orange.utils.addons, orngEnviron
1066            installedAo = Orange.utils.addons.installed_addons[id] if id in Orange.utils.addons.installed_addons else None 
1067            if installedAo:
1068                if not installedAo.directory.startswith(orngEnviron.addOnsDirUser):
1069                    item.disableToggleSignal = True
1070                    item.setCheckState(0, Qt.Checked)
1071                    item.disableToggleSignal = False
1072                    return
1073            if id in self.addOnsToAdd:
1074                del self.addOnsToAdd[id]
1075            elif id not in self.addOnsToRemove:
1076                import Orange.utils.addons
1077                if id in Orange.utils.addons.installed_addons:
1078                    self.addOnsToRemove[id] = Orange.utils.addons.installed_addons[id]
1079                elif id.startswith("registered:"):
1080                    self.addOnsToRemove[id] = item.newest
1081        self.resetChecked(id)   # Refresh all checkboxes for this add-on (it might be in multiple repositories!)
1082        self.refreshInfoPane(item)
1083       
1084    def getRepoFromItem(self, item):
1085        if not item:
1086            return None
1087        import Orange.utils.addons
1088        if hasattr(item, "repository"):
1089            return item.repository
1090        else:
1091            if item.newest.__class__ is not Orange.utils.addons.OrangeAddOnInRepo:
1092                return None
1093            return  item.newest.repository
1094   
1095    def getAddOnFromItem(self, item):       
1096        if hasattr(item, "newest"):
1097            return item.newest
1098        return None
1099
1100    def getAddOnIdFromItem(self, item):
1101        addOn = self.getAddOnFromItem(item)       
1102        return addOn.id if addOn else None
1103       
1104    def refreshInfoPane(self, item=None):
1105        if not item:
1106            item = self.tree.currentItem()
1107        import Orange.utils.addons
1108        if hasattr(item, "newest"):
1109            if item.newest.__class__ is not Orange.utils.addons.OrangeRegisteredAddOn:
1110                import orngEnviron
1111                addOn = item.newest
1112                self.lblDescription.setText(addOn.description.strip() if addOn else "")
1113                self.lblVerAvailValue.setText(addOn.version_str)
1114   
1115                addOnInstalled = Orange.utils.addons.installed_addons[addOn.id] if addOn.id in Orange.utils.addons.installed_addons else None
1116                addOnToInstall = self.addOnsToAdd[addOn.id] if addOn.id in self.addOnsToAdd else None
1117                addOnToRemove = self.addOnsToRemove[addOn.id] if addOn.id in self.addOnsToRemove else None
1118               
1119                self.lblVerInstalledValue.setText((addOnInstalled.version_str+("" if addOnInstalled.directory.startswith(orngEnviron.addOnsDirUser) else " (installed system-wide)")) if addOnInstalled else "-")
1120                self.upgradeButton.setVisible(addOnInstalled!=None and addOnInstalled.version < addOn.version and addOnToInstall!=addOn and addOnInstalled.directory.startswith(orngEnviron.addOnsDirUser))
1121                self.donotUpgradeButton.setVisible(addOn.id in self.addOnsToRemove and addOnToInstall==addOn)
1122                self.webButton.setVisible(addOn.homepage != None)
1123                self.listWidgetsButton.setVisible(len(addOn.widgets) > 0 and addOn.__class__ is Orange.utils.addons.OrangeAddOnInRepo and addOn.repository.has_web_script)
1124               
1125                if addOnToInstall:
1126                    if addOnToRemove: self.lblStatus.setText("marked for upgrade")
1127                    else: self.lblStatus.setText("marked for installation")
1128                elif addOnToRemove: self.lblStatus.setText("marked for removal")
1129                else: self.lblStatus.setText("")
1130   
1131                self.infoPane.setVisible(True)
1132                self.regiInfoPane.setVisible(False)
1133            else:
1134                self.lblRegisteredAddOnInfo.setText("This add-on is registered "+("system-wide." if item.newest.systemwide else "by user."))
1135                self.infoPane.setVisible(False)
1136                self.regiInfoPane.setVisible(True)
1137        else:
1138            self.infoPane.setVisible(False)
1139            self.regiInfoPane.setVisible(False)
1140       
1141    def enableDisableButtons(self):
1142        repo = self.getRepoFromItem(self.tree.currentItem())
1143        import Orange.utils.addons
1144        self.delRepoButton.setEnabled(repo.__class__ is not Orange.utils.addons.OrangeDefaultAddOnRepository if repo!=None else False)
1145        self.editRepoButton.setEnabled(repo.__class__ is not Orange.utils.addons.OrangeDefaultAddOnRepository if repo!=None else False)
1146        self.upgradeAllButton.setEnabled(self.upgradeCandidates() != [])
1147       
1148    def currentItemChanged(self, new, previous):
1149        # Enable/disable buttons
1150        self.enableDisableButtons()
1151           
1152        # Refresh info pane
1153        self.refreshInfoPane(new)
1154   
1155    def resetChecked(self, id):
1156        import Orange.utils.addons
1157        value = id in Orange.utils.addons.installed_addons or id.startswith("registered:")
1158        value = value and id not in self.addOnsToRemove
1159        value = value or id in self.addOnsToAdd
1160        for treeItem in self.tree.addOnItems:
1161            if treeItem.newest.id == id:
1162                treeItem.disableToggleSignal = True
1163                treeItem.setCheckState(0,Qt.Checked if value else Qt.Unchecked);
1164                treeItem.disableToggleSignal = False
1165
1166    def addAddOnsToTree(self, repoItem, addOnDict, insertToBeginning=False):
1167        # Transform dictionary {id->[versions]} list of tuples (newest,[otherVersions])
1168        if type(addOnDict) is list:
1169            addOnList = [(ao, []) for ao in addOnDict]
1170        else:
1171            addOnList = []
1172            for id in addOnDict:
1173                versions = list(addOnDict[id])  # We make a copy, so that we can change it!
1174                newest = versions[0]
1175                for v in versions:
1176                    if v.version > newest.version:
1177                        newest = v
1178                versions.remove(newest)
1179                addOnList.append( (newest, versions) )
1180        # Sort alphabetically
1181        addOnList.sort(key=lambda (newest, versions): newest.name)
1182        # Single-addon packages last
1183        if self.sortSingleLast:
1184            addOnList = [(n, v) for (n, v) in addOnList if not n.has_single_widget] \
1185                      + [(n, v) for (n, v) in addOnList if     n.has_single_widget]
1186        # Installed first
1187        if self.sortInstalledFirst and len(addOnList)>0 and "id" in addOnList[0][0].__dict__:
1188            import Orange.utils.addons
1189            addOnList = [(n, v) for (n, v) in addOnList if     n.id in Orange.utils.addons.installed_addons] \
1190                      + [(n, v) for (n, v) in addOnList if not n.id in Orange.utils.addons.installed_addons]
1191       
1192        for (i, (newest, versions)) in enumerate(addOnList):
1193            addOnItem = QTreeWidgetItem(repoItem if not insertToBeginning else None)
1194            if insertToBeginning:
1195                if repoItem.__class__ is QTreeWidget:
1196                    repoItem.insertTopLevelItem(i, addOnItem)
1197                else:
1198                    repoItem.insertChild(i, addOnItem)
1199            addOnItem.disableToggleSignal = True
1200            addOnItem.setText(0, newest.name)
1201            if newest.has_single_widget():
1202                italFont = QFont(addOnItem.font(0))
1203                italFont.setItalic(True)
1204                addOnItem.setFont(0, italFont)
1205            addOnItem.setCheckState(0,Qt.Unchecked);
1206            addOnItem.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled)
1207            addOnItem.newest = newest
1208            addOnItem.otherVersions = versions
1209
1210            self.tree.addOnItems.append(addOnItem)
1211            repoItem.addOnItemsDict[newest.id] = addOnItem
1212            self.resetChecked(newest.id)
1213            addOnItem.disableToggleSignal = False
1214           
1215
1216    def addRepositoryToTree(self, repo):
1217        repoItem = QTreeWidgetItem(self.tree)
1218        repoItem.repository = repo
1219        repoItem.addOnItemsDict = {}
1220        repoItem.setText(0, repo.name)
1221        boldFont = QFont(repoItem.font(0))
1222        boldFont.setWeight(QFont.Bold)
1223        repoItem.setFont(0, boldFont)
1224        self.tree.repoItems[repo] = repoItem
1225       
1226        addOnsToAdd = {}
1227        visibleAddOns = repo.search_index(self.searchStr)
1228        for (id, versions) in repo.addons.items():
1229            if id in visibleAddOns:
1230                addOnsToAdd[id] = versions
1231        self.addAddOnsToTree(repoItem, addOnsToAdd)
1232       
1233        return repoItem
1234           
1235
1236    def refreshView(self, selectedRegisteredAddOnId=None):
1237        # Save repository items expanded state
1238        expandedRepos = set([])
1239        for (repo, item) in self.tree.repoItems.items():
1240            if item.isExpanded():
1241                expandedRepos.add(repo)
1242        # Save current item selection
1243        selectedRepository = self.getRepoFromItem(self.tree.currentItem())
1244        selectedAddOnId = self.getAddOnIdFromItem(self.tree.currentItem())
1245        #TODO: Save the next repository selection too, in case the current one was deleted
1246
1247        # Clear the tree
1248        self.tree.repoItems = {}
1249        self.tree.addOnItems = []
1250        self.tree.addOnItemsDict = {}
1251        self.tree.clear()
1252       
1253        # Set button visibility
1254        self.editRepoButton.setVisible(self.groupByRepo)
1255        self.delRepoButton.setVisible(self.groupByRepo)
1256       
1257        # Add repositories and add-ons
1258        shownAddOns = set([])
1259        if self.groupByRepo:
1260            for repo in self.repositories:
1261                item = self.addRepositoryToTree(repo)
1262                shownAddOns = shownAddOns.union(set(repo.addons).intersection(repo.search_index(self.searchStr)))
1263        else:
1264            addOns = {}
1265            for repo in self.repositories:
1266                for addOnId in repo.addons:
1267                    if addOnId in repo.search_index(self.searchStr):
1268                        if addOnId in addOns:
1269                            addOns[addOnId].extend(repo.addons[addOnId])
1270                        else:
1271                            addOns[addOnId] = list(repo.addons[addOnId])
1272            self.addAddOnsToTree(self.tree, addOns)
1273            shownAddOns = set(addOns)
1274       
1275        # Add add-ons that are not present in any repository
1276        if self.searchStr.strip() == "":   # but we do not need to search among installed add-ons
1277            import Orange.utils.addons
1278            onlyInstalledAddOns = {}
1279            for addOn in Orange.utils.addons.installed_addons.values():
1280                if addOn.id not in shownAddOns:
1281                    onlyInstalledAddOns[addOn.id] = [addOn]
1282            self.addAddOnsToTree(self.tree, onlyInstalledAddOns, insertToBeginning=True)
1283           
1284        # Registered Add-ons
1285        if Orange.utils.addons.registered_addons != [] or any([id.startswith("registered:") for id in self.addOnsToAdd]):
1286            regiItem = QTreeWidgetItem(self.tree)
1287            regiItem.repository = None
1288            regiItem.addOnItemsDict = {}
1289            regiItem.setText(0, "Registered Add-ons")
1290            boldFont = QFont(regiItem.font(0))
1291            boldFont.setWeight(QFont.Bold)
1292            regiItem.setFont(0, boldFont)
1293            self.tree.repoItems["Registered Add-ons"] = regiItem
1294           
1295            addOnsToAdd = []
1296            import re
1297            words = [word for word in re.split(Orange.utils.addons.index_re, self.searchStr.lower()) if word!=""]
1298            visibleAddOns = [ao for ao in Orange.utils.addons.registered_addons+[ao for ao in self.addOnsToAdd.values() if ao.id.startswith("registered:")] if all([word in ao.name for word in words])]
1299            self.addAddOnsToTree(regiItem, visibleAddOns)
1300            if selectedRegisteredAddOnId:
1301                regiItem.setExpanded(True)
1302                self.tree.setCurrentItem(regiItem.addOnItemsDict[selectedRegisteredAddOnId])
1303           
1304        # Restore repository items expanded state
1305        if len(expandedRepos)==0:
1306            self.tree.expandItem(self.tree.topLevelItem(0))
1307        else:
1308            for (repo, item) in self.tree.repoItems.items():
1309                if repo in expandedRepos:
1310                    item.setExpanded(True)
1311                   
1312        # Restore item selection
1313        if not selectedRegisteredAddOnId:
1314            select = self.tree.topLevelItem(0)
1315            search = None
1316            if selectedRepository in self.tree.repoItems:
1317                select = self.tree.repoItems[selectedRepository]
1318                search = select
1319            elif not selectedRepository:
1320                search = self.tree
1321            if selectedAddOnId and search:
1322                if selectedAddOnId in search.addOnItemsDict:
1323                    select = search.addOnItemsDict[selectedAddOnId]
1324            self.tree.setCurrentItem(select)
1325           
1326    def searchCallback(self):
1327        self.refreshView()
1328   
1329# widget shortcuts dialog
1330class WidgetShortcutDlg(QDialog):
1331    def __init__(self, canvasDlg, *args):
1332        import orngTabs
1333
1334        apply(QDialog.__init__,(self,) + args)
1335        self.canvasDlg = canvasDlg
1336        self.setWindowTitle("Widget Shortcuts")
1337        self.setLayout(QVBoxLayout())
1338        self.layout().setSpacing(10)
1339        self.resize(700,500)
1340
1341        self.invDict = dict([(y, x) for x, y in canvasDlg.widgetShortcuts.items()])
1342        invInvDict = {}
1343
1344        self.tabs = QTabWidget(self)
1345       
1346        extraTabs = [(name, 1) for name in canvasDlg.widgetRegistry.keys() if name not in [tab for (tab, s) in canvasDlg.settings["WidgetTabs"]]]
1347        for tabName, show in canvasDlg.settings["WidgetTabs"] + extraTabs:
1348            if not canvasDlg.widgetRegistry.has_key(tabName):
1349                continue
1350            scrollArea = QScrollArea()
1351            self.tabs.addTab(scrollArea, tabName)
1352            #scrollArea.setWidgetResizable(1)       # you have to use this or set size to wtab manually - otherwise nothing gets shown
1353
1354            wtab = QWidget(self.tabs)
1355            scrollArea.setWidget(wtab)
1356
1357            widgets = [(int(widgetInfo.priority), name, widgetInfo) for (name, widgetInfo) in canvasDlg.widgetRegistry[tabName].items()]
1358            widgets.sort()
1359            rows = (len(widgets)+2) / 3
1360            layout = QGridLayout(wtab)
1361
1362            for i, (priority, name, widgetInfo) in enumerate(widgets):
1363                x = i / rows
1364                y = i % rows
1365
1366                hlayout = QHBoxLayout()
1367                mainBox = QWidget(wtab)
1368                mainBox.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
1369                mainBox.setLayout(hlayout)
1370                layout.addWidget(mainBox, y, x, Qt.AlignTop | Qt.AlignLeft)
1371                label = QLabel(wtab)
1372                label.setPixmap(canvasDlg.getWidgetIcon(widgetInfo).pixmap(40))
1373                hlayout.addWidget(label)
1374
1375                optionsw = QWidget(self)
1376                optionsw.setLayout(QVBoxLayout())
1377                hlayout.addWidget(optionsw)
1378                optionsw.layout().addStretch(1)
1379
1380                OWGUI.widgetLabel(optionsw, name)
1381                key = self.invDict.get(widgetInfo, "<none>")
1382                le = KeyEdit(optionsw, key, self.invDict, widgetInfo, invInvDict)
1383                optionsw.layout().addWidget(le)
1384                invInvDict[key] = le
1385                le.setFixedWidth(60)
1386
1387            wtab.resize(wtab.sizeHint())
1388
1389        # OK, Cancel buttons
1390        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1391        hbox.layout().addStretch(1)
1392        self.okButton = OWGUI.button(hbox, self, "OK", callback = self.accept)
1393        self.cancelButton = OWGUI.button(hbox, self, "Cancel", callback = self.reject)
1394        self.okButton.setDefault(True)
1395
1396        self.layout().addWidget(self.tabs)
1397        self.layout().addWidget(hbox)
1398
1399
1400class AboutDlg(QDialog):
1401    def __init__(self, *args):
1402        QDialog.__init__(self, *args)
1403        self.topLayout = QVBoxLayout(self)
1404#        self.setWindowFlags(Qt.Popup)       # Commented out, because it makes the window appear in the top-left corner on Linux
1405        self.setWindowTitle("About Orange")
1406       
1407        import orngEnviron
1408        logoImage = QPixmap(os.path.join(orngEnviron.directoryNames["canvasDir"], "icons", "splash.png"))
1409        logo = OWGUI.widgetLabel(self, "")
1410        logo.setPixmap(logoImage)
1411       
1412        OWGUI.widgetLabel(self, '<p align="center"><h2>Orange</h2></p>') 
1413       
1414        try:
1415            import Orange.version as version
1416            short_version = version.short_version
1417            hg_revision = version.hg_revision
1418            OWGUI.widgetLabel(self, '<p align="center">version %s</p>' % (short_version))
1419            if not version.release:
1420                OWGUI.widgetLabel(self, '<p align="center">(hg revision %s)</p>' % (hg_revision))
1421        except ImportError:
1422            pass
1423        OWGUI.widgetLabel(self, "" )
1424        #OWGUI.button(self, self, "Close", callback = self.accept)
1425        b = QDialogButtonBox(self)
1426        b.setCenterButtons(1)
1427        self.layout().addWidget(b)
1428        butt = b.addButton(QDialogButtonBox.Close)
1429        self.connect(butt, SIGNAL("clicked()"), self.accept)
1430       
1431
1432class saveApplicationDlg(QDialog):
1433    def __init__(self, *args):
1434        import Orange.utils.addons
1435       
1436        apply(QDialog.__init__,(self,) + args)
1437        self.setWindowTitle("Set Widget Order")
1438        self.setLayout(QVBoxLayout())
1439
1440        listbox = OWGUI.widgetBox(self, 1, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
1441        self.tabOrderList = QListWidget(listbox)
1442        self.tabOrderList.setSelectionMode(QListWidget.SingleSelection)
1443        listbox.layout().addWidget(self.tabOrderList)
1444
1445        w = OWGUI.widgetBox(listbox, sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding))
1446        self.upButton = OWGUI.button(w, self, "Up", callback = self.moveUp)
1447        self.downButton = OWGUI.button(w, self, "Down", callback = self.moveDown)
1448        w.layout().addStretch(1)
1449
1450        hbox = OWGUI.widgetBox(self, orientation = "horizontal", sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed))
1451        OWGUI.button(hbox, self, "Add Separator", callback = self.insertSeparator)
1452        hbox.layout().addStretch(1)
1453        OWGUI.button(hbox, self, "&OK", callback = self.accept)
1454        OWGUI.button(hbox, self, "&Cancel", callback = self.reject)
1455
1456        self.resize(200,250)
1457
1458    def accept(self):
1459        self.shownWidgetList = [str(self.tabOrderList.item(i).text()) for i in range(self.tabOrderList.count())]
1460        QDialog.accept(self)
1461
1462    def insertSeparator(self):
1463        curr = self.tabOrderList.indexFromItem(self.tabOrderList.currentItem()).row()
1464        self.insertWidgetName("[Separator]", curr)
1465
1466    def insertWidgetName(self, name, index = -1):
1467        if index == -1:
1468            self.tabOrderList.addItem(name)
1469        else:
1470            self.tabOrderList.insertItem(index, name)
1471
1472    # move selected widget category up
1473    def moveUp(self):
1474        for i in range(1, self.tabOrderList.count()):
1475            if self.tabOrderList.item(i).isSelected():
1476                item = self.tabOrderList.takeItem(i)
1477                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
1478                self.tabOrderList.insertItem(i-1, item)
1479                item.setSelected(1)
1480
1481
1482    # move selected widget category down
1483    def moveDown(self):
1484        for i in range(self.tabOrderList.count()-2,-1,-1):
1485            if self.tabOrderList.item(i).isSelected():
1486                item = self.tabOrderList.takeItem(i)
1487                for j in range(self.tabOrderList.count()): self.tabOrderList.item(j).setSelected(0)
1488                self.tabOrderList.insertItem(i+1, item)
1489                item.setSelected(1)
1490
1491
1492if __name__=="__main__":
1493    import sys
1494    app = QApplication(sys.argv)
1495    #dlg = saveApplicationDlg(None)
1496    dlg = AboutDlg(None)
1497    dlg.show()
1498    app.exec_()
Note: See TracBrowser for help on using the repository browser.