[QGIS Commit] r12727 - in trunk/qgis: python src/ui

svn_qgis at osgeo.org svn_qgis at osgeo.org
Sun Jan 10 06:14:47 EST 2010


Author: wonder
Date: 2010-01-10 06:14:46 -0500 (Sun, 10 Jan 2010)
New Revision: 12727

Removed:
   trunk/qgis/src/ui/qgspythondialog.ui
Modified:
   trunk/qgis/python/CMakeLists.txt
   trunk/qgis/python/console.py
Log:
[FEATURE] new python console implementation.
Simpler, easier and better :-)


Modified: trunk/qgis/python/CMakeLists.txt
===================================================================
--- trunk/qgis/python/CMakeLists.txt	2010-01-10 00:10:48 UTC (rev 12726)
+++ trunk/qgis/python/CMakeLists.txt	2010-01-10 11:14:46 UTC (rev 12727)
@@ -99,12 +99,7 @@
 ENDIF (BINDINGS_GLOBAL_INSTALL)
 
 
-# python console
-PYQT4_WRAP_UI(PYUI_FILE ${CMAKE_SOURCE_DIR}/src/ui/qgspythondialog.ui)
 
-ADD_CUSTOM_TARGET(pythonconsole ALL DEPENDS ${PYUI_FILE})
-
-
 # Step 4: install built libs to python's site packages
-INSTALL(FILES __init__.py utils.py console.py ${PYUI_FILE} ${CMAKE_CURRENT_BINARY_DIR}/qgisconfig.py ${BINDINGS_LIBS} DESTINATION ${SITE_PKG_PATH}/qgis)
+INSTALL(FILES __init__.py utils.py console.py ${CMAKE_CURRENT_BINARY_DIR}/qgisconfig.py ${BINDINGS_LIBS} DESTINATION ${SITE_PKG_PATH}/qgis)
 

Modified: trunk/qgis/python/console.py
===================================================================
--- trunk/qgis/python/console.py	2010-01-10 00:10:48 UTC (rev 12726)
+++ trunk/qgis/python/console.py	2010-01-10 11:14:46 UTC (rev 12727)
@@ -1,30 +1,60 @@
 # -*- coding: utf-8 -*-
 
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# Some portions of code were taken from managerR plugin.
+
+"""
+Implementation of interactive Python console widget for QGIS.
+
+Has +- the same behaviour as command-line interactive console:
+- runs commands one by one
+- supports expressions that span through more lines
+- has command history, accessible using up/down keys
+- supports pasting of commands
+
+TODO:
+- configuration - init commands, font, ...
+- syntax highlighting
+
+"""
+
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
-
+import sys
 import traceback
-import sys
+import code
 
-from ui_qgspythondialog import Ui_QgsPythonDialog
 
+_init_commands = ["from qgis.core import *", "import qgis.utils"]
+
+
 _console = None
 
-
 def show_console():
   """ called from QGIS to open the console """
   global _console
   if _console is None:
-    _console = QgsPythonConsole()
+    _console = PythonConsole()
   _console.show()
   _console.raise_()
   _console.setWindowState( _console.windowState() & ~Qt.WindowMinimized )
   _console.activateWindow()
+ 
 
 
-
 _old_stdout = sys.stdout
+_console_output = None
 
+# hook for python console so all output will be redirected
+# and then shown in console
+def console_displayhook(obj):
+  global _console_output
+  _console_output = obj
+
 class QgisOutputCatcher:
   def __init__(self):
     self.data = ''
@@ -36,145 +66,218 @@
     return tmp
   def flush(self):
     pass
-  
-def installConsoleHook():
-  sys.stdout = QgisOutputCatcher()
 
-def uninstallConsoleHook():
-  sys.stdout = _old_stdout
+sys.stdout = QgisOutputCatcher()
 
 
+class PythonConsole(QWidget):
+  def __init__(self, parent=None):
+    QWidget.__init__(self, parent)
 
-class ConsoleHistory(object):
-  def __init__(self):
-    self.cmds = []
-    self.pos = 0
-    self.active_cmd = u"" # active, not yet entered command
+    self.edit = PythonEdit()
+    self.l = QVBoxLayout()
+    self.l.addWidget(self.edit)
+    self.setLayout(self.l)
+    self.setWindowTitle("Python console")
 
-  def add_cmd(self, command):
-    if len(command) != 0:
-      self.cmds.append(command)
-      self.pos = len(self.cmds)
-      self.active_cmd = u""
+    s = QSettings()
+    self.restoreGeometry(s.value("/python/console/geometry").toByteArray())
 
