[DB Manager] Add the ability to update SQL Layer

This commit is contained in:
rldhont 2016-07-20 12:19:07 +02:00
parent cd33ccf7f6
commit c1cdfe860d
4 changed files with 985 additions and 0 deletions

View File

@ -199,6 +199,15 @@ class DBManager(QMainWindow):
self.tabs.setCurrentIndex(index) self.tabs.setCurrentIndex(index)
query.nameChanged.connect(functools.partial(self.update_query_tab_name, index, dbname)) query.nameChanged.connect(functools.partial(self.update_query_tab_name, index, dbname))
def runSqlLayerWindow(self, layer):
from dlg_sql_layer_window import DlgSqlLayerWindow
query = DlgSqlLayerWindow(self.iface, layer, self)
lname = layer.name()
tabname = self.tr("Layer") + u" (%s)" % lname
index = self.tabs.addTab(query, tabname)
#self.tabs.setTabIcon(index, db.connection().icon())
self.tabs.setCurrentIndex(index)
def update_query_tab_name(self, index, dbname, queryname): def update_query_tab_name(self, index, dbname, queryname):
if not queryname: if not queryname:
queryname = self.tr("Query") queryname = self.tr("Query")

View File

@ -24,6 +24,9 @@ from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtWidgets import QAction, QApplication from qgis.PyQt.QtWidgets import QAction, QApplication
from qgis.PyQt.QtGui import QIcon from qgis.PyQt.QtGui import QIcon
from qgis.core import QgsMapLayerRegistry, QgsMapLayer, QgsDataSourceURI
import re
from . import resources_rc # NOQA from . import resources_rc # NOQA
@ -48,6 +51,15 @@ class DBManagerPlugin:
else: else:
self.iface.addPluginToMenu(QApplication.translate("DBManagerPlugin", "DB Manager"), self.action) self.iface.addPluginToMenu(QApplication.translate("DBManagerPlugin", "DB Manager"), self.action)
self.layerAction = QAction(QIcon(":/db_manager/icon"), QApplication.translate("DBManagerPlugin", "Update Sql Layer"),
self.iface.mainWindow())
self.layerAction.setObjectName("dbManagerUpdateSqlLayer")
QObject.connect(self.layerAction, SIGNAL("triggered()"), self.onUpdateSqlLayer)
self.iface.legendInterface().addLegendLayerAction(self.layerAction, "", "dbManagerUpdateSqlLayer", QgsMapLayer.VectorLayer, False)
for l in QgsMapLayerRegistry.instance().mapLayers().values():
self.onLayerWasAdded(l)
QgsMapLayerRegistry.instance().layerWasAdded.connect(self.onLayerWasAdded)
def unload(self): def unload(self):
# Remove the plugin menu item and icon # Remove the plugin menu item and icon
if hasattr(self.iface, 'removePluginDatabaseMenu'): if hasattr(self.iface, 'removePluginDatabaseMenu'):
@ -59,9 +71,36 @@ class DBManagerPlugin:
else: else:
self.iface.removeToolBarIcon(self.action) self.iface.removeToolBarIcon(self.action)
self.iface.legendInterface().removeLegendLayerAction(self.layerAction)
QgsMapLayerRegistry.instance().layerWasAdded.disconnect(self.onLayerWasAdded)
if self.dlg is not None: if self.dlg is not None:
self.dlg.close() self.dlg.close()
def onLayerWasAdded(self, aMapLayer):
if aMapLayer.dataProvider().name() in ['postgres', 'spatialite', 'oracle']:
uri = QgsDataSourceURI(aMapLayer.source())
if re.search('^\(SELECT .+ FROM .+\)$', uri.table(), re.S):
self.iface.legendInterface().addLegendLayerActionForLayer(self.layerAction, aMapLayer)
# virtual has QUrl source
# url = QUrl(QUrl.fromPercentEncoding(l.source()))
# url.queryItemValue('query')
# url.queryItemValue('uid')
# url.queryItemValue('geometry')
def onUpdateSqlLayer(self):
l = self.iface.legendInterface().currentLayer()
if l.dataProvider().name() in ['postgres', 'spatialite', 'oracle']:
uri = QgsDataSourceURI(l.source())
if re.search('^\(SELECT .+ FROM .+\)$', uri.table(), re.S):
self.run()
self.dlg.runSqlLayerWindow(l)
# virtual has QUrl source
# url = QUrl(QUrl.fromPercentEncoding(l.source()))
# url.queryItemValue('query')
# url.queryItemValue('uid')
# url.queryItemValue('geometry')
def run(self): def run(self):
# keep opened only one instance # keep opened only one instance
if self.dlg is None: if self.dlg is None:

