Пример #1
0
    def __init__(self, parent=None):
        self.mValues = QMap()
        self.mSuggestions = QMapList()
        self.mSuggestionsAttribute = QString()
        self.Data = VariantPropertyManager.Data()

        super().__init__(parent)
        self.mSuggestionsAttribute = "suggestions"
Пример #2
0
    def __init__(self, parent=None):
        super(DecoratedDoubleSpinBoxFactory, self).__init__(parent)

        self.propertyToData = QMap()
        # We delegate responsibilities for QtDoublePropertyManager, which is a base class
        #   of DecoratedDoublePropertyManager to appropriate 
        self.originalFactory = QtDoubleSpinBoxFactory(self)
        self.createdEditors = QMapList()
        self.editorToProperty = QMap()
Пример #3
0
    def __init__(self, parent = None):
        self.mValues = QMap()
        self.mSuggestions = QMapList()
        self.mSuggestionsAttribute = QString()
        self.Data = VariantPropertyManager.Data()

        super().__init__(parent)
        self.mSuggestionsAttribute = "suggestions"
Пример #4
0
    def __init__(self, parent = None):
        super().__init__(parent)
        self.mExpandedGroups = QMapList()
        self.mObjectsView = ObjectsView()
        self.mMapDocument = None

        self.setObjectName("ObjectsDock")
        self.mActionObjectProperties = QAction(self)
        self.mActionObjectProperties.setIcon(QIcon(":/images/16x16/document-properties.png"))
        Utils.setThemeIcon(self.mActionObjectProperties, "document-properties")
        self.mActionObjectProperties.triggered.connect(self.objectProperties)
        handler = MapDocumentActionHandler.instance()
        widget = QWidget(self)
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(5, 5, 5, 5)
        layout.setSpacing(0)
        layout.addWidget(self.mObjectsView)
        self.mActionNewLayer = QAction(self)
        self.mActionNewLayer.setIcon(QIcon(":/images/16x16/document-new.png"))
        self.mActionNewLayer.triggered.connect(handler.actionAddObjectGroup().triggered)
        self.mActionMoveToGroup = QAction(self)
        self.mActionMoveToGroup.setIcon(QIcon(":/images/16x16/layer-object.png"))
        toolBar = QToolBar()
        toolBar.setFloatable(False)
        toolBar.setMovable(False)
        toolBar.setIconSize(QSize(16, 16))
        toolBar.addAction(self.mActionNewLayer)
        toolBar.addAction(handler.actionDuplicateObjects())
        toolBar.addAction(handler.actionRemoveObjects())
        toolBar.addAction(self.mActionMoveToGroup)
        button = toolBar.widgetForAction(self.mActionMoveToGroup)
        self.mMoveToMenu = QMenu(self)
        button.setPopupMode(QToolButton.InstantPopup)
        button.setMenu(self.mMoveToMenu)
        self.mMoveToMenu.aboutToShow.connect(self.aboutToShowMoveToMenu)
        self.mMoveToMenu.triggered.connect(self.triggeredMoveToMenu)
        toolBar.addAction(self.mActionObjectProperties)
        layout.addWidget(toolBar)
        self.setWidget(widget)
        self.retranslateUi()
        DocumentManager.instance().documentAboutToClose.connect(self.documentAboutToClose)
Пример #5
0
    def __init__(self, parent = None):
        super().__init__(self.tr("Edit Polygons"),
              QIcon(":images/24x24/tool-edit-polygons.png"),
              QKeySequence(self.tr("E")),
              parent)

        self.mSelectedHandles = QSet()
        self.mModifiers = Qt.KeyboardModifiers()
        self.mScreenStart = QPoint()
        self.mOldHandlePositions = QVector()
        self.mAlignPosition = QPointF()
        ## The list of handles associated with each selected map object
        self.mHandles = QMapList()
        self.mOldPolygons = QMap()
        self.mStart = QPointF()

        self.mSelectionRectangle = SelectionRectangle()
        self.mMousePressed = False
        self.mClickedHandle = None
        self.mClickedObjectItem = None
        self.mMode = EditPolygonTool.NoMode
Пример #6
0
    def __init__(self, parent = None):
        super().__init__(parent)

        self.mCreatedEditors = QMapList()
        self.mEditorToProperty = QMap()
