Example #1
0
    def autoMapInternal(self, where, touchedLayer):
        self.mError = ''
        self.mWarning = ''
        if (not self.mMapDocument):
            return
        automatic = touchedLayer != None
        if (not self.mLoaded):
            mapPath = QFileInfo(self.mMapDocument.fileName()).path()
            rulesFileName = mapPath + "/rules.txt"
            if (self.loadFile(rulesFileName)):
                self.mLoaded = True
            else:
                self.errorsOccurred.emit(automatic)
                return

        passedAutoMappers = QVector()
        if (touchedLayer):
            for a in self.mAutoMappers:
                if (a.ruleLayerNameUsed(touchedLayer.name())):
                    passedAutoMappers.append(a)
        else:
            passedAutoMappers = self.mAutoMappers

        if (not passedAutoMappers.isEmpty()):
            # use a pointer to the region, so each automapper can manipulate it and the
            # following automappers do see the impact
            region = QRegion(where)

            undoStack = self.mMapDocument.undoStack()
            undoStack.beginMacro(self.tr("Apply AutoMap rules"))
            aw = AutoMapperWrapper(self.mMapDocument, passedAutoMappers,
                                   region)
            undoStack.push(aw)
            undoStack.endMacro()

        for automapper in self.mAutoMappers:
            self.mWarning += automapper.warningString()
            self.mError += automapper.errorString()

        if self.mWarning != '':
            self.warningsOccurred.emit(automatic)
        if self.mError != '':
            self.errorsOccurred.emit(automatic)
    def autoMapInternal(self, where, touchedLayer):
        self.mError = ''
        self.mWarning = ''
        if (not self.mMapDocument):
            return
        automatic = touchedLayer != None
        if (not self.mLoaded):
            mapPath = QFileInfo(self.mMapDocument.fileName()).path()
            rulesFileName = mapPath + "/rules.txt"
            if (self.loadFile(rulesFileName)):
                self.mLoaded = True
            else:
                self.errorsOccurred.emit(automatic)
                return
                
        passedAutoMappers = QVector()
        if (touchedLayer):
            for a in self.mAutoMappers:
                if (a.ruleLayerNameUsed(touchedLayer.name())):
                    passedAutoMappers.append(a)
        else:
            passedAutoMappers = self.mAutoMappers

        if (not passedAutoMappers.isEmpty()):
            # use a pointer to the region, so each automapper can manipulate it and the
            # following automappers do see the impact
            region = QRegion(where)
        
            undoStack = self.mMapDocument.undoStack()
            undoStack.beginMacro(self.tr("Apply AutoMap rules"))
            aw = AutoMapperWrapper(self.mMapDocument, passedAutoMappers, region)
            undoStack.push(aw)
            undoStack.endMacro()

        for automapper in self.mAutoMappers:
            self.mWarning += automapper.warningString()
            self.mError += automapper.errorString()

        if self.mWarning != '':
            self.warningsOccurred.emit(automatic)
        if self.mError != '':
            self.errorsOccurred.emit(automatic)
    def dropMimeData(self, data, action, row, column, parent):
        if (action == Qt.IgnoreAction):
            return True
        if (column > 0):
            return False
        beginRow = 0
        if (row != -1):
            beginRow = row
        elif parent.isValid():
            beginRow = parent.row()
        else:
            beginRow = self.mFrames.size()
        newFrames = QVector()
        if (data.hasFormat(FRAMES_MIMETYPE)):
            encodedData = data.data(FRAMES_MIMETYPE)
            stream = QDataStream(encodedData, QIODevice.ReadOnly)
            while (not stream.atEnd()):
                frame = Frame()
                frame.tileId = stream.readInt()
                frame.duration = stream.readInt()
                newFrames.append(frame)
        elif (data.hasFormat(TILES_MIMETYPE)):
            encodedData = data.data(TILES_MIMETYPE)
            stream = QDataStream(encodedData, QIODevice.ReadOnly)
            while (not stream.atEnd()):
                frame = Frame()
                frame.tileId = stream.readInt()
                frame.duration = FrameListModel.DEFAULT_DURATION
                newFrames.append(frame)

        if (newFrames.isEmpty()):
            return False
        self.beginInsertRows(QModelIndex(), beginRow,
                             beginRow + newFrames.size() - 1)
        self.mFrames.insert(beginRow, newFrames.size(), Frame())
        for i in range(newFrames.size()):
            self.mFrames[i + beginRow] = newFrames[i]
        self.endInsertRows()
        return True
