source: orange/orange/OrangeCanvas/orngDoc.py @ 8126:5807863e8448

Revision 8126:5807863e8448, 33.7 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Fixed an error with closeEvent handling (would close the canvas even if user canceled the operation).

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