class UiDataManager(QObject):
    """ 
    Handle all interaction between the UI and Data Manager.
    Also stores data as formatted for the UI 

    @param QObject: inherits from QObject Class
    @type QObject: QObject
    """

    rDataChangedSignal = pyqtSignal()
    fDataChangedSignal = pyqtSignal()

    #logging
    global uilog
    uilog = Logger.setup(lf='uiLog')

    def __init__(self, iface, controller):
        """ 
        Initialise the UI Data Manager 

        @param iface: QgisInterface Abstract base class defining interfaces exposed by QgisApp  
        @type iface: Qgisinterface Object
        @param controller: instance of the plugins controller
        @type  controller: AimsUI.AimsClient.Gui.Controller
        """

        QObject.__init__(self)
        self._controller = controller
        self._iface = iface
        self.dm = None
        self._observers = []
        self.data = {
            FEEDS['AF']: {},
            FEEDS['AC']: {},
            FEEDS['AR']: {},
            FEEDS['GC']: {},
            FEEDS['GR']: {}
        }

        self.groups = ('Replace', 'AddLineage', 'ParcelReferenceData'
                       )  # more to come...

        self.rDataChangedSignal.connect(self._controller.rDataChanged)
        self.fDataChangedSignal.connect(self._controller.fDataChanged)

    def startDM(self):
        """
        Start running the DM observer thread and Listener
        (of the DM observer) thread when the plugin is enabled 
        """

        self.dm = DataManager()
        # common data obj
        self.DMData = DMData()
        dmObserver = DMObserver(self.DMData, self.dm)

        listener = Listener(self.DMData)
        self.connect(listener, SIGNAL('dataChanged'), self.dataUpdated)
        ####

        listener.start()
        dmObserver.start()
        uilog.info('dm started')

    def killDm(self):
        """
        Close DataManager at plugin unload
        """

        if self.dm:
            self.dm.close()

    ### Observer Methods ###
    def register(self, observer):
        """
        Listeners of the UIDataManager to regiester themselves to this method
        
        @param observer: listerning class
        @type  observer: AIMS class objects wishing to listen
        """

        self._observers.append(observer)

    @pyqtSlot()
    def dataUpdated(self, data=None, feedType=FEEDS['AR']):
        """
        Slot communicated to when Review data changed. Updates review layer and table data

        @param data: list of AIMS objects related for feed (as communicated in param feedtype)
        @type  data: list
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        """

        self.setData(data, feedType)
        for observer in self._observers:
            observer.notify(feedType)

    def exlopdeGroup(self):
        """
        key groups agianst the group id and groups of single features against each group
        resultant format == 
            {(groupId, groupObj): {addId: addObj, addId: addObj}, (gro...}} 
        """

        gDict = {}
        for gId, gFeats in self.data[FEEDS['GR']].items():
            fDict = {}
            for gFeat in gFeats.meta._entities:
                fDict[gFeat._changeId] = gFeat
            gDict[(gId, gFeats)] = fDict
        self.data[FEEDS['GR']] = gDict

    def idProperty(self, feedtype):
        """
        Returns the property that the each object 
        should derive its reference id from  

        @return: Reference to which id should be used to reference each feedtype  
        @rtype: string
        """

        if feedtype == FEEDS['AF']: return '_components_addressId'
        if feedtype == FEEDS['AR']: return '_changeId'
        if feedtype == FEEDS['GR']: return '_changeGroupId'

    def keyData(self, listofFeatures, feedtype):
        """ Key Data from Data Manager

        @param dataRefresh: list of AIMS objects related for feed (as communicated in param feedtype)
        @type  dataRefresh: list
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        """

        ### was if listofFeatures: but stop the queue being emptied to None
        if listofFeatures or listofFeatures == []:
            li = []
            keyId = self.idProperty(feedtype)
            li = dict((getattr(feat, keyId), feat) for feat in listofFeatures)
            self.data[feedtype] = li

            # [GroupKey:{AdKey:}]
        if feedtype == FEEDS['GR']:
            # key group objects
            self.exlopdeGroup()

    def setData(self, dataRefresh, FeedType):
        # redundant? now straight to keyData?
        """ 
        Method receives new data from the data manager via the UIDM
        observer pattern and then starts the data update process        

        @param dataRefresh: list of AIMS objects related for feed (as communicated in param feedtype)
        @type  dataRefresh: list
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        """

        self.keyData(dataRefresh, FeedType)

    def updateRdata(self, respFeature, feedType):
        """
        Between dm threaded interval data deliveries, temp
        AIMS review objects are created or render irrelevant 
        by user actions. This method updates the main data (self._data)
        to reflect these changes. 
        
        @param respFeature: Aims Address object
        @type  respFeature: AIMSDataManager.Address
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        """
        # remove from data
        if respFeature._queueStatus in ('Declined', 'Accepted'):
            del self.data[FEEDS['AR']][respFeature._changeId]
        else:
            # add to data
            self.data[FEEDS['AR']][respFeature._changeId] = respFeature
            #uilog.info('new AR record with changeid: {}'.format(respFeature._changeId))
        self.rDataChangedSignal.emit()

    def updateFdata(self, respFeature):
        """
        Between dm threaded interval data deliveries, temp
        AIMS feature objects are created or render irrelevant 
        by user actions. This method updates the main data (self._data)
        to reflect these changes.

        @param respFeature: Aims Address object
        @type  respFeature: AIMSDataManager.Address
        """

        # responses do not have a 'full number'
        # as it is required for labeling it is set here

        respFeature.setFullAddressNumber(respFeature.getFullNumber())

        self.data[FEEDS['AF']][respFeature._components_addressId] = respFeature
        self.fDataChangedSignal.emit()

    def updateGdata(self, respFeature):
        groupKey = self.matchGroupKey(respFeature._changeGroupId)
        self.data[FEEDS['GR']][groupKey][respFeature._changeId] = respFeature
        self.rDataChangedSignal.emit()

    def matchGroupKey(self, groupId):
        for groupKey in self.data.get(FEEDS['GR']).keys():
            if groupId in groupKey:
                return groupKey

    def setBbox(self, sw, ne):
        """
        Intermediate method, passes
        bboxes from layer manager to the DM
        
        @param sw: (x ,y)
        @type  sw: tuple
        @param ne: (x ,y)
        @type  ne: tuple         
        """
        #logging
        uilog.info('*** BBOX ***   New bbox passed to dm.setbb')
        self.dm.setbb(sw, ne)
        uilog.info('*** BBOX ***   Bbox set')

    def reviewData(self):
        """
        Returns current group review data 

        @return: Dictionary of group formatted review data
        @rtype: dictionary
        """

        return self.data.get(FEEDS['AR'])

    def groupReviewData(self):
        ''' return (single and group) review data '''
        return self.data.get(FEEDS['GR'])

    def combinedReviewData(self):
        """ 
        De-nests group review data and combines with standard
        review data, Returning a complete "Review Data" set 
        
        @return: Flat dictionary of review items
        @rtype: dictionary
        """

        groupData = self.groupReviewData()
        addData = self.reviewData()

        combinedData = {}
        combinedData.update(addData)
        if not groupData: return addData
        for k, v in groupData.items():
            combinedData.update(v)
        return combinedData

    def featureData(self):
        """
        Returns feature data
        
        return: Dict of aims features {featureID: address obj,...}
        rtype: dictionary
        """
        return self.data.get(FEEDS['AF'])

    # --- DM convenience methods---

    def addAddress(self, feature, respId=None):
        """
        Passes an new AIMS Feature to DataManager 
        to add the feature to the AIM system

        @param feature: Aims Address object
        @type  feature: AIMSDataManager.Address
        @param respId: id used to match response 
        @type  respId: integer 
        """

        uilog.info(
            'obj with respId: {0} passed to convenience method "{1}" '.format(
                respId, 'addAddress'))
        self.dm.addAddress(feature, respId)

    def retireAddress(self, feature, respId=None):
        """
        Passes an AIMS Feature to DataManager for retirement

        @param feature: Aims Address object
        @type  feature: AIMSDataManager.Address
        @param respId: id used to match response 
        @type  respId: integer 
        """

        uilog.info(
            'obj with respId: {0} passed to convenience method "{1}" '.format(
                respId, 'retireAddress'))
        self.dm.retireAddress(feature, respId)

    def updateAddress(self, feature, respId=None):
        """
        Passes an AIMS Feature to the DataManager to 
        update a published feature

        @param feature: Aims Address object
        @type  feature: AIMSDataManager.Address
        @param respId: id used to match response 
        @type  respId: integer 
        """

        uilog.info(
            'obj with respId: {0} passed to convenience method "{1}" '.format(
                respId, 'updateAddress'))
        self.dm.updateAddress(feature, respId)

    def decline(self, feature, feedType, respId=None):
        """
        Passes an AIMS Feature to the DataManager 
        to decline a review item

        @param feature: Aims Address object
        @type  feature: AIMSDataManager.Address
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        @param respId: id used to match response 
        @type  respId: integer 
        """

        uilog.info(
            'obj with respId: {0} passed to convenience method "{1}" '.format(
                respId, 'declineAddress'))
        if feedType == FEEDS['AR']:
            self.dm.declineAddress(feature, respId)
        else:
            self.dm.declineGroup(feature, respId)

    def accept(self, feature, feedType, respId=None):
        """
        Passes an AIMS Feature to the DataManager 
        to accept a review item

        @param feature: Aims Address object
        @type  feature: AIMSDataManager.Address
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        @param respId: id used to match response 
        @type  respId: integer 
        """

        if respId:
            uilog.info(
                'obj with respId: {0} passed to convenience method "{1}" '.
                format(respId, 'acceptAddress'))
            if feedType == FEEDS['AR']:
                self.dm.acceptAddress(feature, respId)
            else:
                self.dm.acceptGroup(feature, respId)

    def repairAddress(self, feature, respId=None):
        """
        Passes an updated AIMS Review Feature to the DataManager to
        be updated on the review feed

        @param feature: Aims Address object
        @type  feature: AIMSDataManager.Address
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        @param respId: id used to match response 
        @type  respId: integer 
        """

        uilog.info(
            'obj with respId: {0} passed to convenience method "{1}" '.format(
                respId, 'repairAddress'))
        self.dm.repairAddress(feature, respId)

    def supplementAddress(self, feature, reqid=None):
        """
        Retrieve properties missing on the feature feed from 
        the last relevant resolution feed feature
        
        @param feature: Feature feed item that require supplement data
        @type  feature: AIMSDataManager.Address
        @param respId: id used to match response 
        @type  respId: integer 
        """

        self.dm.supplementAddress(feature, reqid)

    #--- Groups DM Methods ---

    def repairGroup(self, feature, respId=None):
        """
        Passes an updated AIMS Review Feature to the DataManager
        to be updated on the review feed

        @param feature: Aims Address object
        @type  feature: AIMSDataManager.Address
        @param respId: id used to match response 
        @type  respId: integer 
        """

        uilog.info(
            'obj with respId: {0} passed to convenience method "{1}" '.format(
                respId, 'repairAddress'))
        self.dm.repairGroup(feature, respId)


