Skip to content

Commit e4d0a3f

Browse files
author
wonder
committedJan 10, 2010
[FEATURE] new python console implementation.
Simpler, easier and better :-) git-svn-id: http://svn.osgeo.org/qgis/trunk@12727 c8812cc2-4d05-0410-92ff-de0c093fc19c
1 parent fadd73f commit e4d0a3f

File tree

3 files changed

+236
-256
lines changed

3 files changed

+236
-256
lines changed
 

‎python/CMakeLists.txt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,7 @@ ELSE (BINDINGS_GLOBAL_INSTALL)
9999
ENDIF (BINDINGS_GLOBAL_INSTALL)
100100

101101

102-
# python console
103-
PYQT4_WRAP_UI(PYUI_FILE ${CMAKE_SOURCE_DIR}/src/ui/qgspythondialog.ui)
104-
105-
ADD_CUSTOM_TARGET(pythonconsole ALL DEPENDS ${PYUI_FILE})
106-
107102

108103
# Step 4: install built libs to python's site packages
109-
INSTALL(FILES __init__.py utils.py console.py ${PYUI_FILE} ${CMAKE_CURRENT_BINARY_DIR}/qgisconfig.py ${BINDINGS_LIBS} DESTINATION ${SITE_PKG_PATH}/qgis)
104+
INSTALL(FILES __init__.py utils.py console.py ${CMAKE_CURRENT_BINARY_DIR}/qgisconfig.py ${BINDINGS_LIBS} DESTINATION ${SITE_PKG_PATH}/qgis)
110105

‎python/console.py

Lines changed: 235 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,59 @@
11
# -*- coding: utf-8 -*-
22

3+
# This program is free software; you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation; either version 2 of the License, or
6+
# (at your option) any later version.
7+
8+
# Some portions of code were taken from managerR plugin.
9+
10+
"""
11+
Implementation of interactive Python console widget for QGIS.
12+
13+
Has +- the same behaviour as command-line interactive console:
14+
- runs commands one by one
15+
- supports expressions that span through more lines
16+
- has command history, accessible using up/down keys
17+
- supports pasting of commands
18+
19+
TODO:
20+
- configuration - init commands, font, ...
21+
- syntax highlighting
22+
23+
"""
24+
325
from PyQt4.QtCore import *
426
from PyQt4.QtGui import *
5-
6-
import traceback
727
import sys
28+
import traceback
29+
import code
830

9-
from ui_qgspythondialog import Ui_QgsPythonDialog
1031

11-
_console = None
32+
_init_commands = ["from qgis.core import *", "import qgis.utils"]
33+
1234

35+
_console = None
1336

1437
def show_console():
1538
""" called from QGIS to open the console """
1639
global _console
1740
if _console is None:
18-
_console = QgsPythonConsole()
41+
_console = PythonConsole()
1942
_console.show()
2043
_console.raise_()
2144
_console.setWindowState( _console.windowState() & ~Qt.WindowMinimized )
2245
_console.activateWindow()
23-
46+
2447

2548

2649
_old_stdout = sys.stdout
50+
_console_output = None
51+
52+
# hook for python console so all output will be redirected
53+
# and then shown in console
54+
def console_displayhook(obj):
55+
global _console_output
56+
_console_output = obj
2757

2858
class QgisOutputCatcher:
2959
def __init__(self):
@@ -36,145 +66,218 @@ def get_and_clean_data(self):
3666
return tmp
3767
def flush(self):
3868
pass
39-
40-
def installConsoleHook():
41-
sys.stdout = QgisOutputCatcher()
42-
43-
def uninstallConsoleHook():
44-
sys.stdout = _old_stdout
45-
46-
47-
48-
class ConsoleHistory(object):
49-
def __init__(self):
50-
self.cmds = []
51-
self.pos = 0
52-
self.active_cmd = u"" # active, not yet entered command
53-
54-
def add_cmd(self, command):
55-
if len(command) != 0:
56-
self.cmds.append(command)
57-
self.pos = len(self.cmds)
58-
self.active_cmd = u""
59-
60-
def get_previous_cmd(self, current):
61-
if self.pos == 0:
62-
return None
63-
64-
if self.pos == len(self.cmds):
65-
self.active_cmd = current
66-
else:
67-
self.cmds[self.pos] = current
6869

