Skip to content

Commit ff1431c

Browse files
committedOct 27, 2012
Merge pull request #303 from slarosa/master
[FEATURE] - Split-pane entry and output for PyQGIS console
2 parents 34f677a + 9fb1c11 commit ff1431c

File tree

4 files changed

+259
-71
lines changed

4 files changed

+259
-71
lines changed
 

‎python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ SET(PY_FILES
147147
console_sci.py
148148
console_help.py
149149
console_settings.py
150+
console_output.py
150151
utils.py
151152
)
152153
FILE(GLOB UI_FILES *.ui)

‎python/console.py

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from PyQt4.QtGui import *
2424
from qgis.utils import iface
2525
from console_sci import PythonEdit
26+
from console_output import EditorOutput
2627
from console_help import HelpDialog
2728
from console_settings import optionsDialog
2829

@@ -58,20 +59,6 @@ def console_displayhook(obj):
5859
global _console_output
5960
_console_output = obj
6061

61-
class QgisOutputCatcher:
62-
def __init__(self):
63-
self.data = ''
64-
def write(self, stuff):
65-
self.data += stuff
66-
def get_and_clean_data(self):
67-
tmp = self.data
68-
self.data = ''
69-
return tmp
70-
def flush(self):
71-
pass
72-
73-
sys.stdout = QgisOutputCatcher()
74-
7562
class PythonConsole(QDockWidget):
7663
def __init__(self, parent=None):
7764
QDockWidget.__init__(self, parent)
@@ -92,35 +79,32 @@ def activate(self):
9279
self.raise_()
9380
QDockWidget.setFocus(self)
9481

95-
9682
class PythonConsoleWidget(QWidget):
9783
def __init__(self, parent=None):
9884
QWidget.__init__(self, parent)
99-
self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
100-
85+
self.setWindowTitle(QCoreApplication.translate("PythonConsole", "Python Console"))
10186
self.widgetButton = QWidget()
87+
#self.widgetEditors = QWidget()
88+
10289
self.options = optionsDialog(self)
10390

10491
self.toolBar = QToolBar()
10592
self.toolBar.setEnabled(True)
106-
#self.toolBar.setFont(font)
10793
self.toolBar.setFocusPolicy(Qt.NoFocus)
10894
self.toolBar.setContextMenuPolicy(Qt.DefaultContextMenu)
10995
self.toolBar.setLayoutDirection(Qt.LeftToRight)
11096
self.toolBar.setIconSize(QSize(24, 24))
11197
self.toolBar.setOrientation(Qt.Vertical)
11298
self.toolBar.setMovable(True)
11399
self.toolBar.setFloatable(True)
114-
#self.toolBar.setAllowedAreas(Qt.LeftToolBarArea)
115-
#self.toolBar.setAllowedAreas(Qt.RightToolBarArea)
116-
#self.toolBar.setObjectName(_fromUtf8("toolMappa"))
117100

118-
self.b = QVBoxLayout(self.widgetButton)
119-
self.e = QHBoxLayout(self)
101+
self.b = QGridLayout(self.widgetButton)
102+
self.f = QGridLayout(self)
120103

121-
self.e.setMargin(0)
122-
self.e.setSpacing(0)
104+
self.f.setMargin(0)
105+
self.f.setSpacing(0)
123106
self.b.setMargin(0)
107+
self.b.setSpacing(0)
124108

125109
## Action for Clear button
126110
clearBt = QCoreApplication.translate("PythonConsole", "Clear console")
@@ -268,12 +252,37 @@ def __init__(self, parent=None):
268252

