[db manager] use QScintilla for SQL window

This commit is contained in:
Alexander Bruy 2014-02-06 15:18:40 +02:00
parent e6c2ecd9f3
commit 83baa914bd
4 changed files with 211 additions and 221 deletions

View File

@ -50,10 +50,10 @@ class DlgSqlWindow(QDialog, Ui_Dialog):
settings = QSettings()
self.restoreGeometry(settings.value("/DB_Manager/sqlWindow/geometry", QByteArray(), type=QByteArray))
self.editSql.setAcceptRichText(False)
self.editSql.setFocus()
SqlCompleter(self.editSql, self.db)
SqlHighlighter(self.editSql, self.db)
self.editSql.initCompleter(self.db)
#SqlCompleter(self.editSql, self.db)
#SqlHighlighter(self.editSql, self.db)
# allow to copy results
copyAction = QAction("copy", self)
@ -95,7 +95,7 @@ class DlgSqlWindow(QDialog, Ui_Dialog):
self.presetCombo.setCurrentIndex(-1)
def storePreset(self):
query = self.editSql.toPlainText()
query = self.editSql.text()
name = self.presetName.text()
QgsProject.instance().writeEntry('DBManager','savedQueries/q'+str(name.__hash__())+'/name', name )
QgsProject.instance().writeEntry('DBManager','savedQueries/q'+str(name.__hash__())+'/query', query )
@ -128,24 +128,13 @@ class DlgSqlWindow(QDialog, Ui_Dialog):
self.loadAsLayerGroup.setChecked( checked )
self.loadAsLayerWidget.setVisible( checked )
def getSql(self):
# If the selection obtained from an editor spans a line break,
# the text will contain a Unicode U+2029 paragraph separator
# character instead of a newline \n character
# (see https://qt-project.org/doc/qt-4.8/qtextcursor.html#selectedText)
sql = self.editSql.textCursor().selectedText().replace(unichr(0x2029), "\n")
if sql == "":
sql = self.editSql.toPlainText()
# try to sanitize query
sql = re.sub( ";\\s*$", "", sql )
return sql
def clearSql(self):
self.editSql.clear()
def executeSql(self):
sql = self.getSql()
if sql == "": return
sql = self.editSql.text()
if sql == "":
return
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

View File