69-
self.pos -= 1
70-
return self.cmds[self.pos]
70+
sys.stdout = QgisOutputCatcher()
7171

72-
def get_next_cmd(self, current):
7372

74-
# if we're at active (last) command, don't move
75-
if self.pos == len(self.cmds):
76-
return None
77-
78-
self.cmds[self.pos] = current
79-
self.pos += 1
80-
81-
if self.pos == len(self.cmds):
82-
return self.active_cmd
83-
else:
84-
return self.cmds[self.pos]
85-
86-
87-
88-
class QgsPythonConsole(QWidget, Ui_QgsPythonDialog):
73+
class PythonConsole(QWidget):
8974
def __init__(self, parent=None):
9075
QWidget.__init__(self, parent)
9176

92-
self.setupUi(self)
93-
94-
# minimize button was not enabled on mac
95-
self.setWindowFlags( self.windowFlags() | Qt.WindowMinimizeButtonHint )
96-
97-
self.history = ConsoleHistory()
98-
99-
self.console_globals = {}
100-
101-
def escapeHtml(self, text):
102-
return text.replace("<", "&lt;").replace(">", "&gt;")
103-
104-
@pyqtSlot()
105-
def on_pbnPrev_clicked(self):
106-
cmd = self.history.get_previous_cmd( self.getCommand() )
107-
if cmd is not None:
108-
self.edtCmdLine.setText(cmd)
109-
110-
@pyqtSlot()
111-
def on_pbnNext_clicked(self):
112-
cmd = self.history.get_next_cmd( self.getCommand() )
113-
if cmd is not None:
114-
self.edtCmdLine.setText(cmd)
115-
116-
def getCommand(self):
117-
return unicode(self.edtCmdLine.toPlainText())
118-
119-
def execute(self, single):
120-
command = self.getCommand()
77+
self.edit = PythonEdit()
78+
self.l = QVBoxLayout()
79+
self.l.addWidget(self.edit)
80+
self.setLayout(self.l)
81+
self.setWindowTitle("Python console")
12182

122-
self.history.add_cmd(command)
83+
s = QSettings()
84+
self.restoreGeometry(s.value("/python/console/geometry").toByteArray())
12385

124-
try:
125-
# run command
126-
code = compile(command, '<console>', 'single' if single else 'exec')
127-
res = eval(code, self.console_globals)
128-
result = unicode( res ) if res is not None else u''
129-
130-
# get standard output
131-
output = sys.stdout.get_and_clean_data()
132-
133-
#_old_stdout.write("cmd: '"+command+"'\n")
134-
#_old_stdout.write("res: '"+result+"'\n")
135-
#_old_stdout.write("out: '"+output+"'\n")
136-
#_old_stdout.flush()
137-
138-
# escape the result so python objects display properly and
139-
# we can still use html output to get nicely formatted display
140-
output = self.escapeHtml( output ) + self.escapeHtml( result )
141-
if len(output) != 0:
142-
output += "<br>"
143-
144-
except Exception, e:
145-
#lst = traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)
146-
lst = traceback.format_exception_only(sys.exc_type, sys.exc_value)
147-
errorText = "<br>".join(map(self.escapeHtml, lst))
148-
output = "<font color=\"red\">" + errorText + "</font><br>"
149-
150-
s = "<b><font color=\"green\">&gt;&gt;&gt;</font> " + self.escapeHtml( command ) + "</b><br>" + output
151-
self.edtCmdLine.setText("")
152-
153-
self.txtHistory.moveCursor(QTextCursor.End)
154-
self.txtHistory.insertHtml(s)
155-
self.txtHistory.moveCursor(QTextCursor.End)
156-
self.txtHistory.ensureCursorVisible()
157-
158-
@pyqtSlot()
159-
def on_pbnExecute_clicked(self):
160-
self.execute(False)
161-
162-
@pyqtSlot()
163-
def on_pbnEval_clicked(self):
164-
self.execute(True)
165-
166-
def showEvent(self, event):
167-
QWidget.showEvent(self, event)
168-
installConsoleHook()
86+
def sizeHint(self):
87+
return QSize(500,300)
16988