Пример #7
0
class VariantEditorFactory(QtVariantEditorFactory):
    def __init__(self, parent = None):
        super().__init__(parent)

        self.mCreatedEditors = QMapList()
        self.mEditorToProperty = QMap()

    def __del__(self):
        self.mEditorToProperty.clear()

    def connectPropertyManager(self, manager):
        manager.valueChangedSignal.connect(self.slotPropertyChanged)
        manager.attributeChangedSignal.connect(self.slotPropertyAttributeChanged)
        super().connectPropertyManager(manager)

    def createEditor(self, manager, property, parent):
        type = manager.propertyType(property)
        if (type == VariantPropertyManager.filePathTypeId()):
            editor = FileEdit(parent)
            editor.setFilePath(manager.value(property))
            editor.setFilter(manager.attributeValue(property, "filter"))
            self.mCreatedEditors[property].append(editor)
            self.mEditorToProperty[editor] = property
            editor.filePathChanged.connect(self.slotSetValue)
            editor.destroyed.connect(self.slotEditorDestroyed)
            return editor

        editor = super().createEditor(manager, property, parent)
        if (type == QVariant.String):
            # Add support for "suggestions" attribute that adds a QCompleter to the QLineEdit
            suggestions = manager.attributeValue(property, "suggestions")
            if suggestions and len(suggestions)>0:
                lineEdit = editor
                if lineEdit:
                    completer = QCompleter(suggestions, lineEdit)
                    completer.setCaseSensitivity(Qt.CaseInsensitive)
                    lineEdit.setCompleter(completer)
        return editor

    def disconnectPropertyManager(self, manager):
        manager.valueChangedSignal.disconnect(self.slotPropertyChanged)
        manager.attributeChangedSignal.disconnect(self.slotPropertyAttributeChanged)
        super().disconnectPropertyManager(manager)

    def slotPropertyChanged(self, property, value):
        if (not self.mCreatedEditors.contains(property)):
            return
        editors = self.mCreatedEditors[property]
        for itEditor in editors:
            itEditor.setFilePath(value.toString())

    def slotPropertyAttributeChanged(self, property, attribute, value):
        if (not self.mCreatedEditors.contains(property)):
            return
        if (attribute != "filter"):
            return
        editors = self.mCreatedEditors[property]
        for itEditor in editors:
            itEditor.setFilter(value.toString())

    def slotSetValue(self, value):
        object = self.sender()
        itEditor = self.mEditorToProperty.constBegin()
        while (itEditor != self.mEditorToProperty.constEnd()):
            if (itEditor.key() == object):
                property = itEditor.value()
                manager = self.propertyManager(property)
                if (not manager):
                    return
                manager.setValue(property, value)
                return

            itEditor += 1

    def slotEditorDestroyed(self, object):
        for itEditor in self.mEditorToProperty:
            if (itEditor.key() == object):
                editor = itEditor.key()
                property = itEditor.value()
                self.mEditorToProperty.remove(editor)
                self.mCreatedEditors[property].removeAll(editor)
                if (self.mCreatedEditors[property].isEmpty()):
                    self.mCreatedEditors.remove(property)
                return
Пример #8
0
class DecoratedDoubleSpinBoxFactory(QtAbstractEditorFactory):

    def __init__(self, parent=None):
        super(DecoratedDoubleSpinBoxFactory, self).__init__(parent)

        self.propertyToData = QMap()
        # We delegate responsibilities for QtDoublePropertyManager, which is a base class
        #   of DecoratedDoublePropertyManager to appropriate 
        self.originalFactory = QtDoubleSpinBoxFactory(self)
        self.createdEditors = QMapList()
        self.editorToProperty = QMap()

    # not need to delete editors because they will be deld by originalFactory in its destructor
    def __del__(self):
        pass

    def connectPropertyManager(self, manager):
        self.originalFactory.addPropertyManager(manager)
        manager.prefixChangedSignal.connect(self.slotPrefixChanged)
        manager.suffixChangedSignal.connect(self.slotSuffixChanged)

    def createEditor(self, manager, property, parent):
        base = self.originalFactory
        w = base.findEditor(property, parent)
        if (not w):
            return 0

        spinBox = w
        if (not spinBox):
            return 0

        spinBox.setPrefix(manager.prefix(property))
        spinBox.setSuffix(manager.suffix(property))

        self.createdEditors[property].append(spinBox)
        self.editorToProperty[spinBox] = property

        return spinBox

    def disconnectPropertyManager(self, manager):
        self.originalFactory.removePropertyManager(manager)
        manager.prefixChangedSignal.disconnect(self.slotPrefixChanged)
        manager.suffixChangedSignal.disconnect(self.slotSuffixChanged)

    def slotPrefixChanged(self, property, prefix):
        if (not self.createdEditors.contains(property)):
            return

        manager = self.propertyManager(property)
        if (not manager):
            return

        editors = self.createdEditors[property]
        for editor in editors:
            editor.setPrefix(prefix)

    def slotSuffixChanged(self, property, prefix):
        if (not self.createdEditors.contains(property)):
            return

        manager = self.propertyManager(property)
        if (not manager):
            return

        editors = self.createdEditors[property]
        for editor in editors:
            editor.setSuffix(prefix)

    def slotEditorDestroyed(self, object):
        property = self.editorToProperty.get(object)
        if property:
            editor = object
            self.editorToProperty.remove(editor)
            self.createdEditors[property].removeAll(editor)
            if (self.createdEditors[property].isEmpty()):
                self.createdEditors.remove(property)
            return