# Lineage Related - ON HOLD
#     def openGroup(self):
#         self.dm.replaceGroup()
#
#     def updateGroup(self):
#         self.dm.updateGroup()
#
#     def submitGroup(self):
#         self.dm.submitGroup()
#
#     def closeGroup(self):
#         self.dm.closeGroup()
#
#     def addGroup(self):
#         self.dm.addGroup()
#
#     def removeGroup(self):
#         self.dm.removeGroup()
#

    def response(self, feedtype=None):
        """
        Returns DataMAnager response for a specific feedtype

        @param feedType: Type of AIMS feed
        @type feedType: AIMSDataManager.FeatureFactory.FeedRef

        @return: tuple of AIMSDataManager.Address
        @rtype: tuple
        """

        return self.dm.response(feedtype)

    def isNested(self, feat, prop):
        """
        Test if the class object has said nested entity

        @param feat: Group object
        @type  feat: AIMSDataManager.Address
        @param prop: address property
        @type  prop: string 
        """

        try:
            return hasattr(
                getattr(getattr(feat, 'meta'), '_entities')[0], prop)
        except:
            return False

    def nestedEntities(self, feat, prop):
        """
        Returns property from nested object

        @param feat: Group object
        @type  feat: AIMSDataManager.Address
        @param prop: address property
        @type  prop: string

        @return: AIMSDataManager.Address property
        @rtype: AIMSDataManager.Address property
        """

        return getattr(getattr(getattr(feat, 'meta'), '_entities')[0], prop)

    def flatEntities(self, feat, prop):
        """
        Returns property from flat object

        @param feat: Group object
        @type  feat: AIMSDataManager.Address
        @param prop: address property
        @type  prop: string

        @return: AIMSDataManager.Address property
        @rtype: AIMSDataManager.Address property
        """

        att = getattr(feat, prop)
        return att if att else ''

    def fullRoad(self, feat, feedtype):
        """ 
        Compiles a full road name 'label' for the UI 

        @param feat: Group object
        @type  feat: AIMSDataManager.Address
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef

        @return: Individual road components concatenated
        @rtype: string
        """

        fullRoad = ''
        for prop in [
                '_components_roadPrefix', '_components_roadName',
                '_components_roadType', '_components_roadSuffix',
                '_components_waterRoute'
        ]:
            addProp = None
            # Groups have nested entities
            if feat._changeType in self.groups:
                if self.isNested(feat, prop):
                    addProp = self.nestedEntities(feat, prop)
            # retired have nested entities except when the retired
            # feature is derived from an response object
            elif feat._changeType == 'Retire':
                #if self.isNested(feat, prop):
                if not feat.meta.requestId:
                    if hasattr(
                            getattr(getattr(feat, 'meta'), '_entities')[0],
                            prop):
                        addProp = self.nestedEntities(feat, prop)
                elif hasattr(feat, prop):
                    addProp = self.flatEntities(feat, prop)
            # else we have an Add or update of whoms
            # properties are flat
            elif hasattr(feat, prop):
                addProp = self.flatEntities(feat, prop)
            else:
                continue
            if addProp != None: fullRoad += addProp + ' '
        return fullRoad.lstrip()

    def formatGroupTableData(self, obj, groupProperties):
        """
        Returns data formatted for the group table model 

        @param obj: Group object
        @type  obj: AIMSDataManager.Group.GroupResolution
        @param groupProperties: list of properties required to format table data
        @type  groupProperties: list

        @return: return list reperesting a group entity. 
                Formatted:  ['changeGroupId', 'groupType', 'workflow_sourceOrganisation', 
                            'submitterUserName', 'submittedDate']
        @rtype: list
        """

        groupValues = []
        for prop in groupProperties:
            if hasattr(obj, prop):
                if getattr(obj, prop) != None:
                    groupValues.append(getattr(obj, prop))
                    #continue
            else:
                groupValues.append('')
        return groupValues

    def iterFeatProps(self, feat, featProperties, feedtype):
        """ 
        Iterate over AIMS class objects, Return those
        relevant to the parent model (that related to the feedtype)
        
        @param feat: Address object
        @type  feat: AIMSDataManager.Address
        @param featProperties: List of properties required to format table data
        @type  featProperties: list
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef

        @return: List of properties as formatted for relevant data model
        @rtype: list
        """

        fValues = []
        fValues.extend([
            getattr(feat, '_changeId'),
            feat.getFullNumber(),
            self.fullRoad(feat, feedtype)
        ])  # address and road labels
        for prop in featProperties:
            # Groups have nested entities
            if feat._changeType in self.groups and self.isNested(feat, prop):
                fValues.append(self.nestedEntities(feat, prop))
            # retired have nested entities except when the retired
            # feature is derived from an response object
            elif feat._changeType == 'Retire':
                if self.isNested(feat, prop):
                    fValues.append(self.nestedEntities(feat, prop))
                elif hasattr(feat, prop):
                    fValues.append(self.flatEntities(feat, prop))
            # else we have an Add or update of whoms
            # properties are flat
            elif hasattr(feat, prop):
                fValues.append(self.flatEntities(feat, prop))  #!= None:
            else:
                fValues.append('')
        return fValues

    def formatFeatureTableData(self, feat, featProperties, feedtype):
        """
        Returns data formatted for the feature table model 

        @param feature: Aims Address object
        @type  feature: dictionary
        @param featProperties: List of Address Properties required format Feature Table Data
        @type  featProperties: list
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        
        @return: List representing on row of feature table data 
        @rtype: list 
        """

        if feedtype == FEEDS['AR']:
            return self.iterFeatProps(feat, featProperties, feedtype)
        else:
            fValuesList = []  # AR
            for f in feat.values():
                fValues = self.iterFeatProps(f, featProperties, feedtype)
                fValuesList.append(fValues)
            return fValuesList

    def addClassProps(self, feedtype):
        """
        Properties required to format Group and Feature data for the respective table models
        
        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef

        @return: Tuple of properties required to format the data related to the feedtype 
        @rtype: tuple
        """

        prop = {
            'AR': {
                'kProperties': [
                    '_changeId', '_changeType', '_workflow_sourceOrganisation',
                    '_workflow_submitterUserName', '_workflow_submittedDate'
                ],
                'vProperties': [
                    '_components_lifecycle', '_components_townCity',
                    '_components_suburbLocality'
                ]
            },
            'GR': {
                'kProperties': [
                    '_changeGroupId', '_groupType',
                    '_workflow_sourceOrganisation', '_submitterUserName',
                    '_submittedDate'
                ],
                'vProperties': [
                    '_components_lifecycle', '_components_townCity',
                    '_components_suburbLocality'
                ]
            }
        }

        if feedtype == FEEDS['AR']:
            return (prop['AR']['kProperties'], prop['AR']['vProperties'])
        return (prop['GR']['kProperties'], prop['AR']['vProperties'])

    def formatTableData(self, feedtypes):
        """
        Returns review data as formatted for the review data model 

        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef

        @return: data formated for feature table model
        @rtype: dictionary
        """

        fData = {}
        for feedtype in feedtypes:
            if self.data[feedtype]:
                props = self.addClassProps(feedtype)
                kProperties = props[0]
                vProperties = props[1]
                for k, v in self.data.get(feedtype).items():
                    if feedtype == FEEDS['AR']:
                        if v._queueStatus in ('Declined', 'Accepted'):
                            continue
                    featureValues = []
                    if feedtype == FEEDS['AR']:
                        groupValues = self.formatGroupTableData(v, kProperties)
                        featureValues = [
                            self.formatFeatureTableData(
                                v, vProperties, feedtype)
                        ]
                    else:  #GR
                        featureValues = []
                        groupValues = self.formatGroupTableData(
                            k[1], kProperties)
                        featureValues = self.formatFeatureTableData(
                            v, vProperties, feedtype)
                    fData[tuple(groupValues)] = featureValues
        if fData:
            return fData
        else:
            return {('', '', '', '', ''): [['', '', '', '', '']]}

    def singleFeatureObj(self, objkey):
        """
        Returns a AIMS Address object that matches the object key

        @param feedType: Type of AIMS API feed
        @type  feedType: AIMSDataManager.FeatureFactory.FeedRef
        @param objkey: Feautre id
        @type  objkey: integer     
        """

        return self.data.get(FEEDS['AF'])[(objkey)]

    def singleReviewObj(self, feedtype, objkey):
        ''' return the value of which is an aims review
            obj (group and single) for the keyed data '''
        if feedtype == FEEDS['AR']:
            return self.data.get(feedtype).get(objkey)
        elif feedtype == FEEDS['GR']:
            for k in self.data.get(feedtype):
                if objkey == k[0]: return k[1]

    def currentReviewFeature(self, currentGroup, currentFeatureKey):
        """
        Returns aims feautre object as per supplied data key(s)

        @param currentGroup: Current group key (groupId, changeType)
        @type  currentGroup: tuple
        @param currentFeatureKey: Current Feautre id
        @type  currentFeatureKey: integer

        @return: Address object
        @rtype: AIMSDataManager.Address
        """

        if currentGroup[1] not in ('Add', 'Update', 'Retire'):
            for group in self.data.get(FEEDS['GR']).values():
                if group.has_key(currentFeatureKey):
                    return group[currentFeatureKey]
        else:
            return self.data.get(FEEDS['AR']).get(currentFeatureKey)

    def reviewItemCoords(self, currentGroup, currentFeatureKey):
        """
        Returns the coords of a review obj

        @param currentGroup: Current group key (groupId, changeType)
        @type  currentGroup: tuple
        @param currentFeatureKey: Current Feautre id
        @type  currentFeatureKey: integer

        @return: list [x,y]
        @rtype: list
        """

        obj = self.currentReviewFeature(currentGroup, currentFeatureKey)
        if not obj: return None
        if obj._changeType not in ('Update', 'Add') and not obj.meta.requestId:
            pos = obj.meta.entities[0].getAddressPositions(
            )[0]._position_coordinates
        else:
            pos = obj.getAddressPositions()[0]._position_coordinates
        return pos