Example #4
0
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())
Example #5
0
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())
Example #6
0
class StampBrush(AbstractTileTool):
    ##
    # Emitted when the currently selected tiles changed. The stamp brush emits
    # this signal instead of setting its stamp directly so that the fill tool
    # also gets the new stamp.
    ##
    currentTilesChanged = pyqtSignal(list)
    
    ##
    # Emitted when a stamp was captured from the map. The stamp brush emits
    # this signal instead of setting its stamp directly so that the fill tool
    # also gets the new stamp.
    ##
    stampCaptured = pyqtSignal(TileStamp)
    
    def __init__(self, parent = None):
        super().__init__(self.tr("Stamp Brush"),
                           QIcon(":images/22x22/stock-tool-clone.png"),
                           QKeySequence(self.tr("B")),
                           parent)
        ##
        # This stores the current behavior.
        ##
        self.mBrushBehavior = BrushBehavior.Free

        self.mIsRandom = False
        self.mCaptureStart = QPoint()
        self.mRandomCellPicker = RandomPicker()
        ##
        # mStamp is a tile layer in which is the selection the user made
        # either by rightclicking (Capture) or at the tilesetdock
        ##
        self.mStamp = TileStamp()
        self.mPreviewLayer = None
        self.mMissingTilesets = QVector()
        self.mPrevTilePosition = QPoint()
        self.mStampReference = QPoint()

    def __del__(self):
        pass

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

    def mousePressed(self, event):
        if (not self.brushItem().isVisible()):
            return
        if (event.button() == Qt.LeftButton):
            x = self.mBrushBehavior
            if x==BrushBehavior.Line:
                self.mStampReference = self.tilePosition()
                self.mBrushBehavior = BrushBehavior.LineStartSet
            elif x==BrushBehavior.Circle:
                self.mStampReference = self.tilePosition()
                self.mBrushBehavior = BrushBehavior.CircleMidSet
            elif x==BrushBehavior.LineStartSet:
                self.doPaint()
                self.mStampReference = self.tilePosition()
            elif x==BrushBehavior.CircleMidSet:
                self.doPaint()
            elif x==BrushBehavior.Paint:
                self.beginPaint()
            elif x==BrushBehavior.Free:
                self.beginPaint()
                self.mBrushBehavior = BrushBehavior.Paint
            elif x==BrushBehavior.Capture:
                pass
        else:
            if (event.button() == Qt.RightButton):
                self.beginCapture()

    def mouseReleased(self, event):
        x = self.mBrushBehavior
        if x==BrushBehavior.Capture:
            if (event.button() == Qt.RightButton):
                self.endCapture()
                self.mBrushBehavior = BrushBehavior.Free
        elif x==BrushBehavior.Paint:
            if (event.button() == Qt.LeftButton):
                self.mBrushBehavior = BrushBehavior.Free
                # allow going over different variations by repeatedly clicking
                self.updatePreview()
        else:
            # do nothing?
            pass

    def modifiersChanged(self, modifiers):
        if self.mStamp.isEmpty():
            return
        if (modifiers & Qt.ShiftModifier):
            if (modifiers & Qt.ControlModifier):
                if self.mBrushBehavior == BrushBehavior.LineStartSet:
                    self.mBrushBehavior = BrushBehavior.CircleMidSet
                else:
                    self.mBrushBehavior = BrushBehavior.Circle
            else:
                if self.mBrushBehavior == BrushBehavior.CircleMidSet:
                    self.mBrushBehavior = BrushBehavior.LineStartSet
                else:
                    self.mBrushBehavior = BrushBehavior.Line
        else:
            self.mBrushBehavior = BrushBehavior.Free
        
        self.updatePreview()
        
    def languageChanged(self):
        self.setName(self.tr("Stamp Brush"))
        self.setShortcut(QKeySequence(self.tr("B")))

    ##
    # Sets the stamp that is drawn when painting. The stamp brush takes
    # ownership over the stamp layer.
    ##
    def setStamp(self, stamp):
        if (self.mStamp == stamp):
            return
        
        self.mStamp = stamp
        self.updateRandomList()
        self.updatePreview()

    ##
    # This returns the current tile stamp used for painting.
    ##
    def stamp(self):
        return self.mStamp
        
    def setRandom(self, value):
        if self.mIsRandom == value:
            return
        self.mIsRandom = value

        self.updateRandomList()
        self.updatePreview()

    def tilePositionChanged(self, pos):
        x = self.mBrushBehavior
        if x==BrushBehavior.Paint:
            # Draw a line from the previous point to avoid gaps, skipping the
            # first point, since it was painted when the mouse was pressed, or the
            # last time the mouse was moved.
            points = pointsOnLine(self.mPrevTilePosition, pos)
            editedRegion = QRegion()
            
            ptSize = points.size()
            ptLast = ptSize - 1
            for i in range(1, ptSize):
                self.drawPreviewLayer(QVector(points[i]))

                # Only update the brush item for the last drawn piece
                if i == ptLast:
                    self.brushItem().setTileLayer(self.mPreviewLayer)

                editedRegion |= self.doPaint(PaintFlags.Mergeable | PaintFlags.SuppressRegionEdited)

            self.mapDocument().emitRegionEdited(editedRegion, self.currentTileLayer())
        else:
            self.updatePreview()
            
        self.mPrevTilePosition = pos

    def mapDocumentChanged(self, oldDocument, newDocument):
        super().mapDocumentChanged(oldDocument, newDocument)
        
        if newDocument:
            self.updateRandomList()
            self.updatePreview()

    def beginPaint(self):
        if (self.mBrushBehavior != BrushBehavior.Free):
            return
        self.mBrushBehavior = BrushBehavior.Paint
        self.doPaint()

    ##
    # Merges the tile layer of its brush item into the current map.
    #
    # \a flags can be set to Mergeable, which applies to the undo command.
    #
    # \a offsetX and \a offsetY give an offset where to merge the brush items tile
    # layer into the current map.
    #
    # Returns the edited region.
    ##
    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 beginCapture(self):
        if (self.mBrushBehavior != BrushBehavior.Free):
            return
        self.mBrushBehavior = BrushBehavior.Capture
        self.mCaptureStart = self.tilePosition()
        self.setStamp(TileStamp())

    def endCapture(self):
        if (self.mBrushBehavior != BrushBehavior.Capture):
            return
        self.mBrushBehavior = BrushBehavior.Free
        tileLayer = self.currentTileLayer()
        # Intersect with the layer and translate to layer coordinates
        captured = self.capturedArea()
        captured &= QRect(tileLayer.x(), tileLayer.y(),
                          tileLayer.width(), tileLayer.height())
        if (captured.isValid()):
            captured.translate(-tileLayer.x(), -tileLayer.y())
            map = tileLayer.map()
            capture = tileLayer.copy(captured)
            
            stamp = Map(map.orientation(),
                             capture.width(),
                             capture.height(),
                             map.tileWidth(),
                             map.tileHeight())
            # Add tileset references to map
            for tileset in capture.usedTilesets():
                stamp.addTileset(tileset)

            stamp.addLayer(capture)

            self.stampCaptured.emit(TileStamp(stamp))
        else:
            self.updatePreview()

    def capturedArea(self):
        captured = QRect(self.mCaptureStart, self.tilePosition()).normalized()
        if (captured.width() == 0):
            captured.adjust(-1, 0, 1, 0)
        if (captured.height() == 0):
            captured.adjust(0, -1, 0, 1)
        return captured

    ##
    # Updates the position of the brush item.
    ##
    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)

    ##
    # Updates the list used random stamps.
    # This is done by taking all non-null tiles from the original stamp mStamp.
    ##
    def updateRandomList(self):
        self.mRandomCellPicker.clear()

        if not self.mIsRandom:
            return

        self.mMissingTilesets.clear()
        
        for variation in self.mStamp.variations():
            self.mapDocument().unifyTilesets(variation.map, self.mMissingTilesets)
            tileLayer = variation.tileLayer()
            for x in range(tileLayer.width()):
                for y in range(tileLayer.height()):
                    cell = tileLayer.cellAt(x, y)
                    if not cell.isEmpty():
                        self.mRandomCellPicker.add(cell, cell.tile.probability())

    ##
    # Draws the preview layer.
    # It tries to put at all given points a stamp of the current stamp at the
    # corresponding position.
    # It also takes care, that no overlaps appear.
    # So it will check for every point if it can place a stamp there without
    # overlap.
    ##
    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
