# -*- python -*-
#
# OpenAlea.Visualea: OpenAlea graphical user interface
#
# Copyright 2006-2009 INRIA - CIRAD - INRA
#
# File author(s): Samuel Dufour-Kowalski <samuel.dufour@sophia.inria.fr>
# Christophe Pradal <christophe.prada@cirad.fr>
#
# Distributed under the CeCILL v2 License.
# See accompanying file LICENSE.txt or copy at
# http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
#
# OpenAlea WebSite : http://openalea.gforge.inria.fr
#
################################################################################
"""Python code editor"""
from __future__ import print_function
from builtins import str
from builtins import object
__license__ = "CeCILL V2"
__revision__ = " $Id$"
from qtpy import QtWidgets, QtGui, QtCore
import os
from subprocess import Popen
from openalea.core.settings import Settings
from openalea.visualea.util import open_dialog
from openalea.core.path import path
[docs]
def get_editor():
""" Return the editor class """
editor = PythonCodeEditor
s = Settings()
try:
str = s.get('editor', 'use_external')
l = eval(str)
if(l): editor = ExternalCodeEditor
except:
pass
return editor
[docs]
class AbstractCodeEditor(object):
""" External code editor """
def __init__(self, *args):
pass
[docs]
def is_empty(self):
return False
[docs]
def edit_file(self, filename):
""" Open file in the editor """
[docs]
def edit_module(self, module, class_name=None):
""" Edit the source file of a python module """
import inspect
filename = inspect.getsourcefile(module)
self.edit_file(filename)
[docs]
class ExternalCodeEditor(AbstractCodeEditor):
""" External code editor """
def __init__(self, *args):
AbstractCodeEditor.__init__(self)
[docs]
def get_command(self):
""" Return command to execute """
s = Settings()
cmd = ""
try:
cmd = s.get('editor', 'command')
except:
cmd = ""
if(not cmd):
if('posix' in os.name):
return "/usr/bin/vim"
else:
return "C:\\windows\\notepad.exe"
return cmd
[docs]
def edit_file(self, filename):
""" Open file in the editor """
if(not filename):
ret = QtWidgets.QMessageBox.warning(None, "Error", "Cannot find the file to edit.")
return
c = self.get_command()
try:
Popen([c, filename])
except:
print("Cannot execute %s"%(c,))
[docs]
class PythonCodeEditor(QtWidgets.QWidget, AbstractCodeEditor):
""" Simple Python code editor """
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
AbstractCodeEditor.__init__(self)
self.textedit = self.get_editor()
vboxlayout = QtWidgets.QVBoxLayout(self)
vboxlayout.setContentsMargins(1, 1, 1, 1)
vboxlayout.setSpacing(1)
self.hboxlayout = QtWidgets.QHBoxLayout()
self.hboxlayout.setContentsMargins(1, 1, 1, 1)
self.hboxlayout.setSpacing(1)
self.applybut = QtWidgets.QPushButton("Apply changes", self)
self.hboxlayout.addWidget(self.applybut)
self.savbut = QtWidgets.QPushButton("Save changes", self)
self.hboxlayout.addWidget(self.savbut)
vboxlayout.addLayout(self.hboxlayout)
vboxlayout.addWidget(self.textedit)
self.label = QtWidgets.QLabel("")
vboxlayout.addWidget(self.label)
self.savescut = QtWidgets.QShortcut( QtGui.QKeySequence(QtGui.QKeySequence.Save), self)
self.savescut.activated.connect(self.save_changes)
self.savbut.clicked.connect(self.save_changes)
self.applybut.clicked.connect(self.apply_changes)
[docs]
def on_file_changed(self, path):
ret = QtWidgets.QMessageBox.question(self, "File has changed on the disk.",
"Reload ?\n",
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No,)
if(ret == QtWidgets.QMessageBox.No): return
self.edit_file(self.filename)
[docs]
def get_editor(self):
"""
Return an editor object based on QScintilla if available.
Else, return a standard editor.
"""
try:
from PyQt5.Qsci import QsciScintilla, QsciLexerPython, QsciAPIs
textedit = QsciScintilla(self)
textedit.setAutoIndent(True)
textedit.setAutoCompletionThreshold(2)
textedit.setAutoCompletionSource(QsciScintilla.AcsDocument)
# API
lex = QsciLexerPython(textedit)
textedit.setLexer(lex)
# apis = QsciAPIs(lex)
# apis.prepare()
textedit.setMinimumWidth(250)
textedit.setMinimumHeight(250)
except ImportError:
textedit = QtWidgets.QTextEdit(self)
textedit.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
textedit.setMinimumWidth(200)
textedit.setMinimumHeight(200)
return textedit
[docs]
def goToLine(self, linenb):
""" Go to line nb """
try:
self.ensureLineVisible(linenb)
except:
pass
[docs]
def setText(self, str):
""" Set the text of the editor """
try:
self.textedit.setText(str)
except:
self.textedit.setPlainText(str)
[docs]
def getText(self):
""" Return editor text """
try:
return self.textedit.text()
except:
return self.textedit.toPlainText()
[docs]
def edit_file(self, filename):
""" Open file in the editor """
if(filename):
filename = os.path.abspath(filename)
self.filename = filename
try:
f = open(filename, 'r')
self.textedit.setText(f.read())
self.label.setText("File : " + filename)
self.file_stat = os.stat(filename)
f.close()
self.savbut.setEnabled(True)
self.applybut.setEnabled(False)
self.filewatcher = QtCore.QFileSystemWatcher(self)
self.filewatcher.addPath(self.filename)
self.filewatcher.fileChanged.connect(self.on_file_changed)
except Exception as e:
print(e)
self.src = None
self.applybut.setEnabled(False)
self.savbut.setEnabled(False)
self.textedit.setText(" Sources are not available...")
[docs]
def edit_module(self, module, class_name=None):
""" Edit the source file of a python module """
self.module = module
if(not module):
self.applybut.setEnabled(False)
return
import inspect
filename = inspect.getsourcefile(module)
self.edit_file(filename)
self.savbut.setEnabled(True)
self.applybut.setEnabled(True)
[docs]
def apply_changes(self):
""" Reload file """
if(self.module):
newsrc = str(self.getText())
exec(newsrc, self.module.__dict__)
[docs]
def save_changes(self):
""" Save module """
if(not os.access(self.filename, os.W_OK)):
ret = QtWidgets.QMessageBox.warning(self, "Cannot write file %s", self.filename)
return
self.filewatcher.removePath(self.filename)
try:
f = open(self.filename, 'w')
f.write(str(self.getText()))
self.label.setText("Write file : " + self.filename)
finally:
f.close()
self.filewatcher.addPath(self.filename)
[docs]
class NodeCodeEditor(PythonCodeEditor):
""" Default node editor """
def __init__(self, factory, parent=None):
PythonCodeEditor.__init__(self, parent)
self.factory = factory
self.src = None
self.edit_class(factory)
[docs]
def edit_class(self, nodefactory):
""" Open class source in editor """
try:
self.src = nodefactory.get_node_src()
self.textedit.setText(self.src)
self.label.setText("Module : " + self.factory.nodemodule_path)
except Exception as e:
print(e)
self.src = None
self.applybut.setEnabled(False)
self.savbut.setEnabled(False)
self.textedit.setText(" Sources are not available...")
[docs]
def apply_changes(self):
""" Apply """
self.src = str(self.getText())
if(self.src != self.factory.get_node_src()):
self.factory.apply_new_src(self.src)
[docs]
def save_changes(self):
""" Save module """
ret = QtWidgets.QMessageBox.question(self, "Save",
"Modification will be written in the module\n"+
"Continue ?\n",
QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No,)
if(ret == QtWidgets.QMessageBox.No): return
module_name = self.factory.nodemodule_name
newsrc = str(self.getText())
self.factory.save_new_src(newsrc)
[docs]
class Command(object):
"""
Execute a command depending on a filename.
Create a process and execute the command locally.
"""
def __init__(self, command):
self.p = None
self.command = command
def __del__(self):
if self.p and (self.p.poll() is None):
try:
os.kill(self.p.pid,1)
except:
pass
self.p = None
def __call__(self, filename):
fn = path(filename)
cwd = fn.dirname()
name = str(fn.basename())
if self.p and (self.p.poll() is None):
os.kill(self.p.pid, 1)
cmd = self.command.split('%')[0]
cmd = cmd.split('-')[0]
try:
import win32api
cmd1 = win32api.GetShortPathName(cmd)
command = self.command.replace(cmd, cmd1)
except ImportError:
command = self.command
self.p = Popen(command%name, shell = True, cwd = cwd)
[docs]
class EditorSelector(AbstractCodeEditor, QtWidgets.QWidget):
"""
Dialog to select an editor
"""
def __init__(self, parent, editors, params):
"""
@param editors : dictionnary name:command
@param params : strings to replace command param (%s)
"""
QtWidgets.QWidget.__init__(self, parent)
vboxlayout = QtWidgets.QVBoxLayout(self)
vboxlayout.setContentsMargins(3, 3, 3, 3)
vboxlayout.setSpacing(5)
self.editors = editors
self.params = params
# put the edit button in the first place
keys = list(editors.keys())
if 'edit' in keys:
keys.remove('edit')
keys.insert(0, 'edit')
for k in keys:
but = QtWidgets.QPushButton(self)
but.setText(k)
vboxlayout.addWidget(but)
but.clicked.connect(self.on_button_clicked)
def __del__(self):
""" Destroy widget """
for e in list(self.editors.values()):
try:
e.close()
except:
del e