Пример #2
0
class UiUtility(object):
    """
    Where modular UI methods live and are leveraged 
    """

    # logging
    global uilog
    uilog = Logger.setup(lf='uiLog')

    # (uiElement, [Address.property, Address/QueueEditorWidget.setter, Address.getter, QueueEditorWidget.getter,])
    uiObjMappings = OrderedDict([
        ('uAddressType',
         ['_components_addressType', 'setAddressType', '', 'set']),
        ('uWarning', ['_warning', 'setWarnings', '_getEntities', '']),
        ('uNotes', ['_workflow_sourceReason', 'setSourceReason', '', '']),
        ('ulifeCycle', ['_components_lifecycle', 'setLifecycle', '', '']),
        ('uLevelType', ['_components_levelType', 'setLevelType', '', '']),
        ('uLevelValue', ['_components_levelValue', 'setLevelValue', '', '']),
        ('uUnitType', ['_components_unitType', 'setUnitType', '', '']),
        ('uUnit', ['_components_unitValue', 'setUnitValue', '', '']),
        ('uPrefix',
         ['_components_addressNumberPrefix', 'setAddressNumberPrefix', '',
          '']),
        ('uBase', ['_components_addressNumber', 'setAddressNumber', '', '']),
        ('uAlpha',
         ['_components_addressNumberSuffix', 'setAddressNumberSuffix', '',
          '']),
        ('uHigh',
         ['_components_addressNumberHigh', 'setAddressNumberHigh', '', '']),
        ('uExternalAddressIdScheme', [
            '_components_externalAddressIdScheme',
            'setExternalAddressIdScheme', '', ''
        ]),
        ('uExternalAddId',
         ['_components_externalAddressId', 'setExternalAddressId', '', '']),
        ('uRclId',
         ['_components_roadCentrelineId', 'setRoadCentrelineId', '', '']),
        ('uRoadPrefix', ['_components_roadPrefix', 'setRoadPrefix', '', '']),
        ('uRoadName', ['_components_roadName', 'setRoadName', '', '']),
        ('uRoadTypeName', ['_components_roadType', 'setRoadType', '', '']),
        ('uRoadSuffix', ['_components_roadSuffix', 'setRoadSuffix', '', '']),
        ('uWaterName', ['_components_waterName', 'setWaterName', '', '']),
        ('uWaterRouteName',
         ['_components_waterRoute', 'setWaterRoute', '', '']),
        ('uObjectType',
         ['_addressedObject_objectType', 'setAddObjectType', '', '']),
        ('uObjectName',
         ['_addressedObject_objectName', 'setAddObjectName', '', '']),
        #'uPositionType':['_addressedObject_addressPositions[0]','_addressedObject_addressPositions[0].setPositionType',''],
        ('uExtObjectIdScheme', [
            '_addressedObject_externalObjectIdScheme',
            'setExternalObjectIdScheme', '', ''
        ]),
        ('uExternalObjectId',
         ['_addressedObject_externalObjectId', 'setExternalObjectId', '', '']),
        ('uValuationReference', [
            '_addressedObject_valuationReference', 'setValuationReference', '',
            ''
        ]),
        ('uCertificateOfTitle', [
            '_addressedObject_certificateOfTitle', 'setCertificateOfTitle', '',
            ''
        ]),
        ('uAppellation',
         ['_addressedObject_appellation', 'setAppellation', '', '']),
        ('uMblkOverride', ['_codes_meshblock', 'setMeshblock', '', ''])
    ])

    @staticmethod
    def transform(iface, coords, tgt=4167):
        """
        Ensure point coordinates are in terms of AIMS system
        spatial reference system (4167)

        @param iface: QgisInterface Abstract base class defining interfaces exposed by QgisApp  
        @type iface: Qgisinterface Object
        @param coords: Point
        @type  coords: QgsPoint
        @param tgt: Srs to transform to
        @type  tgt: integer
        """

        src_crs = iface.mapCanvas().mapSettings().destinationCrs()
        tgt_crs = QgsCoordinateReferenceSystem()
        tgt_crs.createFromOgcWmsCrs('EPSG:{}'.format(tgt))
        transform = QgsCoordinateTransform(src_crs, tgt_crs)
        return transform.transform(coords.x(), coords.y())

    @staticmethod
    def setFormCombos(self):
        """
        Set combo boxes to defualt values 
        """

        # set from the parent
        self.uAddressType.addItems(['Road', 'Water'])
        self.ulifeCycle.addItems(['Current', 'Proposed', 'Retired'])
        self.uUnitType.addItems([
            None, 'Apartment', 'Kiosk', 'Room', 'Shop', 'Suite', 'Villa',
            'Flat', 'Unit'
        ])
        self.uLevelType.addItems([None, 'Floor', "Level"])
        self.uObjectType.addItems(['Parcel', 'Building'])
        self.uPositionType.addItems([
            'Unknown', 'Property Centroid', 'Unit Centroid',
            'Frontage Centre Set Back', 'Building Centroid',
            'Property Access Point Set Back', 'Building Access Point',
            'Front Door Access'
        ])

    @staticmethod
    def formMask(self):
        """
        Mask form input values
        """

        self.uBase.setValidator(QRegExpValidator(QRegExp(r'[0-9]{0,6}'), self))
        self.uHigh.setValidator(QRegExpValidator(QRegExp(r'[0-9]{0,6}'), self))
        self.uMblkOverride.setValidator(
            QRegExpValidator(QRegExp(r'[0-9]{0,7}'), self))
        self.uAlpha.setValidator(
            QRegExpValidator(QRegExp(r'^[A-Za-z]{0,3}'), self))
        self.uUnit.setValidator(
            QRegExpValidator(QRegExp(r'[0-9A-Za-z]{0,5}'), self))
        self.uPrefix.setValidator(
            QRegExpValidator(QRegExp(r'[0-9A-Za-z]{0,5}'), self))
        self.uLevelValue.setValidator(
            QRegExpValidator(QRegExp(r'[0-9A-Za-z]{0,7}'), self))

    @staticmethod
    def toUpper(uInput, UiElement):
        """
        Converts lower case to upper case user input information
        for UI elements that are required to be upper case when submitted to API 
        
        @param uInput: User input to a UI component
        @type  uInput: string
        @param UiElement: UI Component
        @type  UiElement: QtGui.QLineEdit | QtGui.QCombo 

        @return: User input cast tp upper case where required
        @rtype: string 
        """

        if UiElement.objectName() in ('uAlpha', 'uUnit', 'uLevelValue',
                                      'uPrefix'):
            return uInput.upper()
        else:
            return uInput

    @staticmethod
    def nullEqualsNone(uInput):
        """
        Cast whitespace or 'NULL' to None

        @rtype: string 
        """

        if uInput == '' or uInput == 'NULL':
            return None
        else:
            return uInput

    @staticmethod
    def extractFlatProperty(feature, property, getter):
        """
        Return the require property for an object

        @param feature: Aims Address object
        @type  feature: AIMSDataManager.Address
        @param property: Required features property 
        @type  property: string
        @param getter: Properties Getter
        @type  getter: string
        """

        if getter:
            # use getter
            if getattr(feature, getter)() != 'None':
                prop = (getattr(feature, getter)())
            else:
                prop = ''
        else:
            # go straight for the objects property
            if unicode(getattr(feature, property)) != 'None':
                prop = unicode(getattr(feature, property))
            else:
                prop = ''
        return prop

    @staticmethod
    def featureToUi(self, parent=None):
        """ 
        Populates update form and review editor queue from aims obj 
        """

        UiUtility.setEditability(self, parent)
        UiUtility.clearForm(self)

        for ui, objProp in UiUtility.uiObjMappings.items():
            # Test the UI has the relative UI component
            if hasattr(self, ui):
                uiElement = getattr(self, ui)
            else:
                continue
            # Test the object has the required property or a getter
            # Groups and retired feature properties may be either nested or flat
            if self.feature._changeType in (
                    'Retire', 'Replace', 'AddLineage', 'ParcelReferenceData',
                    'MeshblockReferenceData'
            ) and self.feature._changeType not in ('Accepted', 'Declined'):
                if hasattr(self.feature, objProp[0]) or hasattr(
                        self.feature, objProp[2]):
                    prop = UiUtility.extractFlatProperty(
                        self.feature, objProp[0], objProp[2])
                elif hasattr(
                        getattr(getattr(self.feature, 'meta'), '_entities')[0],
                        objProp[0]):
                    prop = getattr(
                        getattr(getattr(self.feature, 'meta'), '_entities')[0],
                        objProp[0])
                else:
                    continue
            # Add and update properties are only flat least the are temp objs
            else:
                if hasattr(self.feature, objProp[0]) or hasattr(
                        self.feature, objProp[2]):
                    prop = UiUtility.extractFlatProperty(
                        self.feature, objProp[0], objProp[2])
                else:
                    continue

            # populate relvant UI components
            if isinstance(uiElement, QLineEdit) or isinstance(
                    uiElement, QLabel):
                if ui == 'uWarning':
                    warnings = ''

                    for i in prop:
                        if hasattr(i, '_severity'):
                            warnings += i._severity.upper(
                            ) + ': ' + i._description + ('\n' * 2)
                            uiElement.setText(warnings)

                    for i in self.feature.meta.errors:
                        if hasattr(i, '_severity'):
                            warnings += i._severity.upper(
                            ) + ': ' + i._description + ('\n' * 2)
                    uiElement.setText(warnings)

                    # added to handle the resp warnings which differ
                    if self.feature.meta.errors:
                        for k, v in self.feature.meta.errors.iteritems():
                            if k:
                                for i in v:
                                    warnings += k.upper() + ': ' + i + ('\n' *
                                                                        2)
                        uiElement.setText(warnings)

                # New MBLK overwrite flag implemented in API
                elif ui == 'uMblkOverride' and parent == 'update' and self.feature._codes_isMeshblockOverride != True:
                    continue
                else:
                    uiElement.setText(unicode(prop))
                    continue
            elif isinstance(uiElement, QComboBox):
                uiElement.setCurrentIndex(0)
                uiElement.setCurrentIndex(QComboBox.findText(uiElement, prop))
        if self.feature._changeType not in (
                'Retire') or self.feature.meta.requestId:  #Therefore flat
            posType = self.feature._addressedObject_addressPositions[
                0]._positionType
        else:
            posType = self.feature.meta.entities[
                0]._addressedObject_addressPositions[0]._positionType
        self.uPositionType.setCurrentIndex(
            QComboBox.findText(self.uPositionType, posType))

    @staticmethod
    def formToObj(self):
        """
        Maps user input from the new and update form
        as well as queue editor widget to an AIMS object
        """

        if hasattr(self, 'uQueueEditor'):
            form = self.uQueueEditor
        else:
            form = self

        for uiElement, objProp in UiUtility.uiObjMappings.items():
            #user can not mod warnings ... continue
            if uiElement == 'uWarning':
                continue
            # test if the ui widget/ form ... has the ui component
            if hasattr(form, uiElement):
                uiElement = getattr(form, uiElement)
                setter = getattr(self.feature, objProp[1])
                if isinstance(uiElement, QLineEdit):
                    setter(UiUtility.toUpper(uiElement.text(), uiElement))
                elif isinstance(uiElement, QComboBox):
                    setter(uiElement.currentText())
                elif isinstance(uiElement, QPlainTextEdit):
                    setter(uiElement.toPlainText())
        self.feature._addressedObject_addressPositions[0].setPositionType(
            getattr(form, 'uPositionType').currentText())

    @staticmethod
    def clearForm(self):
        """
        Resets and clears data from UI forms
        """

        widgetChildern = self.findChildren(QWidget, QRegExp(r'^u.*'))
        for child in widgetChildern:
            child.setEnabled(True)
            if isinstance(child, QLineEdit) or isinstance(child, QLabel):
                child.clear()
            elif isinstance(
                    child, QComboBox) and child.objectName() != 'uAddressType':
                child.setCurrentIndex(0)
            elif isinstance(child, QPlainTextEdit):
                child.clear()

    @staticmethod
    def setReadability(self, regMatch, bool=False):
        """
        set the readability / writeabilty of UI Fields

        @param self: the class that called featureToUi() 
        @type  self: ui object
        @param bool: boolean, True = Writeable
        @type  bool: boolean
        """

        uiElements = self.findChildren(QWidget, QRegExp(regMatch))
        for uiElement in uiElements:
            if uiElement.objectName() == 'uRclId':
                uiElement.setReadOnly(True)
            elif isinstance(uiElement, QLineEdit):
                uiElement.setReadOnly(bool)
            elif isinstance(uiElement, QComboBox):
                uiElement.setDisabled(bool)

    @staticmethod
    def setEditability(self, parent=None):
        """
        Toggle editable fields depending 
        on the objects Address Type (road or water) 

        @param self: the class that called featureToUi() 
        @type  self: ui object
        """

        # Set wrtieability of EditFeature Form
        if not parent:
            UiUtility.setReadability(self, r'^u.*', False)
        elif parent == 'update':
            UiUtility.setReadability(self, r'uRoad.*|uWaterR.*', True)
        elif parent == 'rRetire':
            UiUtility.setReadability(self, r'^u.*', True)
            return  # no
        elif parent == 'rUpdate' or parent == 'rAdd':
            UiUtility.setReadability(self, r'^u.*', False)
            UiUtility.setReadability(self, r'uRoad.*|uWaterR.*', True)

        for child in self.findChildren(QWidget):
            child.setEnabled(True)

        # Toggle between Water and Road Fields
        if self.uAddressType.currentText() == 'Road':
            waterChildern = self.findChildren(QWidget, QRegExp(r'uWater.*'))
            for child in waterChildern:
                child.clear()
                child.setDisabled(True)

        elif self.uAddressType.currentText() == 'Water':
            roadChildern = self.findChildren(QWidget, QRegExp(r'uRoad.*'))
            for child in roadChildern:
                child.clear()
                child.setDisabled(True)

    @staticmethod
    def raiseErrorMesg(iface, mesg):
        QMessageBox.warning(iface.mainWindow(), "AIMS Warnings",
                            '{0}'.format(mesg))

    @staticmethod
    def formCompleteness(action, form, iface):
        """
        Test the minimum required properties have been set.
        This is different for New Features and Updated Features
        
        @rtype: boolean
        """

        # All Features must have a Base Number
        if not form.uBase.text():
            UiUtility.raiseErrorMesg(
                iface, 'Please supply a Complete Address Number')
            return False

        # Updates must have both an RCL and Road Name
        if action == 'update':
            if (form.uAddressType.currentText() == 'Road' and
                (not UiUtility.nullEqualsNone(form.uRclId.text())
                 or not UiUtility.nullEqualsNone(form.uRoadName.text()))):
                UiUtility.raiseErrorMesg(
                    iface, 'An Update must Supply a Road Name and RCL Id')
                return False
            elif (form.uAddressType.currentText() == 'Water' and
                  (not UiUtility.nullEqualsNone(form.uRclId.text()) or
                   not UiUtility.nullEqualsNone(form.uWaterRouteName.text()))):
                UiUtility.raiseErrorMesg(
                    iface,
                    'An Update must Supply a Water Route Name and RCL Id')
                return False
            # An address can not got from <x> to proposed
            elif form.ulifeCycle.currentText() == 'Proposed':
                UiUtility.raiseErrorMesg(
                    iface, 'A Feature may not be updated to "Proposed"')
                return False

        # For New Features a road Name is required
        if action == 'add':
            if form.uAddressType.currentText(
            ) == 'Road' and not UiUtility.nullEqualsNone(
                    form.uRoadName.text()):
                UiUtility.raiseErrorMesg(iface, 'Please supply a Road Name')
                return False
            elif (form.uAddressType.currentText() == 'Water' and
                  (not UiUtility.nullEqualsNone(form.uWaterName.text()) or
                   not UiUtility.nullEqualsNone(form.uWaterRouteName.text()))):
                UiUtility.raiseErrorMesg(
                    iface, 'Please supply a Water Route Name and Water Name')
                return False
        return True

    @staticmethod
    def fullNumChanged(obj, newnumber):
        """
        Splits a full (user inputted) address string into address components

        @param obj: The UI class that the user is editing
        @type  obj: e.g. AimsUI.AimsClient.Gui.EditFeatureWidget
        @param newnumber: User input to uFullNum field
        @type  newnumber: string
        """

        # Set address components to None
        [
            i.setText(None) for i in (
                [obj.uPrefix, obj.uUnit, obj.uBase, obj.uAlpha, obj.uHigh])
        ]
        # Split full address into components
        if '-' not in newnumber:
            p = re.compile(
                r'^(?P<flat_prefix>[A-Z]+)?(?:\s)?(?P<flat>[0-9]+/\s*|[0-9]+[A-Z]{,2}/\s*)?(?P<base>[0-9]+)(?P<alpha>[A-Z]+)?$'
            )
            m = p.match(newnumber.upper())
            try:
                if m.group('flat_prefix') is not None:
                    obj.uPrefix.setText(m.group('flat_prefix'))
                if m.group('flat') is not None:
                    obj.uUnit.setText(m.group('flat').strip('/'))
                if m.group('base') is not None:
                    obj.uBase.setText(m.group('base'))
                if m.group('alpha') is not None:
                    obj.uAlpha.setText(m.group('alpha'))
            except:
                pass  #silently. This is an edge case, user must split them self
        else:
            p = re.compile(
                r'^(?P<flat_prefix>[A-Z]+)?(?:\s)?(?P<flat>[0-9]+/\s*|[0-9]+[A-Z]{,2}/\s*)?(?P<base>[0-9]+)(?:-)(?P<high>[0-9]+)(?P<alpha>[A-Z]+)?$'
            )
            m = p.match(newnumber.upper())
            try:
                if m.group('flat_prefix') is not None:
                    obj.uPrefix.setText(m.group('flat_prefix'))
                if m.group('flat') is not None:
                    obj.uUnit.setText(m.group('flat').strip('/'))
                if m.group('base') is not None:
                    obj.uBase.setText(m.group('base'))
                if m.group('high') is not None:
                    obj.uHigh.setText(m.group('high'))
                if m.group('alpha') is not None:
                    obj.uAlpha.setText(m.group('alpha'))
            except:
                pass  #silently