17089
def closeEvent(self, event):
171-
uninstallConsoleHook()
90+
s = QSettings()
91+
s.setValue("/python/console/geometry", QVariant(self.saveGeometry()))
17292
QWidget.closeEvent(self, event)
17393

17494

95+
class PythonEdit(QTextEdit, code.InteractiveInterpreter):
96+
97+
def __init__(self,parent=None):
98+
QTextEdit.__init__(self, parent)
99+
code.InteractiveInterpreter.__init__(self, locals=None)
100+
101+
self.setTextInteractionFlags(Qt.TextEditorInteraction)
102+
self.setAcceptDrops(False)
103+
self.setMinimumSize(30, 30)
104+
self.setUndoRedoEnabled(False)
105+
self.setAcceptRichText(False)
106+
#monofont = QFont("Bitstream Vera Sans Mono", 10)
107+
#self.setFont(monofont)
108+
109+
self.buffer = []
110+
111+
self.insertPlainText("To access Quantum GIS environment from this console\n"
112+
"use qgis.utils.iface object (instance of QgisInterface class).\n"
113+
"\n")
114+
115+
for line in _init_commands:
116+
self.runsource(line)
117+
118+
self.displayPrompt(False)
119+
120+
self.history = QStringList()
121+
self.historyIndex = 0
122+
123+
#from pythonhigh import PythonHighlighter
124+
#self.high = PythonHighlighter(self)
125+
126+
def displayPrompt(self, more=False):
127+
self.currentPrompt = "... " if more else ">>> "
128+
self.currentPromptLength = len(self.currentPrompt)
129+
self.insertPlainText(self.currentPrompt)
130+
self.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)
131+
132+
def isCursorInEditionZone(self):
133+
cursor = self.textCursor()
134+
pos = cursor.position()
135+
block = self.document().lastBlock()
136+
last = block.position() + self.currentPromptLength
137+
return pos >= last
138+
139+
def currentCommand(self):
140+
block = self.cursor.block()
141+
text = block.text()
142+
return text.right(text.length()-self.currentPromptLength)
143+
144+
def showPrevious(self):
145+
if self.historyIndex < len(self.history) and not self.history.isEmpty():
146+
self.cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
147+
self.cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor)
148+
self.cursor.removeSelectedText()
149+
self.cursor.insertText(self.currentPrompt)
150+
self.historyIndex += 1
151+
if self.historyIndex == len(self.history):
152+
self.insertPlainText("")
153+
else:
154+
self.insertPlainText(self.history[self.historyIndex])
155+
156+
def showNext(self):
157+
if self.historyIndex > 0 and not self.history.isEmpty():
158+
self.cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
159+
self.cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor)
160+
self.cursor.removeSelectedText()
161+
self.cursor.insertText(self.currentPrompt)
162+
self.historyIndex -= 1
163+
if self.historyIndex == len(self.history):
164+
self.insertPlainText("")
165+
else:
166+
self.insertPlainText(self.history[self.historyIndex])
167+
168+
def updateHistory(self, command):
169+
if isinstance(command, QStringList):
170+
for line in command:
171+
self.history.append(line)
172+
elif not command == "":
173+
if len(self.history) <= 0 or \
174+
not command == self.history[-1]:
175+
self.history.append(command)
176+
self.historyIndex = len(self.history)
177+
178+
def keyPressEvent(self, e):
179+
self.cursor = self.textCursor()
180+
# if the cursor isn't in the edition zone, don't do anything except Ctrl+C
181+
if not self.isCursorInEditionZone():
182+
if e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier:
183+
if e.key() == Qt.Key_C or e.key() == Qt.Key_A:
184+
QTextEdit.keyPressEvent(self, e)
185+
else:
186+
# all other keystrokes get sent to the input line
187+
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
188+
else:
189+
# if Return is pressed, then perform the commands
190+
if e.key() == Qt.Key_Return:
191+
self.entered()
192+
# if Up or Down is pressed
193+
elif e.key() == Qt.Key_Down:
194+
self.showPrevious()
195+
elif e.key() == Qt.Key_Up:
196+
self.showNext()
197+
# if backspace is pressed, delete until we get to the prompt
198+
elif e.key() == Qt.Key_Backspace:
199+
if not self.cursor.hasSelection() and self.cursor.columnNumber() == self.currentPromptLength:
200+
return
201+
QTextEdit.keyPressEvent(self, e)
202+
# if the left key is pressed, move left until we get to the prompt
203+
elif e.key() == Qt.Key_Left and self.cursor.position() > self.document().lastBlock().position() + self.currentPromptLength:
204+
if e.modifiers() == Qt.ShiftModifier:
205+
anchor = QTextCursor.KeepAnchor
206+
else:
207+
anchor = QTextCursor.MoveAnchor
208+
if (e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier):
209+
self.cursor.movePosition(QTextCursor.WordLeft, anchor)
210+
else:
211+
self.cursor.movePosition(QTextCursor.Left, anchor)
212+
# use normal operation for right key
213+
elif e.key() == Qt.Key_Right:
214+
if e.modifiers() == Qt.ShiftModifier:
215+
anchor = QTextCursor.KeepAnchor
216+
else:
217+
anchor = QTextCursor.MoveAnchor
218+
if (e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier):
219+
self.cursor.movePosition(QTextCursor.WordRight, anchor)
220+
else:
221+
self.cursor.movePosition(QTextCursor.Right, anchor)
222+
# if home is pressed, move cursor to right of prompt
223+
elif e.key() == Qt.Key_Home:
224+
if e.modifiers() == Qt.ShiftModifier:
225+
anchor = QTextCursor.KeepAnchor
226+
else:
227+
anchor = QTextCursor.MoveAnchor
228+
self.cursor.movePosition(QTextCursor.StartOfBlock, anchor, 1)
229+
self.cursor.movePosition(QTextCursor.Right, anchor, self.currentPromptLength)
230+
# use normal operation for end key
231+
elif e.key() == Qt.Key_End:
232+
if e.modifiers() == Qt.ShiftModifier:
233+
anchor = QTextCursor.KeepAnchor
234+
else:
235+
anchor = QTextCursor.MoveAnchor
236+
self.cursor.movePosition(QTextCursor.EndOfBlock, anchor, 1)
237+
# use normal operation for all remaining keys
238+
else:
239+
QTextEdit.keyPressEvent(self, e)
240+
self.setTextCursor(self.cursor)
241+
self.ensureCursorVisible()
242+
243+
def insertFromMimeData(self, source):
244+
self.cursor = self.textCursor()
245+
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor, 1)
246+
self.setTextCursor(self.cursor)
247+
if source.hasText():
248+
pasteList = QStringList()
249+
pasteList = source.text().split("\n")
250+
for line in pasteList:
251+
self.insertPlainText(line)
252+
self.runCommand(unicode(line))
253+
254+
def entered(self):
255+
self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
256+
self.runCommand( unicode(self.currentCommand()) )
257+
258+
def runCommand(self, cmd):
259+
260+
self.updateHistory(cmd)
261+
262+
self.insertPlainText("\n")
263+
264+
self.buffer.append(cmd)
265+
src = "\n".join(self.buffer)
266+
more = self.runsource(src, "<input>")
267+
if not more:
268+
self.buffer = []
269+
270+
output = sys.stdout.get_and_clean_data()
271+
if output:
272+
self.insertPlainText(output)
273+
self.displayPrompt(more)
274+
275+
def write(self, txt):
276+
""" reimplementation from code.InteractiveInterpreter """
277+
self.insertPlainText(txt)
278+
279+
175280
if __name__ == '__main__':
176-
import sys
177281
a = QApplication(sys.argv)
178-
w = QgsPythonConsole()
179-
w.show()
282+
show_console()
180283
a.exec_()

‎src/ui/qgspythondialog.ui

Lines changed: 0 additions & 118 deletions
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.