class RandomPicker(): def __init__(self): self.mSum = 0.0 self.mThresholds = QMap() def add(self, value, probability=1.0): self.mSum += probability self.mThresholds.insert(self.mSum, value) def isEmpty(self): return self.mThresholds.isEmpty() def pick(self): random = (rand() / RAND_MAX) * self.mSum it = self.mThresholds.lowerBound(random) if (it != self.mThresholds.end()): return self.mThresholds.itemByIndex(it)[1] else: return self.mThresholds.itemByIndex(-1)[1] def clear(self): self.mSum = 0.0 self.mThresholds.clear()
class RandomPicker(): def __init__(self): self.mSum = 0.0 self.mThresholds = QMap() def add(self, value, probability = 1.0): self.mSum += probability self.mThresholds.insert(self.mSum, value) def isEmpty(self): return self.mThresholds.isEmpty() def pick(self): random = (rand() / RAND_MAX) * self.mSum it = self.mThresholds.lowerBound(random) if (it != self.mThresholds.end()): return self.mThresholds.itemByIndex(it)[1] else: return self.mThresholds.itemByIndex(-1)[1] def clear(self): self.mSum = 0.0 self.mThresholds.clear()
class QtCursorDatabase(): def __init__(self): self.m_cursorNames = QList() self.m_cursorIcons = QMap() self.m_valueToCursorShape = QMap() self.m_cursorShapeToValue = QMap() self.appendCursor( Qt.ArrowCursor, QCoreApplication.translate("QtCursorDatabase", "Arrow"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-arrow.png")) self.appendCursor( Qt.UpArrowCursor, QCoreApplication.translate("QtCursorDatabase", "Up Arrow"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-uparrow.png") ) self.appendCursor( Qt.CrossCursor, QCoreApplication.translate("QtCursorDatabase", "Cross"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-cross.png")) self.appendCursor( Qt.WaitCursor, QCoreApplication.translate("QtCursorDatabase", "Wait"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-wait.png")) self.appendCursor( Qt.IBeamCursor, QCoreApplication.translate("QtCursorDatabase", "IBeam"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-ibeam.png")) self.appendCursor( Qt.SizeVerCursor, QCoreApplication.translate("QtCursorDatabase", "Size Vertical"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-sizev.png")) self.appendCursor( Qt.SizeHorCursor, QCoreApplication.translate("QtCursorDatabase", "Size Horizontal"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-sizeh.png")) self.appendCursor( Qt.SizeFDiagCursor, QCoreApplication.translate("QtCursorDatabase", "Size Backslash"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-sizef.png")) self.appendCursor( Qt.SizeBDiagCursor, QCoreApplication.translate("QtCursorDatabase", "Size Slash"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-sizeb.png")) self.appendCursor( Qt.SizeAllCursor, QCoreApplication.translate("QtCursorDatabase", "Size All"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-sizeall.png") ) self.appendCursor( Qt.BlankCursor, QCoreApplication.translate("QtCursorDatabase", "Blank"), QIcon()) self.appendCursor( Qt.SplitVCursor, QCoreApplication.translate("QtCursorDatabase", "Split Vertical"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-vsplit.png")) self.appendCursor( Qt.SplitHCursor, QCoreApplication.translate("QtCursorDatabase", "Split Horizontal"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-hsplit.png")) self.appendCursor( Qt.PointingHandCursor, QCoreApplication.translate("QtCursorDatabase", "Pointing Hand"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-hand.png")) self.appendCursor( Qt.ForbiddenCursor, QCoreApplication.translate("QtCursorDatabase", "Forbidden"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-forbidden.png" )) self.appendCursor( Qt.OpenHandCursor, QCoreApplication.translate("QtCursorDatabase", "Open Hand"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-openhand.png" )) self.appendCursor( Qt.ClosedHandCursor, QCoreApplication.translate("QtCursorDatabase", "Closed Hand"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-closedhand.png" )) self.appendCursor( Qt.WhatsThisCursor, QCoreApplication.translate("QtCursorDatabase", "What's This"), QIcon( ":/qt-project.org/qtpropertybrowser/images/cursor-whatsthis.png" )) self.appendCursor( Qt.BusyCursor, QCoreApplication.translate("QtCursorDatabase", "Busy"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-busy.png")) def clear(self): self.m_cursorNames.clear() self.m_cursorIcons.clear() self.m_valueToCursorShape.clear() self.m_cursorShapeToValue.clear() def appendCursor(self, shape, name, icon): if self.m_cursorShapeToValue.get(shape): return value = len(self.m_cursorNames) self.m_cursorNames.append(name) self.m_cursorIcons[value] = icon self.m_valueToCursorShape[value] = shape self.m_cursorShapeToValue[shape] = value def cursorShapeNames(self): return self.m_cursorNames def cursorShapeIcons(self): return self.m_cursorIcons def cursorToShapeName(self, cursor): val = self.cursorToValue(cursor) if val >= 0: return self.m_cursorNames[val] return '' def cursorToShapeIcon(self, cursor): val = self.cursorToValue(cursor) return self.m_cursorIcons[val] def cursorToValue(self, cursor): shape = cursor.shape() return self.m_cursorShapeToValue.get(shape, -1) def valueToCursor(self, value): if value in self.m_valueToCursorShape: return QCursor(self.m_valueToCursorShape[value]) return QCursor()
class RangeSet(): # This class is based on std.map rather than QMap since std.map's insert # method has an overload that takes a hint about where to insert the new # pair. def __init__(self): self.mMap = QMap() ## # Insert \a value in the set of ranges. Has no effect when the value is # already part of an existing range. When possible, an existing range is # extended to include the new value, otherwise a new range is inserted. ## def insert(self, value): if (self.mMap.empty()): self.mMap.insert(value, value) return # We can now assume that 'it' will be at most one end of the range # This is the only full-tree search of the map, everything else is # relative to this it = self.mMap.lowerBound(value) itValue = self.mMap.itemByIndex(it) begin = self.mMap.begin() end = self.mMap.end() if (it == end): # Check whether the value is included in the last range # assert: it != begin it -= 1 itValue = self.mMap.itemByIndex(it) # assert: it.first < value if (itValue[1] >= value): return # Try to add the value to the end of the previous range itValue[1] += 1 if (itValue[1] == value): return # Didn't work, restore the previous range itValue[1] -= 1 # We have to insert a new range self.mMap.insert(it, [value, value]) return # Now we can dereference 'it' itself # assert: it.first >= value if (itValue[0] == value): return # Check whether we can extend the range downwards to include value if (itValue[0] == value + 1): # When extending the range downwards, it may need to be merged # with the previous range. # Remember 'prev' for the insertion hint. It is not necessarily # before the value, if it == begin. prev = itValue if (it != begin): prev = self.mMap.itemByIndex(prev - 1) if (prev[1] == value - 1): # The new value fills the gab. Merge the ranges, leaving # only the first, but with a larger range. prev[1] = itValue[1] self.mMap.erase(itValue[0]) return # No merge needed # To change the key, we have to both add and remove. Add first, # then remove, to avoid invalidating the iterator too early. self.mMap.insert(prev, [value, itValue[1]]) self.mMap.erase(it) return # Check if we can grow the previous range upwards to include value if (it != begin): it -= 1 itValue = self.mMap.itemByIndex(it) if (itValue[1] == value - 1): itValue[1] += 1 return # 'it' now points below the range, unless it was already begin # We couldn't increase an existing range self.mMap.insert(it, [value, value]) ## # Removes all ranges from this set. ## def clear(self): self.mMap.clear() # Only are provided, because it is not safe to modify the # underlying list. Note that const_iterator is a typedef for Range. def begin(self): return self.mMap.begin() def end(self): return self.mMap.end() def isEmpty(self): return self.mMap.empty() def item(self, i): return self.mMap.itemByIndex(i) def __len__(self): return len(self.mMap) def __iter__(self): return self.mMap.__iter__()
class TileStampManager(QObject): setStamp = pyqtSignal(TileStamp) def __init__(self, toolManager, parent=None): super().__init__(parent) self.mStampsByName = QMap() self.mQuickStamps = QVector() for i in range(TileStampManager.quickStampKeys().__len__()): self.mQuickStamps.append(0) self.mTileStampModel = TileStampModel(self) self.mToolManager = toolManager prefs = preferences.Preferences.instance() prefs.stampsDirectoryChanged.connect(self.stampsDirectoryChanged) self.mTileStampModel.stampAdded.connect(self.stampAdded) self.mTileStampModel.stampRenamed.connect(self.stampRenamed) self.mTileStampModel.stampChanged.connect(self.saveStamp) self.mTileStampModel.stampRemoved.connect(self.deleteStamp) self.loadStamps() def __del__(self): # needs to be over here where the TileStamp type is complete pass ## # Returns the keys used for quickly accessible tile stamps. # Note: To store a tile layer <Ctrl> is added. The given keys will work # for recalling the stored values. ## def quickStampKeys(): keys = [ Qt.Key_1, Qt.Key_2, Qt.Key_3, Qt.Key_4, Qt.Key_5, Qt.Key_6, Qt.Key_7, Qt.Key_8, Qt.Key_9 ] return keys def tileStampModel(self): return self.mTileStampModel def createStamp(self): stamp = self.tampFromContext(self.mToolManager.selectedTool()) if (not stamp.isEmpty()): self.mTileStampModel.addStamp(stamp) return stamp def addVariation(self, targetStamp): stamp = stampFromContext(self.mToolManager.selectedTool()) if (stamp.isEmpty()): return if (stamp == targetStamp): # avoid easy mistake of adding duplicates return for variation in stamp.variations(): self.mTileStampModel.addVariation(targetStamp, variation) def selectQuickStamp(self, index): stamp = self.mQuickStamps.at(index) if (not stamp.isEmpty()): self.setStamp.emit(stamp) def createQuickStamp(self, index): stamp = stampFromContext(self.mToolManager.selectedTool()) if (stamp.isEmpty()): return self.setQuickStamp(index, stamp) def extendQuickStamp(self, index): quickStamp = self.mQuickStamps[index] if (quickStamp.isEmpty()): self.createQuickStamp(index) else: self.addVariation(quickStamp) def stampsDirectoryChanged(self): # erase current stamps self.mQuickStamps.fill(TileStamp()) self.mStampsByName.clear() self.mTileStampModel.clear() self.loadStamps() def eraseQuickStamp(self, index): stamp = self.mQuickStamps.at(index) if (not stamp.isEmpty()): self.mQuickStamps[index] = TileStamp() if (not self.mQuickStamps.contains(stamp)): self.mTileStampModel.removeStamp(stamp) def setQuickStamp(self, index, stamp): stamp.setQuickStampIndex(index) # make sure existing quickstamp is removed from stamp model self.eraseQuickStamp(index) self.mTileStampModel.addStamp(stamp) self.mQuickStamps[index] = stamp def loadStamps(self): prefs = preferences.Preferences.instance() stampsDirectory = prefs.stampsDirectory() stampsDir = QDir(stampsDirectory) iterator = QDirIterator(stampsDirectory, ["*.stamp"], QDir.Files | QDir.Readable) while (iterator.hasNext()): stampFileName = iterator.next() stampFile = QFile(stampFileName) if (not stampFile.open(QIODevice.ReadOnly)): continue data = stampFile.readAll() document = QJsonDocument.fromBinaryData(data) if (document.isNull()): # document not valid binary data, maybe it's an JSON text file error = QJsonParseError() document = QJsonDocument.fromJson(data, error) if (error.error != QJsonParseError.NoError): qDebug("Failed to parse stamp file:" + error.errorString()) continue stamp = TileStamp.fromJson(document.object(), stampsDir) if (stamp.isEmpty()): continue stamp.setFileName(iterator.fileInfo().fileName()) self.mTileStampModel.addStamp(stamp) index = stamp.quickStampIndex() if (index >= 0 and index < self.mQuickStamps.size()): self.mQuickStamps[index] = stamp def stampAdded(self, stamp): if (stamp.name().isEmpty() or self.mStampsByName.contains(stamp.name())): # pick the first available stamp name name = QString() index = self.mTileStampModel.stamps().size() while (self.mStampsByName.contains(name)): name = str(index) index += 1 stamp.setName(name) self.mStampsByName.insert(stamp.name(), stamp) if (stamp.fileName().isEmpty()): stamp.setFileName(findStampFileName(stamp.name())) self.saveStamp(stamp) def stampRenamed(self, stamp): existingName = self.mStampsByName.key(stamp) self.mStampsByName.remove(existingName) self.mStampsByName.insert(stamp.name(), stamp) existingFileName = stamp.fileName() newFileName = findStampFileName(stamp.name(), existingFileName) if (existingFileName != newFileName): if (QFile.rename(stampFilePath(existingFileName), stampFilePath(newFileName))): stamp.setFileName(newFileName) def saveStamp(self, stamp): # make sure we have a stamps directory prefs = preferences.Preferences.instance() stampsDirectory = prefs.stampsDirectory() stampsDir = QDir(stampsDirectory) if (not stampsDir.exists() and not stampsDir.mkpath(".")): qDebug("Failed to create stamps directory" + stampsDirectory) return filePath = stampsDir.filePath(stamp.fileName()) file = QSaveFile(filePath) if (not file.open(QIODevice.WriteOnly)): qDebug("Failed to open stamp file for writing" + filePath) return stampJson = stamp.toJson(QFileInfo(filePath).dir()) file.write(QJsonDocument(stampJson).toJson(QJsonDocument.Compact)) if (not file.commit()): qDebug() << "Failed to write stamp" << filePath def deleteStamp(self, stamp): self.mStampsByName.remove(stamp.name()) QFile.remove(stampFilePath(stamp.fileName()))
class RangeSet(): # This class is based on std.map rather than QMap since std.map's insert # method has an overload that takes a hint about where to insert the new # pair. def __init__(self): self.mMap = QMap() ## # Insert \a value in the set of ranges. Has no effect when the value is # already part of an existing range. When possible, an existing range is # extended to include the new value, otherwise a new range is inserted. ## def insert(self, value): if (self.mMap.empty()): self.mMap.insert(value, value) return # We can now assume that 'it' will be at most one end of the range # This is the only full-tree search of the map, everything else is # relative to this it = self.mMap.lowerBound(value) itValue = self.mMap.itemByIndex(it) begin = self.mMap.begin() end = self.mMap.end() if (it == end): # Check whether the value is included in the last range # assert: it != begin it -= 1 itValue = self.mMap.itemByIndex(it) # assert: it.first < value if (itValue[1] >= value): return # Try to add the value to the end of the previous range itValue[1] += 1 if (itValue[1] == value): return # Didn't work, restore the previous range itValue[1] -= 1 # We have to insert a new range self.mMap.insert(it, [value, value]) return # Now we can dereference 'it' itself # assert: it.first >= value if (itValue[0] == value): return # Check whether we can extend the range downwards to include value if (itValue[0] == value + 1): # When extending the range downwards, it may need to be merged # with the previous range. # Remember 'prev' for the insertion hint. It is not necessarily # before the value, if it == begin. prev = itValue if (it != begin): prev = self.mMap.itemByIndex(prev-1) if (prev[1] == value - 1): # The new value fills the gab. Merge the ranges, leaving # only the first, but with a larger range. prev[1] = itValue[1] self.mMap.erase(itValue[0]) return # No merge needed # To change the key, we have to both add and remove. Add first, # then remove, to avoid invalidating the iterator too early. self.mMap.insert(prev, [value, itValue[1]]) self.mMap.erase(it) return # Check if we can grow the previous range upwards to include value if (it != begin): it -= 1 itValue = self.mMap.itemByIndex(it) if (itValue[1] == value - 1): itValue[1] += 1 return # 'it' now points below the range, unless it was already begin # We couldn't increase an existing range self.mMap.insert(it, [value, value]) ## # Removes all ranges from this set. ## def clear(self): self.mMap.clear() # Only are provided, because it is not safe to modify the # underlying list. Note that const_iterator is a typedef for Range. def begin(self): return self.mMap.begin() def end(self): return self.mMap.end() def isEmpty(self): return self.mMap.empty() def item(self, i): return self.mMap.itemByIndex(i) def __len__(self): return len(self.mMap) def __iter__(self): return self.mMap.__iter__()
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.propertyToId = QMap() self.idToProperty = QMap() self.idToExpanded = QMap() editMenu = self.menuBar().addMenu(self.tr("Edit")) newObjectMenu = editMenu.addMenu(self.tr("New Object")) newRectangleAction = QAction(self.tr("Rectangle"), self) newRectangleAction.triggered.connect(self.newRectangle) newObjectMenu.addAction(newRectangleAction) newLineAction = QAction(self.tr("Line"), self) newLineAction.triggered.connect(self.newLine) newObjectMenu.addAction(newLineAction) newEllipseAction = QAction(self.tr("Ellipse"), self) newEllipseAction.triggered.connect(self.newEllipse) newObjectMenu.addAction(newEllipseAction) newTextAction = QAction(self.tr("Text"), self) newTextAction.triggered.connect(self.newText) newObjectMenu.addAction(newTextAction) self.deleteAction = QAction(self.tr("Delete Object"), self) self.deleteAction.triggered.connect(self.deleteObject) editMenu.addAction(self.deleteAction) clearAction = QAction(self.tr("Clear All"), self) clearAction.triggered.connect(self.clearAll) editMenu.addAction(clearAction) fillAction = QAction(self.tr("Fill View"), self) fillAction.triggered.connect(self.fillView) editMenu.addAction(fillAction) self.variantManager = QtVariantPropertyManager(self) self.variantManager.valueChangedSignal.connect(self.valueChanged) variantFactory = QtVariantEditorFactory(self) self.canvas = QtCanvas(800, 600) self.canvasView = CanvasView(self.canvas, self) self.setCentralWidget(self.canvasView) dock = QDockWidget(self) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.propertyEditor = QtTreePropertyBrowser(dock) self.propertyEditor.setFactoryForManager(self.variantManager, variantFactory) dock.setWidget(self.propertyEditor) self.currentItem = QtCanvasItem(None) self.canvasView.itemClickedSignal.connect(self.itemClicked) self.canvasView.itemMovedSignal.connect(self.itemMoved) self.fillView() self.itemClicked(QtCanvasItem(None)) def newRectangle(self): item = self.addRectangle() self.canvas.update() self.itemClicked(item) def newEllipse(self): item = self.addEllipse() self.canvas.update() self.itemClicked(item) def newLine(self): item = self.addLine() self.canvas.update() self.itemClicked(item) def newText(self): item = self.addText() self.canvas.update() self.itemClicked(item) def deleteObject(self): if (not self.currentItem or self.currentItem.isNone()): return self.canvas.removeItem(self.currentItem) self.currentItem = QtCanvasItem(None) self.itemClicked(self.currentItem) self.canvas.update() def clearAll(self): for item in self.canvas.allItems(): self.canvas.removeItem(item) self.itemClicked(QtCanvasItem(None)) self.canvas.update() def fillView(self): for i in range(10): self.addRectangle() self.addEllipse() self.addLine() self.addText() self.canvas.update() def addRectangle(self): item = QtCanvasRectangle(rand() % self.canvas.width(), rand() % self.canvas.height(), 50, 50, self.canvas) item.setBrush( QBrush(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8))) item.setPen( QPen(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8), 4)) item.setZ(rand() % 256) item.show() return item def addEllipse(self): item = QtCanvasEllipse(50, 50, self.canvas) item.setBrush( QBrush(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8))) item.move(rand() % self.canvas.width(), rand() % self.canvas.height()) item.setZ(rand() % 256) item.show() return item def addLine(self): item = QtCanvasLine(self.canvas) item.setPoints( 0, 0, rand() % self.canvas.width() - self.canvas.width() / 2, rand() % self.canvas.height() - self.canvas.height() / 2) item.move(rand() % self.canvas.width(), rand() % self.canvas.height()) item.setPen( QPen(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8), 6)) item.setZ(rand() % 256) item.show() return item def addText(self): item = QtCanvasText(self.canvas) item.setText(self.tr("Text")) item.setColor(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8)) item.move(rand() % self.canvas.width(), rand() % self.canvas.height()) item.setZ(rand() % 256) item.show() return item def itemMoved(self, item): if (item != self.currentItem or self.currentItem.isNone()): return self.variantManager.setValue(self.idToProperty["xpos"], item.x()) self.variantManager.setValue(self.idToProperty["ypos"], item.y()) self.variantManager.setValue(self.idToProperty["zpos"], item.z()) def updateExpandState(self): l = self.propertyEditor.topLevelItems() for item in l: prop = item.property() self.idToExpanded[ self.propertyToId[prop]] = self.propertyEditor.isExpanded(item) def itemClicked(self, item): self.updateExpandState() for p in self.propertyToId.keys(): p.destroy() self.propertyToId.clear() self.idToProperty.clear() self.currentItem = item if (not self.currentItem or self.currentItem.isNone()): self.deleteAction.setEnabled(False) return self.deleteAction.setEnabled(True) property = self.variantManager.addProperty(QVariant.Double, self.tr("Position X")) property.setAttribute("minimum", 0) property.setAttribute("maximum", self.canvas.width()) property.setValue(item.x()) self.addProperty(property, "xpos") property = self.variantManager.addProperty(QVariant.Double, self.tr("Position Y")) property.setAttribute("minimum", 0) property.setAttribute("maximum", self.canvas.height()) property.setValue(item.y()) self.addProperty(property, "ypos") property = self.variantManager.addProperty(QVariant.Double, self.tr("Position Z")) property.setAttribute("minimum", 0) property.setAttribute("maximum", 256) property.setValue(item.z()) self.addProperty(property, "zpos") if (item.rtti() == RttiValues.Rtti_Rectangle): i = item property = self.variantManager.addProperty(QVariant.Color, self.tr("Brush Color")) property.setValue(i.brush().color()) self.addProperty(property, "brush") property = self.variantManager.addProperty(QVariant.Color, self.tr("Pen Color")) property.setValue(i.pen().color()) self.addProperty(property, "pen") property = self.variantManager.addProperty(QVariant.Size, self.tr("Size")) property.setValue(i.size()) self.addProperty(property, "size") elif (item.rtti() == RttiValues.Rtti_Line): i = item property = self.variantManager.addProperty(QVariant.Color, self.tr("Pen Color")) property.setValue(i.pen().color()) self.addProperty(property, "pen") property = self.variantManager.addProperty(QVariant.Point, self.tr("Vector")) property.setValue(i.endPoint()) self.addProperty(property, "endpoint") elif (item.rtti() == RttiValues.Rtti_Ellipse): i = item property = self.variantManager.addProperty(QVariant.Color, self.tr("Brush Color")) property.setValue(i.brush().color()) self.addProperty(property, "brush") property = self.variantManager.addProperty(QVariant.Size, self.tr("Size")) property.setValue(QSize(i.width(), i.height())) self.addProperty(property, "size") elif (item.rtti() == RttiValues.Rtti_Text): i = item property = self.variantManager.addProperty(QVariant.Color, self.tr("Color")) property.setValue(i.color()) self.addProperty(property, "color") property = self.variantManager.addProperty(QVariant.String, self.tr("Text")) property.setValue(i.text()) self.addProperty(property, "text") property = self.variantManager.addProperty(QVariant.Font, self.tr("Font")) property.setValue(i.font()) self.addProperty(property, "font") def addProperty(self, property, id): self.propertyToId[property] = id self.idToProperty[id] = property item = self.propertyEditor.addProperty(property) if (self.idToExpanded.contains(id)): self.propertyEditor.setExpanded(item, self.idToExpanded[id]) def valueChanged(self, property, value): if (not self.propertyToId.contains(property)): return if (not self.currentItem or self.currentItem.isNone()): return id = self.propertyToId[property] if (id == "xpos"): self.currentItem.setX(value) elif (id == "ypos"): self.currentItem.setY(value) elif (id == "zpos"): self.currentItem.setZ(value) elif (id == "text"): if (self.currentItem.rtti() == RttiValues.Rtti_Text): i = self.currentItem i.setText(value) elif (id == "color"): if (self.currentItem.rtti() == RttiValues.Rtti_Text): i = self.currentItem i.setColor(value) elif (id == "brush"): if (self.currentItem.rtti() == RttiValues.Rtti_Rectangle or self.currentItem.rtti() == RttiValues.Rtti_Ellipse): i = self.currentItem b = QBrush(i.brush()) b.setColor(value) i.setBrush(b) elif (id == "pen"): if (self.currentItem.rtti() == RttiValues.Rtti_Rectangle or self.currentItem.rtti() == RttiValues.Rtti_Line): i = self.currentItem p = QPen(i.pen()) p.setColor(value) i.setPen(p) elif (id == "font"): if (self.currentItem.rtti() == RttiValues.Rtti_Text): i = self.currentItem i.setFont(value) elif (id == "endpoint"): if (self.currentItem.rtti() == RttiValues.Rtti_Line): i = self.currentItem p = value i.setPoints(i.startPoint().x(), i.startPoint().y(), p.x(), p.y()) elif (id == "size"): if (self.currentItem.rtti() == RttiValues.Rtti_Rectangle): i = self.currentItem s = value i.setSize(s.width(), s.height()) elif (self.currentItem.rtti() == RttiValues.Rtti_Ellipse): i = self.currentItem s = value i.setSize(s.width(), s.height()) self.canvas.update()
class MapObjectModel(QAbstractItemModel): objectsAdded = pyqtSignal(QList) objectsChanged = pyqtSignal(QList) objectsRemoved = pyqtSignal(QList) def __init__(self, parent): super().__init__(parent) self.mObjectGroups = QList() self.mObjects = QMap() self.mGroups = QMap() self.mMapDocument = None self.mMap = None self.mObject = None self.mObjectGroupIcon = ":/images/16x16/layer-object.png" def index(self, *args): l = len(args) if l>0: tp = type(args[0]) if tp==int: if l==2: args = (args[0], args[1], QModelIndex()) row, column, parent = args if (not parent.isValid()): if (row < self.mObjectGroups.count()): return self.createIndex(row, column, self.mGroups[self.mObjectGroups.at(row)]) return QModelIndex() og = self.toObjectGroup(parent) # happens when deleting the last item in a parent if (row >= og.objectCount()): return QModelIndex() # Paranoia: sometimes "fake" objects are in use (see createobjecttool) if (not self.mObjects.contains(og.objects().at(row))): return QModelIndex() return self.createIndex(row, column, self.mObjects[og.objects()[row]]) elif tp==ObjectGroup: og = args[0] row = self.mObjectGroups.indexOf(og) return self.createIndex(row, 0, self.mGroups[og]) elif tp==MapObject: if l==1: args = (args[0],0) o, column = args row = o.objectGroup().objects().indexOf(o) return self.createIndex(row, column, self.mObjects[o]) def parent(self, index): mapObject = self.toMapObject(index) if mapObject: return self.index(mapObject.objectGroup()) return QModelIndex() def rowCount(self, parent = QModelIndex()): if (not self.mMapDocument): return 0 if (not parent.isValid()): return self.mObjectGroups.size() og = self.toObjectGroup(parent) if og: return og.objectCount() return 0 def columnCount(self, parent = QModelIndex()): return 2 # MapObject name|type def headerData(self, section, orientation, role = Qt.DisplayRole): if (role == Qt.DisplayRole and orientation == Qt.Horizontal): x = section if x==0: return self.tr("Name") elif x==1: return self.tr("Type") return QVariant() def setData(self, index, value, role): mapObject = self.toMapObject(index) if mapObject: x = role if x==Qt.CheckStateRole: c = value visible = (c == Qt.Checked) if (visible != mapObject.isVisible()): command = SetMapObjectVisible(self.mMapDocument, mapObject, visible) self.mMapDocument.undoStack().push(command) return True elif x==Qt.EditRole: s = value if (index.column() == 0 and s != mapObject.name()): undo = self.mMapDocument.undoStack() undo.beginMacro(self.tr("Change Object Name")) undo.push(ChangeMapObject(self.mMapDocument, mapObject, s, mapObject.type())) undo.endMacro() if (index.column() == 1 and s != mapObject.type()): undo = self.mMapDocument.undoStack() undo.beginMacro(self.tr("Change Object Type")) undo.push(ChangeMapObject(self.mMapDocument, mapObject, mapObject.name(), s)) undo.endMacro() return True return False objectGroup = self.toObjectGroup(index) if objectGroup: x = role if x==Qt.CheckStateRole: layerModel = self.mMapDocument.layerModel() layerIndex = self.mMap.layers().indexOf(objectGroup) row = layerModel.layerIndexToRow(layerIndex) layerModel.setData(layerModel.index(row), value, role) return True elif x==Qt.EditRole: newName = value if (objectGroup.name() != newName): layerIndex = self.mMap.layers().indexOf(objectGroup) rename = RenameLayer(self.mMapDocument, layerIndex, newName) self.mMapDocument.undoStack().push(rename) return True return False return False def data(self, index, role = Qt.DisplayRole): mapObject = self.toMapObject(index) if mapObject: x = role if x==Qt.DisplayRole or x==Qt.EditRole: if index.column(): _x = mapObject.type() else: _x = mapObject.name() return _x elif x==Qt.DecorationRole: return QVariant() # no icon . maybe the color? elif x==Qt.CheckStateRole: if (index.column() > 0): return QVariant() if mapObject.isVisible(): _x = Qt.Checked else: _x = Qt.Unchecked return _x elif x==LayerModel.UserRoles.OpacityRole: return 1.0 else: return QVariant() objectGroup = self.toObjectGroup(index) if objectGroup: x = role if x==Qt.DisplayRole or x==Qt.EditRole: if index.column(): _x = QVariant() else: _x = objectGroup.name() return _x elif x==Qt.DecorationRole: if index.column(): _x = QVariant() else: _x = self.mObjectGroupIcon return _x elif x==Qt.CheckStateRole: if (index.column() > 0): return QVariant() if objectGroup.isVisible(): _x = Qt.Checked else: _x = Qt.Unchecked return _x elif x==LayerModel.UserRoles.OpacityRole: return objectGroup.opacity() else: return QVariant() return QVariant() def flags(self, index): rc = super().flags(index) if (index.column() == 0): rc |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable elif (index.parent().isValid()): rc |= Qt.ItemIsEditable # MapObject type return rc def toObjectGroup(self, index): if (not index.isValid()): return None oog = index.internalPointer() if oog: return oog.mGroup def toMapObject(self, index): if (not index.isValid()): return None oog = index.internalPointer() if oog: return oog.mObject def toLayer(self, index): if (not index.isValid()): return None oog = index.internalPointer() if oog: if oog.mGroup: _x = oog.mGroup else: _x = oog.mObject.objectGroup() return _x def setMapDocument(self, mapDocument): if (self.mMapDocument == mapDocument): return if (self.mMapDocument): self.mMapDocument.disconnect() self.beginResetModel() self.mMapDocument = mapDocument self.mMap = None self.mObjectGroups.clear() self.mGroups.clear() self.mGroups.clear() self.mObjects.clear() self.mObjects.clear() if (self.mMapDocument): self.mMap = self.mMapDocument.map() self.mMapDocument.layerAdded.connect(self.layerAdded) self.mMapDocument.layerChanged.connect(self.layerChanged) self.mMapDocument.layerAboutToBeRemoved.connect(self.layerAboutToBeRemoved) for og in self.mMap.objectGroups(): if GROUPS_IN_DISPLAY_ORDER: self.mObjectGroups.prepend(og) else: self.mObjectGroups.append(og) self.mGroups.insert(og, ObjectOrGroup(og)) for o in og.objects(): self.mObjects.insert(o, ObjectOrGroup(o)) self.endResetModel() def insertObject(self, og, index, o): if (index >= 0): _x = index else: _x = og.objectCount() row = _x self.beginInsertRows(self.index(og), row, row) og.insertObject(row, o) self.mObjects.insert(o, ObjectOrGroup(o)) self.endInsertRows() self.objectsAdded.emit(QList([o])) def removeObject(self, og, o): objects = QList() objects.append(o) row = og.objects().indexOf(o) self.beginRemoveRows(self.index(og), row, row) og.removeObjectAt(row) self.mObjects.remove(o) self.endRemoveRows() self.objectsRemoved.emit(objects) return row def moveObjects(self, og, _from, to, count): parent = self.index(og) if (not self.beginMoveRows(parent, _from, _from + count - 1, parent, to)): return og.moveObjects(_from, to, count) self.endMoveRows() # ObjectGroup color changed # FIXME: layerChanged should let the scene know that objects need redrawing def emitObjectsChanged(self, objects): if objects.isEmpty(): return self.objectsChanged.emit(objects) def setObjectName(self, o, name): if o.name() == name: return o.setName(name) index = self.index(o) self.dataChanged.emit(index, index) self.objectsChanged.emit(QList([o])) def setObjectType(self, o, type): if o.type() == type: return o.setType(type) index = self.index(o, 1) self.dataChanged.emit(index, index) self.objectsChanged.emit(QList([o])) def setObjectPolygon(self, o, polygon): if o.polygon() == polygon: return o.setPolygon(polygon) self.objectsChanged.emit(QList([o])) def setObjectPosition(self, o, pos): if o.position() == pos: return o.setPosition(pos) self.objectsChanged.emit(QList([o])) def setObjectSize(self, o, size): if o.size() == size: return o.setSize(size) self.objectsChanged.emit(QList([o])) def setObjectRotation(self, o, rotation): if o.rotation() == rotation: return o.setRotation(rotation) self.objectsChanged.emit(QList([o])) def setObjectVisible(self, o, visible): if o.isVisible() == visible: return o.setVisible(visible) index = self.index(o) self.dataChanged.emit(index, index) self.objectsChanged.emit(QList([o])) def layerAdded(self, index): layer = self.mMap.layerAt(index) og = layer.asObjectGroup() if og: if (not self.mGroups.contains(og)): prev = None for index in range(index - 1, -1, -1): prev = self.mMap.layerAt(index).asObjectGroup() if prev: break if GROUPS_IN_DISPLAY_ORDER: if prev: _x = self.mObjectGroups.indexOf(prev) else: _x = self.mObjectGroups.count() index = _x else: if prev: index = self.mObjectGroups.indexOf(prev) + 1 else: index = 0 self.mObjectGroups.insert(index, og) row = self.mObjectGroups.indexOf(og) self.beginInsertRows(QModelIndex(), row, row) self.mGroups.insert(og, ObjectOrGroup(og)) for o in og.objects(): if (not self.mObjects.contains(o)): self.mObjects.insert(o, ObjectOrGroup(o)) self.endInsertRows() def layerChanged(self, index): layer = self.mMap.layerAt(index) og = layer.asObjectGroup() if og: index = self.index(og) self.dataChanged.emit(index, index) def layerAboutToBeRemoved(self, index): layer = self.mMap.layerAt(index) og = layer.asObjectGroup() if og: row = self.mObjectGroups.indexOf(og) self.beginRemoveRows(QModelIndex(), row, row) self.mObjectGroups.removeAt(row) self.mGroups.remove(og) for o in og.objects(): self.mObjects.remove(og) self.endRemoveRows()
class QtCursorDatabase(): def __init__(self): self.m_cursorNames = QList() self.m_cursorIcons = QMap() self.m_valueToCursorShape = QMap() self.m_cursorShapeToValue = QMap() self.appendCursor(Qt.ArrowCursor, QCoreApplication.translate("QtCursorDatabase", "Arrow"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-arrow.png")) self.appendCursor(Qt.UpArrowCursor, QCoreApplication.translate("QtCursorDatabase", "Up Arrow"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-uparrow.png")) self.appendCursor(Qt.CrossCursor, QCoreApplication.translate("QtCursorDatabase", "Cross"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-cross.png")) self.appendCursor(Qt.WaitCursor, QCoreApplication.translate("QtCursorDatabase", "Wait"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-wait.png")) self.appendCursor(Qt.IBeamCursor, QCoreApplication.translate("QtCursorDatabase", "IBeam"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-ibeam.png")) self.appendCursor(Qt.SizeVerCursor, QCoreApplication.translate("QtCursorDatabase", "Size Vertical"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizev.png")) self.appendCursor(Qt.SizeHorCursor, QCoreApplication.translate("QtCursorDatabase", "Size Horizontal"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeh.png")) self.appendCursor(Qt.SizeFDiagCursor, QCoreApplication.translate("QtCursorDatabase", "Size Backslash"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizef.png")) self.appendCursor(Qt.SizeBDiagCursor, QCoreApplication.translate("QtCursorDatabase", "Size Slash"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeb.png")) self.appendCursor(Qt.SizeAllCursor, QCoreApplication.translate("QtCursorDatabase", "Size All"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeall.png")) self.appendCursor(Qt.BlankCursor, QCoreApplication.translate("QtCursorDatabase", "Blank"), QIcon()) self.appendCursor(Qt.SplitVCursor, QCoreApplication.translate("QtCursorDatabase", "Split Vertical"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-vsplit.png")) self.appendCursor(Qt.SplitHCursor, QCoreApplication.translate("QtCursorDatabase", "Split Horizontal"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-hsplit.png")) self.appendCursor(Qt.PointingHandCursor, QCoreApplication.translate("QtCursorDatabase", "Pointing Hand"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-hand.png")) self.appendCursor(Qt.ForbiddenCursor, QCoreApplication.translate("QtCursorDatabase", "Forbidden"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-forbidden.png")) self.appendCursor(Qt.OpenHandCursor, QCoreApplication.translate("QtCursorDatabase", "Open Hand"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-openhand.png")) self.appendCursor(Qt.ClosedHandCursor, QCoreApplication.translate("QtCursorDatabase", "Closed Hand"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-closedhand.png")) self.appendCursor(Qt.WhatsThisCursor, QCoreApplication.translate("QtCursorDatabase", "What's This"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-whatsthis.png")) self.appendCursor(Qt.BusyCursor, QCoreApplication.translate("QtCursorDatabase", "Busy"), QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-busy.png")) def clear(self): self.m_cursorNames.clear() self.m_cursorIcons.clear() self.m_valueToCursorShape.clear() self.m_cursorShapeToValue.clear() def appendCursor(self,shape, name, icon): if self.m_cursorShapeToValue.get(shape): return value = len(self.m_cursorNames) self.m_cursorNames.append(name) self.m_cursorIcons[value] = icon self.m_valueToCursorShape[value] = shape self.m_cursorShapeToValue[shape] = value def cursorShapeNames(self): return self.m_cursorNames def cursorShapeIcons(self): return self.m_cursorIcons def cursorToShapeName(self,cursor): val = self.cursorToValue(cursor) if val >= 0: return self.m_cursorNames[val] return '' def cursorToShapeIcon(self,cursor): val = self.cursorToValue(cursor) return self.m_cursorIcons[val] def cursorToValue(self,cursor): shape = cursor.shape() return self.m_cursorShapeToValue.get(shape, -1) def valueToCursor(self,value): if value in self.m_valueToCursorShape: return QCursor(self.m_valueToCursorShape[value]) return QCursor()
class MapScene(QGraphicsScene): selectedObjectItemsChanged = pyqtSignal() ## # Constructor. ## def __init__(self, parent): super().__init__(parent) self.mMapDocument = None self.mSelectedTool = None self.mActiveTool = None self.mObjectSelectionItem = None self.mUnderMouse = False self.mCurrentModifiers = Qt.NoModifier, self.mDarkRectangle = QGraphicsRectItem() self.mDefaultBackgroundColor = Qt.darkGray self.mLayerItems = QVector() self.mObjectItems = QMap() self.mObjectLineWidth = 0.0 self.mSelectedObjectItems = QSet() self.mLastMousePos = QPointF() self.mShowTileObjectOutlines = False self.mHighlightCurrentLayer = False self.mGridVisible = False self.setBackgroundBrush(self.mDefaultBackgroundColor) tilesetManager = TilesetManager.instance() tilesetManager.tilesetChanged.connect(self.tilesetChanged) tilesetManager.repaintTileset.connect(self.tilesetChanged) prefs = preferences.Preferences.instance() prefs.showGridChanged.connect(self.setGridVisible) prefs.showTileObjectOutlinesChanged.connect(self.setShowTileObjectOutlines) prefs.objectTypesChanged.connect(self.syncAllObjectItems) prefs.highlightCurrentLayerChanged.connect(self.setHighlightCurrentLayer) prefs.gridColorChanged.connect(self.update) prefs.objectLineWidthChanged.connect(self.setObjectLineWidth) self.mDarkRectangle.setPen(QPen(Qt.NoPen)) self.mDarkRectangle.setBrush(Qt.black) self.mDarkRectangle.setOpacity(darkeningFactor) self.addItem(self.mDarkRectangle) self.mGridVisible = prefs.showGrid() self.mObjectLineWidth = prefs.objectLineWidth() self.mShowTileObjectOutlines = prefs.showTileObjectOutlines() self.mHighlightCurrentLayer = prefs.highlightCurrentLayer() # Install an event filter so that we can get key events on behalf of the # active tool without having to have the current focus. QCoreApplication.instance().installEventFilter(self) ## # Destructor. ## def __del__(self): if QCoreApplication.instance(): QCoreApplication.instance().removeEventFilter(self) ## # Returns the map document this scene is displaying. ## def mapDocument(self): return self.mMapDocument ## # Sets the map this scene displays. ## def setMapDocument(self, mapDocument): if (self.mMapDocument): self.mMapDocument.disconnect() if (not self.mSelectedObjectItems.isEmpty()): self.mSelectedObjectItems.clear() self.selectedObjectItemsChanged.emit() self.mMapDocument = mapDocument if (self.mMapDocument): renderer = self.mMapDocument.renderer() renderer.setObjectLineWidth(self.mObjectLineWidth) renderer.setFlag(RenderFlag.ShowTileObjectOutlines, self.mShowTileObjectOutlines) self.mMapDocument.mapChanged.connect(self.mapChanged) self.mMapDocument.regionChanged.connect(self.repaintRegion) self.mMapDocument.tileLayerDrawMarginsChanged.connect(self.tileLayerDrawMarginsChanged) self.mMapDocument.layerAdded.connect(self.layerAdded) self.mMapDocument.layerRemoved.connect(self.layerRemoved) self.mMapDocument.layerChanged.connect(self.layerChanged) self.mMapDocument.objectGroupChanged.connect(self.objectGroupChanged) self.mMapDocument.imageLayerChanged.connect(self.imageLayerChanged) self.mMapDocument.currentLayerIndexChanged.connect(self.currentLayerIndexChanged) self.mMapDocument.tilesetTileOffsetChanged.connect(self.tilesetTileOffsetChanged) self.mMapDocument.objectsInserted.connect(self.objectsInserted) self.mMapDocument.objectsRemoved.connect(self.objectsRemoved) self.mMapDocument.objectsChanged.connect(self.objectsChanged) self.mMapDocument.objectsIndexChanged.connect(self.objectsIndexChanged) self.mMapDocument.selectedObjectsChanged.connect(self.updateSelectedObjectItems) self.refreshScene() ## # Returns whether the tile grid is visible. ## def isGridVisible(self): return self.mGridVisible ## # Returns the set of selected map object items. ## def selectedObjectItems(self): return QSet(self.mSelectedObjectItems) ## # Sets the set of selected map object items. This translates to a call to # MapDocument.setSelectedObjects. ## def setSelectedObjectItems(self, items): # Inform the map document about the newly selected objects selectedObjects = QList() #selectedObjects.reserve(items.size()) for item in items: selectedObjects.append(item.mapObject()) self.mMapDocument.setSelectedObjects(selectedObjects) ## # Returns the MapObjectItem associated with the given \a mapObject. ## def itemForObject(self, object): return self.mObjectItems[object] ## # Enables the selected tool at this map scene. # Therefore it tells that tool, that this is the active map scene. ## def enableSelectedTool(self): if (not self.mSelectedTool or not self.mMapDocument): return self.mActiveTool = self.mSelectedTool self.mActiveTool.activate(self) self.mCurrentModifiers = QApplication.keyboardModifiers() if (self.mCurrentModifiers != Qt.NoModifier): self.mActiveTool.modifiersChanged(self.mCurrentModifiers) if (self.mUnderMouse): self.mActiveTool.mouseEntered() self.mActiveTool.mouseMoved(self.mLastMousePos, Qt.KeyboardModifiers()) def disableSelectedTool(self): if (not self.mActiveTool): return if (self.mUnderMouse): self.mActiveTool.mouseLeft() self.mActiveTool.deactivate(self) self.mActiveTool = None ## # Sets the currently selected tool. ## def setSelectedTool(self, tool): self.mSelectedTool = tool ## # QGraphicsScene.drawForeground override that draws the tile grid. ## def drawForeground(self, painter, rect): if (not self.mMapDocument or not self.mGridVisible): return offset = QPointF() # Take into account the offset of the current layer layer = self.mMapDocument.currentLayer() if layer: offset = layer.offset() painter.translate(offset) prefs = preferences.Preferences.instance() self.mMapDocument.renderer().drawGrid(painter, rect.translated(-offset), prefs.gridColor()) ## # Override for handling enter and leave events. ## def event(self, event): x = event.type() if x==QEvent.Enter: self.mUnderMouse = True if (self.mActiveTool): self.mActiveTool.mouseEntered() elif x==QEvent.Leave: self.mUnderMouse = False if (self.mActiveTool): self.mActiveTool.mouseLeft() else: pass return super().event(event) def keyPressEvent(self, event): if (self.mActiveTool): self.mActiveTool.keyPressed(event) if (not (self.mActiveTool and event.isAccepted())): super().keyPressEvent(event) def mouseMoveEvent(self, mouseEvent): self.mLastMousePos = mouseEvent.scenePos() if (not self.mMapDocument): return super().mouseMoveEvent(mouseEvent) if (mouseEvent.isAccepted()): return if (self.mActiveTool): self.mActiveTool.mouseMoved(mouseEvent.scenePos(), mouseEvent.modifiers()) mouseEvent.accept() def mousePressEvent(self, mouseEvent): super().mousePressEvent(mouseEvent) if (mouseEvent.isAccepted()): return if (self.mActiveTool): mouseEvent.accept() self.mActiveTool.mousePressed(mouseEvent) def mouseReleaseEvent(self, mouseEvent): super().mouseReleaseEvent(mouseEvent) if (mouseEvent.isAccepted()): return if (self.mActiveTool): mouseEvent.accept() self.mActiveTool.mouseReleased(mouseEvent) ## # Override to ignore drag enter events. ## def dragEnterEvent(self, event): event.ignore() ## # Sets whether the tile grid is visible. ## def setGridVisible(self, visible): if (self.mGridVisible == visible): return self.mGridVisible = visible self.update() def setObjectLineWidth(self, lineWidth): if (self.mObjectLineWidth == lineWidth): return self.mObjectLineWidth = lineWidth if (self.mMapDocument): self.mMapDocument.renderer().setObjectLineWidth(lineWidth) # Changing the line width can change the size of the object items if (not self.mObjectItems.isEmpty()): for item in self.mObjectItems: item[1].syncWithMapObject() self.update() def setShowTileObjectOutlines(self, enabled): if (self.mShowTileObjectOutlines == enabled): return self.mShowTileObjectOutlines = enabled if (self.mMapDocument): self.mMapDocument.renderer().setFlag(RenderFlag.ShowTileObjectOutlines, enabled) if (not self.mObjectItems.isEmpty()): self.update() ## # Sets whether the current layer should be highlighted. ## def setHighlightCurrentLayer(self, highlightCurrentLayer): if (self.mHighlightCurrentLayer == highlightCurrentLayer): return self.mHighlightCurrentLayer = highlightCurrentLayer self.updateCurrentLayerHighlight() ## # Refreshes the map scene. ## def refreshScene(self): self.mLayerItems.clear() self.mObjectItems.clear() self.removeItem(self.mDarkRectangle) self.clear() self.addItem(self.mDarkRectangle) if (not self.mMapDocument): self.setSceneRect(QRectF()) return self.updateSceneRect() map = self.mMapDocument.map() self.mLayerItems.resize(map.layerCount()) if (map.backgroundColor().isValid()): self.setBackgroundBrush(map.backgroundColor()) else: self.setBackgroundBrush(self.mDefaultBackgroundColor) layerIndex = 0 for layer in map.layers(): layerItem = self.createLayerItem(layer) layerItem.setZValue(layerIndex) self.addItem(layerItem) self.mLayerItems[layerIndex] = layerItem layerIndex += 1 tileSelectionItem = TileSelectionItem(self.mMapDocument) tileSelectionItem.setZValue(10000 - 2) self.addItem(tileSelectionItem) self.mObjectSelectionItem = ObjectSelectionItem(self.mMapDocument) self.mObjectSelectionItem.setZValue(10000 - 1) self.addItem(self.mObjectSelectionItem) self.updateCurrentLayerHighlight() ## # Repaints the specified region. The region is in tile coordinates. ## def repaintRegion(self, region, layer): renderer = self.mMapDocument.renderer() margins = self.mMapDocument.map().drawMargins() for r in region.rects(): boundingRect = QRectF(renderer.boundingRect(r)) self.update(QRectF(renderer.boundingRect(r).adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom()))) boundingRect.translate(layer.offset()) self.update(boundingRect) def currentLayerIndexChanged(self): self.updateCurrentLayerHighlight() # New layer may have a different offset, affecting the grid if self.mGridVisible: self.update() ## # Adapts the scene, layers and objects to new map size, orientation or # background color. ## def mapChanged(self): self.updateSceneRect() for item in self.mLayerItems: tli = item if type(tli) == TileLayerItem: tli.syncWithTileLayer() for item in self.mObjectItems.values(): item.syncWithMapObject() map = self.mMapDocument.map() if (map.backgroundColor().isValid()): self.setBackgroundBrush(map.backgroundColor()) else: self.setBackgroundBrush(self.mDefaultBackgroundColor) def tilesetChanged(self, tileset): if (not self.mMapDocument): return if (contains(self.mMapDocument.map().tilesets(), tileset)): self.update() def tileLayerDrawMarginsChanged(self, tileLayer): index = self.mMapDocument.map().layers().indexOf(tileLayer) item = self.mLayerItems.at(index) item.syncWithTileLayer() def layerAdded(self, index): layer = self.mMapDocument.map().layerAt(index) layerItem = self.createLayerItem(layer) self.addItem(layerItem) self.mLayerItems.insert(index, layerItem) z = 0 for item in self.mLayerItems: item.setZValue(z) z += 1 def layerRemoved(self, index): self.mLayerItems.remove(index) ## # A layer has changed. This can mean that the layer visibility, opacity or # offset changed. ## def layerChanged(self, index): layer = self.mMapDocument.map().layerAt(index) layerItem = self.mLayerItems.at(index) layerItem.setVisible(layer.isVisible()) multiplier = 1 if (self.mHighlightCurrentLayer and self.mMapDocument.currentLayerIndex() < index): multiplier = opacityFactor layerItem.setOpacity(layer.opacity() * multiplier) layerItem.setPos(layer.offset()) # Layer offset may have changed, affecting the scene rect and grid self.updateSceneRect() if self.mGridVisible: self.update() ## # When an object group has changed it may mean its color or drawing order # changed, which affects all its objects. ## def objectGroupChanged(self, objectGroup): self.objectsChanged(objectGroup.objects()) self.objectsIndexChanged(objectGroup, 0, objectGroup.objectCount() - 1) ## # When an image layer has changed, it may change size and it may look # differently. ## def imageLayerChanged(self, imageLayer): index = self.mMapDocument.map().layers().indexOf(imageLayer) item = self.mLayerItems.at(index) item.syncWithImageLayer() item.update() ## # When the tile offset of a tileset has changed, it can affect the bounding # rect of all tile layers and tile objects. It also requires a full repaint. ## def tilesetTileOffsetChanged(self, tileset): self.update() for item in self.mLayerItems: tli = item if type(tli) == TileLayerItem: tli.syncWithTileLayer() for item in self.mObjectItems: cell = item.mapObject().cell() if (not cell.isEmpty() and cell.tile.tileset() == tileset): item.syncWithMapObject() ## # Inserts map object items for the given objects. ## def objectsInserted(self, objectGroup, first, last): ogItem = None # Find the object group item for the object group for item in self.mLayerItems: ogi = item if type(ogi)==ObjectGroupItem: if (ogi.objectGroup() == objectGroup): ogItem = ogi break drawOrder = objectGroup.drawOrder() for i in range(first, last+1): object = objectGroup.objectAt(i) item = MapObjectItem(object, self.mMapDocument, ogItem) if (drawOrder == ObjectGroup.DrawOrder.TopDownOrder): item.setZValue(item.y()) else: item.setZValue(i) self.mObjectItems.insert(object, item) ## # Removes the map object items related to the given objects. ## def objectsRemoved(self, objects): for o in objects: i = self.mObjectItems.find(o) self.mSelectedObjectItems.remove(i) # python would not force delete QGraphicsItem self.removeItem(i) self.mObjectItems.erase(o) ## # Updates the map object items related to the given objects. ## def objectsChanged(self, objects): for object in objects: item = self.itemForObject(object) item.syncWithMapObject() ## # Updates the Z value of the objects when appropriate. ## def objectsIndexChanged(self, objectGroup, first, last): if (objectGroup.drawOrder() != ObjectGroup.DrawOrder.IndexOrder): return for i in range(first, last+1): item = self.itemForObject(objectGroup.objectAt(i)) item.setZValue(i) def updateSelectedObjectItems(self): objects = self.mMapDocument.selectedObjects() items = QSet() for object in objects: item = self.itemForObject(object) if item: items.insert(item) self.mSelectedObjectItems = items self.selectedObjectItemsChanged.emit() def syncAllObjectItems(self): for item in self.mObjectItems: item.syncWithMapObject() def createLayerItem(self, layer): layerItem = None tl = layer.asTileLayer() if tl: layerItem = TileLayerItem(tl, self.mMapDocument) else: og = layer.asObjectGroup() if og: drawOrder = og.drawOrder() ogItem = ObjectGroupItem(og) objectIndex = 0 for object in og.objects(): item = MapObjectItem(object, self.mMapDocument, ogItem) if (drawOrder == ObjectGroup.DrawOrder.TopDownOrder): item.setZValue(item.y()) else: item.setZValue(objectIndex) self.mObjectItems.insert(object, item) objectIndex += 1 layerItem = ogItem else: il = layer.asImageLayer() if il: layerItem = ImageLayerItem(il, self.mMapDocument) layerItem.setVisible(layer.isVisible()) return layerItem def updateSceneRect(self): mapSize = self.mMapDocument.renderer().mapSize() sceneRect = QRectF(0, 0, mapSize.width(), mapSize.height()) margins = self.mMapDocument.map().computeLayerOffsetMargins() sceneRect.adjust(-margins.left(), -margins.top(), margins.right(), margins.bottom()) self.setSceneRect(sceneRect) self.mDarkRectangle.setRect(sceneRect) def updateCurrentLayerHighlight(self): if (not self.mMapDocument): return currentLayerIndex = self.mMapDocument.currentLayerIndex() if (not self.mHighlightCurrentLayer or currentLayerIndex == -1): self.mDarkRectangle.setVisible(False) # Restore opacity for all layers for i in range(self.mLayerItems.size()): layer = self.mMapDocument.map().layerAt(i) self.mLayerItems.at(i).setOpacity(layer.opacity()) return # Darken layers below the current layer self.mDarkRectangle.setZValue(currentLayerIndex - 0.5) self.mDarkRectangle.setVisible(True) # Set layers above the current layer to half opacity for i in range(1, self.mLayerItems.size()): layer = self.mMapDocument.map().layerAt(i) if currentLayerIndex < i: _x = opacityFactor else: _x = 1 multiplier = _x self.mLayerItems.at(i).setOpacity(layer.opacity() * multiplier) def eventFilter(self, object, event): x = event.type() if x==QEvent.KeyPress or x==QEvent.KeyRelease: keyEvent = event newModifiers = keyEvent.modifiers() if (self.mActiveTool and newModifiers != self.mCurrentModifiers): self.mActiveTool.modifiersChanged(newModifiers) self.mCurrentModifiers = newModifiers else: pass return False
class TileStampManager(QObject): setStamp = pyqtSignal(TileStamp) def __init__(self, toolManager, parent = None): super().__init__(parent) self.mStampsByName = QMap() self.mQuickStamps = QVector() for i in range(TileStampManager.quickStampKeys().__len__()): self.mQuickStamps.append(0) self.mTileStampModel = TileStampModel(self) self.mToolManager = toolManager prefs = preferences.Preferences.instance() prefs.stampsDirectoryChanged.connect(self.stampsDirectoryChanged) self.mTileStampModel.stampAdded.connect(self.stampAdded) self.mTileStampModel.stampRenamed.connect(self.stampRenamed) self.mTileStampModel.stampChanged.connect(self.saveStamp) self.mTileStampModel.stampRemoved.connect(self.deleteStamp) self.loadStamps() def __del__(self): # needs to be over here where the TileStamp type is complete pass ## # Returns the keys used for quickly accessible tile stamps. # Note: To store a tile layer <Ctrl> is added. The given keys will work # for recalling the stored values. ## def quickStampKeys(): keys=[Qt.Key_1, Qt.Key_2, Qt.Key_3, Qt.Key_4, Qt.Key_5, Qt.Key_6, Qt.Key_7, Qt.Key_8, Qt.Key_9] return keys def tileStampModel(self): return self.mTileStampModel def createStamp(self): stamp = self.tampFromContext(self.mToolManager.selectedTool()) if (not stamp.isEmpty()): self.mTileStampModel.addStamp(stamp) return stamp def addVariation(self, targetStamp): stamp = stampFromContext(self.mToolManager.selectedTool()) if (stamp.isEmpty()): return if (stamp == targetStamp): # avoid easy mistake of adding duplicates return for variation in stamp.variations(): self.mTileStampModel.addVariation(targetStamp, variation) def selectQuickStamp(self, index): stamp = self.mQuickStamps.at(index) if (not stamp.isEmpty()): self.setStamp.emit(stamp) def createQuickStamp(self, index): stamp = stampFromContext(self.mToolManager.selectedTool()) if (stamp.isEmpty()): return self.setQuickStamp(index, stamp) def extendQuickStamp(self, index): quickStamp = self.mQuickStamps[index] if (quickStamp.isEmpty()): self.createQuickStamp(index) else: self.addVariation(quickStamp) def stampsDirectoryChanged(self): # erase current stamps self.mQuickStamps.fill(TileStamp()) self.mStampsByName.clear() self.mTileStampModel.clear() self.loadStamps() def eraseQuickStamp(self, index): stamp = self.mQuickStamps.at(index) if (not stamp.isEmpty()): self.mQuickStamps[index] = TileStamp() if (not self.mQuickStamps.contains(stamp)): self.mTileStampModel.removeStamp(stamp) def setQuickStamp(self, index, stamp): stamp.setQuickStampIndex(index) # make sure existing quickstamp is removed from stamp model self.eraseQuickStamp(index) self.mTileStampModel.addStamp(stamp) self.mQuickStamps[index] = stamp def loadStamps(self): prefs = preferences.Preferences.instance() stampsDirectory = prefs.stampsDirectory() stampsDir = QDir(stampsDirectory) iterator = QDirIterator(stampsDirectory, ["*.stamp"], QDir.Files | QDir.Readable) while (iterator.hasNext()): stampFileName = iterator.next() stampFile = QFile(stampFileName) if (not stampFile.open(QIODevice.ReadOnly)): continue data = stampFile.readAll() document = QJsonDocument.fromBinaryData(data) if (document.isNull()): # document not valid binary data, maybe it's an JSON text file error = QJsonParseError() document = QJsonDocument.fromJson(data, error) if (error.error != QJsonParseError.NoError): qDebug("Failed to parse stamp file:" + error.errorString()) continue stamp = TileStamp.fromJson(document.object(), stampsDir) if (stamp.isEmpty()): continue stamp.setFileName(iterator.fileInfo().fileName()) self.mTileStampModel.addStamp(stamp) index = stamp.quickStampIndex() if (index >= 0 and index < self.mQuickStamps.size()): self.mQuickStamps[index] = stamp def stampAdded(self, stamp): if (stamp.name().isEmpty() or self.mStampsByName.contains(stamp.name())): # pick the first available stamp name name = QString() index = self.mTileStampModel.stamps().size() while(self.mStampsByName.contains(name)): name = str(index) index += 1 stamp.setName(name) self.mStampsByName.insert(stamp.name(), stamp) if (stamp.fileName().isEmpty()): stamp.setFileName(findStampFileName(stamp.name())) self.saveStamp(stamp) def stampRenamed(self, stamp): existingName = self.mStampsByName.key(stamp) self.mStampsByName.remove(existingName) self.mStampsByName.insert(stamp.name(), stamp) existingFileName = stamp.fileName() newFileName = findStampFileName(stamp.name(), existingFileName) if (existingFileName != newFileName): if (QFile.rename(stampFilePath(existingFileName), stampFilePath(newFileName))): stamp.setFileName(newFileName) def saveStamp(self, stamp): # make sure we have a stamps directory prefs = preferences.Preferences.instance() stampsDirectory = prefs.stampsDirectory() stampsDir = QDir(stampsDirectory) if (not stampsDir.exists() and not stampsDir.mkpath(".")): qDebug("Failed to create stamps directory" + stampsDirectory) return filePath = stampsDir.filePath(stamp.fileName()) file = QSaveFile(filePath) if (not file.open(QIODevice.WriteOnly)): qDebug("Failed to open stamp file for writing" + filePath) return stampJson = stamp.toJson(QFileInfo(filePath).dir()) file.write(QJsonDocument(stampJson).toJson(QJsonDocument.Compact)) if (not file.commit()): qDebug() << "Failed to write stamp" << filePath def deleteStamp(self, stamp): self.mStampsByName.remove(stamp.name()) QFile.remove(stampFilePath(stamp.fileName()))
class GidMapper(): ## # Default constructor. Use \l insert to initialize the gid mapper # incrementally. ## def __init__(self, *args): self.mInvalidTile = None self.mTilesetColumnCounts = QMap() self.mFirstGidToTileset = QMap() if len(args)==1: ## # Constructor that initializes the gid mapper using the given \a tilesets. ## firstGid = 1 tilesets = args[0] for tileset in tilesets: self.insert(firstGid, tileset) firstGid += tileset.tileCount() ## # Insert the given \a tileset with \a firstGid as its first global ID. ## def insert(self, firstGid, tileset): self.mFirstGidToTileset.insert(firstGid, tileset) ## # Clears the gid mapper, so that it can be reused. ## def clear(self): self.mFirstGidToTileset.clear() ## # Returns True when no tilesets are known to this gid mapper. ## def isEmpty(self): return self.mFirstGidToTileset.isEmpty() ## # Returns the GID of the invalid tile in case decodeLayerData() returns # the InvalidTile error. ## def invalidTile(self): return self.mInvalidTile ## # Returns the cell data matched by the given \a gid. The \a ok parameter # indicates whether an error occurred. ## def gidToCell(self, gid): result = Cell() # Read out the flags result.flippedHorizontally = (gid & FlippedHorizontallyFlag) result.flippedVertically = (gid & FlippedVerticallyFlag) result.flippedAntiDiagonally = (gid & FlippedAntiDiagonallyFlag) # Clear the flags gid &= ~(FlippedHorizontallyFlag | FlippedVerticallyFlag | FlippedAntiDiagonallyFlag) if (gid == 0): ok = True elif (self.isEmpty()): ok = False else: # Find the tileset containing this tile index = self.mFirstGidToTileset.upperBound(gid) if index==0: ok = False else: item = self.mFirstGidToTileset.itemByIndex(index-1) # Navigate one tileset back since upper bound finds the next tileId = gid - item[0] tileset = item[1] columnCount = self.mTilesetColumnCounts.value(tileset, 0) if (columnCount > 0 and columnCount != tileset.columnCount()): # Correct tile index for changes in image width row = int(tileId / columnCount) column = int(tileId % columnCount) tileId = row * tileset.columnCount() + column result.tile = tileset.tileAt(tileId) ok = True return result, ok ## # Returns the global tile ID for the given \a cell. Returns 0 when the # cell is empty or when its tileset isn't known. ## def cellToGid(self, cell): if (cell.isEmpty()): return 0 tileset = cell.tile.tileset() # Find the first GID for the tileset for item in self.mFirstGidToTileset: if item[1] == tileset: gid = item[0] + cell.tile.id() if (cell.flippedHorizontally): gid |= FlippedHorizontallyFlag if (cell.flippedVertically): gid |= FlippedVerticallyFlag if (cell.flippedAntiDiagonally): gid |= FlippedAntiDiagonallyFlag return gid return 0 ## # This sets the original tileset width. In case the image size has # changed, the tile indexes will be adjusted automatically when using # gidToCell(). ## def setTilesetWidth(self, tileset, width): if (tileset.tileWidth() == 0): return self.mTilesetColumnCounts[tileset] = tileset.columnCountForWidth(width) ## # Encodes the tile layer data of the given \a tileLayer in the given # \a format. This function should only be used for base64 encoding, with or # without compression. ## def encodeLayerData(self, tileLayer, format): if format in [Map.LayerDataFormat.XML, Map.LayerDataFormat.CSV]: raise tileData = QByteArray() for y in range(tileLayer.height()): for x in range(tileLayer.width()): gid = self.cellToGid(tileLayer.cellAt(x, y)) tileData.append(bytes([gid&0xff])) tileData.append(bytes([(gid >> 8)&0xff])) tileData.append(bytes([(gid >> 16)&0xff])) tileData.append(bytes([(gid >> 24)&0xff])) if len(tileData)%4 != 0: raise if (format == Map.LayerDataFormat.Base64Gzip): tileData = compress(tileData, CompressionMethod.Gzip) elif (format == Map.LayerDataFormat.Base64Zlib): tileData = compress(tileData, CompressionMethod.Zlib) return tileData.toBase64() def decodeLayerData(self, tileLayer, layerData, format): if format in [Map.LayerDataFormat.XML, Map.LayerDataFormat.CSV]: raise _layerData = QByteArray() _layerData.append(layerData) decodedData = QByteArray.fromBase64(_layerData) size = (tileLayer.width() * tileLayer.height()) * 4 if (format == Map.LayerDataFormat.Base64Gzip or format == Map.LayerDataFormat.Base64Zlib): decodedData, size = decompress(decodedData, size) if (size != decodedData.length()): return DecodeError.CorruptLayerData data = decodedData.data() x = 0 y = 0 for i in range(0, size - 3, 4): gid = data[i] | data[i + 1] << 8 | data[i + 2] << 16 | data[i + 3] << 24 result, ok = self.gidToCell(gid) if (not ok): self.mInvalidTile = gid if self.isEmpty(): return DecodeError.TileButNoTilesets else: return DecodeError.InvalidTile tileLayer.setCell(x, y, result) x += 1 if (x == tileLayer.width()): x = 0 y += 1 return DecodeError.NoError
class MapScene(QGraphicsScene): selectedObjectItemsChanged = pyqtSignal() ## # Constructor. ## def __init__(self, parent): super().__init__(parent) self.mMapDocument = None self.mSelectedTool = None self.mActiveTool = None self.mObjectSelectionItem = None self.mUnderMouse = False self.mCurrentModifiers = Qt.NoModifier, self.mDarkRectangle = QGraphicsRectItem() self.mDefaultBackgroundColor = Qt.darkGray self.mLayerItems = QVector() self.mObjectItems = QMap() self.mObjectLineWidth = 0.0 self.mSelectedObjectItems = QSet() self.mLastMousePos = QPointF() self.mShowTileObjectOutlines = False self.mHighlightCurrentLayer = False self.mGridVisible = False self.setBackgroundBrush(self.mDefaultBackgroundColor) tilesetManager = TilesetManager.instance() tilesetManager.tilesetChanged.connect(self.tilesetChanged) tilesetManager.repaintTileset.connect(self.tilesetChanged) prefs = preferences.Preferences.instance() prefs.showGridChanged.connect(self.setGridVisible) prefs.showTileObjectOutlinesChanged.connect( self.setShowTileObjectOutlines) prefs.objectTypesChanged.connect(self.syncAllObjectItems) prefs.highlightCurrentLayerChanged.connect( self.setHighlightCurrentLayer) prefs.gridColorChanged.connect(self.update) prefs.objectLineWidthChanged.connect(self.setObjectLineWidth) self.mDarkRectangle.setPen(QPen(Qt.NoPen)) self.mDarkRectangle.setBrush(Qt.black) self.mDarkRectangle.setOpacity(darkeningFactor) self.addItem(self.mDarkRectangle) self.mGridVisible = prefs.showGrid() self.mObjectLineWidth = prefs.objectLineWidth() self.mShowTileObjectOutlines = prefs.showTileObjectOutlines() self.mHighlightCurrentLayer = prefs.highlightCurrentLayer() # Install an event filter so that we can get key events on behalf of the # active tool without having to have the current focus. QCoreApplication.instance().installEventFilter(self) ## # Destructor. ## def __del__(self): if QCoreApplication.instance(): QCoreApplication.instance().removeEventFilter(self) ## # Returns the map document this scene is displaying. ## def mapDocument(self): return self.mMapDocument ## # Sets the map this scene displays. ## def setMapDocument(self, mapDocument): if (self.mMapDocument): self.mMapDocument.disconnect() if (not self.mSelectedObjectItems.isEmpty()): self.mSelectedObjectItems.clear() self.selectedObjectItemsChanged.emit() self.mMapDocument = mapDocument if (self.mMapDocument): renderer = self.mMapDocument.renderer() renderer.setObjectLineWidth(self.mObjectLineWidth) renderer.setFlag(RenderFlag.ShowTileObjectOutlines, self.mShowTileObjectOutlines) self.mMapDocument.mapChanged.connect(self.mapChanged) self.mMapDocument.regionChanged.connect(self.repaintRegion) self.mMapDocument.tileLayerDrawMarginsChanged.connect( self.tileLayerDrawMarginsChanged) self.mMapDocument.layerAdded.connect(self.layerAdded) self.mMapDocument.layerRemoved.connect(self.layerRemoved) self.mMapDocument.layerChanged.connect(self.layerChanged) self.mMapDocument.objectGroupChanged.connect( self.objectGroupChanged) self.mMapDocument.imageLayerChanged.connect(self.imageLayerChanged) self.mMapDocument.currentLayerIndexChanged.connect( self.currentLayerIndexChanged) self.mMapDocument.tilesetTileOffsetChanged.connect( self.tilesetTileOffsetChanged) self.mMapDocument.objectsInserted.connect(self.objectsInserted) self.mMapDocument.objectsRemoved.connect(self.objectsRemoved) self.mMapDocument.objectsChanged.connect(self.objectsChanged) self.mMapDocument.objectsIndexChanged.connect( self.objectsIndexChanged) self.mMapDocument.selectedObjectsChanged.connect( self.updateSelectedObjectItems) self.refreshScene() ## # Returns whether the tile grid is visible. ## def isGridVisible(self): return self.mGridVisible ## # Returns the set of selected map object items. ## def selectedObjectItems(self): return QSet(self.mSelectedObjectItems) ## # Sets the set of selected map object items. This translates to a call to # MapDocument.setSelectedObjects. ## def setSelectedObjectItems(self, items): # Inform the map document about the newly selected objects selectedObjects = QList() #selectedObjects.reserve(items.size()) for item in items: selectedObjects.append(item.mapObject()) self.mMapDocument.setSelectedObjects(selectedObjects) ## # Returns the MapObjectItem associated with the given \a mapObject. ## def itemForObject(self, object): return self.mObjectItems[object] ## # Enables the selected tool at this map scene. # Therefore it tells that tool, that this is the active map scene. ## def enableSelectedTool(self): if (not self.mSelectedTool or not self.mMapDocument): return self.mActiveTool = self.mSelectedTool self.mActiveTool.activate(self) self.mCurrentModifiers = QApplication.keyboardModifiers() if (self.mCurrentModifiers != Qt.NoModifier): self.mActiveTool.modifiersChanged(self.mCurrentModifiers) if (self.mUnderMouse): self.mActiveTool.mouseEntered() self.mActiveTool.mouseMoved(self.mLastMousePos, Qt.KeyboardModifiers()) def disableSelectedTool(self): if (not self.mActiveTool): return if (self.mUnderMouse): self.mActiveTool.mouseLeft() self.mActiveTool.deactivate(self) self.mActiveTool = None ## # Sets the currently selected tool. ## def setSelectedTool(self, tool): self.mSelectedTool = tool ## # QGraphicsScene.drawForeground override that draws the tile grid. ## def drawForeground(self, painter, rect): if (not self.mMapDocument or not self.mGridVisible): return offset = QPointF() # Take into account the offset of the current layer layer = self.mMapDocument.currentLayer() if layer: offset = layer.offset() painter.translate(offset) prefs = preferences.Preferences.instance() self.mMapDocument.renderer().drawGrid(painter, rect.translated(-offset), prefs.gridColor()) ## # Override for handling enter and leave events. ## def event(self, event): x = event.type() if x == QEvent.Enter: self.mUnderMouse = True if (self.mActiveTool): self.mActiveTool.mouseEntered() elif x == QEvent.Leave: self.mUnderMouse = False if (self.mActiveTool): self.mActiveTool.mouseLeft() else: pass return super().event(event) def keyPressEvent(self, event): if (self.mActiveTool): self.mActiveTool.keyPressed(event) if (not (self.mActiveTool and event.isAccepted())): super().keyPressEvent(event) def mouseMoveEvent(self, mouseEvent): self.mLastMousePos = mouseEvent.scenePos() if (not self.mMapDocument): return super().mouseMoveEvent(mouseEvent) if (mouseEvent.isAccepted()): return if (self.mActiveTool): self.mActiveTool.mouseMoved(mouseEvent.scenePos(), mouseEvent.modifiers()) mouseEvent.accept() def mousePressEvent(self, mouseEvent): super().mousePressEvent(mouseEvent) if (mouseEvent.isAccepted()): return if (self.mActiveTool): mouseEvent.accept() self.mActiveTool.mousePressed(mouseEvent) def mouseReleaseEvent(self, mouseEvent): super().mouseReleaseEvent(mouseEvent) if (mouseEvent.isAccepted()): return if (self.mActiveTool): mouseEvent.accept() self.mActiveTool.mouseReleased(mouseEvent) ## # Override to ignore drag enter events. ## def dragEnterEvent(self, event): event.ignore() ## # Sets whether the tile grid is visible. ## def setGridVisible(self, visible): if (self.mGridVisible == visible): return self.mGridVisible = visible self.update() def setObjectLineWidth(self, lineWidth): if (self.mObjectLineWidth == lineWidth): return self.mObjectLineWidth = lineWidth if (self.mMapDocument): self.mMapDocument.renderer().setObjectLineWidth(lineWidth) # Changing the line width can change the size of the object items if (not self.mObjectItems.isEmpty()): for item in self.mObjectItems: item[1].syncWithMapObject() self.update() def setShowTileObjectOutlines(self, enabled): if (self.mShowTileObjectOutlines == enabled): return self.mShowTileObjectOutlines = enabled if (self.mMapDocument): self.mMapDocument.renderer().setFlag( RenderFlag.ShowTileObjectOutlines, enabled) if (not self.mObjectItems.isEmpty()): self.update() ## # Sets whether the current layer should be highlighted. ## def setHighlightCurrentLayer(self, highlightCurrentLayer): if (self.mHighlightCurrentLayer == highlightCurrentLayer): return self.mHighlightCurrentLayer = highlightCurrentLayer self.updateCurrentLayerHighlight() ## # Refreshes the map scene. ## def refreshScene(self): self.mLayerItems.clear() self.mObjectItems.clear() self.removeItem(self.mDarkRectangle) self.clear() self.addItem(self.mDarkRectangle) if (not self.mMapDocument): self.setSceneRect(QRectF()) return self.updateSceneRect() map = self.mMapDocument.map() self.mLayerItems.resize(map.layerCount()) if (map.backgroundColor().isValid()): self.setBackgroundBrush(map.backgroundColor()) else: self.setBackgroundBrush(self.mDefaultBackgroundColor) layerIndex = 0 for layer in map.layers(): layerItem = self.createLayerItem(layer) layerItem.setZValue(layerIndex) self.addItem(layerItem) self.mLayerItems[layerIndex] = layerItem layerIndex += 1 tileSelectionItem = TileSelectionItem(self.mMapDocument) tileSelectionItem.setZValue(10000 - 2) self.addItem(tileSelectionItem) self.mObjectSelectionItem = ObjectSelectionItem(self.mMapDocument) self.mObjectSelectionItem.setZValue(10000 - 1) self.addItem(self.mObjectSelectionItem) self.updateCurrentLayerHighlight() ## # Repaints the specified region. The region is in tile coordinates. ## def repaintRegion(self, region, layer): renderer = self.mMapDocument.renderer() margins = self.mMapDocument.map().drawMargins() for r in region.rects(): boundingRect = QRectF(renderer.boundingRect(r)) self.update( QRectF( renderer.boundingRect(r).adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom()))) boundingRect.translate(layer.offset()) self.update(boundingRect) def currentLayerIndexChanged(self): self.updateCurrentLayerHighlight() # New layer may have a different offset, affecting the grid if self.mGridVisible: self.update() ## # Adapts the scene, layers and objects to new map size, orientation or # background color. ## def mapChanged(self): self.updateSceneRect() for item in self.mLayerItems: tli = item if type(tli) == TileLayerItem: tli.syncWithTileLayer() for item in self.mObjectItems.values(): item.syncWithMapObject() map = self.mMapDocument.map() if (map.backgroundColor().isValid()): self.setBackgroundBrush(map.backgroundColor()) else: self.setBackgroundBrush(self.mDefaultBackgroundColor) def tilesetChanged(self, tileset): if (not self.mMapDocument): return if (contains(self.mMapDocument.map().tilesets(), tileset)): self.update() def tileLayerDrawMarginsChanged(self, tileLayer): index = self.mMapDocument.map().layers().indexOf(tileLayer) item = self.mLayerItems.at(index) item.syncWithTileLayer() def layerAdded(self, index): layer = self.mMapDocument.map().layerAt(index) layerItem = self.createLayerItem(layer) self.addItem(layerItem) self.mLayerItems.insert(index, layerItem) z = 0 for item in self.mLayerItems: item.setZValue(z) z += 1 def layerRemoved(self, index): self.mLayerItems.remove(index) ## # A layer has changed. This can mean that the layer visibility, opacity or # offset changed. ## def layerChanged(self, index): layer = self.mMapDocument.map().layerAt(index) layerItem = self.mLayerItems.at(index) layerItem.setVisible(layer.isVisible()) multiplier = 1 if (self.mHighlightCurrentLayer and self.mMapDocument.currentLayerIndex() < index): multiplier = opacityFactor layerItem.setOpacity(layer.opacity() * multiplier) layerItem.setPos(layer.offset()) # Layer offset may have changed, affecting the scene rect and grid self.updateSceneRect() if self.mGridVisible: self.update() ## # When an object group has changed it may mean its color or drawing order # changed, which affects all its objects. ## def objectGroupChanged(self, objectGroup): self.objectsChanged(objectGroup.objects()) self.objectsIndexChanged(objectGroup, 0, objectGroup.objectCount() - 1) ## # When an image layer has changed, it may change size and it may look # differently. ## def imageLayerChanged(self, imageLayer): index = self.mMapDocument.map().layers().indexOf(imageLayer) item = self.mLayerItems.at(index) item.syncWithImageLayer() item.update() ## # When the tile offset of a tileset has changed, it can affect the bounding # rect of all tile layers and tile objects. It also requires a full repaint. ## def tilesetTileOffsetChanged(self, tileset): self.update() for item in self.mLayerItems: tli = item if type(tli) == TileLayerItem: tli.syncWithTileLayer() for item in self.mObjectItems: cell = item.mapObject().cell() if (not cell.isEmpty() and cell.tile.tileset() == tileset): item.syncWithMapObject() ## # Inserts map object items for the given objects. ## def objectsInserted(self, objectGroup, first, last): ogItem = None # Find the object group item for the object group for item in self.mLayerItems: ogi = item if type(ogi) == ObjectGroupItem: if (ogi.objectGroup() == objectGroup): ogItem = ogi break drawOrder = objectGroup.drawOrder() for i in range(first, last + 1): object = objectGroup.objectAt(i) item = MapObjectItem(object, self.mMapDocument, ogItem) if (drawOrder == ObjectGroup.DrawOrder.TopDownOrder): item.setZValue(item.y()) else: item.setZValue(i) self.mObjectItems.insert(object, item) ## # Removes the map object items related to the given objects. ## def objectsRemoved(self, objects): for o in objects: i = self.mObjectItems.find(o) self.mSelectedObjectItems.remove(i) # python would not force delete QGraphicsItem self.removeItem(i) self.mObjectItems.erase(o) ## # Updates the map object items related to the given objects. ## def objectsChanged(self, objects): for object in objects: item = self.itemForObject(object) item.syncWithMapObject() ## # Updates the Z value of the objects when appropriate. ## def objectsIndexChanged(self, objectGroup, first, last): if (objectGroup.drawOrder() != ObjectGroup.DrawOrder.IndexOrder): return for i in range(first, last + 1): item = self.itemForObject(objectGroup.objectAt(i)) item.setZValue(i) def updateSelectedObjectItems(self): objects = self.mMapDocument.selectedObjects() items = QSet() for object in objects: item = self.itemForObject(object) if item: items.insert(item) self.mSelectedObjectItems = items self.selectedObjectItemsChanged.emit() def syncAllObjectItems(self): for item in self.mObjectItems: item.syncWithMapObject() def createLayerItem(self, layer): layerItem = None tl = layer.asTileLayer() if tl: layerItem = TileLayerItem(tl, self.mMapDocument) else: og = layer.asObjectGroup() if og: drawOrder = og.drawOrder() ogItem = ObjectGroupItem(og) objectIndex = 0 for object in og.objects(): item = MapObjectItem(object, self.mMapDocument, ogItem) if (drawOrder == ObjectGroup.DrawOrder.TopDownOrder): item.setZValue(item.y()) else: item.setZValue(objectIndex) self.mObjectItems.insert(object, item) objectIndex += 1 layerItem = ogItem else: il = layer.asImageLayer() if il: layerItem = ImageLayerItem(il, self.mMapDocument) layerItem.setVisible(layer.isVisible()) return layerItem def updateSceneRect(self): mapSize = self.mMapDocument.renderer().mapSize() sceneRect = QRectF(0, 0, mapSize.width(), mapSize.height()) margins = self.mMapDocument.map().computeLayerOffsetMargins() sceneRect.adjust(-margins.left(), -margins.top(), margins.right(), margins.bottom()) self.setSceneRect(sceneRect) self.mDarkRectangle.setRect(sceneRect) def updateCurrentLayerHighlight(self): if (not self.mMapDocument): return currentLayerIndex = self.mMapDocument.currentLayerIndex() if (not self.mHighlightCurrentLayer or currentLayerIndex == -1): self.mDarkRectangle.setVisible(False) # Restore opacity for all layers for i in range(self.mLayerItems.size()): layer = self.mMapDocument.map().layerAt(i) self.mLayerItems.at(i).setOpacity(layer.opacity()) return # Darken layers below the current layer self.mDarkRectangle.setZValue(currentLayerIndex - 0.5) self.mDarkRectangle.setVisible(True) # Set layers above the current layer to half opacity for i in range(1, self.mLayerItems.size()): layer = self.mMapDocument.map().layerAt(i) if currentLayerIndex < i: _x = opacityFactor else: _x = 1 multiplier = _x self.mLayerItems.at(i).setOpacity(layer.opacity() * multiplier) def eventFilter(self, object, event): x = event.type() if x == QEvent.KeyPress or x == QEvent.KeyRelease: keyEvent = event newModifiers = keyEvent.modifiers() if (self.mActiveTool and newModifiers != self.mCurrentModifiers): self.mActiveTool.modifiersChanged(newModifiers) self.mCurrentModifiers = newModifiers else: pass return False
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 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 MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.propertyToId = QMap() self.idToProperty = QMap() self.idToExpanded = QMap() editMenu = self.menuBar().addMenu(self.tr("Edit")) newObjectMenu = editMenu.addMenu(self.tr("New Object")) newRectangleAction = QAction(self.tr("Rectangle"), self) newRectangleAction.triggered.connect(self.newRectangle) newObjectMenu.addAction(newRectangleAction) newLineAction = QAction(self.tr("Line"), self) newLineAction.triggered.connect(self.newLine) newObjectMenu.addAction(newLineAction) newEllipseAction = QAction(self.tr("Ellipse"), self) newEllipseAction.triggered.connect(self.newEllipse) newObjectMenu.addAction(newEllipseAction) newTextAction = QAction(self.tr("Text"), self) newTextAction.triggered.connect(self.newText) newObjectMenu.addAction(newTextAction) self.deleteAction = QAction(self.tr("Delete Object"), self) self.deleteAction.triggered.connect(self.deleteObject) editMenu.addAction(self.deleteAction) clearAction = QAction(self.tr("Clear All"), self) clearAction.triggered.connect(self.clearAll) editMenu.addAction(clearAction) fillAction = QAction(self.tr("Fill View"), self) fillAction.triggered.connect(self.fillView) editMenu.addAction(fillAction) self.doubleManager = QtDoublePropertyManager(self) self.stringManager = QtStringPropertyManager(self) self.colorManager = QtColorPropertyManager(self) self.fontManager = QtFontPropertyManager(self) self.pointManager = QtPointPropertyManager(self) self.sizeManager = QtSizePropertyManager(self) self.doubleManager.valueChangedSignal.connect(self.valueChanged) self.stringManager.valueChangedSignal.connect(self.valueChanged) self.colorManager.valueChangedSignal.connect(self.valueChanged) self.fontManager.valueChangedSignal.connect(self.valueChanged) self.pointManager.valueChangedSignal.connect(self.valueChanged) self.sizeManager.valueChangedSignal.connect(self.valueChanged) doubleSpinBoxFactory = QtDoubleSpinBoxFactory(self) checkBoxFactory = QtCheckBoxFactory(self) spinBoxFactory = QtSpinBoxFactory(self) lineEditFactory = QtLineEditFactory(self) comboBoxFactory = QtEnumEditorFactory(self) self.canvas = QtCanvas(800, 600) self.canvasView = CanvasView(self.canvas, self) self.setCentralWidget(self.canvasView) dock = QDockWidget(self) self.addDockWidget(Qt.RightDockWidgetArea, dock) self.propertyEditor = QtTreePropertyBrowser(dock) self.propertyEditor.setFactoryForManager(self.doubleManager, doubleSpinBoxFactory) self.propertyEditor.setFactoryForManager(self.stringManager, lineEditFactory) self.propertyEditor.setFactoryForManager(self.colorManager.subIntPropertyManager(), spinBoxFactory) self.propertyEditor.setFactoryForManager(self.fontManager.subIntPropertyManager(), spinBoxFactory) self.propertyEditor.setFactoryForManager(self.fontManager.subBoolPropertyManager(), checkBoxFactory) self.propertyEditor.setFactoryForManager(self.fontManager.subEnumPropertyManager(), comboBoxFactory) self.propertyEditor.setFactoryForManager(self.pointManager.subIntPropertyManager(), spinBoxFactory) self.propertyEditor.setFactoryForManager(self.sizeManager.subIntPropertyManager(), spinBoxFactory) dock.setWidget(self.propertyEditor) self.currentItem = QtCanvasItem(None) self.canvasView.itemClickedSignal.connect(self.itemClicked) self.canvasView.itemMovedSignal.connect(self.itemMoved) self.fillView() self.itemClicked(QtCanvasItem(None)) def newRectangle(self): item = self.addRectangle() self.canvas.update() self.itemClicked(item) def newEllipse(self): item = self.addEllipse() self.canvas.update() self.itemClicked(item) def newLine(self): item = self.addLine() self.canvas.update() self.itemClicked(item) def newText(self): item = self.addText() self.canvas.update() self.itemClicked(item) def deleteObject(self): if (not self.currentItem or self.currentItem.isNone()): return self.canvas.removeItem(self.currentItem) self.currentItem = QtCanvasItem(None) self.itemClicked(self.currentItem) self.canvas.update() def clearAll(self): for item in self.canvas.allItems(): self.canvas.removeItem(item) self.itemClicked(QtCanvasItem(None)) self.canvas.update() def fillView(self): for i in range(10): self.addRectangle() self.addEllipse() self.addLine() self.addText() self.canvas.update() def addRectangle(self): item = QtCanvasRectangle(rand() % self.canvas.width(), rand() % self.canvas.height(), 50, 50, self.canvas) item.setBrush(QBrush(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8))) item.setPen(QPen(QColor(rand() % 32*8, rand() % 32*8, rand() % 32*8), 4)) item.setZ(rand() % 256) item.show() return item def addEllipse(self): item = QtCanvasEllipse(50, 50, self.canvas) item.setBrush(QBrush(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8))) item.move(rand() % self.canvas.width(), rand() % self.canvas.height()) item.setZ(rand() % 256) item.show() return item def addLine(self): item = QtCanvasLine(self.canvas) item.setPoints(0, 0, rand() % self.canvas.width() - self.canvas.width() / 2, rand() % self.canvas.height() - self.canvas.height() / 2) item.move(rand() % self.canvas.width(), rand() % self.canvas.height()) item.setPen(QPen(QColor(rand() % 32*8, rand() % 32*8, rand() % 32*8), 6)) item.setZ(rand() % 256) item.show() return item def addText(self): item = QtCanvasText(self.canvas) item.setText(self.tr("Text")) item.setColor(QColor(rand() % 32*8, rand() % 32*8, rand() % 32*8)) item.move(rand() % self.canvas.width(), rand() % self.canvas.height()) item.setZ(rand() % 256) item.show() return item def itemMoved(self, item): if (item != self.currentItem or self.currentItem.isNone()): return self.doubleManager.setValue(self.idToProperty["xpos"], item.x()) self.doubleManager.setValue(self.idToProperty["ypos"], item.y()) self.doubleManager.setValue(self.idToProperty["zpos"], item.z()) def updateExpandState(self): l = self.propertyEditor.topLevelItems() for item in l: prop = item.property() self.idToExpanded[self.propertyToId[prop]] = self.propertyEditor.isExpanded(item) def itemClicked(self, item): self.updateExpandState() for p in self.propertyToId.keys(): p.destroy() self.propertyToId.clear() self.idToProperty.clear() self.currentItem = item if (not self.currentItem or self.currentItem.isNone()): self.deleteAction.setEnabled(False) return self.deleteAction.setEnabled(True) property = self.doubleManager.addProperty(self.tr("Position X")) self.doubleManager.setRange(property, 0, self.canvas.width()) self.doubleManager.setValue(property, item.x()) self.addProperty(property, "xpos") property = self.doubleManager.addProperty(self.tr("Position Y")) self.doubleManager.setRange(property, 0, self.canvas.height()) self.doubleManager.setValue(property, item.y()) self.addProperty(property, "ypos") property = self.doubleManager.addProperty(self.tr("Position Z")) self.doubleManager.setRange(property, 0, 256) self.doubleManager.setValue(property, item.z()) self.addProperty(property, "zpos") if (item.rtti() == RttiValues.Rtti_Rectangle): i = item property = self.colorManager.addProperty(self.tr("Brush Color")) self.colorManager.setValue(property, i.brush().color()) self.addProperty(property, "brush") property = self.colorManager.addProperty(self.tr("Pen Color")) self.colorManager.setValue(property, i.pen().color()) self.addProperty(property, "pen") property = self.sizeManager.addProperty(self.tr("Size")) self.sizeManager.setValue(property, i.size()) self.addProperty(property, "size") elif (item.rtti() == RttiValues.Rtti_Line): i = item property = self.colorManager.addProperty(self.tr("Pen Color")) self.colorManager.setValue(property, i.pen().color()) self.addProperty(property, "pen") property = self.pointManager.addProperty(self.tr("Vector")) self.pointManager.setValue(property, i.endPoint()) self.addProperty(property, "endpoint") elif (item.rtti() == RttiValues.Rtti_Ellipse): i = item property = self.colorManager.addProperty(self.tr("Brush Color")) self.colorManager.setValue(property, i.brush().color()) self.addProperty(property, "brush") property = self.sizeManager.addProperty(self.tr("Size")) self.sizeManager.setValue(property, QSize(i.width(), i.height())) self.sizeManager.setRange(property, QSize(0, 0), QSize(1000, 1000)) self.addProperty(property, "size") elif (item.rtti() == RttiValues.Rtti_Text): i = item property = self.colorManager.addProperty(self.tr("Color")) self.colorManager.setValue(property, i.color()) self.addProperty(property, "color") property = self.stringManager.addProperty(self.tr("Text")) self.stringManager.setValue(property, i.text()) self.addProperty(property, "text") property = self.fontManager.addProperty(self.tr("Font")) self.fontManager.setValue(property, i.font()) self.addProperty(property, "font") def addProperty(self, property, id): self.propertyToId[property] = id self.idToProperty[id] = property item = self.propertyEditor.addProperty(property) if (self.idToExpanded.contains(id)): self.propertyEditor.setExpanded(item, self.idToExpanded[id]) def valueChanged(self, property, value): if (not self.propertyToId.contains(property)): return if (not self.currentItem or self.currentItem.isNone()): return tp = type(value) id = self.propertyToId[property] if tp == float: if (id == "xpos"): self.currentItem.setX(value) elif (id == "ypos"): self.currentItem.setY(value) elif (id == "zpos"): self.currentItem.setZ(value) elif tp == str: if (id == "text"): if (self.currentItem.rtti() == RttiValues.Rtti_Text): i = self.currentItem i.setText(value) elif tp == QColor: if (id == "color"): if (self.currentItem.rtti() == RttiValues.Rtti_Text): i = self.currentItem i.setColor(value) elif (id == "brush"): if (self.currentItem.rtti() == RttiValues.Rtti_Rectangle or self.currentItem.rtti() == RttiValues.Rtti_Ellipse): i = self.currentItem b = QBrush(i.brush()) b.setColor(value) i.setBrush(b) elif (id == "pen"): if (self.currentItem.rtti() == RttiValues.Rtti_Rectangle or self.currentItem.rtti() == RttiValues.Rtti_Line): i = self.currentItem p = QPen(i.pen()) p.setColor(value) i.setPen(p) elif tp == QFont: if (id == "font"): if (self.currentItem.rtti() == RttiValues.Rtti_Text): i = self.currentItem i.setFont(value) elif tp == QPoint: if (self.currentItem.rtti() == RttiValues.Rtti_Line): i = self.currentItem if (id == "endpoint"): i.setPoints(i.startPoint().x(), i.startPoint().y(), value.x(), value.y()) elif tp == QSize: if (id == "size"): if (self.currentItem.rtti() == RttiValues.Rtti_Rectangle): i = self.currentItem i.setSize(value.width(), value.height()) elif (self.currentItem.rtti() == RttiValues.Rtti_Ellipse): i = self.currentItem i.setSize(value.width(), value.height()) self.canvas.update()
class MapObjectModel(QAbstractItemModel): objectsAdded = pyqtSignal(QList) objectsChanged = pyqtSignal(QList) objectsRemoved = pyqtSignal(QList) def __init__(self, parent): super().__init__(parent) self.mObjectGroups = QList() self.mObjects = QMap() self.mGroups = QMap() self.mMapDocument = None self.mMap = None self.mObject = None self.mObjectGroupIcon = ":/images/16x16/layer-object.png" def index(self, *args): l = len(args) if l > 0: tp = type(args[0]) if tp == int: if l == 2: args = (args[0], args[1], QModelIndex()) row, column, parent = args if (not parent.isValid()): if (row < self.mObjectGroups.count()): return self.createIndex( row, column, self.mGroups[self.mObjectGroups.at(row)]) return QModelIndex() og = self.toObjectGroup(parent) # happens when deleting the last item in a parent if (row >= og.objectCount()): return QModelIndex() # Paranoia: sometimes "fake" objects are in use (see createobjecttool) if (not self.mObjects.contains(og.objects().at(row))): return QModelIndex() return self.createIndex(row, column, self.mObjects[og.objects()[row]]) elif tp == ObjectGroup: og = args[0] row = self.mObjectGroups.indexOf(og) return self.createIndex(row, 0, self.mGroups[og]) elif tp == MapObject: if l == 1: args = (args[0], 0) o, column = args row = o.objectGroup().objects().indexOf(o) return self.createIndex(row, column, self.mObjects[o]) def parent(self, index): mapObject = self.toMapObject(index) if mapObject: return self.index(mapObject.objectGroup()) return QModelIndex() def rowCount(self, parent=QModelIndex()): if (not self.mMapDocument): return 0 if (not parent.isValid()): return self.mObjectGroups.size() og = self.toObjectGroup(parent) if og: return og.objectCount() return 0 def columnCount(self, parent=QModelIndex()): return 2 # MapObject name|type def headerData(self, section, orientation, role=Qt.DisplayRole): if (role == Qt.DisplayRole and orientation == Qt.Horizontal): x = section if x == 0: return self.tr("Name") elif x == 1: return self.tr("Type") return QVariant() def setData(self, index, value, role): mapObject = self.toMapObject(index) if mapObject: x = role if x == Qt.CheckStateRole: c = value visible = (c == Qt.Checked) if (visible != mapObject.isVisible()): command = SetMapObjectVisible(self.mMapDocument, mapObject, visible) self.mMapDocument.undoStack().push(command) return True elif x == Qt.EditRole: s = value if (index.column() == 0 and s != mapObject.name()): undo = self.mMapDocument.undoStack() undo.beginMacro(self.tr("Change Object Name")) undo.push( ChangeMapObject(self.mMapDocument, mapObject, s, mapObject.type())) undo.endMacro() if (index.column() == 1 and s != mapObject.type()): undo = self.mMapDocument.undoStack() undo.beginMacro(self.tr("Change Object Type")) undo.push( ChangeMapObject(self.mMapDocument, mapObject, mapObject.name(), s)) undo.endMacro() return True return False objectGroup = self.toObjectGroup(index) if objectGroup: x = role if x == Qt.CheckStateRole: layerModel = self.mMapDocument.layerModel() layerIndex = self.mMap.layers().indexOf(objectGroup) row = layerModel.layerIndexToRow(layerIndex) layerModel.setData(layerModel.index(row), value, role) return True elif x == Qt.EditRole: newName = value if (objectGroup.name() != newName): layerIndex = self.mMap.layers().indexOf(objectGroup) rename = RenameLayer(self.mMapDocument, layerIndex, newName) self.mMapDocument.undoStack().push(rename) return True return False return False def data(self, index, role=Qt.DisplayRole): mapObject = self.toMapObject(index) if mapObject: x = role if x == Qt.DisplayRole or x == Qt.EditRole: if index.column(): _x = mapObject.type() else: _x = mapObject.name() return _x elif x == Qt.DecorationRole: return QVariant() # no icon . maybe the color? elif x == Qt.CheckStateRole: if (index.column() > 0): return QVariant() if mapObject.isVisible(): _x = Qt.Checked else: _x = Qt.Unchecked return _x elif x == LayerModel.UserRoles.OpacityRole: return 1.0 else: return QVariant() objectGroup = self.toObjectGroup(index) if objectGroup: x = role if x == Qt.DisplayRole or x == Qt.EditRole: if index.column(): _x = QVariant() else: _x = objectGroup.name() return _x elif x == Qt.DecorationRole: if index.column(): _x = QVariant() else: _x = self.mObjectGroupIcon return _x elif x == Qt.CheckStateRole: if (index.column() > 0): return QVariant() if objectGroup.isVisible(): _x = Qt.Checked else: _x = Qt.Unchecked return _x elif x == LayerModel.UserRoles.OpacityRole: return objectGroup.opacity() else: return QVariant() return QVariant() def flags(self, index): rc = super().flags(index) if (index.column() == 0): rc |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable elif (index.parent().isValid()): rc |= Qt.ItemIsEditable # MapObject type return rc def toObjectGroup(self, index): if (not index.isValid()): return None oog = index.internalPointer() if oog: return oog.mGroup def toMapObject(self, index): if (not index.isValid()): return None oog = index.internalPointer() if oog: return oog.mObject def toLayer(self, index): if (not index.isValid()): return None oog = index.internalPointer() if oog: if oog.mGroup: _x = oog.mGroup else: _x = oog.mObject.objectGroup() return _x def setMapDocument(self, mapDocument): if (self.mMapDocument == mapDocument): return if (self.mMapDocument): self.mMapDocument.disconnect() self.beginResetModel() self.mMapDocument = mapDocument self.mMap = None self.mObjectGroups.clear() self.mGroups.clear() self.mGroups.clear() self.mObjects.clear() self.mObjects.clear() if (self.mMapDocument): self.mMap = self.mMapDocument.map() self.mMapDocument.layerAdded.connect(self.layerAdded) self.mMapDocument.layerChanged.connect(self.layerChanged) self.mMapDocument.layerAboutToBeRemoved.connect( self.layerAboutToBeRemoved) for og in self.mMap.objectGroups(): if GROUPS_IN_DISPLAY_ORDER: self.mObjectGroups.prepend(og) else: self.mObjectGroups.append(og) self.mGroups.insert(og, ObjectOrGroup(og)) for o in og.objects(): self.mObjects.insert(o, ObjectOrGroup(o)) self.endResetModel() def insertObject(self, og, index, o): if (index >= 0): _x = index else: _x = og.objectCount() row = _x self.beginInsertRows(self.index(og), row, row) og.insertObject(row, o) self.mObjects.insert(o, ObjectOrGroup(o)) self.endInsertRows() self.objectsAdded.emit(QList([o])) def removeObject(self, og, o): objects = QList() objects.append(o) row = og.objects().indexOf(o) self.beginRemoveRows(self.index(og), row, row) og.removeObjectAt(row) self.mObjects.remove(o) self.endRemoveRows() self.objectsRemoved.emit(objects) return row def moveObjects(self, og, _from, to, count): parent = self.index(og) if (not self.beginMoveRows(parent, _from, _from + count - 1, parent, to)): return og.moveObjects(_from, to, count) self.endMoveRows() # ObjectGroup color changed # FIXME: layerChanged should let the scene know that objects need redrawing def emitObjectsChanged(self, objects): if objects.isEmpty(): return self.objectsChanged.emit(objects) def setObjectName(self, o, name): if o.name() == name: return o.setName(name) index = self.index(o) self.dataChanged.emit(index, index) self.objectsChanged.emit(QList([o])) def setObjectType(self, o, type): if o.type() == type: return o.setType(type) index = self.index(o, 1) self.dataChanged.emit(index, index) self.objectsChanged.emit(QList([o])) def setObjectPolygon(self, o, polygon): if o.polygon() == polygon: return o.setPolygon(polygon) self.objectsChanged.emit(QList([o])) def setObjectPosition(self, o, pos): if o.position() == pos: return o.setPosition(pos) self.objectsChanged.emit(QList([o])) def setObjectSize(self, o, size): if o.size() == size: return o.setSize(size) self.objectsChanged.emit(QList([o])) def setObjectRotation(self, o, rotation): if o.rotation() == rotation: return o.setRotation(rotation) self.objectsChanged.emit(QList([o])) def setObjectVisible(self, o, visible): if o.isVisible() == visible: return o.setVisible(visible) index = self.index(o) self.dataChanged.emit(index, index) self.objectsChanged.emit(QList([o])) def layerAdded(self, index): layer = self.mMap.layerAt(index) og = layer.asObjectGroup() if og: if (not self.mGroups.contains(og)): prev = None for index in range(index - 1, -1, -1): prev = self.mMap.layerAt(index).asObjectGroup() if prev: break if GROUPS_IN_DISPLAY_ORDER: if prev: _x = self.mObjectGroups.indexOf(prev) else: _x = self.mObjectGroups.count() index = _x else: if prev: index = self.mObjectGroups.indexOf(prev) + 1 else: index = 0 self.mObjectGroups.insert(index, og) row = self.mObjectGroups.indexOf(og) self.beginInsertRows(QModelIndex(), row, row) self.mGroups.insert(og, ObjectOrGroup(og)) for o in og.objects(): if (not self.mObjects.contains(o)): self.mObjects.insert(o, ObjectOrGroup(o)) self.endInsertRows() def layerChanged(self, index): layer = self.mMap.layerAt(index) og = layer.asObjectGroup() if og: index = self.index(og) self.dataChanged.emit(index, index) def layerAboutToBeRemoved(self, index): layer = self.mMap.layerAt(index) og = layer.asObjectGroup() if og: row = self.mObjectGroups.indexOf(og) self.beginRemoveRows(QModelIndex(), row, row) self.mObjectGroups.removeAt(row) self.mGroups.remove(og) for o in og.objects(): self.mObjects.remove(og) self.endRemoveRows()
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)