Example #7
0
class Tile(Object):
    def __init__(self, *args):
        super().__init__(Object.TileType)
        
        l = len(args)
        if l==3:
            image, id, tileset = args
            self.mImageSource = QString()
        elif l==4:
            image, imageSource, id, tileset = args
            self.mImageSource = imageSource

        self.mId = id
        self.mTileset = tileset
        self.mImage = image
        self.mTerrain = 0xffffffff
        self.mProbability = 1.0
        self.mObjectGroup = None
        self.mFrames = QVector()
        self.mCurrentFrameIndex = 0
        self.mUnusedTime = 0

    def __del__(self):
        del self.mObjectGroup

    ##
    # Returns the tileset that this tile is part of as a shared pointer.
    ##
    def sharedTileset(self):
        return self.mTileset.sharedPointer()

    ##
    # Returns ID of this tile within its tileset.
    ##
    def id(self):
        return self.mId

    ##
    # Returns the tileset that this tile is part of.
    ##
    def tileset(self):
        return self.mTileset

    ##
    # Returns the image of this tile.
    ##
    def image(self):
        return QPixmap(self.mImage)

    ##
    # Returns the image for rendering this tile, taking into account tile
    # animations.
    ##
    def currentFrameImage(self):
        if (self.isAnimated()):
            frame = self.mFrames.at(self.mCurrentFrameIndex)
            return self.mTileset.tileAt(frame.tileId).image()
        else:
            return QPixmap(self.mImage)

    ##
    # Returns the drawing offset of the tile (in pixels).
    ##
    def offset(self):
        return self.mTileset.tileOffset()

    ##
    # Sets the image of this tile.
    ##
    def setImage(self, image):
        self.mImage = image

    ##
    # Returns the file name of the external image that represents this tile.
    # When this tile doesn't refer to an external image, an empty string is
    # returned.
    ##
    def imageSource(self):
        return self.mImageSource

    ##
    # Returns the file name of the external image that represents this tile.
    # When this tile doesn't refer to an external image, an empty string is
    # returned.
    ##
    def setImageSource(self, imageSource):
        self.mImageSource = imageSource

    ##
    # Returns the width of this tile.
    ##
    def width(self):
        return self.mImage.width()

    ##
    # Returns the height of this tile.
    ##
    def height(self):
        return self.mImage.height()

    ##
    # Returns the size of this tile.
    ##
    def size(self):
        return self.mImage.size()

    ##
    # Returns the Terrain of a given corner.
    ##
    def terrainAtCorner(self, corner):
        return self.mTileset.terrain(self.cornerTerrainId(corner))

    ##
    # Returns the terrain id at a given corner.
    ##
    def cornerTerrainId(self, corner):
        t = (self.terrain() >> (3 - corner)*8) & 0xFF
        if t == 0xFF:
            return -1
        return t

    ##
    # Set the terrain type of a given corner.
    ##
    def setCornerTerrainId(self, corner, terrainId):
        self.setTerrain(setTerrainCorner(self.mTerrain, corner, terrainId))

    ##
    # Returns the terrain for each corner of this tile.
    ##
    def terrain(self):
        return self.mTerrain

    ##
    # Set the terrain for each corner of the tile.
    ##
    def setTerrain(self, terrain):
        if (self.mTerrain == terrain):
            return
        self.mTerrain = terrain
        self.mTileset.markTerrainDistancesDirty()

    ##
    # Returns the probability of this terrain type appearing while painting (0-100%).
    ##
    def probability(self):
        return self.mProbability

    ##
    # Set the relative probability of this tile appearing while painting.
    ##
    def setProbability(self, probability):
        self.mProbability = probability

    ##
    # @return The group of objects associated with this tile. This is generally
    #         expected to be used for editing collision shapes.
    ##
    def objectGroup(self):
        return self.mObjectGroup

    ##
    # Sets \a objectGroup to be the group of objects associated with this tile.
    # The Tile takes ownership over the ObjectGroup and it can't also be part of
    # a map.
    ##
    def setObjectGroup(self, objectGroup):
        if (self.mObjectGroup == objectGroup):
            return
        del self.mObjectGroup
        self.mObjectGroup = objectGroup

    ##
    # Swaps the object group of this tile with \a objectGroup. The tile releases
    # ownership over its existing object group and takes ownership over the new
    # one.
    #
    # @return The previous object group referenced by this tile.
    ##
    def swapObjectGroup(self, objectGroup):
        previousObjectGroup = self.mObjectGroup
        self.mObjectGroup = objectGroup
        return previousObjectGroup

    def frames(self):
        return self.mFrames

    ##
    # Sets the animation frames to be used by this tile. Resets any currently
    # running animation.
    ##
    def setFrames(self, frames):
        self.mFrames = frames
        self.mCurrentFrameIndex = 0
        self.mUnusedTime = 0

    def isAnimated(self):
        return not self.mFrames.isEmpty()

    def currentFrameIndex(self):
        return self.mCurrentFrameIndex

    ##
    # Advances this tile animation by the given amount of milliseconds. Returns
    # whether this caused the current tileId to change.
    ##
    def advanceAnimation(self, ms):
        if (not self.isAnimated()):
            return False
        self.mUnusedTime += ms
        frame = self.mFrames.at(self.mCurrentFrameIndex)
        previousTileId = frame.tileId
        while (frame.duration > 0 and self.mUnusedTime > frame.duration):
            self.mUnusedTime -= frame.duration
            self.mCurrentFrameIndex = (self.mCurrentFrameIndex + 1) % self.mFrames.size()
            frame = self.mFrames.at(self.mCurrentFrameIndex)

        return previousTileId != frame.tileId
