1
1
# -*- coding: utf-8 -*-
2
2
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
+
3
25
from PyQt4 .QtCore import *
4
26
from PyQt4 .QtGui import *
5
-
6
- import traceback
7
27
import sys
28
+ import traceback
29
+ import code
8
30
9
- from ui_qgspythondialog import Ui_QgsPythonDialog
10
31
11
- _console = None
32
+ _init_commands = ["from qgis.core import *" , "import qgis.utils" ]
33
+
12
34
35
+ _console = None
13
36
14
37
def show_console ():
15
38
""" called from QGIS to open the console """
16
39
global _console
17
40
if _console is None :
18
- _console = QgsPythonConsole ()
41
+ _console = PythonConsole ()
19
42
_console .show ()
20
43
_console .raise_ ()
21
44
_console .setWindowState ( _console .windowState () & ~ Qt .WindowMinimized )
22
45
_console .activateWindow ()
23
-
46
+
24
47
25
48
26
49
_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
27
57
28
58
class QgisOutputCatcher :
29
59
def __init__ (self ):
@@ -36,145 +66,218 @@ def get_and_clean_data(self):
36
66
return tmp
37
67
def flush (self ):
38
68
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
68
69
69
- self .pos -= 1
70
- return self .cmds [self .pos ]
70
+ sys .stdout = QgisOutputCatcher ()
71
71
72
- def get_next_cmd (self , current ):
73
72
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 ):
89
74
def __init__ (self , parent = None ):
90
75
QWidget .__init__ (self , parent )
91
76
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 ("<" , "<" ).replace (">" , ">" )
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" )
121
82
122
- self .history .add_cmd (command )
83
+ s = QSettings ()
84
+ self .restoreGeometry (s .value ("/python/console/geometry" ).toByteArray ())
123
85
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\" >>>></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 )
169
88
170
89
def closeEvent (self , event ):
171
- uninstallConsoleHook ()
90
+ s = QSettings ()
91
+ s .setValue ("/python/console/geometry" , QVariant (self .saveGeometry ()))
172
92
QWidget .closeEvent (self , event )
173
93
174
94
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
+
175
280
if __name__ == '__main__' :
176
- import sys
177
281
a = QApplication (sys .argv )
178
- w = QgsPythonConsole ()
179
- w .show ()
282
+ show_console ()
180
283
a .exec_ ()
0 commit comments