Пример #9
0
class VariantPropertyManager(QtVariantPropertyManager):
    TYPEID_FILEPATH = 400

    class Data:
        def __init__(self):
            self.value = QString()
            self.filter = QString()

    def __init__(self, parent = None):
        self.mValues = QMap()
        self.mSuggestions = QMapList()
        self.mSuggestionsAttribute = QString()
        self.Data = VariantPropertyManager.Data()

        super().__init__(parent)
        self.mSuggestionsAttribute = "suggestions"

    def value(self, property):
        if (self.mValues.contains(property)):
            return self.mValues[property].value
        return super().value(property)

    def valueType(self, propertyType):
        if (propertyType == VariantPropertyManager.filePathTypeId()):
            return QVariant.String
        return super().valueType(propertyType)

    def isPropertyTypeSupported(self, propertyType):
        if (propertyType == VariantPropertyManager.filePathTypeId()):
            return True
        return super().isPropertyTypeSupported(propertyType)

    def attributes(self, propertyType):
        if (propertyType == VariantPropertyManager.filePathTypeId()):
            attr = QStringList()
            attr.append("filter")
            return attr

        return super().attributes(propertyType)

    def attributeType(self, propertyType, attribute):
        if (propertyType == VariantPropertyManager.filePathTypeId()):
            if (attribute == "filter"):
                return QVariant.String
            return 0

        return super().attributeType(propertyType, attribute)

    def attributeValue(self, property, attribute):
        if (self.mValues.contains(property)):
            if (attribute == "filter"):
                return self.mValues[property].filter
            return QVariant()

        if (attribute == self.mSuggestionsAttribute and self.mSuggestions.contains(property)):
            return self.mSuggestions[property]
        return super().attributeValue(property, attribute)

    def filePathTypeId():
        return VariantPropertyManager.TYPEID_FILEPATH

    def setValue(self, property, val):
        if (self.mValues.contains(property)):
            if type(val) != str:
                return
            s = val
            d = self.mValues[property]
            if (d.value == s):
                return
            d.value = s
            self.mValues[property] = d
            self.propertyChangedSignal.emit(property)
            self.valueChangedSignal.emit(property, s)
            return

        super().setValue(property, val)

    def setAttribute(self, property, attribute, val):
        if (self.mValues.contains(property)):
            if (attribute == "filter"):
                if type(val) != str:
                    return
                s = val
                d = self.mValues[property]
                if (d.filter == s):
                    return
                d.filter = s
                self.mValues[property] = d
                self.attributeChangedSignal.emit(property, attribute, s)

            return

        if (attribute == self.mSuggestionsAttribute and self.mSuggestions.contains(property)):
            self.mSuggestions[property] = val
        super().setAttribute(property, attribute, val)

    def valueText(self, property):
        if (self.mValues.contains(property)):
            return self.mValues[property].value
        return super().valueText(property)

    def initializeProperty(self, property):
        tp = self.propertyType(property)
        if (tp == VariantPropertyManager.filePathTypeId()):
            self.mValues[property] = VariantPropertyManager.Data()
        elif tp == QVariant.String:
            self.mSuggestions[property] = QStringList()
        super().initializeProperty(property)

    def uninitializeProperty(self, property):
        self.mValues.remove(property)
        self.mSuggestions.remove(property)
        super().uninitializeProperty(property)
