def __init__(self, parent=None): super().__init__(self.tr("Bucket Fill Tool"), QIcon(":images/22x22/stock-tool-bucket-fill.png"), QKeySequence(self.tr("F")), parent) self.mStamp = TileStamp() self.mFillOverlay = None self.mFillRegion = QRegion() self.mMissingTilesets = QVector() self.mIsActive = False self.mLastShiftStatus = False ## # Indicates if the tool is using the random mode. ## self.mIsRandom = False ## # Contains the value of mIsRandom at that time, when the latest call of # tilePositionChanged() took place. # This variable is needed to detect if the random mode was changed during # mFillOverlay being brushed at an area. ## self.mLastRandomStatus = False ## # Contains all used random cells to use in random mode. # The same cell can be in the list multiple times to make different # random weights possible. ## self.mRandomCellPicker = RandomPicker()
def paintEvent(self, event): """ Called when the widget is painted on the window. """ dpr = self.devicePixelRatioF() buffer = QPixmap(self.width() * dpr, self.height() * dpr) buffer.setDevicePixelRatio(dpr) buffer.fill(Qt.transparent) painter = QPainter(buffer) region = QRegion( QRect(0, 0, self._textWidget.sizeHint().width(), self._textWidget.sizeHint().height())) self._textWidget.render(painter, QPoint(0, 0), region) region = QRegion( QRect(0, 0, self._iconWidget.sizeHint().width(), self._iconWidget.sizeHint().height())) x = self._textWidget.sizeHint().width() + 3 self._iconWidget.render(painter, QPoint(x, 0), region) painter.end() painter = QPainter(self) painter.drawPixmap(event.rect(), buffer, buffer.rect()) painter.end()
def __init__(self, parent=None): super(CuriWidget, self).__init__(parent, Qt.FramelessWindowHint | Qt.WindowSystemMenuHint) self.addCustomAction() w = qApp.desktop().screenGeometry().width() h = qApp.desktop().screenGeometry().height() side = round((8 / 9) * min(w / cols, h / rows)) self.setFixedSize(side * QSize(cols, rows)) self.thread = SoundThread(self) self.dragPosition = QPoint() self.button = None self.setWindowIcon(QIcon(":curielements")) region = QRegion(QRect(0, 0, 2 * side, 2 * side), QRegion.Ellipse) region += QRegion(QRect(side, 0, 8 * side, 15 * side)) region += QRegion(QRect(0, side, side, 13 * side)) region += QRegion(QRect(0, 13 * side, 2 * side, 2 * side), QRegion.Ellipse) region += QRegion(QRect(9 * side, side, side, 14 * side)) region += QRegion(QRect(8 * side, 0, 2 * side, 2 * side), QRegion.Ellipse) region += QRegion(QRect(10 * side, 2 * side, 19 * side, 13 * side)) region += QRegion(QRect(28 * side, 2 * side, 2 * side, 2 * side), QRegion.Ellipse) region += QRegion(QRect(29 * side, 3 * side, side, 11 * side)) region += QRegion(QRect(28 * side, 13 * side, 2 * side, 2 * side), QRegion.Ellipse) self.setMask(region) self.atoms = Atoms(self) self.atoms.setGeometry( QRect(1.5 * side, 1.5 * side, 7 * side, 7 * side)) offset = QPoint(10 * side, 3 * side) file = QFile(":elements") file.open(QFile.ReadOnly | QFile.Text) colors = [blue, yellow] self.btns = [] while not file.atEnd(): x, y, name, symbol, electron, description, description2, _ = file.readLine( ).split(',') coordinate = QPoint(int(x), int(y)) text = bytearray(name).decode() btn = ElementButton(QSize(side, side), colors, int(electron), bytearray(symbol).decode(), text, self) btn.move(offset + coordinate * side) btn.clicked.connect(self.button_clicked) self.btns.append(btn) self.imageDescription = DescriptionButton(side * QSize(7, 4.5), blue, self) self.imageDescription.move(1.5 * side, 9 * side) btnSound = DescriptionButton(side * QSize(2, 2), blue, self) btnSound.move(11 * side, 12 * side) btnSound.updateBackground(":soundOn") btnSound.clicked.connect(self.sound_clicled)
def doPaint(self, flags=0): preview = self.mPreviewLayer if not preview: return QRegion() # This method shouldn't be called when current layer is not a tile layer tileLayer = self.currentTileLayer() if (not tileLayer.bounds().intersects( QRect(preview.x(), preview.y(), preview.width(), preview.height()))): return QRegion() paint = PaintTileLayer(self.mapDocument(), tileLayer, preview.x(), preview.y(), preview) if not self.mMissingTilesets.isEmpty(): for tileset in self.mMissingTilesets: AddTileset(self.mapDocument(), tileset, paint) self.mMissingTilesets.clear() paint.setMergeable(flags & PaintFlags.Mergeable) self.mapDocument().undoStack().push(paint) editedRegion = preview.region() if (not (flags & PaintFlags.SuppressRegionEdited)): self.mapDocument().emitRegionEdited(editedRegion, tileLayer) return editedRegion
def itemRegion(self, index): if not index.isValid(): return QRegion() if index.column() != 1: return QRegion(self.itemRect(index)) if self.model().data(index) <= 0.0: return QRegion() startAngle = 0.0 for row in range(self.model().rowCount(self.rootIndex())): sliceIndex = self.model().index(row, 1, self.rootIndex()) value = self.model().data(sliceIndex) if value > 0.0: angle = 360 * value / self.totalValue if sliceIndex == index: slicePath = QPainterPath() slicePath.moveTo(self.totalSize / 2, self.totalSize / 2) slicePath.arcTo(self.margin, self.margin, self.margin + self.pieSize, self.margin + self.pieSize, startAngle, angle) slicePath.closeSubpath() return QRegion(slicePath.toFillPolygon().toPolygon()) startAngle += angle return QRegion()
def setupRuleList(self): combinedRegions = coherentRegions(self.mLayerInputRegions.region() + self.mLayerOutputRegions.region()) combinedRegions = QList( sorted(combinedRegions, key=lambda x: x.y(), reverse=True)) rulesInput = coherentRegions(self.mLayerInputRegions.region()) rulesOutput = coherentRegions(self.mLayerOutputRegions.region()) for i in range(combinedRegions.size()): self.mRulesInput.append(QRegion()) self.mRulesOutput.append(QRegion()) for reg in rulesInput: for i in range(combinedRegions.size()): if (reg.intersects(combinedRegions[i])): self.mRulesInput[i] += reg break for reg in rulesOutput: for i in range(combinedRegions.size()): if (reg.intersects(combinedRegions[i])): self.mRulesOutput[i] += reg break for i in range(self.mRulesInput.size()): checkCoherent = self.mRulesInput.at(i).united( self.mRulesOutput.at(i)) coherentRegions(checkCoherent).length() == 1 return True
def removeTab(self, index): """ Remove a tab at `index`. """ if index >= 0 and index < self.count(): tab = self.__tabs.pop(index) layout_index = self.layout().indexOf(tab.button) if layout_index != -1: self.layout().takeAt(layout_index) self.__group.removeButton(tab.button) tab.button.removeEventFilter(self) if tab.button is self.__sloppyButton: self.__sloppyButton = None self.__sloppyRegion = QRegion() tab.button.deleteLater() tab.button.setParent(None) if self.currentIndex() == index: if self.count(): self.setCurrentIndex(max(index - 1, 0)) else: self.setCurrentIndex(-1)
def __init__(self, parent=None, **kwargs): QWidget.__init__(self, parent, **kwargs) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) self.__tabs = [] self.__currentIndex = -1 self.__changeOnHover = False self.__iconSize = QSize(26, 26) self.__group = QButtonGroup(self, exclusive=True) self.__group.buttonPressed[QAbstractButton].connect( self.__onButtonPressed ) self.setMouseTracking(True) self.__sloppyButton = None self.__sloppyRegion = QRegion() self.__sloppyTimer = QTimer(self, singleShot=True) self.__sloppyTimer.timeout.connect(self.__onSloppyTimeout)
def paintEvent(self, anEvent): self.painter = QPainter(self) self.painter.setRenderHint(QPainter.Antialiasing, on=True) self.diskDiameter = min(self.size().width(), self.size().height()) - 10 self.origin.setX(self.size().width()/2) self.origin.setY(self.size().height()/2) self.painter.setPen(QPen(QColor(122, 0, 127, 255), 3)) radius = self.diskDiameter/2 x = self.origin.x() y = self.origin.y() diskRect = QRect(x - radius, y - radius, self.diskDiameter, self.diskDiameter) self.diskRegion = QRegion(diskRect, QRegion.Ellipse) self.painter.setClipRegion(self.diskRegion) if self.tilesToUpdate: for tile in self.tiles: tile.update(self.painter) self.tilesToUpdate = False else: self.drawTiling() self.painter.setClipping(False) self.painter.setPen(QPen(QColor(5, 0, 127, 255), 3)) self.painter.drawEllipse(diskRect) self.painter.drawPoint(self.origin) self.painter.end()
def paint(self, page, painter, rect, callback=None): """Reimplemented to paint all the sub pages on top of each other.""" # make the call back return with the original page, not the overlay page newcallback = CallBack(callback, page) if callback else None # get the device pixel ratio to paint for try: ratio = painter.device().devicePixelRatioF() except AttributeError: ratio = painter.device().devicePixelRatio() pixmaps = [] covered = QRegion() for p, overlayrect in page.visiblePagesAt(rect): pixmap = QPixmap(overlayrect.size() * ratio) if not pixmap.isNull(): pixmap.setDevicePixelRatio(ratio) pt = QPainter(pixmap) pt.translate(p.pos() - overlayrect.topLeft()) p.paint(pt, overlayrect.translated(-p.pos()), newcallback) pt.end() # even an empty pixmap is appended, otherwise the layer count when # compositing goes awry pos = overlayrect.topLeft() pixmaps.append((pos, pixmap)) covered += overlayrect if QRegion(rect).subtracted(covered): painter.fillRect(rect, page.paperColor or self.paperColor) self.combine(painter, pixmaps)
def draw(self, painter: QPainter): assert self.crop, 'crop must be set' # Compute painter regions for the crop and the blur all_region = QRegion(painter.viewport()) crop_region = QRegion(self.crop) blur_region = all_region.subtracted(crop_region) # Let the QGraphicsBlurEffect only paint in blur_region painter.setClipRegion(blur_region) # Fill with black and set opacity so that the blurred region is drawn darker if self.BLUR_DARKEN > 0.0: painter.fillRect(painter.viewport(), Qt.black) painter.setOpacity(1 - self.BLUR_DARKEN) # Draw the blur effect super().draw(painter) # Restore clipping and opacity painter.setClipping(False) painter.setOpacity(1.0) # Get the source pixmap pixmap, offset = self.sourcePixmap(Qt.DeviceCoordinates, QGraphicsEffect.NoPad) painter.setWorldTransform(QTransform()) # Get the source by adding the offset to the crop location source = self.crop if self.CROP_OFFSET_ENABLED: source = source.translated(self.CROP_OFFSET) painter.drawPixmap(self.crop.topLeft() + offset, pixmap, source)
def __init__(self): super().__init__() self.mMapDocument = None self.mBoundingRect = QRectF() self.mRegion = QRegion() self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
def __init__(self): super(Menu, self).__init__() self.background_picture = QGraphicsView() self.scene = QGraphicsScene() self.grp = QGroupBox("Mon Groupe") self.palette = QPalette() self.proxy = QGraphicsProxyWidget() self.background_picture2 = QPixmap() self.fenetre_creer_une_partie = None self.bouton_creer = QPushButton(self) self.bouton_rejoindre = QPushButton(self) self.bouton_quitter = QPushButton(self) self.bouton_help = QPushButton(self) self.label = QLabel(self) self.init_ui() self.setWindowTitle("Bienvenue dans Skip-Bo!") self.hauteur = int self.quart_hauteur = int self.longueur = int self.quart_longueur = int self.test_rect = QRect() self.test_region = QRegion() self.rsp = None self.rsp2 = None self.rsp3 = None self.join = QDialog() self.verification = QDialog() self.quitter_icon = QIcon() self.join_icon = QIcon() self.creer_icon = QIcon() self.help_icon = QIcon() self.msg_help = QMessageBox()
def __updateGeometry(self): """ Update the shadow geometry to fit the widget's changed geometry. """ widget = self.__widget parent = self.__widgetParent radius = self.radius_ pos = widget.pos() if parent != widget.parentWidget(): pos = widget.parentWidget().mapTo(parent, pos) geom = QRect(pos, widget.size()) geom.adjust(-radius, -radius, radius, radius) if geom != self.geometry(): self.setGeometry(geom) # Set the widget mask (punch a hole through to the `widget` instance. rect = self.rect() mask = QRegion(rect) transparent = QRegion(rect.adjusted(radius, radius, -radius, -radius)) mask = mask.subtracted(transparent) self.setMask(mask)
def mousePressEvent(self, event): ellipse = QRegion(self.rect, QRegion.Ellipse) if ellipse.contains(event.pos()): self.state = not self.state self.update() # call paintEvent()
def init_ui(self): # --------------- Paramètres de la fenêtre -------------------- self.resize(1280, 720) self.setMaximumSize(1280, 720) self.setMinimumSize(1280, 720) self.center() self.background_picture2.load('backgroundmenu3.png') self.palette.setBrush(self.backgroundRole(), QBrush(self.background_picture2)) self.setPalette(self.palette) # --------------- Éléments présents dans la fenêtre ---------- self.hauteur = int(self.frameGeometry().height()) self.quart_hauteur = int(self.hauteur / 4) self.longueur = int(self.frameGeometry().width()) self.quart_longueur = int(self.longueur / 4) self.label.setText("* Skip-Bo est une marque déposée de Mattel Inc.") self.label.move(20, 690) self.label.adjustSize() self.bouton_creer.setText("") self.creer_icon = QIcon("bouton_creer.png") self.bouton_creer.setIcon(self.creer_icon) self.bouton_creer.setIconSize(QSize(320, 90)) self.bouton_creer.setGeometry((self.quart_longueur - 160), (3 * self.quart_hauteur), self.quart_longueur, int(self.quart_hauteur / 2)) self.bouton_creer.clicked.connect(self.creer_nouvelle_partie) self.bouton_rejoindre.setText("") self.join_icon = QIcon("bouton_join.png") self.bouton_rejoindre.setIcon(self.join_icon) self.bouton_rejoindre.setIconSize(QSize(318, 88)) self.bouton_rejoindre.setGeometry(((2 * self.quart_longueur) - 160), (3 * self.quart_hauteur), self.quart_longueur, int(self.quart_hauteur / 2)) self.bouton_rejoindre.clicked.connect(self.joindre_partie) self.bouton_help.setText("") self.help_icon = QIcon("bouton_help.png") self.bouton_help.setIcon(self.help_icon) self.bouton_help.setIconSize(QSize(65, 65)) self.bouton_help.setFixedHeight(100) self.bouton_help.setFixedWidth(100) self.bouton_help.move( self.longueur - int(self.quart_longueur / (4 / 2)), -10) self.bouton_help.clicked.connect(self.help) self.test_rect = QRect(20, 20, 60, 60) self.test_region = QRegion(self.test_rect, QRegion.Ellipse) self.test_region.boundingRect().size() self.bouton_help.setMask(self.test_region) self.bouton_quitter.setText("") self.quitter_icon = QIcon("bouton_quitter.png") self.bouton_quitter.setIcon(self.quitter_icon) self.bouton_quitter.setIconSize(QSize(318, 88)) self.bouton_quitter.setGeometry(((3 * self.quart_longueur) - 160), (3 * self.quart_hauteur), self.quart_longueur, int(self.quart_hauteur / 2)) self.bouton_quitter.clicked.connect(self.quitter)
def clearOverlay(self): # Clear connections before clearing overlay so there is no # risk of getting a callback and causing an infinite loop self.clearConnections(self.mapDocument()) self.brushItem().clear() self.mFillOverlay = None self.mFillRegion = QRegion()
def setTileLayer(self, tileLayer): self.mTileLayer = tileLayer if (tileLayer): self.mRegion = tileLayer.region() else: self.mRegion = QRegion() self.updateBoundingRect() self.update()
def mask_label_region(self): """ Mask the webcam label region to avoid mouse events :return: """ region = QRegion(self.webcamDisplayLabel.frameGeometry()) region -= QRegion(self.webcamDisplayLabel.geometry()) region += self.webcamDisplayLabel.childrenRegion() self.webcamDisplayLabel.setMask(region)
def __init__(self, map, fileName=QString()): super().__init__() ## # The filename of a plugin is unique. So it can be used to determine # the right plugin to be used for saving or reloading the map. # The nameFilter of a plugin can not be used, since it's translatable. # The filename of a plugin must not change while maps are open using this # plugin. ## self.mReaderFormat = None self.mWriterFormat = None self.mExportFormat = None self.mSelectedArea = QRegion() self.mSelectedObjects = QList() self.mSelectedTiles = QList() self.mCurrentLayerIndex = 0 self.mLastSaved = QDateTime() self.mLastExportFileName = '' self.mFileName = fileName self.mMap = map self.mLayerModel = LayerModel(self) self.mCurrentObject = map ## Current properties object. ## self.mRenderer = None self.mMapObjectModel = MapObjectModel(self) self.mTerrainModel = TerrainModel(self, self) self.mUndoStack = QUndoStack(self) self.createRenderer() if (map.layerCount() == 0): _x = -1 else: _x = 0 self.mCurrentLayerIndex = _x self.mLayerModel.setMapDocument(self) # Forward signals emitted from the layer model self.mLayerModel.layerAdded.connect(self.onLayerAdded) self.mLayerModel.layerAboutToBeRemoved.connect( self.onLayerAboutToBeRemoved) self.mLayerModel.layerRemoved.connect(self.onLayerRemoved) self.mLayerModel.layerChanged.connect(self.layerChanged) # Forward signals emitted from the map object model self.mMapObjectModel.setMapDocument(self) self.mMapObjectModel.objectsAdded.connect(self.objectsAdded) self.mMapObjectModel.objectsChanged.connect(self.objectsChanged) self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved) self.mMapObjectModel.rowsInserted.connect( self.onMapObjectModelRowsInserted) self.mMapObjectModel.rowsRemoved.connect( self.onMapObjectModelRowsInsertedOrRemoved) self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved) self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved) self.mUndoStack.cleanChanged.connect(self.modifiedChanged) # Register tileset references tilesetManager = TilesetManager.instance() tilesetManager.addReferences(self.mMap.tilesets())
def setCells(self, x, y, layer, mask=QRegion()): # Determine the overlapping area area = QRegion(QRect(x, y, layer.width(), layer.height())) area &= QRect(0, 0, self.width(), self.height()) if (not mask.isEmpty()): area &= mask for rect in area.rects(): for _x in range(rect.left(), rect.right() + 1): for _y in range(rect.top(), rect.bottom() + 1): self.setCell(_x, _y, layer.cellAt(_x - x, _y - y))
def setCells(self, x, y, layer, mask = QRegion()): # Determine the overlapping area area = QRegion(QRect(x, y, layer.width(), layer.height())) area &= QRect(0, 0, self.width(), self.height()) if (not mask.isEmpty()): area &= mask for rect in area.rects(): for _x in range(rect.left(), rect.right()+1): for _y in range(rect.top(), rect.bottom()+1): self.setCell(_x, _y, layer.cellAt(_x - x, _y - y))
def updateActions(self): map = None currentLayerIndex = -1 selection = QRegion() selectedObjectsCount = 0 canMergeDown = False if (self.mMapDocument): map = self.mMapDocument.map() currentLayerIndex = self.mMapDocument.currentLayerIndex() selection = self.mMapDocument.selectedArea() selectedObjectsCount = self.mMapDocument.selectedObjects().count() if (currentLayerIndex > 0): upper = map.layerAt(currentLayerIndex) lower = map.layerAt(currentLayerIndex - 1) canMergeDown = lower.canMergeWith(upper) self.mActionSelectAll.setEnabled(bool(map)) self.mActionSelectNone.setEnabled(not selection.isEmpty()) self.mActionCropToSelection.setEnabled(not selection.isEmpty()) self.mActionAddTileLayer.setEnabled(bool(map)) self.mActionAddObjectGroup.setEnabled(bool(map)) self.mActionAddImageLayer.setEnabled(bool(map)) if map: _x = map.layerCount() else: _x = 0 layerCount = _x hasPreviousLayer = currentLayerIndex >= 0 and currentLayerIndex < layerCount - 1 hasNextLayer = currentLayerIndex > 0 self.mActionDuplicateLayer.setEnabled(currentLayerIndex >= 0) self.mActionMergeLayerDown.setEnabled(canMergeDown) self.mActionSelectPreviousLayer.setEnabled(hasPreviousLayer) self.mActionSelectNextLayer.setEnabled(hasNextLayer) self.mActionMoveLayerUp.setEnabled(hasPreviousLayer) self.mActionMoveLayerDown.setEnabled(hasNextLayer) self.mActionToggleOtherLayers.setEnabled(layerCount > 1) self.mActionRemoveLayer.setEnabled(currentLayerIndex >= 0) self.mActionLayerProperties.setEnabled(currentLayerIndex >= 0) self.mActionDuplicateObjects.setEnabled(selectedObjectsCount > 0) self.mActionRemoveObjects.setEnabled(selectedObjectsCount > 0) duplicateText = QString() removeText = QString() if (selectedObjectsCount > 0): duplicateText = self.tr("Duplicate %n Object(s)", "", selectedObjectsCount) removeText = self.tr("Remove %n Object(s)", "", selectedObjectsCount) else: duplicateText = self.tr("Duplicate Objects") removeText = self.tr("Remove Objects") self.mActionDuplicateObjects.setText(duplicateText) self.mActionRemoveObjects.setText(removeText)
def paintableRegion(self, *args): l = len(args) if l==4: x, y, width, height = args return self.paintableRegion(QRect(x, y, width, height)) elif l==1: region = args[0] bounds = QRegion(self.mTileLayer.bounds()) intersection = bounds.intersected(region) selection = self.mMapDocument.selectedArea() if (not selection.isEmpty()): intersection &= selection return intersection
def __init__(self, mask_path: Path): mask_pix = QPixmap() if not mask_pix.load(str(mask_path)): raise ValueError(f"Failed to load mask from {mask_path}") self.size = mask_pix.size() logger.debug("size: %s", self.size) self.clip_region = QRegion( mask_pix.createMaskFromColor(QColor("white"), Qt.MaskInColor) ) logger.debug("clip_region: %s", self.clip_region) logger.debug("clip_region bounding rect: %s", self.clip_region.boundingRect())
def setCurrentIndex(self, index): """ Set the current tab index. """ if self.__currentIndex != index: self.__currentIndex = index self.__sloppyRegion = QRegion() self.__sloppyButton = None if index != -1: self.__tabs[index].button.setChecked(True) self.currentChanged.emit(index)
def update_cursor_blocks(self): # Update the areas of the previously selected and currently selected blocks in the editor to avoid artifacts block = self.textCursor().block() if self._last_selected_block: currentBlockRect = self.document().documentLayout( ).blockBoundingRect(block).toRect() lastBlockRect = self.document().documentLayout().blockBoundingRect( self._last_selected_block).toRect() viewportoffset = self.verticalScrollBar().value() currentBlockRect.moveTop(currentBlockRect.y() - viewportoffset) lastBlockRect.moveTop(lastBlockRect.y() - viewportoffset) region = QRegion(currentBlockRect).united(QRegion(lastBlockRect)) self.viewport().update(region) self._last_selected_block = block
def doErase(self, continuation): tileLayer = self.currentTileLayer() tilePos = self.tilePosition() eraseRegion = QRegion(tilePos.x(), tilePos.y(), 1, 1) if (continuation): for p in pointsOnLine(self.mLastTilePos, tilePos): eraseRegion |= QRegion(p.x(), p.y(), 1, 1) self.mLastTilePos = self.tilePosition() if (not tileLayer.bounds().intersects(eraseRegion.boundingRect())): return erase = EraseTiles(self.mapDocument(), tileLayer, eraseRegion) erase.setMergeable(continuation) self.mapDocument().undoStack().push(erase) self.mapDocument().emitRegionEdited(eraseRegion, tileLayer)
def take_screenshot(window, screenshots_dir): timestamp = int(time.time()) pixmap = QPixmap(window.rect().size()) window.render(pixmap, QPoint(), QRegion(window.rect())) screenshots_dir.mkdir(exist_ok=True) img_name = 'exception_screenshot_%d.jpg' % timestamp pixmap.save(str(screenshots_dir / img_name))
def __init__(self, parent = None): super().__init__(self.tr("Bucket Fill Tool"), QIcon(":images/22x22/stock-tool-bucket-fill.png"), QKeySequence(self.tr("F")), parent) self.mStamp = TileStamp() self.mFillOverlay = None self.mFillRegion = QRegion() self.mMissingTilesets = QVector() self.mIsActive = False self.mLastShiftStatus = False ## # Indicates if the tool is using the random mode. ## self.mIsRandom = False ## # Contains the value of mIsRandom at that time, when the latest call of # tilePositionChanged() took place. # This variable is needed to detect if the random mode was changed during # mFillOverlay being brushed at an area. ## self.mLastRandomStatus = False ## # Contains all used random cells to use in random mode. # The same cell can be in the list multiple times to make different # random weights possible. ## self.mRandomCellPicker = RandomPicker()
def __updateCurve(self): curveData = self.__d_curve.data() curveData.values().lock() numPoints = curveData.size() if ( numPoints > self.__d_paintedPoints ): doClip = not self.canvas().testAttribute( Qt.WA_PaintOnScreen ) if ( doClip ): # Depending on the platform setting a clip might be an important # performance issue. F.e. for Qt Embedded this reduces the # part of the backing store that has to be copied out - maybe # to an unaccelerated frame buffer device. xMap = self.canvasMap( self.__d_curve.xAxis() ) yMap = self.canvasMap( self.__d_curve.yAxis() ) br = Qwt.qwtBoundingRect( curveData, self.__d_paintedPoints - 1, numPoints - 1 ) clipRect = Qwt.QwtScaleMap.transform( xMap, yMap, br ).toRect() self.__d_directPainter.setClipRegion( QRegion(clipRect) ) self.__d_directPainter.drawSeries( self.__d_curve, self.__d_paintedPoints - 1, numPoints - 1 ) self.__d_paintedPoints = numPoints curveData.values().unlock()
def resizeEvent(self, event): side = min(self.width(), self.height()) maskedRegion = QRegion(self.width() / 2 - side / 2, self.height() / 2 - side / 2, side, side, QRegion.Ellipse) self.setMask(maskedRegion)
def __handleMask(self, maskType): """ Private method to calculate the handle mask. @param maskType type of the mask to be used (SnapshotRegionGrabber.FillMask or SnapshotRegionGrabber.StrokeMask) @return calculated mask (QRegion) """ mask = QRegion() for rect in self.__handles: if maskType == SnapshotRegionGrabber.StrokeMask: r = QRegion(rect) mask += r.subtracted(QRegion(rect.adjusted(1, 1, -1, -1))) else: mask += QRegion(rect.adjusted(1, 1, -1, -1)) return mask
def __init__(self, map, fileName = QString()): super().__init__() ## # The filename of a plugin is unique. So it can be used to determine # the right plugin to be used for saving or reloading the map. # The nameFilter of a plugin can not be used, since it's translatable. # The filename of a plugin must not change while maps are open using this # plugin. ## self.mReaderFormat = None self.mWriterFormat = None self.mExportFormat = None self.mSelectedArea = QRegion() self.mSelectedObjects = QList() self.mSelectedTiles = QList() self.mCurrentLayerIndex = 0 self.mLastSaved = QDateTime() self.mLastExportFileName = '' self.mFileName = fileName self.mMap = map self.mLayerModel = LayerModel(self) self.mCurrentObject = map ## Current properties object. ## self.mRenderer = None self.mMapObjectModel = MapObjectModel(self) self.mTerrainModel = TerrainModel(self, self) self.mUndoStack = QUndoStack(self) self.createRenderer() if (map.layerCount() == 0): _x = -1 else: _x = 0 self.mCurrentLayerIndex = _x self.mLayerModel.setMapDocument(self) # Forward signals emitted from the layer model self.mLayerModel.layerAdded.connect(self.onLayerAdded) self.mLayerModel.layerAboutToBeRemoved.connect(self.onLayerAboutToBeRemoved) self.mLayerModel.layerRemoved.connect(self.onLayerRemoved) self.mLayerModel.layerChanged.connect(self.layerChanged) # Forward signals emitted from the map object model self.mMapObjectModel.setMapDocument(self) self.mMapObjectModel.objectsAdded.connect(self.objectsAdded) self.mMapObjectModel.objectsChanged.connect(self.objectsChanged) self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved) self.mMapObjectModel.rowsInserted.connect(self.onMapObjectModelRowsInserted) self.mMapObjectModel.rowsRemoved.connect(self.onMapObjectModelRowsInsertedOrRemoved) self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved) self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved) self.mUndoStack.cleanChanged.connect(self.modifiedChanged) # Register tileset references tilesetManager = TilesetManager.instance() tilesetManager.addReferences(self.mMap.tilesets())
def updatePreview(self, *args): l = len(args) if l==0: ## # Updates the position of the brush item based on the mouse position. ## self.updatePreview(self.tilePosition()) elif l==1: tilePos = args[0] if not self.mapDocument(): return tileRegion = QRegion() if self.mBrushBehavior == BrushBehavior.Capture: self.mPreviewLayer = None tileRegion = self.capturedArea() elif self.mStamp.isEmpty(): self.mPreviewLayer = None tileRegion = QRect(tilePos, QSize(1, 1)) else: if self.mBrushBehavior == BrushBehavior.LineStartSet: self.drawPreviewLayer(pointsOnLine(self.mStampReference, tilePos)) elif self.mBrushBehavior == BrushBehavior.CircleMidSet: self.drawPreviewLayer(pointsOnEllipse(self.mStampReference, tilePos)) elif self.mBrushBehavior == BrushBehavior.Capture: # already handled above pass elif self.mBrushBehavior == BrushBehavior.Circle: # while finding the mid point, there is no need to show # the (maybe bigger than 1x1) stamp self.mPreviewLayer.clear() tileRegion = QRect(tilePos, QSize(1, 1)) elif self.mBrushBehavior==BrushBehavior.Line or self.mBrushBehavior==BrushBehavior.Free or self.mBrushBehavior==BrushBehavior.Paint: self.drawPreviewLayer(QVector(tilePos)) self.brushItem().setTileLayer(self.mPreviewLayer) if not tileRegion.isEmpty(): self.brushItem().setTileRegion(tileRegion)
def autoMap(self, where): # first resize the active area if (self.mAutoMappingRadius): region = QRegion() for r in where.rects(): region += r.adjusted(- self.mAutoMappingRadius, - self.mAutoMappingRadius, + self.mAutoMappingRadius, + self.mAutoMappingRadius) #where += region # delete all the relevant area, if the property "DeleteTiles" is set if (self.mDeleteTiles): setLayersRegion = self.getSetLayersRegion() for i in range(self.mLayerList.size()): translationTable = self.mLayerList.at(i) for layer in translationTable.keys(): index = self.mLayerList.at(i).value(layer) dstLayer = self.mMapWork.layerAt(index) region = setLayersRegion.intersected(where) dstTileLayer = dstLayer.asTileLayer() if (dstTileLayer): dstTileLayer.erase(region) else: self.eraseRegionObjectGroup(self.mMapDocument, dstLayer.asObjectGroup(), region) # Increase the given region where the next automapper should work. # This needs to be done, so you can rely on the order of the rules at all # locations ret = QRegion() for rect in where.rects(): for i in range(self.mRulesInput.size()): # at the moment the parallel execution does not work yet # TODO: make multithreading available! # either by dividing the rules or the region to multiple threads ret = ret.united(self.applyRule(i, rect))
def drawRect(painter, rect, outline, fill=QColor()): """ Module function to draw a rectangle with the given parameters. @param painter reference to the painter to be used (QPainter) @param rect rectangle to be drawn (QRect) @param outline color of the outline (QColor) @param fill fill color (QColor) """ clip = QRegion(rect) clip = clip.subtracted(QRegion(rect.adjusted(1, 1, -1, -1))) painter.save() painter.setClipRegion(clip) painter.setPen(Qt.NoPen) painter.setBrush(outline) painter.drawRect(rect) if fill.isValid(): painter.setClipping(False) painter.setBrush(fill) painter.drawRect(rect.adjusted(1, 1, -1, -1)) painter.restore()
def copy(self, *args): l = len(args) if l==1: region = args[0] if type(region) != QRegion: region = QRegion(region) area = region.intersected(QRect(0, 0, self.width(), self.height())) bounds = region.boundingRect() areaBounds = area.boundingRect() offsetX = max(0, areaBounds.x() - bounds.x()) offsetY = max(0, areaBounds.y() - bounds.y()) copied = TileLayer(QString(), 0, 0, bounds.width(), bounds.height()) for rect in area.rects(): for x in range(rect.left(), rect.right()+1): for y in range(rect.top(), rect.bottom()+1): copied.setCell(x - areaBounds.x() + offsetX, y - areaBounds.y() + offsetY, self.cellAt(x, y)) return copied elif l==4: x, y, width, height = args return self.copy(QRegion(x, y, width, height))
def __init__(self, mapDocument, target, x, y, source): super().__init__() self.mMapDocument = mapDocument self.mTarget = target self.mSource = source.clone() self.mX = x self.mY = y self.mPaintedRegion = QRegion(x, y, source.width(), source.height()) self.mMergeable = False self.mErased = self.mTarget.copy(self.mX - self.mTarget.x(), self.mY - self.mTarget.y(), self.mSource.width(), self.mSource.height()) self.setText(QCoreApplication.translate("Undo Commands", "Paint"))
def resizeEvent(self, event): super(RoundImageWidget, self).resizeEvent(event) width = self._imageLabel.width() height = self._imageLabel.height() radius = min(width, height) / 10 / 2 verticalRegion = QRegion(0, radius, width, height - 2 * radius) horizontalRegion = QRegion(radius, 0, width - 2 * radius, height) circle = QRegion(0, 0, 2 * radius, 2 * radius, QRegion.Ellipse) region = verticalRegion.united(horizontalRegion) region = region.united(circle) region = region.united(circle.translated(width - 2 * radius, 0)) region = region.united(circle.translated(width - 2 * radius, height - 2 * radius)) region = region.united(circle.translated(0, height - 2 * radius)) self._imageLabel.setMask(region)
def __grabRect(self): """ Private method to grab the selected rectangle (i.e. do the snapshot). """ if self.__mode == SnapshotRegionGrabber.Ellipse: ell = QRegion(self.__selection, QRegion.Ellipse) if not ell.isEmpty(): self.__grabbing = True xOffset = self.__pixmap.rect().x() - ell.boundingRect().x() yOffset = self.__pixmap.rect().y() - ell.boundingRect().y() translatedEll = ell.translated(xOffset, yOffset) pixmap2 = QPixmap(ell.boundingRect().size()) pixmap2.fill(Qt.transparent) pt = QPainter() pt.begin(pixmap2) if pt.paintEngine().hasFeature(QPaintEngine.PorterDuff): pt.setRenderHints( QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform, True) pt.setBrush(Qt.black) pt.setPen(QPen(QBrush(Qt.black), 0.5)) pt.drawEllipse(translatedEll.boundingRect()) pt.setCompositionMode(QPainter.CompositionMode_SourceIn) else: pt.setClipRegion(translatedEll) pt.setCompositionMode(QPainter.CompositionMode_Source) pt.drawPixmap(pixmap2.rect(), self.__pixmap, ell.boundingRect()) pt.end() self.grabbed.emit(pixmap2) else: r = QRect(self.__selection) if not r.isNull() and r.isValid(): self.__grabbing = True self.grabbed.emit(self.__pixmap.copy(r))
def deactivate(self, scene): super().deactivate(scene) self.mFillRegion = QRegion() self.mIsActive = False
def drawPreviewLayer(self, _list): self.mPreviewLayer = None if self.mStamp.isEmpty(): return if self.mIsRandom: if self.mRandomCellPicker.isEmpty(): return paintedRegion = QRegion() for p in _list: paintedRegion += QRect(p, QSize(1, 1)) bounds = paintedRegion.boundingRect() preview = TileLayer(QString(), bounds.x(), bounds.y(), bounds.width(), bounds.height()) for p in _list: cell = self.mRandomCellPicker.pick() preview.setCell(p.x() - bounds.left(), p.y() - bounds.top(), cell) self.mPreviewLayer = preview else: self.mMissingTilesets.clear() paintedRegion = QRegion() operations = QVector() regionCache = QHash() for p in _list: variation = self.mStamp.randomVariation() self.mapDocument().unifyTilesets(variation.map, self.mMissingTilesets) stamp = variation.tileLayer() stampRegion = QRegion() if regionCache.contains(stamp): stampRegion = regionCache.value(stamp) else: stampRegion = stamp.region() regionCache.insert(stamp, stampRegion) centered = QPoint(p.x() - int(stamp.width() / 2), p.y() - int(stamp.height() / 2)) region = stampRegion.translated(centered.x(), centered.y()) if not paintedRegion.intersects(region): paintedRegion += region op = PaintOperation(centered, stamp) operations.append(op) bounds = paintedRegion.boundingRect() preview = TileLayer(QString(), bounds.x(), bounds.y(), bounds.width(), bounds.height()) for op in operations: preview.merge(op.pos - bounds.topLeft(), op.stamp) self.mPreviewLayer = preview
def paintEvent(self, evt): """ Protected method handling paint events. @param evt paint event (QPaintEvent) """ if self.__grabbing: # grabWindow() should just get the background return painter = QPainter(self) pal = QPalette(QToolTip.palette()) font = QToolTip.font() handleColor = pal.color(QPalette.Active, QPalette.Highlight) handleColor.setAlpha(160) overlayColor = QColor(0, 0, 0, 160) textColor = pal.color(QPalette.Active, QPalette.Text) textBackgroundColor = pal.color(QPalette.Active, QPalette.Base) painter.drawPixmap(0, 0, self.__pixmap) painter.setFont(font) r = QRect(self.__selection) if not self.__selection.isNull(): grey = QRegion(self.rect()) if self.__mode == SnapshotRegionGrabber.Ellipse: reg = QRegion(r, QRegion.Ellipse) else: reg = QRegion(r) grey = grey.subtracted(reg) painter.setClipRegion(grey) painter.setPen(Qt.NoPen) painter.setBrush(overlayColor) painter.drawRect(self.rect()) painter.setClipRect(self.rect()) drawRect(painter, r, handleColor) if self.__showHelp: painter.setPen(textColor) painter.setBrush(textBackgroundColor) self.__helpTextRect = painter.boundingRect( self.rect().adjusted(2, 2, -2, -2), Qt.TextWordWrap, self.__helpText).translated( -self.__desktop.x(), -self.__desktop.y()) self.__helpTextRect.adjust(-2, -2, 4, 2) drawRect(painter, self.__helpTextRect, textColor, textBackgroundColor) painter.drawText( self.__helpTextRect.adjusted(3, 3, -3, -3), Qt.TextWordWrap, self.__helpText) if self.__selection.isNull(): return # The grabbed region is everything which is covered by the drawn # rectangles (border included). This means that there is no 0px # selection, since a 0px wide rectangle will always be drawn as a line. txt = "{0:n}, {1:n} ({2:n} x {3:n})".format( self.__selection.x(), self.__selection.y(), self.__selection.width(), self.__selection.height()) textRect = painter.boundingRect(self.rect(), Qt.AlignLeft, txt) boundingRect = textRect.adjusted(-4, 0, 0, 0) if textRect.width() < r.width() - 2 * self.__handleSize and \ textRect.height() < r.height() - 2 * self.__handleSize and \ r.width() > 100 and \ r.height() > 100: # center, unsuitable for small selections boundingRect.moveCenter(r.center()) textRect.moveCenter(r.center()) elif r.y() - 3 > textRect.height() and \ r.x() + textRect.width() < self.rect().width(): # on top, left aligned boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3)) textRect.moveBottomLeft(QPoint(r.x() + 2, r.y() - 3)) elif r.x() - 3 > textRect.width(): # left, top aligned boundingRect.moveTopRight(QPoint(r.x() - 3, r.y())) textRect.moveTopRight(QPoint(r.x() - 5, r.y())) elif r.bottom() + 3 + textRect.height() < self.rect().bottom() and \ r.right() > textRect.width(): # at bottom, right aligned boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3)) textRect.moveTopRight(QPoint(r.right() - 2, r.bottom() + 3)) elif r.right() + textRect.width() + 3 < self.rect().width(): # right, bottom aligned boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom())) textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom())) # If the above didn't catch it, you are running on a very # tiny screen... drawRect(painter, boundingRect, textColor, textBackgroundColor) painter.drawText(textRect, Qt.AlignHCenter, txt) if (r.height() > self.__handleSize * 2 and r.width() > self.__handleSize * 2) or \ not self.__mouseDown: self.__updateHandles() painter.setPen(Qt.NoPen) painter.setBrush(handleColor) painter.setClipRegion( self.__handleMask(SnapshotRegionGrabber.StrokeMask)) painter.drawRect(self.rect()) handleColor.setAlpha(60) painter.setBrush(handleColor) painter.setClipRegion( self.__handleMask(SnapshotRegionGrabber.FillMask)) painter.drawRect(self.rect())
class BrushItem(QGraphicsItem): ## # Constructor. ## def __init__(self): super().__init__() self.mMapDocument = None self.mBoundingRect = QRectF() self.mRegion = QRegion() self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption) ## # Sets the map document this brush is operating on. ## def setMapDocument(self, mapDocument): if (self.mMapDocument == mapDocument): return self.mMapDocument = mapDocument # The tiles in the stamp may no longer be valid self.clear() ## # Clears the tile layer and region set on this item. ## def clear(self): self.setTileLayer(None) ## # Sets a tile layer representing this brush. When no tile layer is set, # the brush only draws the selection color. # # The BrushItem does not take ownership over the tile layer, but makes a # personal copy of the tile layer. ## def setTileLayer(self, tileLayer): self.mTileLayer = tileLayer if (tileLayer): self.mRegion = tileLayer.region() else: self.mRegion = QRegion() self.updateBoundingRect() self.update() ## # Returns the current tile layer. ## def tileLayer(self): return self.mTileLayer ## # Changes the position of the tile layer, if one is set. ## def setTileLayerPosition(self, pos): if (not self.mTileLayer): return oldPosition = QPoint(self.mTileLayer.x(), self.mTileLayer.y()) if (oldPosition == pos): return self.mRegion.translate(pos - oldPosition) self.mTileLayer.setX(pos.x()) self.mTileLayer.setY(pos.y()) self.updateBoundingRect() ## # Sets the region of tiles that this brush item occupies. ## def setTileRegion(self, region): if type(region)!=QRegion: region = QRegion(region) if (self.mRegion == region): return self.mRegion = region self.updateBoundingRect() ## # Sets the layer offset used by the currently active layer. ## def setLayerOffset(self, offset): self.setPos(offset) ## # Returns the region of the current tile layer or the region that was set # using setTileRegion. ## def tileRegion(self): return self.mRegion # QGraphicsItem def boundingRect(self): return self.mBoundingRect def paint(self, painter, option, widget = None): insideMapHighlight = QApplication.palette().highlight().color() insideMapHighlight.setAlpha(64) outsideMapHighlight = QColor(255, 0, 0, 64) mapWidth = self.mMapDocument.map().width() mapHeight = self.mMapDocument.map().height() mapRegion = QRegion(0, 0, mapWidth, mapHeight) insideMapRegion = self.mRegion.intersected(mapRegion) outsideMapRegion = self.mRegion.subtracted(mapRegion) renderer = self.mMapDocument.renderer() if (self.mTileLayer): opacity = painter.opacity() painter.setOpacity(0.75) renderer.drawTileLayer(painter, self.mTileLayer, option.exposedRect) painter.setOpacity(opacity) renderer.drawTileSelection(painter, insideMapRegion, insideMapHighlight, option.exposedRect) renderer.drawTileSelection(painter, outsideMapRegion, outsideMapHighlight, option.exposedRect) def updateBoundingRect(self): self.prepareGeometryChange() if (not self.mMapDocument): self.mBoundingRect = QRectF() return bounds = self.mRegion.boundingRect() self.mBoundingRect = QRectF(self.mMapDocument.renderer().boundingRect(bounds)) # Adjust for amount of pixels tiles extend at the top and to the right if (self.mTileLayer): map = self.mMapDocument.map() drawMargins = self.mTileLayer.drawMargins() drawMargins.setTop(drawMargins.top() - map.tileHeight()) drawMargins.setRight(drawMargins.right() - map.tileWidth()) # Since we're also drawing a tile selection, we should not apply # negative margins self.mBoundingRect.adjust(min(0, -drawMargins.left()), min(0, -drawMargins.top()), max(0, drawMargins.right()), max(0, drawMargins.bottom()))
class BucketFillTool(AbstractTileTool): def tr(self, sourceText, disambiguation = '', n = -1): return QCoreApplication.translate('BucketFillTool', sourceText, disambiguation, n) def __init__(self, parent = None): super().__init__(self.tr("Bucket Fill Tool"), QIcon(":images/22x22/stock-tool-bucket-fill.png"), QKeySequence(self.tr("F")), parent) self.mStamp = TileStamp() self.mFillOverlay = None self.mFillRegion = QRegion() self.mMissingTilesets = QVector() self.mIsActive = False self.mLastShiftStatus = False ## # Indicates if the tool is using the random mode. ## self.mIsRandom = False ## # Contains the value of mIsRandom at that time, when the latest call of # tilePositionChanged() took place. # This variable is needed to detect if the random mode was changed during # mFillOverlay being brushed at an area. ## self.mLastRandomStatus = False ## # Contains all used random cells to use in random mode. # The same cell can be in the list multiple times to make different # random weights possible. ## self.mRandomCellPicker = RandomPicker() def __del__(self): pass def activate(self, scene): super().activate(scene) self.mIsActive = True self.tilePositionChanged(self.tilePosition()) def deactivate(self, scene): super().deactivate(scene) self.mFillRegion = QRegion() self.mIsActive = False def mousePressed(self, event): if (event.button() != Qt.LeftButton or self.mFillRegion.isEmpty()): return if (not self.brushItem().isVisible()): return preview = self.mFillOverlay if not preview: return paint = PaintTileLayer(self.mapDocument(), self.currentTileLayer(), preview.x(), preview.y(), preview) paint.setText(QCoreApplication.translate("Undo Commands", "Fill Area")) if not self.mMissingTilesets.isEmpty(): for tileset in self.mMissingTilesets: AddTileset(self.mapDocument(), tileset, paint) self.mMissingTilesets.clear() fillRegion = QRegion(self.mFillRegion) self.mapDocument().undoStack().push(paint) self.mapDocument().emitRegionEdited(fillRegion, self.currentTileLayer()) def mouseReleased(self, event): pass def modifiersChanged(self, modifiers): # Don't need to recalculate fill region if there was no fill region if (not self.mFillOverlay): return self.tilePositionChanged(self.tilePosition()) def languageChanged(self): self.setName(self.tr("Bucket Fill Tool")) self.setShortcut(QKeySequence(self.tr("F"))) ## # Sets the stamp that is drawn when filling. The BucketFillTool takes # ownership over the stamp layer. ## def setStamp(self, stamp): # Clear any overlay that we presently have with an old stamp self.clearOverlay() self.mStamp = stamp self.updateRandomListAndMissingTilesets() if (self.mIsActive and self.brushItem().isVisible()): self.tilePositionChanged(self.tilePosition()) ## # This returns the actual tile layer which is used to define the current # state. ## def stamp(self): return TileStamp(self.mStamp) def setRandom(self, value): if (self.mIsRandom == value): return self.mIsRandom = value self.updateRandomListAndMissingTilesets() # Don't need to recalculate fill region if there was no fill region if (not self.mFillOverlay): return self.tilePositionChanged(self.tilePosition()) def tilePositionChanged(self, tilePos): # Skip filling if the stamp is empty if self.mStamp.isEmpty(): return # Make sure that a tile layer is selected tileLayer = self.currentTileLayer() if (not tileLayer): return shiftPressed = QApplication.keyboardModifiers() & Qt.ShiftModifier fillRegionChanged = False regionComputer = TilePainter(self.mapDocument(), tileLayer) # If the stamp is a single tile, ignore it when making the region if (not shiftPressed and self.mStamp.variations().size() == 1): variation = self.mStamp.variations().first() stampLayer = variation.tileLayer() if (stampLayer.size() == QSize(1, 1) and stampLayer.cellAt(0, 0) == regionComputer.cellAt(tilePos)): return # This clears the connections so we don't get callbacks self.clearConnections(self.mapDocument()) # Optimization: we don't need to recalculate the fill area # if the new mouse position is still over the filled region # and the shift modifier hasn't changed. if (not self.mFillRegion.contains(tilePos) or shiftPressed != self.mLastShiftStatus): # Clear overlay to make way for a new one self.clearOverlay() # Cache information about how the fill region was created self.mLastShiftStatus = shiftPressed # Get the new fill region if (not shiftPressed): # If not holding shift, a region is generated from the current pos self.mFillRegion = regionComputer.computePaintableFillRegion(tilePos) else: # If holding shift, the region is the selection bounds self.mFillRegion = self.mapDocument().selectedArea() # Fill region is the whole map if there is no selection if (self.mFillRegion.isEmpty()): self.mFillRegion = tileLayer.bounds() # The mouse needs to be in the region if (not self.mFillRegion.contains(tilePos)): self.mFillRegion = QRegion() fillRegionChanged = True # Ensure that a fill region was created before making an overlay layer if (self.mFillRegion.isEmpty()): return if (self.mLastRandomStatus != self.mIsRandom): self.mLastRandomStatus = self.mIsRandom fillRegionChanged = True if (not self.mFillOverlay): # Create a new overlay region fillBounds = self.mFillRegion.boundingRect() self.mFillOverlay = TileLayer(QString(), fillBounds.x(), fillBounds.y(), fillBounds.width(), fillBounds.height()) # Paint the new overlay if (not self.mIsRandom): if (fillRegionChanged or self.mStamp.variations().size() > 1): fillWithStamp(self.mFillOverlay, self.mStamp, self.mFillRegion.translated(-self.mFillOverlay.position())) fillRegionChanged = True else: self.randomFill(self.mFillOverlay, self.mFillRegion) fillRegionChanged = True if (fillRegionChanged): # Update the brush item to draw the overlay self.brushItem().setTileLayer(self.mFillOverlay) # Create connections to know when the overlay should be cleared self.makeConnections() def mapDocumentChanged(self, oldDocument, newDocument): super().mapDocumentChanged(oldDocument, newDocument) self.clearConnections(oldDocument) # Reset things that are probably invalid now if newDocument: self.updateRandomListAndMissingTilesets() self.clearOverlay() def clearOverlay(self): # Clear connections before clearing overlay so there is no # risk of getting a callback and causing an infinite loop self.clearConnections(self.mapDocument()) self.brushItem().clear() self.mFillOverlay = None self.mFillRegion = QRegion() def makeConnections(self): if (not self.mapDocument()): return # Overlay may need to be cleared if a region changed self.mapDocument().regionChanged.connect(self.clearOverlay) # Overlay needs to be cleared if we switch to another layer self.mapDocument().currentLayerIndexChanged.connect(self.clearOverlay) # Overlay needs be cleared if the selection changes, since # the overlay may be bound or may need to be bound to the selection self.mapDocument().selectedAreaChanged.connect(self.clearOverlay) def clearConnections(self, mapDocument): if (not mapDocument): return try: mapDocument.regionChanged.disconnect(self.clearOverlay) mapDocument.currentLayerIndexChanged.disconnect(self.clearOverlay) mapDocument.selectedAreaChanged.disconnect(self.clearOverlay) except: pass ## # Updates the list of random cells. # This is done by taking all non-null tiles from the original stamp mStamp. ## def updateRandomListAndMissingTilesets(self): self.mRandomCellPicker.clear() self.mMissingTilesets.clear() for variation in self.mStamp.variations(): self.mapDocument().unifyTilesets(variation.map, self.mMissingTilesets) if self.mIsRandom: for cell in variation.tileLayer(): if not cell.isEmpty(): self.mRandomCellPicker.add(cell, cell.tile.probability()) ## # Fills the given \a region in the given \a tileLayer with random tiles. ## def randomFill(self, tileLayer, region): if (region.isEmpty() or self.mRandomList.empty()): return for rect in region.translated(-tileLayer.position()).rects(): for _x in range(rect.left(), rect.right()+1): for _y in range(rect.top(), rect.bottom()+1): tileLayer.setCell(_x, _y, self.mRandomCellPicker.pick())
def applyRule(self, ruleIndex, where): ret = QRect() if (self.mLayerList.isEmpty()): return ret ruleInput = self.mRulesInput.at(ruleIndex) ruleOutput = self.mRulesOutput.at(ruleIndex) rbr = ruleInput.boundingRect() # Since the rule itself is translated, we need to adjust the borders of the # loops. Decrease the size at all sides by one: There must be at least one # tile overlap to the rule. minX = where.left() - rbr.left() - rbr.width() + 1 minY = where.top() - rbr.top() - rbr.height() + 1 maxX = where.right() - rbr.left() + rbr.width() - 1 maxY = where.bottom() - rbr.top() + rbr.height() - 1 # In this list of regions it is stored which parts or the map have already # been altered by exactly this rule. We store all the altered parts to # make sure there are no overlaps of the same rule applied to # (neighbouring) places appliedRegions = QList() if (self.mNoOverlappingRules): for i in range(self.mMapWork.layerCount()): appliedRegions.append(QRegion()) for y in range(minY, maxY+1): for x in range(minX, maxX+1): anymatch = False for index in self.mInputRules.indexes: ii = self.mInputRules[index] allLayerNamesMatch = True for name in ii.names: i = self.mMapWork.indexOfLayer(name, Layer.TileLayerType) if (i == -1): allLayerNamesMatch = False else: setLayer = self.mMapWork.layerAt(i).asTileLayer() allLayerNamesMatch &= compareLayerTo(setLayer, ii[name].listYes, ii[name].listNo, ruleInput, QPoint(x, y)) if (allLayerNamesMatch): anymatch = True break if (anymatch): r = 0 # choose by chance which group of rule_layers should be used: if (self.mLayerList.size() > 1): r = qrand() % self.mLayerList.size() if (not self.mNoOverlappingRules): self.copyMapRegion(ruleOutput, QPoint(x, y), self.mLayerList.at(r)) ret = ret.united(rbr.translated(QPoint(x, y))) continue missmatch = False translationTable = self.mLayerList.at(r) layers = translationTable.keys() # check if there are no overlaps within this rule. ruleRegionInLayer = QVector() for i in range(layers.size()): layer = layers.at(i) appliedPlace = QRegion() tileLayer = layer.asTileLayer() if (tileLayer): appliedPlace = tileLayer.region() else: appliedPlace = tileRegionOfObjectGroup(layer.asObjectGroup()) ruleRegionInLayer.append(appliedPlace.intersected(ruleOutput)) if (appliedRegions.at(i).intersects( ruleRegionInLayer[i].translated(x, y))): missmatch = True break if (missmatch): continue self.copyMapRegion(ruleOutput, QPoint(x, y), self.mLayerList.at(r)) ret = ret.united(rbr.translated(QPoint(x, y))) for i in range(translationTable.size()): appliedRegions[i] += ruleRegionInLayer[i].translated(x, y) return ret
def tilePositionChanged(self, tilePos): # Skip filling if the stamp is empty if self.mStamp.isEmpty(): return # Make sure that a tile layer is selected tileLayer = self.currentTileLayer() if (not tileLayer): return shiftPressed = QApplication.keyboardModifiers() & Qt.ShiftModifier fillRegionChanged = False regionComputer = TilePainter(self.mapDocument(), tileLayer) # If the stamp is a single tile, ignore it when making the region if (not shiftPressed and self.mStamp.variations().size() == 1): variation = self.mStamp.variations().first() stampLayer = variation.tileLayer() if (stampLayer.size() == QSize(1, 1) and stampLayer.cellAt(0, 0) == regionComputer.cellAt(tilePos)): return # This clears the connections so we don't get callbacks self.clearConnections(self.mapDocument()) # Optimization: we don't need to recalculate the fill area # if the new mouse position is still over the filled region # and the shift modifier hasn't changed. if (not self.mFillRegion.contains(tilePos) or shiftPressed != self.mLastShiftStatus): # Clear overlay to make way for a new one self.clearOverlay() # Cache information about how the fill region was created self.mLastShiftStatus = shiftPressed # Get the new fill region if (not shiftPressed): # If not holding shift, a region is generated from the current pos self.mFillRegion = regionComputer.computePaintableFillRegion(tilePos) else: # If holding shift, the region is the selection bounds self.mFillRegion = self.mapDocument().selectedArea() # Fill region is the whole map if there is no selection if (self.mFillRegion.isEmpty()): self.mFillRegion = tileLayer.bounds() # The mouse needs to be in the region if (not self.mFillRegion.contains(tilePos)): self.mFillRegion = QRegion() fillRegionChanged = True # Ensure that a fill region was created before making an overlay layer if (self.mFillRegion.isEmpty()): return if (self.mLastRandomStatus != self.mIsRandom): self.mLastRandomStatus = self.mIsRandom fillRegionChanged = True if (not self.mFillOverlay): # Create a new overlay region fillBounds = self.mFillRegion.boundingRect() self.mFillOverlay = TileLayer(QString(), fillBounds.x(), fillBounds.y(), fillBounds.width(), fillBounds.height()) # Paint the new overlay if (not self.mIsRandom): if (fillRegionChanged or self.mStamp.variations().size() > 1): fillWithStamp(self.mFillOverlay, self.mStamp, self.mFillRegion.translated(-self.mFillOverlay.position())) fillRegionChanged = True else: self.randomFill(self.mFillOverlay, self.mFillRegion) fillRegionChanged = True if (fillRegionChanged): # Update the brush item to draw the overlay self.brushItem().setTileLayer(self.mFillOverlay) # Create connections to know when the overlay should be cleared self.makeConnections()
class PaintTileLayer(QUndoCommand): ## # Constructor. # # @param mapDocument the map document that's being edited # @param target the target layer to paint on # @param x the x position of the paint location # @param y the y position of the paint location # @param source the source layer to paint on the target layer ## def __init__(self, mapDocument, target, x, y, source): super().__init__() self.mMapDocument = mapDocument self.mTarget = target self.mSource = source.clone() self.mX = x self.mY = y self.mPaintedRegion = QRegion(x, y, source.width(), source.height()) self.mMergeable = False self.mErased = self.mTarget.copy(self.mX - self.mTarget.x(), self.mY - self.mTarget.y(), self.mSource.width(), self.mSource.height()) self.setText(QCoreApplication.translate("Undo Commands", "Paint")) def __del__(self): del self.mSource del self.mErased ## # Sets whether this undo command can be merged with an existing command. ## def setMergeable(self, mergeable): self.mMergeable = mergeable def undo(self): painter = TilePainter(self.mMapDocument, self.mTarget) painter.setCells(self.mX, self.mY, self.mErased, self.mPaintedRegion) super().undo() # undo child commands def redo(self): super().redo() # redo child commands painter = TilePainter(self.mMapDocument, self.mTarget) painter.drawCells(self.mX, self.mY, self.mSource) def id(self): return UndoCommands.Cmd_PaintTileLayer def mergeWith(self, other): o = other if (not (self.mMapDocument == o.mMapDocument and self.mTarget == o.mTarget and o.mMergeable)): return False newRegion = o.mPaintedRegion.subtracted(self.mPaintedRegion) combinedRegion = self.mPaintedRegion.united(o.mPaintedRegion) bounds = QRect(self.mX, self.mY, self.mSource.width(), self.mSource.height()) combinedBounds = combinedRegion.boundingRect() # Resize the erased tiles and source layers when necessary if (bounds != combinedBounds): shift = bounds.topLeft() - combinedBounds.topLeft() self.mErased.resize(combinedBounds.size(), shift) self.mSource.resize(combinedBounds.size(), shift) mX = combinedBounds.left() self.mY = combinedBounds.top() self.mPaintedRegion = combinedRegion # Copy the painted tiles from the other command over pos = QPoint(o.mX, o.mY) - combinedBounds.topLeft() self.mSource.merge(pos, o.mSource) # Copy the newly erased tiles from the other command over for rect in newRegion.rects(): for y in range(rect.top(), rect.bottom()+1): for x in range(rect.left(), rect.right()+1): self.mErased.setCell(x - mX, y - self.mY, o.mErased.cellAt(x - o.mX, y - o.mY)) return True
class MapDocument(QObject): fileNameChanged = pyqtSignal(str, str) modifiedChanged = pyqtSignal() saved = pyqtSignal() ## # Emitted when the selected tile region changes. Sends the currently # selected region and the previously selected region. ## selectedAreaChanged = pyqtSignal(QRegion, QRegion) ## # Emitted when the list of selected objects changes. ## selectedObjectsChanged = pyqtSignal() ## # Emitted when the list of selected tiles from the dock changes. ## selectedTilesChanged = pyqtSignal() currentObjectChanged = pyqtSignal(list) ## # Emitted when the map size or its tile size changes. ## mapChanged = pyqtSignal() layerAdded = pyqtSignal(int) layerAboutToBeRemoved = pyqtSignal(int) layerRenamed = pyqtSignal(int) layerRemoved = pyqtSignal(int) layerChanged = pyqtSignal(int) ## # Emitted after a new layer was added and the name should be edited. # Applies to the current layer. ## editLayerNameRequested = pyqtSignal() editCurrentObject = pyqtSignal() ## # Emitted when the current layer index changes. ## currentLayerIndexChanged = pyqtSignal(int) ## # Emitted when a certain region of the map changes. The region is given in # tile coordinates. ## regionChanged = pyqtSignal(QRegion, Layer) ## # Emitted when a certain region of the map was edited by user input. # The region is given in tile coordinates. # If multiple layers have been edited, multiple signals will be emitted. ## regionEdited = pyqtSignal(QRegion, Layer) tileLayerDrawMarginsChanged = pyqtSignal(TileLayer) tileTerrainChanged = pyqtSignal(QList) tileProbabilityChanged = pyqtSignal(Tile) tileObjectGroupChanged = pyqtSignal(Tile) tileAnimationChanged = pyqtSignal(Tile) objectGroupChanged = pyqtSignal(ObjectGroup) imageLayerChanged = pyqtSignal(ImageLayer) tilesetAboutToBeAdded = pyqtSignal(int) tilesetAdded = pyqtSignal(int, Tileset) tilesetAboutToBeRemoved = pyqtSignal(int) tilesetRemoved = pyqtSignal(Tileset) tilesetMoved = pyqtSignal(int, int) tilesetFileNameChanged = pyqtSignal(Tileset) tilesetNameChanged = pyqtSignal(Tileset) tilesetTileOffsetChanged = pyqtSignal(Tileset) tilesetChanged = pyqtSignal(Tileset) objectsAdded = pyqtSignal(QList) objectsInserted = pyqtSignal(ObjectGroup, int, int) objectsRemoved = pyqtSignal(QList) objectsChanged = pyqtSignal(QList) objectsIndexChanged = pyqtSignal(ObjectGroup, int, int) propertyAdded = pyqtSignal(Object, str) propertyRemoved = pyqtSignal(Object, str) propertyChanged = pyqtSignal(Object, str) propertiesChanged = pyqtSignal(Object) ## # Constructs a map document around the given map. The map document takes # ownership of the map. ## def __init__(self, map, fileName = QString()): super().__init__() ## # The filename of a plugin is unique. So it can be used to determine # the right plugin to be used for saving or reloading the map. # The nameFilter of a plugin can not be used, since it's translatable. # The filename of a plugin must not change while maps are open using this # plugin. ## self.mReaderFormat = None self.mWriterFormat = None self.mExportFormat = None self.mSelectedArea = QRegion() self.mSelectedObjects = QList() self.mSelectedTiles = QList() self.mCurrentLayerIndex = 0 self.mLastSaved = QDateTime() self.mLastExportFileName = '' self.mFileName = fileName self.mMap = map self.mLayerModel = LayerModel(self) self.mCurrentObject = map ## Current properties object. ## self.mRenderer = None self.mMapObjectModel = MapObjectModel(self) self.mTerrainModel = TerrainModel(self, self) self.mUndoStack = QUndoStack(self) self.createRenderer() if (map.layerCount() == 0): _x = -1 else: _x = 0 self.mCurrentLayerIndex = _x self.mLayerModel.setMapDocument(self) # Forward signals emitted from the layer model self.mLayerModel.layerAdded.connect(self.onLayerAdded) self.mLayerModel.layerAboutToBeRemoved.connect(self.onLayerAboutToBeRemoved) self.mLayerModel.layerRemoved.connect(self.onLayerRemoved) self.mLayerModel.layerChanged.connect(self.layerChanged) # Forward signals emitted from the map object model self.mMapObjectModel.setMapDocument(self) self.mMapObjectModel.objectsAdded.connect(self.objectsAdded) self.mMapObjectModel.objectsChanged.connect(self.objectsChanged) self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved) self.mMapObjectModel.rowsInserted.connect(self.onMapObjectModelRowsInserted) self.mMapObjectModel.rowsRemoved.connect(self.onMapObjectModelRowsInsertedOrRemoved) self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved) self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved) self.mUndoStack.cleanChanged.connect(self.modifiedChanged) # Register tileset references tilesetManager = TilesetManager.instance() tilesetManager.addReferences(self.mMap.tilesets()) ## # Destructor. ## def __del__(self): # Unregister tileset references tilesetManager = TilesetManager.instance() tilesetManager.removeReferences(self.mMap.tilesets()) del self.mRenderer del self.mMap ## # Saves the map to its current file name. Returns whether or not the file # was saved successfully. If not, <i>error</i> will be set to the error # message if it is not 0. ## def save(self, *args): l = len(args) if l==0: args = ('') if l==1: arg = args[0] file = QFileInfo(arg) if not file.isFile(): fileName = self.fileName() error = args[0] else: fileName = arg error = '' return self.save(fileName, error) if l==2: ## # Saves the map to the file at \a fileName. Returns whether or not the # file was saved successfully. If not, <i>error</i> will be set to the # error message if it is not 0. # # If the save was successful, the file name of this document will be set # to \a fileName. # # The map format will be the same as this map was opened with. ## fileName, error = args mapFormat = self.mWriterFormat tmxMapFormat = TmxMapFormat() if (not mapFormat): mapFormat = tmxMapFormat if (not mapFormat.write(self.map(), fileName)): if (error): error = mapFormat.errorString() return False self.undoStack().setClean() self.setFileName(fileName) self.mLastSaved = QFileInfo(fileName).lastModified() self.saved.emit() return True ## # Loads a map and returns a MapDocument instance on success. Returns 0 # on error and sets the \a error message. ## def load(fileName, mapFormat = None): error = '' tmxMapFormat = TmxMapFormat() if (not mapFormat and not tmxMapFormat.supportsFile(fileName)): # Try to find a plugin that implements support for this format formats = PluginManager.objects() for format in formats: if (format.supportsFile(fileName)): mapFormat = format break map = None errorString = '' if mapFormat: map = mapFormat.read(fileName) errorString = mapFormat.errorString() else: map = tmxMapFormat.read(fileName) errorString = tmxMapFormat.errorString() if (not map): error = errorString return None, error mapDocument = MapDocument(map, fileName) if mapFormat: mapDocument.setReaderFormat(mapFormat) if mapFormat.hasCapabilities(MapFormat.Write): mapDocument.setWriterFormat(mapFormat) return mapDocument, error def fileName(self): return self.mFileName def lastExportFileName(self): return self.mLastExportFileName def setLastExportFileName(self, fileName): self.mLastExportFileName = fileName def readerFormat(self): return self.mReaderFormat def setReaderFormat(self, format): self.mReaderFormat = format def writerFormat(self): return self.mWriterFormat def setWriterFormat(self, format): self.mWriterFormat = format def exportFormat(self): return self.mExportFormat def setExportFormat(self, format): self.mExportFormat = format ## # Returns the name with which to display this map. It is the file name without # its path, or 'untitled.tmx' when the map has no file name. ## def displayName(self): displayName = QFileInfo(self.mFileName).fileName() if len(displayName)==0: displayName = self.tr("untitled.tmx") return displayName ## # Returns whether the map has unsaved changes. ## def isModified(self): return not self.mUndoStack.isClean() def lastSaved(self): return self.mLastSaved ## # Returns the map instance. Be aware that directly modifying the map will # not allow the GUI to update itself appropriately. ## def map(self): return self.mMap ## # Sets the current layer to the given index. ## def setCurrentLayerIndex(self, index): changed = self.mCurrentLayerIndex != index self.mCurrentLayerIndex = index ## This function always sends the following signal, even if the index # didn't actually change. This is because the selected index in the layer # table view might be out of date anyway, and would otherwise not be # properly updated. # # This problem happens due to the selection model not sending signals # about changes to its current index when it is due to insertion/removal # of other items. The selected item doesn't change in that case, but our # layer index does. ## self.currentLayerIndexChanged.emit(self.mCurrentLayerIndex) if (changed and self.mCurrentLayerIndex != -1): self.setCurrentObject(self.currentLayer()) ## # Returns the index of the currently selected layer. Returns -1 if no # layer is currently selected. ## def currentLayerIndex(self): return self.mCurrentLayerIndex ## # Returns the currently selected layer, or 0 if no layer is currently # selected. ## def currentLayer(self): if (self.mCurrentLayerIndex == -1): return None return self.mMap.layerAt(self.mCurrentLayerIndex) ## # Resize this map to the given \a size, while at the same time shifting # the contents by \a offset. ## def resizeMap(self, size, offset): movedSelection = self.mSelectedArea.translated(offset) newArea = QRect(-offset, size) visibleArea = self.mRenderer.boundingRect(newArea) origin = self.mRenderer.tileToPixelCoords_(QPointF()) newOrigin = self.mRenderer.tileToPixelCoords_(-offset) pixelOffset = origin - newOrigin # Resize the map and each layer self.mUndoStack.beginMacro(self.tr("Resize Map")) for i in range(self.mMap.layerCount()): layer = self.mMap.layerAt(i) x = layer.layerType() if x==Layer.TileLayerType: tileLayer = layer self.mUndoStack.push(ResizeTileLayer(self, tileLayer, size, offset)) elif x==Layer.ObjectGroupType: objectGroup = layer # Remove objects that will fall outside of the map for o in objectGroup.objects(): if (not visibleIn(visibleArea, o, self.mRenderer)): self.mUndoStack.push(RemoveMapObject(self, o)) else: oldPos = o.position() newPos = oldPos + pixelOffset self.mUndoStack.push(MoveMapObject(self, newPos, oldPos)) elif x==Layer.ImageLayerType: # Currently not adjusted when resizing the map break self.mUndoStack.push(ResizeMap(self, size)) self.mUndoStack.push(ChangeSelectedArea(self, movedSelection)) self.mUndoStack.endMacro() # TODO: Handle layers that don't match the map size correctly ## # Offsets the layers at \a layerIndexes by \a offset, within \a bounds, # and optionally wraps on the X or Y axis. ## def offsetMap(self, layerIndexes, offset, bounds, wrapX, wrapY): if (layerIndexes.empty()): return if (layerIndexes.size() == 1): self.mUndoStack.push(OffsetLayer(self, layerIndexes.first(), offset, bounds, wrapX, wrapY)) else: self.mUndoStack.beginMacro(self.tr("Offset Map")) for layerIndex in layerIndexes: self.mUndoStack.push(OffsetLayer(self, layerIndex, offset, bounds, wrapX, wrapY)) self.mUndoStack.endMacro() ## # Flips the selected objects in the given \a direction. ## def flipSelectedObjects(self, direction): if (self.mSelectedObjects.isEmpty()): return self.mUndoStack.push(FlipMapObjects(self, self.mSelectedObjects, direction)) ## # Rotates the selected objects. ## def rotateSelectedObjects(self, direction): if (self.mSelectedObjects.isEmpty()): return self.mUndoStack.beginMacro(self.tr("Rotate %n Object(s)", "", self.mSelectedObjects.size())) # TODO: Rotate them properly as a group for mapObject in self.mSelectedObjects: oldRotation = mapObject.rotation() newRotation = oldRotation if (direction == RotateDirection.RotateLeft): newRotation -= 90 if (newRotation < -180): newRotation += 360 else: newRotation += 90 if (newRotation > 180): newRotation -= 360 self.mUndoStack.push(RotateMapObject(self, mapObject, newRotation, oldRotation)) self.mUndoStack.endMacro() ## # Adds a layer of the given type to the top of the layer stack. After adding # the new layer, emits editLayerNameRequested(). ## def addLayer(self, layerType): layer = None name = QString() x = layerType if x==Layer.TileLayerType: name = self.tr("Tile Layer %d"%(self.mMap.tileLayerCount() + 1)) layer = TileLayer(name, 0, 0, self.mMap.width(), self.mMap.height()) elif x==Layer.ObjectGroupType: name = self.tr("Object Layer %d"%(self.mMap.objectGroupCount() + 1)) layer = ObjectGroup(name, 0, 0, self.mMap.width(), self.mMap.height()) elif x==Layer.ImageLayerType: name = self.tr("Image Layer %d"%(self.mMap.imageLayerCount() + 1)) layer = ImageLayer(name, 0, 0, self.mMap.width(), self.mMap.height()) index = self.mMap.layerCount() self.mUndoStack.push(AddLayer(self, index, layer)) self.setCurrentLayerIndex(index) self.editLayerNameRequested.emit() ## # Duplicates the currently selected layer. ## def duplicateLayer(self): if (self.mCurrentLayerIndex == -1): return duplicate = self.mMap.layerAt(self.mCurrentLayerIndex).clone() duplicate.setName(self.tr("Copy of %s"%duplicate.name())) index = self.mCurrentLayerIndex + 1 cmd = AddLayer(self, index, duplicate) cmd.setText(self.tr("Duplicate Layer")) self.mUndoStack.push(cmd) self.setCurrentLayerIndex(index) ## # Merges the currently selected layer with the layer below. This only works # when the layers can be merged. # # \see Layer.canMergeWith ## def mergeLayerDown(self): if (self.mCurrentLayerIndex < 1): return upperLayer = self.mMap.layerAt(self.mCurrentLayerIndex) lowerLayer = self.mMap.layerAt(self.mCurrentLayerIndex - 1) if (not lowerLayer.canMergeWith(upperLayer)): return merged = lowerLayer.mergedWith(upperLayer) self.mUndoStack.beginMacro(self.tr("Merge Layer Down")) self.mUndoStack.push(AddLayer(self, self.mCurrentLayerIndex - 1, merged)) self.mUndoStack.push(RemoveLayer(self, self.mCurrentLayerIndex)) self.mUndoStack.push(RemoveLayer(self, self.mCurrentLayerIndex)) self.mUndoStack.endMacro() ## # Moves the given layer up. Does nothing when no valid layer index is # given. ## def moveLayerUp(self, index): if index<0 or index>=self.mMap.layerCount() - 1: return self.mUndoStack.push(MoveLayer(self, index, MoveLayer.Up)) ## # Moves the given layer down. Does nothing when no valid layer index is # given. ## def moveLayerDown(self, index): if index<1 or index>=self.mMap.layerCount(): return self.mUndoStack.push(MoveLayer(self, index, MoveLayer.Down)) ## # Removes the given layer. ## def removeLayer(self, index): if index<0 or index>=self.mMap.layerCount(): return self.mUndoStack.push(RemoveLayer(self, index)) ## # Show or hide all other layers except the layer at the given index. # If any other layer is visible then all layers will be hidden, otherwise # the layers will be shown. ## def toggleOtherLayers(self, index): self.mLayerModel.toggleOtherLayers(index) ## # Adds a tileset to this map at the given \a index. Emits the appropriate # signal. ## def insertTileset(self, index, tileset): self.tilesetAboutToBeAdded.emit(index) self.mMap.insertTileset(index, tileset) tilesetManager = TilesetManager.instance() tilesetManager.addReference(tileset) self.tilesetAdded.emit(index, tileset) ## # Removes the tileset at the given \a index from this map. Emits the # appropriate signal. # # \warning Does not make sure that any references to tiles in the removed # tileset are cleared. ## def removeTilesetAt(self, index): self.tilesetAboutToBeRemoved.emit(index) tileset = self.mMap.tilesets().at(index) if (tileset == self.mCurrentObject or isFromTileset(self.mCurrentObject, tileset)): self.setCurrentObject(None) self.mMap.removeTilesetAt(index) self.tilesetRemoved.emit(tileset) tilesetManager = TilesetManager.instance() tilesetManager.removeReference(tileset) def moveTileset(self, _from, to): if (_from == to): return tileset = self.mMap.tilesets().at(_from) self.mMap.removeTilesetAt(_from) self.mMap.insertTileset(to, tileset) self.tilesetMoved.emit(_from, to) def setTilesetFileName(self, tileset, fileName): tileset.setFileName(fileName) self.tilesetFileNameChanged.emit(tileset) def setTilesetName(self, tileset, name): tileset.setName(name) self.tilesetNameChanged.emit(tileset) def setTilesetTileOffset(self, tileset, tileOffset): tileset.setTileOffset(tileOffset) self.mMap.recomputeDrawMargins() self.tilesetTileOffsetChanged.emit(tileset) def duplicateObjects(self, objects): if (objects.isEmpty()): return self.mUndoStack.beginMacro(self.tr("Duplicate %n Object(s)", "", objects.size())) clones = QList() for mapObject in objects: clone = mapObject.clone() clones.append(clone) self.mUndoStack.push(AddMapObject(self, mapObject.objectGroup(), clone)) self.mUndoStack.endMacro() self.setSelectedObjects(clones) def removeObjects(self, objects): if (objects.isEmpty()): return self.mUndoStack.beginMacro(self.tr("Remove %n Object(s)", "", objects.size())) for mapObject in objects: self.mUndoStack.push(RemoveMapObject(self, mapObject)) self.mUndoStack.endMacro() def moveObjectsToGroup(self, objects, objectGroup): if (objects.isEmpty()): return self.mUndoStack.beginMacro(self.tr("Move %n Object(s) to Layer", "", objects.size())) for mapObject in objects: if (mapObject.objectGroup() == objectGroup): continue self.mUndoStack.push(MoveMapObjectToGroup(self, mapObject, objectGroup)) self.mUndoStack.endMacro() def setProperty(self, object, name, value): hadProperty = object.hasProperty(name) object.setProperty(name, value) if (hadProperty): self.propertyChanged.emit(object, name) else: self.propertyAdded.emit(object, name) def setProperties(self, object, properties): object.setProperties(properties) self.propertiesChanged.emit(object) def removeProperty(self, object, name): object.removeProperty(name) self.propertyRemoved.emit(object, name) ## # Returns the layer model. Can be used to modify the layer stack of the # map, and to display the layer stack in a view. ## def layerModel(self): return self.mLayerModel def mapObjectModel(self): return self.mMapObjectModel def terrainModel(self): return self.mTerrainModel ## # Returns the map renderer. ## def renderer(self): return self.mRenderer ## # Creates the map renderer. Should be called after changing the map # orientation. ## def createRenderer(self): if (self.mRenderer): del self.mRenderer x = self.mMap.orientation() if x==Map.Orientation.Isometric: self.mRenderer = IsometricRenderer(self.mMap) elif x==Map.Orientation.Staggered: self.mRenderer = StaggeredRenderer(self.mMap) elif x==Map.Orientation.Hexagonal: self.mRenderer = HexagonalRenderer(self.mMap) else: self.mRenderer = OrthogonalRenderer(self.mMap) ## # Returns the undo stack of this map document. Should be used to push any # commands on that modify the map. ## def undoStack(self): return self.mUndoStack ## # Returns the selected area of tiles. ## def selectedArea(self): return QRegion(self.mSelectedArea) ## # Sets the selected area of tiles. ## def setSelectedArea(self, selection): if (self.mSelectedArea != selection): oldSelectedArea = self.mSelectedArea self.mSelectedArea = selection self.selectedAreaChanged.emit(self.mSelectedArea, oldSelectedArea) ## # Returns the list of selected objects. ## def selectedObjects(self): return self.mSelectedObjects ## # Sets the list of selected objects, emitting the selectedObjectsChanged # signal. ## def setSelectedObjects(self, selectedObjects): if selectedObjects.nequal(self.mSelectedObjects): self.mSelectedObjects = selectedObjects self.selectedObjectsChanged.emit() if (selectedObjects.size() == 1): self.setCurrentObject(selectedObjects.first()) ## # Returns the list of selected tiles. ## def selectedTiles(self): return self.mSelectedTiles def setSelectedTiles(self, selectedTiles): self.mSelectedTiles = selectedTiles self.selectedTilesChanged.emit() def currentObject(self): return self.mCurrentObject def setCurrentObject(self, object): if (object == self.mCurrentObject): return self.mCurrentObject = object self.currentObjectChanged.emit([object]) def currentObjects(self): objects = QList() if (self.mCurrentObject): if (self.mCurrentObject.typeId() == Object.MapObjectType and not self.mSelectedObjects.isEmpty()): for mapObj in self.mSelectedObjects: objects.append(mapObj) elif (self.mCurrentObject.typeId() == Object.TileType and not self.mSelectedTiles.isEmpty()): for tile in self.mSelectedTiles: objects.append(tile) else: objects.append(self.mCurrentObject) return objects def unifyTilesets(self, *args): l = len(args) if l==1: ## # Makes sure the all tilesets which are used at the given \a map will be # present in the map document. # # To reach the aim, all similar tilesets will be replaced by the version # in the current map document and all missing tilesets will be added to # the current map document. # # \warning This method assumes that the tilesets in \a map are managed by # the TilesetManager! ## map = args[0] undoCommands = QList() existingTilesets = self.mMap.tilesets() tilesetManager = TilesetManager.instance() # Add tilesets that are not yet part of this map for tileset in map.tilesets(): if (existingTilesets.contains(tileset)): continue replacement = tileset.findSimilarTileset(existingTilesets) if (not replacement): undoCommands.append(AddTileset(self, tileset)) continue # Merge the tile properties sharedTileCount = min(tileset.tileCount(), replacement.tileCount()) for i in range(sharedTileCount): replacementTile = replacement.tileAt(i) properties = replacementTile.properties() properties.merge(tileset.tileAt(i).properties()) undoCommands.append(ChangeProperties(self, self.tr("Tile"), replacementTile, properties)) map.replaceTileset(tileset, replacement) tilesetManager.addReference(replacement) tilesetManager.removeReference(tileset) if (not undoCommands.isEmpty()): self.mUndoStack.beginMacro(self.tr("Tileset Changes")) for command in undoCommands: self.mUndoStack.push(command) self.mUndoStack.endMacro() elif l==2: map, missingTilesets = args existingTilesets = self.mMap.tilesets() tilesetManager = TilesetManager.instance() for tileset in map.tilesets(): # tileset already added if existingTilesets.contains(tileset): continue replacement = tileset.findSimilarTileset(existingTilesets) # tileset not present and no replacement tileset found if not replacement: if not missingTilesets.contains(tileset): missingTilesets.append(tileset) continue # replacement tileset found, change given map map.replaceTileset(tileset, replacement) tilesetManager.addReference(replacement) tilesetManager.removeReference(tileset) ## # Emits the map changed signal. This signal should be emitted after changing # the map size or its tile size. ## def emitMapChanged(self): self.mapChanged.emit() ## # Emits the region changed signal for the specified region. The region # should be in tile coordinates. This method is used by the TilePainter. ## def emitRegionChanged(self, region, layer): self.regionChanged.emit(region, layer) ## # Emits the region edited signal for the specified region and tile layer. # The region should be in tile coordinates. This should be called from # all map document changing classes which are triggered by user input. ## def emitRegionEdited(self, region, layer): self.regionEdited.emit(region, layer) def emitTileLayerDrawMarginsChanged(self, layer): self.tileLayerDrawMarginsChanged.emit(layer) ## # Emits the tileset changed signal. This signal is currently used when adding # or removing tiles from a tileset. # # @todo Emit more specific signals. ## def emitTilesetChanged(self, tileset): self.tilesetChanged.emit(tileset) ## # Emits the signal notifying about the terrain probability of a tile changing. ## def emitTileProbabilityChanged(self, tile): self.tileProbabilityChanged.emit(tile) ## # Emits the signal notifying tileset models about changes to tile terrain # information. All the \a tiles need to be from the same tileset. ## def emitTileTerrainChanged(self, tiles): if (not tiles.isEmpty()): self.tileTerrainChanged.emit(tiles) ## # Emits the signal notifying the TileCollisionEditor about the object group # of a tile changing. ## def emitTileObjectGroupChanged(self, tile): self.tileObjectGroupChanged.emit(tile) ## # Emits the signal notifying about the animation of a tile changing. ## def emitTileAnimationChanged(self, tile): self.tileAnimationChanged.emit(tile) ## # Emits the objectGroupChanged signal, should be called when changing the # color or drawing order of an object group. ## def emitObjectGroupChanged(self, objectGroup): self.objectGroupChanged.emit(objectGroup) ## # Emits the imageLayerChanged signal, should be called when changing the # image or the transparent color of an image layer. ## def emitImageLayerChanged(self, imageLayer): self.imageLayerChanged.emit(imageLayer) ## # Emits the editLayerNameRequested signal, to get renamed. ## def emitEditLayerNameRequested(self): self.editLayerNameRequested.emit() ## # Emits the editCurrentObject signal, which makes the Properties window become # visible and take focus. ## def emitEditCurrentObject(self): self.editCurrentObject.emit() ## # Before forwarding the signal, the objects are removed from the list of # selected objects, triggering a selectedObjectsChanged signal when # appropriate. ## def onObjectsRemoved(self, objects): self.deselectObjects(objects) self.objectsRemoved.emit(objects) def onMapObjectModelRowsInserted(self, parent, first, last): objectGroup = self.mMapObjectModel.toObjectGroup(parent) if (not objectGroup): # we're not dealing with insertion of objects return self.objectsInserted.emit(objectGroup, first, last) self.onMapObjectModelRowsInsertedOrRemoved(parent, first, last) def onMapObjectModelRowsInsertedOrRemoved(self, parent, first, last): objectGroup = self.mMapObjectModel.toObjectGroup(parent) if (not objectGroup): return # Inserting or removing objects changes the index of any that come after lastIndex = objectGroup.objectCount() - 1 if (last < lastIndex): self.objectsIndexChanged.emit(objectGroup, last + 1, lastIndex) def onObjectsMoved(self, parent, start, end, destination, row): if (parent != destination): return objectGroup = self.mMapObjectModel.toObjectGroup(parent) # Determine the full range over which object indexes changed first = min(start, row) last = max(end, row - 1) self.objectsIndexChanged.emit(objectGroup, first, last) def onLayerAdded(self, index): self.layerAdded.emit(index) # Select the first layer that gets added to the map if (self.mMap.layerCount() == 1): self.setCurrentLayerIndex(0) def onLayerAboutToBeRemoved(self, index): layer = self.mMap.layerAt(index) if (layer == self.mCurrentObject): self.setCurrentObject(None) # Deselect any objects on this layer when necessary og = layer if type(og) == ObjectGroup: self.deselectObjects(og.objects()) self.layerAboutToBeRemoved.emit(index) def onLayerRemoved(self, index): # Bring the current layer index to safety currentLayerRemoved = self.mCurrentLayerIndex == self.mMap.layerCount() if (currentLayerRemoved): self.mCurrentLayerIndex = self.mCurrentLayerIndex - 1 self.layerRemoved.emit(index) # Emitted after the layerRemoved signal so that the MapScene has a chance # of synchronizing before adapting to the newly selected index if (currentLayerRemoved): self.currentLayerIndexChanged.emit(self.mCurrentLayerIndex) def onTerrainRemoved(self, terrain): if (terrain == self.mCurrentObject): self.setCurrentObject(None) def setFileName(self, fileName): if (self.mFileName == fileName): return oldFileName = self.mFileName self.mFileName = fileName self.fileNameChanged.emit(fileName, oldFileName) def deselectObjects(self, objects): # Unset the current object when it was part of this list of objects if (self.mCurrentObject and self.mCurrentObject.typeId() == Object.MapObjectType): if (objects.contains(self.mCurrentObject)): self.setCurrentObject(None) removedCount = 0 for object in objects: removedCount += self.mSelectedObjects.removeAll(object) if (removedCount > 0): self.selectedObjectsChanged.emit() def disconnect(self): try: super().disconnect() except: pass