Skip to content

Commit 59a5a14

Browse files
committed
[FEATURE] new Scintilla-based script editor for Processing
1 parent 017f8f2 commit 59a5a14

17 files changed

+686
-520
lines changed

python/plugins/processing/script/CreateNewScriptAction.py renamed to python/plugins/processing/gui/CreateNewScriptAction.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,35 @@
2323
# This will get replaced with a git SHA1 when you do a git archive
2424
__revision__ = '$Format:%H$'
2525

26-
from processing.script.EditScriptDialog import EditScriptDialog
26+
from PyQt4.QtGui import *
27+
2728
from processing.gui.ToolboxAction import ToolboxAction
28-
import os
29-
from PyQt4 import QtGui
29+
from processing.gui.ScriptEditorDialog import ScriptEditorDialog
30+
31+
import processing.resources_rc
3032

3133
class CreateNewScriptAction(ToolboxAction):
3234

33-
def __init__(self):
34-
self.name="Create new script"
35-
self.group="Tools"
35+
SCRIPT_PYTHON = 0
36+
SCRIPT_R = 1
37+
38+
def __init__(self, actionName, scriptType):
39+
self.name = actionName
40+
self.group = "Tools"
41+
self.scriptType = scriptType
3642

3743
def getIcon(self):
38-
return QtGui.QIcon(os.path.dirname(__file__) + "/../images/script.png")
44+
if self.scriptType == self.SCRIPT_PYTHON:
45+
return QIcon(":/sextante/images/script.png")
46+
elif self.scriptType == self.SCRIPT_R:
47+
return QIcon(":/sextante/images/r.png")
3948

4049
def execute(self):
41-
dlg = EditScriptDialog(None)
50+
dlg = None
51+
if self.scriptType == self.SCRIPT_PYTHON:
52+
dlg = ScriptEditorDialog(ScriptEditorDialog.SCRIPT_PYTHON, None)
53+
if self.scriptType == self.SCRIPT_R:
54+
dlg = ScriptEditorDialog(ScriptEditorDialog.SCRIPT_R, None)
4255
dlg.exec_()
4356
if dlg.update:
4457
self.toolbox.updateTree()

python/plugins/processing/script/DeleteScriptAction.py renamed to python/plugins/processing/gui/DeleteScriptAction.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,37 @@
2323
# This will get replaced with a git SHA1 when you do a git archive
2424
__revision__ = '$Format:%H$'
2525

26-
from processing.script.ScriptAlgorithm import ScriptAlgorithm
27-
from processing.gui.ContextAction import ContextAction
2826
import os
29-
from PyQt4 import QtGui
27+
28+
from PyQt4.QtGui import *
29+
30+
from processing.gui.ContextAction import ContextAction
31+
32+
from processing.r.RAlgorithm import RAlgorithm
33+
from processing.script.ScriptAlgorithm import ScriptAlgorithm
3034

3135
class DeleteScriptAction(ContextAction):
3236

33-
def __init__(self):
34-
self.name="Delete script"
37+
SCRIPT_PYTHON = 0
38+
SCRIPT_R = 1
39+
40+
def __init__(self, scriptType):
41+
self.name = "Delete script"
42+
self.scriptType = scriptType
3543

3644
def isEnabled(self):
37-
return isinstance(self.alg, ScriptAlgorithm)
45+
if self.scriptType == self.SCRIPT_PYTHON:
46+
return isinstance(self.alg, ScriptAlgorithm)
47+
elif self.scriptType == self.SCRIPT_R:
48+
return isinstance(self.alg, RAlgorithm)
3849

3950
def execute(self, alg):
40-
reply = QtGui.QMessageBox.question(None, 'Confirmation',
41-
"Are you sure you want to delete this script?", QtGui.QMessageBox.Yes |
42-
QtGui.QMessageBox.No, QtGui.QMessageBox.No)
43-
if reply == QtGui.QMessageBox.Yes:
51+
reply = QMessageBox.question(None,
52+
"Confirmation",
53+
"Are you sure you want to delete this script?",
54+
QMessageBox.Yes | QMessageBox.No,
55+
QMessageBox.No
56+
)
57+
if reply == QMessageBox.Yes:
4458
os.remove(self.alg.descriptionFile)
45-
self.toolbox.updateTree()
59+
self.toolbox.updateTree()

python/plugins/processing/script/EditScriptAction.py renamed to python/plugins/processing/gui/EditScriptAction.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,30 @@
2323
# This will get replaced with a git SHA1 when you do a git archive
2424
__revision__ = '$Format:%H$'
2525