@ -1,185 +0,0 @@
# -*- coding: utf-8 -*-
"""
/***************************************************************************
Name : DB Manager
Description : Database manager plugin for QGIS
Date : May 23, 2011
copyright : (C) 2011 by Giuseppe Sucameli
email : brush.tyler@gmail.com
The content of this file is based on
- Python Syntax Highlighting Example by Carson J. Q. Farmer (GPLv2 license)
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
"""
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class SqlHighlighter(QSyntaxHighlighter):
COLOR_KEYWORD = QColor(0x00,0x00,0xE6)
COLOR_FUNCTION = QColor(0xCE,0x7B,0x00)
COLOR_COMMENT = QColor(0x96,0x96,0x96)
COLOR_CONSTANT = Qt.magenta
COLOR_IDENTIFIER = QColor(0x00,0x99,0x00)
COLOR_PARAMETER = QColor(0x25,0x9D,0x9D)
def __init__(self, editor, db=None):
QSyntaxHighlighter.__init__(self, editor)
self.editor = editor
self.rules = []
self.styles = {}
# keyword
format = QTextCharFormat()
format.setForeground( QBrush(self.COLOR_KEYWORD, Qt.SolidPattern) )
#format.setFontWeight( QFont.Bold )
self.styles['keyword'] = format
# function and delimiter
format = QTextCharFormat()
format.setForeground( QBrush(self.COLOR_FUNCTION, Qt.SolidPattern) )
self.styles['function'] = format
self.styles['delimiter'] = format
# identifier
format = QTextCharFormat()
format.setForeground( QBrush(self.COLOR_IDENTIFIER, Qt.SolidPattern) )
self.styles['identifier'] = format
# comment
format = QTextCharFormat()
format.setForeground( QBrush(self.COLOR_COMMENT, Qt.SolidPattern) )
self.styles['comment'] = format
# constant (numbers, strings)
format = QTextCharFormat()
format.setForeground( QBrush(self.COLOR_CONSTANT, Qt.SolidPattern) )
self.styles['constant'] = format
if db:
self.load(db)
def highlightBlock(self, text):
index = 0
rule_sel = None
rule_index = -1
while index < len(text):
# search for the rule that matches starting from the less index
rule_sel = None
rule_index = -1
rule_length = 0
for rule in self.rules:
regex = rule.regex()
pos = regex.indexIn(text, index)
if pos >= 0:
if rule_sel == None or rule_index > pos:
rule_sel = rule
rule_index = pos
rule_length = len(regex.cap(0))
if rule_sel == None: # no rule matches
break
# apply the rule found before
self.setFormat(rule_index, rule_length, self.styles[rule_sel.type()])
index = rule_index + rule_length
self.setCurrentBlockState( 0 )
# handle with multiline comments
index = 0
if self.previousBlockState() != 1:
index = self.multiLineCommentStart.indexIn(text, index)
while index >= 0:
# if the last applied rule is a single-line comment,
# then avoid multiline comments that start after it
if rule_sel != None and rule_sel.type() == 'comment' and index >= rule_index:
break
pos = self.multiLineCommentEnd.indexIn(text, index)
comment_length = 0
if pos < 0:
self.setCurrentBlockState(1)
comment_length = len(text) - index;
else:
comment_length = pos - index + len(self.multiLineCommentEnd.cap(0))
self.setFormat(index, comment_length, self.styles['comment'])
index = self.multiLineCommentStart.indexIn(text, index + comment_length)
def load(self, db=None):
self.rules = []
rules = None
if db:
rules = db.connector.getSqlDictionary()
if not rules:
# use the generic sql dictionary
from .sql_dictionary import getSqlDictionary
rules = getSqlDictionary()
for name in self.styles.keys():
if not name in rules:
continue
for value in rules[name]:
regex = QRegExp( u"\\b%s\\b" % QRegExp.escape(value), Qt.CaseInsensitive )
rule = HighlightingRule(name, regex)
self.rules.append( rule )
# delimiter
regex = QRegExp( "[\)\(]" )
rule = HighlightingRule('delimiter', regex)
self.rules.append( rule )
# identifier
regex = QRegExp( r'"[^"\\]*(\\.[^"\\]*)*"' )
regex.setMinimal( True )
rule = HighlightingRule('identifier', regex)
self.rules.append( rule )
# constant (numbers, strings)
# string
regex = QRegExp( r"'[^'\\]*(\\.[^'\\]*)*'" )
regex.setMinimal( True )
rule = HighlightingRule('constant', regex)
self.rules.append( rule )
# number
regex = QRegExp( r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b' )
regex.setMinimal( True )
rule = HighlightingRule('constant', regex)
self.rules.append( rule )
# single-line comment
regex = QRegExp( "--.*$" )
rule = HighlightingRule('comment', regex)
self.rules.append( rule )
# multi-line comment
self.multiLineCommentStart = QRegExp( "/\\*" )
self.multiLineCommentEnd = QRegExp( "\\*/" )
class HighlightingRule:
def __init__(self, typ, regex):
self._type = typ
self._regex = regex
def type(self):
return self._type
def regex(self):
return QRegExp(self._regex)

View File

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
ScriptEdit.py
---------------------
Date : February 2014
Copyright : (C) 2014 by Alexander Bruy
Email : alexander dot bruy 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. *
* *
***************************************************************************
"""
__author__ = 'Alexander Bruy'
__date__ = 'February 2014'
__copyright__ = '(C) 2014, Alexander Bruy'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.Qsci import *
from qgis.core import *
class SqlEdit(QsciScintilla):
LEXER_PYTHON = 0
LEXER_R = 1
def __init__(self, parent=None):
QsciScintilla.__init__(self, parent)
self.lexer = None
self.api = None
self.setCommonOptions()
self.initShortcuts()
def setCommonOptions(self):
# Enable non-ASCII characters
self.setUtf8(True)
# Default font
font = QFont()
font.setFamily('Courier')
font.setFixedPitch(True)
font.setPointSize(10)
self.setFont(font)
self.setMarginsFont(font)
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
self.setWrapMode(QsciScintilla.WrapWord)
self.setWrapVisualFlags(QsciScintilla.WrapFlagByText,
QsciScintilla.WrapFlagNone, 4)
self.setSelectionForegroundColor(QColor('#2e3436'))
self.setSelectionBackgroundColor(QColor('#babdb6'))
# Show line numbers
self.setMarginWidth(1, '000')
self.setMarginLineNumbers(1, True)
self.setMarginsForegroundColor(QColor('#2e3436'))
self.setMarginsBackgroundColor(QColor('#babdb6'))
# Highlight current line
self.setCaretLineVisible(True)
self.setCaretLineBackgroundColor(QColor('#d3d7cf'))
# Folding
self.setFolding(QsciScintilla.BoxedTreeFoldStyle)
self.setFoldMarginColors(QColor('#d3d7cf'), QColor('#d3d7cf'))
# Mark column 80 with vertical line
self.setEdgeMode(QsciScintilla.EdgeLine)
self.setEdgeColumn(80)
self.setEdgeColor(QColor('#eeeeec'))
# Indentation
self.setAutoIndent(True)
self.setIndentationsUseTabs(False)
self.setIndentationWidth(4)
self.setTabIndents(True)
self.setBackspaceUnindents(True)
self.setTabWidth(4)
# Autocomletion
self.setAutoCompletionThreshold(2)
self.setAutoCompletionSource(QsciScintilla.AcsAPIs)
self.setAutoCompletionCaseSensitivity(False)
# Load font from Python console settings
settings = QSettings()
fontName = settings.value('pythonConsole/fontfamilytext', 'Monospace')
fontSize = int(settings.value('pythonConsole/fontsize', 10))
self.defaultFont = QFont(fontName)
self.defaultFont.setFixedPitch(True)
self.defaultFont.setPointSize(fontSize)
self.defaultFont.setStyleHint(QFont.TypeWriter)
self.defaultFont.setStretch(QFont.SemiCondensed)
self.defaultFont.setLetterSpacing(QFont.PercentageSpacing, 87.0)
self.defaultFont.setBold(False)
self.boldFont = QFont(self.defaultFont)
self.boldFont.setBold(True)
self.italicFont = QFont(self.defaultFont)
self.italicFont.setItalic(True)
self.setFont(self.defaultFont)
self.setMarginsFont(self.defaultFont)
self.initLexer()
def initShortcuts(self):
(ctrl, shift) = (self.SCMOD_CTRL << 16, self.SCMOD_SHIFT << 16)
# Disable some shortcuts
self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('D') + ctrl)
self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('L') + ctrl)
self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('L') + ctrl
+ shift)
self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord('T') + ctrl)
#self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord("Z") + ctrl)
#self.SendScintilla(QsciScintilla.SCI_CLEARCMDKEY, ord("Y") + ctrl)
# Use Ctrl+Space for autocompletion
self.shortcutAutocomplete = QShortcut(QKeySequence(Qt.CTRL
+ Qt.Key_Space), self)
self.shortcutAutocomplete.setContext(Qt.WidgetShortcut)
self.shortcutAutocomplete.activated.connect(self.autoComplete)
def autoComplete(self):
self.autoCompleteFromAll()
def initLexer(self):
self.lexer = QsciLexerSQL()
colorDefault = QColor('#2e3436')
colorComment = QColor('#c00')
colorCommentBlock = QColor('#3465a4')
colorNumber = QColor('#4e9a06')
colorType = QColor('#4e9a06')
colorKeyword = QColor('#204a87')
colorString = QColor('#ce5c00')
self.lexer.setDefaultFont(self.defaultFont)
self.lexer.setDefaultColor(colorDefault)
self.lexer.setColor(colorComment, 1)
self.lexer.setColor(colorNumber, 2)
self.lexer.setColor(colorString, 3)
self.lexer.setColor(colorString, 4)
self.lexer.setColor(colorKeyword, 5)
self.lexer.setColor(colorString, 6)
self.lexer.setColor(colorString, 7)
self.lexer.setColor(colorType, 8)
self.lexer.setColor(colorCommentBlock, 12)
self.lexer.setColor(colorString, 15)
self.lexer.setFont(self.italicFont, 1)
self.lexer.setFont(self.boldFont, 5)
self.lexer.setFont(self.boldFont, 8)
self.lexer.setFont(self.italicFont, 12)
self.setLexer(self.lexer)
def initCompleter(self, db):
dictionary = None
if db:
dictionary = db.connector.getSqlDictionary()
if not dictionary:
# use the generic sql dictionary
from .sql_dictionary import getSqlDictionary
dictionary = getSqlDictionary()
wordlist = []
for name, value in dictionary.iteritems():
wordlist += value # concat lists
wordlist = list(set(wordlist)) # remove duplicates
self.api = QsciAPIs(self.lexer)
for word in wordlist:
self.api.add(word)
self.api.prepare()
self.lexer.setAPIs(self.api)

View File

@ -70,21 +70,7 @@
</layout>
</item>
<item>
<widget class="CompletionTextEdit" name="editSql">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="html">
<string notr="true">&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:'Ubuntu'; font-size:11pt; 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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="SqlEdit" name="editSql"/>
</item>
<item>
<layout class="QHBoxLayout">
@ -370,13 +356,12 @@ columns</string>
</widget>
<customwidgets>
<customwidget>
<class>CompletionTextEdit</class>
<class>SqlEdit</class>
<extends>QTextEdit</extends>
<header>..completer</header>
<header>..sqledit</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>editSql</tabstop>
<tabstop>btnExecute</tabstop>
<tabstop>btnClear</tabstop>
<tabstop>viewResult</tabstop>