2013-08-20 18:56:03 +02:00

594 lines
16 KiB
Python

# -*- 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
***************************************************************************/
/***************************************************************************
* *
* 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 *
from PyQt4.QtGui import *
from .db_plugins import supportedDbTypes, createDbPlugin
from .db_plugins.plugin import BaseError, DbError, Table
from .dlg_db_error import DlgDbError
import qgis.core
try:
from . import resources_rc
except ImportError:
pass
class TreeItem(QObject):
def __init__(self, data, parent=None):
QObject.__init__(self, parent)
self.populated = False
self.itemData = data
self.childItems = []
if parent:
parent.appendChild(self)
def childRemoved(self, child):
self.itemChanged()
def itemChanged(self):
self.emit( SIGNAL("itemChanged"), self )
def itemRemoved(self):
self.emit( SIGNAL("itemRemoved"), self )
def populate(self):
self.populated = True
return True
def getItemData(self):
return self.itemData
def appendChild(self, child):
self.childItems.append(child)
self.connect(child, SIGNAL("itemRemoved"), self.childRemoved)
def child(self, row):
return self.childItems[row]
def removeChild(self, row):
if row >= 0 and row < len(self.childItems):
self.childItems[row].itemData.deleteLater()
self.disconnect(self.childItems[row], SIGNAL("itemRemoved"), self.childRemoved)
del self.childItems[row]
def childCount(self):
return len(self.childItems)
def columnCount(self):
return 1
def row(self):
if self.parent():
for row, item in enumerate(self.parent().childItems):
if item is self:
return row
return 0
def data(self, column):
return "" if column == 0 else None
def icon(self):
return None
def path(self):
pathList = []
if self.parent():
pathList.extend( self.parent().path() )
pathList.append( self.data(0) )
return pathList
class PluginItem(TreeItem):
def __init__(self, dbplugin, parent=None):
TreeItem.__init__(self, dbplugin, parent)
def populate(self):
if self.populated:
return True
# create items for connections
for c in self.getItemData().connections():
ConnectionItem(c, self)
self.populated = True
return True
def data(self, column):
if column == 0:
return self.getItemData().typeNameString()
return None
def icon(self):
return self.getItemData().icon()
def path(self):
return [ self.getItemData().typeName() ]
class ConnectionItem(TreeItem):
def __init__(self, connection, parent=None):
TreeItem.__init__(self, connection, parent)
# load (shared) icon with first instance of table item
if not hasattr(ConnectionItem, 'connectedIcon'):
ConnectionItem.connectedIcon = QIcon(":/db_manager/icons/plugged.png")
ConnectionItem.disconnectedIcon = QIcon(":/db_manager/icons/unplugged.png")
def data(self, column):
if column == 0:
return self.getItemData().connectionName()
return None
def populate(self):
if self.populated:
return True
connection = self.getItemData()
if connection.database() == None:
# connect to database
try:
if not connection.connect():
return False
except BaseError, e:
QMessageBox.warning( None, self.tr("Unable to connect"), unicode(e) )
return False
database = connection.database()
self.connect(database, SIGNAL("changed"), self.itemChanged)
self.connect(database, SIGNAL("deleted"), self.itemRemoved)
schemas = database.schemas()
if schemas != None:
for s in schemas:
SchemaItem(s, self)
else:
tables = database.tables()
for t in tables:
TableItem(t, self)
self.populated = True
return True
def isConnected(self):
return self.getItemData().database() != None
#def icon(self):
# return self.connectedIcon if self.isConnected() else self.disconnectedIcon
class SchemaItem(TreeItem):
def __init__(self, schema, parent):
TreeItem.__init__(self, schema, parent)
self.connect(schema, SIGNAL("changed"), self.itemChanged)
self.connect(schema, SIGNAL("deleted"), self.itemRemoved)
# load (shared) icon with first instance of schema item
if not hasattr(SchemaItem, 'schemaIcon'):
SchemaItem.schemaIcon = QIcon(":/db_manager/icons/namespace.png")
def data(self, column):
if column == 0:
return self.getItemData().name
return None
def icon(self):
return self.schemaIcon
def populate(self):
if self.populated:
return True
for t in self.getItemData().tables():
TableItem(t, self)
self.populated = True
return True
class TableItem(TreeItem):
def __init__(self, table, parent):
TreeItem.__init__(self, table, parent)
self.connect(table, SIGNAL("changed"), self.itemChanged)
self.connect(table, SIGNAL("deleted"), self.itemRemoved)
self.populate()
# load (shared) icon with first instance of table item
if not hasattr(TableItem, 'tableIcon'):
TableItem.tableIcon = QIcon(":/db_manager/icons/table.png")
TableItem.viewIcon = QIcon(":/db_manager/icons/view.png")
TableItem.layerPointIcon = QIcon(":/db_manager/icons/layer_point.png")
TableItem.layerLineIcon = QIcon(":/db_manager/icons/layer_line.png")
TableItem.layerPolygonIcon = QIcon(":/db_manager/icons/layer_polygon.png")
TableItem.layerRasterIcon = QIcon(":/db_manager/icons/layer_raster.png")
TableItem.layerUnknownIcon = QIcon(":/db_manager/icons/layer_unknown.png")
def data(self, column):
if column == 0:
return self.getItemData().name
elif column == 1:
if self.getItemData().type == Table.VectorType:
return self.getItemData().geomType
return None
def icon(self):
if self.getItemData().type == Table.VectorType:
geom_type = self.getItemData().geomType
if geom_type is not None:
if geom_type.find('POINT') != -1:
return self.layerPointIcon
elif geom_type.find('LINESTRING') != -1:
return self.layerLineIcon
elif geom_type.find('POLYGON') != -1:
return self.layerPolygonIcon
return self.layerUnknownIcon
elif self.getItemData().type == Table.RasterType:
return self.layerRasterIcon
if self.getItemData().isView:
return self.viewIcon
return self.tableIcon
def path(self):
pathList = []
if self.parent():
pathList.extend( self.parent().path() )
if self.getItemData().type == Table.VectorType:
pathList.append( "%s::%s" % ( self.data(0), self.getItemData().geomColumn ) )
else:
pathList.append( self.data(0) )
return pathList
class DBModel(QAbstractItemModel):
def __init__(self, parent=None):
QAbstractItemModel.__init__(self, parent)
self.treeView = parent
self.header = [self.tr('Databases')]
self.isImportVectorAvail = hasattr(qgis.core, 'QgsVectorLayerImport')
if self.isImportVectorAvail:
self.connect(self, SIGNAL("importVector"), self.importVector)
self.rootItem = TreeItem(None, None)
for dbtype in supportedDbTypes():
dbpluginclass = createDbPlugin( dbtype )
PluginItem( dbpluginclass, self.rootItem )
def refreshItem(self, item):
if isinstance(item, TreeItem):
# find the index for the tree item using the path
index = self._rPath2Index( item.path() )
else:
# find the index for the db item
index = self._rItem2Index(item)
if index.isValid():
self._refreshIndex(index)
else:
qDebug( "invalid index" )
def _rItem2Index(self, item, parent=None):
if parent == None:
parent = QModelIndex()
if item == self.getItem(parent):
return parent
if not parent.isValid() or parent.internalPointer().populated:
for i in range( self.rowCount(parent) ):
index = self.index(i, 0, parent)
index = self._rItem2Index(item, index)
if index.isValid():
return index
return QModelIndex()
def _rPath2Index(self, path, parent=None, n=0):
if parent == None:
parent = QModelIndex()
if path == None or len(path) == 0:
return parent
for i in range( self.rowCount(parent) ):
index = self.index(i, 0, parent)
if self._getPath(index)[n] == path[0]:
return self._rPath2Index( path[1:], index, n+1 )
return parent
def getItem(self, index):
if not index.isValid():
return None
return index.internalPointer().getItemData()
def _getPath(self, index):
if not index.isValid():
return None
return index.internalPointer().path()
def columnCount(self, parent):
return 1
def data(self, index, role):
if not index.isValid():
return None
if role == Qt.DecorationRole and index.column() == 0:
icon = index.internalPointer().icon()
if icon: return icon
if role != Qt.DisplayRole and role != Qt.EditRole:
return None
retval = index.internalPointer().data(index.column())
return retval
def flags(self, index):
if not index.isValid():
return Qt.NoItemFlags
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if index.column() == 0:
item = index.internalPointer()
if isinstance(item, SchemaItem) or isinstance(item, TableItem):
flags |= Qt.ItemIsEditable
if isinstance(item, TableItem):
flags |= Qt.ItemIsDragEnabled
if self.isImportVectorAvail: # allow to import a vector layer
if isinstance(item, ConnectionItem) and item.populated:
flags |= Qt.ItemIsDropEnabled
if isinstance(item, SchemaItem) or isinstance(item, TableItem):
flags |= Qt.ItemIsDropEnabled
return flags
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(self.header):
return self.header[section]
return None
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QModelIndex()
parentItem = parent.internalPointer() if parent.isValid() else self.rootItem
childItem = parentItem.child(row)
if childItem:
return self.createIndex(row, column, childItem)
return QModelIndex()
def parent(self, index):
if not index.isValid():
return QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parent()
if parentItem == self.rootItem:
return QModelIndex()
return self.createIndex(parentItem.row(), 0, parentItem)
def rowCount(self, parent):
parentItem = parent.internalPointer() if parent.isValid() else self.rootItem
if not parentItem.populated:
self._refreshIndex( parent, True )
return parentItem.childCount()
def hasChildren(self, parent):
parentItem = parent.internalPointer() if parent.isValid() else self.rootItem
return parentItem.childCount() > 0 or not parentItem.populated
def setData(self, index, value, role):
if role != Qt.EditRole or index.column() != 0:
return False
item = index.internalPointer()
new_value = unicode(value)
if isinstance(item, SchemaItem) or isinstance(item, TableItem):
obj = item.getItemData()
# rename schema or table or view
if new_value == obj.name:
return False
QApplication.setOverrideCursor(Qt.WaitCursor)
try:
obj.rename(new_value)
self._onDataChanged(index)
except BaseError, e:
DlgDbError.showError(e, self.treeView)
return False
finally:
QApplication.restoreOverrideCursor()
return True
return False
def removeRows(self, row, count, parent):
self.beginRemoveRows(parent, row, count+row-1)
item = parent.internalPointer()
for i in range(row, count+row):
item.removeChild(row)
self.endRemoveRows()
def _refreshIndex(self, index, force=False):
QApplication.setOverrideCursor(Qt.WaitCursor)
try:
item = index.internalPointer() if index.isValid() else self.rootItem
prevPopulated = item.populated
if prevPopulated:
self.removeRows(0, self.rowCount(index), index)
item.populated = False
if prevPopulated or force:
if item.populate():
for child in item.childItems:
self.connect(child, SIGNAL("itemChanged"), self.refreshItem)
self._onDataChanged( index )
else:
self.emit( SIGNAL("notPopulated"), index )
except BaseError, e:
item.populated = False
return
finally:
QApplication.restoreOverrideCursor()
def _onDataChanged(self, indexFrom, indexTo=None):
if indexTo == None: indexTo = indexFrom
self.emit( SIGNAL('dataChanged(const QModelIndex &, const QModelIndex &)'), indexFrom, indexTo)
QGIS_URI_MIME = "application/x-vnd.qgis.qgis.uri"
def mimeTypes(self):
return ["text/uri-list", self.QGIS_URI_MIME]
def mimeData(self, indexes):
mimeData = QMimeData()
encodedData = QByteArray()
stream = QDataStream(encodedData, QIODevice.WriteOnly)
for index in indexes:
if not index.isValid():
continue
if not isinstance(index.internalPointer(), TableItem):
continue
table = self.getItem( index )
stream.writeQString( table.mimeUri() )
mimeData.setData(self.QGIS_URI_MIME, encodedData)
return mimeData
def dropMimeData(self, data, action, row, column, parent):
if action == Qt.IgnoreAction:
return True
if not self.isImportVectorAvail:
return False
added = 0
if data.hasUrls():
for u in data.urls():
filename = u.toLocalFile()
if filename == "":
continue
if qgis.core.QgsRasterLayer.isValidRasterFileName( filename ):
layerType = 'raster'
providerKey = 'gdal'
else:
layerType = 'vector'
providerKey = 'ogr'
layerName = QFileInfo(filename).completeBaseName()
if self.importLayer( layerType, providerKey, layerName, filename, parent ):
added += 1
if data.hasFormat(self.QGIS_URI_MIME):
for uri in qgis.core.QgsMimeDataUtils.decodeUriList( data ):
if self.importLayer( uri.layerType, uri.providerKey, uri.name, uri.uri, parent ):
added += 1
return added > 0
def importLayer(self, layerType, providerKey, layerName, uriString, parent):
if not self.isImportVectorAvail:
return False
if layerType == 'raster':
return False # not implemented yet
inLayer = qgis.core.QgsRasterLayer(uriString, layerName, providerKey)
else:
inLayer = qgis.core.QgsVectorLayer(uriString, layerName, providerKey)
if not inLayer.isValid():
# invalid layer
QMessageBox.warning(None, self.tr("Invalid layer"), self.tr("Unable to load the layer %s") % inLayer.name)
return False
# retrieve information about the new table's db and schema
outItem = parent.internalPointer()
outObj = outItem.getItemData()
outDb = outObj.database()
outSchema = None
if isinstance(outItem, SchemaItem):
outSchema = outObj
elif isinstance(outItem, TableItem):
outSchema = outObj.schema()
# toIndex will point to the parent item of the new table
toIndex = parent
if isinstance(toIndex.internalPointer(), TableItem):
toIndex = toIndex.parent()
if inLayer.type() == inLayer.VectorLayer:
# create the output uri
schema = outSchema.name if outDb.schemas() != None and outSchema != None else ""
pkCol = geomCol = ""
# default pk and geom field name value
if providerKey in ['postgres', 'spatialite']:
inUri = qgis.core.QgsDataSourceURI( inLayer.source() )
pkCol = inUri.keyColumn()
geomCol = inUri.geometryColumn()
outUri = outDb.uri()
outUri.setDataSource( schema, layerName, geomCol, "", pkCol )
self.emit( SIGNAL("importVector"), inLayer, outDb, outUri, toIndex )
return True
return False
def importVector(self, inLayer, outDb, outUri, parent):
if not self.isImportVectorAvail:
return False
try:
from dlg_import_vector import DlgImportVector
dlg = DlgImportVector(inLayer, outDb, outUri)
QApplication.restoreOverrideCursor()
if dlg.exec_():
self._refreshIndex( parent )
finally:
inLayer.deleteLater()