-  def get_previous_cmd(self, current):
-    if self.pos == 0:
-      return None
+  def sizeHint(self):
+    return QSize(500,300)
 
-    if self.pos == len(self.cmds):
-      self.active_cmd = current
-    else:
-      self.cmds[self.pos] = current
+  def closeEvent(self, event):
+    s = QSettings()
+    s.setValue("/python/console/geometry", QVariant(self.saveGeometry()))
+    QWidget.closeEvent(self, event)
 
-    self.pos -= 1
-    return self.cmds[self.pos]
 
-  def get_next_cmd(self, current):
+class PythonEdit(QTextEdit, code.InteractiveInterpreter):
 
-    # if we're at active (last) command, don't move
-    if self.pos == len(self.cmds):
-      return None
-      
-    self.cmds[self.pos] = current
-    self.pos += 1
+  def __init__(self,parent=None):
+    QTextEdit.__init__(self, parent)
+    code.InteractiveInterpreter.__init__(self, locals=None)
 
-    if self.pos == len(self.cmds):
-      return self.active_cmd
-    else:
-      return self.cmds[self.pos]
+    self.setTextInteractionFlags(Qt.TextEditorInteraction)
+    self.setAcceptDrops(False)
+    self.setMinimumSize(30, 30)
+    self.setUndoRedoEnabled(False)
+    self.setAcceptRichText(False)
+    #monofont = QFont("Bitstream Vera Sans Mono", 10)
+    #self.setFont(monofont)
 
+    self.buffer = []
 
+    self.insertPlainText("To access Quantum GIS environment from this console\n"
+                         "use qgis.utils.iface object (instance of QgisInterface class).\n"
+			 "\n")
 
-class QgsPythonConsole(QWidget, Ui_QgsPythonDialog):
-  def __init__(self, parent=None):
-    QWidget.__init__(self, parent)
+    for line in _init_commands:
+      self.runsource(line)
 
-    self.setupUi(self)
+    self.displayPrompt(False)
 
-    # minimize button was not enabled on mac
-    self.setWindowFlags( self.windowFlags() | Qt.WindowMinimizeButtonHint )
+    self.history = QStringList()
+    self.historyIndex = 0
 
-    self.history = ConsoleHistory()
+    #from pythonhigh import PythonHighlighter
+    #self.high = PythonHighlighter(self)
 
-    self.console_globals = {}
-  
-  def escapeHtml(self, text):
-    return text.replace("<", "&lt;").replace(">", "&gt;")
+  def displayPrompt(self, more=False):
+    self.currentPrompt = "... " if more else ">>> "
+    self.currentPromptLength = len(self.currentPrompt)
+    self.insertPlainText(self.currentPrompt)
+    self.moveCursor(QTextCursor.End, QTextCursor.MoveAnchor)
 
-  @pyqtSlot()
-  def on_pbnPrev_clicked(self):
-    cmd = self.history.get_previous_cmd( self.getCommand() )
-    if cmd is not None:
-      self.edtCmdLine.setText(cmd)
+  def isCursorInEditionZone(self):
+    cursor = self.textCursor()
+    pos = cursor.position()
+    block = self.document().lastBlock()
+    last = block.position() + self.currentPromptLength
+    return pos >= last
 
-  @pyqtSlot()
-  def on_pbnNext_clicked(self):
-    cmd = self.history.get_next_cmd( self.getCommand() )
-    if cmd is not None:
-      self.edtCmdLine.setText(cmd)
+  def currentCommand(self):
+    block = self.cursor.block()
+    text = block.text()
+    return text.right(text.length()-self.currentPromptLength)
 
-  def getCommand(self):
-    return unicode(self.edtCmdLine.toPlainText())
+  def showPrevious(self):
+        if self.historyIndex < len(self.history) and not self.history.isEmpty():
+            self.cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
+            self.cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor)
+            self.cursor.removeSelectedText()
+            self.cursor.insertText(self.currentPrompt)
+            self.historyIndex += 1
+            if self.historyIndex == len(self.history):
+                self.insertPlainText("")
+            else:
+                self.insertPlainText(self.history[self.historyIndex])
 
-  def execute(self, single):
-    command = self.getCommand()
+  def showNext(self):
+        if  self.historyIndex > 0 and not self.history.isEmpty():
+            self.cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
+            self.cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor)
+            self.cursor.removeSelectedText()
+            self.cursor.insertText(self.currentPrompt)
+            self.historyIndex -= 1
+            if self.historyIndex == len(self.history):
+                self.insertPlainText("")
+            else:
+                self.insertPlainText(self.history[self.historyIndex])
 
