source: orange/orange/OrangeCanvas/orngDoc.py @ 9546:2b6cc6f397fe

Revision 9546:2b6cc6f397fe, 36.0 KB checked in by ales_erjavec <ales.erjavec@…>, 2 years ago (diff)

Renamed widget channel names in line with the new naming rules/convention.
Added backwards compatibility in orngDoc loadDocument to enable loading of schemas saved before the change.

Line 
1# Author: Gregor Leban (gregor.leban@fri.uni-lj.si)
2# Description:
3#    document class - main operations (save, load, ...)
4#
5from __future__ import with_statement
6
7from PyQt4.QtCore import *
8from PyQt4.QtGui import *
9import sys, os, os.path, traceback
10from xml.dom.minidom import Document, parse
11import orngView, orngCanvasItems, orngTabs
12from orngDlgs import *
13from orngSignalManager import SignalManager
14import cPickle, math, orngHistory
15
16_CHANNEL_NAME_MAP = \
17    {'Additional Tables': 'Additional Data',
18     'Attribute Definitions': 'Feature Definitions',
19     'Attribute List': 'Features',
20     'Attribute Pair': 'Interacting Features',
21     'Attribute Selection List': 'Features',
22     'Attribute Statistics': 'Feature Statistics',
23     'Attribute selection': 'Features',
24     'Attributes': 'Features',
25     'Choosen Tree': 'Selected Tree',
26     'Covered Examples': 'Covered Data',
27     'Data Instances': 'Data',
28     'Data Table': 'Data',
29     'Distance Matrix': 'Distances',
30     'Distance matrix': 'Distances',
31     'Example Subset': 'Data Subset',
32     'Example Table': 'Data',
33     'Examples': 'Data',
34     'Examples A': 'Data',
35     'Examples B': 'Data',
36     'Graph with ExampleTable': 'Graph with Data',
37     'Input Data': 'Data',
38     'Input Table': 'Data',
39     'Instances': 'Data',
40     'Items Distance Matrix': 'Distances',
41     'Items Subset': 'Item Subset',
42     'Items to Mark': 'Marked Itenms',
43     'KNN Classifier': 'kNN Classifier',
44     'Marked Examples': 'Marked Data',
45     'Matching Examples': 'Merged Data',
46     'Mismatching Examples': 'Mismatched Data',
47     'Output Data': 'Data',
48     'Output Table': 'Data',
49     'Preprocessed Example Table': 'Preprocessed Data',
50     'Primary Table': 'Primary Data',
51     'Reduced Example Table': 'Reduced Data',
52     'Remaining Examples': 'Remaining Data',
53     'SOMMap': 'SOM',
54     'Sample': 'Data Sample',
55     'Selected Attributes List': 'Selected Features',
56     'Selected Examples': 'Selected Data',
57     'Selected Instances': 'Selected Data',
58     'Selected Items Distance Matrix': 'Distance Matrix',
59     'Shuffled Data Table': 'Shuffled Data',
60     'Train Data': 'Training Data',
61     'Training data': 'Data',
62     'Unselected Examples': 'Other Data',
63     'Unselected Items': 'Other Items',
64     }
65   
66
67class SchemaDoc(QWidget):
68    def __init__(self, canvasDlg, *args):
69        QWidget.__init__(self, *args)
70        self.canvasDlg = canvasDlg
71        self.ctrlPressed = 0
72
73        self.lines = []                         # list of orngCanvasItems.CanvasLine items
74        self.widgets = []                       # list of orngCanvasItems.CanvasWidget items
75        self.signalManager = SignalManager()    # signal manager to correctly process signals
76
77        self.schemaPath = self.canvasDlg.settings["saveSchemaDir"]
78        self.schemaName = ""
79        self.loadedSettingsDict = {}
80        self.setLayout(QVBoxLayout())
81        #self.canvas = QGraphicsScene(0,0,2000,2000)
82        self.canvas = QGraphicsScene()
83
84        self.guide_text = self.canvas.addSimpleText("Right-click to add widgets")
85        self.guide_text.setBrush(QBrush(QColor(235,235,235)))
86        font = QFont()
87        font.setStyleHint(QFont.SansSerif)
88        font.setPixelSize(36)
89        self.guide_text.setFont(font)
90
91        oneItem = self.canvas.addRect(QRectF(0.0, 0.0, 300.0, 300.0)) # initial item so sceneRect always contains QPoint(0, 0)
92        self.canvas.sceneRect() # call sceneRect so it updates the rect
93        self.canvas.removeItem(oneItem)
94       
95        self.canvasView = orngView.SchemaView(self, self.canvas, self)
96        self.layout().addWidget(self.canvasView)
97        self.layout().setMargin(0)
98        self.schemaID = orngHistory.logNewSchema()
99
100        self.update_guide()
101
102    def update_guide(self):
103        """ Sets the visibility of the guide text """
104        visible = not len(self.widgets)
105        self.guide_text.setVisible(visible)
106        if visible:
107            self.canvasView.ensureVisible(self.guide_text)
108
109    def isSchemaChanged(self):
110        """ Is this schema document modified.
111        """
112        return self.loadedSettingsDict != dict([(widget.caption, widget.instance.saveSettingsStr()) for widget in self.widgets])
113   
114    def setSchemaModified(self, state):
115        """ Set the modified document state flag.
116        """
117        # Update the loaded settings dict so we know if the widget
118        # settings have changed from the last save when we quit
119        # the application (see closeEvent handler)
120        if not state:
121            self.loadedSettingsDict = dict([(widget.caption, widget.instance.saveSettingsStr()) for widget in self.widgets])
122        else:
123            self.loadedSettingsDict = {}
124       
125    def closeEvent(self, ce):
126        if self.saveBeforeClose():
127            self.clear()
128            self.removeTempDoc()
129            ce.accept()
130        else:
131            ce.ignore()
132            return
133           
134        QWidget.closeEvent(self, ce)
135        orngHistory.logCloseSchema(self.schemaID)
136       
137       
138    # save a temp document whenever anything changes. this doc is deleted on closeEvent
139    # in case that Orange crashes, Canvas on the next start offers an option to reload the crashed schema with links frozen
140    def saveTempDoc(self):
141        if self.widgets != []:
142            tempName = os.path.join(self.canvasDlg.canvasSettingsDir, "tempSchema.tmp")
143            self.save(tempName)
144       
145    def removeTempDoc(self):
146        tempName = os.path.join(self.canvasDlg.canvasSettingsDir, "tempSchema.tmp")
147        if os.path.exists(tempName):
148            os.remove(tempName)
149
150    # called to properly close all widget contexts
151    def synchronizeContexts(self):
152        for widget in self.widgets[::-1]:
153            widget.instance.synchronizeContexts()
154
155    # add line connecting widgets outWidget and inWidget
156    # if necessary ask which signals to connect
157    def addLine(self, outWidget, inWidget, enabled=True, saveTempDoc=True):
158        if outWidget == inWidget: 
159            return None
160        # check if line already exists
161        line = self.getLine(outWidget, inWidget)
162        if line:
163            self.resetActiveSignals(outWidget, inWidget, None, enabled, saveTempDoc)
164            return None
165
166        if self.signalManager.existsPath(inWidget.instance, outWidget.instance):
167            QMessageBox.critical( self, "Failed to Connect", "Circular connections are not allowed in Orange Canvas.", QMessageBox.Ok)
168            return None
169
170        dialog = SignalDialog(self.canvasDlg, self.canvasDlg)
171        dialog.setOutInWidgets(outWidget, inWidget)
172        connectStatus = dialog.addDefaultLinks()
173        if connectStatus == 0:
174            QMessageBox.information( self, "Failed to Connect", "Selected widgets don't share a common signal type.", QMessageBox.Ok)
175            return
176
177        # if there are multiple choices, how to connect this two widget, then show the dialog
178        if len(dialog.getLinks()) > 1 or dialog.multiplePossibleConnections or dialog.getLinks() == []:
179            if dialog.exec_() == QDialog.Rejected:
180                return None
181        else:
182            dialog.deleteLater() # Dialog must be deleted
183
184#        self.signalManager.setFreeze(1)
185        with self.signalManager.freeze(outWidget.instance):
186            linkCount = 0
187            for (outName, inName) in dialog.getLinks():
188                linkCount += self.addLink(outWidget, inWidget, outName, inName, enabled, saveTempDoc=False)
189
190#        self.signalManager.setFreeze(0, outWidget.instance)
191
192        # if signals were set correctly create the line, update widget tooltips and show the line
193        line = self.getLine(outWidget, inWidget)
194        if line:
195            outWidget.updateTooltip()
196            inWidget.updateTooltip()
197           
198        if saveTempDoc:
199            self.saveTempDoc()
200        return line
201
202
203    # reset signals of an already created line
204    def resetActiveSignals(self, outWidget, inWidget, newSignals = None, enabled = 1, saveTempDoc=True):
205        #print "<extra>orngDoc.py - resetActiveSignals() - ", outWidget, inWidget, newSignals
206        signals = []
207        for line in self.lines:
208            if line.outWidget == outWidget and line.inWidget == inWidget:
209                signals = line.getSignals()
210
211        if newSignals == None:
212            dialog = SignalDialog(self.canvasDlg, self.canvasDlg)
213            dialog.setOutInWidgets(outWidget, inWidget)
214            for (outName, inName) in signals:
215                #print "<extra>orngDoc.py - SignalDialog.addLink() - adding signal to dialog: ", outName, inName
216                dialog.addLink(outName, inName)
217
218            # if there are multiple choices, how to connect this two widget, then show the dialog
219            if dialog.exec_() == QDialog.Rejected:
220                return
221
222            newSignals = dialog.getLinks()
223
224        for (outName, inName) in signals:
225            if (outName, inName) not in newSignals:
226                self.removeLink(outWidget, inWidget, outName, inName, saveTempDoc=False)
227                signals.remove((outName, inName))
228
229        with self.signalManager.freeze(outWidget.instance):
230            for (outName, inName) in newSignals:
231                if (outName, inName) not in signals:
232                    self.addLink(outWidget, inWidget, outName, inName, enabled, saveTempDoc=False)
233
234        outWidget.updateTooltip()
235        inWidget.updateTooltip()
236       
237        if saveTempDoc:
238            self.saveTempDoc()
239
240
241
242    # add one link (signal) from outWidget to inWidget. if line doesn't exist yet, we create it
243    def addLink(self, outWidget, inWidget, outSignalName, inSignalName, enabled=1, saveTempDoc=True):
244        #print "<extra>orngDoc - addLink() - ", outWidget, inWidget, outSignalName, inSignalName
245        # in case there already exists link to inSignalName in inWidget that is single, we first delete it
246        widgetInstance = inWidget.instance.removeExistingSingleLink(inSignalName)
247        if widgetInstance:
248            widget = self.findWidgetFromInstance(widgetInstance)
249            existingSignals = self.signalManager.findSignals(widgetInstance, inWidget.instance)
250            for (outN, inN) in existingSignals:
251                if inN == inSignalName:
252                    self.removeLink(widget, inWidget, outN, inN)
253                    line = self.getLine(widget, inWidget)
254                    if line:
255                        line.updateTooltip()
256
257        # if line does not exist yet, we must create it
258        existingSignals = self.signalManager.findSignals(outWidget.instance, inWidget.instance)
259        if not existingSignals:
260            line = orngCanvasItems.CanvasLine(self.signalManager, self.canvasDlg, self.canvasView, outWidget, inWidget, self.canvas)
261            self.lines.append(line)
262            line.show()
263            outWidget.addOutLine(line)
264            outWidget.updateTooltip()
265            inWidget.addInLine(line)
266            inWidget.updateTooltip()
267        else:
268            line = self.getLine(outWidget, inWidget)
269
270        ok = self.signalManager.addLink(outWidget.instance, inWidget.instance, outSignalName, inSignalName, enabled)
271        if not ok:
272            self.removeLink(outWidget, inWidget, outSignalName, inSignalName)
273            QMessageBox.warning( None, "Orange Canvas", "Unable to add link. Try restarting Orange Canvas.", QMessageBox.Ok + QMessageBox.Default, 0)
274            return 0
275        else:
276            orngHistory.logAddLink(self.schemaID, outWidget, inWidget, outSignalName)
277
278        line.updateTooltip()
279        line.update()
280        return 1
281
282
283    # remove only one signal from connected two widgets. If no signals are left, delete the line
284    def removeLink(self, outWidget, inWidget, outSignalName, inSignalName, saveTempDoc=True):
285        #print "<extra> orngDoc.py - removeLink() - ", outWidget, inWidget, outSignalName, inSignalName
286        self.signalManager.removeLink(outWidget.instance, inWidget.instance, outSignalName, inSignalName)
287
288       
289        otherSignals = self.signalManager.getLinks(outWidget.instance, inWidget.instance, outSignalName, inSignalName)
290        if not otherSignals:
291            self.removeLine(outWidget, inWidget, saveTempDoc=False)
292
293        if saveTempDoc:
294            self.saveTempDoc()
295
296
297    # remove line line
298    def removeLine1(self, line, saveTempDoc=True):
299        for (outName, inName) in line.getSignals():
300            self.signalManager.removeLink(line.outWidget.instance, line.inWidget.instance, outName, inName)   # update SignalManager
301
302        self.lines.remove(line)
303        line.inWidget.removeLine(line)
304        line.outWidget.removeLine(line)
305        line.inWidget.updateTooltip()
306        line.outWidget.updateTooltip()
307        line.remove()
308        if saveTempDoc:
309            self.saveTempDoc()
310
311    # remove line, connecting two widgets
312    def removeLine(self, outWidget, inWidget, saveTempDoc=True):
313        """ Remove the line connecting two widgets
314        """
315        #print "<extra> orngDoc.py - removeLine() - ", outWidget, inWidget
316        line = self.getLine(outWidget, inWidget)
317        if line:
318            self.removeLine1(line, saveTempDoc)
319
320    # add new widget
321    def addWidget(self, widgetInfo, x= -1, y=-1, caption = "", widgetSettings = {}, saveTempDoc = True):
322        qApp.setOverrideCursor(Qt.WaitCursor)
323        try:
324            newwidget = orngCanvasItems.CanvasWidget(self.signalManager, self.canvas, self.canvasView, widgetInfo, self.canvasDlg.defaultPic, self.canvasDlg, widgetSettings)
325        except:
326            type, val, traceback = sys.exc_info()
327            sys.excepthook(type, val, traceback)  # we pretend that we handled the exception, so that it doesn't crash canvas
328            qApp.restoreOverrideCursor()
329            return None
330
331        if x==-1 or y==-1:
332            if self.widgets != []:
333                x = self.widgets[-1].x() + 110
334                y = self.widgets[-1].y()
335            else:
336                x = 30
337                y = 150
338        newwidget.setCoords(x, y)
339        # move the widget to a valid position if necessary
340        invalidPosition = (self.canvasView.findItemTypeCount(self.canvas.collidingItems(newwidget), orngCanvasItems.CanvasWidget) > 0)
341        if invalidPosition:
342            for r in range(20, 200, 20):
343                for fi in [90, -90, 180, 0, 45, -45, 135, -135]:
344                    xOff = r * math.cos(math.radians(fi))
345                    yOff = r * math.sin(math.radians(fi))
346                    rect = QRectF(x+xOff, y+yOff, 48, 48)
347                    invalidPosition = self.canvasView.findItemTypeCount(self.canvas.items(rect), orngCanvasItems.CanvasWidget) > 0
348                    if not invalidPosition:
349                        newwidget.setCoords(x+xOff, y+yOff)
350                        break
351                if not invalidPosition:
352                    break
353           
354        #self.canvasView.ensureVisible(newwidget)
355
356        if caption == "":
357            caption = newwidget.caption
358
359        if self.getWidgetByCaption(caption):
360            i = 2
361            while self.getWidgetByCaption(caption + " (" + str(i) + ")"):
362                i+=1
363            caption = caption + " (" + str(i) + ")"
364        newwidget.updateText(caption)
365        newwidget.instance.setWindowTitle(caption)
366
367        self.widgets.append(newwidget)
368        if saveTempDoc:
369            self.saveTempDoc()
370        self.canvas.update()
371
372        # show the widget and activate the settings
373        try:
374            self.signalManager.addWidget(newwidget.instance)
375            newwidget.show()
376            newwidget.updateTooltip()
377            newwidget.setProcessing(1)
378            if self.canvasDlg.settings["saveWidgetsPosition"]:
379                newwidget.instance.restoreWidgetPosition()
380            newwidget.setProcessing(0)
381            orngHistory.logAddWidget(self.schemaID, id(newwidget), (newwidget.widgetInfo.category, newwidget.widgetInfo.name), newwidget.x(), newwidget.y())
382        except:
383            type, val, traceback = sys.exc_info()
384            sys.excepthook(type, val, traceback)  # we pretend that we handled the exception, so that it doesn't crash canvas
385
386        qApp.restoreOverrideCursor()
387        self.update_guide()
388        return newwidget
389
390    # remove widget
391    def removeWidget(self, widget, saveTempDoc = True):
392        if not widget:
393            return
394       
395        #with self.signalManager.freeze():
396        while widget.inLines != []: self.removeLine1(widget.inLines[0])
397        while widget.outLines != []:  self.removeLine1(widget.outLines[0])
398   
399        self.signalManager.removeWidget(widget.instance)
400           
401        self.widgets.remove(widget)
402        widget.remove()
403        if saveTempDoc:
404            self.saveTempDoc()
405       
406        self.update_guide()
407        orngHistory.logRemoveWidget(self.schemaID, id(widget), (widget.widgetInfo.category, widget.widgetInfo.name))
408
409    def clear(self):
410        self.canvasDlg.setCaption()
411        for widget in self.widgets[::-1]:   
412            self.removeWidget(widget, saveTempDoc = False)   # remove widgets from last to first
413        self.canvas.update()
414        self.schemaPath = self.canvasDlg.settings["saveSchemaDir"]
415        self.schemaName = ""
416        self.loadedSettingsDict = {}
417        self.saveTempDoc()
418
419    def enableAllLines(self):
420        with self.signalManager.freeze():
421            for line in self.lines:
422                self.signalManager.setLinkEnabled(line.outWidget.instance, line.inWidget.instance, 1)
423                line.update()
424        self.canvas.update()
425
426    def disableAllLines(self):
427        for line in self.lines:
428            self.signalManager.setLinkEnabled(line.outWidget.instance, line.inWidget.instance, 0)
429            line.update()
430        self.canvas.update()
431       
432    def setFreeze(self, bool):
433        self.signalManager.setFreeze(self.signalManager.freezing + (1 if bool else -1), None)
434
435    # return a new widget instance of a widget with filename "widgetName"
436    def addWidgetByFileName(self, widgetFileName, x, y, caption, widgetSettings = {}, saveTempDoc = True):
437        for category in self.canvasDlg.widgetRegistry.keys():
438            for name, widget in self.canvasDlg.widgetRegistry[category].items():
439                if widget.fileName == widgetFileName: 
440                    return self.addWidget(widget, x, y, caption, widgetSettings, saveTempDoc)
441        return None
442
443    # return the widget instance that has caption "widgetName"
444    def getWidgetByCaption(self, widgetName):
445        for widget in self.widgets:
446            if (widget.caption == widgetName):
447                return widget
448        return None
449
450    def getWidgetCaption(self, widgetInstance):
451        for widget in self.widgets:
452            if widget.instance == widgetInstance:
453                return widget.caption
454        print "Error. Invalid widget instance : ", widgetInstance
455        return ""
456
457
458    # get line from outWidget to inWidget
459    def getLine(self, outWidget, inWidget):
460        for line in self.lines:
461            if line.outWidget == outWidget and line.inWidget == inWidget:
462                return line
463        return None
464
465
466    # find orngCanvasItems.CanvasWidget from widget instance
467    def findWidgetFromInstance(self, widgetInstance):
468        for widget in self.widgets:
469            if widget.instance == widgetInstance:
470                return widget
471        return None
472
473
474    # ###########################################
475    # SAVING, LOADING, ....
476    # ###########################################
477    def reportAll(self):
478        for widget in self.widgets:
479            widget = widget.instance
480            if hasattr(widget, "sendReport"):
481                widget.reportAndFinish()
482           
483    def saveDocument(self):
484        if self.schemaName == "":
485            self.saveDocumentAs()
486        else:
487            self.save()
488            self.setSchemaModified(False)
489
490    def saveDocumentAs(self):
491        name = str(QFileDialog.getSaveFileName(self, "Save Orange Schema", os.path.join(self.schemaPath, self.schemaName or "Untitled.ows"), "Orange Widget Schema (*.ows)"))
492        if os.path.splitext(name)[0] == "":
493            return
494        if os.path.splitext(name)[1].lower() != ".ows":
495            name = os.path.splitext(name)[0] + ".ows"
496        self.save(name)
497        self.setSchemaModified(False)
498       
499    # save the file
500    def save(self, filename=None):
501        if filename is None:
502            filename = os.path.join(self.schemaPath, self.schemaName)
503           
504        # create xml document
505        doc = Document()
506        schema = doc.createElement("schema")
507        widgets = doc.createElement("widgets")
508        lines = doc.createElement("channels")
509        settings = doc.createElement("settings")
510        doc.appendChild(schema)
511        schema.appendChild(widgets)
512        schema.appendChild(lines)
513        schema.appendChild(settings)
514        settingsDict = {}
515
516        #save widgets
517        for widget in self.widgets:
518            temp = doc.createElement("widget")
519            temp.setAttribute("xPos", str(int(widget.x())) )
520            temp.setAttribute("yPos", str(int(widget.y())) )
521            temp.setAttribute("caption", widget.caption)
522            temp.setAttribute("widgetName", widget.widgetInfo.fileName)
523            settingsDict[widget.caption] = widget.instance.saveSettingsStr()
524            widgets.appendChild(temp)
525
526        #save connections
527        for line in self.lines:
528            temp = doc.createElement("channel")
529            temp.setAttribute("outWidgetCaption", line.outWidget.caption)
530            temp.setAttribute("inWidgetCaption", line.inWidget.caption)
531            temp.setAttribute("enabled", str(line.getEnabled()))
532            temp.setAttribute("signals", str(line.getSignals()))
533            lines.appendChild(temp)
534
535        settings.setAttribute("settingsDictionary", str(settingsDict))
536
537        xmlText = doc.toprettyxml()
538
539        file = open(filename, "wt")
540        file.write(xmlText)
541        file.close()
542        doc.unlink()
543
544        if os.path.splitext(filename)[1].lower() == ".ows":
545            (self.schemaPath, self.schemaName) = os.path.split(filename)
546            self.canvasDlg.settings["saveSchemaDir"] = self.schemaPath
547            self.canvasDlg.addToRecentMenu(filename)
548            self.canvasDlg.setCaption(self.schemaName)
549
550    def saveBeforeClose(self):
551        """ Call to ask the user to save the schema before it is closed.
552        Return True if save was successful or user did not want to save,
553        else return False (user canceled the operation).
554         
555        """
556        newSettings = self.isSchemaChanged()
557
558        self.synchronizeContexts()
559        if self.widgets != []:
560            self.save(os.path.join(self.canvasDlg.canvasSettingsDir, "lastSchema.tmp"))
561
562        if self.canvasDlg.settings["dontAskBeforeClose"]:
563            if newSettings and self.schemaName != "":
564                self.save()
565            return True
566               
567        elif newSettings:
568            res = QMessageBox.question(self, 'Orange Canvas', 'Do you wish to save the schema?', QMessageBox.Yes, QMessageBox.No, QMessageBox.Cancel)
569            if res == QMessageBox.Yes:
570                self.saveDocument()
571                return True
572            elif res == QMessageBox.No:
573                return True
574            else:     
575                # User pressed cancel - we don't want to close the document
576                return False
577        else:
578            return True
579
580    # load a scheme with name "filename"
581    def loadDocument(self, filename, caption = None, freeze = 0):
582        self.clear()
583       
584        if not os.path.exists(filename):
585            if os.path.splitext(filename)[1].lower() != ".tmp":
586                QMessageBox.critical(self, 'Orange Canvas', 'Unable to locate file "'+ filename + '"',  QMessageBox.Ok)
587            return
588
589        # set cursor
590        qApp.setOverrideCursor(Qt.WaitCursor)
591        failureText = ""
592       
593        if os.path.splitext(filename)[1].lower() == ".ows":
594            self.schemaPath, self.schemaName = os.path.split(filename)
595            self.canvasDlg.setCaption(caption or self.schemaName)
596        self.signalManager.freeze().push()
597        try:
598            #load the data ...
599            doc = parse(str(filename))
600            schema = doc.firstChild
601            widgets = schema.getElementsByTagName("widgets")[0]
602            lines = schema.getElementsByTagName("channels")[0]
603            settings = schema.getElementsByTagName("settings")
604            settingsDict = eval(str(settings[0].getAttribute("settingsDictionary")))
605            self.loadedSettingsDict = settingsDict
606             
607            # read widgets
608            loadedOk = 1
609            for widget in widgets.getElementsByTagName("widget"):
610                name = widget.getAttribute("widgetName")
611                settings = cPickle.loads(settingsDict[widget.getAttribute("caption")])
612                tempWidget = self.addWidgetByFileName(name, int(widget.getAttribute("xPos")), int(widget.getAttribute("yPos")), widget.getAttribute("caption"), settings, saveTempDoc = False)
613                if not tempWidget:
614                    #QMessageBox.information(self, 'Orange Canvas','Unable to create instance of widget \"'+ name + '\"',  QMessageBox.Ok + QMessageBox.Default)
615                    failureText += '<nobr>Unable to create instance of a widget <b>%s</b></nobr><br>' %(name)
616                    loadedOk = 0
617                qApp.processEvents()
618
619            #read lines
620            lineList = lines.getElementsByTagName("channel")
621            for line in lineList:
622                inCaption = line.getAttribute("inWidgetCaption")
623                outCaption = line.getAttribute("outWidgetCaption")
624                if freeze: enabled = 0
625                else:      enabled = int(line.getAttribute("enabled"))
626                signals = line.getAttribute("signals")
627                inWidget = self.getWidgetByCaption(inCaption)
628                outWidget = self.getWidgetByCaption(outCaption)
629                if inWidget == None or outWidget == None:
630                    failureText += "<nobr>Failed to create a signal line between widgets <b>%s</b> and <b>%s</b></nobr><br>" % (outCaption, inCaption)
631                    loadedOk = 0
632                    continue
633
634                signalList = eval(signals)
635               
636                for (outName, inName) in signalList:
637                    if not outName in [t[0] for t in outWidget.instance.outputs] \
638                            and outName in _CHANNEL_NAME_MAP:
639                        outName = _CHANNEL_NAME_MAP[outName]
640                       
641                    if not inName in [t[0] for t in inWidget.instance.inputs] \
642                            and inName in _CHANNEL_NAME_MAP:
643                        inName = _CHANNEL_NAME_MAP[inName]
644                       
645                    self.addLink(outWidget, inWidget, outName, inName, enabled, saveTempDoc=False)
646                #qApp.processEvents()
647        finally:
648            qApp.restoreOverrideCursor()
649            self.signalManager.freeze().pop()
650
651        for widget in self.widgets:
652            widget.updateTooltip()
653        self.canvas.update()
654
655        self.saveTempDoc()
656
657        if not loadedOk:
658            QMessageBox.information(self, 'Schema Loading Failed', 'The following errors occured while loading the schema: <br><br>' + failureText,  QMessageBox.Ok + QMessageBox.Default)
659
660        if self.widgets:
661            self.signalManager.processNewSignals(self.widgets[0].instance)
662           
663        # Store the loaded settings dict again
664        self.loadedSettingsDict = dict((widget.caption, widget.instance.saveSettingsStr()) for widget in self.widgets)
665        self.canvasDlg.setWindowModified(False)
666
667        # do we want to restore last position and size of the widget
668        if self.canvasDlg.settings["saveWidgetsPosition"]:
669            for widget in self.widgets:
670                widget.instance.restoreWidgetStatus()
671           
672       
673
674    # save document as application
675    def saveDocumentAsApp(self, asTabs = 1):
676        # get filename
677        extension = sys.platform == "win32" and ".pyw" or ".py"
678        appName = (os.path.splitext(self.schemaName)[0] or "Schema") + extension
679        appPath = os.path.exists(self.canvasDlg.settings["saveApplicationDir"]) and self.canvasDlg.settings["saveApplicationDir"] or self.schemaPath
680        qname = QFileDialog.getSaveFileName(self, "Save Orange Schema as Application", os.path.join(appPath, appName) , "Orange Scripts (*%s)" % extension)
681        if qname.isEmpty(): return
682        (appPath, appName) = os.path.split(str(qname))
683        appNameWithoutExt = os.path.splitext(appName)[0]
684        if os.path.splitext(appName)[1].lower() not in [".py", ".pyw"]: appName = appNameWithoutExt + extension
685        self.canvasDlg.settings["saveApplicationDir"] = appPath
686
687        saveDlg = saveApplicationDlg(None)
688
689        # add widget captions
690        for instance in self.signalManager.widgets:
691            widget = None
692            for i in range(len(self.widgets)):
693                if self.widgets[i].instance == instance: saveDlg.insertWidgetName(self.widgets[i].caption)
694
695        if saveDlg.exec_() == QDialog.Rejected:
696            return
697
698        #format string with file content
699        t = "    "  # instead of tab
700        n = "\n"
701
702        start = """#!/usr/bin/env python
703
704#This file is automatically created by Orange Canvas and containing an Orange schema
705
706import orngEnviron
707import orngDebugging
708import sys, os, cPickle, orange, orngSignalManager, OWGUI
709from OWBaseWidget import *
710
711class GUIApplication(OWBaseWidget):
712    def __init__(self,parent=None):
713        self.signalManager = orngSignalManager.SignalManager()
714        OWBaseWidget.__init__(self, title = '%s', signalManager = self.signalManager)
715        self.widgets = {}
716        self.loadSettings()
717        """ % (appNameWithoutExt)
718
719        if asTabs == 1:
720            start += """
721        self.tabs = QTabWidget(self)
722        self.setLayout(QVBoxLayout())
723        self.layout().addWidget(self.tabs)
724        self.resize(800,600)"""
725        else:
726            start += """
727        self.setLayout(QVBoxLayout())
728        self.box = OWGUI.widgetBox(self, 'Widgets')"""
729
730
731        links = "# add widget signals\n"+t+t + "self.signalManager.setFreeze(1)\n" +t+t
732        widgetParameters = ""
733
734        # gui for shown widgets
735        for widgetName in saveDlg.shownWidgetList:    # + saveDlg.hiddenWidgetList
736            if widgetName != "[Separator]":
737                widget = None
738                for i in range(len(self.widgets)):
739                    if self.widgets[i].caption == widgetName: widget = self.widgets[i]
740
741                shown = widgetName in saveDlg.shownWidgetList
742                widgetParameters += "self.createWidget('%s', '%s', '%s', %d, self.signalManager)\n" % (widget.widgetInfo.fileName, widget.widgetInfo.icon, widget.caption, shown) +t+t
743            else:
744                if not asTabs:
745                    widgetParameters += "self.box.layout().addSpacing(10)\n" +t+t
746
747        for line in self.lines:
748            if not line.getEnabled(): continue
749            for (outName, inName) in line.getSignals():
750                links += "self.signalManager.addLink( self.widgets['" + line.outWidget.caption+ "'], self.widgets['" + line.inWidget.caption+ "'], '" + outName + "', '" + inName + "', 1)\n" +t+t
751
752        links += "self.signalManager.setFreeze(0)\n" +t+t
753        if not asTabs:
754            widgetParameters += """
755        box2 = OWGUI.widgetBox(self, 1)
756        exitButton = OWGUI.button(box2, self, "Exit", callback = self.accept)
757        self.layout().addStretch(100)"""
758
759        if asTabs:
760            guiText = "OWGUI.createTabPage(self.tabs, caption, widget)"
761        else:
762            guiText = "OWGUI.button(self.box, self, caption, callback = widget.reshow)"
763
764        progress = """
765        statusBar = QStatusBar(self)
766        self.layout().addWidget(statusBar)
767        self.caption = QLabel('', statusBar)
768        self.caption.setMaximumWidth(230)
769        self.progress = QProgressBar(statusBar)
770        self.progress.setMaximumWidth(100)
771        self.status = QLabel("", statusBar)
772        self.status.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred))
773        statusBar.addWidget(self.progress)
774        statusBar.addWidget(self.caption)
775        statusBar.addWidget(self.status)"""
776
777        handlerFuncts = """
778    def createWidget(self, fname, iconName, caption, shown, signalManager):
779        widgetSettings = cPickle.loads(self.strSettings[caption])
780        m = __import__(fname)
781        widget = m.__dict__[fname].__new__(m.__dict__[fname], _settingsFromSchema = widgetSettings)
782        widget.__init__(signalManager=signalManager)
783        widget.setEventHandler(self.eventHandler)
784        widget.setProgressBarHandler(self.progressHandler)
785        widget.setWidgetIcon(iconName)
786        widget.setWindowTitle(caption)
787        self.signalManager.addWidget(widget)
788        self.widgets[caption] = widget
789        if shown: %s
790        for dlg in getattr(widget, "wdChildDialogs", []):
791            dlg.setEventHandler(self.eventHandler)
792            dlg.setProgressBarHandler(self.progressHandler)
793
794    def eventHandler(self, text, eventVerbosity = 1):
795        if orngDebugging.orngVerbosity >= eventVerbosity:
796            self.status.setText(text)
797
798    def progressHandler(self, widget, val):
799        if val < 0:
800            self.caption.setText("<nobr>Processing: <b>" + str(widget.captionTitle) + "</b></nobr>")
801            self.progress.setValue(0)
802        elif val >100:
803            self.caption.setText("")
804            self.progress.reset()
805        else:
806            self.progress.setValue(val)
807            self.update()
808
809    def loadSettings(self):
810        try:
811            file = open("%s", "r")
812            self.strSettings = cPickle.load(file)
813            file.close()
814
815        except:
816            print "unable to load settings"
817            pass
818
819    def closeEvent(self, ev):
820        OWBaseWidget.closeEvent(self, ev)
821        if orngDebugging.orngDebuggingEnabled: return
822        strSettings = {}
823        for (name, widget) in self.widgets.items():
824            widget.synchronizeContexts()
825            strSettings[name] = widget.saveSettingsStr()
826            widget.close()
827        file = open("%s", "w")
828        cPickle.dump(strSettings, file)
829        file.close()
830
831if __name__ == "__main__":
832    application = QApplication(sys.argv)
833    ow = GUIApplication()
834    ow.show()
835    # comment the next line if in debugging mode and are interested only in output text in 'signalManagerOutput.txt' file
836    application.exec_()
837        """ % (guiText, appNameWithoutExt + ".sav", appNameWithoutExt + ".sav")
838
839
840        #save app
841        f = open(os.path.join(appPath, appName), "wt")
842        f.write(start + n+n+t+t+ widgetParameters + n+t+t + progress + n+n+t+t + links + n + handlerFuncts)
843        f.close()
844
845        # save widget settings
846        list = {}
847        for widget in self.widgets:
848            list[widget.caption] = widget.instance.saveSettingsStr()
849
850        f = open(os.path.join(appPath, appNameWithoutExt) + ".sav", "wt")
851        cPickle.dump(list, f)
852        f.close
853       
854       
855    def dumpWidgetVariables(self):
856        for widget in self.widgets:
857            self.canvasDlg.output.write("<hr><b>%s</b><br>" % (widget.caption))
858            v = vars(widget.instance).keys()
859            v.sort()
860            for val in v:
861                self.canvasDlg.output.write("%s = %s" % (val, getattr(widget.instance, val)))
862
863    def keyReleaseEvent(self, e):
864        self.ctrlPressed = int(e.modifiers()) & Qt.ControlModifier != 0
865        e.ignore()
866
867    def keyPressEvent(self, e):
868        self.ctrlPressed = int(e.modifiers()) & Qt.ControlModifier != 0
869        if e.key() > 127 or e.key() < 0:
870            #e.ignore()
871            QWidget.keyPressEvent(self, e)
872            return
873
874        # the list could include (e.ShiftButton, "Shift") if the shift key didn't have the special meaning
875        pressed = "-".join(filter(None, [int(e.modifiers()) & x and y for x, y in [(Qt.ControlModifier, "Ctrl"), (Qt.AltModifier, "Alt")]]) + [chr(e.key())])
876        widgetToAdd = self.canvasDlg.widgetShortcuts.get(pressed)
877        if widgetToAdd:
878            self.addWidget(widgetToAdd)
879            if e.modifiers() & Qt.ShiftModifier and len(self.widgets) > 1:
880                self.addLine(self.widgets[-2], self.widgets[-1])
881        else:
882            #e.ignore()
883            QWidget.keyPressEvent(self, e)
884
885#    def resizeEvent(self, ev):
886#        QWidget.resizeEvent(self, ev)
887#        self.canvas.addRect(self.canvasView.size().width()-1, self.canvasView.size().height()-1, 1, 1)
Note: See TracBrowser for help on using the repository browser.