Пример #3
0
class Controller(QObject):
    ''' 
    Managers all UI Components and Plugins Tools as well as 
    initialisation and dessimnation of Singleton Instances  
        
     '''
    # log
    global uilog
    uilog = Logger.setup(lf='uiLog')

    _instance = None

    def __init__(self, iface):
        """ 
        Initialise UI Data Manager and Response Handler 
        
        @param iface: QgisInterface Abstract base class defining interfaces exposed by QgisApp  
        @type iface: Qgisinterface Object
        """

        QObject.__init__(self)
        self.iface = iface
        self._queues = None
        self._dockWindow = None
        self._currentMapTool = None
        self.rclParent = None
        self.currentRevItem = None
        self.actions = []
        if Controller._instance == None:
            Controller._instance = self
        self.uidm = UiDataManager(self.iface, self)
        self.RespHandler = ResponseHandler(self.iface, self.uidm)

        self.refLayer = None
        self.adrlayer = None
        self.revLayer = None

    def initGui(self):
        """ 
        Set up UI within QGIS 
        """

        # set srs
        self._displayCrs = QgsCoordinateReferenceSystem()
        self._displayCrs.createFromOgcWmsCrs('EPSG:4167')
        self.iface.mapCanvas().mapSettings().setDestinationCrs(
            self._displayCrs)

        # init layerManager
        self._layerManager = LayerManager(self.iface, self)
        self._layerManager.registerFunctions()
        # init Highlighter
        self.highlighter = FeatureHighlighter(self.iface, self._layerManager,
                                              self)

        # Build an action list from QGIS navigation toolbar
        actionList = self.iface.mapNavToolToolBar().actions()
        self.actions = self.iface.mapNavToolToolBar().actions()

        # Main address editing window
        self._loadaction = QAction(
            QIcon(':/plugins/QGIS-AIMS-Plugin/resources/loadaddress.png'),
            'QGIS-AIMS-Plugin', self.iface.mainWindow())
        self._loadaction.setWhatsThis('Open the QGIS-AIMS-Plugin')
        self._loadaction.setStatusTip('Open the QGIS-AIMS-Plugin')
        self._loadaction.triggered.connect(self.loadQueues)
        self._loadaction.triggered.connect(self.loadLayers)
        self._loadaction.triggered.connect(self.enableAddressLayer)
        self._loadaction.triggered.connect(self.startDM)

        # Create new address tool
        self._createnewaddressaction = QAction(
            QIcon(':/plugins/QGIS-AIMS-Plugin/resources/newaddresspoint.png'),
            'Create AIMS Feature', self.iface.mainWindow())
        self._createnewaddressaction.setWhatsThis('Create AIMS Feature')
        self._createnewaddressaction.setStatusTip('Create AIMS Feature')
        self._createnewaddressaction.setEnabled(False)
        self._createnewaddressaction.setCheckable(True)
        self._createnewaddressaction.triggered.connect(
            self.startNewAddressTool)
        self._createnewaddresstool = CreateNewAddressTool(
            self.iface, self._layerManager, self)
        self._createnewaddresstool.setAction(self._createnewaddressaction)
        self.actions.append(self._createnewaddressaction)

        # Delete address point
        self._deladdressaction = QAction(
            QIcon(':/plugins/QGIS-AIMS-Plugin/resources/deleteaddress.png'),
            'Delete AIMS Feature', self.iface.mainWindow())
        self._deladdressaction.setWhatsThis('Delete AIMS Feature')
        self._deladdressaction.setStatusTip('Delete AIMS Feature')
        self._deladdressaction.setEnabled(False)
        self._deladdressaction.setCheckable(True)
        self._deladdressaction.triggered.connect(self.startDelAddressTool)
        self._deladdtool = DelAddressTool(self.iface, self._layerManager, self)
        self._deladdtool.setAction(self._deladdressaction)
        self.actions.append(self._deladdressaction)

        # Move address
        self._moveaddressaction = QAction(
            QIcon(':/plugins/QGIS-AIMS-Plugin/resources/moveaddress.png'),
            'Move AIMS Feature(s)', self.iface.mainWindow())
        self._moveaddressaction.setWhatsThis('Move AIMS Feature(s)')
        self._moveaddressaction.setStatusTip('Move AIMS Feature(s)')
        self._moveaddressaction.setEnabled(False)
        self._moveaddressaction.setCheckable(True)
        self._moveaddressaction.triggered.connect(self.startMoveAddressTool)
        self._moveaddtool = MoveAddressTool(self.iface, self._layerManager,
                                            self)
        self._moveaddtool.setAction(self._moveaddressaction)
        self.actions.append(self._moveaddressaction)

        # Update address
        self._updateaddressaction = QAction(
            QIcon(':/plugins/QGIS-AIMS-Plugin/resources/updateaddress.png'),
            'Update AIMS Feature', self.iface.mainWindow())
        self._updateaddressaction.setWhatsThis('Update AIMS Feature')
        self._updateaddressaction.setStatusTip('Update AIMS Feature')
        self._updateaddressaction.setEnabled(False)
        self._updateaddressaction.setCheckable(True)
        self._updateaddressaction.triggered.connect(
            self.startUpdateAddressTool)
        self._updateaddtool = UpdateAddressTool(self.iface, self._layerManager,
                                                self)
        self._updateaddtool.setAction(self._updateaddressaction)
        self.actions.append(self._updateaddressaction)

        # RCL tool -- Not a QAction as triggered from many palaces but not the toolbar
        self._rcltool = GetRcl(self.iface, self._layerManager, self)

        # UpdateReview Position tool -- Not a QAction as triggered initiated from review queue form
        self._updateReviewPos = UpdateReviewPosition(self.iface,
                                                     self._layerManager, self)

        # Address lineage
        """
        self._lineageaction = QAction(QIcon(':/plugins/QGIS-AIMS-Plugin/resources/lineage.png'), 
            'Build Lineage Relationships Between Features', self.iface.mainWindow())
        self._lineageaction.setWhatsThis('Build Lineage Relationships Between Features')
        self._lineageaction.setStatusTip('Build Lineage Relationships Between Features')
        self._lineageaction.setEnabled(False)
        self._lineageaction.setCheckable(True)
        self._lineagetool = LineageTool( self.iface, self._layerManager, self)
        self._lineageaction.triggered.connect(self._lineagetool.setEnabled)
        self.actions.append(self._lineageaction)
        """

        # Address highlighter
        self._highlightaction = QAction(
            QIcon(":/plugins/QGIS-AIMS-Plugin/resources/addresshighlight.png"),
            "Electoral address highlighter", self.iface.mainWindow())
        self._highlightaction.setWhatsThis(
            "Turn the electoral address highlighter on or off")
        self._highlightaction.setStatusTip(
            "Turn the electoral address highlighter on or off")
        self._highlightaction.setText('Highlightaction')
        self._highlightaction.setEnabled(False)
        self._highlightaction.setCheckable(True)
        self._highlightaction.toggled.connect(self.highlighter.setEnabled)

        # Add to own toolbar
        self._toolbar = self.iface.addToolBar('QGIS-AIMS-Plugin')
        self._toolbar.addAction(self._createnewaddressaction)
        self._toolbar.addAction(self._deladdressaction)
        self._toolbar.addAction(self._updateaddressaction)
        self._toolbar.addAction(self._moveaddressaction)
        #self._toolbar.addAction(self._lineageaction)
        self._toolbar.addAction(self._highlightaction)

        # Add actions to menu
        self.iface.addToolBarIcon(self._loadaction)
        self.iface.addPluginToMenu('&QGIS-AIMS-Plugin', self._loadaction)
        self.iface.addPluginToMenu('&QGIS-AIMS-Plugin',
                                   self._createnewaddressaction)
        self.iface.addPluginToMenu('&QGIS-AIMS-Plugin', self._deladdressaction)
        self.iface.addPluginToMenu('&QGIS-AIMS-Plugin',
                                   self._updateaddressaction)
        self.iface.addPluginToMenu('&QGIS-AIMS-Plugin',
                                   self._moveaddressaction)
        self.iface.addPluginToMenu('&QGIS-AIMS-Plugin', self._highlightaction)

        # capture maptool selection changes
        QObject.connect(self.iface.mapCanvas(),
                        SIGNAL("mapToolSet(QgsMapTool *)"),
                        self.mapToolChanged)

        # Add actions from QGIS attributes toolbar (handling QWidgetActions)
        tmpActionList = self.iface.attributesToolBar().actions()
        for action in tmpActionList:
            if isinstance(action, QWidgetAction):
                actionList.extend(action.defaultWidget().actions())
            else:
                actionList.append(action)
        # ... could add other toolbars' action lists...

        # Build a group with actions from actionList
        group = QActionGroup(self.iface.mainWindow())
        group.setExclusive(True)
        for qgisAction in actionList:
            group.addAction(qgisAction)

        # Add our own actions
        for action in self.actions:
            if action.text() == 'Highlightaction': continue
            group.addAction(action)

    # Plugin Management
    def loadQueues(self):
        """ 
        Initialise Loading of the queue widgets into QGIS 
        """
        queues = self.Queues()
        if not queues.isVisible():
            queues.parent().show()

    def Queues(self):
        """ 
        Load of the queue widgets into QGIS
        @rtype: QtGui.QTabWidget  
        @return: Docked QTabWidget with UI compinets for displaying and
                 editing AIMS features
        """
        if not self._queues:
            queues = AimsQueueWidget(self.iface.mainWindow())
            self._dockWindow = DockWindow(self.iface.mainWindow(), queues,
                                          "AimsQueues", "Aims Queues")
            self._queues = queues
            self._dockWindow.unloadPlugin.connect(self.unload)
        return self._queues

    def startDM(self):
        """
        Start the Data Manager only once the user enables the Plugin
        """
        self.uidm.startDM()

    def enableAddressLayer(self):
        """ 
        enable tools that are dependent on the Address Layer
        only when the address layer exists 
        """

        self._deladdressaction.setEnabled(True)
        self._createnewaddressaction.setEnabled(True)
        self._moveaddressaction.setEnabled(True)
        self._updateaddressaction.setEnabled(True)
        self._highlightaction.setEnabled(True)

    def loadLayers(self):
        """ 
        Install map layers
        """

        if not self.refLayer:
            self.refLayer = self._layerManager.installRefLayers()
        if not self.adrlayer:
            self._layerManager.installAimsLayer('adr', 'AIMS Features')
        if not self.revLayer:
            self._layerManager.installAimsLayer('rev', 'AIMS Review')
        self._layerManager.initialiseExtentEvent()

    def mapToolChanged(self):
        """ 
        Track the current maptool (excluding rcl tool) to allow 
        for rollback to previous tool when the Rcltool is deactivated 
        """

        if (isinstance(self.iface.mapCanvas().mapTool(), GetRcl) == False
                and isinstance(self.iface.mapCanvas().mapTool(),
                               UpdateReviewPosition) == False):
            self._currentMapTool = self.iface.mapCanvas().mapTool()
            #self.highlighter.hideAll()
            # logging
            uilog.info('*** TOOL CHANGE ***    {0} started'.format(
                self.iface.mapCanvas().mapTool()))

    def setPreviousMapTool(self):
        """ 
        Roll back to the previous maptool
        """
        if self.iface.mapCanvas().mapTool() != self._currentMapTool:
            self.iface.mapCanvas().setMapTool(self._currentMapTool)

    def startNewAddressTool(self):
        """
        Enable the 'create new address' map tool 
        """
        self.iface.mapCanvas().setMapTool(self._createnewaddresstool)
        self._createnewaddresstool.setEnabled(True)

    def startRclTool(self, parent=None):
        """
        Enable the 'get rcl tool' map tool 
        
        @param parent: Map that enabled the RCL tool. Based on the RCL tools
                        parent, different highlighting of features is performed
        @type  parent: string     
        """

        self.rclParent = parent
        self.iface.mapCanvas().setMapTool(self._rcltool)
        self._rcltool.setEnabled(True)

    def startUpdateReviewPosTool(self, revItem=None):
        """ 
        Enable the 'get update Review position tool' map tool
        @param revItem: The current Review Item that is assigned to self.currentRevItem 
        @type  revItem: AIMSDataManager.Address.AddressResolution() Object 
        """

        self.currentRevItem = revItem
        self.iface.mapCanvas().setMapTool(self._updateReviewPos)
        self._rcltool.setEnabled(True)

    def startMoveAddressTool(self):
        """ 
        Enable the 'move address' map tool 
        """

        self.iface.mapCanvas().setMapTool(self._moveaddtool)
        self._moveaddtool.setEnabled(True)

    def startUpdateAddressTool(self):
        """ 
        Enable the "update address" map tool 
        """

        self.iface.mapCanvas().setMapTool(self._updateaddtool)
        self._updateaddtool.setEnabled(True)

    def startDelAddressTool(self):
        """
        Enable the "delete address" map tool 
        """

        self.iface.mapCanvas().setMapTool(self._deladdtool)
        self._deladdtool.setEnabled(True)

    '''
    def startLineageTool(self):
        """ 
        Enable the "lineage" map tool 
        """
        self.iface.mapCanvas().setMapTool(self._lineagetool)
        self._deladdtool.setEnabled(True) 
    '''

    def unload(self):
        """
        Remove Plugins UI Elements From QGIS
        """

        self._layerManager.disconnectExtentEvent()
        if self._queues:
            self._queues.close()
            self._queues = None
        self.uidm.killDm()
        self.iface.mainWindow().removeToolBar(self._toolbar)
        self.iface.removeToolBarIcon(self._loadaction)
        self.iface.removePluginMenu('&QGIS-AIMS-Plugin', self._loadaction)
        self.iface.removePluginMenu('&QGIS-AIMS-Plugin',
                                    self._createnewaddressaction)
        self.iface.removePluginMenu('&QGIS-AIMS-Plugin',
                                    self._deladdressaction)
        self.iface.removePluginMenu('&QGIS-AIMS-Plugin',
                                    self._updateaddressaction)
        self.iface.removePluginMenu('&QGIS-AIMS-Plugin',
                                    self._moveaddressaction)
        #self.iface.removePluginMenu('&QGIS-AIMS-Plugin', self._lineageaction)
        self.iface.removePluginMenu("&QGIS-AIMS-Plugin'",
                                    self._highlightaction)
        self._layerManager

    @pyqtSlot()
    def rDataChanged(self):
        """ 
        Review data changed, update review layer and table 
        """

        self._queues.uResolutionTab.refreshData()
        self._layerManager.updateReviewLayer()

    @pyqtSlot()
    def fDataChanged(self):
        """
        Feature data changed, update review layer and table 
        """

        self._layerManager.getAimsFeatures()