269253
self.b.addWidget(self.toolBar)
270254
self.edit = PythonEdit()
271-
self.setFocusProxy( self.edit )
272-
273-
self.e.addWidget(self.widgetButton)
274-
self.e.addWidget(self.edit)
255+
self.textEditOut = EditorOutput()
256+
257+
self.setFocusProxy(self.edit)
258+
259+
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
260+
sizePolicy.setHorizontalStretch(0)
261+
sizePolicy.setVerticalStretch(0)
262+
sizePolicy.setHeightForWidth(self.widgetButton.sizePolicy().hasHeightForWidth())
263+
self.widgetButton.setSizePolicy(sizePolicy)
264+
265+
self.consoleFrame = QFrame(self)
266+
self.consoleFrame.setObjectName("consoleFrame")
267+
self.consoleLayout = QVBoxLayout(self.consoleFrame)
268+
self.consoleLayout.setSpacing(0)
269+
self.consoleLayout.setMargin(0)
270+
271+
sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
272+
sizePolicy.setHorizontalStretch(0)
273+
sizePolicy.setVerticalStretch(0)
274+
sizePolicy.setHeightForWidth(self.textEditOut.sizePolicy().hasHeightForWidth())
275+
self.textEditOut.setSizePolicy(sizePolicy)
276+
self.consoleLayout.addWidget(self.textEditOut)
277+
278+
self.edit.setMinimumSize(QSize(0, 32))
279+
self.edit.setMaximumSize(QSize(16777215, 32))
280+
self.consoleLayout.addWidget(self.edit)
281+
282+
self.f.addWidget(self.widgetButton, 0, 0)
283+
self.f.addWidget(self.consoleFrame, 0, 1)
275284

276-
self.clearButton.triggered.connect(self.edit.clearConsole)
285+
self.clearButton.triggered.connect(self.textEditOut.clearConsole)
277286
self.optionsButton.triggered.connect(self.openSettings)
278287
self.loadIfaceButton.triggered.connect(self.iface)
279288
self.loadSextanteButton.triggered.connect(self.sextante)
@@ -326,7 +335,7 @@ def saveScriptFile(self):
326335
if not filename.endswith(".py"):
327336
fName += ".py"
328337
sF = open(fName,'w')
329-
listText = self.edit.getTextFromEditor()
338+
listText = self.textEditOut.getTextFromEditor()
330339
is_first_line = True
331340
for s in listText:
332341
if s[0:3] in (">>>", "..."):
@@ -337,7 +346,7 @@ def saveScriptFile(self):
337346
sF.write('\n')
338347
sF.write(s)
339348
sF.close()
340-
349+
341350
def openHelp(self):
342351
dlg = HelpDialog()
343352
dlg.exec_()
@@ -348,6 +357,7 @@ def openSettings(self):
348357

349358
def prefChanged(self):
350359
self.edit.refreshLexerProperties()
360+
self.textEditOut.refreshLexerProperties()
351361

352362
def closeEvent(self, event):
353363
self.edit.writeHistoryFile()