Пример #10
0
class ObjectsDock(QDockWidget):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.mExpandedGroups = QMapList()
        self.mObjectsView = ObjectsView()
        self.mMapDocument = None

        self.setObjectName("ObjectsDock")
        self.mActionObjectProperties = QAction(self)
        self.mActionObjectProperties.setIcon(QIcon(":/images/16x16/document-properties.png"))
        Utils.setThemeIcon(self.mActionObjectProperties, "document-properties")
        self.mActionObjectProperties.triggered.connect(self.objectProperties)
        handler = MapDocumentActionHandler.instance()
        widget = QWidget(self)
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(5, 5, 5, 5)
        layout.setSpacing(0)
        layout.addWidget(self.mObjectsView)
        self.mActionNewLayer = QAction(self)
        self.mActionNewLayer.setIcon(QIcon(":/images/16x16/document-new.png"))
        self.mActionNewLayer.triggered.connect(handler.actionAddObjectGroup().triggered)
        self.mActionMoveToGroup = QAction(self)
        self.mActionMoveToGroup.setIcon(QIcon(":/images/16x16/layer-object.png"))
        toolBar = QToolBar()
        toolBar.setFloatable(False)
        toolBar.setMovable(False)
        toolBar.setIconSize(QSize(16, 16))
        toolBar.addAction(self.mActionNewLayer)
        toolBar.addAction(handler.actionDuplicateObjects())
        toolBar.addAction(handler.actionRemoveObjects())
        toolBar.addAction(self.mActionMoveToGroup)
        button = toolBar.widgetForAction(self.mActionMoveToGroup)
        self.mMoveToMenu = QMenu(self)
        button.setPopupMode(QToolButton.InstantPopup)
        button.setMenu(self.mMoveToMenu)
        self.mMoveToMenu.aboutToShow.connect(self.aboutToShowMoveToMenu)
        self.mMoveToMenu.triggered.connect(self.triggeredMoveToMenu)
        toolBar.addAction(self.mActionObjectProperties)
        layout.addWidget(toolBar)
        self.setWidget(widget)
        self.retranslateUi()
        DocumentManager.instance().documentAboutToClose.connect(self.documentAboutToClose)

    def setMapDocument(self, mapDoc):
        if (self.mMapDocument):
            self.saveExpandedGroups(self.mMapDocument)
            self.mMapDocument.disconnect()

        self.mMapDocument = mapDoc
        self.mObjectsView.setMapDocument(mapDoc)
        if (self.mMapDocument):
            self.restoreExpandedGroups(self.mMapDocument)
            self.mMapDocument.selectedObjectsChanged.connect(self.updateActions)

        self.updateActions()

    def changeEvent(self, e):
        super().changeEvent(e)
        x = e.type()
        if x==QEvent.LanguageChange:
            self.retranslateUi()
        else:
            pass

    def updateActions(self):
        if self.mMapDocument:
            count = self.mMapDocument.selectedObjects().count()
        else:
            count = 0
        enabled = count > 0
        self.mActionObjectProperties.setEnabled(count == 1)
        if (self.mMapDocument and (self.mMapDocument.map().objectGroupCount() < 2)):
            enabled = False
        self.mActionMoveToGroup.setEnabled(enabled)
        self.mActionMoveToGroup.setToolTip(self.tr("Move %n Object(s) to Layer", "", count))

    def aboutToShowMoveToMenu(self):
        self.mMoveToMenu.clear()
        for objectGroup in self.mMapDocument.map().objectGroups():
            action = self.mMoveToMenu.addAction(objectGroup.name())
            action.setData(QVariant(objectGroup))

    def triggeredMoveToMenu(self, action):
        handler = MapDocumentActionHandler.instance()
        objectGroup = action.data()
        handler.moveObjectsToGroup(objectGroup)

    def objectProperties(self):
        selectedObjects = self.mMapDocument.selectedObjects()
        mapObject = selectedObjects.first()
        self.mMapDocument.setCurrentObject(mapObject)
        self.mMapDocument.emitEditCurrentObject()

    def documentAboutToClose(self, mapDocument):
        self.mExpandedGroups.remove(mapDocument)

    def retranslateUi(self):
        self.setWindowTitle(self.tr("Objects"))
        self.mActionNewLayer.setToolTip(self.tr("Add Object Layer"))
        self.mActionObjectProperties.setToolTip(self.tr("Object Properties"))
        self.updateActions()

    def saveExpandedGroups(self, mapDoc):
        self.mExpandedGroups[mapDoc].clear()
        for og in mapDoc.map().objectGroups():
            if (self.mObjectsView.isExpanded(self.mObjectsView.model().index(og))):
                self.mExpandedGroups[mapDoc].append(og)

    def restoreExpandedGroups(self, mapDoc):
        for og in self.mExpandedGroups[mapDoc]:
            self.mObjectsView.setExpanded(self.mObjectsView.model().index(og), True)
        self.mExpandedGroups[mapDoc].clear()
        # Also restore the selection
        for o in mapDoc.selectedObjects():
            index = self.mObjectsView.model().index(o)
            self.mObjectsView.selectionModel().select(index, QItemSelectionModel.Select | QItemSelectionModel.Rows)
Пример #11
0
class VariantPropertyManager(QtVariantPropertyManager):
    TYPEID_FILEPATH = 400

    class Data:
        def __init__(self):
            self.value = QString()
            self.filter = QString()

    def __init__(self, parent=None):
        self.mValues = QMap()
        self.mSuggestions = QMapList()
        self.mSuggestionsAttribute = QString()
        self.Data = VariantPropertyManager.Data()

        super().__init__(parent)
        self.mSuggestionsAttribute = "suggestions"

    def value(self, property):
        if (self.mValues.contains(property)):
            return self.mValues[property].value
        return super().value(property)

    def valueType(self, propertyType):
        if (propertyType == VariantPropertyManager.filePathTypeId()):
            return QVariant.String
        return super().valueType(propertyType)

    def isPropertyTypeSupported(self, propertyType):
        if (propertyType == VariantPropertyManager.filePathTypeId()):
            return True
        return super().isPropertyTypeSupported(propertyType)

    def attributes(self, propertyType):
        if (propertyType == VariantPropertyManager.filePathTypeId()):
            attr = QStringList()
            attr.append("filter")
            return attr

        return super().attributes(propertyType)

    def attributeType(self, propertyType, attribute):
        if (propertyType == VariantPropertyManager.filePathTypeId()):
            if (attribute == "filter"):
                return QVariant.String
            return 0

        return super().attributeType(propertyType, attribute)

    def attributeValue(self, property, attribute):
        if (self.mValues.contains(property)):
            if (attribute == "filter"):
                return self.mValues[property].filter
            return QVariant()

        if (attribute == self.mSuggestionsAttribute
                and self.mSuggestions.contains(property)):
            return self.mSuggestions[property]
        return super().attributeValue(property, attribute)

    def filePathTypeId():
        return VariantPropertyManager.TYPEID_FILEPATH

    def setValue(self, property, val):
        if (self.mValues.contains(property)):
            if type(val) != str:
                return
            s = val
            d = self.mValues[property]
            if (d.value == s):
                return
            d.value = s
            self.mValues[property] = d
            self.propertyChangedSignal.emit(property)
            self.valueChangedSignal.emit(property, s)
            return

        super().setValue(property, val)

    def setAttribute(self, property, attribute, val):
        if (self.mValues.contains(property)):
            if (attribute == "filter"):
                if type(val) != str:
                    return
                s = val
                d = self.mValues[property]
                if (d.filter == s):
                    return
                d.filter = s
                self.mValues[property] = d
                self.attributeChangedSignal.emit(property, attribute, s)

            return

        if (attribute == self.mSuggestionsAttribute
                and self.mSuggestions.contains(property)):
            self.mSuggestions[property] = val
        super().setAttribute(property, attribute, val)

    def valueText(self, property):
        if (self.mValues.contains(property)):
            return self.mValues[property].value
        return super().valueText(property)

    def initializeProperty(self, property):
        tp = self.propertyType(property)
        if (tp == VariantPropertyManager.filePathTypeId()):
            self.mValues[property] = VariantPropertyManager.Data()
        elif tp == QVariant.String:
            self.mSuggestions[property] = QStringList()
        super().initializeProperty(property)

    def uninitializeProperty(self, property):
        self.mValues.remove(property)
        self.mSuggestions.remove(property)
        super().uninitializeProperty(property)