class ReviewQueueWidget(Ui_ReviewQueueWidget, QWidget):
    ''' connects View <--> Proxy <--> Data Model 
                and manage review data'''
    #logging
    global uilog
    uilog = Logger.setup(lf='uiLog')

    def __init__(self, parent=None, controller=None):
        QWidget.__init__(self, parent)
        self.setupUi(self)
        self.setController(controller)
        self._iface = self._controller.iface
        self.highlight = self._controller.highlighter
        self.uidm = self._controller.uidm
        self.uidm.register(self)
        self.reviewData = None
        self.currentFeatureKey = 0
        self.currentAdrCoord = [0, 0]
        self.feature = None
        self.currentGroup = (0, 0)  #(id, type)
        self.altSelectionId = ()
        self.comboSelection = []

        # Connections
        self.uDisplayButton.clicked.connect(self.display)
        self.uUpdateButton.clicked.connect(self.updateFeature)
        self.uRejectButton.clicked.connect(self.decline)
        self.uAcceptButton.clicked.connect(self.accept)

        # Features View
        self._featureProxyModel = QSortFilterProxyModel()
        featuresHeader = [
            'Id', 'Full Num', 'Full Road', 'Life Cycle', 'Town',
            'Suburb Locality'
        ]
        self.featuresTableView = self.uFeaturesTableView
        self.featureModel = FeatureTableModel(self.reviewData, featuresHeader)
        self._featureProxyModel.setSourceModel(self.featureModel)
        self.featuresTableView.setModel(self._featureProxyModel)
        self.featuresTableView.rowSelected.connect(self.featureSelected)
        self.featuresTableView.resizeColumnsToContents()
        self.featuresTableView.setColumnHidden(5, True)
        self.featuresTableView.selectRow(0)

        # Group View
        self._groupProxyModel = QSortFilterProxyModel()
        self._groupProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self._groupProxyModel.layoutChanged.connect(self.groupSelected)
        groupHeader = ['Id', 'Change', 'Source Org.', 'Submitter Name', 'Date']
        self.groupTableView = self.uGroupTableView

        self.groupModel = GroupTableModel(self.reviewData, self.featureModel,
                                          groupHeader)
        self._groupProxyModel.setSourceModel(self.groupModel)
        self.groupTableView.setModel(self._groupProxyModel)
        self.groupTableView.resizeColumnsToContents()
        self.groupTableView.rowSelectionChanged.connect(self.groupSelected)

        # connect combobox_users to view and model
        self.comboModelUser = QStandardItemModel()
        self.comboBoxUser.setView(QListView())
        self.comboBoxUser.setModel(self.comboModelUser)
        self.comboBoxUser.view().clicked.connect(
            self.applyFilter)  # combo box checked
        self.comboBoxUser.view().pressed.connect(
            self.userFilterChanged)  # or more probable, list item clicked
        self.popUserCombo()

    def setController(self, controller):
        """  
        Access and assign the single instance of the Controller 
        
        @param controller: instance of the plugins controller
        @type  controller: AimsUI.AimsClient.Gui.Controller
        """

        import Controller
        if not controller:
            controller = Controller.instance()
        self._controller = controller

    def notify(self, feedType):
        """
        Observer pattern, registered with uidm 
        
        @param feedType: feed type indicator
        """

        if feedType == FEEDS['AF']: return
        uilog.info('*** NOTIFY ***     Notify A[{}]'.format(feedType))
        self.refreshData()

    def setMarker(self, coords):
        """
        Add a review marker to canvas if the highlight action action is checked 

        @param coords: list [x , y]
        @type coords: list
        """

        self.highlight.setReview(coords)

    def refreshData(self):
        """
        Update Review Queue data 
        """

        # request new data
        self.reviewData = self.uidm.formatTableData((FEEDS['GR'], FEEDS['AR']))

        self.groupModel.beginResetModel()
        self.groupModel.refreshData(self.reviewData)
        self.groupModel.endResetModel()

        self.featureModel.beginResetModel()
        self.featureModel.refreshData(self.reviewData)
        self.featureModel.endResetModel()
        self.popUserCombo()

        uilog.info('*** NOTIFY ***     Table Data Refreshed')

        if self.reviewData:
            self.reinstateSelection()

    def reinstateSelection(self):
        """
        Select group item based on the last selected
        or alternative (next) feature in queue 
        """

        if self.currentFeatureKey:
            matchedIndex = self.groupModel.findfield('{}'.format(
                self.currentGroup[0]))

            if matchedIndex.isValid() == False:
                matchedIndex = self.groupModel.findfield('{}'.format(
                    self.altSelectionId)) or 0
            row = matchedIndex.row()
            self.groupModel.setKey(row)

            if row != -1:
                self.groupTableView.selectRow(
                    self._groupProxyModel.mapFromSource(matchedIndex).row())
                #self.featuresTableView.selectRow(0)
                self.reinstateFeatSelection()
                coords = self.uidm.reviewItemCoords(self.currentGroup,
                                                    self.currentFeatureKey)
                if coords:
                    self.setMarker(coords)
            else:
                self.uQueueEditor.clearForm()

    def singleReviewObj(self, feedType, objKey):
        """
        Return either single or group
        review object as per supplied key 
        
        @param feedType: feed type indicator
        @type feedType: AIMSDataManager.AimsUtility.FeedRef
        @param objKey: object reference id
        @type objKey: integer
        
        @return: AIMS Address Feature
        @rtype: AIMSDataManager.Address
        """

        if objKey:
            return self.uidm.singleReviewObj(feedType, objKey)

    def currentReviewFeature(self):
        """
        Returns the current review feature as registered by last 
        review item selection 
        
        @return: AIMS Address Feature
        @rtype: AIMSDataManager.Address
        """

        return self.uidm.currentReviewFeature(self.currentGroup,
                                              self.currentFeatureKey)

    def featureSelected(self, row=None):
        """ 
        Sets the current feature reference when the user selects a feature

        @param row: The row the user has selected
        @type row: integer
        """

        if self.currentGroup[0]:
            fProxyIndex = self.featuresTableView.selectionModel().currentIndex(
            )
            fSourceIndex = self._featureProxyModel.mapToSource(fProxyIndex)
            self.currentFeatureKey = self.featureModel.tableSelectionMade(
                fSourceIndex.row())
            self.uQueueEditor.currentFeatureToUi(self.currentReviewFeature())
            coords = self.uidm.reviewItemCoords(self.currentGroup,
                                                self.currentFeatureKey)
            if coords:
                self.setMarker(coords)

    def reinstateFeatSelection(self):
        """
        When data is refreshed, attempt to reinstate
        the last feature selection        
        """

        if self.currentFeatureKey:
            matchedIndex = self.featureModel.findfield('{}'.format(
                self.currentFeatureKey))
            if matchedIndex.isValid():
                self.featuresTableView.selectRow(
                    self._featureProxyModel.mapFromSource(matchedIndex).row())
                return
        self.featuresTableView.selectRow(0)

    def groupSelected(self, row=None):
        """
        Set reference to current and alternative group records 

        @param row: The row the user has selected
        @type row: integer
        """

        proxyIndex = self.groupTableView.selectionModel().currentIndex()
        sourceIndex = self._groupProxyModel.mapToSource(proxyIndex)
        self.currentGroup = self.groupModel.tableSelectionMade(
            sourceIndex.row())

        altProxyIndex = self.groupTableView.model().index(
            proxyIndex.row() + 1, 0)

        if self._groupProxyModel.rowCount() == 0:
            self.currentGroup = None
            self.altSelectionId = 0
        elif self._groupProxyModel.rowCount() == 1:
            self.altSelectionId = 0
            #self.featuresTableView.selectRow(0)
            self.reinstateFeatSelection()
            return
        elif altProxyIndex.row() == -1:
            altProxyIndex = self.groupTableView.model().index(
                proxyIndex.row() - 1, 0)

        self.altSelectionId = self.groupTableView.model().data(altProxyIndex)
        self.reinstateFeatSelection()
        #self.featuresTableView.selectRow(0)

    def userFilterChanged(self, index):
        """ 
        Capture the user selection from filtering comboBox
        
        @param index: Combobox Index
        @type index: QModelIndex    
        """

        item = self.comboBoxUser.model().itemFromIndex(index)
        if item.checkState() == Qt.Checked:
            item.setCheckState(Qt.Unchecked)
        else:
            item.setCheckState(Qt.Checked)
        self.applyFilter(self.comboBoxUser)
        self.groupSelected()

    def groupsFilter(self, row, data):
        """
        Apply filter to group data
        
        @param data: String of active group filter item separated by vBars
        @type data: string     
        """

        self._groupProxyModel.setFilterKeyColumn(-1)
        self._groupProxyModel.setFilterRegExp(data)

    def applyFilter(self, parent):
        """ 
        Filter Group Table when the comboBoxUser parameters are modified
        """
        self.comboSelection = []
        uFilter = ''
        model = parent.model()
        for row in range(model.rowCount()):
            item = model.item(row)
            if item.checkState() == Qt.Checked:
                uFilter += '|' + item.text()
                self.comboSelection.append(item.text())
        self.groupsFilter(row, str(uFilter)[1:])

    def popUserCombo(self):
        """
        Obtain all unique and active AIMS publisher values 
        """

        data = list(set(self.groupModel.getUsers()))
        data.sort()
        self.popCombo(data, self.comboModelUser)

    def popCombo(self, cElements, model):
        """
        Populate the comboBoxUser with unique system users
        
        @param cElements: List of Elements to populate combo box
        @type cElements: list
        @param model: The ComboBox ItemModel
        @type model: QtGui.QStandardItemModel
        """

        for i in range(len(cElements)):
            item = QStandardItem(cElements[i])
            item.setCheckable(True)
            if item.text() in self.comboSelection:
                item.setCheckState(Qt.Checked)
            model.setItem(i, item)

    def updateFeature(self):
        """
        Update the properties of a review queue item 
        """

        self.feature = self.currentReviewFeature()
        if not self.feature:
            return
        if self.feature._changeType == 'Retire':
            UiUtility.raiseErrorMesg(self._iface,
                                     'Retire Items cannot be updated')
            return
        if UiUtility.formCompleteness('update', self.uQueueEditor,
                                      self._iface):
            UiUtility.formToObj(self)
            respId = int(time.time())
            self.uidm.repairAddress(self.feature, respId)
            self._controller.RespHandler.handleResp(respId, FEEDS['AR'])
            self.feature = None
            self.uQueueEditor.featureId = 0

    def isDuplicateOnRoad(self, reviewObj):
        """
        Hack. Solution to adding duplicates to a road has
        seen the API down grade duplicates from warning to infos. 
        the requirement is to now raise a warning to the user when
        creating a duplicate on a road. Unfortunately this can only
        be caught at this late stage by match the info strings.  
        
        
        @param resObj: resolution object that is being accpeted
        @type feedType: AIMSDataManager.Address.
        """
        info = []
        dupOnRoad = 'Address is not Unique on the road object'
        # Standard API feed
        if hasattr(reviewObj.meta, '_entities'):
            info = [
                reviewObj.meta._entities[x]._description
                for x in range(len(reviewObj.meta._entities))
                if hasattr(reviewObj.meta._entities[x], '_description')
            ]
        # Temp obj created from API response
        if hasattr(reviewObj.meta, '_errors'):
            # is dict if populated
            if type(reviewObj.meta._errors) is dict:
                info = [
                    reviewObj.meta._errors['info'][x]
                    for x in range(len(reviewObj.meta._errors['info']))
                    if reviewObj.meta._errors.has_key('info')
                ]

        if dupOnRoad in info:
            proceed = QMessageBox.question(
                self._iface.mainWindow(), 'Duplicate Warning',
                '{} \n \n Do You Want To Proceed and Create / Modify a Duplicate Address'
                .format(dupOnRoad), QMessageBox.Yes | QMessageBox.No,
                QMessageBox.No)
            if proceed == QMessageBox.No:
                return False

        return True

    def reviewResolution(self, action):
        """
        Decline or Accept review item as per the action parameter  

        @param feedType: feed type indicator
        @type feedType: AIMSDataManager.AimsUtility.FeedRef
        """

        for row in self.groupTableView.selectionModel().selectedRows():
            sourceIndex = self._groupProxyModel.mapToSource(row)
            objRef = ()
            objRef = self.groupModel.getObjRef(sourceIndex)
            feedType = FEEDS['GR'] if objRef[1] not in (
                'Add', 'Update', 'Retire') else FEEDS['AR']
            reviewObj = self.singleReviewObj(feedType, objRef[0])
            if reviewObj:
                respId = int(time.time())
                if action == 'accept':
                    if not self.isDuplicateOnRoad(reviewObj): return
                    self.uidm.accept(reviewObj, feedType, respId)
                elif action == 'decline':
                    self.uidm.decline(reviewObj, feedType, respId)

                if self._controller.RespHandler.handleResp(
                        respId, feedType, action):
                    self.highlight.hideReview()
                self.reinstateSelection()

    def decline(self):
        """
        Decline review item 
        """

        self.reviewResolution('decline')

    def accept(self):
        """
        Accept review item 
        """

        self.reviewResolution('accept')

    def display(self):
        """
        Zoom to Review Items Coordinates 
        """

        if self.currentFeatureKey:
            coords = self.uidm.reviewItemCoords(self.currentGroup,
                                                self.currentFeatureKey)
            if self.currentAdrCoord == coords or not coords:
                return
            self.currentAdrCoord = coords
            buffer = .00100
            extents = QgsRectangle(coords[0] - buffer, coords[1] - buffer,
                                   coords[0] + buffer, coords[1] + buffer)
            self._iface.mapCanvas().setExtent(extents)
            self._iface.mapCanvas().refresh()
            self.setMarker(coords)

    @pyqtSlot()
    def rDataChanged(self):
        """
        Slot communicated to when the UIDataManager Modifies 
        the Review Data
        """

        self._queues.uResolutionTab.refreshData()