‎python/console_output.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# -*- coding:utf-8 -*-
2+
"""
3+
/***************************************************************************
4+
Python Conosle for QGIS
5+
-------------------
6+
begin : 2012-09-10
7+
copyright : (C) 2012 by Salvatore Larosa
8+
email : lrssvtml (at) gmail (dot) com
9+
***************************************************************************/
10+
11+
/***************************************************************************
12+
* *
13+
* This program is free software; you can redistribute it and/or modify *
14+
* it under the terms of the GNU General Public License as published by *
15+
* the Free Software Foundation; either version 2 of the License, or *
16+
* (at your option) any later version. *
17+
* *
18+
***************************************************************************/
19+
Some portions of code were taken from https://code.google.com/p/pydee/
20+
"""
21+
22+
from PyQt4.QtCore import *
23+
from PyQt4.QtGui import *
24+
from PyQt4.Qsci import (QsciScintilla,
25+
QsciScintillaBase,
26+
QsciLexerPython)
27+
from console_sci import PythonEdit
28+
import sys
29+
30+
class writeOut:
31+
def __init__(self, edit, out=None, style=None):
32+
"""
33+
This class allow to write stdout and stderr
34+
"""
35+
self.editor = edit
36+
self.out = None
37+
self.style = style
38+
39+
def write(self, m):
40+
if self.style == "traceback":
41+
self.editor.SendScintilla(QsciScintilla.SCI_SETSTYLING, len(m), 1)
42+
self.editor.append(m)
43+
self.editor.SendScintilla(QsciScintilla.SCI_SETSTYLING, len(m), 1)
44+
else:
45+
self.editor.append(m)
46+
self.move_cursor_to_end()
47+
48+
if self.out:
49+
self.out.write(m)
50+
51+
def move_cursor_to_end(self):
52+
"""Move cursor to end of text"""
53+
line, index = self.get_end_pos()
54+
self.editor.setCursorPosition(line, index)
55+
self.editor.ensureCursorVisible()
56+
self.editor.ensureLineVisible(line)
57+
58+
def get_end_pos(self):
59+
"""Return (line, index) position of the last character"""
60+
line = self.editor.lines() - 1
61+
return (line, self.editor.text(line).length())
62+
63+
def flush(self):
64+
pass
65+
66+
class EditorOutput(QsciScintilla):
67+
def __init__(self, parent=None):
68+
#QsciScintilla.__init__(self, parent)
69+
super(EditorOutput,self).__init__(parent)
70+
# Enable non-ascii chars for editor
71+
self.setUtf8(True)
72+
73+
sys.stdout = writeOut(self, sys.stdout)
74+
sys.stderr = writeOut(self, sys.stderr, "traceback")
75+
76+
self.edit = PythonEdit()
77+
self.setLexers()
78+
self.setReadOnly(True)
79+
80+
# Set the default font
81+
font = QFont()
82+
font.setFamily('Courier')
83+
font.setFixedPitch(True)
84+
font.setPointSize(10)
85+
self.setFont(font)
86+
self.setMarginsFont(font)
87+
# Margin 0 is used for line numbers
88+
#fm = QFontMetrics(font)
89+
self.setMarginsFont(font)
90+
self.setMarginWidth(1, "00000")
91+
self.setMarginLineNumbers(1, True)
92+
self.setMarginsForegroundColor(QColor("#3E3EE3"))
93+
self.setMarginsBackgroundColor(QColor("#f9f9f9"))
94+
self.setCaretLineVisible(True)
95+
self.setCaretLineBackgroundColor(QColor("#fcf3ed"))
96+
97+
98+
# Folding
99+
#self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
100+
#self.setFoldMarginColors(QColor("#99CC66"),QColor("#333300"))
101+
#self.setWrapMode(QsciScintilla.WrapCharacter)
102+
103+
## Edge Mode : does not seems to work
104+
#self.setEdgeMode(QsciScintilla.EdgeLine)
105+
#self.setEdgeColumn(80)
106+
#self.setEdgeColor(QColor("#FF0000"))
107+
108+
self.SendScintilla(QsciScintilla.SCI_SETWRAPMODE, 2)
109+
self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
110+
111+
def refreshLexerProperties(self):
112+
self.setLexers()
113+
114+
def setLexers(self):
115+
self.lexer = QsciLexerPython()
116+
117+
settings = QSettings()
118+
loadFont = settings.value("pythonConsole/fontfamilytext").toString()
119+
fontSize = settings.value("pythonConsole/fontsize").toInt()[0]
120+
font = QFont(loadFont)
121+
font.setFixedPitch(True)
122+
font.setPointSize(fontSize)
123+
124+
self.lexer.setDefaultFont(font)
125+
self.lexer.setColor(Qt.red, 1)
126+
self.lexer.setColor(Qt.darkGreen, 5)
127+
self.lexer.setColor(Qt.darkBlue, 15)
128+
self.lexer.setFont(font, 1)
129+
self.lexer.setFont(font, 2)
130+
self.lexer.setFont(font, 3)
131+
self.lexer.setFont(font, 4)
132+
133+
self.setLexer(self.lexer)
134+
135+
def getTextFromEditor(self):
136+
text = self.text()
137+
textList = text.split("\n")
138+
return textList
139+
140+
def clearConsole(self):
141+
#self.SendScintilla(QsciScintilla.SCI_CLEARALL)
142+
self.setText('')
143+
144+
def contextMenuEvent(self, e):
145+
menu = QMenu(self)
146+
runAction = menu.addAction("Enter Selected")
147+
copyAction = menu.addAction("Copy CTRL+C")
148+
runAction.setEnabled(False)
149+
if self.hasSelectedText():
150+
runAction.setEnabled(True)
151+
action = menu.exec_(self.mapToGlobal(e.pos()))
152+
if action == runAction:
153+
cmd = self.selectedText()
154+
self.edit.insertFromDropPaste(cmd)
155+
self.edit.entered()
156+
if action == copyAction:
157+
self.copy()
158+
159+
def copy(self):
160+
"""Copy text to clipboard... or keyboard interrupt"""
161+
if self.hasSelectedText():
162+
text = unicode(self.selectedText())
163+
text = text.replace('>>> ', '').replace('... ', '').strip() # removing prompts
164+
QApplication.clipboard().setText(text)
165+
else:
166+
self.emit(SIGNAL("keyboard_interrupt()"))
167+

‎python/console_sci.py

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(self, parent=None):
5151

5252
self.buffer = []
5353

54-
self.insertInitText()
54+
#self.insertInitText()
5555
self.displayPrompt(False)
5656

