source: orange/Orange/OrangeWidgets/Prototypes/OWRScript.py @ 10462:f615f9bb8348

Revision 10462:f615f9bb8348, 15.4 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Added unicode filename support for save/load widgets.

Line 
1"""<name>R Script</name>
2<description>Run R scripts</description>
3<icon>icons/RScript.png</icon>
4"""
5
6
7from OWWidget import *
8
9from OWPythonScript import OWPythonScript, Script, ScriptItemDelegate, PythonConsole
10from OWItemModels import PyListModel, ModelActionsWidget
11
12import Orange.misc.r as r
13
14globalenv = r.globalenv
15
16import rpy2.rinterface
17import rpy2.robjects as robjects
18import rpy2.rlike.container as rlc
19
20class RPy2Console(PythonConsole):
21   
22    def interact(self, banner=None):
23        if banner is None:
24            banner ="R console:"
25        return PythonConsole.interact(self, banner)
26       
27    def push(self, line):
28        if self.history[0] != line:
29            self.history.insert(0, line)
30        self.historyInd = 0
31        more = 0
32        try:
33            r_res = self._rpy(line)
34            if not isinstance(r_res, rpy2.rinterface.RNULLType):
35                self.write(r_res.r_repr())
36        except rpy2.rinterface.RRuntimeError, ex:
37            self.write(ex.message)
38        except Exception, ex:
39            self.write(repr(ex))
40       
41        self.write("\n")
42       
43    def _rpy(self, script):
44        r_res = robjects.r(script)
45        return r_res
46   
47    def setLocals(self, locals):
48        r_obj = {}
49        for key, val in locals.items():
50            if isinstance(val, orange.ExampleTable):
51                dataframe = r.dataframe(val)
52                globalenv[key] = dataframe
53            elif isinstance(val, orange.SymMatrix):
54                matrix = r.matrix(val)
55                globalenv[key] = matrix
56               
57               
58class RSyntaxHighlighter(QSyntaxHighlighter):
59    def __init__(self, parent=None):
60        self.keywordFormat = QTextCharFormat()
61        self.keywordFormat.setForeground(QBrush(Qt.darkGray))
62        self.keywordFormat.setFontWeight(QFont.Bold)
63        self.stringFormat = QTextCharFormat()
64        self.stringFormat.setForeground(QBrush(Qt.darkGreen))
65        self.defFormat = QTextCharFormat()
66        self.defFormat.setForeground(QBrush(Qt.black))
67        self.defFormat.setFontWeight(QFont.Bold)
68        self.commentFormat = QTextCharFormat()
69        self.commentFormat.setForeground(QBrush(Qt.lightGray))
70        self.decoratorFormat = QTextCharFormat()
71        self.decoratorFormat.setForeground(QBrush(Qt.darkGray))
72        self.constantFormat = QTextCharFormat()
73        self.constantFormat.setForeground(QBrush(Qt.blue))
74       
75        self.keywords = ["TRUE", "FALSE", "if", "else", "NULL", r"\.\.\.", "<-", "for", "while", "repeat", "next",
76                         "break", "switch", "function"]
77        self.rules = [(QRegExp(r"\b%s\b" % keyword), self.keywordFormat) for keyword in self.keywords] + \
78                     [
79#                     [(QRegExp(r"\bdef\s+([A-Za-z_]+[A-Za-z0-9_]+)\s*\("), self.defFormat),
80#                      (QRegExp(r"\bclass\s+([A-Za-z_]+[A-Za-z0-9_]+)\s*\("), self.defFormat),
81                      (QRegExp(r"'.*'"), self.stringFormat),
82                      (QRegExp(r'".*"'), self.stringFormat),
83                      (QRegExp(r"#.*"), self.commentFormat),
84                      (QRegExp(r"[0-9]+\.?[0-9]*"), self.constantFormat)
85#                      (QRegExp(r"@[A-Za-z_]+[A-Za-z0-9_]+"), self.decoratorFormat)]
86                     ]
87                     
88        QSyntaxHighlighter.__init__(self, parent)
89       
90    def highlightBlock(self, text):
91        for pattern, format in self.rules:
92            exp = QRegExp(pattern)
93            index = exp.indexIn(text)
94            while index >= 0:
95                length = exp.matchedLength()
96                if exp.numCaptures() > 0:
97                    self.setFormat(exp.pos(1), len(str(exp.cap(1))), format)
98                else:
99                    self.setFormat(exp.pos(0), len(str(exp.cap(0))), format)
100                index = exp.indexIn(text, index + length)
101               
102        ## Multi line strings
103#        start = QRegExp(r"(''')|" + r'(""")')
104#        end = QRegExp(r"(''')|" + r'(""")')
105#        self.setCurrentBlockState(0)
106#        startIndex, skip = 0, 0
107#        if self.previousBlockState() != 1:
108#            startIndex, skip = start.indexIn(text), 3
109#        while startIndex >= 0:
110#            endIndex = end.indexIn(text, startIndex + skip)
111#            if endIndex == -1:
112#                self.setCurrentBlockState(1)
113#                commentLen = len(text) - startIndex
114#            else:
115#                commentLen = endIndex - startIndex + 3
116#            self.setFormat(startIndex, commentLen, self.stringFormat)
117#            startIndex, skip = start.indexIn(text, startIndex + commentLen + 3), 3
118
119class RScriptEditor(QPlainTextEdit):
120    pass
121
122class RScript(Script):
123    pass
124
125class LibraryWidget(QWidget):
126    def __init__(self, parent=None):
127        QWidget.__init__(self, parent)
128        self.setLayout(QVBoxLayout())
129       
130        self.layout().setSpacing(1)
131        self.layout().setContentsMargins(0, 0, 0, 0)
132        self.view = QListView(self)
133        self.view.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
134        self.layout().addWidget(self.view)
135       
136        self.addScriptAction = QAction("+", self)
137        self.addScriptAction.pyqtConfigure(toolTip="Add a new script to library")
138       
139        self.updateScriptAction = QAction("Update", self)
140        self.updateScriptAction.pyqtConfigure(toolTip="Save changes in the editor to library", shortcut=QKeySequence.Save)
141       
142        self.removeScriptAction = QAction("-", self)
143        self.removeScriptAction.pyqtConfigure(toolTip="Remove selected script from file")
144       
145        self.moreAction = QAction("More", self)
146        self.moreAction.pyqtConfigure(toolTip="More actions")#, icon=self.style().standardIcon(QStyle.SP_ToolBarHorizontalExtensionButton))
147       
148        self.addScriptFromFile = QAction("Add script from file", self)
149        self.addScriptFromFile.pyqtConfigure(toolTip="Add a new script to library from a file")
150       
151        self.saveScriptToFile = QAction("Save script to file", self)
152        self.saveScriptToFile.pyqtConfigure(toolTip="Save script to a file", shortcut=QKeySequence.SaveAs)
153       
154        menu = QMenu(self)
155        menu.addActions([self.addScriptFromFile, self.saveScriptToFile])
156        self.moreAction.setMenu(menu)
157       
158        self.actionsWidget = ModelActionsWidget([self.addScriptAction, self.updateScriptAction, 
159                                            self.removeScriptAction, self.moreAction], self)
160#        self.actionsWidget.buttons[1].setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
161        self.actionsWidget.layout().setSpacing(1)
162       
163        self.layout().addWidget(self.actionsWidget)
164       
165        self.resize(800, 600)
166       
167       
168    def setScriptModel(self, model):
169        """ Set the script model to show
170        """
171        self.model = model
172        if self.view is not None:
173            self.view.setModel(model)
174       
175       
176    def setView(self, view):
177        """ Set the view (QListView or subclass) to use
178        """
179        if self.view is not None:
180            self.layout().removeItemAt(0) 
181        self.view = view
182        self.layout().insertItem(0, view)
183        if self.model is not None:
184            self.view.setModel(self.model)
185           
186           
187    def setActionsWidget(self, widget):
188        """ Set the widget below the view to widget (removing the previous widget)
189        """
190        self.layout().removeItemAt(1)
191        self.actionsWidget = widget
192        self.layout().insertWidget(1, widget)
193
194    def setDocumentEditor(self, editor):
195        self.editor = editor
196       
197
198class OWRScript(OWWidget):
199    settingsList = ["scriptLibraryList", "selectedScriptIndex", "lastDir"]
200    def __init__(self, parent=None, signalManager=None, name="R Script"):
201        OWWidget.__init__(self, parent, signalManager, name)
202       
203        self.inputs = [("in_data", ExampleTable, self.setData), ("in_distance", orange.SymMatrix, self.setDistance)]
204        self.outputs = [("out_data", ExampleTable), ("out_distance", orange.SymMatrix), ("out_learner", orange.Learner), ("out_classifier", orange.Classifier)]
205       
206        self.in_data, self.in_distance = None, None
207        self.scriptLibraryList = [RScript("New script", "x <- c(1,2,5)\ny <- c(2,1 6)\nplot(x,y)\n")]
208        self.selectedScriptIndex = 0
209        self.selectedGrDev = "PNG"
210       
211        self.lastDir = os.path.expanduser("~/script.R")
212       
213        self.loadSettings()
214       
215        self.defaultFont = QFont("Monaco") if sys.platform == "darwin" else QFont("Courier")
216       
217        self.splitter = QSplitter(Qt.Vertical, self.mainArea)
218        self.mainArea.layout().addWidget(self.splitter)
219        self.scriptEdit = RScriptEditor()
220        self.splitter.addWidget(self.scriptEdit)
221        self.console = RPy2Console({}, self)
222        self.splitter.addWidget(self.console)
223        self.splitter.setSizes([2,1])
224       
225        self.libraryModel = PyListModel([], self, Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable)
226        self.libraryModel.wrap(self.scriptLibraryList)
227       
228        self.infoBox = OWGUI.widgetLabel(OWGUI.widgetBox(self.controlArea, "Info", addSpace=True), "")
229        self.infoBox.setText("<p>Execute R script</p>Input variables<li><ul>in_data</ul><ul>in_distance</ul></li>Output variables:<li><ul>out_data</ul></p/>")
230       
231        box = OWGUI.widgetBox(self.controlArea, "Library", addSpace=True)
232       
233        self.libraryWidget = LibraryWidget()
234        self.libraryView = self.libraryWidget.view
235        box.layout().addWidget(self.libraryWidget)
236       
237        self.libraryWidget.view.setItemDelegate(ScriptItemDelegate(self))
238        self.libraryWidget.setScriptModel(self.libraryModel)
239        self.libraryWidget.setDocumentEditor(self.scriptEdit)
240       
241        box = OWGUI.widgetBox(self.controlArea, "Run")
242        OWGUI.button(box, self, "Execute", callback=self.runScript, tooltip="Execute the script")
243       
244        OWGUI.rubber(self.controlArea)
245       
246       
247        self.connect(self.libraryWidget.addScriptAction, SIGNAL("triggered()"), self.onAddNewScript)
248        self.connect(self.libraryWidget.updateScriptAction, SIGNAL("triggered()"), self.onUpdateScript)
249        self.connect(self.libraryWidget.removeScriptAction, SIGNAL("triggered()"), self.onRemoveScript)
250        self.connect(self.libraryWidget.addScriptFromFile, SIGNAL("triggered()"), self.onAddScriptFromFile)
251        self.connect(self.libraryWidget.saveScriptToFile, SIGNAL("triggered()"), self.onSaveScriptToFile)
252       
253        self.connect(self.libraryView.selectionModel(), SIGNAL("selectionChanged(QItemSelection, QItemSelection)"), self.onScriptSelection)
254       
255        self._cachedDocuments = {}
256       
257        self.grView = QLabel()
258       
259        self.resize(800, 600)
260        QTimer.singleShot(30, self.initGrDevice)
261       
262    def initGrDevice(self):
263        import tempfile
264        self.grDevFile = tempfile.NamedTemporaryFile("rwb", prefix="orange-rscript-grDev", suffix=".png", delete=False)
265        self.grDevFile.close()
266        robjects.r('png("%s", width=512, height=512)' % self.grDevFile.name)
267        self.fileWatcher = QFileSystemWatcher(self)
268        self.fileWatcher.addPath(self.grDevFile.name)
269        self.connect(self.fileWatcher, SIGNAL("fileChanged(QString)"), self.updateGraphicsView)
270       
271    def updateGraphicsView(self, filename):
272        self.grView.setPixmap(QPixmap(filename))
273        self.grView.show()
274        if self.grDevFile.name not in list(self.fileWatcher.files()):
275            # The file can be removed and recreated by R, in which case we need to re-add it
276            self.fileWatcher.addPath(self.grDevFile.name)
277
278    def selectScript(self, index):
279        index = self.libraryModel.index(index)
280        self.libraryView.selectionModel().select(index, QItemSelectionModel.ClearAndSelect)
281       
282    def selectedScript(self):
283        rows = self.libraryView.selectionModel().selectedRows()
284        rows = [index.row() for index in rows]
285        if rows:
286            return rows[0]
287        else:
288            return None
289       
290    def onAddNewScript(self):
291        self.libraryModel.append(RScript("New Script", ""))
292        self.selectScript(len(self.libraryModel) - 1)
293       
294    def onRemoveScript(self):
295        row = self.selectedScript()
296        if row is not None:
297            del self.libraryModel[row]
298       
299    def onUpdateScript(self):
300        row = self.selectedScript()
301        if row is not None:
302            self.libraryModel[row].script = str(self.scriptEdit.toPlainText())
303            self.scriptEdit.document().setModified(False)
304            self.libraryModel.emitDataChanged([row])   
305       
306    def onAddScriptFromFile(self):
307        filename = QFileDialog.getOpenFileName(self, "Open script", self.lastDir)
308        filename = unicode(filename)
309        if filename:
310            script = open(filename, "rb").read()
311            self.lastDir, name = os.path.split(filename)
312            self.libraryModel.append(RScript(name, script, sourceFileName=filename))
313            self.selectScript(len(self.libraryModel) - 1)   
314           
315    def onSaveScriptToFile(self):
316        row = self.selectedScript()
317        if row is not None:
318            script = self.libraryModel[row]
319            filename = QFileDialog.getSaveFileName(self, "Save Script As", script.sourceFileName or self.lastDir)
320            filename = unicode(filename)
321            if filename:
322                self.lastDir, name = os.path.split(filename)
323                script.sourceFileName = filename
324                script.flags = 0
325                open(filename, "wb").write(script.script)
326                       
327    def onScriptSelection(self, *args):
328        row = self.selectedScript()
329        if row is not None:
330            self.scriptEdit.setDocument(self.documentForScript(row))
331                       
332    def documentForScript(self, script=0):
333        if type(script) != RScript:
334            script = self.libraryModel[script]
335        if script not in self._cachedDocuments:
336            doc = QTextDocument(self)
337            doc.setDocumentLayout(QPlainTextDocumentLayout(doc))
338            doc.setPlainText(script.script)
339            doc.setDefaultFont(QFont(self.defaultFont))
340            doc.highlighter = RSyntaxHighlighter(doc)
341            self.connect(doc, SIGNAL("modificationChanged(bool)"), self.onModificationChanged)
342            doc.setModified(False)
343            self._cachedDocuments[script] = doc
344        return self._cachedDocuments[script]
345   
346    def onModificationChanged(self, changed):
347        row = self.selectedScript()
348        if row is not None:
349            self.libraryModel[row].flags = RScript.Modified if changed else 0
350            self.libraryModel.emitDataChanged([row]) 
351                             
352    def setData(self, data):
353        self.in_data = data
354        self.console.setLocals(self.getLocals())
355
356    def setDistance(self, matrix):
357        self.in_distance = matrix
358        self.console.setLocals(self.getLocals())
359
360    def getLocals(self):
361        return {"in_data": self.in_data,
362                "in_distance": self.in_distance,
363               }
364       
365    def runScript(self):
366        self.console.push('png("%s", width=512, height=512)\n' % self.grDevFile.name)
367        self.console.push(str(self.scriptEdit.toPlainText()))
368        self.console.new_prompt(">>> ")
369        robjects.r("dev.off()\n")
370       
371   
372if __name__ == "__main__":
373    app = QApplication(sys.argv)
374    w = OWRScript()
375    data = orange.ExampleTable("../../doc/datasets/iris.tab")
376    w.setData(data)
377    w.show()
378    app.exec_()
379    w.saveSettings()
380       
381             
382       
Note: See TracBrowser for help on using the repository browser.