class ResponseHandler(object):

    # logging
    global uilog
    uilog = Logger.setup(lf='uiLog')

    def __init__(self, iface, uidm):
        self._iface = iface
        self.uidm = uidm
        self.updateSuccessful = None
        self.afar = {
            ft: AddressFactory.getInstance(FEEDS['AR'])
            for ft in FeedType.reverse
        }
        self.afaf = {
            ft: AddressFactory.getInstance(FEEDS['AF'])
            for ft in FeedType.reverse
        }

    def updateData(self, respObj, feedType, action):
        """
        Update the UiDataManager's data to reflect the received response from the API

        @param respObj: AIMS Feature
        @type  respObj: AIMSDataManager.Address.AddressResolution
        @param feedType: feed type indicator
        @type feedType: AIMSDataManager.AimsUtility.FeedRef
        @param action: Either Accept or Decline indicating review actions
        @type action: QtGui.QWidget()
        """

        if hasattr(respObj, '_changeGroupId') and feedType == FEEDS['AR']:
            respObj = self.afar[FeedType.RESOLUTIONFEED].cast(respObj)
            # Hack to allow the supplementing of missing AF data with AR data
            if action == 'supplement':
                self.updateSuccessful = respObj
                return
            self.uidm.updateGdata(respObj)
        elif feedType == FEEDS['AC']:
            respObj = self.afar[FeedType.RESOLUTIONFEED].cast(respObj)
            self.uidm.updateRdata(respObj, feedType)
        elif feedType == FEEDS['AR']:
            # Hack to allow the supplementing of missing AF data with AR data
            if action == 'supplement':
                self.updateSuccessful = respObj
                return
            self.uidm.updateRdata(respObj, feedType)
            if respObj._queueStatus == 'Accepted':
                self.ismeshblockoverride(respObj)
                respObj = self.afaf[FeedType.FEATURES].cast(respObj)
                self.uidm.updateFdata(respObj)
        else:
            self.updateSuccessful = False
            return
        self.updateSuccessful = True
        return

    def ismeshblockoverride(self, respObj):
        """
        The derived mblk override flag is not in the resp
        obj, here we check and create if need be
        
        @param respObj: AIMS Feature
        @type  respObj: AIMSDataManager.Address.AddressResolution
        """

        if hasattr(respObj, '_codes_meshblock'):
            if respObj._codes_meshblock:
                respObj.setIsMeshblockOverride(True)

    def displayWarnings(self, warnings):
        """
        Raise warnings to the user
        
        @param warnings: Tuple of the warnings that are require to shown to the user
        @type  warnings: tuple
        """

        message = ''
        for warning in warnings:
            message += u'\u2022 {}\n'.format(warning)
        QMessageBox.warning(self._iface.mainWindow(), "Action Rejected",
                            message)

    def matchResp(self, response, respId, feedType, i, action):
        """
        Compile a list of warnings that are at the "Reject" level.
        If there are no warnings for the matching respID, proceed to update data

        @param response: AIMS Feature
        @type  response: AIMSDataManager.Address.AddressResolution
        @param respId: Int that relates the request and response
        @type  respId: integer
        @param feedType: feed type indicator
        @type feedType: AIMSDataManager.AimsUtility.FeedRef
        @param i: Number iterations taken the responseHandler to receive a response. Logging only
        @type i: integer
        @param action: Either Accept or Decline indicating review actions
        @type action: QtGui.QWidget()
        """

        warnings = []
        for resp in response:
            if resp.meta._requestId == respId:
                #logging
                uilog.info(
                    ' *** DATA ***    response received from DM for respId: {0} of type: {1} after {2} seconds'
                    .format(respId, feedType, i))
                if resp.meta._errors['reject']:
                    self.displayWarnings(resp.meta.errors['reject'])
                    return True

                # Hack -- failed acceptance but has an accepted status
                elif resp.meta._errors[
                        'warning'] and resp._queueStatus == 'Accepted':
                    self.displayWarnings(resp.meta.errors['warning'])
                    resp.setQueueStatus('Under Review')
                # Hack -- failed acceptance but  has an accepted status
                elif resp.meta._errors[
                        'error'] and resp._queueStatus == 'Accepted':
                    self.displayWarnings(resp.meta.errors['error'])
                    resp.setQueueStatus('Under Review')

                # else captured resp and no critical warnings
                # precede to update self._data
                self.updateData(resp, feedType, action)
                return True

    def handleResp(self, respId, feedType, action=None):
        """
        Test for a response in the response queue with the relevant repId'''
        
        @param respId: Int that relates the request and response
        @type  respId: integer
        @param feedType: feed type indicator
        @type feedType: AIMSDataManager.AimsUtility.FeedRef
        @param action: Either Accept or Decline indicating review actions
        @type action: QtGui.QWidget()
        """
        self.updateSuccessful = False

        for i in range(0, 20):
            resp = self.uidm.response(feedType)
            if resp and resp != (None, ):  # found and processed response
                if self.matchResp(resp, respId, feedType, i, action):
                    return self.updateSuccessful
            else:
                time.sleep(1)

        self._iface.messageBar().pushMessage(
            "Incomplete Response",
            "Data may not be complete - Please expect a data refresh shortly",
            level=QgsMessageBar.WARNING)
        #logging
        uilog.info(
            ' *** DATA ***    Time Out ({0} seconds): No response received from DM for respId: {1} of feedtype: {2}'
            .format(i, respId, feedType))