5757
for line in _init_commands:
@@ -69,8 +69,8 @@ def __init__(self, parent=None):
6969
#self.selectToMatchingBrace()
7070

7171
# Current line visible with special background color
72-
self.setCaretLineVisible(True)
73-
self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
72+
#self.setCaretLineVisible(True)
73+
#self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
7474
self.setCaretWidth(2)
7575

7676
# Set Python lexer
@@ -93,12 +93,14 @@ def __init__(self, parent=None):
9393
# Use raw message to Scintilla here (all messages are documented
9494
# here: http://www.scintilla.org/ScintillaDoc.html)
9595
self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0)
96+
self.SendScintilla(QsciScintilla.SCI_SETVSCROLLBAR, 0)
97+
9698

9799
# not too small
98100
#self.setMinimumSize(500, 300)
99-
self.setMinimumHeight(125)
101+
self.setMinimumHeight(32)
100102

101-
self.SendScintilla(QsciScintilla.SCI_SETWRAPMODE, 1)
103+
self.SendScintilla(QsciScintilla.SCI_SETWRAPMODE, 2)
102104
self.SendScintilla(QsciScintilla.SCI_EMPTYUNDOBUFFER)
103105

104106
## Disable command key
@@ -117,20 +119,14 @@ def __init__(self, parent=None):
117119
self.newShortcutCAS.activated.connect(self.showHistory)
118120
self.connect(self, SIGNAL('userListActivated(int, const QString)'),
119121
self.completion_list_selected)
122+
123+
self.createStandardContextMenu()
120124

121125
def showHistory(self):
122126
self.showUserList(1, QStringList(self.history))
123127

124128
def autoComplete(self):
125129
self.autoCompleteFromAll()
126-
127-
def clearConsole(self):
128-
"""Clear the contents of the console."""
129-
self.SendScintilla(QsciScintilla.SCI_CLEARALL)
130-
#self.setText('')
131-
self.insertInitText()
132-
self.displayPrompt(False)
133-
self.setFocus()
134130

135131
def commandConsole(self, command):
136132
if not self.is_cursor_on_last_line():
@@ -142,23 +138,20 @@ def commandConsole(self, command):
142138
if command == "iface":
143139
"""Import QgisInterface class"""
144140
self.append('from qgis.utils import iface')
145-
self.move_cursor_to_end()
146141
elif command == "sextante":
147142
"""Import Sextante class"""
148143
self.append('from sextante.core.Sextante import Sextante')
149-
self.move_cursor_to_end()
150144
elif command == "cLayer":
151145
"""Retrieve current Layer from map camvas"""
152146
self.append('cLayer = iface.mapCanvas().currentLayer()')
153-
self.move_cursor_to_end()
154147
elif command == "qtCore":
155148
"""Import QtCore class"""
156149
self.append('from PyQt4.QtCore import *')
157-
self.move_cursor_to_end()
158150
elif command == "qtGui":
159151
"""Import QtGui class"""
160152
self.append('from PyQt4.QtGui import *')
161-
self.move_cursor_to_end()
153+
self.entered()
154+
self.move_cursor_to_end()
162155
self.setFocus()
163156

164157
def setLexers(self):
@@ -172,6 +165,10 @@ def setLexers(self):
172165
font = QFont(loadFont)
173166
font.setFixedPitch(True)
174167
font.setPointSize(fontSize)
168+
font.setStyleHint(QFont.TypeWriter)
169+
font.setStretch(QFont.SemiCondensed)
170+
font.setLetterSpacing(QFont.PercentageSpacing, 87.0)
171+
font.setBold(False)
175172

176173
self.lexer.setDefaultFont(font)
177174
self.lexer.setColor(Qt.red, 1)
@@ -209,9 +206,9 @@ def completion_list_selected(self, id, txt):
209206

210207
def insertInitText(self):
211208
#self.setLexers(False)
212-
txtInit = QCoreApplication.translate("PythonConsole",
213-
"## To access Quantum GIS environment from this console\n"
214-
"## use qgis.utils.iface object (instance of QgisInterface class). Read help for more info.\n\n")
209+
txtInit = QCoreApplication.translate("PythonConsole", "## Interactive Python Console for Quantum GIS\n\n")
210+
#"## To access Quantum GIS environment from this console\n"
211+
#"## use qgis.utils.iface object (instance of QgisInterface class). Read help for more info.\n\n")
215212
initText = self.setText(txtInit)
216213

