Source code for ddui

# -*- coding: utf-8 -*-
"""
ddui
--------
Classes that make up or steer the DataDrivenUI
"""
"""
/***************************************************************************
 DataDrivenInputMask
                                 A QGIS plugin
 Applies a data-driven input mask to any PostGIS-Layer
                              -------------------
        begin                : 2012-06-21
        copyright            : (C) 2012 by Bernhard Ströbl / Kommunale Immobilien Jena
        email                : bernhard.stroebl@jena.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
# Import the PyQt and QGIS libraries
from PyQt4 import QtCore,  QtGui,  QtSql
from qgis.core import *
from dderror import DdError,  DbError
from ddattribute import *
from dddialog import DdDialog,  DdSearchDialog
import ddtools
import xml.etree.ElementTree as ET
import icons_rc

[docs]class DdFormHelper: def __init__(self, thisDialog, layerId, featureId): app = QgsApplication.instance() ddManager = app.ddManager lIface = ddManager.iface.legendInterface() for aLayer in lIface.layers(): if aLayer.id() == layerId: #QtGui.QMessageBox.information(None, "", aLayer.name()) #thisDialog.hide() feat = QgsFeature() featureFound = aLayer.getFeatures(QgsFeatureRequest().setFilterFid(featureId).setFlags(QgsFeatureRequest.NoGeometry)).nextFeature(feat) if featureFound: result = ddManager.showFeatureForm(aLayer, feat) #QtGui.QMessageBox.information(None, "", str(thisDialog)) thisDialog.setVisible(False) if result == 1: thisDialog.accept() else: thisDialog.reject() break
#self.prntWidget = self.thisDialog.parentWidget()
[docs]def ddFormInit1(dialog, layerId, featureId): dialog.setProperty("helper", DdFormHelper(dialog, layerId, featureId))
[docs]def ddFormInit(dialog, layerId, featureId): app = QgsApplication.instance() ddManager = app.ddManager lIface = ddManager.iface.legendInterface() for aLayer in lIface.layers(): if aLayer.id() == layerId: feat = QgsFeature() featureFound = aLayer.getFeatures(QgsFeatureRequest().setFilterFid(featureId).setFlags(QgsFeatureRequest.NoGeometry)).nextFeature(feat) if featureFound: try: layerValues = ddManager.ddLayers[aLayer.id()] except KeyError: ddManager.initLayer(aLayer, skip = [], labels = {}, fieldOrder = [], fieldGroups = {}, minMax = {}, noSearchFields = [], \ showParents = True, createAction = True, db = None, inputMask = True, searchMask = True, \ inputUi = None, searchUi = None, helpText = "") layerValues = ddManager.ddLayers[aLayer.id()] db = layerValues[1] ui = layerValues[2] dlg = DdDialog(ddManager, ui, aLayer, feat, db, dialog) dlg.show() result = dlg.exec_() if result == 1: layer.setModified()
[docs]class DdEventFilter(QtCore.QObject): '''Event filter class to be applied to DdLineEdit's input widgets throws a signal even when the input widget is not enabled''' doubleClicked = QtCore.pyqtSignal()
[docs] def eventFilter(self, watchedObject, event): typ = event.type() if typ == QtCore.QEvent.MouseButtonDblClick: self.doubleClicked.emit() return False # if you want to filter the event out, i.e. stop it being handled further, return true; otherwise return false.
[docs]class DataDrivenUi(object): '''Creates the DataDrivenUi When subclassing this class, you want to rewrite createUi and use DdManager's setUi method to apply your custom ui to the layer''' def __init__(self, iface): self.iface = iface def __str__(self): return "<ddui.DataDrivenUi>" def __debug(self, title, str): QgsMessageLog.logMessage(title + "\n" + str)
[docs] def configureLayer(self, ddTable, skip, labels, fieldOrder, fieldGroups, minMax, noSearchFields, db, createAction, helpText, fieldDisable): '''read configuration from db''' #check if config tables exist in db configOid = ddtools.getOid(DdTable(schemaName = "public", tableName = "dd_table"), db) if configOid != None: # read values for this table from config tables query = QtSql.QSqlQuery(db) sQuery = "SELECT COALESCE(\"table_help\", \'\'), \ \"table_action\", \ COALESCE(\"tab_alias\", \'\'), \ COALESCE(\"tab_tooltip\", \'\'), \ \"field_name\", \ COALESCE(\"field_alias\", \'\'), \ \"field_skip\", \ \"field_search\", \ \"field_min\", \ \"field_max\", \ COALESCE(\"field_enabled\",true)::varchar \ FROM \"public\".\"dd_table\" t \ LEFT JOIN \"public\".\"dd_tab\" tb ON t.id = tb.\"dd_table_id\" \ LEFT JOIN \"public\".\"dd_field\" f ON tb.id = f.\"dd_tab_id\" \ WHERE \"table_schema\" = :schema AND \"table_name\" = :table\ ORDER BY \"tab_order\", \"field_order\"" query.prepare(sQuery) query.bindValue(":schema", ddTable.schemaName) query.bindValue(":table", ddTable.tableName) query.exec_() if query.isActive(): lastTab = None firstDataSet = True while query.next(): if firstDataSet: helpText += query.value(0) firstDataSet = False createAction = query.value(1) tabAlias = query.value(2) tabTooltip = query.value(3) fieldName = query.value(4) fieldAlias = query.value(5) fieldSkip = query.value(6) fieldSearch = query.value(7) fieldMin = query.value(8) fieldMax = query.value(9) fieldEnable = query.value(10) if tabAlias != lastTab and not fieldSkip: if tabAlias != "": lastTab = tabAlias fieldGroups[fieldName] = [tabAlias, tabTooltip] fieldOrder.append(fieldName) if fieldAlias != "": labels[fieldName] = fieldAlias if fieldSkip: skip.append(fieldName) if not fieldSearch: noSearchFields.append(fieldName) if fieldMin != None or fieldMax != None: minMax[fieldName] = [fieldMin, fieldMax] if fieldEnable == "false": if fieldDisable.count(fieldName) == 0: fieldDisable.append(fieldName) else: DbError(query) query.finish() return [skip, labels, fieldOrder, fieldGroups, minMax, noSearchFields, createAction, helpText, fieldDisable]
def __createForms(self, thisTable, db, skip, labels, fieldOrder, fieldGroups, minMax, noSearchFields, showParents, showChildren, readConfigTables, createAction, fieldDisable): """create the forms (DdFom instances) shown in the tabs of the Dialog (DdDialog instance)""" ddForms = [] ddSearchForms = [] ddAttributes = self.getAttributes( thisTable, db, labels, minMax, fieldDisable = fieldDisable) # do not pass skip here otherwise pk fields might not be included for anAtt in ddAttributes: if anAtt.isPK: n2mAttributes = self.getN2mAttributes(db, thisTable, anAtt.name, anAtt.num, labels, showChildren, skip, fieldDisable) ddAttributes = ddAttributes + n2mAttributes #check if we need a QToolBox needsToolBox = (len(ddAttributes) > 5) unorderedAttributes = [] msg = "" # loop through the attributes and get one-line types (QLineEdit, QComboBox) first for anAttribute in ddAttributes: msg = msg + " " + anAttribute.name nextAtt = False #check if this attribute is supposed to be skipped for skipName in skip: if skipName == anAttribute.name: nextAtt = True break if nextAtt: continue # skip it if anAttribute.type == "text" or anAttribute.type == "n2m" or anAttribute.type == "table": needsToolBox = True unorderedAttributes.append(anAttribute) # create an ordered list of attributes if len(fieldOrder) > 0: orderedAttributes = [] for aFieldName in fieldOrder: counter = 0 while counter < len(unorderedAttributes): anAttribute = unorderedAttributes.pop() if aFieldName == anAttribute.name: orderedAttributes.append(anAttribute) break else: unorderedAttributes.insert(0, anAttribute) counter += 1 # put the rest in orderedAttributes.extend(unorderedAttributes) else: orderedAttributes = unorderedAttributes defaultFormWidget = DdFormWidget(thisTable, needsToolBox) defaultSearchFormWidget = DdFormWidget(thisTable, needsToolBox) useFieldGroups = (len(fieldGroups) > 0 and len(fieldOrder) > 0) if not useFieldGroups: # we only need one form ddFormWidget = defaultFormWidget ddSearchFormWidget = defaultSearchFormWidget else: ddFormWidget = None for anAttribute in orderedAttributes: addToSearch = True for key in fieldGroups.iterkeys(): if key == anAttribute.name: # we need a new form if ddFormWidget != None: # there is one active, add them to the lists ddForms.append(ddFormWidget) ddSearchForms.append(ddSearchFormWidget) tabTitle = fieldGroups[key][0] try: tabToolTip = fieldGroups[key][1] except: tabToolTip = "" aTable = DdTable(thisTable.oid, thisTable.schemaName, thisTable.tableName, tabToolTip, tabTitle) ddFormWidget = DdFormWidget(aTable, needsToolBox) ddSearchFormWidget = DdFormWidget(aTable, needsToolBox) break if anAttribute.type == "text": ddInputWidget = DdTextEdit(anAttribute) elif anAttribute.type == "n2m": if anAttribute.subType == "list": ddInputWidget = DdN2mListWidget(anAttribute) elif anAttribute.subType == "tree": ddInputWidget = DdN2mTreeWidget(anAttribute) elif anAttribute.type == "table": ddInputWidget = DdN2mTableWidget(anAttribute) #addToSearch = False else: # one line attributes if anAttribute.isFK: ddInputWidget = DdComboBox(anAttribute) else: if anAttribute.isTypeFloat(): if anAttribute.isArray: ddInputWidget = DdArrayTableWidgetDouble(anAttribute) else: ddInputWidget = DdLineEditDouble(anAttribute) elif anAttribute.isTypeInt(): if anAttribute.isArray: ddInputWidget = DdArrayTableWidgetInt(anAttribute) else: ddInputWidget = DdLineEditInt(anAttribute) else: if anAttribute.type == "bool": if anAttribute.isArray: ddInputWidget = DdArrayTableWidgetBool(anAttribute) else: ddInputWidget = DdLineEditBoolean(anAttribute) elif anAttribute.type == "date": if anAttribute.isArray: ddInputWidget = DdArrayTableWidgetDate(anAttribute) else: ddInputWidget = DdDateEdit(anAttribute) elif anAttribute.type == "geometry": ddInputWidget = DdLineEditGeometry(anAttribute) addToSearch = False else: if anAttribute.isArray: ddInputWidget = DdArrayTableWidget(anAttribute) else: if anAttribute.type == "varchar" and anAttribute.length > 256: ddInputWidget = DdTextEdit(anAttribute) else: ddInputWidget = DdLineEdit(anAttribute) if ddFormWidget == None: # fallback in case fieldOrder and fieldGroups do not match ddFormWidget = defaultFormWidget ddSearchFormWidget = defaultSearchFormWidget ddFormWidget.addInputWidget(ddInputWidget) if addToSearch: if len(noSearchFields) > 0: addToSearch = (noSearchFields.count(anAttribute.name) == 0) if addToSearch: ddSearchFormWidget.addInputWidget(ddInputWidget) ddForms.append(ddFormWidget) ddSearchForms.append(ddSearchFormWidget) if showParents: # do not show this table in the parent's form skip.append(thisTable.tableName) # go recursivly into thisTable's parents for aParent in self.getParents(thisTable, db): if readConfigTables: pSkip, pLabels, pFieldOrder, pFieldGroups, pMinMax, pNoSearchFields, pCreateAction, pHelpText, pFieldDisable = \ self.configureLayer(aParent, [], {}, [], {}, {}, [], db, createAction, "", []) if pSkip == []: pSkip = skip if pLabels == {}: pLabels = labels if pFieldOrder == []: pFieldOrder = fieldOrder if pFieldGroups == {}: pFieldGroups = fieldGroups if pMinMax == {}: pMinMax = minMax if pNoSearchFields == []: pNoSearchFields = noSearchFields if pFieldDisable == []: pFieldDisable = fieldDisable parentForms, parentSearchForms = self.__createForms(aParent, db, pSkip, pLabels, pFieldOrder, pFieldGroups, pMinMax, pNoSearchFields, showParents, False, readConfigTables, pCreateAction, pFieldDisable) ddForms = ddForms + parentForms ddSearchForms = ddSearchForms + parentSearchForms return [ddForms, ddSearchForms]
[docs] def createUi(self, thisTable, db, skip = [], labels = {}, fieldOrder = [], fieldGroups = {}, minMax = {}, \ noSearchFields = [], showParents = True, showChildren = True, inputMask = True, searchMask = True, \ helpText = "", createAction = True, readConfigTables = False, fieldDisable = []): '''creates default uis for this table (DdTable instance) showChildren [Boolean]: show tabs for 1-to-1 relations (children) see ddmanager.initLayer for other parameters ''' if readConfigTables: skip, labels, fieldOrder, fieldGroups, minMax, noSearchFields, \ createAction, helpText, fieldDisable = self.configureLayer( \ thisTable, skip, labels, fieldOrder, fieldGroups, minMax, \ noSearchFields, db, createAction, \ helpText, fieldDisable) forms, searchForms = self.__createForms( thisTable, db, skip, labels, fieldOrder, fieldGroups, minMax, noSearchFields, showParents, showChildren, readConfigTables, createAction, fieldDisable) if inputMask: ui = DdDialogWidget() ui.setHelpText(helpText) for ddFormWidget in forms: ui.addFormWidget(ddFormWidget) else: ui = None if searchMask: searchUi = DdDialogWidget() for ddFormWidget in searchForms: searchUi.addFormWidget(ddFormWidget) else: searchUi = None return [ui, searchUi]
[docs] def getParent(self, thisTable, db): ''' deprecated''' #Problem: eine Tabelle kann mehrere Eltern haben query = QtSql.QSqlQuery(db) sQuery = "SELECT c.oid, n.nspname, c.relname \ FROM pg_inherits i \ JOIN pg_class c ON i.inhparent = c.oid \ JOIN pg_namespace n ON c.relnamespace = n.oid \ WHERE i.inhrelid = :oid" query.prepare(sQuery) query.bindValue(":oid", thisTable.oid) query.exec_() parentTable = DdTable() if query.isActive(): if query.size() == 0: query.finish() else: while query.next(): oid = query.value(0) schema = query.value(1) table = query.value(2) parentTable.oid = oid parentTable.schemaName = schema parentTable.tableName = table break query.finish() else: DbError(query) return parentTable
[docs] def getParents(self, thisTable, db): ''' query the DB to get a table's parents if any A table has a parent if its primary key is defined on one field only and is at the same time a foreign key to another table's primary key. Thus there is a 1:1 relationship between the two.''' query = QtSql.QSqlQuery(db) sQuery = "SELECT \ c.oid, ns.nspname, c.relname, COALESCE(d.description,'') \ FROM pg_attribute att \ JOIN (SELECT * FROM pg_constraint WHERE contype = 'f') fcon ON att.attrelid = fcon.conrelid AND att.attnum = ANY (fcon.conkey) \ JOIN (SELECT * FROM pg_constraint WHERE contype = 'p') pcon ON att.attrelid = pcon.conrelid AND att.attnum = ANY (pcon.conkey) \ JOIN pg_class c ON fcon.confrelid = c.oid \ JOIN pg_namespace ns ON c.relnamespace = ns.oid \ LEFT JOIN (SELECT * FROM pg_description WHERE objsubid = 0) d ON c.oid = d.objoid \ WHERE att.attnum > 0 \ AND att.attisdropped = false \ AND att.attrelid = :oid \ AND array_length(pcon.conkey, 1) = 1" # AND array_length(pcon.conkey, 1) = 1: # if we have a combined PK we are in a n-to-m relational table # pg_description.objsubid = 0 for description on tables query.prepare(sQuery) query.bindValue(":oid", thisTable.oid) query.exec_() parents = [] if query.isActive(): if query.size() == 0: query.finish() else: while query.next(): oid = query.value(0) schema = query.value(1) table = query.value(2) comment = query.value(3) parentTable = DdTable(oid, schema, table, comment) parents.append(parentTable) query.finish() else: DbError(query) return parents
[docs] def getN2mAttributes(self, db, thisTable, attName, attNum, labels, showChildren, skip = [], fieldDisable = []): '''find those tables (n2mtable) where our pk is a fk''' n2mAttributes = [] pkQuery = QtSql.QSqlQuery(db) sPkQuery = "SELECT array_length(pk.conkey, 1), att.attname, att.attnum, c.oid as table_oid,n.nspname,c.relname, f.numfields, COALESCE(d.description,'') as comment, COALESCE(inpk.in,0) as inpk \ FROM pg_attribute att \ JOIN (SELECT * FROM pg_constraint WHERE contype = 'f') fk ON att.attrelid = fk.conrelid AND att.attnum = ANY (fk.conkey) \ JOIN (SELECT * FROM pg_constraint WHERE contype = 'p') pk ON pk.conrelid = fk.conrelid \ LEFT JOIN (SELECT 1 AS in, conrelid, conkey FROM pg_constraint where contype = 'p') inpk ON inpk.conrelid = fk.conrelid AND att.attnum = ANY (inpk.conkey) \ JOIN pg_class c ON fk.conrelid = c.oid \ JOIN pg_namespace n ON c.relnamespace = n.oid \ LEFT JOIN pg_description d ON c.oid = d.objoid AND 0 = d.objsubid \ JOIN(SELECT attrelid, count(attrelid) as numfields \ FROM pg_attribute \ WHERE attnum > 0 \ AND attisdropped = false \ GROUP BY attrelid) f ON c.oid = f.attrelid \ WHERE fk.confrelid = :oid \ AND :attNum = ANY(fk.confkey) " # 0 = d.objsubid: comment on table only, not on its columns pkQuery.prepare(sPkQuery) pkQuery.bindValue(":oid", thisTable.oid) pkQuery.bindValue(":attNum", attNum) pkQuery.exec_() if pkQuery.isActive(): while pkQuery.next(): numPkFields = pkQuery.value(0) relationFeatureIdField = pkQuery.value(1) fkAttNum = pkQuery.value(2) #is the attribute number in n2mtable relationOid = pkQuery.value(3) relationSchema = pkQuery.value(4) relationTable = pkQuery.value(5) numFields = pkQuery.value(6) relationComment = pkQuery.value(7) inPk = bool(pkQuery.value(8)) ddRelationTable = DdTable(relationOid, relationSchema, relationTable) if inPk: # either n:m or 1:1 if numPkFields == 1: # 1:1 relation if showChildren: # show 1:1 related tables, too subType = "table" maxRows = 1 showParents = False else: continue elif numPkFields > 1: # n:m relation if numFields == 2: # get the related table i.e. the table where the other FK field is the PK relatedQuery = QtSql.QSqlQuery(db) sRelatedQuery = "SELECT c.oid, n.nspname, c.relname, att.attname \ FROM pg_constraint con \ JOIN pg_class c ON con.confrelid = c.oid \ JOIN pg_namespace n ON c.relnamespace = n.oid \ JOIN (\ SELECT * \ FROM pg_attribute \ WHERE attnum > 0 \ AND attisdropped = false \ AND attnum != :attNum1 \ ) att ON con.conrelid = att.attrelid \ WHERE conrelid = :relationOid \ AND contype = 'f' \ AND :attNum2 != ANY(conkey)" # we do not want the table where we came from in the results, therefore :attNum != ANY(confkey) # JOIN pg_class c ON con.confrelid = c.oid -- referenced table # WHERE conrelid = :relationOid -- table this constraint is on # AND contype = 'f' -- foreign key constraint # AND :attNum2 != ANY(conkey) -- list of constrained columns relatedQuery.prepare(sRelatedQuery) relatedQuery.bindValue(":relationOid", relationOid) relatedQuery.bindValue(":attNum1", fkAttNum) relatedQuery.bindValue(":attNum2", fkAttNum) relatedQuery.exec_() if relatedQuery.isActive(): if relatedQuery.size() == 0: #no relatedTable but a Table with two PK columns subType = "table" maxRows = 1 showParents = False elif relatedQuery.size() == 1: while relatedQuery.next(): relatedOid = relatedQuery.value(0) relatedSchema = relatedQuery.value(1) relatedTable = relatedQuery.value(2) relationRelatedIdField = relatedQuery.value(3) ddRelatedTable = DdTable(relatedOid, relatedSchema, relatedTable) relatedQuery.finish() relatedFieldsQuery = QtSql.QSqlQuery(db) relatedFieldsQuery.prepare(self.__attributeQuery("att.attnum")) relatedFieldsQuery.bindValue(":oid", relatedOid) relatedFieldsQuery.exec_() if relatedFieldsQuery.isActive(): if relatedFieldsQuery.size() == 2: subType = "list" else: subType = "tree" relatedIdField = None relatedDisplayCandidate = None relatedDisplayField = None i = 0 fieldList = [] while relatedFieldsQuery.next(): relatedAttName = relatedFieldsQuery.value(0) relatedAttNum = relatedFieldsQuery.value(1) relatedAttNotNull = bool(relatedFieldsQuery.value(2)) relatedAttHasDefault = bool(relatedFieldsQuery.value(3)) relatedAttIsChild = bool(relatedFieldsQuery.value(4)) relatedAttLength = relatedFieldsQuery.value(5) relatedAttTyp = relatedFieldsQuery.value(6) relatedAttComment = relatedFieldsQuery.value(7) relatedAttDefault = relatedFieldsQuery.value(8) relatedAttConstraint = relatedFieldsQuery.value(9) if relatedAttConstraint == "p": # PK of the related table relatedIdField = relatedAttName if relatedAttTyp == "varchar" or relatedAttTyp == "char": if relatedAttNotNull and not relatedDisplayField: # we use the first one relatedDisplayField = relatedAttName #we display only char attributes fieldList.append(relatedAttName) relatedFieldsQuery.finish() if not relatedDisplayCandidate: # there was no string field relatedDisplayCandidate = relatedIdField if not relatedDisplayField: # there was no notNull string field relatedDisplayField = relatedDisplayCandidate else: DbError(relatedFieldsQuery) else: subType = "table" maxRows = None showParents = False relatedQuery.finish() else: DbError(relatedQuery) elif numFields > 2: subType = "table" maxRows = None showParents = False else: # 1:n relation subType = "table" maxRows = None showParents = True try: attLabel = labels[str(relationTable)] except KeyError: attLabel = None attEnableWidget = fieldDisable.count(ddRelationTable.tableName) == 0 if subType == "table": configList = self.configureLayer( ddRelationTable, [], {}, [], {}, {}, [], db, True, "", []) skipThese = configList[0] rLabels = configList[1] rMinMax = configList[4] attributes = self.getAttributes( ddRelationTable, db, rLabels, rMinMax, skipThese) attrsToKeep = [] for aRelAtt in attributes: if aRelAtt.type != "geometry": attrsToKeep.append(aRelAtt) ddAtt = DdTableAttribute( ddRelationTable, relationComment, attLabel, relationFeatureIdField, attrsToKeep, maxRows, showParents, attName, attEnableWidget) else: relatedForeignKeys = self.getForeignKeys(ddRelatedTable, db) ddAtt = DdN2mAttribute( ddRelationTable, ddRelatedTable, subType, relationComment, attLabel, relationFeatureIdField, relationRelatedIdField, relatedIdField, relatedDisplayField, fieldList, relatedForeignKeys, attEnableWidget) try: skip.index(ddAtt.name) addAtt = False except: addAtt = True if addAtt: n2mAttributes.append(ddAtt) pkQuery.finish() else: DbError(pkQuery) return n2mAttributes
[docs] def getAttributes(self, thisTable, db, labels, minMax, skip = [], fieldDisable = []): ''' query the DB and create DdAttributes''' ddAttributes = [] query = QtSql.QSqlQuery(db) sQuery = self.__attributeQuery("att.attnum") query.prepare(sQuery) query.bindValue(":oid", thisTable.oid) query.exec_() retValue = dict() if query.isActive(): if query.size() == 0: query.finish() else: foreignKeys = self.getForeignKeys(thisTable, db) while query.next(): attName = query.value(0) nextResult = False for skipName in skip: if skipName == attName: nextResult = True break if nextResult: continue attNum = query.value(1) attNotNull = query.value(2) attHasDefault = query.value(3) attIsChild = query.value(4) attLength = query.value(5) attTyp = query.value(6) attComment = query.value(7) attDefault = query.value(8) attConstraint = query.value(9) constrainedAttNums = query.value(10) attCategory = query.value(11) arrayDelim = query.value(12) attTyp = attTyp.replace("_", "") # if array type is e.g. _varchar if not self.isSupportedType(attTyp): continue attEnableWidget = fieldDisable.count(attName) == 0 isPK = attConstraint == "p" # PrimaryKey isArray = attCategory == "A" if isArray: normalAtt = True else: if isPK: constrainedAttNums = constrainedAttNums.replace("{", "").replace("}", "").split(",") else: constrainedAttNums = [] if isPK and len(constrainedAttNums) == 1: # if table has a single PK we do not care if it is a FK, too because we # do not treat a parent in a 1:1 relation as lookup table normalAtt = True else: try: # is this attribute a FK fk = foreignKeys[attName] try: attLabel = labels[str(attName)] except KeyError: attLabel = attName + " (" + fk[2] + ")" try: fkComment = fk[3] except IndexError: fkComment = "" if attComment == "": attComment = fkComment else: if not fkComment == "": attComment = attComment + "\n(" + fkComment + ")" ddAtt = DdFkLayerAttribute( thisTable, attTyp, attNotNull, attName, attComment, attNum, isPK, attDefault, attHasDefault, fk[1], attLabel, attEnableWidget) normalAtt = False except KeyError: # no fk defined normalAtt = True if normalAtt: try: attLabel = labels[str(attName)] except KeyError: attLabel = None try: thisMinMax = minMax[str(attName)] except KeyError: thisMinMax = None if thisMinMax == None: thisMin = None thisMax = None else: thisMin = thisMinMax[0] thisMax = thisMinMax[1] if attTyp == "date": ddAtt = DdDateLayerAttribute( thisTable, attTyp, attNotNull, attName, attComment, attNum, isPK, False, attDefault, attHasDefault, attLength, attLabel, thisMin, thisMax, enableWidget = attEnableWidget, isArray = isArray, arrayDelim = arrayDelim) elif attTyp == "geometry": ddAtt = DdGeometryAttribute( thisTable, attTyp, attName, attComment, attNum) else: ddAtt = DdLayerAttribute( thisTable, attTyp, attNotNull, attName, attComment, attNum, isPK, False, attDefault, attHasDefault, attLength, attLabel, thisMin, thisMax, attEnableWidget, isArray, arrayDelim) ddAttributes.append(ddAtt) query.finish() else: DbError(query) return ddAttributes
[docs] def isSupportedType(self, typ): supportedAttributeTypes = ["int2", "int4", "int8", "bpchar", "varchar", "float4", "float8", "text", "bool", "date", "geometry"] supported = False for aTyp in supportedAttributeTypes: if aTyp == typ: supported = True break return supported
[docs] def getForeignKeys(self, thisTable, db): '''querys this table's foreign keys and returns a dict attnum: [str: Type of the lookup field, str: sql to query lookup values, str: Name of the value field in the lookup table, str: Comment on lookup table]''' query = QtSql.QSqlQuery(db) sQuery = "SELECT \ att.attname, \ t.typname as typ, \ CAST(valatt.attnotnull as integer) as notnull, \ valatt.attname, \ ((((((('SELECT ' || quote_ident(valatt.attname)) || ' as value, ') || quote_ident(refatt.attname)) || ' as key FROM ') \ || quote_ident(ns.nspname)) || '.') || quote_ident(c.relname)) || ' ;' AS sql_key, \ ((((((('SELECT ' || quote_ident(refatt.attname)) || ' as value, ') || quote_ident(refatt.attname)) || ' as key FROM ') \ || quote_ident(ns.nspname)) || '.') || quote_ident(c.relname)) || ' ;' AS default_sql, \ COALESCE(d.description, '') as comment, \ COALESCE(valcon.contype, 'x') as valcontype \ FROM pg_attribute att \ JOIN (SELECT * FROM pg_constraint WHERE contype = 'f') con ON att.attrelid = con.conrelid AND att.attnum = ANY (con.conkey) \ JOIN pg_class c ON con.confrelid = c.oid \ JOIN pg_namespace ns ON c.relnamespace = ns.oid \ JOIN pg_attribute refatt ON con.confrelid = refatt.attrelid AND con.confkey[1] = refatt.attnum \ JOIN pg_attribute valatt ON con.confrelid = valatt.attrelid \ LEFT JOIN pg_constraint valcon ON valatt.attrelid = valcon.conrelid AND valatt.attnum = ANY (valcon.conkey)\ JOIN pg_type t ON valatt.atttypid = t.oid \ LEFT JOIN pg_description d ON con.confrelid = d.objoid AND 0 = d.objsubid \ WHERE att.attnum > 0 \ AND att.attisdropped = false \ AND valatt.attnum > 0 \ AND valatt.attisdropped = false \ AND att.attrelid = :oid \ ORDER BY att.attnum, valatt.attnum" # Query returns all fields in the lookup table for each attnum # JOIN valcon in order to not display any fields that are FKs themsselves # add "AND valatt.attnum != con.confkey[1]" if you want to keep PKs out query.prepare(sQuery) query.bindValue(":oid", thisTable.oid) query.exec_() foreignKeys = dict() if query.isActive(): while query.next(): attName = query.value(0) fieldType = query.value(1) notNull = query.value(2) valAttName = query.value(3) keySql = query.value(4) defaultSql = query.value(5) comment = query.value(6) contype = query.value(7) if contype == "f": continue try: fk = foreignKeys[attName] if fk[0] != "varchar": # we do not already have a varchar field as value field # find a field with a suitable type if notNull and (fieldType == "varchar" or fieldType == "char"): foreignKeys[attName] = [fieldType, keySql, valAttName, comment] except KeyError: if notNull and (fieldType == "varchar" or fieldType == "char"): foreignKeys[attName] = [fieldType, keySql, valAttName, comment] else: # put the first in foreignKeys[attName] = [fieldType, defaultSql, valAttName, comment] query.finish() else: DbError(query) return foreignKeys
[docs] def getOid(self, thisTable, db): ''' query the DB to get a table's oid''' return ddtools.getOid(thisTable, db)
def __attributeQuery(self, order = "lower(att.attname)"): sQuery = "SELECT \ att.attname, \ att.attnum, \ CAST(att.attnotnull as integer) as notnull, \ CAST(att.atthasdef as integer) as hasdef, \ CASE att.attinhcount WHEN 0 THEN 0 ELSE 1 END as ischild, \ CASE att.attlen WHEN -1 THEN att.atttypmod -4 ELSE att.attlen END as length, \ t.typname as typ, \ COALESCE(d.description, '') as comment, \ COALESCE(ad.adsrc, '') as default, \ COALESCE(con.contype, '') as contype, \ COALESCE(con.conkey, ARRAY[]::smallint[]) as constrained_columns, \ t.typcategory, \ t.typdelim \ FROM pg_attribute att \ JOIN pg_type t ON att.atttypid = t.oid \ LEFT JOIN pg_description d ON att.attrelid = d.objoid AND att.attnum = d.objsubid \ LEFT JOIN pg_attrdef ad ON att.attrelid = ad.adrelid AND att.attnum = ad.adnum \ LEFT JOIN (SELECT * FROM pg_constraint WHERE contype = \'p\') con ON att.attrelid = con.conrelid AND att.attnum = ANY (con.conkey) \ WHERE att.attnum > 0 \ AND att.attisdropped = false \ AND att.attrelid = :oid \ ORDER BY " + order # contype = \'p\': only primary key constraints are of interest # att.attnum > 0 skip system columns # att.attisdropped = false -- skip deleted columns return sQuery
[docs]class DdWidget(object): '''abstract base class of all ui-widgets''' def __init__(self): pass def __str__(self): return "<ddui.DdWidget>"
[docs] def checkInput(self, layer, feature): '''check if input is valid returns True if not implemented in child class''' return True
[docs] def setupUi(self, parent, db): '''create the ui must be implemented in child classes''' raise NotImplementedError("Should have implemented setupUi")
[docs] def initialize(self, layer, feature, db, mode = 0): '''initialize this widget for feature in layer must be implemented in child classes''' raise NotImplementedError("Should have implemented initialize")
[docs] def save(self, layer, feature, db): '''saves the input must be implemented in child classes''' raise NotImplementedError("Should have implemented save")
[docs] def discard(self): '''discards the input''' pass
[docs] def reset(self): '''reset anything changed by the DdWidget''' pass
[docs] def search(self, layer): '''creates search string must be implemented in child classes''' raise NotImplementedError("Should have implemented search")
[docs] def createSearch(self, parentElement): '''creates search xml''' pass
[docs] def applySearch(self, parentElement): '''read the appropriate parentElement's tag and apply to the widget''' pass
[docs] def debug(self, msg): QgsMessageLog.logMessage(msg)
[docs] def getPk(self, feature, layer): '''only relevant for QGIS 2.16 and newer: read the pk-id value either from the feature itself or, in case of a bigint pk field from the field''' thisId = feature.id() if thisId > 0 and QGis.QGIS_VERSION_INT >= 21600: #not for newly created features uri = QgsDataSourceURI(layer.dataProvider().dataSourceUri()) pkFieldName = uri.keyColumn() pkFieldIndex = layer.fieldNameIndex(pkFieldName.replace("\"","")) pkField = layer.fields()[pkFieldIndex] pkFieldType = pkField.typeName() if pkFieldType == 'int8': # bigint thisId = feature[pkFieldIndex] return thisId
[docs]class DdDialogWidget(DdWidget): '''This is the mask ui''' def __init__(self): DdWidget.__init__(self) self.forms = [] self.helpText = "" def __str__(self): return "<ddui.DdDialogWidget>"
[docs] def setupUi(self, ddDialog, db): # ddDialog is a child of QDialog ddDialog.setObjectName("DataDrivenInputMask") ddDialog.setWindowModality(QtCore.Qt.ApplicationModal) verticalLayout = QtGui.QVBoxLayout(ddDialog) verticalLayout.setObjectName("DataDrivenInputMask_horizontalLayout") self.scrollArea = QtGui.QScrollArea() self.scrollArea.setWidgetResizable(True) self.scrollArea.setObjectName("DataDrivenInputMask_scrollArea") verticalLayout.addWidget(self.scrollArea) self.scrollAreaWidgetContents = QtGui.QWidget(ddDialog) self.scrollAreaWidgetContents.setObjectName("DataDrivenInputMask_scrollAreaWidgetContents") self.scrollAreaLayout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents) self.scrollAreaLayout.setObjectName("DataDrivenInputMask_scrollAreaLayout") self.mainTab = QtGui.QTabWidget(self.scrollAreaWidgetContents) self.mainTab.setObjectName("DataDrivenInputMask_mainTab") # remove forms without widgets (e.g. if the form only has one widget # and this widget is no search widget but an input widget the form # is not shown in the search dialog formsToKeep = [] while len(self.forms) > 0: aForm = self.forms.pop() if len(aForm.inputWidgets) > 0: formsToKeep.append(aForm) self.forms = formsToKeep self.forms.reverse() for i in range(len(self.forms)): aTab = QtGui.QWidget(self.mainTab) aTab.setObjectName("tab" + str(i)) aForm = self.forms[i] tabLayout = QtGui.QFormLayout(aTab) tabLayout.setObjectName("tabLayout" + str(i)) aForm.setupUi(aTab, db) if aForm.ddTable.title: tabTitle = aForm.ddTable.title else: tabTitle = aForm.ddTable.tableName self.mainTab.addTab(aTab, tabTitle) if aForm.ddTable.comment != "": aTab.setToolTip(aForm.ddTable.comment) self.mainTab.setCurrentIndex(0) self.scrollAreaLayout.addWidget(self.mainTab) self.scrollArea.setWidget(self.scrollAreaWidgetContents) self.buttonBox = QtGui.QDialogButtonBox(ddDialog) self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setObjectName("buttonBox") self.buttonBox.accepted.connect(ddDialog.accept) self.buttonBox.rejected.connect(ddDialog.reject) if self.helpText != "": self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Help) self.buttonBox.helpRequested.connect(ddDialog.helpRequested) else: self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) verticalLayout.addWidget(self.buttonBox)
[docs] def setHelpText(self, helpText): self.helpText = helpText
[docs] def addFormWidget(self, ddFormWidget): '''add this DdFormWidget to the ui''' self.forms.append(ddFormWidget)
[docs] def addInputWidget(self, inputWidget, ddFormWidgetIndex = None, beforeWidget = None): '''add inputWidget to form with ddFormWidgetIndex beforeWidget (index in form)''' if len(self.forms) > 0: if isinstance(ddFormWidgetIndex, int): try: ddFormWidget = self.forms[ddFormWidgetIndex] except IndexError: ddFormWidget = self.forms[len(self.forms) - 1] else: ddFormWidget = self.forms[len(self.forms) - 1] ddFormWidget.addInputWidget(inputWidget, beforeWidget)
[docs] def initialize(self, layer, feature, db, mode = 0): for aForm in self.forms: aForm.initialize(layer, feature, db, mode)
[docs] def checkInput(self, layer, feature): inputOk = True for aForm in self.forms: if not aForm.checkInput(layer, feature): inputOk = False break return inputOk
[docs] def save(self, layer, feature, db): hasChanges = False layerDict = {} # layerId:[layerObject, doSave] for aForm in self.forms: doSave = aForm.save(layer, feature, db) # check if there is anything to save try: oldSave = layerDict[aForm.layer.id()][1] # already in, get current save status except KeyError: oldSave = None if oldSave == None: #add to dict layerDict[aForm.layer.id()] = [aForm.layer, doSave] elif oldSave == False: # replace with the new value layerDict[aForm.layer.id()][1] = doSave hasChanges = False for aLayerId in layerDict.iterkeys(): aLayer = layerDict[aLayerId][0] doSave = layerDict[aLayerId][1] if doSave: hasChanges = True if not aLayer.commitChanges(): DdError(QtGui.QApplication.translate("DdError", "Could not save changes for layer:", None, QtGui.QApplication.UnicodeUTF8) + " " + aLayer.name()) else: if not aLayer.rollBack(): DdError(QtGui.QApplication.translate("DdError", "Could not discard changes for layer:", None, QtGui.QApplication.UnicodeUTF8) + " " + aLayer.name()) layer.startEditing() return hasChanges
[docs] def discard(self): for aForm in self.forms: aForm.discard()
[docs] def search(self, layer): searchSql = "" for aForm in self.forms: thisSearch = aForm.search(layer) if thisSearch != "": if searchSql != "": searchSql += " AND " searchSql += thisSearch return searchSql
[docs] def createSearch(self, parentElement): #parentElement is the XML root element in this case for aForm in self.forms: aForm.createSearch(parentElement)
[docs] def applySearch(self, parentElement): #parentElement is the XML root element in this case for aForm in self.forms: aForm.applySearch(parentElement)
[docs]class DdFormWidget(DdWidget): '''DdForms are the content of DdDialog, each DdDialog needs at least one DdForm (tab). The class arranges its input widgets either in a QToolBox or in the DdDialogWidget's current tab A form can represent a (parent) layer with the corresponding (parent) feature''' def __init__(self, ddTable, hasToolBox = False, layer = None): DdWidget.__init__(self) self.ddTable = ddTable self.hasToolBox = hasToolBox self.layer = layer self.wasEditable = False self.feature = None self.parent = None self.oldSubsetString = "" self.inputWidgets = [] def __str__(self): return "<ddui.DdFormWidget>" def __getLayer(self, db): # find the layer in the project layer = self.parentDialog.ddManager.findPostgresLayer(db, self.ddTable) if not layer: # load the layer into the project layer = self.parentDialog.ddManager.loadPostGISLayer(db, self.ddTable) return layer def __setLayerEditable(self): # put layer in edintg mode ok = self.layer.isEditable() # is already in editMode if not ok: # try to start editing ok = self.layer.startEditing() if not ok: DdError(QtGui.QApplication.translate("DdWarning", "Layer cannot be put in editing mode:", None, QtGui.QApplication.UnicodeUTF8) + self.layer.name()) return ok
[docs] def setupUi(self, parent, db): self.parent = parent pParent = self.parent while (True): # get the DdDialog instance in order to have access to ddManager pParent = pParent.parentWidget() if isinstance(pParent, DdDialog) or isinstance(pParent, DdSearchDialog): self.parentDialog = pParent break for ddInputWidget in self.inputWidgets: ddInputWidget.setupUi(parent, db) if self.layer == None: # has not been passed to __init__ self.layer = self.__getLayer(db)
[docs] def addInputWidget(self, ddInputWidget, beforeWidget = None): '''insert this DdInputWidget into this DdForm before Widget''' if beforeWidget == None or not isinstance(beforeWidget, int): self.inputWidgets.append(ddInputWidget) else: self.inputWidgets.insert(beforeWidget, ddInputWidget)
[docs] def initialize(self, layer, feature, db, mode = 0): self.mode = mode self.oldSubsetString = self.layer.subsetString() enableAll = False if self.mode == 1: # search feature for anInputWidget in self.inputWidgets: anInputWidget.initialize(self.layer, feature, db, self.mode) else: if layer.id() == self.layer.id(): self.feature = feature self.wasEditable = layer.isEditable() enableAll = layer.isEditable() else: layerPkList = layer.pendingPkAttributesList() if len(layerPkList) != 1: self.feature = None # no combined keys else: layerPkIdx = layerPkList[0] pkValues = [] if self.mode == 0: pkValue = feature[layerPkIdx] if pkValue == None: self.feature = None else: pkValues.append(str(pkValue)) elif self.mode == 2: for aFeat in layer.selectedFeatures(): pkValue = aFeat[layerPkIdx] if pkValue == None: self.feature = None pkValues = [] break else: pkValue = str(pkValue) pkValues.append(pkValue) if len(pkValues) > 0: thisPkList = self.layer.pendingPkAttributesList() if len(thisPkList) != 1: self.feature = None else: #self.oldSubsetString = self.layer.subsetString() thisPkField = self.layer.pendingFields().field(thisPkList[0]) newSubsetString = "\"" + thisPkField.name() + "\" IN (" for i in range(len(pkValues)): pkValue = pkValues[i] if thisPkField.typeName().find("char") != -1: pkValue = "\'" + pkValue + "\'" # quote value as string if i == 0: newSubsetString += pkValue else: newSubsetString += "," + pkValue newSubsetString += ")" self.layer.setSubsetString(newSubsetString) self.layer.reload() self.layer.selectAll() if self.mode == 0 and self.layer.selectedFeatureCount() != 1: # there is no or several features matching our feature self.feature = None elif self.mode == 2 and self.layer.selectedFeatureCount() == 0: self.feature = None else: self.feature = self.layer.selectedFeatures()[0] if layer.isEditable(): enableAll = self.__setLayerEditable() if self.mode == 0: self.layer.removeSelection() for anInputWidget in self.inputWidgets: anInputWidget.initialize(self.layer, self.feature, db, self.mode) if enableAll: anInputWidget.enableAccordingToDdAttribute() else: # enable only n2m widgets to make them scrollable if isinstance(anInputWidget, DdN2mWidget): anInputWidget.enableAccordingToDdAttribute() if not self.feature: self.parent.setEnabled(False)
[docs] def checkInput(self, layer, feature): inputOk = True if self.parent.isEnabled(): #only check if the tab is enbaled, e.g. parents are not enabled if this is a new feature for anInputWidget in self.inputWidgets: if not anInputWidget.checkInput(self.layer, self.feature): inputOk = False break return inputOk
[docs] def search(self, layer): searchSql = "" parentSql = "" if self.parent.isEnabled(): for anInputWidget in self.inputWidgets: thisSearch = anInputWidget.search(self.layer) if thisSearch != "": if searchSql != "": searchSql += " AND " searchSql += thisSearch if searchSql != "": if layer.id() != self.layer.id(): # this is a parent layer layerPkList = layer.pendingPkAttributesList() selfLayerPkList = self.layer.pendingPkAttributesList() if len(layerPkList) == 1 and len(selfLayerPkList) == 1: layerPkName = layer.dataProvider().fields()[layerPkList[0]].name() selfLayerPkName = self.layer.dataProvider().fields()[selfLayerPkList[0]].name() parentSql = "\"" + layerPkName + "\" IN (SELECT \"" + selfLayerPkName + "\" FROM \"" + \ self.ddTable.schemaName + "\".\"" + self.ddTable.tableName + "\" WHERE " if parentSql != "": searchSql = parentSql + searchSql + ")" self.reset() return searchSql
[docs] def save(self, layer, feature, db): hasChanges = False if self.parent.isEnabled(): for anInputWidget in self.inputWidgets: if anInputWidget.save(self.layer, self.feature, db): hasChanges = True self.reset() return hasChanges
[docs] def discard(self): if self.parent.isEnabled(): if self.layer.isEditable(): for anInputWidget in self.inputWidgets: anInputWidget.discard() if not self.layer.rollBack(): DdError(QtGui.QApplication.translate("DdError", "Could not discard changes for layer:", None, QtGui.QApplication.UnicodeUTF8) + " "+ self.layer.name()) if self.wasEditable: self.layer.startEditing() self.reset()
[docs] def reset(self): for anInputWidget in self.inputWidgets: anInputWidget.reset() if self.layer.subsetString() != self.oldSubsetString: # reset previous subset string self.layer.setSubsetString(self.oldSubsetString) self.layer.reload() if self.layer.geometryType() != 4: self.parentDialog.ddManager.iface.mapCanvas().refresh()
[docs] def createSearch(self, parentElement): #parentElement is the XML root element in this case #check if there is already an element for this table (happens if tabs are used) tableElement = None for aTableElement in parentElement.findall("table"): if aTableElement.get("tableName", None) == self.ddTable.tableName: tableElement = aTableElement break if tableElement == None: #create it tableElement = ET.SubElement(parentElement, "table") tableElement.set("tableName", self.ddTable.tableName) for anInputWidget in self.inputWidgets: anInputWidget.createSearch(tableElement)
[docs] def applySearch(self, parentElement): #parentElement is the XML root element in this case #check if there is an element for this table tableElement = None for aTableElement in parentElement.findall("table"): if aTableElement.get("tableName", None) == self.ddTable.tableName: tableElement = aTableElement break if tableElement != None: #create it for anInputWidget in self.inputWidgets: anInputWidget.applySearch(tableElement)
[docs]class DdInputWidget(DdWidget): '''abstract super class for any input widget, corresponds to a DdAttribute''' def __init__(self, ddAttribute): DdWidget.__init__(self) self.attribute = ddAttribute self.hasChanges = False self.inputWidget = None def __str__(self): return "<ddui.DdInputWidget %s>" % str(self.attribute.name)
[docs] def setEnabled(self, enable): '''enable the inputWidget''' if self.inputWidget != None: self.inputWidget.setEnabled(enable)
[docs] def registerChange(self , thisValue): '''slot to be called when user changes the input''' self.hasChanges = True
[docs] def getLabel(self): '''returns the label for this DdInputWidget''' labelString = self.attribute.getLabel() return labelString
[docs] def createLabel(self, parent): '''creates a QLabel object''' labelString = self.getLabel() label = QtGui.QLabel(labelString, parent) label.setObjectName("lbl" + parent.objectName() + self.attribute.name) label.setToolTip(self.attribute.comment) return label
[docs] def getMaxValueFromTable(self, schemaName, tableName, db): '''querys schema.table in db to get the highest occuring values for this DdInputWidget's attribute''' query = QtSql.QSqlQuery(db) query.prepare("SELECT \"" + \ self.attribute.name + \ "\" FROM \"" + \ schemaName + "\".\"" + \ tableName + \ "\" ORDER BY \"" + self.attribute.name + "\" DESC LIMIT 1;") query.exec_() if query.isActive(): if query.first(): return query.value(0) else: return 0 query.finish() else: DbError(query)
[docs] def enableAccordingToDdAttribute(self): self.setEnabled(self.attribute.enableWidget)
[docs] def boolToString(self, thisValue): '''convert boolean to string''' if thisValue == None: thisString = "NULL" else: if thisValue: thisString = QtGui.QApplication.translate( "DdLabel", "Yes", None, QtGui.QApplication.UnicodeUTF8) else: thisString = QtGui.QApplication.translate( "DdLabel", "No", None, QtGui.QApplication.UnicodeUTF8) return thisString
[docs] def dateToString(self, thisValue): '''convert QDate to a string''' loc = QtCore.QLocale.system() return loc.toString(thisValue)
[docs] def doubleToString(self, thisValue): '''convert a double to string''' loc = QtCore.QLocale.system() return loc.toString(thisValue)
[docs] def intToString(self, thisValue): '''convert an integer to a string''' loc = QtCore.QLocale.system() return loc.toString(thisValue)
[docs]class DdLineEdit(DdInputWidget): '''abstract class for all Input Widgets that can be represented in one line, creates a QLineEdit as default InputWidget, adds it together with a QCheckBox (to store null values) and a label to a QFormLayout.''' def __init__(self, attribute): DdInputWidget.__init__(self, attribute) self.chk = None self.betweenWidget = None self.inputWidget = None self.mode = 0 # 0 = single feature edit, 1 = search, 2 = multi edi def __str__(self): return "<ddui.DdOneLineInputWidget %s>" % str(self.attribute.name)
[docs] def getDefault(self): '''function to strip quotation marks and data type from default string''' thisValue = self.attribute.default if True: thisValue = thisValue.split("::")[0] if thisValue.find("nextval") != -1: thisValue = thisValue + ")" else: thisValue = thisValue.replace("\'", "") return thisValue
[docs] def getFeatureValue(self, layer, feature): '''returns a str representing the value in this field for this feature; if the value is null, None is returned, if it is a new feature the default value is returned if available.''' if feature == None: return None if self.mode == 2 and not self.attribute.notNull: return None fieldIndex = self.getFieldIndex(layer) thisValue = feature[fieldIndex] if feature.id() < 0 and thisValue == None: # new feature if self.mode == 1: # no return value for search feature if self.attribute.hasDefault: thisValue = self.getDefault() return thisValue
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QLineEdit(parent) # defaultInputWidget inputWidget.setObjectName("txl" + parent.objectName() + self.attribute.name) inputWidget.textChanged.connect(self.registerChange) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(inputWidget.sizePolicy().hasHeightForWidth()) inputWidget.setSizePolicy(sizePolicy) betweenWidget = QtGui.QLineEdit(parent) # defaultInputWidget betweenWidget.setObjectName("txl" + parent.objectName() + self.attribute.name + "between") betweenWidget.textChanged.connect(self.registerChange) return [inputWidget, betweenWidget]
[docs] def manageChk(self, thisValue): '''check/uncheck the null checkbox depending on the value and the attribute's notNull property''' if self.attribute.notNull: # uncheck in order to make inputWidget editable self.chk.setChecked(False) else: self.chk.setChecked(thisValue == None)
[docs] def setValidator(self, min = None, max = None): '''set a validator, if needed must be implemented in child classes''' pass
# public methods
[docs] def setValue(self, thisValue): '''sets thisValue into the input widget''' if thisValue == None: thisValue = "" self.inputWidget.setText(unicode(thisValue))
[docs] def setSearchValue(self, thisValue): '''sets the search value, diverging implementations in subclasses''' self.setValue(thisValue)
[docs] def setBetweenValue(self, thisValue): '''sets thisValue into the betweenWidget''' if thisValue == None: thisValue = "" self.betweenWidget.setText(unicode(thisValue))
[docs] def toString(self, thisValue): return unicode(thisValue)
[docs] def getValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.inputWidget.text() if thisValue == "": thisValue = None return thisValue
[docs] def getBetweenValue(self): thisValue = None if not self.chk.isChecked(): if self.betweenWidget != None: thisValue = self.betweenWidget.text() if thisValue == "": thisValue = None return thisValue
[docs] def setupUi(self, parent, db): '''setup the label and add the inputWidget to parents formLayout''' self.label = self.createLabel(parent) hLayout = QtGui.QHBoxLayout() hLayout.setObjectName( "hLayout" + parent.objectName() + self.attribute.name) self.searchCbx = QtGui.QComboBox(parent) self.searchEventFilter = DdEventFilter(self.searchCbx) self.searchCbx.installEventFilter(self.searchEventFilter) self.searchEventFilter.doubleClicked.connect(self.onDoubleClick) searchItems = ["=", "!="] if not self.attribute.isFK: if self.attribute.isTypeChar(): searchItems += ["IN", "LIKE", "ILIKE"] elif (self.attribute.isTypeInt() or self.attribute.isTypeFloat()): searchItems += [ "IN", ">", "<", ">=", "<=", "BETWEEN...AND..."] else: if self.attribute.type == "text": searchItems += ["IN", "LIKE", "ILIKE"] elif self.attribute.type == "date": searchItems += [ ">", "<", ">=", "<=", "BETWEEN...AND..."] if not self.attribute.notNull: searchItems += ["IS NULL", "IS NOT NULL"] self.searchCbx.addItems(searchItems) self.searchCbx.currentIndexChanged.connect(self.searchCbxChanged) hLayout.addWidget(self.searchCbx) inputWidgets = self.createInputWidget(parent) self.inputWidget = inputWidgets[0] self.eventFilter = DdEventFilter(self.inputWidget) self.inputWidget.installEventFilter(self.eventFilter) self.eventFilter.doubleClicked.connect(self.onDoubleClick) self.betweenWidget = inputWidgets[1] self.inputWidget.setToolTip(self.attribute.comment) hLayout.addWidget(self.inputWidget) if self.betweenWidget != None: self.betweenWidget.setVisible(False) hLayout.addWidget(self.betweenWidget) self.chk = QtGui.QCheckBox(QtGui.QApplication.translate("DdInfo", "Null", None, QtGui.QApplication.UnicodeUTF8), parent) self.chk.setObjectName("chk" + parent.objectName() + self.attribute.name) self.chk.setToolTip(QtGui.QApplication.translate("DdInfo", "Check if you want to save an empty (or null) value.", None, QtGui.QApplication.UnicodeUTF8)) self.chk.stateChanged.connect(self.chkStateChanged) self.chk.setVisible(not self.attribute.notNull) hLayout.addWidget(self.chk) newRow = parent.layout().rowCount() + 1 parent.layout().setWidget(newRow, QtGui.QFormLayout.LabelRole, self.label) parent.layout().setLayout(newRow, QtGui.QFormLayout.FieldRole, hLayout)
[docs] def setEnabled(self, enable): if self.inputWidget != None: if enable: self.inputWidget.setEnabled(self.chk.checkState() == QtCore.Qt.Unchecked) else: self.inputWidget.setEnabled(enable) self.chk.setEnabled(enable)
[docs] def setNull(self, setnull): '''Set this inputWidget to NULL''' if setnull: thisValue = None else: if self.attribute.hasDefault: if self.searchCbx.isVisible(): thisValue = "" else: thisValue = self.getDefault() else: thisValue = "" self.setValue(thisValue) if self.betweenWidget != None: self.setBetweenValue(thisValue)
[docs] def initialize(self, layer, feature, db, mode = 0): self.mode = mode if feature == None: self.searchCbx.setVisible(False) self.manageChk(None) else: if self.mode == 1: # searchFeature self.chk.setChecked(True) self.chk.setVisible(True) self.chk.setText(QtGui.QApplication.translate("DdInfo", "Ignore", None, QtGui.QApplication.UnicodeUTF8)) self.chk.setToolTip(QtGui.QApplication.translate("DdInfo", "Check if you want this field to be ignored in the search.", None, QtGui.QApplication.UnicodeUTF8)) self.searchCbx.setVisible(True) else: self.searchCbx.setVisible(False) thisValue = self.getFeatureValue(layer, feature) self.setValue(thisValue) self.setValidator(min = thisValue, max = thisValue) # make sure the validator does not kick out an already existing value self.manageChk(thisValue) self.hasChanges = (feature.id() < 0) # register this change only for new feature
[docs] def validate(self, thisValue, layer, feature, showMsg = True): '''checks if value is within min/max range (if defined) and returns the value; subclasses can manipulate thisValue in order to make it valid input''' accepted = True msgShown = False if self.hasChanges: if thisValue != None: if self.attribute.min != None: if thisValue < self.attribute.min: if showMsg: QtGui.QMessageBox.warning( None, self.label.text(), QtGui.QApplication.translate( "DdWarning", "Field value is too small! Minimum is ", None, QtGui.QApplication.UnicodeUTF8 ) + self.toString(self.attribute.min)) msgShown = True accepted = False if accepted: if self.attribute.max != None: if thisValue > self.attribute.max: if showMsg: QtGui.QMessageBox.warning( None, self.label.text(), QtGui.QApplication.translate( "DdWarning", "Field value is too large! Maximum is ", None, QtGui.QApplication.UnicodeUTF8 ) + self.toString(self.attribute.max)) msgShown = True accepted = False else: if not self.chk.isChecked() and self.attribute.hasDefault: thisValue = self.getDefault() return [accepted, msgShown]
[docs] def checkInput(self, layer, feature): ''' check if current value is an acceptable result ''' thisValue = self.getValue() if self.attribute.notNull and thisValue == None: QtGui.QMessageBox.warning(None, self.label.text(), QtGui.QApplication.translate( "DdWarning", "Field must not be empty!", None, QtGui.QApplication.UnicodeUTF8) ) return False else: if self.getFeatureValue(layer, feature) == thisValue: # not changing a value is always allowed return True else: accepted, msgShown = self.validate(thisValue, layer, feature) if not accepted and not msgShown: QtGui.QMessageBox.warning(None, self.label.text(), QtGui.QApplication.translate( "DdWarning", "Input is not valid! Field type is %s", None, QtGui.QApplication.UnicodeUTF8 ) % str(self.attribute.type) ) return accepted
[docs] def getFieldIndex(self, layer): '''return the field index for this DdInputWidget's attribute's name in this layer''' if layer: fieldIndex = layer.fieldNameIndex(self.attribute.name) else: fieldIndex = self.attribute.num if fieldIndex == -1: DdError(QtGui.QApplication.translate( "DdError", "Field not found in layer: ", None, QtGui.QApplication.UnicodeUTF8 ) + layer.name() + "." + self.attribute.name) return fieldIndex
[docs] def save(self, layer, feature, db): thisValue = self.getValue() fieldIndex = self.getFieldIndex(layer) if self.hasChanges: if self.mode == 0: layer.changeAttributeValue(feature.id(), fieldIndex, thisValue) elif self.mode == 2: for anId in layer.selectedFeaturesIds(): layer.changeAttributeValue(anId, fieldIndex, thisValue) return self.hasChanges
[docs] def search(self, layer): '''create search sql-string''' searchSql = "" thisValue = self.getValue() operator = self.searchCbx.currentText() if not self.chk.isChecked(): if operator == "IS NULL" or operator == "IS NOT NULL": searchSql += "\"" + self.attribute.name + "\" " + operator else: if thisValue != None: if (self.attribute.isTypeInt() or self.attribute.isTypeFloat()): thisValue = str(thisValue) elif self.attribute.isTypeChar(): thisValue = thisValue.replace("\'", "").strip() if operator == "IN": thisValue = unicode(thisValue) elif operator == "LIKE" or operator == "ILIKE": thisValue = thisValue.replace("*", "%") #get rid of user's wildcard if thisValue.find("%") == -1: thisValue = "%" + thisValue + "%" # add wildcard thisValue = "\'" + unicode(thisValue) + "\'" else: thisValue = "\'" + unicode(thisValue) + "\'" else: if self.attribute.type == "bool": if thisValue: thisValue = "\'t\'" else: thisValue = "\'f\'" elif self.attribute.type == "text": if operator == "IN": thisValue = unicode(thisValue) elif operator == "LIKE" or operator == "ILIKE": thisValue = thisValue.replace("*", "%") #get rid of user's wildcard if thisValue.find("%") == -1: thisValue = "%" + thisValue + "%" # add wildcard thisValue = "\'" + unicode(thisValue) + "\'" else: thisValue = "\'" + unicode(thisValue) + "\'" elif self.attribute.type == "date": thisValue = "\'" + thisValue.toString("yyyy-MM-dd") + "\'" else: thisValue = self.toString(thisValue) if operator == "IN": inSql = "" for aValue in thisValue.split(","): aValue = aValue.strip() if inSql == "": inSql = "\"" + self.attribute.name + "\" " + operator + " (" else: inSql += "," if self.attribute.isTypeChar(): inSql += "\'" + aValue + "\'" else: if self.attribute.type == "text": inSql += "\'" + aValue + "\'" else: inSql += aValue searchSql += inSql + ")" elif operator == "BETWEEN...AND...": betweenValue = self.getBetweenValue() if betweenValue != None: if (self.attribute.isTypeInt() or self.attribute.isTypeFloat()): betweenValue = str(betweenValue) if self.attribute.type == "date": betweenValue = "\'" + betweenValue.toString("yyyy-MM-dd") + "\'" searchSql += "\"" + self.attribute.name + "\" BETWEEN " + thisValue + " AND " + betweenValue else: operator = ">=" searchSql += "\"" + self.attribute.name + "\" " + operator + " " + thisValue else: searchSql += "\"" + self.attribute.name + "\" " + operator + " " + thisValue return searchSql
[docs] def createSearch(self, parentElement): '''create the search XML''' fieldElement = None if not self.chk.isChecked(): thisValue = self.getValue() operator = self.searchCbx.currentText() fieldElement = ET.SubElement(parentElement, "field") fieldElement.set("fieldName", self.attribute.name) fieldElement.set("type", self.attribute.type) fieldElement.set("widgetType", "DdLineEdit") operatorElement = ET.SubElement(fieldElement, "operator") valueElement = ET.SubElement(fieldElement, "value") if operator == "IS NULL" or operator == "IS NOT NULL": thisValue = "" else: if thisValue != None: if (self.attribute.isTypeInt() or self.attribute.isTypeFloat()): thisValue = str(thisValue) elif self.attribute.isTypeChar(): thisValue = unicode(thisValue.replace("\'", "").strip()) else: if self.attribute.type == "bool": thisValue = str(thisValue) elif self.attribute.type == "text": thisValue = unicode(thisValue.replace("\'", "").strip()) elif self.attribute.type == "date": thisValue = thisValue.toString("yyyy-MM-dd") else: thisValue = self.toString(thisValue) #if thisValue != "None": valueElement.text = thisValue if operator == "BETWEEN...AND...": betweenValue = self.getBetweenValue() if betweenValue != None: if (self.attribute.isTypeInt() or self.attribute.isTypeFloat()): betweenValue = str(betweenValue) elif self.attribute.isTypeChar(): betweenValue = unicode(betweenValue.replace("\'", "").strip()) else: if self.attribute.type == "bool": betweenValue = str(betweenValue) elif self.attribute.type == "text": betweenValue = unicode(betweenValue.replace("\'", "").strip()) elif self.attribute.type == "date": betweenValue = betweenValue.toString("yyyy-MM-dd") else: betweenValue = self.toString(betweenValue) betweenElement = ET.SubElement(fieldElement, "bvalue") betweenElement.text = betweenValue else: operator = ">=" operatorElement.text = operator return fieldElement
[docs] def applySearch(self, parentElement): notFound = True for fieldElement in parentElement.findall("field"): if fieldElement.get("fieldName") == self.attribute.name: notFound = False operatorElement = fieldElement.find("operator") if operatorElement != None: self.chk.setChecked(False) operator = operatorElement.text for i in range(self.searchCbx.count()): if self.searchCbx.itemText( i ) == operator: self.searchCbx.setCurrentIndex( i ) break valueElement = fieldElement.find("value") if valueElement != None: value = valueElement.text self.setSearchValue(value) bvalueElement = fieldElement.find("bvalue") if bvalueElement != None: betweenValue = bvalueElement.text self.setBetweenValue(betweenValue) break if notFound: self.chk.setChecked(True)
#slots
[docs] def chkStateChanged(self, newState): '''slot: disables the input widget if the null checkbox is checked and vice versa''' if self.mode == 1: self.inputWidget.setEnabled(newState == QtCore.Qt.Unchecked) else: self.inputWidget.setEnabled( newState == QtCore.Qt.Unchecked and self.attribute.enableWidget) if self.betweenWidget != None: if self.betweenWidget.isVisible(): self.betweenWidget.setEnabled(newState == QtCore.Qt.Unchecked) self.searchCbx.setEnabled(newState == QtCore.Qt.Unchecked) self.setNull(newState == QtCore.Qt.Checked) self.hasChanges = True
[docs] def searchCbxChanged(self, thisIndex): '''steer visibility of betweenWidget''' if self.betweenWidget != None: self.betweenWidget.setVisible(self.searchCbx.itemText(thisIndex) == "BETWEEN...AND...")
[docs] def onDoubleClick(self): '''slot called when event filter on self.inputWidget throws doubleClick signal''' doActivate = self.chk.isChecked() # activate only if currently deactivated if self.mode != 1: doActivate = self.attribute.enableWidget # activate only if enableWidget flag is True (not if in searchMode) if doActivate: self.chk.setChecked(False) self.inputWidget.setFocus()
[docs]class DdLineEditInt(DdLineEdit): '''input widget (QLineEdit) for an IntegerValue''' def __init__(self, attribute): DdLineEdit.__init__(self, attribute) def __str__(self): return "<ddui.DdLineEditInt %s>" % str(self.attribute.name)
[docs] def setValue(self, thisValue): if thisValue == None: thisValue = "" else: # convert int to a locale string representation if self.mode != 1: try: thisInt = int(thisValue) thisValue = self.toString(thisInt) except ValueError: if thisValue != self.getDefault(): thisValue = "" self.inputWidget.setText(thisValue)
[docs] def toString(self, thisValue): return self.intToString(thisValue)
[docs] def getFeatureValue(self, layer, feature): if feature == None: return None if self.mode == 2 and not self.attribute.notNull: return None fieldIndex = self.getFieldIndex(layer) thisValue = feature[fieldIndex] if feature.id() < 0 and (thisValue == None or not isinstance(thisValue, int)): # new feature and no value set or sequence value in its original form if self.mode != 1: # no return value for search feature if self.attribute.hasDefault: thisValue = self.getDefault() return thisValue
[docs] def getValue(self, noSerial = False): if self.chk.isChecked(): thisValue = None else: thisValue = self.inputWidget.text() if thisValue == "": thisValue = None else: if self.mode == 1: loc = QtCore.QLocale.system() else: loc = self.inputWidget.validator().locale() intValue, accepted = loc.toInt(thisValue.replace(loc.groupSeparator(), "")) if accepted: thisValue = intValue else: if self.mode != 1: if noSerial: # if thisValue is a serial we set it to None thisValue = None return thisValue
[docs] def setNull(self, setnull): '''Set this inputWidget to NULL; set only to default if default is an integer''' if setnull: thisValue = None else: if self.attribute.hasDefault: if self.mode == 1: thisValue = "" else: thisValue = self.getDefault() try: thisValue = str(int(thisValue)) except ValueError: thisValue = "" else: thisValue = "" self.setValue(thisValue) self.setBetweenValue(thisValue)
[docs] def checkDefault(self, feature): accepted = True thisValue = self.getDefault() try: thisValue = int(thisValue) except ValueError: if feature.id() >= 0: # only sequence values for new features thisValue = None accepted = False return [accepted, thisValue]
[docs] def save(self, layer, feature, db): thisValue = self.getValue(noSerial = True) # save None in case of a serial (which is only allowed for new features anyways fieldIndex = self.getFieldIndex(layer) if self.hasChanges: if self.mode == 0: layer.changeAttributeValue(feature.id(), fieldIndex, thisValue) elif self.mode == 2: for anId in layer.selectedFeaturesIds(): layer.changeAttributeValue(anId, fieldIndex, thisValue) return self.hasChanges
[docs] def validate(self, thisValue, layer, feature, showMsg = True): accepted = True msgShown = False if isinstance(thisValue, int): return DdLineEdit.validate(self, thisValue, feature, showMsg) else: if thisValue == None: if not self.chk.isChecked() and self.attribute.hasDefault: accepted, thisValue = self.checkDefault(feature) else: loc = self.inputWidget.validator().locale() validationState = self.inputWidget.validator().validate(thisValue, 0)[0] if validationState == 0: #clearly invalid if self.attribute.hasDefault: # could be a serial if thisValue == self.getDefault(): # unchanged serial => ok accepted, thisValue = self.checkDefault(feature) else: accepted = False else: accepted = False else: #validationState == 1: intermediate, 2: accepted thisValue, accepted = loc.toInt(thisValue.replace(loc.groupSeparator(), "")) # replace groupSeparator but not decimalSeparator if accepted: accepted, msgShown = DdLineEdit.validate(self, thisValue, feature, showMsg) if accepted: self.setValue(thisValue) return [accepted, msgShown]
[docs] def setValidator(self, min = None, max = None): '''sets an appropriate QValidator for the QLineEdit if this DdInputWidget's attribute has min/max values validator is set to them A Python2 int covers PostgreSQL's int2, int4 and int8 Upon initialization setValidator is called with the current value as min and max''' validator = ddtools.getIntValidator(self.inputWidget, self.attribute, min, max) self.inputWidget.setValidator(validator) self.betweenWidget.setValidator(validator)
[docs]class DdLineEditDouble(DdLineEdit): '''input widget (QLineEdit) for a DoubleValue''' def __init__(self, attribute): DdLineEdit.__init__(self, attribute) def __str__(self): return "<ddui.DdLineEditDouble %s>" % str(self.attribute.name)
[docs] def setValue(self, thisValue): if thisValue == None: thisValue = "" else: try: thisDouble = float(thisValue) thisValue = self.toString(thisDouble) except ValueError: thisValue = "" self.inputWidget.setText(thisValue)
[docs] def setBetweenValue(self, thisValue): if thisValue == None: thisValue = "" else: try: thisDouble = float(thisValue) thisValue = self.toString(thisDouble) except ValueError: thisValue = "" self.betweenWidget.setText(thisValue)
[docs] def toString(self, thisValue): return self.doubleToString(thisValue)
[docs] def getValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.inputWidget.text() if thisValue == "": thisValue = None else: if self.mode == 1: loc = QtCore.QLocale.system() else: loc = self.inputWidget.validator().locale() thisDouble = loc.toDouble(thisValue.replace(loc.groupSeparator(), "")) if thisDouble[1]: thisValue = thisDouble[0] else: if self.mode != 1: thisValue = None return thisValue
[docs] def getBetweenValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.betweenWidget.text() if thisValue == "": thisValue = None else: loc = QtCore.QLocale.system() thisDouble = loc.toDouble(thisValue.replace(loc.groupSeparator(), "")) if thisDouble[1]: thisValue = thisDouble[0] else: if self.mode != 1: thisValue = None return thisValue
[docs] def setValidator(self, min = None, max = None): '''sets an appropriate QValidator for the QLineEdit if this DdInputWidget's attribute has min/max values validator is set to them''' validator = ddtools.getDoubleValidator(self.inputWidget, self.attribute, min, max) self.inputWidget.setValidator(validator) self.betweenWidget.setValidator(validator)
[docs] def validate(self, thisValue, layer, feature, showMsg = True): accepted = True msgShown = False if isinstance(thisValue, float): accepted, msgShown = DdLineEdit.validate(self, thisValue, feature, showMsg) else: accepted = (not self.attribute.notNull and thisValue == None) if accepted: self.setValue(thisValue) return [accepted, msgShown]
[docs]class DdLineEditChar(DdLineEdit): '''input widget (QLineEdit) for a char or varchar''' def __init__(self, attribute): DdLineEdit.__init__(self, attribute) def __str__(self): return "<ddui.DdLineEditChar %s>" % str(self.attribute.name)
[docs] def checkInput(self, layer, feature): ok = DdLineEdit.checkInput(layer, feature) if ok: thisValue = self.getValue() size = thisValue.size() if size > self.attribute.length: QtGui.QMessageBox.warning(None, self.label.text(), QtGui.QApplication.translate("DdWarning", "Input in Field is too long! ", None, QtGui.QApplication.UnicodeUTF8) ) return False if self.attribute.type == "char" and size != self.attribute.length: QtGui.QMessageBox.warning(None, self.label.text(), QtGui.QApplication.translate("DdWarning", "Input in Field is too short!", None, QtGui.QApplication.UnicodeUTF8) ) return False return ok
[docs]class DdComboBox(DdLineEdit): '''input widget (QComboBox) for a foreign key''' def __init__(self, attribute): DdLineEdit.__init__(self, attribute) self.values = {} def __str__(self): return "<ddui.DdComboBox %s>" % str(self.attribute.name)
[docs] def getFeatureValue(self, layer, feature): '''returns a value representing the value in this field for this feature''' if feature == None: return None if self.mode == 2 and not self.attribute.notNull: return None fieldIndex = self.getFieldIndex(layer) thisValue = feature[fieldIndex] #returns None if empty if feature.id() < 0 and thisValue == None: # new feature and no value set if self.mode != 1: # no return value for search feature if self.attribute.hasDefault: if self.attribute.isTypeInt(): thisValue = int(self.getDefault()) elif self.attribute.isTypeChar(): thisValue = self.getDefault() return thisValue
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QComboBox(parent) # defaultInputWidget inputWidget.setObjectName("cbx" + parent.objectName() + self.attribute.name) inputWidget.setEditable(True) inputWidget.currentIndexChanged.connect(self.registerChange) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(inputWidget.sizePolicy().hasHeightForWidth()) inputWidget.setSizePolicy(sizePolicy) return [inputWidget, None]
[docs] def readValues(self, db): '''read the values to be shown in the QComboBox from the db''' self.values == {} query = QtSql.QSqlQuery(db) query.prepare(self.attribute.queryForCbx) query.exec_() if query.isActive(): while query.next(): # returns false when all records are done sValue = query.value(0) if not isinstance(sValue, unicode): sValue = str(sValue) keyValue = query.value(1) self.values[keyValue] = [sValue] query.finish() return True else: DbError(query) return False
[docs] def fill(self): '''fill the QComboBox with the values''' if self.values != {}: self.inputWidget.clear() for keyValue, valueArray in self.values.iteritems(): sValue = valueArray[0] self.inputWidget.addItem(sValue, keyValue) #sort the comboBox model = self.inputWidget.model() proxy = QtGui.QSortFilterProxyModel(self.inputWidget) proxy.setSourceModel(model) model.setParent(proxy) model.sort(0)
[docs] def prepareCompleter(self): '''user can type in comboBox, appropriate values are displayed''' completerList = [] for keyValue, valueArray in self.values.iteritems(): completerList.append(valueArray[0]) self.completer = QtGui.QCompleter(completerList) #values method of dict class self.completer.setCaseSensitivity(0) self.inputWidget.setCompleter(self.completer)
[docs] def setNull(self, setnull): thisValue = None if setnull: self.inputWidget.clear() else: self.fill() if self.attribute.hasDefault: if self.attribute.isTypeInt(): thisValue = int(self.getDefault()) elif self.attribute.isTypeChar(): thisValue = self.getDefault() self.setValue(thisValue)
[docs] def setValue(self, thisValue): if thisValue == None: self.inputWidget.setCurrentIndex(0) else: for i in range(self.inputWidget.count()): if self.inputWidget.itemData(i) == thisValue: self.inputWidget.setCurrentIndex(i) break
[docs] def setSearchValue(self, thisValue): '''search value is always type string (because it is stored in xml) foreign keys normally are of type int''' if self.attribute.isTypeInt(): try: thisValue = int(thisValue) except: thisValue = None self.setValue(thisValue)
[docs] def getValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.inputWidget.itemData(self.inputWidget.currentIndex()) return thisValue
[docs] def setupUi(self, parent, db): DdLineEdit.setupUi(self, parent, db) if self.readValues(db): self.fill() self.prepareCompleter()
[docs]class DdDateEdit(DdLineEdit): '''input widget (QDateEdit) for a date field''' def __init__(self, attribute): DdLineEdit.__init__(self, attribute) def __str__(self): return "<ddui.DdDateEdit %s>" % str(self.attribute.name)
[docs] def setValidator(self, min = None, max = None): '''set the min and max date if attribute has min/max use either the passed values or attributes min/max''' if self.attribute.min != None: thisMin = self.attribute.min if min != None: if min < self.attribute.min: thisMin = min self.inputWidget.setMinimumDate(thisMin) self.betweenWidget.setMinimumDate(thisMin) if self.attribute.max != None: thisMax = self.attribute.max if max != None: if max > self.attribute.max: thisMax = max self.inputWidget.setMaximumDate(thisMax) self.betweenWidget.setMaximumDate(thisMax)
[docs] def getFeatureValue(self, layer, feature): '''returns a QDate representing the value in this field for this feature''' if feature == None: return None if self.mode == 2 and not self.attribute.notNull: return None fieldIndex = self.getFieldIndex(layer) thisValue = feature[fieldIndex] if thisValue == QtCore.QDate(): thisValue = None if thisValue == None: if self.mode != 1: # no return value for search feature if feature.id() < 0 and self.attribute.hasDefault: thisValue = self.getDefault().toDate() else: if self.attribute.notNull: thisValue = QtCore.QDate.currentDate() if isinstance(thisValue, unicode): if thisValue.find("now") != -1 or thisValue.find("current_date") != -1: thisValue = QtCore.QDate.currentDate() return thisValue
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QDateEdit(parent) inputWidget.setCalendarPopup(True) loc = QtCore.QLocale.system() inputWidget.setDisplayFormat(loc.dateFormat()) inputWidget.setObjectName("dat" + parent.objectName() + self.attribute.name) inputWidget.setToolTip(self.attribute.comment) inputWidget.dateChanged.connect(self.registerChange) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(inputWidget.sizePolicy().hasHeightForWidth()) inputWidget.setSizePolicy(sizePolicy) betweenWidget = QtGui.QDateEdit(parent) betweenWidget.setCalendarPopup(True) betweenWidget.setDisplayFormat(loc.dateFormat()) betweenWidget.setObjectName("dat" + parent.objectName() + self.attribute.name + "between") betweenWidget.dateChanged.connect(self.registerChange) return [inputWidget, betweenWidget]
[docs] def setNull(self, setnull): if setnull: if self.attribute.max != None: thisValue = self.attribute.max else: thisValue = self.inputWidget.maximumDate() else: if self.attribute.hasDefault: thisValue = self.getDefault() thisValue = QtCore.QDate.fromString(thisValue, self.attribute.dateFormat) else: thisValue = None self.setBetweenValue(thisValue) self.setValue(thisValue)
[docs] def setSearchValue(self, thisValue): '''sets the search value''' if thisValue != None: if isinstance(thisValue, unicode) or isinstance(thisValue, str): # this is the case when derived from XML newDate = QtCore.QDate.fromString(thisValue, "yyyy-MM-dd") if newDate.isNull(): thisValue = None else: thisValue = newDate self.setValue(thisValue)
[docs] def setBetweenValue(self, thisValue): '''sets the between value''' newDate = QtCore.QDate.currentDate() if thisValue != None: if isinstance(thisValue, unicode) or isinstance(thisValue, str): # this is the case when derived from XML newDate = QtCore.QDate.fromString(thisValue, "yyyy-MM-dd") if newDate.isNull(): newDate = QtCore.QDate.currentDate() self.betweenWidget.setDate(newDate)
[docs] def toString(self, thisValue): return self.dateToString(thisValue)
[docs] def setValue(self, thisValue): if not thisValue: # i.e. None newDate = QtCore.QDate.currentDate() else: newDate = thisValue self.inputWidget.setDate(newDate)
[docs] def getValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.inputWidget.date() return thisValue
[docs] def getBetweenValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.betweenWidget.date() return thisValue
[docs]class DdLineEditBoolean(DdLineEdit): '''input widget Radio Buttons labeld with yes/no for a boolean field''' def __init__(self, attribute): DdLineEdit.__init__(self, attribute) self.inputWidgetYes = None self.inputWidgetNo = None def __str__(self): return "<ddui.DdLineEditBoolean %s>" % str(self.attribute.name)
[docs] def getFeatureValue(self, layer, feature): '''returns a boolean representing the value in this field for this feature''' if feature == None: return None if self.mode == 2 and not self.attribute.notNull: return None fieldIndex = self.getFieldIndex(layer) thisValue = feature[fieldIndex] if thisValue: if thisValue == "f" or thisValue == "false" or thisValue == "False": thisValue = False else: thisValue = True if feature.id() < 0 and thisValue == None: # new feature and no value set if self.mode != 1: # no return value for search feature if self.attribute.hasDefault: thisValue = bool(self.getDefault()) return thisValue
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QWidget(parent) hLayout = QtGui.QHBoxLayout(inputWidget) inputWidget.setObjectName("frm" + parent.objectName() + self.attribute.name) self.inputWidgetYes = QtGui.QRadioButton(inputWidget) self.inputWidgetYes.setObjectName("radYes" + parent.objectName() + self.attribute.name) self.inputWidgetYes.toggled.connect(self.registerChange) yesEventFilter = DdEventFilter(self.inputWidgetYes) self.inputWidgetYes.installEventFilter(yesEventFilter) yesEventFilter.doubleClicked.connect(self.onDoubleClick) labelYes = QtGui.QLabel(QtGui.QApplication.translate( "DdLabel", "Yes", None, QtGui.QApplication.UnicodeUTF8), inputWidget) labelYes.setObjectName("lblYes" + parent.objectName() + self.attribute.name) hLayout.addWidget(labelYes) hLayout.addWidget(self.inputWidgetYes) self.inputWidgetNo = QtGui.QRadioButton(inputWidget) self.inputWidgetNo.setObjectName("radNo" + parent.objectName() + self.attribute.name) self.inputWidgetNo.toggled.connect(self.registerChange) noEventFilter = DdEventFilter(self.inputWidgetNo) self.inputWidgetNo.installEventFilter(noEventFilter) noEventFilter.doubleClicked.connect(self.onDoubleClick) labelNo = QtGui.QLabel(QtGui.QApplication.translate( "DdLabel", "No", None, QtGui.QApplication.UnicodeUTF8), inputWidget) labelNo.setObjectName("lblNo" + parent.objectName() + self.attribute.name) hLayout.addWidget(labelNo) hLayout.addWidget(self.inputWidgetNo) return [inputWidget, None]
[docs] def setupUi(self, parent, db): '''setup the label and add the inputWidget to parents formLayout''' self.label = self.createLabel(parent) self.label.setAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft) hLayout = QtGui.QHBoxLayout() hLayout.setObjectName( "hLayout" + parent.objectName() + self.attribute.name) self.searchCbx = QtGui.QComboBox(parent) self.searchEventFilter = DdEventFilter(self.searchCbx) self.searchCbx.installEventFilter(self.searchEventFilter) self.searchEventFilter.doubleClicked.connect(self.onDoubleClick) searchItems = ["=", "!="] if not self.attribute.notNull: searchItems += ["IS NULL", "IS NOT NULL"] self.searchCbx.addItems(searchItems) self.searchCbx.currentIndexChanged.connect(self.searchCbxChanged) hLayout.addWidget(self.searchCbx) inputWidgets = self.createInputWidget(parent) self.inputWidget = inputWidgets[0] self.eventFilter = DdEventFilter(self.inputWidget) self.inputWidget.installEventFilter(self.eventFilter) self.eventFilter.doubleClicked.connect(self.onDoubleClick) self.inputWidget.setToolTip(self.attribute.comment) hLayout.addWidget(self.inputWidget) self.chk = QtGui.QCheckBox(QtGui.QApplication.translate("DdInfo", "Null", None, QtGui.QApplication.UnicodeUTF8), parent) self.chk.setObjectName("chk" + parent.objectName() + self.attribute.name) self.chk.setToolTip(QtGui.QApplication.translate("DdInfo", "Check if you want to save an empty (or null) value.", None, QtGui.QApplication.UnicodeUTF8)) self.chk.stateChanged.connect(self.chkStateChanged) self.chk.setVisible(not self.attribute.notNull) hLayout.addItem(QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)) hLayout.addWidget(self.chk) newRow = parent.layout().rowCount() + 1 parent.layout().setWidget(newRow, QtGui.QFormLayout.LabelRole, self.label) parent.layout().setLayout(newRow, QtGui.QFormLayout.FieldRole, hLayout)
[docs] def setNull(self, setnull): '''Set this inputWidget to NULL''' thisValue = None if not setnull: if self.attribute.hasDefault: thisValue = self.getDefault() if thisValue == "true": thisValue = True else: thisValue = False self.setValue(thisValue)
[docs] def setValue(self, thisValue): if isinstance(thisValue, str): # happens whe read from XML if thisValue.upper() == "TRUE": thisValue = True elif thisValue.upper() == "FALSE": thisValue = False else: thisValue = None if None == thisValue: #handle Null values self.inputWidgetYes.setChecked(False) # false self.inputWidgetNo.setChecked(False) # false else: self.inputWidgetYes.setChecked(thisValue) self.inputWidgetNo.setChecked(not thisValue)
[docs] def getValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.inputWidgetYes.isChecked() return thisValue
# class DdCheckBox is deprecated and replaced by DdLineEditBoolean
[docs]class DdCheckBox(DdLineEditBoolean): '''subclass DdLineEditBoolean to issue a deprecation warning''' def __init__(self, attribute): from warnings import warn warn("The 'DdCheckBox' class was renamed to 'DdLineEditBoolean'", DeprecationWarning) DdLineEditBoolean.__init__(self, attribute)
[docs]class DdTextEdit(DdLineEdit): '''input widget (QTextEdit) for a text field''' def __init__(self, attribute): DdLineEdit.__init__(self, attribute) def __str__(self): return "<ddui.DdTextEdit %s>" % str(self.attribute.name)
[docs] def registerChange(self): self.hasChanges = True
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QPlainTextEdit(parent) inputWidget.setTabChangesFocus(True) inputWidget.setObjectName("txt" + parent.objectName() + self.attribute.name) inputWidget.textChanged.connect(self.registerChange) return [inputWidget, None]
[docs] def setValue(self, thisValue): if thisValue == None: thisValue = "" self.inputWidget.setPlainText(thisValue)
[docs] def getValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.inputWidget.toPlainText() if thisValue == '': thisValue = None return thisValue
[docs]class DdN2mWidget(DdInputWidget): '''abstract class for any n-to-m relation''' def __init__(self, attribute): DdInputWidget.__init__(self, attribute) self.tableLayer = None self.oldSubsetString = "" self.featureId = [] self.forEdit = False self.timer = QtCore.QTimer() self.timer.setSingleShot(True) self.timer.timeout.connect(self.forwardAccept) self.uncheckedItems = {} # dicts to store possible values self.checkedItems = {} self.childs = {} # dict to store all childs self.columnWidths = [] # for tables store width of columns
[docs] def setSizeMax(self, widget): sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(widget.sizePolicy().hasHeightForWidth()) widget.setSizePolicy(sizePolicy)
[docs] def setupUi(self, parent, db): frame = QtGui.QFrame(parent) frame.setFrameShape(QtGui.QFrame.NoFrame) #make it invisible frame.setFrameShadow(QtGui.QFrame.Raised) frame.setObjectName("frame" + parent.objectName() + self.attribute.name) label = self.createLabel(frame) inputWidgets = self.createInputWidget(frame) self.inputWidget = inputWidgets[0] self.setSizeMax(frame) self.inputWidget.setToolTip(self.attribute.comment) vLayout = QtGui.QVBoxLayout(frame) hLayout = QtGui.QHBoxLayout() vLayout.setObjectName( "vLayout" + parent.objectName() + self.attribute.name) hLayout.addWidget(label) spacerItem = QtGui.QSpacerItem(20, 20, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Minimum) hLayout.addItem(spacerItem) self.filterButton = QtGui.QPushButton(frame) self.filterButton.setObjectName("btn" + parent.objectName() + \ self.attribute.name + "_filterButton") filterIcon = QtGui.QIcon(":/filter.png") self.filterButton.setIcon(filterIcon) self.filterButton.setToolTip( QtGui.QApplication.translate("DdInfo", "Apply filter", None, QtGui.QApplication.UnicodeUTF8)) self.filterButton.clicked.connect(self.filter) self.filterButton.setEnabled(False) hLayout.addWidget(self.filterButton) self.removeFilterButton = QtGui.QPushButton(frame) self.removeFilterButton.setObjectName("btn" + parent.objectName() + \ self.attribute.name + "_removeFilterButton") removeFilterIcon = QtGui.QIcon(":/remove_filter.png") self.removeFilterButton.setIcon(removeFilterIcon) self.removeFilterButton.setToolTip( QtGui.QApplication.translate("DdInfo", "Remove filter", None, QtGui.QApplication.UnicodeUTF8)) self.removeFilterButton.clicked.connect(self.removeFilter) self.removeFilterButton.setEnabled(False) hLayout.addWidget(self.removeFilterButton) self.filterText = QtGui.QLineEdit(frame) self.filterText.setObjectName("txl" + parent.objectName() + \ self.attribute.name + "_filter") self.filterText.setToolTip( QtGui.QApplication.translate("DdInfo", "Enter filter expression", None, QtGui.QApplication.UnicodeUTF8)) self.filterText.textChanged.connect(self.filterTxlChanged) self.filterText.returnPressed.connect(self.filter) hLayout.addWidget(self.filterText) vLayout.addLayout(hLayout) vLayout.addWidget(self.inputWidget) newRow = parent.layout().rowCount() + 1 parent.layout().setWidget(newRow, QtGui.QFormLayout.SpanningRole, frame) pParent = parent while (True): pParent = pParent.parentWidget() if isinstance(pParent, DdDialog) or isinstance(pParent, DdSearchDialog): self.parentDialog = pParent break
#def reset(self): # self.applySubsetString(True)
[docs] def forwardAccept(self): '''Slot to be called from self.timer''' self.parentDialog.setForwardReturn()
[docs] def filterTxlChanged(self, newText): self.filterButton.setEnabled(newText.strip() != "") self.parentDialog.setForwardReturn(False)
[docs] def removeFilter(self): self.filterText.textChanged.disconnect(self.filterTxlChanged) self.filterText.setText("") self.filterText.textChanged.connect(self.filterTxlChanged) self.filterText.setFocus() self.removeFilterButton.setEnabled(False) self.fill()
[docs] def filter(self): '''Slot when user clicks on filter button''' self.removeFilterButton.setEnabled(True) self.filterButton.setEnabled(False) self.fill() self.filterText.setFocus() self.timer.start(10 * \ (len(self.uncheckedItems) + len(self.checkedItems)))
# timer is needed so that returnPress event is not pending anymore # when self.parentDialog.setForwardReturn() is called # otherwise pressing return will close the dialog, too
[docs] def initialize(self, layer, feature, db, mode = 0): '''This method needs to be called by all subclasses!''' self.uncheckedItems = {} self.checkedItems = {} self.mode = mode # default mode is 0 = single feature edit, mode = 1: search mode, mode = 2 multi edit if feature != None: self.featureId = [] if self.mode == 2: for anId in layer.selectedFeaturesIds(): if anId >= 0: self.featureId.append(anId) if self.featureId == []: self.featureId = [self.getPk(feature, layer)] if self.mode == 1: #search ui self.forEdit = True elif self.mode == 2: #disable for multi-edit mode self.forEdit = False else: self.forEdit = self.featureId[0] > 0 if self.forEdit: self.forEdit = layer.isEditable()
[docs] def initializeLayer(self, layer, feature, db, doShowParents = False, withMask = False, skip = []): from warnings import warn warn("The 'DdN2mWidget.initializeLayer' method was renamed to 'initializeTableLayer'", DeprecationWarning) self.initializeTableLayer(self, db, doShowParents, withMask, skip)
[docs] def initializeTableLayer(self, db, doShowParents = False, withMask = False, skip = []): # find the layer in the project self.tableLayer = self.parentDialog.ddManager.findPostgresLayer(db, self.attribute.table) if not self.tableLayer: # load the layer into the project self.tableLayer = self.parentDialog.ddManager.loadPostGISLayer(db, self.attribute.table) if withMask: # create a DdUi for the layer without the featureIdField it it has not yet been created try: self.parentDialog.ddManager.ddLayers[self.tableLayer.id()] except KeyError: skip.append(self.attribute.relationFeatureIdField) self.parentDialog.ddManager.initLayer(self.tableLayer, skip = skip, \ showParents = doShowParents, searchMask = True, \ labels = {}, fieldOrder = [], fieldGroups = {}, minMax = {}, noSearchFields = [], \ createAction = True, db = None, inputMask = True, \ inputUi = None, searchUi = None, helpText = "") # reinitialize inputMask only self.oldSubsetString = self.tableLayer.subsetString() if self.mode != 1: #not search ui if self.forEdit: self.forEdit = self.tableLayer.isEditable() if not self.forEdit: self.forEdit = self.tableLayer.startEditing() if not self.forEdit: DdError(QtGui.QApplication.translate("DdInfo", "Layer cannot be edited: ", None, QtGui.QApplication.UnicodeUTF8) + self.tableLayer.name(), showInLog = True)
[docs] def keyForChild(self, parentId): return self.childs[parentId][0].lower()
[docs] def prepareFeatureIdForSubsetString(self): idList = "" for i in range(len(self.featureId)): anId = self.featureId[i] if i == 0: idList += str(anId) else: idList += "," + str(anId) return idList
[docs] def applySubsetString(self, reset = True): if self.tableLayer != None: if reset: if self.tableLayer.setSubsetString(self.oldSubsetString): self.tableLayer.reload() return True else: # reduce the features in self.tableLayer to those related to feature idList = self.prepareFeatureIdForSubsetString() subsetString = self.attribute.subsetString + "(" + idList + ")" if self.tableLayer.setSubsetString(subsetString): self.tableLayer.reload() return True return False
[docs] def createFeature(self, fid = None): '''create a new QgsFeature for the relation table with this fid''' if fid: newFeature = QgsFeature(fid) else: newFeature = QgsFeature() # gid wird automatisch vergeben provider = self.tableLayer.dataProvider() fields = self.tableLayer.pendingFields() newFeature.initAttributes(fields.count()) if self.mode != 1: for i in range(fields.count()): newFeature.setAttribute(i,provider.defaultValue(i)) return newFeature
[docs] def reset(self): '''save column widths''' for i in range(len(self.columnWidths)): thisWidth = self.inputWidget.columnWidth(i) self.columnWidths[i] = thisWidth
[docs] def save(self, layer, feature, db): if self.forEdit: if self.hasChanges: if self.tableLayer.isEditable(): if self.tableLayer.isModified(): if self.tableLayer.commitChanges(): return True else: DdError(QtGui.QApplication.translate("DdError", "Could not save changes for layer:", None, QtGui.QApplication.UnicodeUTF8) + " " + self.tableLayer.name()) self.discard() else: self.discard() else: self.discard() self.reset() return False
[docs] def discard(self): self.reset() if self.tableLayer.isEditable(): if not self.tableLayer.rollBack(): DdError(QtGui.QApplication.translate("DdError", "Could not discard changes for layer:", None, QtGui.QApplication.UnicodeUTF8) + " " + self.tableLayer.name()) return None
[docs]class DdN2mListWidget(DdN2mWidget): '''input widget (clickable QListWidget) for simple n2m relations''' #TODO: implement multi-edit mode def __init__(self, attribute): DdN2mWidget.__init__(self, attribute) def __str__(self): return "<ddui.DdN2mListWidget %s>" % str(self.attribute.name)
[docs] def registerChange(self, thisItem): if self.forEdit: featureIdField = self.tableLayer.fieldNameIndex(self.attribute.relationFeatureIdField) relatedIdField = self.tableLayer.fieldNameIndex(self.attribute.relationRelatedIdField) itemId = thisItem.id if itemId in self.uncheckedItems: itemText = self.uncheckedItems[itemId] del self.uncheckedItems[itemId] else: if itemId in self.checkedItems: itemText = self.checkedItems[itemId] del self.checkedItems[itemId] if thisItem.checkState() == 2: self.checkedItems[itemId] = itemText for anId in self.featureId: feat = self.createFeature() feat.setAttribute(featureIdField, anId) feat.setAttribute(relatedIdField, itemId) self.tableLayer.addFeature(feat, False) else: self.uncheckedItems[itemId] = itemText self.applySubsetString(False) self.tableLayer.selectAll() for aFeature in self.tableLayer.selectedFeatures(): for anId in self.featureId: if aFeature[featureIdField] == anId: if aFeature[relatedIdField] == itemId: idToDelete = aFeature.id() self.tableLayer.deleteFeature(idToDelete) self.applySubsetString(True) self.hasChanges = True else: # do not show any changes self.inputWidget.itemChanged.disconnect(self.registerChange) if thisItem.checkState() == 2: thisItem.setCheckState(0) else: thisItem.setCheckState(2) self.inputWidget.itemChanged.connect(self.registerChange)
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QListWidget(parent) # defaultInputWidget inputWidget.setObjectName("lst" + parent.objectName() + self.attribute.name) inputWidget.itemChanged.connect(self.registerChange) return [inputWidget, None]
[docs] def initialize(self, layer, feature, db, mode = 0): DdN2mWidget.initialize(self, layer, feature, db, mode) if feature != None: self.initializeTableLayer(db) query = QtSql.QSqlQuery(db) dispStatement = self.attribute.displayStatement if self.mode == 2: dispStatement = dispStatement.replace("ORDER BY checked DESC,","ORDER BY ") query.prepare(dispStatement) query.bindValue(":featureId", self.getPk(feature, layer)) query.exec_() if query.isActive(): self.inputWidget.clear() while query.next(): # returns false when all records are done parentId = int(query.value(0)) parent = unicode(query.value(1)) if self.mode == 2: checked = 0 else: checked = int(query.value(2)) if checked == 2: self.checkedItems[parentId] = parent else: self.uncheckedItems[parentId] = parent self.childs[parentId] = [parent] query.finish() self.fill() else: DbError(query)
[docs] def createWidgetItem(self, parentId, parent): parentItem = QtGui.QListWidgetItem(parent, self.inputWidget) parentItem.id = parentId return parentItem
[docs] def fill(self): ''' fill the listbox with items''' filterExpression = self.filterText.text().strip().lower() self.inputWidget.itemChanged.disconnect(self.registerChange) self.inputWidget.clear() for parentId in sorted(self.checkedItems, key = self.keyForChild): parent = self.checkedItems[parentId] parentItem = self.createWidgetItem(parentId, parent) parentItem.setCheckState(2) self.inputWidget.addItem(parentItem) for parentId in sorted(self.uncheckedItems, key = self.keyForChild): parent = self.uncheckedItems[parentId] doAdd = filterExpression == "" if not doAdd: doAdd = parent.lower().find(filterExpression) != -1 if doAdd: parentItem = self.createWidgetItem(parentId, parent) parentItem.setCheckState(0) self.inputWidget.addItem(parentItem) self.inputWidget.itemChanged.connect(self.registerChange)
[docs] def search(self, layer): searchSql = "" if self.hasChanges: layerPkList = layer.pendingPkAttributesList() ids = "" for i in range(self.inputWidget.count()): anItem = self.inputWidget.item(i) if anItem.checkState() == 2: if ids != "": ids += "," ids += str(anItem.id) if len(layerPkList) == 1 and ids != "": layerPkName = layer.dataProvider().fields()[layerPkList[0]].name() selfLayerPkName = self.attribute.relationFeatureIdField searchSql = "\"" + layerPkName + "\" IN (SELECT \"" + selfLayerPkName + "\" FROM \"" + \ self.attribute.table.schemaName + "\".\"" + self.attribute.table.tableName + "\" WHERE \"" + \ self.attribute.relationRelatedIdField + "\" IN (" + ids + "))" return searchSql
[docs] def createSearch(self, parentElement): fieldElement = None if self.hasChanges: fieldElement = ET.SubElement(parentElement, "field") fieldElement.set("fieldName", self.attribute.name) fieldElement.set("type", self.attribute.type) fieldElement.set("widgetType", "DdN2mListWidget") for i in range(self.inputWidget.count()): anItem = self.inputWidget.item(i) if anItem.checkState() == 2: valueElement = ET.SubElement(fieldElement, "value") valueElement.text = str(anItem.id) return fieldElement
[docs] def applySearch(self, parentElement): self.inputWidget.itemChanged.disconnect(self.registerChange) for fieldElement in parentElement.findall("field"): if fieldElement.get("fieldName") == self.attribute.name: for valueElement in fieldElement.findall("value"): thisValue = int(valueElement.text) for i in range(self.inputWidget.count()): anItem = self.inputWidget.item(i) if anItem.id == thisValue: anItem.setCheckState(2) self.hasChanges = True break self.inputWidget.itemChanged.connect(self.registerChange)
[docs]class DdN2mTreeWidget(DdN2mWidget): '''input widget (clickable QTreeWidget) for n2m relations with more than one additional field in the related table TreeWidget is initialized directly from the DB''' #TODO: implement multi-edit mode def __init__(self, attribute): DdN2mWidget.__init__(self, attribute) def __str__(self): return "<ddui.DdN2mTreeWidget %s>" % str(self.attribute.name)
[docs] def registerChange(self, thisItem, thisColumn): if thisColumn == 0: if self.forEdit: featureIdField = self.tableLayer.fieldNameIndex(self.attribute.relationFeatureIdField) relatedIdField = self.tableLayer.fieldNameIndex(self.attribute.relationRelatedIdField) itemId = thisItem.id if itemId in self.uncheckedItems: parent = self.uncheckedItems[itemId] del self.uncheckedItems[itemId] else: if itemId in self.checkedItems: parent = self.checkedItems[itemId] del self.checkedItems[itemId] if thisItem.checkState(0) == 2: self.checkedItems[itemId] = parent for anId in self.featureId: feat = self.createFeature() feat.setAttribute(featureIdField, anId) feat.setAttribute(relatedIdField, itemId) self.tableLayer.addFeature(feat, False) else: self.uncheckedItems[itemId] = parent self.applySubsetString(False) self.tableLayer.selectAll() for aFeature in self.tableLayer.selectedFeatures(): for anId in self.featureId: if aFeature[featureIdField] == anId: if aFeature[relatedIdField] == itemId: idToDelete = aFeature.id() self.tableLayer.deleteFeature(idToDelete) self.applySubsetString(True) self.hasChanges = True else: # do not show any changes self.inputWidget.itemChanged.disconnect(self.registerChange) if thisItem.checkState(0) == 2: thisItem.setCheckState(0, 0) else: thisItem.setCheckState(0, 2) self.inputWidget.itemChanged.connect(self.registerChange)
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QTreeWidget(parent) inputWidget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) inputWidget.setHeaderHidden(True) inputWidget.setColumnCount(1) inputWidget.setObjectName("tre" + parent.objectName() + self.attribute.name) inputWidget.itemChanged.connect(self.registerChange) return [inputWidget, None]
[docs] def initialize(self, layer, feature, db, mode = 0): DdN2mWidget.initialize(self, layer, feature, db, mode) if feature != None: self.initializeTableLayer(db) query = QtSql.QSqlQuery(db) dispStatement = self.attribute.displayStatement if self.mode == 2: dispStatement = dispStatement.replace("ORDER BY checked DESC,","ORDER BY ") query.prepare(dispStatement) query.bindValue(":featureId", self.getPk(feature, layer)) query.exec_() if query.isActive(): while query.next(): # returns false when all records are done parentId = int(query.value(0)) parent = unicode(query.value(1)) if self.mode == 2: checked = 0 else: checked = int(query.value(2)) childs = [parent] for i in range(len(self.attribute.fieldList) -1): val = query.value(i + 3) if val != None: childs.append(val) else: # no more fields left break if checked == 2: self.checkedItems[parentId] = parent else: self.uncheckedItems[parentId] = parent self.childs[parentId] = childs query.finish() self.fill() else: DbError(query)
[docs] def createWidgetItem(self, parentId, childs): parentItem = QtGui.QTreeWidgetItem(self.inputWidget) parentItem.id = parentId parent = childs[0] parentItem.setText(0, parent) for i in range(1, len(childs)): val = childs[i] childItem = QtGui.QTreeWidgetItem(parentItem) childItem.setText(0, val) parentItem.addChild(childItem) parentItem.setExpanded(False) return parentItem
[docs] def fill(self): filterExpression = self.filterText.text().strip().lower() self.inputWidget.itemChanged.disconnect(self.registerChange) self.inputWidget.clear() for parentId in sorted(self.checkedItems, key = self.keyForChild): childs = self.childs[parentId] parentItem = self.createWidgetItem(parentId, childs) parentItem.setCheckState(0, 2) self.inputWidget.addTopLevelItem(parentItem) for parentId in sorted(self.uncheckedItems, key = self.keyForChild): childs = self.childs[parentId] doAdd = filterExpression == "" if not doAdd: doAdd = childs[0].lower().find(filterExpression) != -1 if doAdd: parentItem = self.createWidgetItem(parentId, childs) parentItem.setCheckState(0, 0) self.inputWidget.addTopLevelItem(parentItem) self.inputWidget.itemChanged.connect(self.registerChange)
[docs] def search(self, layer): searchSql = "" if self.hasChanges: layerPkList = layer.pendingPkAttributesList() ids = "" for i in range(self.inputWidget.topLevelItemCount() -1): anItem = self.inputWidget.topLevelItem(i) if anItem.checkState(0) == 2: if ids != "": ids += "," ids += str(anItem.id) if len(layerPkList) == 1 and ids != "": layerPkName = layer.dataProvider().fields()[layerPkList[0]].name() selfLayerPkName = self.attribute.relationFeatureIdField searchSql = "\"" + layerPkName + "\" IN (SELECT \"" + selfLayerPkName + "\" FROM \"" + \ self.attribute.table.schemaName + "\".\"" + self.attribute.table.tableName + "\" WHERE \"" + \ self.attribute.relationRelatedIdField + "\" IN (" + ids + "))" return searchSql
[docs] def createSearch(self, parentElement): fieldElement = None if self.hasChanges: fieldElement = ET.SubElement(parentElement, "field") fieldElement.set("fieldName", self.attribute.name) fieldElement.set("type", self.attribute.type) fieldElement.set("widgetType", "DdN2mTreeWidget") for i in range(self.inputWidget.topLevelItemCount() -1): anItem = self.inputWidget.topLevelItem(i) if anItem.checkState(0) == 2: ET.SubElement(fieldElement, "value").text = str(anItem.id) return fieldElement
[docs] def applySearch(self, parentElement): self.inputWidget.itemChanged.disconnect(self.registerChange) for fieldElement in parentElement.findall("field"): if fieldElement.get("fieldName") == self.attribute.name: for valueElement in fieldElement.findall("value"): thisValue = int(valueElement.text) for i in range(self.inputWidget.topLevelItemCount() -1): anItem = self.inputWidget.topLevelItem(i) if anItem.id == thisValue: anItem.setCheckState(0, 2) self.hasChanges = True break break self.inputWidget.itemChanged.connect(self.registerChange)
[docs]class DdN2mTableWidget(DdN2mWidget): '''a input widget for n-to-m relations with more than one field in the relation table The input widget consists of a QTableWidget and an add (+) and a remove (-) button''' #TODO: implement multi-edit mode def __init__(self, attribute): DdN2mWidget.__init__(self, attribute) self.fkValues = {} self.root = ET.Element('DdSearch') self.ddManager = QgsApplication.instance().ddManager def __str__(self): return "<ddui.DdN2mTableWidget %s>" % str(self.attribute.name)
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QTableWidget(parent) inputWidget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) inputWidget.setColumnCount(len(self.attribute.attributes)) fieldNames = [] for anAtt in self.attribute.attributes: fieldNames.append(anAtt.getLabel()) horizontalHeaders = fieldNames inputWidget.setHorizontalHeaderLabels(horizontalHeaders) inputWidget.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) inputWidget.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) inputWidget.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) inputWidget.setSortingEnabled(True) inputWidget.cellDoubleClicked.connect(self.doubleClick) inputWidget.itemSelectionChanged.connect(self.selectionChanged) inputWidget.setObjectName("tbl" + parent.objectName() + self.attribute.name) return [inputWidget, None]
[docs] def fill(self): self.inputWidget.setRowCount(0) if self.mode == 1: ddTable = self.ddManager.makeDdTable(self.tableLayer) thisFeature = self.createFeature(-3333) for tableElement in self.root.findall("table"): if tableElement.get("tableName") == ddTable.tableName: for fieldElement in tableElement.findall("field"): for anAttr in self.attribute.attributes: attName = anAttr.name if fieldElement.get("fieldName") == attName: operatorElement = fieldElement.find("operator") searchString = "" if operatorElement != None: operator = operatorElement.text valueElement = fieldElement.find("value") if valueElement != None and \ operator != "IS NULL" and \ operator != "IS NOT NULL": textValue = valueElement.text if anAttr.isFK: values = self.fkValues[anAttr.name] if anAttr.isTypeInt(): lookupValue = int(textValue) elif anAttr.isTypeFloat(): lookupValue = float(textValue) else: lookupValue = textValue try: aValue = values[lookupValue] except KeyError: aValue = textValue if not isinstance(aValue, unicode): aValue = str(aValue) else: aValue = textValue if operator == "BETWEEN...AND...": bvalueElement = fieldElement.find("bvalue") if bvalueElement != None: operator = "BETWEEN" aValue += " AND " + bvalueElement.text else: aValue = "" searchString = operator + " " + aValue thisFeature[self.tableLayer.fieldNameIndex(attName)] = searchString break self.appendRow(thisFeature) self.addButton.setEnabled(False) elif self.mode == 2: self.addButton.setEnabled(False) else: self.applySubsetString(False) # display the features in the QTableWidget for aFeat in self.tableLayer.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)): self.appendRow(aFeat) if self.forEdit: if self.attribute.maxRows: self.addButton.setEnabled(self.inputWidget.rowCount() < self.attribute.maxRows) else: self.addButton.setEnabled(False) # reset here in case the same table is connected twice self.applySubsetString(True)
[docs] def getFkValues(self, db): for anAtt in self.attribute.attributes: if anAtt.isFK: values = {} if not anAtt.notNull: values[""] = None query = QtSql.QSqlQuery(db) query.prepare(anAtt.queryForCbx) query.exec_() if query.isActive(): while query.next(): # returns false when all records are done sValue =query.value(0) keyValue = query.value(1) values[keyValue] = sValue query.finish() else: DbError(query) self.fkValues[anAtt.name] = values
[docs] def initialize(self, layer, feature, db, mode = 0): DdN2mWidget.initialize(self, layer, feature, db, mode) if feature != None: if self.mode == 1: self.root = ET.Element('DdSearch') self.initializeTableLayer(db, self.attribute.showParents, withMask = True) # read the values for any foreignKeys self.getFkValues(db) self.fill() for i in range(len(self.columnWidths)): thisWidth = self.columnWidths[i] if thisWidth != None: self.inputWidget.setColumnWidth(i, thisWidth)
[docs] def getFeatureValues(self, thisFeature): '''get a string representation of the values of this feature, None values are returned as "NULL" ''' fetureValues = [] for i in range(len(self.attribute.attributes)): anAtt = self.attribute.attributes[i] try: aValue = thisFeature[self.tableLayer.fieldNameIndex(anAtt.name)] except: DdError("Field " + anAtt.name + " not found!") continue if self.mode == 1: if isinstance(aValue, QtCore.QPyNullVariant): aValue = "" else: if isinstance(aValue, QtCore.QPyNullVariant): aValue = 'NULL' else: if anAtt.isFK and len(self.fkValues) > 0: values = self.fkValues[anAtt.name] try: aValue = values[aValue] except KeyError: aValue = 'NULL' if anAtt.type == "bool": if not isinstance(aValue, bool): aValue = (aValue == "t" or aValue == "true" or aValue == "True") aValue = self.boolToString(aValue) fetureValues.append(aValue) return fetureValues
[docs] def createTableWidgetItem(self, aValue): if isinstance(aValue, int) or isinstance(aValue, float): item = QtGui.QTableWidgetItem() item.setData(QtCore.Qt.DisplayRole, aValue) else: if isinstance(aValue, QtCore.QDate): aValue = self.dateToString(aValue) elif isinstance(aValue, bool): aValue = self.boolToString(aValue) else: aValue = unicode(aValue) item = QtGui.QTableWidgetItem(aValue) return item
[docs] def fillRow(self, thisRow, thisFeature): '''fill thisRow with values from thisFeature''' values = self.getFeatureValues(thisFeature) for i in range(len(values)): aValue = values[i] item = self.createTableWidgetItem(aValue) if i == 0: item.feature = thisFeature self.inputWidget.setItem(thisRow, i, item)
[docs] def appendRow(self, thisFeature): '''add a new row to the QTableWidget''' thisRow = self.inputWidget.rowCount() # identical with index of row to be appended as row indices are 0 based self.inputWidget.setRowCount(thisRow + 1) # append a row self.fillRow(thisRow, thisFeature)
[docs] def setupUi(self, parent, db): frame = QtGui.QFrame(parent) frame.setFrameShape(QtGui.QFrame.StyledPanel) frame.setFrameShadow(QtGui.QFrame.Raised) frame.setObjectName("frame" + parent.objectName() + self.attribute.name) label = self.createLabel(frame) inputWidgets = self.createInputWidget(frame) self.inputWidget = inputWidgets[0] self.setSizeMax(frame) self.inputWidget.setToolTip(self.attribute.comment) vLayout = QtGui.QVBoxLayout(frame) vLayout.setObjectName( "vLayout" + parent.objectName() + self.attribute.name) hLayout = QtGui.QHBoxLayout() hLayout.setObjectName( "hLayout" + parent.objectName() + self.attribute.name) hLayout.addWidget(label) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) hLayout.addItem(spacerItem) self.addButton = QtGui.QPushButton(QtGui.QApplication.translate("DdInput", "Add", None, QtGui.QApplication.UnicodeUTF8) , frame) self.removeButton = QtGui.QPushButton(QtGui.QApplication.translate("DdInput", "Remove", None, QtGui.QApplication.UnicodeUTF8) , frame) self.addButton.clicked.connect(self.add) self.removeButton.clicked.connect(self.remove) self.removeButton.setEnabled(False) hLayout.addWidget(self.addButton) hLayout.addWidget(self.removeButton) vLayout.addLayout(hLayout) vLayout.addWidget(self.inputWidget) newRow = parent.layout().rowCount() + 1 parent.layout().setWidget(newRow, QtGui.QFormLayout.SpanningRole, frame) pParent = parent while (True): pParent = pParent.parentWidget() if isinstance(pParent, DdDialog) or isinstance(pParent, DdSearchDialog): self.parentDialog = pParent break for i in range(len(self.attribute.attributes)): self.columnWidths.append(None)
[docs] def search(self, layer): '''create search sql-string''' searchSql = "" ddTable = self.ddManager.makeDdTable(self.tableLayer) if self.inputWidget.rowCount() == 1: for tableElement in self.root.findall("table"): if tableElement.get("tableName") == ddTable.tableName: for fieldElement in tableElement.findall("field"): fieldName = fieldElement.get("fieldName") for anAttribute in self.attribute.attributes: if anAttribute.name == fieldName: if searchSql == "": searchSql = self.attribute.pkAttName + " IN (SELECT \"" + \ self.attribute.relationFeatureIdField + "\"" searchSql += " FROM \"" + ddTable.schemaName + "\".\"" + ddTable.tableName + "\" WHERE " else: searchSql += " AND " operatorElement= fieldElement.find("operator") if operatorElement != None: operator = operatorElement.text thisValue = "" if operator != "IS NULL" and operator != "IS NOT NULL": valueElement = fieldElement.find("value") if valueElement != None: thisValue = valueElement.text if (anAttribute.isTypeInt() or anAttribute.isTypeFloat()): thisValue = thisValue elif anAttribute.isTypeChar(): if thisValue[:1] != "\'": thisValue = "\'" + thisValue + "\'" else: if anAttribute.type == "bool": if thisValue.upper() == "TRUE": thisValue = "\'t\'" else: thisValue = "\'f\'" elif anAttribute.type == "text": if thisValue[:1] != "\'": thisValue = "\'" + thisValue + "\'" elif anAttribute.type == "date": thisValue = "\'" + thisValue + "\'" if operator == "IN": searchSql += "\"" + anAttribute.name + "\" " + operator + " (" + thisValue + ")" elif operator == "BETWEEN...AND...": bvalueElement = fieldElement.find("bvalue") if bvalueElement != None: betweenValue = bvalueElement.text if betweenValue != None: if anAttribute.type == "date": betweenValue = "\'" + betweenValue + "\'" searchSql += "\"" + anAttribute.name + "\" BETWEEN " + thisValue + " AND " + betweenValue else: operator = ">=" searchSql += "\"" + anAttribute.name + "\" " + operator + " " + thisValue else: operator = ">=" searchSql += "\"" + anAttribute.name + "\" " + operator + " " + thisValue else: searchSql += "\"" + anAttribute.name + "\" " + operator + " " + thisValue break if searchSql != "": searchSql += ")" return searchSql
[docs] def createSearch(self, parentElement): ddTable = self.ddManager.makeDdTable(self.tableLayer) for tableElement in self.root.findall("table"): if tableElement.get("tableName") == ddTable.tableName: parentElement.append(tableElement) break
[docs] def applySearch(self, parentElement): ddTable = self.ddManager.makeDdTable(self.tableLayer) for tableElement in parentElement.findall("table"): if tableElement.get("tableName") == ddTable.tableName: self.root = ET.Element('DdSearch') self.root.append(tableElement) self.fill() break
# SLOTS
[docs] def selectionChanged(self): '''slot to be called when the QTableWidget's selection has changed''' if self.forEdit: self.removeButton.setEnabled( len(self.inputWidget.selectedItems()) > 0 and \ self.attribute.enableWidget)
[docs] def doubleClick(self, thisRow, thisColumn): '''slot to be called when the user double clicks on the QTableWidget''' if self.mode == 1: self.ddManager.setLastSearch(self.tableLayer, self.root) result = self.ddManager.showSearchForm(self.tableLayer) if result == 1: self.root = self.ddManager.ddLayers[self.tableLayer.id()][6] thisRow = self.inputWidget.currentRow() self.inputWidget.removeRow(thisRow) self.fill() if self.mode == 0: if self.attribute.enableWidget: featureItem = self.inputWidget.item(thisRow, 0) thisFeature = featureItem.feature result = self.ddManager.showFeatureForm(self.tableLayer, thisFeature, showParents = self.attribute.showParents) if result == 1: # user clicked OK # make sure user did not change parentFeatureId #self.tableLayer.changeAttributeValue(thisFeature.id(), # self.tableLayer.fieldNameIndex(self.attribute.relationFeatureIdField), self.featureId) # refresh thisFeature with the new values self.tableLayer.getFeatures( QgsFeatureRequest().setFilterFid(thisFeature.id()).setFlags( QgsFeatureRequest.NoGeometry)).nextFeature(thisFeature) self.fillRow(thisRow, thisFeature) self.hasChanges = True
[docs] def add(self): '''slot to be called when the user clicks on the add button''' if self.mode == 1: result = self.ddManager.showSearchForm(self.tableLayer) if result == 1: self.root = self.ddManager.ddLayers[self.tableLayer.id()][6] self.fill() elif self.mode == 0: thisFeature = self.createFeature() # set the parentFeature's id relationFeatFldIdx = self.tableLayer.fieldNameIndex(self.attribute.relationFeatureIdField) thisFeature[relationFeatFldIdx] = self.featureId[0] if self.tableLayer.addFeature(thisFeature): result = self.ddManager.showFeatureForm(self.tableLayer, thisFeature, askForSave = False) if result == 1: # user clicked OK self.fill()
[docs] def remove(self): '''slot to be called when the user clicks on the remove button''' thisRow = self.inputWidget.currentRow() if self.mode == 1: self.inputWidget.removeRow(thisRow) self.addButton.setEnabled(True) self.root = ET.Element('DdSearch') elif self.mode == 0: featureItem = self.inputWidget.takeItem(thisRow, 0) thisFeature = featureItem.feature self.tableLayer.deleteFeature(thisFeature.id()) self.inputWidget.removeRow(thisRow) self.hasChanges = True if self.attribute.maxRows: self.addButton.setEnabled(self.inputWidget.rowCount() < self.attribute.maxRows)
[docs] def enableAccordingToDdAttribute(self): if self.attribute.enableWidget: self.setEnabled(self.attribute.enableWidget) else: self.inputWidget.setEnabled(True) self.addButton.setEnabled(False)
[docs]class DdArrayTableWidget(DdLineEdit): '''a table widget to show/edit values of an array field''' def __init__(self, attribute): DdLineEdit.__init__(self, attribute) def __str__(self): return "<ddui.DdArrayTableWidget %s>" % str(self.attribute.name)
[docs] def setSizeMax(self, widget): sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(widget.sizePolicy().hasHeightForWidth()) widget.setSizePolicy(sizePolicy)
[docs] def getFeatureValue(self, layer, feature): '''returns an array[str] representing the value in this field for this feature; if the value is null, None is returned, if it is a new feature the default is returned.''' if feature == None: return None if self.mode == 2 and not self.attribute.notNull: return None fieldIndex = self.getFieldIndex(layer) thisValue = feature[fieldIndex] retValue = None if thisValue != None: if self.attribute.isTypeChar(): thisValue = thisValue.replace("\"", "") # QGIS adds " if string contains spaces thisValue = thisValue.replace("NULL", "\'\'") thisValue = thisValue.replace("{\'", "").replace("\'}", "") thisValue = thisValue.split("\'" + self.attribute.arrayDelim + "\'") else: thisValue = thisValue.replace("{", "").replace("}", "") thisValue = thisValue.split(self.attribute.arrayDelim) retValue = [] for aValue in thisValue: retValue.append(self.fromString(aValue)) if feature.id() < 0 and thisValue == None: # new feature if self.mode != 1: # no return value for search feature if self.attribute.hasDefault: retValue = self.getDefault() return retValue
[docs] def setValue(self, thisValue): '''sets thisValue into the input widget''' self.inputWidget.setRowCount(0) if thisValue != None: for aValue in thisValue: self.appendRow(aValue)
[docs] def extractValues(self): ''' extract the values from the cells; implementation for strings ''' thisValue = [] for aRow in range(self.inputWidget.rowCount()): nullChk = self.inputWidget.cellWidget( aRow, self.inputWidget.columnCount() - 1) if (self.mode != 1) and nullChk.isChecked(): thisValue.append(None) else: thisColumn = self.valueColumnIndex thisCellWidget = self.inputWidget.cellWidget(aRow, thisColumn) if thisCellWidget != None: aValue = self.extractCellWidgetValue(thisCellWidget) thisValue.append(aValue) else: aValue = self.inputWidget.item(aRow, thisColumn).text() thisValue.append(self.fromString(aValue)) if thisValue == []: thisValue = None return thisValue
[docs] def extractCellWidgetValue(self, thisCellWidget): lineEdit = thisCellWidget return self.fromString(lineEdit.text())
[docs] def getValue(self): if self.chk.isChecked(): thisValue = None else: thisValue = self.extractValues() return thisValue
[docs] def getDefault(self): '''function to strip quotation marks and data type from default string''' thisValue = self.attribute.default thisValue = thisValue.replace("ARRAY[", "") thisValue = thisValue[0:len(thisValue)-1] # drop closing ] for aType in ["::text", "::character varying"]: if thisValue.find(aType) != -1: thisType = aType thisValue = thisValue.replace(thisType, "") # strip type def returnValue = [] for aValue in thisValue.split(self.attribute.arrayDelim): aValue = aValue.strip() if self.attribute.isTypeChar: aValue = aValue.replace("'", "") returnValue.append(aValue) return returnValue
[docs] def setNull(self, setnull): '''Set this inputWidget to NULL''' thisValue = None if (not setnull) and (self.mode != 1) and self.attribute.hasDefault: thisValue = self.getDefault() self.setValue(thisValue)
[docs] def save(self, layer, feature, db): if self.hasChanges: fieldIndex = self.getFieldIndex(layer) thisValue = self.getValue() if thisValue == None: saveValue = None else: saveValue = "{" for aValue in thisValue: if aValue == None: aValue = "NULL" # save nulls in order to preserve the number of elements in array else: aValue = self.toSqlString(aValue) if saveValue != "{": saveValue += self.attribute.arrayDelim saveValue += aValue saveValue += "}" if saveValue == "{}": saveValue = None if self.mode == 0: layer.changeAttributeValue(feature.id(), fieldIndex, saveValue) elif self.mode == 2: for anId in layer.selectedFeaturesIds(): layer.changeAttributeValue(anId, fieldIndex, saveValue) return self.hasChanges
[docs] def createInputWidget(self, parent): inputWidget = QtGui.QTableWidget(parent) inputWidget.setColumnCount(2) self.valueColumnIndex = 0 inputWidget.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) inputWidget.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) #inputWidget.horizontalHeader().setVisible(False) inputWidget.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked) inputWidget.setSortingEnabled(True) inputWidget.cellChanged.connect(self.cellChanged) #inputWidget.cellDoubleClicked.connect(self.cellDoubleClicked) inputWidget.itemSelectionChanged.connect(self.selectionChanged) inputWidget.cellClicked.connect(self.cellClicked) inputWidget.setObjectName("tbl" + parent.objectName() + self.attribute.name) return [inputWidget, None]
[docs] def initialize(self, layer, feature, db, mode = 0): self.mode = mode if feature == None: self.manageChk(None) else: logicHeader = QtGui.QApplication.translate( "DdInfo", "logical", None, QtGui.QApplication.UnicodeUTF8) operatorHeader = QtGui.QApplication.translate( "DdInfo", "operator", None, QtGui.QApplication.UnicodeUTF8) valueHeader = QtGui.QApplication.translate( "DdInfo", "value", None, QtGui.QApplication.UnicodeUTF8) nullHeader = QtGui.QApplication.translate("DdInfo", "Null", None, QtGui.QApplication.UnicodeUTF8) if self.mode == 1: # searchFeature self.inputWidget.setColumnCount(4) self.inputWidget.hideColumn(3) self.inputWidget.setHorizontalHeaderLabels([ logicHeader, valueHeader, operatorHeader]) self.chk.setChecked(True) self.chk.setVisible(True) self.chk.setText(QtGui.QApplication.translate( "DdInfo", "Ignore", None, QtGui.QApplication.UnicodeUTF8)) self.chk.setToolTip(QtGui.QApplication.translate( "DdInfo", "Check if you want this field to be ignored in the search.", None, QtGui.QApplication.UnicodeUTF8)) self.valueColumnIndex = 1 else: self.inputWidget.setHorizontalHeaderLabels( [valueHeader, nullHeader]) thisValue = self.getFeatureValue(layer, feature) self.setValue(thisValue) #self.setValidator(min = thisValue, max = thisValue) # make sure the validator does not kick out an already existing value self.manageChk(thisValue) self.hasChanges = (feature.id() < 0) # register this change only for new feature
[docs] def validate(self, thisValue, layer, feature, showMsg = True): ''' checks if values are within min/max range (if defined) ''' accepted = True msgShown = False featureValue = self.getFeatureValue(layer, feature) if self.hasChanges: if thisValue != None: for i in range(len(thisValue)): aValue = thisValue[i] try: fValue = featureValue[i] except: fValue = None if aValue != None and aValue != fValue: if self.attribute.min != None: if aValue < self.attribute.min: if showMsg: QtGui.QMessageBox.warning( None, self.getLabel(), QtGui.QApplication.translate( "DdWarning", "Value ", None, QtGui.QApplication.UnicodeUTF8 ) + self.toString(aValue) + QtGui.QApplication.translate( "DdWarning", " is too small! Minimum is ", None, QtGui.QApplication.UnicodeUTF8 ) + self.toString(self.attribute.min)) msgShown = True accepted = False break if self.attribute.max != None: if aValue > self.attribute.max: if showMsg: QtGui.QMessageBox.warning( None, self.getLabel(), QtGui.QApplication.translate( "DdWarning", "Value ", None, QtGui.QApplication.UnicodeUTF8 ) + self.toString(aValue) + QtGui.QApplication.translate( "DdWarning", " is too large! Maximum is ", None, QtGui.QApplication.UnicodeUTF8 ) + self.toString(self.attribute.max)) msgShown = True accepted = False break else: if not self.chk.isChecked() and self.attribute.hasDefault: thisValue = self.getDefault() self.setValue(thisValue) return [accepted, msgShown]
[docs] def setSearchCombo(self, thisRow, link = None, operator = None): '''sets combo boxes for searching to columns 0 and 1''' combo = QtGui.QComboBox() searchItems = ["=", "!="] if self.attribute.isTypeChar(): searchItems += ["LIKE", "ILIKE"] else: if self.attribute.type != "bool": searchItems += [">", "<", ">=", "<="] li = [] for a in ["ANY", "ALL"]: for s in searchItems: li.append (s + " " + a) if not self.attribute.notNull: li.append("IS NULL") li.append ("IS NOT NULL") combo.addItems(li) if operator != None: idx = li.index(operator) combo.setCurrentIndex(idx) self.inputWidget.setCellWidget(thisRow, 2, combo) if thisRow > 0: li2 = ["AND", "OR"] combo2 = QtGui.QComboBox() combo2.addItems(li2) if link != None: idx2 = li2.index(link) combo2.setCurrentIndex(idx2) self.inputWidget.setCellWidget(thisRow, 0, combo2)
[docs] def getDefaultItemValue(self): '''returns the default value to be added for new items''' return ""
[docs] def setCellWidget(self, thisRow, aValue): '''replace the QTableWidget item with an appropriate QWidget''' lineEdit = QtGui.QLineEdit(self.inputWidget) if aValue == None: lineEdit.setText(self.getDefaultItemValue()) else: lineEdit.setText(self.toString(aValue)) lineEdit.textChanged.connect(self.registerChange) lineEdit.editingFinished.connect(self.focusLost) self.inputWidget.setCellWidget(thisRow, self.valueColumnIndex, lineEdit)
[docs] def setRowValue(self, thisRow, aValue, checkIfNon = True): ''' sets this row's value to thisValue; default implementation for strings ''' if aValue == None: item = QtGui.QTableWidgetItem("") else: item = QtGui.QTableWidgetItem(self.toString(aValue)) self.inputWidget.setItem(thisRow, self.valueColumnIndex, item) nullChk = QtGui.QCheckBox() nullChk.setChecked(aValue == None and checkIfNon) nullChk.stateChanged.connect(self.nullChkStateChanged) self.inputWidget.setCellWidget(thisRow, self.inputWidget.columnCount() - 1, nullChk)
[docs] def fillRow(self, thisRow, aValue, link = None, operator = None, checkIfNone = True): '''fill thisRow with aValue''' if self.mode == 1: self.setSearchCombo(thisRow, link, operator) self.setRowValue(thisRow, aValue, checkIfNone) self.inputWidget.resizeColumnToContents( self.valueColumnIndex)
[docs] def appendRow(self, aValue, link = None, operator = None, checkIfNone = True): '''add a new row to the QTableWidget''' thisRow = self.inputWidget.rowCount() # identical with index of row to be appended as row indices are 0 based self.inputWidget.setRowCount(thisRow + 1) # append a row self.fillRow(thisRow, aValue, link, operator, checkIfNone) return thisRow
[docs] def insertRow(self, thisRow, aValue, link = None, operator = None): '''add a new row to the QTableWidget''' self.inputWidget.insertRow(thisRow) # append a row self.fillRow(thisRow, aValue, link, operator) return thisRow
[docs] def setupUi(self, parent, db): frame = QtGui.QFrame(parent) frame.setFrameShape(QtGui.QFrame.StyledPanel) frame.setFrameShadow(QtGui.QFrame.Raised) frame.setObjectName("frame" + parent.objectName() + self.attribute.name) label = self.createLabel(frame) inputWidgets = self.createInputWidget(frame) self.inputWidget = inputWidgets[0] self.setSizeMax(frame) self.inputWidget.setToolTip(self.attribute.comment) vLayout = QtGui.QVBoxLayout(frame) vLayout.setObjectName( "vLayout" + parent.objectName() + self.attribute.name) hLayout = QtGui.QHBoxLayout() hLayout.setObjectName( "hLayout" + parent.objectName() + self.attribute.name) hLayout.addWidget(label) spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) hLayout.addItem(spacerItem) self.addButton = QtGui.QPushButton( QtGui.QApplication.translate("DdInput", "Add", None, QtGui.QApplication.UnicodeUTF8), frame) self.removeButton = QtGui.QPushButton( QtGui.QApplication.translate("DdInput", "Remove", None, QtGui.QApplication.UnicodeUTF8) , frame) self.addButton.clicked.connect(self.add) self.addButton.setEnabled(self.attribute.enableWidget) self.removeButton.clicked.connect(self.remove) self.removeButton.setEnabled(False) hLayout.addWidget(self.addButton) hLayout.addWidget(self.removeButton) self.chk = QtGui.QCheckBox(QtGui.QApplication.translate("DdInfo", "Null", None, QtGui.QApplication.UnicodeUTF8), parent) self.chk.setObjectName("chk" + parent.objectName() + self.attribute.name) self.chk.setToolTip(QtGui.QApplication.translate( "DdInfo", "Check if you want to save an empty (or null) value.", None, QtGui.QApplication.UnicodeUTF8)) self.chk.stateChanged.connect(self.chkStateChanged) self.chk.setVisible(not self.attribute.notNull) hLayout.addWidget(self.chk) vLayout.addLayout(hLayout) vLayout.addWidget(self.inputWidget) newRow = parent.layout().rowCount() + 1 parent.layout().setWidget(newRow, QtGui.QFormLayout.SpanningRole, frame)
[docs] def toSqlString(self, aValue): ''' convert aValue to a string representation that can be used in a SQL statement default implementation for string ''' retValue = "'" + self.toString(aValue) + "'" return retValue
[docs] def fromString(self, aValue): ''' convert aValue from its string representation to a value of the appropriate class default implementation for string ''' if aValue == "": # empty string considered NULL return None else: return aValue
[docs] def search(self, layer): '''create search sql-string''' searchSql = "" if not self.chk.isChecked(): thisValue = self.getValue() for aRow in range(len(thisValue)): operator = self.inputWidget.cellWidget(aRow, 2).currentText() if aRow > 0: link = self.inputWidget.cellWidget(aRow, 0).currentText() searchSql += " " + link + " " if operator == "IS NULL" or operator == "IS NOT NULL": searchSql += " \"" + self.attribute.name + "\" " + operator else: aValue = thisValue[aRow] searchSql += self.toSqlString(aValue) + \ operator + " (\"" + self.attribute.name + "\")" if searchSql != "": searchSql = "(" + searchSql + ")" return searchSql
[docs] def createSearch(self, parentElement): '''create the search XML''' fieldElement = None if not self.chk.isChecked(): thisValue = self.getValue() fieldElement = ET.SubElement(parentElement, "field") fieldElement.set("fieldName", self.attribute.name) fieldElement.set("type", self.attribute.type) fieldElement.set("widgetType", "DdArrayTableWidget") arrayElement = ET.SubElement(fieldElement, "array") for aRow in range(len(thisValue)): aValue = thisValue[aRow] valueElement = ET.SubElement(arrayElement, "entry") operator = self.inputWidget.cellWidget(aRow, 2).currentText() linkElement = ET.SubElement(valueElement, "link") if operator != "IS NULL" and operator != "IS NOT NULL": valueElement.set("value", self.toString(aValue)) if aRow > 0: link = self.inputWidget.cellWidget(aRow, 0).currentText() linkElement.text = link operatorElement = ET.SubElement(valueElement, "operator") operatorElement.text = operator return fieldElement
[docs] def applySearch(self, parentElement): notFound = True for fieldElement in parentElement.findall("field"): if fieldElement.get("fieldName") == self.attribute.name: arrayElement = fieldElement.find("array") if arrayElement != None: self.chk.setChecked(False) self.inputWidget.removeRow(0) # checking adds a row notFound = False for valueElement in arrayElement.findall("entry"): operator = valueElement.find("operator").text if operator == "IS NULL" or operator == "IS NOT NULL": aValue = None else: aValue = self.fromString(valueElement.get("value")) link = valueElement.find("link").text if link == "": link = None self.appendRow(aValue, link, operator) break if notFound: self.chk.setChecked(True)
#SLOTs
[docs] def nullChkStateChanged(self, newState): self.hasChanges = True nullChk = QgsApplication.instance().focusWidget() thisRow = self.inputWidget.rowAt(nullChk.pos().y()) self.inputWidget.removeRow(thisRow) if newState == QtCore.Qt.Checked: self.insertRow(thisRow, None) else: self.insertRow(thisRow, self.getDefaultItemValue())
[docs] def focusLost(self, thisRow = None): '''implementation in child classes''' if thisRow == None: lineEdit = QgsApplication.instance().focusWidget() thisRow = self.inputWidget.rowAt(lineEdit.pos().y()) if thisRow == None: # we left this DdWidget return None if thisRow == self.inputWidget.currentRow(): return None # do not do anything if we stay in the row thisColumn = self.valueColumnIndex lineEdit = self.inputWidget.cellWidget(thisRow, thisColumn) if lineEdit != None: thisValue = self.fromString(lineEdit.text()) operator = None link = None if self.mode == 1: operator = self.inputWidget.cellWidget(thisRow, 2).currentText() if thisRow > 0: link = self.inputWidget.cellWidget(thisRow, 0).currentText() self.inputWidget.removeRow(thisRow) self.insertRow(thisRow, thisValue, link, operator)
[docs] def cellClicked(self, thisRow, thisColumn): if thisColumn != self.valueColumnIndex: thisColumn = self.valueColumnIndex # dataColumn thisItem = self.inputWidget.item(thisRow, thisColumn) if thisItem != None: thisValue = thisItem.text() if thisValue == "": thisValue = None else: thisValue = self.fromString(thisValue) self.setRowValue(thisRow, None, checkIfNon = False) self.setCellWidget(thisRow, thisValue)
[docs] def cellChanged(self, thisRow, thisColumn): self.registerChange(None)
[docs] def chkStateChanged(self, newState): '''slot: disables the input widget if the null checkbox is checked and vice versa''' self.setNull(newState == QtCore.Qt.Checked) if self.mode == 1: self.inputWidget.setEnabled(newState == QtCore.Qt.Unchecked) self.addButton.setEnabled(newState == QtCore.Qt.Unchecked) if newState == QtCore.Qt.Unchecked: self.appendRow(None) else: if self.attribute.enableWidget: self.inputWidget.setEnabled( newState == QtCore.Qt.Unchecked) self.addButton.setEnabled( newState == QtCore.Qt.Unchecked) if newState == QtCore.Qt.Unchecked and \ self.inputWidget.rowCount() == 0: self.appendRow(None, checkIfNone = False) self.hasChanges = True
[docs] def selectionChanged(self): '''slot to be called when the QTableWidget's selection has changed''' self.removeButton.setEnabled(len(self.inputWidget.selectedItems()) > 0)
[docs] def add(self): '''slot to be called when the user clicks on the add button''' self.appendRow(self.getDefaultItemValue()) self.hasChanges = True
[docs] def remove(self): '''slot to be called when the user clicks on the remove button''' thisRow = self.inputWidget.currentRow() self.inputWidget.removeRow(thisRow) if self.mode == 1: if thisRow == 0: if self.inputWidget.rowCount() > 0: # we need to get rid of the combo box in the first column # therefore we refill the tableWidget completely thisValue = self.getValue() operators = [] links = [] for aRow in range(len(thisValue)): operators.append(self.inputWidget.cellWidget(aRow, 1).currentText()) links.append(self.inputWidget.cellWidget(aRow, 0).currentText()) self.inputWidget.clear() self.inputWidget.setRowCount(0) for aRow in range(len(thisValue)): aValue = thisValue[aRow] operator = operators[aRow] link = links[aRow] self.appendRow(aValue, link, operator) self.addButton.setEnabled(True) self.root = ET.Element('DdSearch') else: self.hasChanges = True
[docs]class DdArrayTableWidgetInt(DdArrayTableWidget): '''a table widget to show/edit values of an integer array field''' def __init__(self, attribute): DdArrayTableWidget.__init__(self, attribute) def __str__(self): return "<ddui.DdArrayTableWidgetInt %s>" % str(self.attribute.name)
[docs] def toString(self, thisValue): loc = QtCore.QLocale.system() return loc.toString(thisValue)
[docs] def toSqlString(self, aValue): retValue = str(aValue) return retValue
[docs] def fromString(self, aValue): try: newValue = int(aValue) except: loc = QtCore.QLocale.system() newValue, valid = loc.toInt(aValue) if not valid: newValue, valid = loc.toLongLong(aValue) if not valid: newValue = None return newValue
[docs] def getDefaultItemValue(self): if self.attribute.min != None: return self.attribute.min else: return 0
[docs] def setCellWidget(self, thisRow, aValue): lineEdit = QtGui.QLineEdit(self.inputWidget) if aValue == None: lineEdit.setText(self.toString(self.getDefaultItemValue())) else: lineEdit.setText(self.toString(aValue)) if self.mode != 1: validator = ddtools.getIntValidator(lineEdit, self.attribute, aValue, aValue) # pass aValue as min and max so existing values are # allowed no matter of attribute's min and max lineEdit.setValidator(validator) lineEdit.textChanged.connect(self.registerChange) lineEdit.editingFinished.connect(self.focusLost) self.inputWidget.setCellWidget(thisRow, self.valueColumnIndex, lineEdit)
[docs]class DdArrayTableWidgetDouble(DdArrayTableWidget): '''a table widget to show/edit values of an integer array field''' def __init__(self, attribute): DdArrayTableWidget.__init__(self, attribute) def __str__(self): return "<ddui.DdArrayTableWidgetDouble %s>" % str(self.attribute.name)
[docs] def getDefaultItemValue(self): if self.attribute.min != None: return self.attribute.min else: return 0
[docs] def setCellWidget(self, thisRow, aValue): lineEdit = QtGui.QLineEdit(self.inputWidget) if aValue == None: lineEdit.setText(self.toString(self.getDefaultItemValue())) else: lineEdit.setText(self.toString(aValue)) if self.mode != 1: validator = ddtools.getDoubleValidator(lineEdit, self.attribute, aValue, aValue) # pass aValue as min and max so existing values are # allowed no matter of attribute's min and max lineEdit.setValidator(validator) lineEdit.textChanged.connect(self.registerChange) lineEdit.editingFinished.connect(self.focusLost) self.inputWidget.setCellWidget(thisRow, self.valueColumnIndex, lineEdit)
[docs] def toString(self, thisValue): loc = QtCore.QLocale.system() return loc.toString(thisValue)
[docs] def toSqlString(self, aValue): retValue = str(aValue) return retValue
[docs] def fromString(self, aValue): try: newValue = float(aValue) except: loc = QtCore.QLocale.system() newValue, valid = loc.toDouble(aValue) if not valid: newValue = None return newValue
[docs]class DdArrayTableWidgetBool(DdArrayTableWidget): '''a table widget to show/edit values of a boolean array field''' def __init__(self, attribute): DdArrayTableWidget.__init__(self, attribute) def __str__(self): return "<ddui.DdArrayTableWidgetBool %s>" % str(self.attribute.name)
[docs] def extractCellWidgetValue(self, thisCellWidget): chk = thisCellWidget return chk.isChecked()
[docs] def getDefaultItemValue(self): return False
[docs] def setCellWidget(self, thisRow, aValue): chk = QtGui.QCheckBox(self.inputWidget) if aValue == None: chk.setChecked(self.getDefaultItemValue()) else: chk.setChecked(aValue) chk.stateChanged.connect(self.registerChange) chk.released.connect(self.focusLost) self.inputWidget.setCellWidget(thisRow, self.valueColumnIndex, chk)
[docs] def focusLost(self, thisRow = None): if thisRow == None: chk = QgsApplication.instance().focusWidget() thisRow = self.inputWidget.rowAt(chk.pos().y()) if thisRow == None: # we left this DdWidget return None if thisRow == self.inputWidget.currentRow(): return None thisColumn = self.valueColumnIndex chk = self.inputWidget.cellWidget(thisRow,thisColumn) if chk != None: thisValue = chk.isChecked() operator = None link = None if self.mode == 1: operator = self.inputWidget.cellWidget(aRow, 2).currentText() if thisRow > 0: link = self.inputWidget.cellWidget(aRow, 0).currentText() self.inputWidget.removeRow(thisRow) self.insertRow(thisRow, thisValue, link, operator)
[docs] def toSqlString(self, aValue): if aValue: retValue = "true" else: retValue = "false" return retValue
[docs] def fromString(self, aValue): if aValue == "True" or aValue == "true" or aValue == "t": newValue = True elif aValue == "False" or aValue == "false" or aValue == "f": newValue = False else: newValue = None return newValue
[docs]class DdArrayTableWidgetDate(DdArrayTableWidget): '''a table widget to show/edit values of a date array field''' def __init__(self, attribute): DdArrayTableWidget.__init__(self, attribute) def __str__(self): return "<ddui.DdArrayTableWidgetBool %s>" % str(self.attribute.name)
[docs] def extractCellWidgetValue(self, thisCellWidget): dateEdit = thisCellWidget return dateEdit.date()
[docs] def getDefaultItemValue(self): if self.attribute.min != None: return self.attribute.min else: return QtCore.QDate.currentDate()
[docs] def setCellWidget(self, thisRow, aValue): date = QtGui.QDateEdit(self.inputWidget) date.setCalendarPopup(True) loc = QtCore.QLocale.system() date.setDisplayFormat(loc.dateFormat()) if self.mode != 1: if self.attribute.min != None: thisMin = self.attribute.min if aValue != None: if aValue < self.attribute.min: thisMin = min date.setMinimumDate(thisMin) if self.attribute.max != None: thisMax = self.attribute.max if aValue != None: if aValue > self.attribute.max: thisMax = max date.setMaximumDate(thisMax) if aValue == None: date.setDate(self.getDefaultItemValue()) else: date.setDate(aValue) date.dateChanged.connect(self.registerChange) date.editingFinished.connect(self.focusLost) self.inputWidget.setCellWidget(thisRow, self.valueColumnIndex, date)
[docs] def focusLost(self, thisRow = None): if thisRow == None: date = QgsApplication.instance().focusWidget() thisRow = self.inputWidget.rowAt(date.pos().y()) if thisRow == None: # we left this DdWidget return None if thisRow == self.inputWidget.currentRow(): return None thisColumn = self.valueColumnIndex date = self.inputWidget.cellWidget(thisRow, thisColumn) if date != None: thisValue = date.date() operator = None link = None if self.mode == 1: operator = self.inputWidget.cellWidget(aRow, 2).currentText() if thisRow > 0: link = self.inputWidget.cellWidget(aRow, 0).currentText() self.inputWidget.removeRow(thisRow) self.insertRow(thisRow, thisValue, link, operator)
[docs] def toString(self, thisValue): loc = QtCore.QLocale.system() return loc.toString(thisValue)
[docs] def toSqlString(self, aValue): retValue = aValue.toString("yyyy-MM-dd") retValue = "\'" + retValue + "\'" return retValue
[docs] def fromString(self, aValue): newValue = QtCore.QDate.fromString(aValue,'yyyy-MM-dd') if newValue.isNull(): loc = QtCore.QLocale.system() newValue = loc.toDate(aValue) if newValue.isNull(): newValue = None return newValue
[docs]class DdLineEditGeometry(DdLineEditInt): def __init__(self, attribute): DdLineEditInt.__init__(self, attribute) def __str__(self): return "<ddui.DdLineEditGeometry %s>" % str(self.attribute.name)
[docs] def getFeatureValue(self, layer, feature): if feature == None or self.mode == 2: return None geom = feature.geometry() if geom == None: # possible if new feature from within another mask self.inputWidget.setVisible(False) self.label.setVisible(False) return None else: area = geom.area() if area > 0: self.label.setText(QtGui.QApplication.translate( "DdInfo", "GIS area", None, QtGui.QApplication.UnicodeUTF8)) return int(round(area)) else: length = int(geom.length()) if length > 0: self.label.setText(QtGui.QApplication.translate( "DdInfo", "GIS length", None, QtGui.QApplication.UnicodeUTF8)) return round(length) else: self.inputWidget.setVisible(False) self.label.setVisible(False) return None
[docs] def checkDefault(self, feature): return [True, None]
[docs] def checkInput(self, layer, feature): return True
[docs] def save(self, layer, feature, db): return True
[docs] def validate(self, thisValue, layer, feature, showMsg = True): return True