source: orange/Orange/OrangeCanvas/orngDlgs.py @ 11869:0f2b07be78bb

Revision 11869:0f2b07be78bb, 62.0 KB checked in by markotoplak, 7 weeks ago (diff)

Do not load addon shelve on startup.

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