# -*- coding:utf-8 -*- """ /*************************************************************************** Python Conosle for QGIS ------------------- begin : 2012-09-10 copyright : (C) 2012 by Salvatore Larosa email : lrssvtml (at) gmail (dot) com ***************************************************************************/ /*************************************************************************** * * * 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 https://code.google.com/p/pydee/ """ from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4.Qsci import (QsciScintilla, QsciScintillaBase, QsciLexerPython, QsciAPIs) from qgis.core import QgsApplication from qgis.gui import QgsMessageBar import sys import os import subprocess import datetime class KeyFilter(QObject): SHORTCUTS = { ("Control", "T"): lambda w, t: w.newTabEditor(), ("Control", "M"): lambda w, t: t.save(), ("Control", "W"): lambda w, t: t.close() } def __init__(self, window, tab, *args): QObject.__init__(self, *args) self.window = window self.tab = tab self._handlers = {} for shortcut, handler in KeyFilter.SHORTCUTS.iteritems(): modifiers = shortcut[0] if not isinstance(modifiers, list): modifiers = [modifiers] qt_mod_code = Qt.NoModifier for each in modifiers: qt_mod_code |= getattr(Qt, each + "Modifier") qt_keycode = getattr(Qt, "Key_" + shortcut[1].upper()) handlers = self._handlers.get(qt_keycode, []) handlers.append((qt_mod_code, handler)) self._handlers[qt_keycode] = handlers def get_handler(self, key, modifier): for modifiers, handler in self._handlers.get(key, []): if modifiers == modifier: return handler return None def eventFilter(self, obj, event): if event.type() == QEvent.KeyPress and event.key() < 256: handler = self.get_handler(event.key(), event.modifiers()) if handler: handler(self.window, self.tab) return QObject.eventFilter(self, obj, event) class Editor(QsciScintilla): #ARROW_MARKER_NUM = 4 def __init__(self, parent=None): super(Editor,self).__init__(parent) self.parent = parent # Enable non-ascii chars for editor self.setUtf8(True) #self.insertInitText() self.setLexers() # Set the default font font = QFont() font.setFamily('Courier') font.setFixedPitch(True) font.setPointSize(10) self.setFont(font) self.setMarginsFont(font) # Margin 0 is used for line numbers #fm = QFontMetrics(font) #fontmetrics = QFontMetrics(font) self.setMarginsFont(font) self.setMarginWidth(1, "00000") self.setMarginLineNumbers(1, True) self.setMarginsForegroundColor(QColor("#3E3EE3")) self.setMarginsBackgroundColor(QColor("#f9f9f9")) self.setCaretLineVisible(True) self.setCaretLineBackgroundColor(QColor("#fcf3ed")) # Clickable margin 1 for showing markers # self.setMarginSensitivity(1, True) # self.connect(self, # SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'), # self.on_margin_clicked) # self.markerDefine(QsciScintilla.RightArrow, # self.ARROW_MARKER_NUM) # self.setMarkerBackgroundColor(QColor("#ee1111"), # self.ARROW_MARKER_NUM) self.setMinimumHeight(120) self.setAutoCompletionThreshold(2) self.setAutoCompletionSource(self.AcsAPIs) # Folding self.setFolding(QsciScintilla.PlainFoldStyle) self.setFoldMarginColors(QColor("#cccccc"),QColor("#cccccc")) #self.setWrapMode(QsciScintilla.WrapCharacter) ## Edge Mode self.setEdgeMode(QsciScintilla.EdgeLine) self.setEdgeColumn(80) self.setEdgeColor(QColor("#FF0000")) self.setWrapMode(QsciScintilla.WrapCharacter) self.setWhitespaceVisibility(QsciScintilla.WsVisibleAfterIndent) self.SendScintilla(QsciScintilla.SCI_SETHSCROLLBAR, 0) # Annotations #self.setAnnotationDisplay(QsciScintilla.ANNOTATION_BOXED) # Indentation self.setAutoIndent(True) self.setIndentationsUseTabs(False) self.setIndentationWidth(4) self.setTabIndents(True) self.setBackspaceUnindents(True) self.setTabWidth(4) self.setIndentationGuides(True) ## Disable command key ctrl, shift = self.SCMOD_CTRL<<16, self.SCMOD_SHIFT<<16 self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('L')+ ctrl) self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('T')+ ctrl) self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('D')+ ctrl) #self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('Z')+ ctrl) self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('Y')+ ctrl) self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('L')+ ctrl+shift) ## New QShortcut = ctrl+space/ctrl+alt+space for Autocomplete self.newShortcutCS = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Space), self) #self.newShortcutCAS = QShortcut(QKeySequence(Qt.CTRL + Qt.ALT + Qt.Key_Space), self) self.newShortcutCS.activated.connect(self.autoComplete) self.runScut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_E), self) self.runScut.activated.connect(self.runScriptCode) def autoComplete(self): self.autoCompleteFromAll() #self.modificationChanged.connect(self.textEdited) def on_margin_clicked(self, nmargin, nline, modifiers): # Toggle marker for the line the margin was clicked on if self.markersAtLine(nline) != 0: self.markerDelete(nline, self.ARROW_MARKER_NUM) else: self.markerAdd(nline, self.ARROW_MARKER_NUM) def refreshLexerProperties(self): self.setLexers() def setLexers(self): from qgis.core import QgsApplication self.lexer = QsciLexerPython() settings = QSettings() loadFont = settings.value("pythonConsole/fontfamilytext", "Monospace").toString() fontSize = settings.value("pythonConsole/fontsize", 10).toInt()[0] font = QFont(loadFont) font.setFixedPitch(True) font.setPointSize(fontSize) font.setStyleHint(QFont.TypeWriter) font.setStretch(QFont.SemiCondensed) font.setLetterSpacing(QFont.PercentageSpacing, 87.0) font.setBold(False) self.lexer.setDefaultFont(font) self.lexer.setColor(Qt.red, 1) self.lexer.setColor(Qt.darkGreen, 5) self.lexer.setColor(Qt.darkBlue, 15) self.lexer.setFont(font, 1) self.lexer.setFont(font, 3) self.lexer.setFont(font, 4) self.api = QsciAPIs(self.lexer) chekBoxAPI = settings.value("pythonConsole/preloadAPI", True).toBool() if chekBoxAPI: self.api.loadPrepared( QgsApplication.pkgDataPath() + "/python/qsci_apis/pyqgis_master.pap" ) else: apiPath = settings.value("pythonConsole/userAPI").toStringList() for i in range(0, len(apiPath)): self.api.load(QString(unicode(apiPath[i]))) self.api.prepare() self.lexer.setAPIs(self.api) self.setLexer(self.lexer) def contextMenuEvent(self, e): menu = QMenu(self) iconRun = QgsApplication.getThemeIcon("console/iconRunConsole.png") # iconOpen = QgsApplication.getThemeIcon("console/iconOpenConsole.png") # iconSave = QgsApplication.getThemeIcon("console/iconSaveConsole.png") iconCodePad = QgsApplication.getThemeIcon("console/iconCodepadConsole.png") iconNewEditor = QgsApplication.getThemeIcon("console/iconTabEditorConsole.png") hideEditorAction = menu.addAction("Hide Editor", self.hideEditor) menu.addSeparator() newTabAction = menu.addAction(iconNewEditor, "New Tab", self.parent.newTab, QKeySequence(Qt.CTRL + Qt.Key_T)) closeTabAction = menu.addAction("Close Tab", self.parent.close, QKeySequence(Qt.CTRL + Qt.Key_W)) menu.addSeparator() # openAction = menu.addAction(iconOpen, # "Open", # self.parent.openFile) # saveAction = menu.addAction(iconSave, # "Save", # self.parent.save, # QKeySequence(Qt.CTRL + Qt.Key_M)) # menu.addSeparator() runSelected = menu.addAction(iconRun, "Enter selected", self.runSelectedCode, QKeySequence(Qt.CTRL + Qt.Key_E)) runScript = menu.addAction(iconRun, "Run Script", self.runScriptCode) menu.addSeparator() undoAction = menu.addAction("Undo", self.undo, QKeySequence.Undo) redoAction = menu.addAction("Redo", self.redo, QKeySequence.Redo) menu.addSeparator() cutAction = menu.addAction("Cut", self.cut, QKeySequence.Cut) copyAction = menu.addAction("Copy", self.copy, QKeySequence.Copy) pasteAction = menu.addAction("Paste", self.paste, QKeySequence.Paste) menu.addSeparator() codePadAction = menu.addAction(iconCodePad, "Share on codepad", self.codepad) menu.addSeparator() selectAllAction = menu.addAction("Select All", self.selectAll, QKeySequence.SelectAll) pasteAction.setEnabled(False) codePadAction.setEnabled(False) cutAction.setEnabled(False) runSelected.setEnabled(False) copyAction.setEnabled(False) selectAllAction.setEnabled(False) closeTabAction.setEnabled(False) undoAction.setEnabled(False) redoAction.setEnabled(False) if self.parent.mw.count() > 1: closeTabAction.setEnabled(True) if self.hasSelectedText(): runSelected.setEnabled(True) copyAction.setEnabled(True) cutAction.setEnabled(True) codePadAction.setEnabled(True) if not self.text() == '': selectAllAction.setEnabled(True) if self.isUndoAvailable(): undoAction.setEnabled(True) if self.isRedoAvailable(): redoAction.setEnabled(True) if QApplication.clipboard().text() != "": pasteAction.setEnabled(True) action = menu.exec_(self.mapToGlobal(e.pos())) def codepad(self): import urllib2, urllib listText = self.selectedText().split('\n') getCmd = [] for strLine in listText: if strLine != "": #if s[0:3] in (">>>", "..."): # filter for special command (_save,_clear) and comment if strLine[4] != "_" and strLine[:2] != "##": strLine.replace(">>> ", "").replace("... ", "") getCmd.append(unicode(strLine)) pasteText= u"\n".join(getCmd) url = 'http://codepad.org' values = {'lang' : 'Python', 'code' : pasteText, 'submit':'Submit'} try: response = urllib2.urlopen(url, urllib.urlencode(values)) url = response.read() for href in url.split(""): if "Link:" in href: ind=href.index('Link:') found = href[ind+5:] for i in found.split('">'): if ' 0: if self.count() < 2: #self.setTabsClosable(False) self.removeTab(self.indexOf(tab)) #pass self.newTabEditor() else: self.removeTab(self.indexOf(tab)) self.currentWidget().setFocus(Qt.TabFocusReason) def setTabTitle(self, tab, title): self.setTabText(self.indexOf(tab), title) def _removeTab(self, tab): if self.widget(tab).newEditor.isModified(): res = QMessageBox.question( self, 'Save Script', 'The script "%s" has been modified, save changes ?' % self.tabText(tab), QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel ) if res == QMessageBox.Save: self.widget(tab).save() elif res == QMessageBox.Cancel: return else: self.parent.updateTabListScript(self.widget(tab).path) self.removeTab(tab) else: if self.widget(tab).path in self.restoreTabList: self.parent.updateTabListScript(self.widget(tab).path) if self.count() <= 2: self.setTabsClosable(False) self.removeTab(tab) else: self.removeTab(tab) self.currentWidget().setFocus(Qt.TabFocusReason) def buttonClosePressed(self): self.closeCurrentWidget() def closeCurrentWidget(self): currWidget = self.currentWidget() if currWidget and currWidget.close(): self.removeTab( self.currentIndex() ) currWidget = self.currentWidget() if currWidget: currWidget.setFocus(Qt.TabFocusReason) if currWidget.path in self.restoreTabList: #print currWidget.path self.parent.updateTabListScript(currWidget.path) def restoreTabs(self): for script in self.restoreTabList: pathFile = str(script.toString()) if os.path.exists(pathFile): tabName = pathFile.split('/')[-1] self.newTabEditor(tabName, pathFile) self.topFrame.close() def closeRestore(self): self.parent.updateTabListScript('empty') self.topFrame.close() def showFileTabMenu(self): self.fileTabMenu.clear() for index in range(self.count()): action = self.fileTabMenu.addAction(self.tabIcon(index), self.tabText(index)) action.setData(QVariant(index)) def showFileTabMenuTriggered(self, action): index, ok = action.data().toInt() if ok: self.setCurrentIndex(index) def widgetMessageBar(self, iface, text): timeout = iface.messageTimeout() currWidget = self.currentWidget() currWidget.infoBar.pushMessage('Editor', text, QgsMessageBar.INFO, timeout)