Example #8
0
class StampBrush(AbstractTileTool):
    ##
    # Emitted when the currently selected tiles changed. The stamp brush emits
    # this signal instead of setting its stamp directly so that the fill tool
    # also gets the new stamp.
    ##
    currentTilesChanged = pyqtSignal(list)

    ##
    # Emitted when a stamp was captured from the map. The stamp brush emits
    # this signal instead of setting its stamp directly so that the fill tool
    # also gets the new stamp.
    ##
    stampCaptured = pyqtSignal(TileStamp)

    def __init__(self, parent=None):
        super().__init__(self.tr("Stamp Brush"),
                         QIcon(":images/22x22/stock-tool-clone.png"),
                         QKeySequence(self.tr("B")), parent)
        ##
        # This stores the current behavior.
        ##
        self.mBrushBehavior = BrushBehavior.Free

        self.mIsRandom = False
        self.mCaptureStart = QPoint()
        self.mRandomCellPicker = RandomPicker()
        ##
        # mStamp is a tile layer in which is the selection the user made
        # either by rightclicking (Capture) or at the tilesetdock
        ##
        self.mStamp = TileStamp()
        self.mPreviewLayer = None
        self.mMissingTilesets = QVector()
        self.mPrevTilePosition = QPoint()
        self.mStampReference = QPoint()

    def __del__(self):
        pass

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

    def mousePressed(self, event):
        if (not self.brushItem().isVisible()):
            return
        if (event.button() == Qt.LeftButton):
            x = self.mBrushBehavior
            if x == BrushBehavior.Line:
                self.mStampReference = self.tilePosition()
                self.mBrushBehavior = BrushBehavior.LineStartSet
            elif x == BrushBehavior.Circle:
                self.mStampReference = self.tilePosition()
                self.mBrushBehavior = BrushBehavior.CircleMidSet
            elif x == BrushBehavior.LineStartSet:
                self.doPaint()
                self.mStampReference = self.tilePosition()
            elif x == BrushBehavior.CircleMidSet:
                self.doPaint()
            elif x == BrushBehavior.Paint:
                self.beginPaint()
            elif x == BrushBehavior.Free:
                self.beginPaint()
                self.mBrushBehavior = BrushBehavior.Paint
            elif x == BrushBehavior.Capture:
                pass
        else:
            if (event.button() == Qt.RightButton):
                self.beginCapture()

    def mouseReleased(self, event):
        x = self.mBrushBehavior
        if x == BrushBehavior.Capture:
            if (event.button() == Qt.RightButton):
                self.endCapture()
                self.mBrushBehavior = BrushBehavior.Free
        elif x == BrushBehavior.Paint:
            if (event.button() == Qt.LeftButton):
                self.mBrushBehavior = BrushBehavior.Free
                # allow going over different variations by repeatedly clicking
                self.updatePreview()
        else:
            # do nothing?
            pass

    def modifiersChanged(self, modifiers):
        if self.mStamp.isEmpty():
            return
        if (modifiers & Qt.ShiftModifier):
            if (modifiers & Qt.ControlModifier):
                if self.mBrushBehavior == BrushBehavior.LineStartSet:
                    self.mBrushBehavior = BrushBehavior.CircleMidSet
                else:
                    self.mBrushBehavior = BrushBehavior.Circle
            else:
                if self.mBrushBehavior == BrushBehavior.CircleMidSet:
                    self.mBrushBehavior = BrushBehavior.LineStartSet
                else:
                    self.mBrushBehavior = BrushBehavior.Line
        else:
            self.mBrushBehavior = BrushBehavior.Free

        self.updatePreview()

    def languageChanged(self):
        self.setName(self.tr("Stamp Brush"))
        self.setShortcut(QKeySequence(self.tr("B")))

    ##
    # Sets the stamp that is drawn when painting. The stamp brush takes
    # ownership over the stamp layer.
    ##
    def setStamp(self, stamp):
        if (self.mStamp == stamp):
            return

        self.mStamp = stamp
        self.updateRandomList()
        self.updatePreview()

    ##
    # This returns the current tile stamp used for painting.
    ##
    def stamp(self):
        return self.mStamp

    def setRandom(self, value):
        if self.mIsRandom == value:
            return
        self.mIsRandom = value

        self.updateRandomList()
        self.updatePreview()

    def tilePositionChanged(self, pos):
        x = self.mBrushBehavior
        if x == BrushBehavior.Paint:
            # Draw a line from the previous point to avoid gaps, skipping the
            # first point, since it was painted when the mouse was pressed, or the
            # last time the mouse was moved.
            points = pointsOnLine(self.mPrevTilePosition, pos)
            editedRegion = QRegion()

            ptSize = points.size()
            ptLast = ptSize - 1
            for i in range(1, ptSize):
                self.drawPreviewLayer(QVector(points[i]))

                # Only update the brush item for the last drawn piece
                if i == ptLast:
                    self.brushItem().setTileLayer(self.mPreviewLayer)

                editedRegion |= self.doPaint(PaintFlags.Mergeable
                                             | PaintFlags.SuppressRegionEdited)

            self.mapDocument().emitRegionEdited(editedRegion,
                                                self.currentTileLayer())
        else:
            self.updatePreview()

        self.mPrevTilePosition = pos

    def mapDocumentChanged(self, oldDocument, newDocument):
        super().mapDocumentChanged(oldDocument, newDocument)

        if newDocument:
            self.updateRandomList()
            self.updatePreview()

    def beginPaint(self):
        if (self.mBrushBehavior != BrushBehavior.Free):
            return
        self.mBrushBehavior = BrushBehavior.Paint
        self.doPaint()

    ##
    # Merges the tile layer of its brush item into the current map.
    #
    # \a flags can be set to Mergeable, which applies to the undo command.
    #
    # \a offsetX and \a offsetY give an offset where to merge the brush items tile
    # layer into the current map.
    #
    # Returns the edited region.
    ##
    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 beginCapture(self):
        if (self.mBrushBehavior != BrushBehavior.Free):
            return
        self.mBrushBehavior = BrushBehavior.Capture
        self.mCaptureStart = self.tilePosition()
        self.setStamp(TileStamp())

    def endCapture(self):
        if (self.mBrushBehavior != BrushBehavior.Capture):
            return
        self.mBrushBehavior = BrushBehavior.Free
        tileLayer = self.currentTileLayer()
        # Intersect with the layer and translate to layer coordinates
        captured = self.capturedArea()
        captured &= QRect(tileLayer.x(), tileLayer.y(), tileLayer.width(),
                          tileLayer.height())
        if (captured.isValid()):
            captured.translate(-tileLayer.x(), -tileLayer.y())
            map = tileLayer.map()
            capture = tileLayer.copy(captured)

            stamp = Map(map.orientation(), capture.width(), capture.height(),
                        map.tileWidth(), map.tileHeight())
            # Add tileset references to map
            for tileset in capture.usedTilesets():
                stamp.addTileset(tileset)

            stamp.addLayer(capture)

            self.stampCaptured.emit(TileStamp(stamp))
        else:
            self.updatePreview()

    def capturedArea(self):
        captured = QRect(self.mCaptureStart, self.tilePosition()).normalized()
        if (captured.width() == 0):
            captured.adjust(-1, 0, 1, 0)
        if (captured.height() == 0):
            captured.adjust(0, -1, 0, 1)
        return captured

    ##
    # Updates the position of the brush item.
    ##
    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)

    ##
    # Updates the list used random stamps.
    # This is done by taking all non-null tiles from the original stamp mStamp.
    ##
    def updateRandomList(self):
        self.mRandomCellPicker.clear()

        if not self.mIsRandom:
            return

        self.mMissingTilesets.clear()

        for variation in self.mStamp.variations():
            self.mapDocument().unifyTilesets(variation.map,
                                             self.mMissingTilesets)
            tileLayer = variation.tileLayer()
            for x in range(tileLayer.width()):
                for y in range(tileLayer.height()):
                    cell = tileLayer.cellAt(x, y)
                    if not cell.isEmpty():
                        self.mRandomCellPicker.add(cell,
                                                   cell.tile.probability())

    ##
    # Draws the preview layer.
    # It tries to put at all given points a stamp of the current stamp at the
    # corresponding position.
    # It also takes care, that no overlaps appear.
    # So it will check for every point if it can place a stamp there without
    # overlap.
    ##
    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