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 __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()
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 __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 __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 __init__(self, parent = None): super().__init__(parent) self.mCreatedEditors = QMapList() self.mEditorToProperty = QMap()
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
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
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)
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)
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)
def __init__(self, parent=None): super().__init__(parent) self.mCreatedEditors = QMapList() self.mEditorToProperty = QMap()
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
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)