-    self.history.add_cmd(command)
+  def updateHistory(self, command):
+        if isinstance(command, QStringList):
+            for line in command:
+                self.history.append(line)
+        elif not command == "":
+            if len(self.history) <= 0 or \
+            not command == self.history[-1]:
+                self.history.append(command)
+        self.historyIndex = len(self.history)
 
-    try:
-      # run command
-      code = compile(command, '<console>', 'single' if single else 'exec')
-      res = eval(code, self.console_globals)
-      result = unicode( res ) if res is not None else u''
-      
-      # get standard output
-      output = sys.stdout.get_and_clean_data()
+  def keyPressEvent(self, e):
+        self.cursor = self.textCursor()
+        # if the cursor isn't in the edition zone, don't do anything except Ctrl+C
+        if not self.isCursorInEditionZone():
+            if e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier:
+                if e.key() == Qt.Key_C or e.key() == Qt.Key_A:
+                    QTextEdit.keyPressEvent(self, e)
+            else:
+                # all other keystrokes get sent to the input line
+                self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
+        else:
+            # if Return is pressed, then perform the commands
+            if e.key() == Qt.Key_Return:
+                self.entered()
+            # if Up or Down is pressed
+            elif e.key() == Qt.Key_Down:
+                self.showPrevious()
+            elif e.key() == Qt.Key_Up:
+                self.showNext()
+            # if backspace is pressed, delete until we get to the prompt
+            elif e.key() == Qt.Key_Backspace:
+                if not self.cursor.hasSelection() and self.cursor.columnNumber() == self.currentPromptLength:
+                    return
+                QTextEdit.keyPressEvent(self, e)
+            # if the left key is pressed, move left until we get to the prompt
+            elif e.key() == Qt.Key_Left and self.cursor.position() > self.document().lastBlock().position() + self.currentPromptLength:
+                if e.modifiers() == Qt.ShiftModifier:
+                    anchor = QTextCursor.KeepAnchor
+                else:
+                    anchor = QTextCursor.MoveAnchor
+                if (e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier):
+                    self.cursor.movePosition(QTextCursor.WordLeft, anchor)
+                else:
+                    self.cursor.movePosition(QTextCursor.Left, anchor)
+            # use normal operation for right key
+            elif e.key() == Qt.Key_Right:
+                if e.modifiers() == Qt.ShiftModifier:
+                    anchor = QTextCursor.KeepAnchor
+                else:
+                    anchor = QTextCursor.MoveAnchor
+                if (e.modifiers() == Qt.ControlModifier or e.modifiers() == Qt.MetaModifier):
+                    self.cursor.movePosition(QTextCursor.WordRight, anchor)
+                else:
+                    self.cursor.movePosition(QTextCursor.Right, anchor)
+            # if home is pressed, move cursor to right of prompt
+            elif e.key() == Qt.Key_Home:
+                if e.modifiers() == Qt.ShiftModifier:
+                    anchor = QTextCursor.KeepAnchor
+                else:
+                    anchor = QTextCursor.MoveAnchor
+                self.cursor.movePosition(QTextCursor.StartOfBlock, anchor, 1)
+                self.cursor.movePosition(QTextCursor.Right, anchor, self.currentPromptLength)
+            # use normal operation for end key
+            elif e.key() == Qt.Key_End:
+                if e.modifiers() == Qt.ShiftModifier:
+                    anchor = QTextCursor.KeepAnchor
+                else:
+                    anchor = QTextCursor.MoveAnchor
+                self.cursor.movePosition(QTextCursor.EndOfBlock, anchor, 1)
+            # use normal operation for all remaining keys
+            else:
+                QTextEdit.keyPressEvent(self, e)
+        self.setTextCursor(self.cursor)
+        self.ensureCursorVisible()
 
-      #_old_stdout.write("cmd: '"+command+"'\n")
-      #_old_stdout.write("res: '"+result+"'\n")
-      #_old_stdout.write("out: '"+output+"'\n")
-      #_old_stdout.flush()
+  def insertFromMimeData(self, source):
+        self.cursor = self.textCursor()
+        self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor, 1)
+        self.setTextCursor(self.cursor)
+        if source.hasText():
+            pasteList = QStringList()
+            pasteList = source.text().split("\n")
+            for line in pasteList:
+		self.insertPlainText(line)
+		self.runCommand(unicode(line))
 
-      # escape the result so python objects display properly and
-      # we can still use html output to get nicely formatted display
-      output = self.escapeHtml( output ) + self.escapeHtml( result )
-      if len(output) != 0:
-        output += "<br>"
+  def entered(self):
+    self.cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
+    self.runCommand( unicode(self.currentCommand()) )
 