217214
def getText(self):
@@ -413,6 +410,16 @@ def keyPressEvent(self, e):
413410
## TODO: press event for auto-completion file directory
414411
else:
415412
QsciScintilla.keyPressEvent(self, e)
413+
414+
def contextMenuEvent(self, e):
415+
menu = QMenu(self)
416+
copyAction = menu.addAction("Copy CTRL+C")
417+
pasteAction = menu.addAction("Paste CTRL+V")
418+
action = menu.exec_(self.mapToGlobal(e.pos()))
419+
if action == copyAction:
420+
self.copy()
421+
elif action == pasteAction:
422+
self.paste()
416423

417424
def mousePressEvent(self, e):
418425
"""
@@ -458,18 +465,20 @@ def dropEvent(self, e):
458465
def insertFromDropPaste(self, textDP):
459466
pasteList = textDP.split("\n")
460467
for line in pasteList[:-1]:
468+
line.replace(">>> ", "").replace("... ", "")
461469
self.insert(line)
462470
self.move_cursor_to_end()
463-
#self.SendScintilla(QsciScintilla.SCI_DELETEBACK)
464471
self.runCommand(unicode(self.currentCommand()))
465472
if pasteList[-1] != "":
466-
self.insert(unicode(pasteList[-1]))
473+
line = pasteList[-1]
474+
line.replace(">>> ", "").replace("... ", "")
475+
self.insert(unicode(line))
467476
self.move_cursor_to_end()
468477

469-
def getTextFromEditor(self):
470-
text = self.text()
471-
textList = text.split("\n")
472-
return textList
478+
# def getTextFromEditor(self):
479+
# text = self.text()
480+
# textList = text.split("\n")
481+
# return textList
473482

474483
def insertTextFromFile(self, listOpenFile):
475484
for line in listOpenFile[:-1]:
@@ -483,7 +492,7 @@ def insertTextFromFile(self, listOpenFile):
483492

484493
def entered(self):
485494
self.move_cursor_to_end()
486-
self.runCommand( unicode(self.currentCommand()) )
495+
self.runCommand( unicode(self.currentCommand()) )
487496
self.setFocus()
488497
self.move_cursor_to_end()
489498
#self.SendScintilla(QsciScintilla.SCI_EMPTYUNDOBUFFER)
@@ -498,9 +507,14 @@ def currentCommand(self):
498507
return cmd
499508

500509
def runCommand(self, cmd):
510+
self.write_stdout(cmd)
501511
import webbrowser
502512
self.updateHistory(cmd)
503-
self.SendScintilla(QsciScintilla.SCI_NEWLINE)
513+
line, pos = self.getCursorPosition()
514+
selCmdLenght = self.text(line).length()
515+
self.setSelection(line, 0, line, selCmdLenght)
516+
self.removeSelectedText()
517+
#self.SendScintilla(QsciScintilla.SCI_NEWLINE)
504518
if cmd in ('_save', '_clear', '_clearAll', '_pyqgis', '_api'):
505519
if cmd == '_save':
506520
self.writeHistoryFile()
@@ -528,25 +542,21 @@ def runCommand(self, cmd):
528542
elif cmd == '_api':
529543
webbrowser.open( "http://www.qgis.org/api/" )
530544

531-
output = sys.stdout.get_and_clean_data()
532-
if output:
533-
self.append(output)
534545
self.displayPrompt(False)
535546
else:
536547
self.buffer.append(cmd)
537548
src = u"\n".join(self.buffer)
538549
more = self.runsource(src, "<input>")
539550
if not more:
540551
self.buffer = []
541-
output = sys.stdout.get_and_clean_data()
542-
if output:
543-
self.append(output)
544552
self.move_cursor_to_end()
545553
self.displayPrompt(more)
546554

547555
def write(self, txt):
548-
self.SendScintilla(QsciScintilla.SCI_SETSTYLING, len(txt), 1)
549-
self.append(txt)
550-
self.SendScintilla(QsciScintilla.SCI_SETSTYLING, len(txt), 1)
551-
556+
sys.stderr.write(txt)
552557

558+
def write_stdout(self, txt):
559+
if len(txt) > 0:
560+
getCmdString = self.text()
561+
prompt = getCmdString[0:4]
562+
sys.stdout.write(prompt+txt+'\n')

0 commit comments

Comments
 (0)
Please sign in to comment.