Пример #12
0
    def __init__(self, parent=None):
        super().__init__(parent)

        self.mCreatedEditors = QMapList()
        self.mEditorToProperty = QMap()
Пример #13
0
class VariantEditorFactory(QtVariantEditorFactory):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.mCreatedEditors = QMapList()
        self.mEditorToProperty = QMap()

    def __del__(self):
        self.mEditorToProperty.clear()

    def connectPropertyManager(self, manager):
        manager.valueChangedSignal.connect(self.slotPropertyChanged)
        manager.attributeChangedSignal.connect(
            self.slotPropertyAttributeChanged)
        super().connectPropertyManager(manager)

    def createEditor(self, manager, property, parent):
        type = manager.propertyType(property)
        if (type == VariantPropertyManager.filePathTypeId()):
            editor = FileEdit(parent)
            editor.setFilePath(manager.value(property))
            editor.setFilter(manager.attributeValue(property, "filter"))
            self.mCreatedEditors[property].append(editor)
            self.mEditorToProperty[editor] = property
            editor.filePathChanged.connect(self.slotSetValue)
            editor.destroyed.connect(self.slotEditorDestroyed)
            return editor

        editor = super().createEditor(manager, property, parent)
        if (type == QVariant.String):
            # Add support for "suggestions" attribute that adds a QCompleter to the QLineEdit
            suggestions = manager.attributeValue(property, "suggestions")
            if suggestions and len(suggestions) > 0:
                lineEdit = editor
                if lineEdit:
                    completer = QCompleter(suggestions, lineEdit)
                    completer.setCaseSensitivity(Qt.CaseInsensitive)
                    lineEdit.setCompleter(completer)
        return editor

    def disconnectPropertyManager(self, manager):
        manager.valueChangedSignal.disconnect(self.slotPropertyChanged)
        manager.attributeChangedSignal.disconnect(
            self.slotPropertyAttributeChanged)
        super().disconnectPropertyManager(manager)

    def slotPropertyChanged(self, property, value):
        if (not self.mCreatedEditors.contains(property)):
            return
        editors = self.mCreatedEditors[property]
        for itEditor in editors:
            itEditor.setFilePath(value.toString())

    def slotPropertyAttributeChanged(self, property, attribute, value):
        if (not self.mCreatedEditors.contains(property)):
            return
        if (attribute != "filter"):
            return
        editors = self.mCreatedEditors[property]
        for itEditor in editors:
            itEditor.setFilter(value.toString())

    def slotSetValue(self, value):
        object = self.sender()
        itEditor = self.mEditorToProperty.constBegin()
        while (itEditor != self.mEditorToProperty.constEnd()):
            if (itEditor.key() == object):
                property = itEditor.value()
                manager = self.propertyManager(property)
                if (not manager):
                    return
                manager.setValue(property, value)
                return

            itEditor += 1

    def slotEditorDestroyed(self, object):
        for itEditor in self.mEditorToProperty:
            if (itEditor.key() == object):
                editor = itEditor.key()
                property = itEditor.value()
                self.mEditorToProperty.remove(editor)
                self.mCreatedEditors[property].removeAll(editor)
                if (self.mCreatedEditors[property].isEmpty()):
                    self.mCreatedEditors.remove(property)
                return