26-
from processing.script.ScriptAlgorithm import ScriptAlgorithm
2726
from processing.gui.ContextAction import ContextAction
28-
from processing.script.EditScriptDialog import EditScriptDialog
27+
from processing.gui.ScriptEditorDialog import ScriptEditorDialog
28+
29+
from processing.r.RAlgorithm import RAlgorithm
30+
from processing.script.ScriptAlgorithm import ScriptAlgorithm
2931

3032
class EditScriptAction(ContextAction):
3133

32-
def __init__(self):
33-
self.name="Edit script"
34+
SCRIPT_PYTHON = 0
35+
SCRIPT_R = 1
36+
37+
def __init__(self, scriptType):
38+
self.name = "Edit script"
39+
self.scriptType = scriptType
3440

3541
def isEnabled(self):
36-
return isinstance(self.alg, ScriptAlgorithm)
42+
if self.scriptType == ScriptEditorDialog.SCRIPT_PYTHON:
43+
return isinstance(self.alg, ScriptAlgorithm)
44+
elif self.scriptType == ScriptEditorDialog.SCRIPT_R:
45+
return isinstance(self.alg, RAlgorithm)
3746

3847
def execute(self):
39-
dlg = EditScriptDialog(self.alg)
48+
dlg = ScriptEditorDialog(self.scriptType, self.alg)
49+
dlg.show()
4050
dlg.exec_()
4151
if dlg.update:
42-
self.toolbox.updateTree()
52+
self.toolbox.updateTree()
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
***************************************************************************
5+
ScriptEdit.py
6+
---------------------
7+
Date : April 2013
8+
Copyright : (C) 2013 by Alexander Bruy
9+
Email : alexander dot bruy at gmail dot com
10+
***************************************************************************
11+
* *
12+
* This program is free software; you can redistribute it and/or modify *
13+
* it under the terms of the GNU General Public License as published by *
14+
* the Free Software Foundation; either version 2 of the License, or *
15+
* (at your option) any later version. *
16+
* *
17+
***************************************************************************
18+
"""
19+
20+
__author__ = 'Alexander Bruy'
21+
__date__ = 'April 2013'
22+
__copyright__ = '(C) 2013, Alexander Bruy'
23+
# This will get replaced with a git SHA1 when you do a git archive
24+
__revision__ = '$Format:%H$'
25+
26+
from PyQt4.QtCore import *
27+
from PyQt4.QtGui import *
28+
from PyQt4.Qsci import *
29+
30+
from qgis.core import *
31+
32+
class ScriptEdit(QsciScintilla):
33+
34+
LEXER_PYTHON = 0
35+
LEXER_R = 1
36+
37+
def __init__(self, parent=None):
38+
QsciScintilla.__init__(self, parent)
39+
40+
self.lexer = None
41+
self.api = None
42+
self.lexerType = -1
43+
44+
self.setCommonOptions()
45+
self.initShortcuts()
46+
47+
def setCommonOptions(self):
48+
# enable non-ASCII characters
49+
self.setUtf8(True)
50+
51+
# default font
52+
font = QFont()
53+
font.setFamily("Courier")
54+
font.setFixedPitch(True)
55+
font.setPointSize(10)
56+
self.setFont(font)
57+
self.setMarginsFont(font)
58+
59+
self.initLexer()
60+
61+
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
62+
63+
self.setWrapMode(QsciScintilla.WrapWord)
64+
self.setWrapVisualFlags(QsciScintilla.WrapFlagByText, QsciScintilla.WrapFlagNone, 4)
65+
66+
self.setSelectionForegroundColor(QColor("#2e3436"))
67+
self.setSelectionBackgroundColor(QColor("#babdb6"))
68+
69+
# show line numbers
70+
self.setMarginWidth(1, "000")
71+
self.setMarginLineNumbers(1, True)
72+
self.setMarginsForegroundColor(QColor("#2e3436"))
73+
self.setMarginsBackgroundColor(QColor("#babdb6"))
74+
75+
# highlight current line
76+
self.setCaretLineVisible(True)
77+
self.setCaretLineBackgroundColor(QColor("#d3d7cf"))
78+
79+
# folding
80+
self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
81+
self.setFoldMarginColors(QColor("#d3d7cf"), QColor("#d3d7cf"))
82+
83+
# mark column 80 with vertical line
84+
self.setEdgeMode(QsciScintilla.EdgeLine)
85+
self.setEdgeColumn(80)
86+
self.setEdgeColor(QColor("#eeeeec"))
87+
88+
# indentation
89+
self.setAutoIndent(True)
90+
self.setIndentationsUseTabs(False)
91+
self.setIndentationWidth(4)
92+
self.setTabIndents(True)
93+
self.setBackspaceUnindents(True)
94+
self.setTabWidth(4)
95+
96+
# autocomletion
97+
self.setAutoCompletionThreshold(2)
98+
self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
99+
100+
# load font from Python console settings
101+
settings = QSettings()
102+
fontName = settings.value("pythonConsole/fontfamilytext", "Monospace").toString()
103+
fontSize = settings.value("pythonConsole/fontsize", 10).toInt()[0]
104+
105+
self.defaultFont = QFont(fontName)
106+
self.defaultFont.setFixedPitch(True)
107+
self.defaultFont.setPointSize(fontSize)
108+
self.defaultFont.setStyleHint(QFont.TypeWriter)
109+
self.defaultFont.setStretch(QFont.SemiCondensed)
110+
self.defaultFont.setLetterSpacing(QFont.PercentageSpacing, 87.0)
111+
self.defaultFont.setBold(False)
112+
113+
self.boldFont = QFont(self.defaultFont)
114+
self.boldFont.setBold(True)
115+
116+
self.italicFont = QFont(self.defaultFont)
117+
self.italicFont.setItalic(True)
118+
119+
self.setFont(self.defaultFont)
120+
self.setMarginsFont(self.defaultFont)
121+
122+
def initShortcuts(self):
123+
ctrl, shift = self.SCMOD_CTRL << 16, self.SCMOD_SHIFT << 16
124+
125+
# disable some shortcuts
126+
self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord("D") + ctrl)
127+
self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord("L") + ctrl)
128+
self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord("L") + ctrl + shift)
129+
self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord("T") + ctrl)
130+
#self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord("Z") + ctrl)
131+
#self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord("Y") + ctrl)
132+
133+
# use Ctrl+Space for autocompletion
134+
self.shortcutAutocomplete = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Space), self)
135+
self.shortcutAutocomplete.setContext(Qt.WidgetShortcut)
136+
self.shortcutAutocomplete.activated.connect(self.autoComplete)
137+
138+
def autoComplete(self):
139+
self.autoCompleteFromAll()
140+
141+
def setLexerType(self, lexerType):
142+
self.lexerType = lexerType
143+
self.initLexer()
144+
145+
def initLexer(self):
146+
if self.lexerType == self.LEXER_PYTHON:
147+
self.lexer = QsciLexerPython()
148+
149+
colorDefault = QColor("#2e3436")
150+
colorComment = QColor("#c00")
151+
colorCommentBlock = QColor("#3465a4")
152+
colorNumber = QColor("#4e9a06")
153+
colorType = QColor("#4e9a06")
154+
colorKeyword = QColor("#204a87")
155+
colorString = QColor("#ce5c00")
156+
157+
self.lexer.setDefaultFont(self.defaultFont)
158+
self.lexer.setDefaultColor(colorDefault)
159+
160+
self.lexer.setColor(colorComment, 1)
161+
self.lexer.setColor(colorNumber, 2)
162+
self.lexer.setColor(colorString, 3)
163+
self.lexer.setColor(colorString, 4)
164+
self.lexer.setColor(colorKeyword, 5)
165+
self.lexer.setColor(colorString, 6)
166+
self.lexer.setColor(colorString, 7)
167+
self.lexer.setColor(colorType, 8)
168+
self.lexer.setColor(colorCommentBlock, 12)
169+
self.lexer.setColor(colorString, 15)
170+
171+
self.lexer.setFont(self.italicFont, 1)
172+
self.lexer.setFont(self.boldFont, 5)
173+
self.lexer.setFont(self.boldFont, 8)
174+
self.lexer.setFont(self.italicFont, 12)
175+
176+
self.api = QsciAPIs(self.lexer)
177+
178+
settings = QSettings()
179+
useDefaultAPI = settings.value("pythonConsole/preloadAPI", True).toBool()
180+
if useDefaultAPI:
181+
# load QGIS API shipped with Python console
182+
self.api.loadPrepared(QgsApplication.pkgDataPath() + "/python/qsci_apis/pyqgis_master.pap")
183+
else:
184+
# load user-defined API files
185+
apiPaths = settings.value("pythonConsole/userAPI").toStringList()
186+
for path in apiPaths:
187+
self.api.load(path)
188+
self.api.prepare()
189+
self.lexer.setAPIs(self.api)
190+
elif self.lexerType == self.LEXER_R:
191+
# R lexer
192+
pass
193+
194+
self.setLexer(self.lexer)

0 commit comments

Comments
 (0)