-    except Exception, e:
-      #lst = traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)
-      lst = traceback.format_exception_only(sys.exc_type, sys.exc_value)
-      errorText = "<br>".join(map(self.escapeHtml, lst))
-      output = "<font color=\"red\">" + errorText + "</font><br>"
+  def runCommand(self, cmd):
 
-    s = "<b><font color=\"green\">&gt;&gt;&gt;</font> " + self.escapeHtml( command ) + "</b><br>" + output
-    self.edtCmdLine.setText("")
+    self.updateHistory(cmd)
 
-    self.txtHistory.moveCursor(QTextCursor.End)
-    self.txtHistory.insertHtml(s)
-    self.txtHistory.moveCursor(QTextCursor.End)
-    self.txtHistory.ensureCursorVisible()
+    self.insertPlainText("\n")
 
-  @pyqtSlot()
-  def on_pbnExecute_clicked(self):
-    self.execute(False)
+    self.buffer.append(cmd)
+    src = "\n".join(self.buffer)
+    more = self.runsource(src, "<input>")
+    if not more:
+      self.buffer = []
 
-  @pyqtSlot()
-  def on_pbnEval_clicked(self):
-    self.execute(True)
+    output = sys.stdout.get_and_clean_data()
+    if output:
+      self.insertPlainText(output)
+    self.displayPrompt(more)
 
-  def showEvent(self, event):
-    QWidget.showEvent(self, event)
-    installConsoleHook()
+  def write(self, txt):
+    """ reimplementation from code.InteractiveInterpreter """
+    self.insertPlainText(txt)
 
-  def closeEvent(self, event):
-    uninstallConsoleHook()
-    QWidget.closeEvent(self, event)
 
-
 if __name__ == '__main__':
-  import sys
   a = QApplication(sys.argv)
-  w = QgsPythonConsole()
-  w.show()
+  show_console()
   a.exec_()

Deleted: trunk/qgis/src/ui/qgspythondialog.ui
===================================================================
--- trunk/qgis/src/ui/qgspythondialog.ui	2010-01-10 00:10:48 UTC (rev 12726)
+++ trunk/qgis/src/ui/qgspythondialog.ui	2010-01-10 11:14:46 UTC (rev 12727)
@@ -1,118 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>QgsPythonDialog</class>
- <widget class="QDialog" name="QgsPythonDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>625</width>
-    <height>641</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Python console</string>
-  </property>
-  <layout class="QGridLayout" name="gridLayout">
-   <item row="0" column="0" colspan="5">
-    <widget class="QLabel" name="lblInfo">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="text">
-      <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans'; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;To access Quantum GIS environment from this python console use qgis.utils.iface object which is an instance of QgisInterface class.&lt;br /&gt;Usage e.g.: qgis.utils.iface.zoomFull()&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-     </property>
-     <property name="wordWrap">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="4">
-    <widget class="QPushButton" name="pbnPrev">
-     <property name="text">
-      <string>&amp;Previous</string>
-     </property>
-     <property name="shortcut">
-      <string comment="Ctrl+Up"/>
-     </property>
-    </widget>
-   </item>
-   <item row="3" column="4">
-    <widget class="QPushButton" name="pbnExecute">
-     <property name="text">
-      <string>E&amp;xecute</string>
-     </property>
-    </widget>
-   </item>
-   <item row="4" column="4">
-    <widget class="QPushButton" name="pbnEval">
-     <property name="text">
-      <string>&amp;Eval</string>
-     </property>
-    </widget>
-   </item>
-   <item row="5" column="4">
-    <widget class="QPushButton" name="pbnNext">
-     <property name="text">
-      <string>&amp;Next</string>
-     </property>
-     <property name="shortcut">
-      <string/>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="1" rowspan="4" colspan="2">
-    <widget class="QLabel" name="lblPrompt">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
-       <horstretch>0</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="text">
-      <string>&gt;&gt;&gt;</string>
-     </property>
-    </widget>
-   </item>
-   <item row="1" column="0" colspan="5">
-    <widget class="QTextBrowser" name="txtHistory">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-       <horstretch>0</horstretch>
-       <verstretch>3</verstretch>
-      </sizepolicy>
-     </property>
-     <property name="html">
-      <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="3" rowspan="4">
-    <widget class="QTextEdit" name="edtCmdLine">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
-       <horstretch>0</horstretch>
-       <verstretch>1</verstretch>
-      </sizepolicy>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <tabstops>
-  <tabstop>txtHistory</tabstop>
- </tabstops>
- <resources/>
- <connections/>
-</ui>



More information about the QGIS-commit mailing list