Пример #6
0
class MoveAddressTool(QgsMapToolIdentify):
    """
    Tool for relocating AIMS Features
    """

    # logging
    global uilog
    uilog = Logger.setup(lf='uiLog')

    def __init__(self, iface, layerManager, controller):
        """
        Intialise Move Address Tool
        
        @param iface: QgisInterface Abstract base class defining interfaces exposed by QgisApp  
        @type iface: Qgisinterface Object
        @param layerManager: Plugins layer manager
        @type  layerManager: AimsUI.LayerManager()
        @param controller: Instance of the plugins controller
        @type  controller: AimsUI.AimsClient.Gui.Controller()
        """

        QgsMapToolIdentify.__init__(self, iface.mapCanvas())
        self._iface = iface
        self._canvas = iface.mapCanvas()
        self._layers = layerManager
        self._controller = controller
        self.RespHandler = ResponseHandler(self._iface, self._controller.uidm)
        self.highlighter = self._controller.highlighter
        self.afc = {
            ft: AddressFactory.getInstance(FEEDS['AC'])
            for ft in FeedType.reverse
        }
        self.aff = FeatureFactory.getInstance(
            FeedRef(FeatureType.ADDRESS, FeedType.FEATURES))
        self._features = []
        self._sb = self._iface.mainWindow().statusBar()
        self.activate()

    def activate(self):
        """
        Activate Move Address Tool
        """

        QgsMapTool.activate(self)
        self._sb.showMessage("Select feature to move")
        self.cursor = QCursor(Qt.CrossCursor)
        self.parent().setCursor(self.cursor)

    def deactivate(self):
        """
        Deactivate Move Address Tool
        """

        self._sb.clearMessage()

    def setEnabled(self, enabled):
        """ 
        When Tool related QAction is checked/unchecked
        Activate / Disable respectively

        @param enabled: Tool enabled. Boolean value
        @type enabled: boolean
        """

        self._enabled = enabled
        if enabled:
            self.activate()
        else:
            self.deactivate()

    def setMarker(self, coords):
        """
        Place marker on canvas to indicate the feature to be moved

        @param coords: QgsPoint
        @type coords: QgsPoint
        """

        self.highlighter.setAddress(coords)

    def hideMarker(self):
        """
        Remove the marker from the canvas
        """

        self.highlighter.hideAddress()

    def canvasReleaseEvent(self, mouseEvent):
        """
        Identify the AIMS Feature(s) the user clicked

        @param mouseEvent: QtGui.QMouseEvent
        @type mouseEvent: QtGui.QMouseEvent
        """

        self._iface.setActiveLayer(self._layers.addressLayer())

        if mouseEvent.button() == Qt.LeftButton:
            results = self.identify(mouseEvent.x(), mouseEvent.y(),
                                    self.ActiveLayer, self.VectorLayer)
            # Ensure feature list and highlighting is reset
            self._features = []
            self.hideMarker()

            if len(results) == 0:
                return
            elif len(results) == 1:
                # Highlight feature
                coords = results[0].mFeature.geometry().asPoint()
                self.setMarker(coords)
                # create address object for feature. It is this obj properties that will be passed to API
                self._features.append(
                    self._controller.uidm.singleFeatureObj(
                        results[0].mFeature.attribute('addressId')))
                self._sb.showMessage("Right click for features new location")

            else:  # Stacked points

                identifiedFeatures = []
                coords = results[0].mFeature.geometry().asPoint()
                self.setMarker(coords)
                for i in range(0, len(results)):
                    identifiedFeatures.append(
                        dict(fullAddress=results[i].mFeature.attribute(
                            'fullAddress'),
                             addressId=results[i].mFeature.attribute(
                                 'addressId')))

                dlg = MoveAddressDialog(self._iface.mainWindow())
                moveFeatures = dlg.selectFeatures(identifiedFeatures)

                if moveFeatures:
                    # Form a list of Ids as selected by the user
                    moveFeaturesIds = [d['addressId'] for d in moveFeatures]

                    for featureId in moveFeaturesIds:
                        self._features.append(
                            self._controller.uidm.singleFeatureObj(featureId))

                    self._sb.showMessage(
                        "Right click for features new location")

                else:
                    self._features = None

        # Right click for new position
        if mouseEvent.button() == Qt.RightButton:
            results = self.identify(mouseEvent.x(), mouseEvent.y(),
                                    self.ActiveLayer, self.VectorLayer)
            if self._features:
                if len(results) == 0:
                    coords = self.toMapCoordinates(
                        QPoint(mouseEvent.x(), mouseEvent.y()))
                else:
                    # Snapping. i.e Move to stack
                    coords = results[0].mFeature.geometry().asPoint()

                # set new coords for all selected features
                coords = list(UiUtility.transform(self._iface, coords))

                for feature in self._features:
                    # Hack to retrieve the properties missing on the
                    # feature feed from the resolution feed <
                    respId = int(time.time())
                    self._controller.uidm.supplementAddress(feature, respId)
                    feature = self._controller.RespHandler.handleResp(
                        respId, FEEDS['AR'], 'supplement')
                    # />
                    #feature.type = FEEDS['AF']

                    # below clone is part of a fix still to be tested
                    clone = feature.clone(feature, self.aff.get())
                    clone._addressedObject_addressPositions[0].setCoordinates(
                        coords, )
                    if clone._codes_isMeshblockOverride != True:
                        clone.setMeshblock(None)
                    respId = int(time.time())
                    clone = self.afc[FeedType.CHANGEFEED].cast(clone)
                    self._controller.uidm.updateAddress(clone, respId)
                    self.RespHandler.handleResp(respId, FEEDS['AC'])

                self._features = []
                self.hideMarker()
                self._sb.clearMessage()