View File

@ -0,0 +1,530 @@
# -*- 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
- PG_Manager by Martin Dobias (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.QtCore import Qt, QObject, QSettings, QByteArray, SIGNAL, pyqtSignal
from PyQt4.QtGui import QDialog, QWidget, QAction, QKeySequence, \
QDialogButtonBox, QApplication, QCursor, QMessageBox, QClipboard, QInputDialog, QIcon, QStyledItemDelegate, QStandardItemModel, QStandardItem
from PyQt4.Qsci import QsciAPIs
from PyQt4.QtXml import QDomDocument
from qgis.core import QgsProject, QgsDataSourceURI
from .db_plugins import createDbPlugin
from .db_plugins.plugin import BaseError
from .db_plugins.postgis.plugin import PGDatabase
from .dlg_db_error import DlgDbError
from .dlg_query_builder import QueryBuilderDlg
try:
from qgis.gui import QgsCodeEditorSQL
except:
from .sqledit import SqlEdit
from qgis import gui
gui.QgsCodeEditorSQL = SqlEdit
from .ui.ui_DlgSqlLayerWindow import Ui_DbManagerDlgSqlLayerWindow as Ui_Dialog
import re
class DlgSqlLayerWindow(QWidget, Ui_Dialog):
nameChanged = pyqtSignal(str)
def __init__(self, iface, layer, parent=None):
QWidget.__init__(self, parent)
self.iface = iface
self.layer = layer
uri = QgsDataSourceURI(layer.source())
dbplugin = None
db = None
if layer.dataProvider().name() == 'postgres':
dbplugin = createDbPlugin('postgis', 'postgres')
elif layer.dataProvider().name() == 'spatialite':
dbplugin = createDbPlugin('spatialite', 'spatialite')
elif layer.dataProvider().name() == 'oracle':
dbplugin = createDbPlugin('oracle', 'oracle')
elif layer.dataProvider().name() == 'virtual':
dbplugin = createDbPlugin('vlayers', 'virtual')
if dbplugin:
dbplugin.connectToUri(uri)
db = dbplugin.db
self.dbplugin = dbplugin
self.db = db
self.filter = ""
self.allowMultiColumnPk = isinstance(db, PGDatabase) # at the moment only PostgreSQL allows a primary key to span multiple columns, spatialite doesn't
self.aliasSubQuery = isinstance(db, PGDatabase) # only PostgreSQL requires subqueries to be aliases
self.setupUi(self)
self.setWindowTitle(
u"%s - %s [%s]" % (self.windowTitle(), db.connection().connectionName(), db.connection().typeNameString()))
self.defaultLayerName = 'QueryLayer'
if self.allowMultiColumnPk:
self.uniqueColumnCheck.setText(self.trUtf8("Column(s) with unique values"))
else:
self.uniqueColumnCheck.setText(self.trUtf8("Column with unique values"))
self.editSql.setFocus()
self.editSql.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.editSql.setMarginVisible(True)
self.initCompleter()
# allow copying results
copyAction = QAction("copy", self)
self.viewResult.addAction(copyAction)
copyAction.setShortcuts(QKeySequence.Copy)
copyAction.triggered.connect(self.copySelectedResults)
self.btnExecute.clicked.connect(self.executeSql)
self.btnSetFilter.clicked.connect(self.setFilter)
self.btnClear.clicked.connect(self.clearSql)
self.presetStore.clicked.connect(self.storePreset)
self.presetDelete.clicked.connect(self.deletePreset)
self.presetCombo.activated[str].connect(self.loadPreset)
self.presetCombo.activated[str].connect(self.presetName.setText)
self.updatePresetsCombobox()
self.geomCombo.setEditable(True)
self.geomCombo.lineEdit().setReadOnly(True)
self.uniqueCombo.setEditable(True)
self.uniqueCombo.lineEdit().setReadOnly(True)
self.uniqueModel = QStandardItemModel(self.uniqueCombo)
self.uniqueCombo.setModel(self.uniqueModel)
if self.allowMultiColumnPk:
self.uniqueCombo.setItemDelegate(QStyledItemDelegate())
self.uniqueModel.itemChanged.connect(self.uniqueChanged) # react to the (un)checking of an item
self.uniqueCombo.lineEdit().textChanged.connect(self.uniqueTextChanged) # there are other events that change the displayed text and some of them can not be caught directly
self.layerTypeWidget.hide() # show if load as raster is supported
#self.loadLayerBtn.clicked.connect(self.loadSqlLayer)
self.updateLayerBtn.clicked.connect(self.updateSqlLayer)
self.getColumnsBtn.clicked.connect(self.fillColumnCombos)
self.queryBuilderFirst = True
self.queryBuilderBtn.setIcon(QIcon(":/db_manager/icons/sql.gif"))
self.queryBuilderBtn.clicked.connect(self.displayQueryBuilder)
self.presetName.textChanged.connect(self.nameChanged)
# Update from layer
# Fisrtly the SQL from QgsDataSourceURI table
sql = uri.table()
if uri.keyColumn() == '_uid_':
match = re.search('^\(SELECT .+ AS _uid_,\* FROM \((.*)\) AS _subq_.+_\s*\)$', sql, re.S)
if match:
sql = match.group(1)
else:
match = re.search('^\((SELECT .+ FROM .+)\)$', sql, re.S)
if match:
sql = match.group(1)
self.editSql.setText(sql)
self.executeSql()
# Then the columns
self.geomCombo.setCurrentIndex(self.geomCombo.findText(uri.geometryColumn(), Qt.MatchExactly))
if uri.keyColumn() != '_uid_':
self.uniqueColumnCheck.setCheckState(Qt.Checked)
if self.allowMultiColumnPk:
itemsData = uri.keyColumn().split(',')
for item in self.uniqueModel.findItems("*", Qt.MatchWildcard):
if item.data() in itemsData:
item.setCheckState(Qt.Checked)
else:
keyColumn = uri.keyColumn()
for item in self.uniqueModel.findItems("*", Qt.MatchWildcard):
if item.data() == keyColumn:
self.uniqueCombo.setCurrentIndex(self.uniqueModel.indexFromItem(item).row())
# Finally layer name, filter and selectAtId
self.layerNameEdit.setText(layer.name())
self.filter = uri.sql()
if uri.selectAtIdDisabled():
self.avoidSelectById.setCheckState(Qt.Checked)
def updatePresetsCombobox(self):
self.presetCombo.clear()
names = []
entries = QgsProject.instance().subkeyList('DBManager', 'savedQueries')
for entry in entries:
name = QgsProject.instance().readEntry('DBManager', 'savedQueries/' + entry + '/name')[0]
names.append(name)
for name in sorted(names):
self.presetCombo.addItem(name)
self.presetCombo.setCurrentIndex(-1)
def storePreset(self):
query = self._getSqlQuery()
if query == "":
return
name = self.presetName.text()
QgsProject.instance().writeEntry('DBManager', 'savedQueries/q' + unicode(name.__hash__()) + '/name', name)
QgsProject.instance().writeEntry('DBManager', 'savedQueries/q' + unicode(name.__hash__()) + '/query', query)
index = self.presetCombo.findText(name)
if index == -1:
self.presetCombo.addItem(name)
self.presetCombo.setCurrentIndex(self.presetCombo.count() - 1)
else:
self.presetCombo.setCurrentIndex(index)
def deletePreset(self):
name = self.presetCombo.currentText()
QgsProject.instance().removeEntry('DBManager', 'savedQueries/q' + unicode(name.__hash__()))
self.presetCombo.removeItem(self.presetCombo.findText(name))
self.presetCombo.setCurrentIndex(-1)
def loadPreset(self, name):
query = QgsProject.instance().readEntry('DBManager', 'savedQueries/q' + unicode(name.__hash__()) + '/query')[0]
name = QgsProject.instance().readEntry('DBManager', 'savedQueries/q' + unicode(name.__hash__()) + '/name')[0]
self.editSql.setText(query)
def clearSql(self):
self.editSql.clear()
self.editSql.setFocus()
self.filter = ""
def executeSql(self):
sql = self._getSqlQuery()
if sql == "":
return
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
# delete the old model
old_model = self.viewResult.model()
self.viewResult.setModel(None)
if old_model:
old_model.deleteLater()
cols = []
quotedCols = []
try:
# set the new model
model = self.db.sqlResultModel(sql, self)
self.viewResult.setModel(model)
self.lblResult.setText(self.tr("%d rows, %.1f seconds") % (model.affectedRows(), model.secs()))
cols = self.viewResult.model().columnNames()
for col in cols:
quotedCols.append(self.db.connector.quoteId(col))
except BaseError as e:
QApplication.restoreOverrideCursor()
DlgDbError.showError(e, self)
self.uniqueModel.clear()
self.geomCombo.clear()
return
self.setColumnCombos(cols, quotedCols)
self.update()
QApplication.restoreOverrideCursor()
def _getSqlLayer(self, _filter):
hasUniqueField = self.uniqueColumnCheck.checkState() == Qt.Checked
if hasUniqueField:
if self.allowMultiColumnPk:
checkedCols = []
for item in self.uniqueModel.findItems("*", Qt.MatchWildcard):
if item.checkState() == Qt.Checked:
checkedCols.append(item.data())
uniqueFieldName = ",".join(checkedCols)
elif self.uniqueCombo.currentIndex() >= 0:
uniqueFieldName = self.uniqueModel.item(self.uniqueCombo.currentIndex()).data()
else:
uniqueFieldName = None
else:
uniqueFieldName = None
hasGeomCol = self.hasGeometryCol.checkState() == Qt.Checked
if hasGeomCol:
geomFieldName = self.geomCombo.currentText()
else:
geomFieldName = None
query = self._getSqlQuery()
if query == "":
return None
# remove a trailing ';' from query if present
if query.strip().endswith(';'):
query = query.strip()[:-1]
from qgis.core import QgsMapLayer, QgsMapLayerRegistry
layerType = QgsMapLayer.VectorLayer if self.vectorRadio.isChecked() else QgsMapLayer.RasterLayer
# get a new layer name
names = []
for layer in QgsMapLayerRegistry.instance().mapLayers().values():
names.append(layer.name())
layerName = self.layerNameEdit.text()
if layerName == "":
layerName = self.defaultLayerName
newLayerName = layerName
index = 1
while newLayerName in names:
index += 1
newLayerName = u"%s_%d" % (layerName, index)
# create the layer
layer = self.db.toSqlLayer(query, geomFieldName, uniqueFieldName, newLayerName, layerType,
self.avoidSelectById.isChecked(), _filter)
if layer.isValid():
return layer
else:
return None
def loadSqlLayer(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
try:
layer = self._getSqlLayer(self.filter)
if layer == None:
return
from qgis.core import QgsMapLayerRegistry
QgsMapLayerRegistry.instance().addMapLayers([layer], True)
finally:
QApplication.restoreOverrideCursor()
def updateSqlLayer(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
try:
layer = self._getSqlLayer(self.filter)
if layer == None:
return
#self.layer.dataProvider().setDataSourceUri(layer.dataProvider().dataSourceUri())
#self.layer.dataProvider().reloadData()
XMLDocument = QDomDocument("style")
XMLMapLayers = XMLDocument.createElement("maplayers")
XMLMapLayer = XMLDocument.createElement("maplayer")
self.layer.writeLayerXML(XMLMapLayer, XMLDocument)
XMLMapLayer.firstChildElement("datasource").firstChild().setNodeValue(layer.source())
XMLMapLayers.appendChild(XMLMapLayer)
XMLDocument.appendChild(XMLMapLayers)
self.layer.readLayerXML(XMLMapLayer)
self.layer.reload()
self.iface.actionDraw().trigger()
self.iface.mapCanvas().refresh()
self.iface.legendInterface().refreshLayerSymbology(layer)
finally:
QApplication.restoreOverrideCursor()
def fillColumnCombos(self):
query = self._getSqlQuery()
if query == "":
return
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
# remove a trailing ';' from query if present
if query.strip().endswith(';'):
query = query.strip()[:-1]
# get all the columns
cols = []
quotedCols = []
connector = self.db.connector
if self.aliasSubQuery:
# get a new alias
aliasIndex = 0
while True:
alias = "_subQuery__%d" % aliasIndex
escaped = re.compile('\\b("?)' + re.escape(alias) + '\\1\\b')
if not escaped.search(query):
break
aliasIndex += 1
sql = u"SELECT * FROM (%s\n) AS %s LIMIT 0" % (unicode(query), connector.quoteId(alias))
else:
sql = u"SELECT * FROM (%s\n) WHERE 1=0" % unicode(query)
c = None
try:
c = connector._execute(None, sql)
cols = connector._get_cursor_columns(c)
for col in cols:
quotedCols.append(connector.quoteId(col))
except BaseError as e:
QApplication.restoreOverrideCursor()
DlgDbError.showError(e, self)
self.uniqueModel.clear()
self.geomCombo.clear()
return
finally:
if c:
c.close()
del c
self.setColumnCombos(cols, quotedCols)
QApplication.restoreOverrideCursor()
def setColumnCombos(self, cols, quotedCols):
# get sensible default columns. do this before sorting in case there's hints in the column order (eg, id is more likely to be first)
try:
defaultGeomCol = next(col for col in cols if col in ['geom', 'geometry', 'the_geom', 'way'])
except:
defaultGeomCol = None
try:
defaultUniqueCol = [col for col in cols if 'id' in col][0]
except:
defaultUniqueCol = None
colNames = sorted(zip(cols, quotedCols))
newItems = []
uniqueIsFilled = False
for (col, quotedCol) in colNames:
item = QStandardItem(col)
item.setData(quotedCol)
item.setEnabled(True)
item.setCheckable(self.allowMultiColumnPk)
item.setSelectable(not self.allowMultiColumnPk)
if self.allowMultiColumnPk:
matchingItems = self.uniqueModel.findItems(col)
if matchingItems:
item.setCheckState(matchingItems[0].checkState())
uniqueIsFilled = uniqueIsFilled or matchingItems[0].checkState() == Qt.Checked
else:
item.setCheckState(Qt.Unchecked)
newItems.append(item)
if self.allowMultiColumnPk:
self.uniqueModel.clear()
self.uniqueModel.appendColumn(newItems)
self.uniqueChanged()
else:
previousUniqueColumn = self.uniqueCombo.currentText()
self.uniqueModel.clear()
self.uniqueModel.appendColumn(newItems)
if self.uniqueModel.findItems(previousUniqueColumn):
self.uniqueCombo.setEditText(previousUniqueColumn)
uniqueIsFilled = True
oldGeometryColumn = self.geomCombo.currentText()
self.geomCombo.clear()
self.geomCombo.addItems(cols)
self.geomCombo.setCurrentIndex(self.geomCombo.findText(oldGeometryColumn, Qt.MatchExactly))
# set sensible default columns if the columns are not already set
try:
if self.geomCombo.currentIndex() == -1:
self.geomCombo.setCurrentIndex(cols.index(defaultGeomCol))
except:
pass
items = self.uniqueModel.findItems(defaultUniqueCol)
if items and not uniqueIsFilled:
if self.allowMultiColumnPk:
items[0].setCheckState(Qt.Checked)
else:
self.uniqueCombo.setEditText(defaultUniqueCol)
try:
pass
except:
pass
def copySelectedResults(self):
if len(self.viewResult.selectedIndexes()) <= 0:
return
model = self.viewResult.model()
# convert to string using tab as separator
text = model.headerToString("\t")
for idx in self.viewResult.selectionModel().selectedRows():
text += "\n" + model.rowToString(idx.row(), "\t")
QApplication.clipboard().setText(text, QClipboard.Selection)
QApplication.clipboard().setText(text, QClipboard.Clipboard)
def initCompleter(self):
dictionary = None
if self.db:
dictionary = self.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
api = QsciAPIs(self.editSql.lexer())
for word in wordlist:
api.add(word)
api.prepare()
self.editSql.lexer().setAPIs(api)
def displayQueryBuilder(self):
dlg = QueryBuilderDlg(self.iface, self.db, self, reset=self.queryBuilderFirst)
self.queryBuilderFirst = False
r = dlg.exec_()
if r == QDialog.Accepted:
self.editSql.setText(dlg.query)
def _getSqlQuery(self):
sql = self.editSql.selectedText()
if len(sql) == 0:
sql = self.editSql.text()
return sql
def uniqueChanged(self):
# when an item is (un)checked, simply trigger an update of the combobox text
self.uniqueTextChanged(None)
def uniqueTextChanged(self, text):
# Whenever there is new text displayed in the combobox, check if it is the correct one and if not, display the correct one.
checkedItems = []
for item in self.uniqueModel.findItems("*", Qt.MatchWildcard):
if item.checkState() == Qt.Checked:
checkedItems.append(item.text())
label = ", ".join(checkedItems)
if text != label:
self.uniqueCombo.setEditText(label)
def setFilter(self):
from qgis.gui import QgsQueryBuilder
layer = self._getSqlLayer("")
if not layer:
return
dlg = QgsQueryBuilder(layer)
dlg.setSql(self.filter)
if dlg.exec_():
self.filter = dlg.sql()
layer.deleteLater()

View File

@ -0,0 +1,407 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DbManagerDlgSqlLayerWindow</class>
<widget class="QDialog" name="DbManagerDlgSqlLayerWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>662</width>
<height>525</height>
</rect>
</property>
<property name="windowTitle">
<string>SQL window</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="avoidSelectById">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Avoid selecting feature by id. Sometimes - especially when running expensive queries/views - fetching the data sequentially instead of fetching features by id can be much quicker.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Avoid selecting by feature id</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="updateLayerBtn">
<property name="text">
<string>Update</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="queryBuilderBtn">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Saved query:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="presetCombo"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="presetName">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="presetStore">
<property name="text">
<string>Store</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="presetDelete">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QgsCodeEditorSQL" name="editSql"/>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QPushButton" name="btnExecute">
<property name="text">
<string>&amp;Execute (F5)</string>
</property>
<property name="shortcut">
<string>F5</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblResult">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnClear">
<property name="text">
<string>&amp;Clear</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="viewResult">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>3</verstretch>
</sizepolicy>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QCheckBox" name="uniqueColumnCheck">
<property name="text">
<string>Column(s) with
unique values</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="uniqueCombo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::NoInsert</enum>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="hasGeometryCol">
<property name="text">
<string>Geometry column</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="geomCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::NoInsert</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="getColumnsBtn">
<property name="text">
<string>Retrieve
columns</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Layer name (prefix)</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="layerNameEdit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string notr="true"/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="layerTypeWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Type</string>
</property>
<property name="indent">
<number>40</number>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="vectorRadio">
<property name="text">
<string>Vector</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rasterRadio">
<property name="text">
<string>Raster</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSetFilter">
<property name="text">
<string>Set filter</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsCodeEditorSQL</class>
<extends>QTextEdit</extends>
<header>qgis.gui</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>btnExecute</tabstop>
<tabstop>btnClear</tabstop>
<tabstop>viewResult</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>hasGeometryCol</sender>
<signal>toggled(bool)</signal>
<receiver>geomCombo</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>379</x>
<y>385</y>
</hint>
<hint type="destinationlabel">
<x>525</x>
<y>385</y>
</hint>
</hints>
</connection>
<connection>
<sender>uniqueColumnCheck</sender>
<signal>toggled(bool)</signal>
<receiver>uniqueCombo</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>109</x>
<y>385</y>
</hint>
<hint type="destinationlabel">
<x>274</x>
<y>385</y>
</hint>
</hints>
</connection>
</connections>
</ui>