Пример #14
0
class EditPolygonTool(AbstractObjectTool):
    NoMode, Selecting, Moving = range(3)

    def __init__(self, parent = None):
        super().__init__(self.tr("Edit Polygons"),
              QIcon(":images/24x24/tool-edit-polygons.png"),
              QKeySequence(self.tr("E")),
              parent)

        self.mSelectedHandles = QSet()
        self.mModifiers = Qt.KeyboardModifiers()
        self.mScreenStart = QPoint()
        self.mOldHandlePositions = QVector()
        self.mAlignPosition = QPointF()
        ## The list of handles associated with each selected map object
        self.mHandles = QMapList()
        self.mOldPolygons = QMap()
        self.mStart = QPointF()

        self.mSelectionRectangle = SelectionRectangle()
        self.mMousePressed = False
        self.mClickedHandle = None
        self.mClickedObjectItem = None
        self.mMode = EditPolygonTool.NoMode

    def __del__(self):
        del self.mSelectionRectangle

    def tr(self, sourceText, disambiguation = '', n = -1):
        return QCoreApplication.translate('EditPolygonTool', sourceText, disambiguation, n)

    def activate(self, scene):
        super().activate(scene)
        self.updateHandles()
        # TODO: Could be more optimal by separating the updating of handles from
        # the creation and removal of handles depending on changes in the
        # selection, and by only updating the handles of the objects that changed.
        self.mapDocument().objectsChanged.connect(self.updateHandles)
        scene.selectedObjectItemsChanged.connect(self.updateHandles)
        self.mapDocument().objectsRemoved.connect(self.objectsRemoved)

    def deactivate(self, scene):
        try:
            self.mapDocument().objectsChanged.disconnect(self.updateHandles)
            scene.selectedObjectItemsChanged.disconnect(self.updateHandles)
        except:
            pass
        # Delete all handles
        self.mHandles.clear()
        self.mSelectedHandles.clear()
        self.mClickedHandle = None
        super().deactivate(scene)

    def mouseEntered(self):
        pass
    def mouseMoved(self, pos, modifiers):
        super().mouseMoved(pos, modifiers)
        if (self.mMode == EditPolygonTool.NoMode and self.mMousePressed):
            screenPos = QCursor.pos()
            dragDistance = (self.mScreenStart - screenPos).manhattanLength()
            if (dragDistance >= QApplication.startDragDistance()):
                if (self.mClickedHandle):
                    self.startMoving()
                else:
                    self.startSelecting()

        x = self.mMode
        if x==EditPolygonTool.Selecting:
            self.mSelectionRectangle.setRectangle(QRectF(self.mStart, pos).normalized())
        elif x==EditPolygonTool.Moving:
            self.updateMovingItems(pos, modifiers)
        elif x==EditPolygonTool.NoMode:
            pass

    def mousePressed(self, event):
        if (self.mMode != EditPolygonTool.NoMode): # Ignore additional presses during select/move
            return
        x = event.button()
        if x==Qt.LeftButton:
            self.mMousePressed = True
            self.mStart = event.scenePos()
            self.mScreenStart = event.screenPos()
            items = self.mapScene().items(self.mStart,
                                                                   Qt.IntersectsItemShape,
                                                                   Qt.DescendingOrder,
                                                                   viewTransform(event))
            self.mClickedObjectItem = first(items, MapObjectItem)
            self.mClickedHandle = first(items, PointHandle)
        elif x==Qt.RightButton:
            items = self.mapScene().items(event.scenePos(),
                                                                   Qt.IntersectsItemShape,
                                                                   Qt.DescendingOrder,
                                                                   viewTransform(event))
            clickedHandle = first(items)
            if (clickedHandle or not self.mSelectedHandles.isEmpty()):
                self.showHandleContextMenu(clickedHandle,
                                      event.screenPos())
            else:
                super().mousePressed(event)
        else:
            super().mousePressed(event)

    def mouseReleased(self, event):
        if (event.button() != Qt.LeftButton):
            return
        x = self.mMode
        if x==EditPolygonTool.NoMode:
            if (self.mClickedHandle):
                selection = self.mSelectedHandles
                modifiers = event.modifiers()
                if (modifiers & (Qt.ShiftModifier | Qt.ControlModifier)):
                    if (selection.contains(self.mClickedHandle)):
                        selection.remove(self.mClickedHandle)
                    else:
                        selection.insert(self.mClickedHandle)
                else:
                    selection.clear()
                    selection.insert(self.mClickedHandle)

                self.setSelectedHandles(selection)
            elif (self.mClickedObjectItem):
                selection = self.mapScene().selectedObjectItems()
                modifiers = event.modifiers()
                if (modifiers & (Qt.ShiftModifier | Qt.ControlModifier)):
                    if (selection.contains(self.mClickedObjectItem)):
                        selection.remove(self.mClickedObjectItem)
                    else:
                        selection.insert(self.mClickedObjectItem)
                else:
                    selection.clear()
                    selection.insert(self.mClickedObjectItem)

                self.mapScene().setSelectedObjectItems(selection)
                self.updateHandles()
            elif (not self.mSelectedHandles.isEmpty()):
                # First clear the handle selection
                self.setSelectedHandles(QSet())
            else:
                # If there is no handle selection, clear the object selection
                self.mapScene().setSelectedObjectItems(QSet())
                self.updateHandles()
        elif x==EditPolygonTool.Selecting:
            self.updateSelection(event)
            self.mapScene().removeItem(self.mSelectionRectangle)
            self.mMode = EditPolygonTool.NoMode
        elif x==EditPolygonTool.Moving:
            self.finishMoving(event.scenePos())

        self.mMousePressed = False
        self.mClickedHandle = None

    def modifiersChanged(self, modifiers):
        self.mModifiers = modifiers

    def languageChanged(self):
        self.setName(self.tr("Edit Polygons"))
        self.setShortcut(QKeySequence(self.tr("E")))

    def updateHandles(self):
        selection = self.mapScene().selectedObjectItems()
        # First destroy the handles for objects that are no longer selected

        for l in range(len(self.mHandles)):
            i = self.mHandles.itemByIndex(l)
            if (not selection.contains(i[0])):
                for handle in i[1]:
                    if (handle.isSelected()):
                        self.mSelectedHandles.remove(handle)
                    del handle

                del self.mHandles[l]

        renderer = self.mapDocument().renderer()
        for item in selection:
            object = item.mapObject()
            if (not object.cell().isEmpty()):
                continue
            polygon = object.polygon()
            polygon.translate(object.position())
            pointHandles = self.mHandles.get(item)
            # Create missing handles
            while (pointHandles.size() < polygon.size()):
                handle = PointHandle(item, pointHandles.size())
                pointHandles.append(handle)
                self.mapScene().addItem(handle)

            # Remove superfluous handles
            while (pointHandles.size() > polygon.size()):
                handle = pointHandles.takeLast()
                if (handle.isSelected()):
                    self.mSelectedHandles.remove(handle)
                del handle

            # Update the position of all handles
            for i in range(pointHandles.size()):
                point = polygon.at(i)
                handlePos = renderer.pixelToScreenCoords_(point)
                internalHandlePos = handlePos - item.pos()
                pointHandles.at(i).setPos(item.mapToScene(internalHandlePos))

            self.mHandles.insert(item, pointHandles)

    def objectsRemoved(self, objects):
        if (self.mMode == EditPolygonTool.Moving):
            # Make sure we're not going to try to still change these objects when
            # finishing the move operation.
            # TODO: In addition to avoiding crashes, it would also be good to
            # disallow other actions while moving.
            for object in objects:
                self.mOldPolygons.remove(object)

    def deleteNodes(self):
        if (self.mSelectedHandles.isEmpty()):
            return
        p = groupIndexesByObject(self.mSelectedHandles)
        undoStack = self.mapDocument().undoStack()
        delText = self.tr("Delete %n Node(s)", "", self.mSelectedHandles.size())
        undoStack.beginMacro(delText)
        for i in p:
            object = i[0]
            indexRanges = i[1]
            oldPolygon = object.polygon()
            newPolygon = oldPolygon
            # Remove points, back to front to keep the indexes valid
            it = indexRanges.end()
            begin = indexRanges.begin()
            # assert: end != begin, since there is at least one entry
            while(it != begin):
                it -= 1
                newPolygon.remove(it.first(), it.length())
            if (newPolygon.size() < 2):
                # We've removed the entire object
                undoStack.push(RemoveMapObject(self.mapDocument(), object))
            else:
                undoStack.push(ChangePolygon(self.mapDocument(), object, newPolygon, oldPolygon))

        undoStack.endMacro()

    def joinNodes(self):
        if (self.mSelectedHandles.size() < 2):
            return
        p = groupIndexesByObject(self.mSelectedHandles)
        undoStack = self.mapDocument().undoStack()
        macroStarted = False
        for i in p:
            object = i[0]
            indexRanges = i[1]
            closed = object.shape() == MapObject.Polygon
            oldPolygon = object.polygon()
            newPolygon = joinPolygonNodes(oldPolygon, indexRanges,
                                                    closed)
            if (newPolygon.size() < oldPolygon.size()):
                if (not macroStarted):
                    undoStack.beginMacro(self.tr("Join Nodes"))
                    macroStarted = True

                undoStack.push(ChangePolygon(self.mapDocument(), object, newPolygon, oldPolygon))

        if (macroStarted):
            undoStack.endMacro()

    def splitSegments(self):
        if (self.mSelectedHandles.size() < 2):
            return
        p = groupIndexesByObject(self.mSelectedHandles)
        undoStack = self.mapDocument().undoStack()
        macroStarted = False
        for i in p:
            object = i[0]
            indexRanges = i[1]
            closed = object.shape() == MapObject.Polygon
            oldPolygon = object.polygon()
            newPolygon = splitPolygonSegments(oldPolygon, indexRanges,
                                                        closed)
            if (newPolygon.size() > oldPolygon.size()):
                if (not macroStarted):
                    undoStack.beginMacro(self.tr("Split Segments"))
                    macroStarted = True

                undoStack.push(ChangePolygon(self.mapDocument(), object, newPolygon, oldPolygon))

        if (macroStarted):
            undoStack.endMacro()

    def setSelectedHandles(self, handles):
        for handle in self.mSelectedHandles:
            if (not handles.contains(handle)):
                handle.setSelected(False)
        for handle in handles:
            if (not self.mSelectedHandles.contains(handle)):
                handle.setSelected(True)
        self.mSelectedHandles = handles

    def setSelectedHandle(self, handle):
        self.setSelectedHandles(QSet([handle]))

    def updateSelection(self, event):
        rect = QRectF(self.mStart, event.scenePos()).normalized()
        # Make sure the rect has some contents, otherwise intersects returns False
        rect.setWidth(max(1.0, rect.width()))
        rect.setHeight(max(1.0, rect.height()))
        oldSelection = self.mapScene().selectedObjectItems()
        if (oldSelection.isEmpty()):
            # Allow selecting some map objects only when there aren't any selected
            selectedItems = QSet()
            for item in self.mapScene().items(rect, Qt.IntersectsItemShape, Qt.DescendingOrder, viewTransform(event)):
                if type(item) == MapObjectItem:
                    selectedItems.insert(item)

            newSelection = QSet()
            if (event.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier)):
                newSelection = oldSelection | selectedItems
            else:
                newSelection = selectedItems

            self.mapScene().setSelectedObjectItems(newSelection)
            self.updateHandles()
        else:
            # Update the selected handles
            selectedHandles = QSet()
            for item in self.mapScene().items(rect, Qt.IntersectsItemShape, Qt.DescendingOrder, viewTransform(event)):
                if type(item) == PointHandle:
                    selectedHandles.insert(item)

            if (event.modifiers() & (Qt.ControlModifier | Qt.ShiftModifier)):
                self.setSelectedHandles(self.mSelectedHandles | selectedHandles)
            else:
                self.setSelectedHandles(selectedHandles)

    def startSelecting(self):
        self.mMode = EditPolygonTool.Selecting
        self.mapScene().addItem(self.mSelectionRectangle)

    def startMoving(self):
        # Move only the clicked handle, if it was not part of the selection
        if (not self.mSelectedHandles.contains(self.mClickedHandle)):
            self.setSelectedHandle(self.mClickedHandle)
        self.mMode = EditPolygonTool.Moving
        renderer = self.mapDocument().renderer()
        # Remember the current object positions
        self.mOldHandlePositions.clear()
        self.mOldPolygons.clear()
        self.mAlignPosition = renderer.screenToPixelCoords_((self.mSelectedHandles.begin()).pos())
        for handle in self.mSelectedHandles:
            pos = renderer.screenToPixelCoords_(handle.pos())
            self.mOldHandlePositions.append(handle.pos())
            if (pos.x() < self.mAlignPosition.x()):
                self.mAlignPosition.setX(pos.x())
            if (pos.y() < self.mAlignPosition.y()):
                self.mAlignPosition.setY(pos.y())
            mapObject = handle.mapObject()
            if (not self.mOldPolygons.contains(mapObject)):
                self.mOldPolygons.insert(mapObject, mapObject.polygon())

    def updateMovingItems(self, pos, modifiers):
        renderer = self.mapDocument().renderer()
        diff = pos - self.mStart
        snapHelper = SnapHelper(renderer, modifiers)
        if (snapHelper.snaps()):
            alignScreenPos = renderer.pixelToScreenCoords_(self.mAlignPosition)
            newAlignScreenPos = alignScreenPos + diff
            newAlignPixelPos = renderer.screenToPixelCoords_(newAlignScreenPos)
            snapHelper.snap(newAlignPixelPos)
            diff = renderer.pixelToScreenCoords_(newAlignPixelPos) - alignScreenPos

        i = 0
        for handle in self.mSelectedHandles:
            # update handle position
            newScreenPos = self.mOldHandlePositions.at(i) + diff
            handle.setPos(newScreenPos)

            # calculate new pixel position of polygon node
            item = handle.mapObjectItem()
            newInternalPos = item.mapFromScene(newScreenPos)
            newScenePos = item.pos() + newInternalPos
            newPixelPos = renderer.screenToPixelCoords_(newScenePos)

            # update the polygon
            mapObject = item.mapObject()
            polygon = mapObject.polygon()
            polygon[handle.pointIndex()] = newPixelPos - mapObject.position()
            self.mapDocument().mapObjectModel().setObjectPolygon(mapObject, polygon)

            i += 1

    def finishMoving(self, pos):
        self.mMode = EditPolygonTool.NoMode
        if (self.mStart == pos or self.mOldPolygons.isEmpty()): # Move is a no-op
            return
        undoStack = self.mapDocument().undoStack()
        undoStack.beginMacro(self.tr("Move %n Point(s)", "", self.mSelectedHandles.size()))
        # TODO: This isn't really optimal. Would be better to have a single undo
        # command that supports changing multiple map objects.
        for i in self.mOldPolygons:
            undoStack.push(ChangePolygon(self.mapDocument(), i[0], i[1]))

        undoStack.endMacro()
        self.mOldHandlePositions.clear()
        self.mOldPolygons.clear()

    def showHandleContextMenu(self, clickedHandle, screenPos):
        if (clickedHandle and not self.mSelectedHandles.contains(clickedHandle)):
            self.setSelectedHandle(clickedHandle)
        n = self.mSelectedHandles.size()
        delIcon = QIcon(":images/16x16/edit-delete.png")
        delText = self.tr("Delete %n Node(s)", "", n)
        menu = QMenu()
        deleteNodesAction = menu.addAction(delIcon, delText)
        joinNodesAction = menu.addAction(self.tr("Join Nodes"))
        splitSegmentsAction = menu.addAction(self.tr("Split Segments"))
        Utils.setThemeIcon(deleteNodesAction, "edit-delete")
        joinNodesAction.setEnabled(n > 1)
        splitSegmentsAction.setEnabled(n > 1)
        deleteNodesAction.triggered.connect(self.deleteNodes)
        joinNodesAction.triggered.connect(self.joinNodes)
        splitSegmentsAction.triggered.connect(self.splitSegments)
        menu.exec(screenPos)