Example #1
0
    def addTiles(self):
        tileset = self.currentTileset()
        if (not tileset):
            return
        prefs = preferences.Preferences.instance()
        startLocation = QFileInfo(
            prefs.lastPath(preferences.Preferences.ImageFile)).absolutePath()
        filter = Utils.readableImageFormatsFilter()
        files = QFileDialog.getOpenFileNames(self.window(),
                                             self.tr("Add Tiles"),
                                             startLocation, filter)
        tiles = QList()
        id = tileset.tileCount()
        for file in files:
            image = QPixmap(file)
            if (not image.isNull()):
                tiles.append(Tile(image, file, id, tileset))
                id += 1
            else:
                warning = QMessageBox(QMessageBox.Warning,
                                      self.tr("Add Tiles"),
                                      self.tr("Could not load \"%s\"!" % file),
                                      QMessageBox.Ignore | QMessageBox.Cancel,
                                      self.window())
                warning.setDefaultButton(QMessageBox.Ignore)
                if (warning.exec() != QMessageBox.Ignore):
                    tiles.clear()
                    return

        if (tiles.isEmpty()):
            return
        prefs.setLastPath(preferences.Preferences.ImageFile, files.last())
        self.mMapDocument.undoStack().push(
            AddTiles(self.mMapDocument, tileset, tiles))
Example #2
0
class QtCursorDatabase():
    def __init__(self):
        self.m_cursorNames = QList()
        self.m_cursorIcons = QMap()
        self.m_valueToCursorShape = QMap()
        self.m_cursorShapeToValue = QMap()

        self.appendCursor(
            Qt.ArrowCursor,
            QCoreApplication.translate("QtCursorDatabase", "Arrow"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-arrow.png"))
        self.appendCursor(
            Qt.UpArrowCursor,
            QCoreApplication.translate("QtCursorDatabase", "Up Arrow"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-uparrow.png")
        )
        self.appendCursor(
            Qt.CrossCursor,
            QCoreApplication.translate("QtCursorDatabase", "Cross"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-cross.png"))
        self.appendCursor(
            Qt.WaitCursor,
            QCoreApplication.translate("QtCursorDatabase", "Wait"),
            QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-wait.png"))
        self.appendCursor(
            Qt.IBeamCursor,
            QCoreApplication.translate("QtCursorDatabase", "IBeam"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-ibeam.png"))
        self.appendCursor(
            Qt.SizeVerCursor,
            QCoreApplication.translate("QtCursorDatabase", "Size Vertical"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-sizev.png"))
        self.appendCursor(
            Qt.SizeHorCursor,
            QCoreApplication.translate("QtCursorDatabase", "Size Horizontal"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-sizeh.png"))
        self.appendCursor(
            Qt.SizeFDiagCursor,
            QCoreApplication.translate("QtCursorDatabase", "Size Backslash"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-sizef.png"))
        self.appendCursor(
            Qt.SizeBDiagCursor,
            QCoreApplication.translate("QtCursorDatabase", "Size Slash"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-sizeb.png"))
        self.appendCursor(
            Qt.SizeAllCursor,
            QCoreApplication.translate("QtCursorDatabase", "Size All"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-sizeall.png")
        )
        self.appendCursor(
            Qt.BlankCursor,
            QCoreApplication.translate("QtCursorDatabase", "Blank"), QIcon())
        self.appendCursor(
            Qt.SplitVCursor,
            QCoreApplication.translate("QtCursorDatabase", "Split Vertical"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-vsplit.png"))
        self.appendCursor(
            Qt.SplitHCursor,
            QCoreApplication.translate("QtCursorDatabase", "Split Horizontal"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-hsplit.png"))
        self.appendCursor(
            Qt.PointingHandCursor,
            QCoreApplication.translate("QtCursorDatabase", "Pointing Hand"),
            QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-hand.png"))
        self.appendCursor(
            Qt.ForbiddenCursor,
            QCoreApplication.translate("QtCursorDatabase", "Forbidden"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-forbidden.png"
            ))
        self.appendCursor(
            Qt.OpenHandCursor,
            QCoreApplication.translate("QtCursorDatabase", "Open Hand"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-openhand.png"
            ))
        self.appendCursor(
            Qt.ClosedHandCursor,
            QCoreApplication.translate("QtCursorDatabase", "Closed Hand"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-closedhand.png"
            ))
        self.appendCursor(
            Qt.WhatsThisCursor,
            QCoreApplication.translate("QtCursorDatabase", "What's This"),
            QIcon(
                ":/qt-project.org/qtpropertybrowser/images/cursor-whatsthis.png"
            ))
        self.appendCursor(
            Qt.BusyCursor,
            QCoreApplication.translate("QtCursorDatabase", "Busy"),
            QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-busy.png"))

    def clear(self):
        self.m_cursorNames.clear()
        self.m_cursorIcons.clear()
        self.m_valueToCursorShape.clear()
        self.m_cursorShapeToValue.clear()

    def appendCursor(self, shape, name, icon):
        if self.m_cursorShapeToValue.get(shape):
            return
        value = len(self.m_cursorNames)
        self.m_cursorNames.append(name)
        self.m_cursorIcons[value] = icon
        self.m_valueToCursorShape[value] = shape
        self.m_cursorShapeToValue[shape] = value

    def cursorShapeNames(self):
        return self.m_cursorNames

    def cursorShapeIcons(self):
        return self.m_cursorIcons

    def cursorToShapeName(self, cursor):
        val = self.cursorToValue(cursor)
        if val >= 0:
            return self.m_cursorNames[val]
        return ''

    def cursorToShapeIcon(self, cursor):
        val = self.cursorToValue(cursor)
        return self.m_cursorIcons[val]

    def cursorToValue(self, cursor):
        shape = cursor.shape()
        return self.m_cursorShapeToValue.get(shape, -1)

    def valueToCursor(self, value):
        if value in self.m_valueToCursorShape:
            return QCursor(self.m_valueToCursorShape[value])
        return QCursor()
Example #3
0
class Tileset(Object):
    ##
    # Constructor.
    #
    # @param name        the name of the tileset
    # @param tileWidth   the width of the tiles in the tileset
    # @param tileHeight  the height of the tiles in the tileset
    # @param tileSpacing the spacing between the tiles in the tileset image
    # @param margin      the margin around the tiles in the tileset image
    ##
    def __init__(self, name, tileWidth, tileHeight, tileSpacing = 0, margin = 0):
        super().__init__(Object.TilesetType)

        self.mName = name
        self.mTileWidth = tileWidth
        self.mTileHeight = tileHeight
        self.mTileSpacing = tileSpacing
        self.mMargin = margin
        self.mImageWidth = 0
        self.mImageHeight = 0
        self.mColumnCount = 0
        self.mTerrainDistancesDirty = False

        self.mTileOffset = QPoint()
        self.mFileName = QString()
        self.mTiles = QList()
        self.mTransparentColor = QColor()
        self.mImageSource = QString()
        self.mTerrainTypes = QList()
        self.mWeakPointer = None

    ##
    # Destructor.
    ##
    def __del__(self):
        self.mTiles.clear()
        self.mTerrainTypes.clear()

    def create(name, tileWidth, tileHeight, tileSpacing = 0, margin = 0):
        tileset = Tileset(name, tileWidth, tileHeight, tileSpacing, margin)
        tileset.mWeakPointer = tileset
        return tileset
    
    def __iter__(self):
        return self.mTiles.__iter__()
        
    ##
    # Returns the name of this tileset.
    ##
    def name(self):
        return self.mName

    ##
    # Sets the name of this tileset.
    ##
    def setName(self, name):
        self.mName = name

    ##
    # Returns the file name of this tileset. When the tileset isn't an
    # external tileset, the file name is empty.
    ##
    def fileName(self):
        return self.mFileName

    ##
    # Sets the filename of this tileset.
    ##
    def setFileName(self, fileName):
        self.mFileName = fileName

    ##
    # Returns whether this tileset is external.
    ##
    def isExternal(self):
        return self.mFileName!=''

    ##
    # Returns the maximum width of the tiles in this tileset.
    ##
    def tileWidth(self):
        return self.mTileWidth

    ##
    # Returns the maximum height of the tiles in this tileset.
    ##
    def tileHeight(self):
        return self.mTileHeight

    ##
    # Returns the maximum size of the tiles in this tileset.
    ##
    def tileSize(self):
        return QSize(self.mTileWidth, self.mTileHeight)

    ##
    # Returns the spacing between the tiles in the tileset image.
    ##
    def tileSpacing(self):
        return self.mTileSpacing

    ##
    # Returns the margin around the tiles in the tileset image.
    ##
    def margin(self):
        return self.mMargin

    ##
    # Returns the offset that is applied when drawing the tiles in this
    # tileset.
    ##
    def tileOffset(self):
        return self.mTileOffset

    ##
    # @see tileOffset
    ##
    def setTileOffset(self, offset):
        self.mTileOffset = offset

    ##
    # Returns a const reference to the list of tiles in this tileset.
    ##
    def tiles(self):
        return QList(self.mTiles)

    ##
    # Returns the tile for the given tile ID.
    # The tile ID is local to this tileset, which means the IDs are in range
    # [0, tileCount() - 1].
    ##
    def tileAt(self, id):
        if id < self.mTiles.size():
            return self.mTiles.at(id)
        return None

    ##
    # Returns the number of tiles in this tileset.
    ##
    def tileCount(self):
        return self.mTiles.size()

    ##
    # Returns the number of tile columns in the tileset image.
    ##
    def columnCount(self):
        return self.mColumnCount

    ##
    # Returns the width of the tileset image.
    ##
    def imageWidth(self):
        return self.mImageWidth

    ##
    # Returns the height of the tileset image.
    ##
    def imageHeight(self):
        return self.mImageHeight

    ##
    # Returns the transparent color, or an invalid color if no transparent
    # color is used.
    ##
    def transparentColor(self):
        return QColor(self.mTransparentColor)

    ##
    # Sets the transparent color. Pixels with this color will be masked out
    # when loadFromImage() is called.
    ##
    def setTransparentColor(self, c):
        self.mTransparentColor = c

    ##
    # Load this tileset from the given tileset \a image. This will replace
    # existing tile images in this tileset with new ones. If the new image
    # contains more tiles than exist in the tileset new tiles will be
    # appended, if there are fewer tiles the excess images will be blanked.
    #
    # The tile width and height of this tileset must be higher than 0.
    #
    # @param image    the image to load the tiles from
    # @param fileName the file name of the image, which will be remembered
    #                 as the image source of this tileset.
    # @return <code>true</code> if loading was successful, otherwise
    #         returns <code>false</code>
    ##
    def loadFromImage(self, *args):
        l = len(args)
        if l==2:
            image, fileName = args

            tileSize = self.tileSize()
            margin = self.margin()
            spacing = self.tileSpacing()
    
            if (image.isNull()):
                return False
            stopWidth = image.width() - tileSize.width()
            stopHeight = image.height() - tileSize.height()
            oldTilesetSize = self.tileCount()
            tileNum = 0
            for y in range(margin, stopHeight+1, tileSize.height() + spacing):
                for x in range(margin, stopWidth+1, tileSize.width() + spacing):
                    tileImage = image.copy(x, y, tileSize.width(), tileSize.height())
                    tilePixmap = QPixmap.fromImage(tileImage)
                    if (self.mTransparentColor.isValid()):
                        mask = tileImage.createMaskFromColor(self.mTransparentColor.rgb())
                        tilePixmap.setMask(QBitmap.fromImage(mask))

                    if (tileNum < oldTilesetSize):
                        self.mTiles.at(tileNum).setImage(tilePixmap)
                    else:
                        self.mTiles.append(Tile(tilePixmap, tileNum, self))

                    tileNum += 1

            # Blank out any remaining tiles to avoid confusion
            while (tileNum < oldTilesetSize):
                tilePixmap = QPixmap(tileSize)
                tilePixmap.fill()
                self.mTiles.at(tileNum).setImage(tilePixmap)
                tileNum += 1

            self.mImageWidth = image.width()
            self.mImageHeight = image.height()
            self.mColumnCount = self.columnCountForWidth(self.mImageWidth)
            self.mImageSource = fileName
            return True
        elif l==1:
            ##
            # Convenience override that loads the image using the QImage constructor.
            ##
            fileName = args[0]
            return self.loadFromImage(QImage(fileName), fileName)

    ##
    # This checks if there is a similar tileset in the given list.
    # It is needed for replacing this tileset by its similar copy.
    ##
    def findSimilarTileset(self, tilesets):
        for candidate in tilesets:
            if (candidate.tileCount() != self.tileCount()):
                continue
            if (candidate.imageSource() != self.imageSource()):
                continue
            if (candidate.tileSize() != self.tileSize()):
                continue
            if (candidate.tileSpacing() != self.tileSpacing()):
                continue
            if (candidate.margin() != self.margin()):
                continue
            if (candidate.tileOffset() != self.tileOffset()):
                continue

            # For an image collection tileset, check the image sources
            if (self.imageSource()==''):
                if (not sameTileImages(self, candidate)):
                    continue

            return candidate
            
        return None

    ##
    # Returns the file name of the external image that contains the tiles in
    # this tileset. Is an empty string when this tileset doesn't have a
    # tileset image.
    ##
    def imageSource(self):
        return self.mImageSource

    ##
    # Returns the column count that this tileset would have if the tileset
    # image would have the given \a width. This takes into account the tile
    # size, margin and spacing.
    ##
    def columnCountForWidth(self, width):
        return int((width - self.mMargin + self.mTileSpacing) / (self.mTileWidth + self.mTileSpacing))

    ##
    # Returns a const reference to the list of terrains in this tileset.
    ##
    def terrains(self):
        return QList(self.mTerrainTypes)

    ##
    # Returns the number of terrain types in this tileset.
    ##
    def terrainCount(self):
        return self.mTerrainTypes.size()

    ##
    # Returns the terrain type at the given \a index.
    ##
    def terrain(self, index):
        if index >= 0:
            _x = self.mTerrainTypes[index]
        else:
            _x = None
        return _x

    ##
    # Adds a new terrain type.
    #
    # @param name      the name of the terrain
    # @param imageTile the id of the tile that represents the terrain visually
    # @return the created Terrain instance
    ##
    def addTerrain(self, name, imageTileId):
        terrain = Terrain(self.terrainCount(), self, name, imageTileId)
        self.insertTerrain(self.terrainCount(), terrain)
        return terrain

    ##
    # Adds the \a terrain type at the given \a index.
    #
    # The terrain should already have this tileset associated with it.
    ##
    def insertTerrain(self, index, terrain):
        self.mTerrainTypes.insert(index, terrain)
        # Reassign terrain IDs
        for terrainId in range(index, self.mTerrainTypes.size()):
            self.mTerrainTypes.at(terrainId).mId = terrainId
        # Adjust tile terrain references
        for tile in self.mTiles:
            for corner in range(4):
                terrainId = tile.cornerTerrainId(corner)
                if (terrainId >= index):
                    tile.setCornerTerrainId(corner, terrainId + 1)

        self.mTerrainDistancesDirty = True

    ##
    # Removes the terrain type at the given \a index and returns it. The
    # caller becomes responsible for the lifetime of the terrain type.
    #
    # This will cause the terrain ids of subsequent terrains to shift up to
    # fill the space and the terrain information of all tiles in this tileset
    # will be updated accordingly.
    ##
    def takeTerrainAt(self, index):
        terrain = self.mTerrainTypes.takeAt(index)
        # Reassign terrain IDs
        for terrainId in range(index, self.mTerrainTypes.size()):
            self.mTerrainTypes.at(terrainId).mId = terrainId

        # Clear and adjust tile terrain references
        for tile in self.mTiles:
            for corner in range(4):
                terrainId = tile.cornerTerrainId(corner)
                if (terrainId == index):
                    tile.setCornerTerrainId(corner, 0xFF)
                elif (terrainId > index):
                    tile.setCornerTerrainId(corner, terrainId - 1)

        self.mTerrainDistancesDirty = True
        return terrain

    ##
    # Returns the transition penalty(/distance) between 2 terrains. -1 if no
    # transition is possible.
    ##
    def terrainTransitionPenalty(self, terrainType0, terrainType1):
        if (self.mTerrainDistancesDirty):
            self.recalculateTerrainDistances()
            self.mTerrainDistancesDirty = False

        if terrainType0 == 255:
            terrainType0 = -1
        if terrainType1 == 255:
            terrainType1 = -1

        # Do some magic, since we don't have a transition array for no-terrain
        if (terrainType0 == -1 and terrainType1 == -1):
            return 0
        if (terrainType0 == -1):
            return self.mTerrainTypes.at(terrainType1).transitionDistance(terrainType0)
        return self.mTerrainTypes.at(terrainType0).transitionDistance(terrainType1)

    ##
    # Adds a new tile to the end of the tileset.
    ##
    def addTile(self, image, source=QString()):
        newTile = Tile(image, source, self.tileCount(), self)
        self.mTiles.append(newTile)
        if (self.mTileHeight < image.height()):
            self.mTileHeight = image.height()
        if (self.mTileWidth < image.width()):
            self.mTileWidth = image.width()
        return newTile

    def insertTiles(self, index, tiles):
        count = tiles.count()
        for i in range(count):
            self.mTiles.insert(index + i, tiles.at(i))
        # Adjust the tile IDs of the remaining tiles
        for i in range(index + count, self.mTiles.size()):
            self.mTiles.at(i).mId += count
        self.updateTileSize()

    def removeTiles(self, index, count):
        first = self.mTiles.begin() + index
        last = first + count
        last = self.mTiles.erase(first, last)
        # Adjust the tile IDs of the remaining tiles
        for last in self.mTiles:
            last.mId -= count
        self.updateTileSize()

    ##
    # Sets the \a image to be used for the tile with the given \a id.
    ##
    def setTileImage(self, id, image, source = QString()):
        # This operation is not supposed to be used on tilesets that are based
        # on a single image
        tile = self.tileAt(id)
        if (not tile):
            return
        previousImageSize = tile.image().size()
        newImageSize = image.size()
        tile.setImage(image)
        tile.setImageSource(source)
        if (previousImageSize != newImageSize):
            # Update our max. tile size
            if (previousImageSize.height() == self.mTileHeight or
                    previousImageSize.width() == self.mTileWidth):
                # This used to be the max image; we have to recompute
                self.updateTileSize()
            else:
                # Check if we have a new maximum
                if (self.mTileHeight < newImageSize.height()):
                    self.mTileHeight = newImageSize.height()
                if (self.mTileWidth < newImageSize.width()):
                    self.mTileWidth = newImageSize.width()

    ##
    # Used by the Tile class when its terrain information changes.
    ##
    def markTerrainDistancesDirty(self):
        self.mTerrainDistancesDirty = True

    ##
    # Sets tile size to the maximum size.
    ##
    def updateTileSize(self):
        maxWidth = 0
        maxHeight = 0
        for tile in self.mTiles:
            size = tile.size()
            if (maxWidth < size.width()):
                maxWidth = size.width()
            if (maxHeight < size.height()):
                maxHeight = size.height()

        self.mTileWidth = maxWidth
        self.mTileHeight = maxHeight

    ##
    # Calculates the transition distance matrix for all terrain types.
    ##
    def recalculateTerrainDistances(self):
        # some fancy macros which can search for a value in each byte of a word simultaneously
        def hasZeroByte(dword):
            return (dword - 0x01010101) & ~dword & 0x80808080

        def hasByteEqualTo(dword, value):
            return hasZeroByte(dword ^ int(~0/255 * value))

        # Terrain distances are the number of transitions required before one terrain may meet another
        # Terrains that have no transition path have a distance of -1
        for i in range(self.terrainCount()):
            type = self.terrain(i)
            distance = QVector()
            for _x in range(self.terrainCount() + 1):
                distance.append(-1)
            # Check all tiles for transitions to other terrain types
            for j in range(self.tileCount()):
                t = self.tileAt(j)
                if (not hasByteEqualTo(t.terrain(), i)):
                    continue
                # This tile has transitions, add the transitions as neightbours (distance 1)
                tl = t.cornerTerrainId(0)
                tr = t.cornerTerrainId(1)
                bl = t.cornerTerrainId(2)
                br = t.cornerTerrainId(3)
                # Terrain on diagonally opposite corners are not actually a neighbour
                if (tl == i or br == i):
                    distance[tr + 1] = 1
                    distance[bl + 1] = 1

                if (tr == i or bl == i):
                    distance[tl + 1] = 1
                    distance[br + 1] = 1

                # terrain has at least one tile of its own type
                distance[i + 1] = 0

            type.setTransitionDistances(distance)

        # Calculate indirect transition distances
        bNewConnections = False
        # Repeat while we are still making new connections (could take a
        # number of iterations for distant terrain types to connect)
        while bNewConnections:
            bNewConnections = False
            # For each combination of terrain types
            for i in range(self.terrainCount()):
                t0 = self.terrain(i)
                for j in range(self.terrainCount()):
                    if (i == j):
                        continue
                    t1 = self.terrain(j)
                    # Scan through each terrain type, and see if we have any in common
                    for t in range(-1, self.terrainCount()):
                        d0 = t0.transitionDistance(t)
                        d1 = t1.transitionDistance(t)
                        if (d0 == -1 or d1 == -1):
                            continue
                        # We have cound a common connection
                        d = t0.transitionDistance(j)
                        # If the new path is shorter, record the new distance
                        if (d == -1 or d0 + d1 < d):
                            d = d0 + d1
                            t0.setTransitionDistance(j, d)
                            t1.setTransitionDistance(i, d)
                            # We're making progress, flag for another iteration...
                            bNewConnections = True

    def sharedPointer(self):
        return self.mWeakPointer
Example #4
0
class QtGroupBoxPropertyBrowserPrivate():
    def __init__(self):
        self.q_ptr = None
        self.m_indexToItem = QMap()
        self.m_itemToIndex = QMap()
        self.m_widgetToItem = QMap()
        self.m_mainLayout = 0
        self.m_children = QList()
        self.m_recreateQueue = QList()

    def createEditor(self, property, parent):
        return self.q_ptr.createEditor(property, parent)

    def init(self, parent):
        self.m_mainLayout = QGridLayout()
        parent.setLayout(self.m_mainLayout)
        item = QSpacerItem(0, 0, QSizePolicy.Fixed, QSizePolicy.Expanding)
        self.m_mainLayout.addItem(item, 0, 0)

    def slotEditorDestroyed(self):
        editor = self.q_ptr.sender()
        if (not editor):
            return
        if (not editor in self.m_widgetToItem.keys()):
            return
        self.m_widgetToItem[editor].widget = 0
        self.m_widgetToItem.remove(editor)

    def slotUpdate(self):
        for item in self.m_recreateQueue:
            par = item.parent
            w = 0
            l = 0
            oldRow = -1
            if (not par):
                w = self.q_ptr
                l = self.m_mainLayout
                oldRow = self.m_children.indexOf(item)
            else:
                w = par.groupBox
                l = par.layout
                oldRow = par.children.indexOf(item)
                if (self.hasHeader(par)):
                    oldRow += 2

            if (item.widget):
                item.widget.setParent(w)
            elif (item.widgetLabel):
                item.widgetLabel.setParent(w)
            else:
                item.widgetLabel = QLabel(w)
                item.widgetLabel.setSizePolicy(QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed))
                item.widgetLabel.setTextFormat(Qt.PlainText)

            span = 1
            if (item.widget):
                l.addWidget(item.widget, oldRow, 1, 1, 1)
            elif (item.widgetLabel):
                l.addWidget(item.widgetLabel, oldRow, 1, 1, 1)
            else:
                span = 2
            item.label = QLabel(w)
            item.label.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
            l.addWidget(item.label, oldRow, 0, 1, span)

            self.updateItem(item)

        self.m_recreateQueue.clear()

    def updateLater(self):
        QTimer.singleShot(0, self.q_ptr, self.slotUpdate())

    def propertyInserted(self, index, afterIndex):
        afterItem = self.m_indexToItem[afterIndex]
        parentItem = self.m_indexToItem.value(index.parent())

        newItem = WidgetItem()
        newItem.parent = parentItem

        layout = 0
        parentWidget = 0
        row = -1
        if (not afterItem):
            row = 0
            if (parentItem):
                parentItem.children.insert(0, newItem)
            else:
                self.m_children.insert(0, newItem)
        else:
            if (parentItem):
                row = parentItem.children.indexOf(afterItem) + 1
                parentItem.children.insert(row, newItem)
            else:
                row = self.m_children.indexOf(afterItem) + 1
                self.m_children.insert(row, newItem)

        if (parentItem and self.hasHeader(parentItem)):
            row += 2

        if (not parentItem):
            layout = self.m_mainLayout
            parentWidget = self.q_ptr
        else:
            if not parentItem.groupBox:
                self.m_recreateQueue.removeAll(parentItem)
                par = parentItem.parent
                w = 0
                l = 0
                oldRow = -1
                if (not par):
                    w = self.q_ptr
                    l = self.m_mainLayout
                    oldRow = self.m_children.indexOf(parentItem)
                else:
                    w = par.groupBox
                    l = par.layout
                    oldRow = par.children.indexOf(parentItem)
                    if (self.hasHeader(par)):
                        oldRow += 2

                parentItem.groupBox = QGroupBox(w)
                parentItem.layout = QGridLayout()
                parentItem.groupBox.setLayout(parentItem.layout)
                if (parentItem.label):
                    l.removeWidget(parentItem.label)
                    parentItem.label.close()
                    parentItem.label = 0

                if (parentItem.widget):
                    l.removeWidget(parentItem.widget)
                    parentItem.widget.setParent(parentItem.groupBox)
                    parentItem.layout.addWidget(parentItem.widget, 0, 0, 1, 2)
                    parentItem.line = QFrame(parentItem.groupBox)
                elif (parentItem.widgetLabel):
                    l.removeWidget(parentItem.widgetLabel)
                    parentItem.widgetLabel.close()
                    parentItem.widgetLabel = 0

                if (parentItem.line):
                    parentItem.line.setFrameShape(QFrame.HLine)
                    parentItem.line.setFrameShadow(QFrame.Sunken)
                    parentItem.layout.addWidget(parentItem.line, 1, 0, 1, 2)

                l.addWidget(parentItem.groupBox, oldRow, 0, 1, 2)
                self.updateItem(parentItem)

            layout = parentItem.layout
            parentWidget = parentItem.groupBox

        newItem.label = QLabel(parentWidget)
        newItem.label.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        newItem.widget = self.createEditor(index.property(), parentWidget)
        if (not newItem.widget):
            newItem.widgetLabel = QLabel(parentWidget)
            newItem.widgetLabel.setSizePolicy(QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed))
            newItem.widgetLabel.setTextFormat(Qt.PlainText)
        else:
            newItem.widget.destroyed.connect(self.slotEditorDestroyed)
            self.m_widgetToItem[newItem.widget] = newItem

        self.insertRow(layout, row)
        span = 1
        if (newItem.widget):
            layout.addWidget(newItem.widget, row, 1)
        elif (newItem.widgetLabel):
            layout.addWidget(newItem.widgetLabel, row, 1)
        else:
            span = 2
        layout.addWidget(newItem.label, row, 0, 1, span)

        self.m_itemToIndex[newItem] = index
        self.m_indexToItem[index] = newItem

        self.updateItem(newItem)

    def propertyRemoved(self, index):
        item = self.m_indexToItem[index]

        self.m_indexToItem.remove(index)
        self.m_itemToIndex.remove(item)

        parentItem = item.parent

        row = -1

        if (parentItem):
            row = parentItem.children.indexOf(item)
            parentItem.children.removeAt(row)
            if (self.hasHeader(parentItem)):
                row += 2
        else:
            row = self.m_children.indexOf(item)
            self.m_children.removeAt(row)

        if (item.widget):
            item.widget.close()
            del item.widget
        if (item.label):
            item.label.close()
            del item.label
        if (item.widgetLabel):
            item.widgetLabel.close()
            del item.widgetLabel
        if (item.groupBox):
            item.groupBox.close()
            del item.groupBox

        if (not parentItem):
            self.removeRow(self.m_mainLayout, row)
        elif len(parentItem.children) > 0:
            self.removeRow(parentItem.layout, row)
        else:
            par = parentItem.parent
            l = 0
            oldRow = -1
            if (not par):
                l = self.m_mainLayout
                oldRow = self.m_children.indexOf(parentItem)
            else:
                l = par.layout
                oldRow = par.children.indexOf(parentItem)
                if (self.hasHeader(par)):
                    oldRow += 2

            if (parentItem.widget):
                parentItem.widget.hide()
                parentItem.widget.setParent(0)
            elif (parentItem.widgetLabel):
                parentItem.widgetLabel.hide()
                parentItem.widgetLabel.setParent(0)
            else:
                #parentItem.widgetLabel = QLabel(w)
                pass

            l.removeWidget(parentItem.groupBox)
            parentItem.groupBox.close()
            parentItem.groupBox = 0
            parentItem.line = 0
            parentItem.layout = 0
            if (not parentItem in self.m_recreateQueue):
                 self.m_recreateQueue.append(parentItem)
            self.updateLater()

        self.m_recreateQueue.removeAll(item)

        del item

    def insertRow(self, layout, row):
        itemToPos = QMap()
        idx = 0
        while (idx < layout.count()):
            r, c, rs, cs = layout.getItemPosition(idx)
            if (r >= row):
                itemToPos[layout.takeAt(idx)] = QRect(r + 1, c, rs, cs)
            else:
                idx += 1

        for it in itemToPos.keys():
            r = itemToPos[it]
            layout.addItem(it, r.x(), r.y(), r.width(), r.height())

    def removeRow(self, layout, row):
        itemToPos = QMap()
        idx = 0
        while (idx < layout.count()):
            r, c, rs, cs = layout.getItemPosition(idx)
            if (r > row):
                itemToPos[layout.takeAt(idx)] = QRect(r - 1, c, rs, cs)
            else:
                idx += 1

        for it in itemToPos.keys():
            r = itemToPos[it]
            layout.addItem(it, r.x(), r.y(), r.width(), r.height())

    def hasHeader(self, item):
        if (item.widget):
            return True
        return False

    def propertyChanged(self, index):
        item = self.m_indexToItem[index]

        self.updateItem(item)

    def updateItem(self, item):
        property = self.m_itemToIndex[item].property()
        if (item.groupBox):
            font = item.groupBox.font()
            font.setUnderline(property.isModified())
            item.groupBox.setFont(font)
            item.groupBox.setTitle(property.propertyName())
            item.groupBox.setToolTip(property.toolTip())
            item.groupBox.setStatusTip(property.statusTip())
            item.groupBox.setWhatsThis(property.whatsThis())
            item.groupBox.setEnabled(property.isEnabled())

        if (item.label):
            font = item.label.font()
            font.setUnderline(property.isModified())
            item.label.setFont(font)
            item.label.setText(property.propertyName())
            item.label.setToolTip(property.toolTip())
            item.label.setStatusTip(property.statusTip())
            item.label.setWhatsThis(property.whatsThis())
            item.label.setEnabled(property.isEnabled())

        if (item.widgetLabel):
            font = item.widgetLabel.font()
            font.setUnderline(False)
            item.widgetLabel.setFont(font)
            item.widgetLabel.setText(property.valueText())
            item.widgetLabel.setToolTip(property.valueText())
            item.widgetLabel.setEnabled(property.isEnabled())

        if (item.widget):
            font = item.widget.font()
            font.setUnderline(False)
            item.widget.setFont(font)
            item.widget.setEnabled(property.isEnabled())
            item.widget.setToolTip(property.valueText())
Example #5
0
class Map(Object):

    ##
    # The orientation of the map determines how it should be rendered. An
    # Orthogonal map is using rectangular tiles that are aligned on a
    # straight grid. An Isometric map uses diamond shaped tiles that are
    # aligned on an isometric projected grid. A Hexagonal map uses hexagon
    # shaped tiles that fit into each other by shifting every other row.
    ##
    class Orientation(Enum):
        Unknown, Orthogonal, Isometric, Staggered, Hexagonal = range(5)

    ##
    # The different formats in which the tile layer data can be stored.
    ##
    class LayerDataFormat(Enum):
        XML        = 0
        Base64     = 1
        Base64Gzip = 2
        Base64Zlib = 3
        CSV        = 4

    ##
    # The order in which tiles are rendered on screen.
    ##
    class RenderOrder(Enum):
        RightDown  = 0
        RightUp    = 1
        LeftDown   = 2
        LeftUp     = 3

    ##
    # Which axis is staggered. Only used by the isometric staggered and
    # hexagonal map renderers.
    ##
    class StaggerAxis(Enum):
        StaggerX, StaggerY = range(2)

    ##
    # When staggering, specifies whether the odd or the even rows/columns are
    # shifted half a tile right/down. Only used by the isometric staggered and
    # hexagonal map renderers.
    ##
    class StaggerIndex(Enum):
        StaggerOdd  = 0
        StaggerEven = 1

    def __init__(self, *args):
        self.mOrientation = 0
        self.mRenderOrder = 0
        self.mWidth = 0
        self.mHeight = 0
        self.mTileWidth = 0
        self.mTileHeight = 0
        self.mHexSideLength = 0
        self.mStaggerAxis = 0
        self.mStaggerIndex = 0
        self.mBackgroundColor = QColor()
        self.mDrawMargins = QMargins()
        self.mLayers = QList()
        self.mTilesets = QVector()
        self.mLayerDataFormat = None
        self.mNextObjectId = 0

        l = len(args)
        if l==1:
            ##
            # Copy constructor. Makes sure that a deep-copy of the layers is created.
            ##
            map = args[0]
            super().__init__(map)

            self.mLayers = QList()
            self.mOrientation = map.mOrientation
            self.mRenderOrder = map.mRenderOrder
            self.mWidth = map.mWidth
            self.mHeight = map.mHeight
            self.mTileWidth = map.mTileWidth
            self.mTileHeight = map.mTileHeight
            self.mHexSideLength = map.mHexSideLength
            self.mStaggerAxis = map.mStaggerAxis
            self.mStaggerIndex = map.mStaggerIndex
            self.mBackgroundColor = map.mBackgroundColor
            self.mDrawMargins = map.mDrawMargins
            self.mTilesets = map.mTilesets
            self.mLayerDataFormat = map.mLayerDataFormat
            self.mNextObjectId = 1
            for layer in map.mLayers:
                clone = layer.clone()
                clone.setMap(self)
                self.mLayers.append(clone)
        elif l==5:
            ##
            # Constructor, taking map orientation, size and tile size as parameters.
            ##
            orientation, width, height, tileWidth, tileHeight = args
            super().__init__(Object.MapType)

            self.mLayers = QList()
            self.mTilesets = QList()
            self.mOrientation = orientation
            self.mRenderOrder = Map.RenderOrder.RightDown
            self.mWidth = width
            self.mHeight = height
            self.mTileWidth = tileWidth
            self.mTileHeight = tileHeight
            self.mHexSideLength = 0
            self.mStaggerAxis = Map.StaggerAxis.StaggerY
            self.mStaggerIndex = Map.StaggerIndex.StaggerOdd
            self.mLayerDataFormat = Map.LayerDataFormat.Base64Zlib
            self.mNextObjectId = 1

    ##
    # Destructor.
    ##
    def __del__(self):
        self.mLayers.clear()

    ##
    # Returns the orientation of the map.
    ##
    def orientation(self):
        return self.mOrientation

    ##
    # Sets the orientation of the map.
    ##
    def setOrientation(self, orientation):
        self.mOrientation = orientation

    ##
    # Returns the render order of the map.
    ##
    def renderOrder(self):
        return self.mRenderOrder

    ##
    # Sets the render order of the map.
    ##
    def setRenderOrder(self, renderOrder):
        self.mRenderOrder = renderOrder

    ##
    # Returns the width of this map in tiles.
    ##
    def width(self):
        return self.mWidth

    ##
    # Sets the width of this map in tiles.
    ##
    def setWidth(self, width):
        self.mWidth = width

    ##
    # Returns the height of this map in tiles.
    ##
    def height(self):
        return self.mHeight

    ##
    # Sets the height of this map in tiles.
    ##
    def setHeight(self, height):
        self.mHeight = height

    ##
    # Returns the size of this map. Provided for convenience.
    ##
    def size(self):
        return QSize(self.mWidth, self.mHeight)

    ##
    # Returns the tile width of this map.
    ##
    def tileWidth(self):
        return self.mTileWidth

    ##
    # Sets the width of one tile.
    ##
    def setTileWidth(self, width):
        self.mTileWidth = width

    ##
    # Returns the tile height used by this map.
    ##
    def tileHeight(self):
        return self.mTileHeight

    ##
    # Sets the height of one tile.
    ##
    def setTileHeight(self, height):
        self.mTileHeight = height

    ##
    # Returns the size of one tile. Provided for convenience.
    ##
    def tileSize(self):
        return QSize(self.mTileWidth, self.mTileHeight)

    def hexSideLength(self):
        return self.mHexSideLength

    def setHexSideLength(self, hexSideLength):
        self.mHexSideLength = hexSideLength

    def staggerAxis(self):
        return self.mStaggerAxis

    def setStaggerAxis(self, staggerAxis):
        self.mStaggerAxis = staggerAxis

    def staggerIndex(self):
        return self.mStaggerIndex

    def setStaggerIndex(self, staggerIndex):
        self.mStaggerIndex = staggerIndex

    ##
    # Adjusts the draw margins to be at least as big as the given margins.
    # Called from tile layers when their tiles change.
    ##
    def adjustDrawMargins(self, margins):
        # The TileLayer includes the maximum tile size in its draw margins. So
        # we need to subtract the tile size of the map, since that part does not
        # contribute to additional margin.
        self.mDrawMargins = maxMargins(QMargins(margins.left(),
                                           margins.top() - self.mTileHeight,
                                           margins.right() - self.mTileWidth,
                                           margins.bottom()),
                                  self.mDrawMargins)

    ##
    # Computes the extra margins due to layer offsets. These need to be taken into
    # account when determining the bounding rect of the map for example.
    ##
    def computeLayerOffsetMargins(self):
        offsetMargins = QMargins()

        for layer in self.mLayers:
            offset = layer.offset()
            offsetMargins = maxMargins(QMargins(math.ceil(-offset.x()),
                                                math.ceil(-offset.y()),
                                                math.ceil(offset.x()),
                                                math.ceil(offset.y())),
                                       offsetMargins)

        return offsetMargins

    ##
    # Returns the margins that have to be taken into account when figuring
    # out which part of the map to repaint after changing some tiles.
    #
    # @see TileLayer.drawMargins
    ##
    def drawMargins(self):
        return self.mDrawMargins

    ##
    # Recomputes the draw margins for this map and each of its tile layers. Needed
    # after the tile offset of a tileset has changed for example.
    #
    # \sa TileLayer.recomputeDrawMargins
    ##
    def recomputeDrawMargins(self):
        self.mDrawMargins = QMargins()
        for layer in self.mLayers:
            tileLayer = layer.asTileLayer()
            if tileLayer:
                tileLayer.recomputeDrawMargins()

    ##
    # Returns the number of layers of this map.
    ##
    def layerCount(self, *args):
        l = len(args)
        if l==0:
            return self.mLayers.size()
        elif l==1:
            ##
            # Convenience function that returns the number of layers of this map that
            # match the given \a type.
            ##
            tp = args[0]

            count = 0
            for layer in self.mLayers:
               if (layer.layerType() == tp):
                   count += 1
            return count

    def tileLayerCount(self):
        return self.layerCount(Layer.TileLayerType)

    def objectGroupCount(self):
        return self.layerCount(Layer.ObjectGroupType)

    def imageLayerCount(self):
        return self.layerCount(Layer.ImageLayerType)

    ##
    # Returns the layer at the specified index.
    ##
    def layerAt(self, index):
        return self.mLayers.at(index)

    ##
    # Returns the list of layers of this map. This is useful when you want to
    # use foreach.
    ##
    def layers(self, *args):
        l = len(args)
        if l==0:
            return QList(self.mLayers)
        elif l==1:
            tp = args[0]
            layers = QList()
            for layer in self.mLayers:
                if (layer.layerType() == tp):
                    layers.append(layer)
            return layers

    def objectGroups(self):
        layers = QList()
        for layer in self.mLayers:
            og = layer.asObjectGroup()
            if og:
                layers.append(og)
        return layers

    def tileLayers(self):
        layers = QList()
        for layer in self.mLayers:
            tl = layer.asTileLayer()
            if tl:
                layers.append(tl)
        return layers

    ##
    # Adds a layer to this map.
    ##
    def addLayer(self, layer):
        self.adoptLayer(layer)
        self.mLayers.append(layer)

    ##
    # Returns the index of the layer given by \a layerName, or -1 if no
    # layer with that name is found.
    #
    # The second optional parameter specifies the layer types which are
    # searched.
    ##
    def indexOfLayer(self, layerName, layertypes = Layer.AnyLayerType):
        for index in range(self.mLayers.size()):
            if (self.layerAt(index).name() == layerName and (layertypes & self.layerAt(index).layerType())):
                return index
        return -1

    ##
    # Adds a layer to this map, inserting it at the given index.
    ##
    def insertLayer(self, index, layer):
        self.adoptLayer(layer)
        self.mLayers.insert(index, layer)

    ##
    # Removes the layer at the given index from this map and returns it.
    # The caller becomes responsible for the lifetime of this layer.
    ##
    def takeLayerAt(self, index):
        layer = self.mLayers.takeAt(index)
        layer.setMap(None)
        return layer

    ##
    # Adds a tileset to this map. The map does not take ownership over its
    # tilesets, this is merely for keeping track of which tilesets are used by
    # the map, and their saving order.
    #
    # @param tileset the tileset to add
    ##
    def addTileset(self, tileset):
        self.mTilesets.append(tileset)

    ##
    # Convenience function to be used together with Layer.usedTilesets()
    ##
    def addTilesets(self, tilesets):
        for tileset in tilesets:
            self.addTileset(tileset)
            
    ##
    # Inserts \a tileset at \a index in the list of tilesets used by this map.
    ##
    def insertTileset(self, index, tileset):
        self.mTilesets.insert(index, tileset)

    ##
    # Returns the index of the given \a tileset, or -1 if it is not used in
    # this map.
    ##
    def indexOfTileset(self, tileset):
        return self.mTilesets.indexOf(tileset)

    ##
    # Removes the tileset at \a index from this map.
    #
    # \warning Does not make sure that this map no longer refers to tiles from
    #          the removed tileset!
    #
    # \sa addTileset
    ##
    def removeTilesetAt(self, index):
        self.mTilesets.removeAt(index)

    ##
    # Replaces all tiles from \a oldTileset with tiles from \a newTileset.
    # Also replaces the old tileset with the new tileset in the list of
    # tilesets.
    ##
    def replaceTileset(self, oldTileset, newTileset):
        index = self.mTilesets.indexOf(oldTileset)
        for layer in self.mLayers:
            layer.replaceReferencesToTileset(oldTileset, newTileset)
        self.mTilesets[index] = newTileset

    ##
    # Returns the number of tilesets of this map.
    ##
    def tilesetCount(self):
        return self.mTilesets.size()

    ##
    # Returns the tileset at the given index.
    ##
    def tilesetAt(self, index):
        return self.mTilesets.at(index)

    ##
    # Returns the tilesets that the tiles on this map are using.
    ##
    def tilesets(self):
        return QList(self.mTilesets)

    ##
    # Returns the background color of this map.
    ##
    def backgroundColor(self):
        return QColor(self.mBackgroundColor)

    ##
    # Sets the background color of this map.
    ##
    def setBackgroundColor(self, color):
        self.mBackgroundColor = color

    ##
    # Returns whether the given \a tileset is used by any tile layer of this
    # map.
    ##
    def isTilesetUsed(self, tileset):
        for layer in self.mLayers:
            if (layer.referencesTileset(tileset)):
                return True
        return False

    ##
    # Creates a new map that contains the given \a layer. The map size will be
    # determined by the size of the layer.
    #
    # The orientation defaults to Unknown and the tile width and height will
    # default to 0. In case this map needs to be rendered, these properties
    # will need to be properly set.
    ##
    def fromLayer(layer):
        result = Map(Map.Orientation.Unknown, layer.width(), layer.height(), 0, 0)
        result.addLayer(layer)
        return result

    def layerDataFormat(self):
        return self.mLayerDataFormat

    def setLayerDataFormat(self, format):
        self.mLayerDataFormat = format

    ##
    # Sets the next id to be used for objects on this map.
    ##
    def setNextObjectId(self, nextId):
        self.mNextObjectId = nextId

    ##
    # Returns the next object id for this map.
    ##
    def nextObjectId(self):
        return self.mNextObjectId

    ##
    # Returns the next object id for this map and allocates a new one.
    ##
    def takeNextObjectId(self):
        return self.mNextObjectId+1

    def adoptLayer(self, layer):
        layer.setMap(self)
        tileLayer = layer.asTileLayer()
        if tileLayer:
            self.adjustDrawMargins(tileLayer.drawMargins())
        group = layer.asObjectGroup()
        if group:
            for o in group.objects():
                if (o.id() == 0):
                    o.setId(self.takeNextObjectId())
Example #6
0
class PropertyWidget(QDockWidget, Ui_PropertyWidget):
    itemUpdated = pyqtSignal(int)
    nameUpdated = pyqtSignal(int)
    
    def __init__(self, parent=None):
        super(PropertyWidget, self).__init__(parent)
        self.setupUi(self)
        
        self.groupManager = QtGroupPropertyManager(self)
        self.boolManager = QtBoolPropertyManager(self)
        self.intManager = QtIntPropertyManager(self)
        self.doubleManager = QtDoublePropertyManager(self)
        self.stringManager = QtStringPropertyManager(self)
        self.colorManager = QtColorPropertyManager(self)
        self.enumManager = QtEnumPropertyManager(self)
        self.pointManager = QtPointFPropertyManager(self)
        
        self.boolManager.valueChangedSignal.connect(self.valueChanged)
        self.intManager.valueChangedSignal.connect(self.valueChanged)
        self.doubleManager.valueChangedSignal.connect(self.valueChanged)
        self.stringManager.valueChangedSignal.connect(self.valueChanged)
        self.colorManager.valueChangedSignal.connect(self.valueChanged)
        self.enumManager.valueChangedSignal.connect(self.valueChanged)
        self.pointManager.valueChangedSignal.connect(self.valueChanged)

        
        checkBoxFactory = QtCheckBoxFactory(self)
        spinBoxFactory = QtSpinBoxFactory(self)
        doubleSpinBoxFactory = QtDoubleSpinBoxFactory(self)
        lineEditFactory = QtLineEditFactory(self)
        comboBoxFactory = QtEnumEditorFactory(self)
        charEditfactory = QtCharEditorFactory(self)


        self.prop_browser.setFactoryForManager(\
            self.boolManager, checkBoxFactory)
        self.prop_browser.setFactoryForManager(\
            self.intManager, spinBoxFactory)
        self.prop_browser.setFactoryForManager(\
            self.doubleManager, doubleSpinBoxFactory)
        self.prop_browser.setFactoryForManager(\
            self.stringManager, lineEditFactory)
        self.prop_browser.setFactoryForManager(\
            self.colorManager.subIntPropertyManager(), spinBoxFactory)
        self.prop_browser.setFactoryForManager(\
            self.enumManager, comboBoxFactory)
            
        self.prop_browser.setFactoryForManager(\
            self.pointManager.subDoublePropertyManager(), doubleSpinBoxFactory)

        self.selectedNodeID_ = -1
        
        # for CS Nodes
        #self.CS_ = {}
        self.EnumToCSID_ = {}
        self.CSIDtoEnum_ = {}
        self.CSNames_ = QList()
        
        # for Material Nodes
        self.EnumToMatID_ = {}
        self.MatIDtoEnum_ = {}
        self.MatNames_ = QList()
        
    # Slot functions -----------------------------------------------------------
    @pyqtSlot(QList)
    def nodesSelected(self, nodeIDs):
        self.prop_browser.clear()
        nodeID = nodeIDs.last()
        if nodeID == None:
            return
        
        node = bzmag.getObject(nodeID)
        if node == None:
            self.selectedNode_ = None
            self.line_path.setText('')
            return
        
        path = node.getAbsolutePath()
        
        #self.CS_.clear()
        self.EnumToCSID_.clear()
        self.CSIDtoEnum_.clear()
        self.CSNames_.clear()
        
        self.EnumToMatID_.clear()
        self.MatIDtoEnum_.clear()
        self.MatNames_.clear()
        
        cs_root = bzmag.get('/coordinate')
        self.build_ObjectItem(cs_root, self.CSIDtoEnum_, self.EnumToCSID_, self.CSNames_, 'Global')
        
        mat_root = bzmag.get('/material')
        self.build_ObjectItem(mat_root, self.MatIDtoEnum_, self.EnumToMatID_, self.MatNames_, 'Vaccum')
        
        if path == '' : path = '/'
        self.line_path.setText(path)
        self.line_path.setReadOnly(True)
        
        #print('Binding node path', path)
        #node = bzmag.get(path)    
        self.selectedNodeID_ = nodeID
        
        
        for type_name in node.getGenerations():
            group = self.groupManager.addProperty(type_name)
            prop_names = node.getPropertyNames(type_name)
            if not prop_names:
                continue
         
            for prop_name in prop_names:
                prop_name, prop_value, prop_type, readonly = \
                    node.getProperty(prop_name)
                #print(prop_name, prop_value, prop_type, readonly)
                self.add_property(group, prop_name, prop_value, prop_type, readonly)
            
            #print('Property Browse : Add Property...', group)
            self.prop_browser.addProperty(group)

    # ------------------------------------------------------------------------     
    def valueChanged(self, property, value):
        if self.selectedNodeID_ == -1:
            return
        
        nodeID = self.selectedNodeID_
        node = bzmag.getObject(nodeID)
        prop_name = property.propertyName()
        prop_name, prop_value, prop_type, readonly = node.getProperty(prop_name)
        #print(prop_name, prop_value, prop_type, readonly)
            
        # when type of the value is 'QColor', make value as '[r, g, b, a]'
        #if type(value).__name__ == 'QColor':
        if prop_type == 'color':
            value = '{}, {}, {}, {}'.format(value.red(), value.green(), \
                value.blue(), value.alpha())
        
        if prop_type == 'node':
            #print('Node value changed : ', value)
            #if value in self.EnumToCSID_.keys():
            #value = self.EnumToCSID_[value]
            
            if prop_name == 'CoordinateSystem':
                value = self.EnumToCSID_[value]
                
            elif prop_name == 'Material':
                value = self.EnumToMatID_[value]

        # when the node is not readonly , update the property value
        if not readonly:
            #print(node, prop_name, str(value))
            node.setProperty(str(prop_name), str(value))
            
        # when the property is 'name' update NOHTree and Node Path
        if prop_name == 'name':
            path = node.getAbsolutePath()
            self.nameUpdated.emit(nodeID)
            self.line_path.setText(path)
            
        self.itemUpdated.emit(nodeID)
        
    # private members -----------------------------------------------------------
    def add_property(self, group, name, value, type, readonly):
        # type is boolean
        if type == 'bool':
            item = self.boolManager.addProperty(name)   
            self.boolManager.setValue(item, (value == 'true'))
            #self.boolManager.setReadOnly(item, readonly)
            group.addSubProperty(item)

        # type is integer types
        elif type == 'int' or type == 'uint' or type == 'int16' or type == 'uint16' or\
             type == 'int32' or type == 'uint32' or type == 'int64' or type == 'uint64':
            item = self.intManager.addProperty(name)
            #self.intManager.setRange(item, 0, 256)
            self.intManager.setValue(item, int(value))
            self.intManager.setReadOnly(item, readonly)
            group.addSubProperty(item)
            
        # type is floating types (float32 or float64)
        elif type == 'float32' or type == 'float64':
            item = self.doubleManager.addProperty(self.tr(name))
            self.doubleManager.setValue(item, float(value))
            #self.doubleManager.setRange(item, 0, 256)
            self.doubleManager.setReadOnly(item, readonly)
            group.addSubProperty(item)
        
        # type is string types (stirng or uri)
        elif type == 'string' or type == 'uri':
            item = self.stringManager.addProperty(name)
            self.stringManager.setValue(item, value)
            self.stringManager.setReadOnly(item, readonly)
            group.addSubProperty(item)
            
            if name == 'name':
                regExp = QRegExp('^[a-z + A-Z + _]+\\w+$')
                self.stringManager.setRegExp(item, regExp)
        
        # type == color
        elif type == 'color':
            item = self.colorManager.addProperty(name)
            rgba = value.split(',')
            color = QColor(int(rgba[0]), int(rgba[1]), int(rgba[2]), int(rgba[3]))
            self.colorManager.setValue(item, color)
            group.addSubProperty(item)
        
        # type == vector2
        elif type == 'vector2': 
            item = self.pointManager.addProperty(name)
            xy = value.split(',')
            pt = QPointF(float(xy[0]), float(xy[1]))
            self.pointManager.setValue(item, pt)
            group.addSubProperty(item)
            
        # type == node or object
        elif type == 'object' or type == 'node':
            NodeID = int(value)
            if name == 'CoordinateSystem':
                item = self.enumManager.addProperty(name)
                self.enumManager.setEnumNames(item, self.CSNames_)
                self.enumManager.setValue(item, self.CSIDtoEnum_[NodeID])
                group.addSubProperty(item)
                
            elif name == 'Material':
                item = self.enumManager.addProperty(name)
                self.enumManager.setEnumNames(item, self.MatNames_)
                self.enumManager.setValue(item, self.MatIDtoEnum_[NodeID])
                group.addSubProperty(item)
            
            
    # ------------------------------------------------------------------------        
    def build_ObjectItem(self, parent, IDtoEnum, EnumToID, Name, DefaultName, index = 0):
        if index == 0:
            IDtoEnum[-1] = index
            EnumToID[index] = -1
            Name.append(DefaultName)
            index = index + 1
            
        for node in parent.getChildren():
            IDtoEnum[node.getID()] = index
            EnumToID[index] = node.getID()
            Name.append(node.getName())
            index = index + 1
            
            self.build_ObjectItem(node, IDtoEnum, EnumToID, Name, DefaultName, index)
Example #7
0
class AutoMapper(QObject):
    ##
    # Constructs an AutoMapper.
    # All data structures, which only rely on the rules map are setup
    # here.
    #
    # @param workingDocument: the map to work on.
    # @param rules: The rule map which should be used for automapping
    # @param rulePath: The filepath to the rule map.
    ##
    def __init__(self, workingDocument, rules, rulePath):
        ##
        # where to work in
        ##
        self.mMapDocument = workingDocument

        ##
        # the same as mMapDocument.map()
        ##
        self.mMapWork = None
        if workingDocument:
            self.mMapWork = workingDocument.map()

        ##
        # map containing the rules, usually different than mMapWork
        ##
        self.mMapRules = rules

        ##
        # This contains all added tilesets as pointers.
        # if rules use Tilesets which are not in the mMapWork they are added.
        # keep track of them, because we need to delete them afterwards,
        # when they still are unused
        # they will be added while setupTilesets().
        ##
        self.mAddedTilesets = QVector()

        ##
        # description see: mAddedTilesets, just described by Strings
        ##
        self.mAddedTileLayers = QList()

        ##
        # Points to the tilelayer, which defines the inputregions.
        ##
        self.mLayerInputRegions = None

        ##
        # Points to the tilelayer, which defines the outputregions.
        ##
        self.mLayerOutputRegions = None

        ##
        # Contains all tilelayer pointers, which names begin with input*
        # It is sorted by index and name
        ##
        self.mInputRules = InputLayers()

        ##
        # List of Regions in mMapRules to know where the input rules are
        ##
        self.mRulesInput = QList()

        ##
        # List of regions in mMapRules to know where the output of a
        # rule is.
        # mRulesOutput[i] is the output of that rule,
        # which has the input at mRulesInput[i], meaning that mRulesInput
        # and mRulesOutput must match with the indexes.
        ##
        self.mRulesOutput = QList()

        ##
        # The inner set with layers to indexes is needed for translating
        # tile layers from mMapRules to mMapWork.
        #
        # The key is the pointer to the layer in the rulemap. The
        # pointer to the layer within the working map is not hardwired, but the
        # position in the layerlist, where it was found the last time.
        # This loosely bound pointer ensures we will get the right layer, since we
        # need to check before anyway, and it is still fast.
        #
        # The list is used to hold different translation tables
        # => one of the tables is chosen by chance, so randomness is available
        ##
        self.mLayerList = QList()
        ##
        # store the name of the processed rules file, to have detailed
        # error messages available
        ##
        self.mRulePath = rulePath

        ##
        # determines if all tiles in all touched layers should be deleted first.
        ##
        self.mDeleteTiles = False

        ##
        # This variable determines, how many overlapping tiles should be used.
        # The bigger the more area is remapped at an automapping operation.
        # This can lead to higher latency, but provides a better behavior on
        # interactive automapping.
        # It defaults to zero.
        ##
        self.mAutoMappingRadius = 0

        ##
        # Determines if a rule is allowed to overlap it
        ##
        self.mNoOverlappingRules = False

        self.mTouchedObjectGroups = QSet()
        self.mWarning = QString()
        self.mTouchedTileLayers = QSet()
        self.mError = ''

        if (not self.setupRuleMapProperties()):
            return
        if (not self.setupRuleMapTileLayers()):
            return
        if (not self.setupRuleList()):
            return

    def __del__(self):
        self.cleanUpRulesMap()

    ##
    # Checks if the passed \a ruleLayerName is used in this instance
    # of Automapper.
    ##
    def ruleLayerNameUsed(self, ruleLayerName):
        return self.mInputRules.names.contains(ruleLayerName)

    ##
    # Call prepareLoad first! Returns a set of strings describing the tile
    # layers, which could be touched considering the given layers of the
    # rule map.
    ##
    def getTouchedTileLayers(self):
        return self.mTouchedTileLayers

    ##
    # This needs to be called directly before the autoMap call.
    # It sets up some data structures which change rapidly, so it is quite
    # painful to keep these datastructures up to date all time. (indices of
    # layers of the working map)
    ##
    def prepareAutoMap(self):
        self.mError = ''
        self.mWarning = ''
        if (not self.setupMissingLayers()):
            return False
        if (not self.setupCorrectIndexes()):
            return False
        if (not self.setupTilesets(self.mMapRules, self.mMapWork)):
            return False
        return True

    ##
    # Here is done all the automapping.
    ##
    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))

        #where = where.united(ret)

    ##
    # This cleans all datastructures, which are setup via prepareAutoMap,
    # so the auto mapper becomes ready for its next automatic mapping.
    ##
    def cleanAll(self):
        self.cleanTilesets()
        self.cleanTileLayers()

    ##
    # Contains all errors until operation was canceled.
    # The errorlist is cleared within prepareLoad and prepareAutoMap.
    ##
    def errorString(self):
        return self.mError

    ##
    # Contains all warnings which occur at loading a rules map or while
    # automapping.
    # The errorlist is cleared within prepareLoad and prepareAutoMap.
    ##
    def warningString(self):
        return self.mWarning

    ##
    # Reads the map properties of the rulesmap.
    # @return returns True when anything is ok, False when errors occured.
    ##
    def setupRuleMapProperties(self):
        properties = self.mMapRules.properties()
        for key in properties.keys():
            value = properties.value(key)
            raiseWarning = True
            if (key.toLower() == "deletetiles"):
                if (value.canConvert(QVariant.Bool)):
                    self.mDeleteTiles = value.toBool()
                    raiseWarning = False
            elif (key.toLower() == "automappingradius"):
                if (value.canConvert(QVariant.Int)):
                    self.mAutoMappingRadius = value
                    raiseWarning = False
            elif (key.toLower() == "nooverlappingrules"):
                if (value.canConvert(QVariant.Bool)):
                    self.mNoOverlappingRules = value.toBool()
                    raiseWarning = False

            if (raiseWarning):
                self.mWarning += self.tr(
                    "'%s': Property '%s' = '%s' does not make sense. \nIgnoring this property."
                    % (self.mRulePath, key, value.toString()) + '\n')

        return True

    def cleanUpRulesMap(self):
        self.cleanTilesets()
        # mMapRules can be empty, when in prepareLoad the very first stages fail.
        if (not self.mMapRules):
            return
        tilesetManager = TilesetManager.instance()
        tilesetManager.removeReferences(self.mMapRules.tilesets())
        del self.mMapRules
        self.mMapRules = None
        self.cleanUpRuleMapLayers()
        self.mRulesInput.clear()
        self.mRulesOutput.clear()

    ##
    # Searches the rules layer for regions and stores these in \a rules.
    # @return returns True when anything is ok, False when errors occured.
    ##
    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

    ##
    # Sets up the layers in the rules map, which are used for automapping.
    # The layers are detected and put in the internal data structures
    # @return returns True when anything is ok, False when errors occured.
    ##
    def setupRuleMapTileLayers(self):
        error = QString()
        for layer in self.mMapRules.layers():
            layerName = layer.name()
            if (layerName.lower().startswith("regions")):
                treatAsBoth = layerName.toLower() == "regions"
                if (layerName.lower().endswith("input") or treatAsBoth):
                    if (self.mLayerInputRegions):
                        error += self.tr(
                            "'regions_input' layer must not occur more than once.\n"
                        )

                    if (layer.isTileLayer()):
                        self.mLayerInputRegions = layer.asTileLayer()
                    else:
                        error += self.tr(
                            "'regions_*' layers must be tile layers.\n")

                if (layerName.lower().endswith("output") or treatAsBoth):
                    if (self.mLayerOutputRegions):
                        error += self.tr(
                            "'regions_output' layer must not occur more than once.\n"
                        )

                    if (layer.isTileLayer()):
                        self.mLayerOutputRegions = layer.asTileLayer()
                    else:
                        error += self.tr(
                            "'regions_*' layers must be tile layers.\n")

                continue

            nameStartPosition = layerName.indexOf('_') + 1
            # name is all characters behind the underscore (excluded)
            name = layerName.right(layerName.size() - nameStartPosition)
            # group is all before the underscore (included)
            index = layerName.left(nameStartPosition)
            if (index.lower().startswith("output")):
                index.remove(0, 6)
            elif (index.lower().startswith("inputnot")):
                index.remove(0, 8)
            elif (index.lower().startswith("input")):
                index.remove(0, 5)
            # both 'rule' and 'output' layers will require and underscore and
            # rely on the correct position detected of the underscore
            if (nameStartPosition == 0):
                error += self.tr(
                    "Did you forget an underscore in layer '%d'?\n" %
                    layerName)
                continue

            if (layerName.startsWith("input", Qt.CaseInsensitive)):
                isNotList = layerName.lower().startswith("inputnot")
                if (not layer.isTileLayer()):
                    error += self.tr(
                        "'input_*' and 'inputnot_*' layers must be tile layers.\n"
                    )
                    continue

                self.mInputRules.names.insert(name)
                if (not self.mInputRules.indexes.contains(index)):
                    self.mInputRules.indexes.insert(index)
                    self.mInputRules.insert(index, InputIndex())

                if (not self.mInputRules[index].names.contains(name)):
                    self.mInputRules[index].names.insert(name)
                    self.mInputRules[index].insert(name, InputIndexName())

                if (isNotList):
                    self.mInputRules[index][name].listNo.append(
                        layer.asTileLayer())
                else:
                    self.mInputRules[index][name].listYes.append(
                        layer.asTileLayer())
                continue

            if layerName.lower().startswith("output"):
                if (layer.isTileLayer()):
                    self.mTouchedTileLayers.insert(name)
                else:
                    self.mTouchedObjectGroups.insert(name)
                type = layer.layerType()
                layerIndex = self.mMapWork.indexOfLayer(name, type)
                found = False
                for translationTable in self.mLayerList:
                    if (translationTable.index == index):
                        translationTable.insert(layer, layerIndex)
                        found = True
                        break

                if (not found):
                    self.mLayerList.append(RuleOutput())
                    self.mLayerList.last().insert(layer, layerIndex)
                    self.mLayerList.last().index = index

                continue

            error += self.tr(
                "Layer '%s' is not recognized as a valid layer for Automapping.\n"
                % layerName)

        if (not self.mLayerInputRegions):
            error += self.tr("No 'regions' or 'regions_input' layer found.\n")
        if (not self.mLayerOutputRegions):
            error += self.tr("No 'regions' or 'regions_output' layer found.\n")
        if (self.mInputRules.isEmpty()):
            error += self.tr("No input_<name> layer found!\n")
        # no need to check for mInputNotRules.size() == 0 here.
        # these layers are not necessary.
        if error != '':
            error = self.mRulePath + '\n' + error
            self.mError += error
            return False

        return True

    ##
    # Checks if all needed layers in the working map are there.
    # If not, add them in the correct order.
    ##
    def setupMissingLayers(self):
        # make sure all needed layers are there:
        for name in self.mTouchedTileLayers:
            if (self.mMapWork.indexOfLayer(name, Layer.TileLayerType) != -1):
                continue
            index = self.mMapWork.layerCount()
            tilelayer = TileLayer(name, 0, 0, self.mMapWork.width(),
                                  self.mMapWork.height())
            self.mMapDocument.undoStack().push(
                AddLayer(self.mMapDocument, index, tilelayer))
            self.mAddedTileLayers.append(name)

        for name in self.mTouchedObjectGroups:
            if (self.mMapWork.indexOfLayer(name, Layer.ObjectGroupType) != -1):
                continue
            index = self.mMapWork.layerCount()
            objectGroup = ObjectGroup(name, 0, 0, self.mMapWork.width(),
                                      self.mMapWork.height())
            self.mMapDocument.undoStack().push(
                AddLayer(self.mMapDocument, index, objectGroup))
            self.mAddedTileLayers.append(name)

        return True

    ##
    # Checks if the layers setup as in setupRuleMapLayers are still right.
    # If it's not right, correct them.
    # @return returns True if everything went fine. False is returned when
    #         no set layer was found
    ##
    def setupCorrectIndexes(self):
        # make sure all indexes of the layer translationtables are correct.
        for i in range(self.mLayerList.size()):
            translationTable = self.mLayerList.at(i)
            for layerKey in translationTable.keys():
                name = layerKey.name()
                pos = name.indexOf('_') + 1
                name = name.right(name.length() - pos)
                index = translationTable.value(layerKey, -1)
                if (index >= self.mMapWork.layerCount() or index == -1
                        or name != self.mMapWork.layerAt(index).name()):
                    newIndex = self.mMapWork.indexOfLayer(
                        name, layerKey.layerType())
                    translationTable.insert(layerKey, newIndex)

        return True

    ##
    # sets up the tilesets which are used in automapping.
    # @return returns True when anything is ok, False when errors occured.
    #        (in that case will be a msg box anyway)
    ##
    # This cannot just be replaced by MapDocument::unifyTileset(Map),
    # because here mAddedTileset is modified.
    def setupTilesets(self, src, dst):
        existingTilesets = dst.tilesets()
        tilesetManager = TilesetManager.instance()
        # Add tilesets that are not yet part of dst map
        for tileset in src.tilesets():
            if (existingTilesets.contains(tileset)):
                continue
            undoStack = self.mMapDocument.undoStack()
            replacement = tileset.findSimilarTileset(existingTilesets)
            if (not replacement):
                self.mAddedTilesets.append(tileset)
                undoStack.push(AddTileset(self.mMapDocument, 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())
                undoStack.push(
                    ChangeProperties(self.mMapDocument, self.tr("Tile"),
                                     replacementTile, properties))

            src.replaceTileset(tileset, replacement)
            tilesetManager.addReference(replacement)
            tilesetManager.removeReference(tileset)

        return True

    ##
    # Returns the conjunction of of all regions of all setlayers
    ##
    def getSetLayersRegion(self):
        result = QRegion()
        for name in self.mInputRules.names:
            index = self.mMapWork.indexOfLayer(name, Layer.TileLayerType)
            if (index == -1):
                continue
            setLayer = self.mMapWork.layerAt(index).asTileLayer()
            result |= setLayer.region()

        return result

    ##
    # This copies all Tiles from TileLayer src to TileLayer dst
    #
    # In src the Tiles are taken from the rectangle given by
    # src_x, src_y, width and height.
    # In dst they get copied to a rectangle given by
    # dst_x, dst_y, width, height .
    # if there is no tile in src TileLayer, there will nothing be copied,
    # so the maybe existing tile in dst will not be overwritten.
    #
    ##
    def copyTileRegion(self, srcLayer, srcX, srcY, width, height, dstLayer,
                       dstX, dstY):
        startX = max(dstX, 0)
        startY = max(dstY, 0)
        endX = min(dstX + width, dstLayer.width())
        endY = min(dstY + height, dstLayer.height())
        offsetX = srcX - dstX
        offsetY = srcY - dstY
        for x in range(startX, endX):
            for y in range(startY, endY):
                cell = srcLayer.cellAt(x + offsetX, y + offsetY)
                if (not cell.isEmpty()):
                    # this is without graphics update, it's done afterwards for all
                    dstLayer.setCell(x, y, cell)

    ##
    # This copies all objects from the \a src_lr ObjectGroup to the \a dst_lr
    # in the given rectangle.
    #
    # The rectangle is described by the upper left corner \a src_x \a src_y
    # and its \a width and \a height. The parameter \a dst_x and \a dst_y
    # offset the copied objects in the destination object group.
    ##
    def copyObjectRegion(self, srcLayer, srcX, srcY, width, height, dstLayer,
                         dstX, dstY):
        undo = self.mMapDocument.undoStack()
        rect = QRectF(srcX, srcY, width, height)
        pixelRect = self.mMapDocument.renderer().tileToPixelCoords_(rect)
        objects = objectsInRegion(srcLayer, pixelRect.toAlignedRect())
        pixelOffset = self.mMapDocument.renderer().tileToPixelCoords(
            dstX, dstY)
        pixelOffset -= pixelRect.topLeft()
        clones = QList()
        for obj in objects:
            clone = obj.clone()
            clones.append(clone)
            clone.setX(clone.x() + pixelOffset.x())
            clone.setY(clone.y() + pixelOffset.y())
            undo.push(AddMapObject(self.mMapDocument, dstLayer, clone))

    ##
    # This copies multiple TileLayers from one map to another.
    # Only the region \a region is considered for copying.
    # In the destination it will come to the region translated by Offset.
    # The parameter \a LayerTranslation is a map of which layers of the rulesmap
    # should get copied into which layers of the working map.
    ##
    def copyMapRegion(self, region, offset, layerTranslation):
        for i in range(layerTranslation.keys().size()):
            _from = layerTranslation.keys().at(i)
            to = self.mMapWork.layerAt(layerTranslation.value(_from))
            for rect in region.rects():
                fromTileLayer = _from.asTileLayer()
                fromObjectGroup = _from.asObjectGroup()
                if (fromTileLayer):
                    toTileLayer = to.asTileLayer()
                    self.copyTileRegion(fromTileLayer, rect.x(), rect.y(),
                                        rect.width(), rect.height(),
                                        toTileLayer,
                                        rect.x() + offset.x(),
                                        rect.y() + offset.y())
                elif (fromObjectGroup):
                    toObjectGroup = to.asObjectGroup()
                    self.copyObjectRegion(fromObjectGroup, rect.x(), rect.y(),
                                          rect.width(), rect.height(),
                                          toObjectGroup,
                                          rect.x() + offset.x(),
                                          rect.y() + offset.y())
                else:
                    pass

    ##
    # This goes through all the positions of the mMapWork and checks if
    # there fits the rule given by the region in mMapRuleSet.
    # if there is a match all Layers are copied to mMapWork.
    # @param ruleIndex: the region which should be compared to all positions
    #              of mMapWork will be looked up in mRulesInput and mRulesOutput
    # @return where: an rectangle where the rule actually got applied
    ##
    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

    ##
    # Cleans up the data structes filled by setupRuleMapLayers(),
    # so the next rule can be processed.
    ##
    def cleanUpRuleMapLayers(self):
        self.cleanTileLayers()
        it = QList.const_iterator()
        for it in self.mLayerList:
            del it
        self.mLayerList.clear()
        # do not delete mLayerRuleRegions, it is owned by the rulesmap
        self.mLayerInputRegions = None
        self.mLayerOutputRegions = None
        self.mInputRules.clear()

    ##
    # Cleans up the data structes filled by setupTilesets(),
    # so the next rule can be processed.
    ##
    def cleanTilesets(self):
        for tileset in self.mAddedTilesets:
            if (self.mMapWork.isTilesetUsed(tileset)):
                continue
            index = self.mMapWork.indexOfTileset(tileset)
            if (index == -1):
                continue
            undo = self.mMapDocument.undoStack()
            undo.push(RemoveTileset(self.mMapDocument, index))

        self.mAddedTilesets.clear()

    ##
    # Cleans up the added tile layers setup by setupMissingLayers(),
    # so we have a minimal addition of tile layers by the automapping.
    ##
    def cleanTileLayers(self):
        for tilelayerName in self.mAddedTileLayers:
            layerIndex = self.mMapWork.indexOfLayer(tilelayerName,
                                                    Layer.TileLayerType)
            if (layerIndex == -1):
                continue
            layer = self.mMapWork.layerAt(layerIndex)
            if (not layer.isEmpty()):
                continue
            undo = self.mMapDocument.undoStack()
            undo.push(RemoveLayer(self.mMapDocument, layerIndex))

        self.mAddedTileLayers.clear()
Example #8
0
class AutoMapper(QObject):
    ##
    # Constructs an AutoMapper.
    # All data structures, which only rely on the rules map are setup
    # here.
    #
    # @param workingDocument: the map to work on.
    # @param rules: The rule map which should be used for automapping
    # @param rulePath: The filepath to the rule map.
    ##
    def __init__(self, workingDocument, rules, rulePath):
        ##
        # where to work in
        ##
        self.mMapDocument = workingDocument

        ##
        # the same as mMapDocument.map()
        ##
        self.mMapWork = None
        if workingDocument:
            self.mMapWork = workingDocument.map()

        ##
        # map containing the rules, usually different than mMapWork
        ##
        self.mMapRules = rules

        ##
        # This contains all added tilesets as pointers.
        # if rules use Tilesets which are not in the mMapWork they are added.
        # keep track of them, because we need to delete them afterwards,
        # when they still are unused
        # they will be added while setupTilesets().
        ##
        self.mAddedTilesets = QVector()

        ##
        # description see: mAddedTilesets, just described by Strings
        ##
        self.mAddedTileLayers = QList()

        ##
        # Points to the tilelayer, which defines the inputregions.
        ##
        self.mLayerInputRegions = None

        ##
        # Points to the tilelayer, which defines the outputregions.
        ##
        self.mLayerOutputRegions = None

        ##
        # Contains all tilelayer pointers, which names begin with input*
        # It is sorted by index and name
        ##
        self.mInputRules = InputLayers()

        ##
        # List of Regions in mMapRules to know where the input rules are
        ##
        self.mRulesInput = QList()

        ##
        # List of regions in mMapRules to know where the output of a
        # rule is.
        # mRulesOutput[i] is the output of that rule,
        # which has the input at mRulesInput[i], meaning that mRulesInput
        # and mRulesOutput must match with the indexes.
        ##
        self.mRulesOutput = QList()

        ##
        # The inner set with layers to indexes is needed for translating
        # tile layers from mMapRules to mMapWork.
        #
        # The key is the pointer to the layer in the rulemap. The
        # pointer to the layer within the working map is not hardwired, but the
        # position in the layerlist, where it was found the last time.
        # This loosely bound pointer ensures we will get the right layer, since we
        # need to check before anyway, and it is still fast.
        #
        # The list is used to hold different translation tables
        # => one of the tables is chosen by chance, so randomness is available
        ##
        self.mLayerList = QList()
        ##
        # store the name of the processed rules file, to have detailed
        # error messages available
        ##
        self.mRulePath = rulePath

        ##
        # determines if all tiles in all touched layers should be deleted first.
        ##
        self.mDeleteTiles = False

        ##
        # This variable determines, how many overlapping tiles should be used.
        # The bigger the more area is remapped at an automapping operation.
        # This can lead to higher latency, but provides a better behavior on
        # interactive automapping.
        # It defaults to zero.
        ##
        self.mAutoMappingRadius = 0

        ##
        # Determines if a rule is allowed to overlap it
        ##
        self.mNoOverlappingRules = False

        self.mTouchedObjectGroups = QSet()
        self.mWarning = QString()
        self.mTouchedTileLayers = QSet()
        self.mError = ''

        if (not self.setupRuleMapProperties()):
            return
        if (not self.setupRuleMapTileLayers()):
            return
        if (not self.setupRuleList()):
            return

    def __del__(self):
        self.cleanUpRulesMap()

    ##
    # Checks if the passed \a ruleLayerName is used in this instance
    # of Automapper.
    ##
    def ruleLayerNameUsed(self, ruleLayerName):
        return self.mInputRules.names.contains(ruleLayerName)

    ##
    # Call prepareLoad first! Returns a set of strings describing the tile
    # layers, which could be touched considering the given layers of the
    # rule map.
    ##
    def getTouchedTileLayers(self):
        return self.mTouchedTileLayers

    ##
    # This needs to be called directly before the autoMap call.
    # It sets up some data structures which change rapidly, so it is quite
    # painful to keep these datastructures up to date all time. (indices of
    # layers of the working map)
    ##
    def prepareAutoMap(self):
        self.mError = ''
        self.mWarning = ''
        if (not self.setupMissingLayers()):
            return False
        if (not self.setupCorrectIndexes()):
            return False
        if (not self.setupTilesets(self.mMapRules, self.mMapWork)):
            return False
        return True

    ##
    # Here is done all the automapping.
    ##
    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))

        #where = where.united(ret)

    ##
    # This cleans all datastructures, which are setup via prepareAutoMap,
    # so the auto mapper becomes ready for its next automatic mapping.
    ##
    def cleanAll(self):
        self.cleanTilesets()
        self.cleanTileLayers()

    ##
    # Contains all errors until operation was canceled.
    # The errorlist is cleared within prepareLoad and prepareAutoMap.
    ##
    def errorString(self):
        return self.mError

    ##
    # Contains all warnings which occur at loading a rules map or while
    # automapping.
    # The errorlist is cleared within prepareLoad and prepareAutoMap.
    ##
    def warningString(self):
        return self.mWarning

    ##
    # Reads the map properties of the rulesmap.
    # @return returns True when anything is ok, False when errors occured.
    ##
    def setupRuleMapProperties(self):
        properties = self.mMapRules.properties()
        for key in properties.keys():
            value = properties.value(key)
            raiseWarning = True
            if (key.toLower() == "deletetiles"):
                if (value.canConvert(QVariant.Bool)):
                    self.mDeleteTiles = value.toBool()
                    raiseWarning = False
            elif (key.toLower() == "automappingradius"):
                if (value.canConvert(QVariant.Int)):
                    self.mAutoMappingRadius = value
                    raiseWarning = False
            elif (key.toLower() == "nooverlappingrules"):
                if (value.canConvert(QVariant.Bool)):
                    self.mNoOverlappingRules = value.toBool()
                    raiseWarning = False

            if (raiseWarning):
                self.mWarning += self.tr("'%s': Property '%s' = '%s' does not make sense. \nIgnoring this property."%(self.mRulePath, key, value.toString()) + '\n')

        return True

    def cleanUpRulesMap(self):
        self.cleanTilesets()
        # mMapRules can be empty, when in prepareLoad the very first stages fail.
        if (not self.mMapRules):
            return
        tilesetManager = TilesetManager.instance()
        tilesetManager.removeReferences(self.mMapRules.tilesets())
        del self.mMapRules
        self.mMapRules = None
        self.cleanUpRuleMapLayers()
        self.mRulesInput.clear()
        self.mRulesOutput.clear()

    ##
    # Searches the rules layer for regions and stores these in \a rules.
    # @return returns True when anything is ok, False when errors occured.
    ##
    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

    ##
    # Sets up the layers in the rules map, which are used for automapping.
    # The layers are detected and put in the internal data structures
    # @return returns True when anything is ok, False when errors occured.
    ##
    def setupRuleMapTileLayers(self):
        error = QString()
        for layer in self.mMapRules.layers():
            layerName = layer.name()
            if (layerName.lower().startswith("regions")):
                treatAsBoth = layerName.toLower() == "regions"
                if (layerName.lower().endswith("input") or treatAsBoth):
                    if (self.mLayerInputRegions):
                        error += self.tr("'regions_input' layer must not occur more than once.\n")

                    if (layer.isTileLayer()):
                        self.mLayerInputRegions = layer.asTileLayer()
                    else:
                        error += self.tr("'regions_*' layers must be tile layers.\n")

                if (layerName.lower().endswith("output") or treatAsBoth):
                    if (self.mLayerOutputRegions):
                        error += self.tr("'regions_output' layer must not occur more than once.\n")

                    if (layer.isTileLayer()):
                        self.mLayerOutputRegions = layer.asTileLayer()
                    else:
                        error += self.tr("'regions_*' layers must be tile layers.\n")

                continue

            nameStartPosition = layerName.indexOf('_') + 1
            # name is all characters behind the underscore (excluded)
            name = layerName.right(layerName.size() - nameStartPosition)
            # group is all before the underscore (included)
            index = layerName.left(nameStartPosition)
            if (index.lower().startswith("output")):
                index.remove(0, 6)
            elif (index.lower().startswith("inputnot")):
                index.remove(0, 8)
            elif (index.lower().startswith("input")):
                index.remove(0, 5)
            # both 'rule' and 'output' layers will require and underscore and 
            # rely on the correct position detected of the underscore
            if (nameStartPosition == 0):
                error += self.tr("Did you forget an underscore in layer '%d'?\n"%layerName)
                continue

            if (layerName.startsWith("input", Qt.CaseInsensitive)):
                isNotList = layerName.lower().startswith("inputnot")
                if (not layer.isTileLayer()):
                    error += self.tr("'input_*' and 'inputnot_*' layers must be tile layers.\n")
                    continue

                self.mInputRules.names.insert(name)
                if (not self.mInputRules.indexes.contains(index)):
                    self.mInputRules.indexes.insert(index)
                    self.mInputRules.insert(index, InputIndex())

                if (not self.mInputRules[index].names.contains(name)):
                    self.mInputRules[index].names.insert(name)
                    self.mInputRules[index].insert(name, InputIndexName())

                if (isNotList):
                    self.mInputRules[index][name].listNo.append(layer.asTileLayer())
                else:
                    self.mInputRules[index][name].listYes.append(layer.asTileLayer())
                continue

            if layerName.lower().startswith("output"):
                if (layer.isTileLayer()):
                    self.mTouchedTileLayers.insert(name)
                else:
                    self.mTouchedObjectGroups.insert(name)
                type = layer.layerType()
                layerIndex = self.mMapWork.indexOfLayer(name, type)
                found = False
                for translationTable in self.mLayerList:
                    if (translationTable.index == index):
                        translationTable.insert(layer, layerIndex)
                        found = True
                        break

                if (not found):
                    self.mLayerList.append(RuleOutput())
                    self.mLayerList.last().insert(layer, layerIndex)
                    self.mLayerList.last().index = index

                continue

            error += self.tr("Layer '%s' is not recognized as a valid layer for Automapping.\n"%layerName)

        if (not self.mLayerInputRegions):
            error += self.tr("No 'regions' or 'regions_input' layer found.\n")
        if (not self.mLayerOutputRegions):
            error += self.tr("No 'regions' or 'regions_output' layer found.\n")
        if (self.mInputRules.isEmpty()):
            error += self.tr("No input_<name> layer found!\n")
        # no need to check for mInputNotRules.size() == 0 here.
        # these layers are not necessary.
        if error != '':
            error = self.mRulePath + '\n' + error
            self.mError += error
            return False

        return True

    ##
    # Checks if all needed layers in the working map are there.
    # If not, add them in the correct order.
    ##
    def setupMissingLayers(self):
        # make sure all needed layers are there:
        for name in self.mTouchedTileLayers:
            if (self.mMapWork.indexOfLayer(name, Layer.TileLayerType) != -1):
                continue
            index = self.mMapWork.layerCount()
            tilelayer = TileLayer(name, 0, 0, self.mMapWork.width(), self.mMapWork.height())
            self.mMapDocument.undoStack().push(AddLayer(self.mMapDocument, index, tilelayer))
            self.mAddedTileLayers.append(name)

        for name in self.mTouchedObjectGroups:
            if (self.mMapWork.indexOfLayer(name, Layer.ObjectGroupType) != -1):
                continue
            index = self.mMapWork.layerCount()
            objectGroup = ObjectGroup(name, 0, 0,
                                                       self.mMapWork.width(),
                                                       self.mMapWork.height())
            self.mMapDocument.undoStack().push(AddLayer(self.mMapDocument, index, objectGroup))
            self.mAddedTileLayers.append(name)

        return True

    ##
    # Checks if the layers setup as in setupRuleMapLayers are still right.
    # If it's not right, correct them.
    # @return returns True if everything went fine. False is returned when
    #         no set layer was found
    ##
    def setupCorrectIndexes(self):
        # make sure all indexes of the layer translationtables are correct.
        for i in range(self.mLayerList.size()):
            translationTable = self.mLayerList.at(i)
            for layerKey in translationTable.keys():
                name = layerKey.name()
                pos = name.indexOf('_') + 1
                name = name.right(name.length() - pos)
                index = translationTable.value(layerKey, -1)
                if (index >= self.mMapWork.layerCount() or index == -1 or
                        name != self.mMapWork.layerAt(index).name()):
                    newIndex = self.mMapWork.indexOfLayer(name, layerKey.layerType())
                    translationTable.insert(layerKey, newIndex)

        return True

    ##
    # sets up the tilesets which are used in automapping.
    # @return returns True when anything is ok, False when errors occured.
    #        (in that case will be a msg box anyway)
    ##
    # This cannot just be replaced by MapDocument::unifyTileset(Map),
    # because here mAddedTileset is modified.
    def setupTilesets(self, src, dst):
        existingTilesets = dst.tilesets()
        tilesetManager = TilesetManager.instance()
        # Add tilesets that are not yet part of dst map
        for tileset in src.tilesets():
            if (existingTilesets.contains(tileset)):
                continue
            undoStack = self.mMapDocument.undoStack()
            replacement = tileset.findSimilarTileset(existingTilesets)
            if (not replacement):
                self.mAddedTilesets.append(tileset)
                undoStack.push(AddTileset(self.mMapDocument, 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())
                undoStack.push(ChangeProperties(self.mMapDocument,
                                                     self.tr("Tile"),
                                                     replacementTile,
                                                     properties))

            src.replaceTileset(tileset, replacement)
            tilesetManager.addReference(replacement)
            tilesetManager.removeReference(tileset)

        return True

    ##
    # Returns the conjunction of of all regions of all setlayers
    ##
    def getSetLayersRegion(self):
        result = QRegion()
        for name in self.mInputRules.names:
            index = self.mMapWork.indexOfLayer(name, Layer.TileLayerType)
            if (index == -1):
                continue
            setLayer = self.mMapWork.layerAt(index).asTileLayer()
            result |= setLayer.region()

        return result

    ##
    # This copies all Tiles from TileLayer src to TileLayer dst
    #
    # In src the Tiles are taken from the rectangle given by
    # src_x, src_y, width and height.
    # In dst they get copied to a rectangle given by
    # dst_x, dst_y, width, height .
    # if there is no tile in src TileLayer, there will nothing be copied,
    # so the maybe existing tile in dst will not be overwritten.
    #
    ##
    def copyTileRegion(self, srcLayer, srcX, srcY, width, height, dstLayer, dstX, dstY):
        startX = max(dstX, 0)
        startY = max(dstY, 0)
        endX = min(dstX + width, dstLayer.width())
        endY = min(dstY + height, dstLayer.height())
        offsetX = srcX - dstX
        offsetY = srcY - dstY
        for x in range(startX, endX):
            for y in range(startY, endY):
                cell = srcLayer.cellAt(x + offsetX, y + offsetY)
                if (not cell.isEmpty()):
                    # this is without graphics update, it's done afterwards for all
                    dstLayer.setCell(x, y, cell)

    ##
    # This copies all objects from the \a src_lr ObjectGroup to the \a dst_lr
    # in the given rectangle.
    #
    # The rectangle is described by the upper left corner \a src_x \a src_y
    # and its \a width and \a height. The parameter \a dst_x and \a dst_y
    # offset the copied objects in the destination object group.
    ##
    def copyObjectRegion(self, srcLayer, srcX, srcY, width, height, dstLayer, dstX, dstY):
        undo = self.mMapDocument.undoStack()
        rect = QRectF(srcX, srcY, width, height)
        pixelRect = self.mMapDocument.renderer().tileToPixelCoords_(rect)
        objects = objectsInRegion(srcLayer, pixelRect.toAlignedRect())
        pixelOffset = self.mMapDocument.renderer().tileToPixelCoords(dstX, dstY)
        pixelOffset -= pixelRect.topLeft()
        clones = QList()
        for obj in objects:
            clone = obj.clone()
            clones.append(clone)
            clone.setX(clone.x() + pixelOffset.x())
            clone.setY(clone.y() + pixelOffset.y())
            undo.push(AddMapObject(self.mMapDocument, dstLayer, clone))

    ##
    # This copies multiple TileLayers from one map to another.
    # Only the region \a region is considered for copying.
    # In the destination it will come to the region translated by Offset.
    # The parameter \a LayerTranslation is a map of which layers of the rulesmap
    # should get copied into which layers of the working map.
    ##
    def copyMapRegion(self, region, offset, layerTranslation):
        for i in range(layerTranslation.keys().size()):
            _from = layerTranslation.keys().at(i)
            to = self.mMapWork.layerAt(layerTranslation.value(_from))
            for rect in region.rects():
                fromTileLayer = _from.asTileLayer()
                fromObjectGroup = _from.asObjectGroup()
                if (fromTileLayer):
                    toTileLayer = to.asTileLayer()
                    self.copyTileRegion(fromTileLayer, rect.x(), rect.y(),
                                   rect.width(), rect.height(),
                                   toTileLayer,
                                   rect.x() + offset.x(), rect.y() + offset.y())
                elif (fromObjectGroup):
                    toObjectGroup = to.asObjectGroup()
                    self.copyObjectRegion(fromObjectGroup, rect.x(), rect.y(),
                                     rect.width(), rect.height(),
                                     toObjectGroup,
                                     rect.x() + offset.x(), rect.y() + offset.y())
                else:
                    pass

    ##
    # This goes through all the positions of the mMapWork and checks if
    # there fits the rule given by the region in mMapRuleSet.
    # if there is a match all Layers are copied to mMapWork.
    # @param ruleIndex: the region which should be compared to all positions
    #              of mMapWork will be looked up in mRulesInput and mRulesOutput
    # @return where: an rectangle where the rule actually got applied
    ##
    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

    ##
    # Cleans up the data structes filled by setupRuleMapLayers(),
    # so the next rule can be processed.
    ##
    def cleanUpRuleMapLayers(self):
        self.cleanTileLayers()
        it = QList.const_iterator()
        for it in self.mLayerList:
            del it
        self.mLayerList.clear()
        # do not delete mLayerRuleRegions, it is owned by the rulesmap
        self.mLayerInputRegions = None
        self.mLayerOutputRegions = None
        self.mInputRules.clear()

    ##
    # Cleans up the data structes filled by setupTilesets(),
    # so the next rule can be processed.
    ##
    def cleanTilesets(self):
        for tileset in self.mAddedTilesets:
            if (self.mMapWork.isTilesetUsed(tileset)):
                continue
            index = self.mMapWork.indexOfTileset(tileset)
            if (index == -1):
                continue
            undo = self.mMapDocument.undoStack()
            undo.push(RemoveTileset(self.mMapDocument, index))

        self.mAddedTilesets.clear()

    ##
    # Cleans up the added tile layers setup by setupMissingLayers(),
    # so we have a minimal addition of tile layers by the automapping.
    ##
    def cleanTileLayers(self):
        for tilelayerName in self.mAddedTileLayers:
            layerIndex = self.mMapWork.indexOfLayer(tilelayerName,
                                                          Layer.TileLayerType)
            if (layerIndex == -1):
                continue
            layer = self.mMapWork.layerAt(layerIndex)
            if (not layer.isEmpty()):
                continue
            undo = self.mMapDocument.undoStack()
            undo.push(RemoveLayer(self.mMapDocument, layerIndex))

        self.mAddedTileLayers.clear()
Example #9
0
class ObjectGroup(Layer):
    ##
    # Objects within an object group can either be drawn top down (sorted
    # by their y-coordinate) or by index (manual stacking order).
    #
    # The default is top down.
    ##
    class DrawOrder():
        UnknownOrder = -1
        TopDownOrder = 1
        IndexOrder = 2

    ##
    # Default constructor.
    ##
    def __init__(self, *args):
        self.mObjects = QList()
        self.mColor = QColor()

        l = len(args)
        if l==0:
            super().__init__(Layer.ObjectGroupType, QString(), 0, 0, 0, 0)
        elif l==5:
            ##
            # Constructor with some parameters.
            ##
            name, x, y, width, height = args

            super().__init__(Layer.ObjectGroupType, name, x, y, width, height)
        else:
            pass
        self.mDrawOrder = ObjectGroup.DrawOrder.IndexOrder

    ##
    # Destructor.
    ##
    def __del__(self):
        self.mObjects.clear()

    ##
    # Returns a pointer to the list of objects in this object group.
    ##
    def objects(self):
        return QList(self.mObjects)

    ##
    # Returns the number of objects in this object group.
    ##
    def objectCount(self):
        return self.mObjects.size()

    ##
    # Returns the object at the specified index.
    ##
    def objectAt(self, index):
        return self.mObjects.at(index)

    ##
    # Adds an object to this object group.
    ##
    def addObject(self, object):
        self.mObjects.append(object)
        object.setObjectGroup(self)
        if (self.mMap and object.id() == 0):
            object.setId(self.mMap.takeNextObjectId())

    ##
    # Inserts an object at the specified index. This is only used for undoing
    # the removal of an object at the moment, to make sure not to change the
    # saved order of the objects.
    ##
    def insertObject(self, index, object):
        self.mObjects.insert(index, object)
        object.setObjectGroup(self)
        if (self.mMap and object.id() == 0):
            object.setId(self.mMap.takeNextObjectId())

    ##
    # Removes an object from this object group. Ownership of the object is
    # transferred to the caller.
    #
    # @return the index at which the specified object was removed
    ##
    def removeObject(self, object):
        index = self.mObjects.indexOf(object)
        self.mObjects.removeAt(index)
        object.setObjectGroup(None)
        return index

    ##
    # Removes the object at the given index. Ownership of the object is
    # transferred to the caller.
    #
    # This is faster than removeObject when you've already got the index.
    #
    # @param index the index at which to remove an object
    ##
    def removeObjectAt(self, index):
        object = self.mObjects.takeAt(index)
        object.setObjectGroup(None)

    ##
    # Moves \a count objects starting at \a from to the index given by \a to.
    #
    # The \a to index may not lie within the range of objects that is
    # being moved.
    ##
    def moveObjects(self, _from, to, count):
        # It's an error when 'to' lies within the moving range of objects
        # Nothing to be done when 'to' is the start or the end of the range, or
        # when the number of objects to be moved is 0.
        if (to == _from or to == _from + count or count == 0):
            return
        movingObjects = self.mObjects[_from:_from+count]
        self.mObjects.erase(_from, _from + count)
        if (to > _from):
            to -= count
        for i in range(count):
            self.mObjects.insert(to + i, movingObjects[i])

    ##
    # Returns the bounding rect around all objects in this object group.
    ##
    def objectsBoundingRect(self):
        boundingRect = QRectF()
        for object in self.mObjects:
            boundingRect = boundingRect.united(object.bounds())
        return boundingRect

    ##
    # Returns whether this object group contains any objects.
    ##
    def isEmpty(self):
        return self.mObjects.isEmpty()

    ##
    # Computes and returns the set of tilesets used by this object group.
    ##
    def usedTilesets(self):
        tilesets = QSet()
        for object in self.mObjects:
            tile = object.cell().tile
            if tile:
                tilesets.insert(tile.sharedTileset())
        return tilesets

    ##
    # Returns whether any tile objects in this object group reference tiles
    # in the given tileset.
    ##
    def referencesTileset(self, tileset):
        for object in self.mObjects:
            tile = object.cell().tile
            if (tile and tile.tileset() == tileset):
                return True

        return False

    ##
    # Replaces all references to tiles from \a oldTileset with tiles from
    # \a newTileset.
    ##
    def replaceReferencesToTileset(self, oldTileset, newTileset):
        for object in self.mObjects:
            tile = object.cell().tile
            if (tile and tile.tileset() == oldTileset):
                cell = object.cell()
                cell.tile = Tileset.tileAt(tile.id())
                object.setCell(cell)

    ##
    # Offsets all objects within the group by the \a offset given in pixel
    # coordinates, and optionally wraps them. The object's center must be
    # within \a bounds, and wrapping occurs if the displaced center is out of
    # the bounds.
    #
    # \sa TileLayer.offset()
    ##
    def offsetObjects(self, offset, bounds, wrapX, wrapY):
        for object in self.mObjects:
            objectCenter = object.bounds().center()
            if (not bounds.contains(objectCenter)):
                continue
            newCenter = QPointF(objectCenter + offset)
            if (wrapX and bounds.width() > 0):
                nx = math.fmod(newCenter.x() - bounds.left(), bounds.width())
                if nx < 0:
                    x = bounds.width() + nx
                else:
                    x = nx
                newCenter.setX(bounds.left() + x)

            if (wrapY and bounds.height() > 0):
                ny = math.fmod(newCenter.y() - bounds.top(), bounds.height())
                if ny < 0:
                    x = bounds.height() + ny
                else:
                    x = ny
                newCenter.setY(bounds.top() + x)

            object.setPosition(object.position() + (newCenter - objectCenter))

    def canMergeWith(self, other):
        return other.isObjectGroup()

    def mergedWith(self, other):
        og = other
        merged = self.clone()
        for mapObject in og.objects():
            merged.addObject(mapObject.clone())
        return merged

    ##
    # Returns the color of the object group, or an invalid color if no color
    # is set.
    ##
    def color(self):
        return self.mColor

    ##
    # Sets the display color of the object group.
    ##
    def setColor(self, color):
        if type(color) != QColor:
            color = QColor(color)
        self.mColor = color

    ##
    # Returns the draw order for the objects in this group.
    #
    # \sa ObjectGroup.DrawOrder
    ##
    def drawOrder(self):
        return self.mDrawOrder

    ##
    # Sets the draw order for the objects in this group.
    #
    # \sa ObjectGroup.DrawOrder
    ##
    def setDrawOrder(self, drawOrder):
        self.mDrawOrder = drawOrder

    ##
    # Returns a duplicate of this ObjectGroup.
    #
    # \sa Layer.clone()
    ##
    def clone(self):
        return self.initializeClone(ObjectGroup(self.mName, self.mX, self.mY, self.mWidth, self.mHeight))

    def initializeClone(self, clone):
        super().initializeClone(clone)
        for object in self.mObjects:
            clone.addObject(object.clone())
        clone.setColor(self.mColor)
        clone.setDrawOrder(self.mDrawOrder)
        return clone
class QtCursorDatabase():
    def __init__(self):
        self.m_cursorNames = QList()
        self.m_cursorIcons = QMap()
        self.m_valueToCursorShape = QMap()
        self.m_cursorShapeToValue = QMap()

        self.appendCursor(Qt.ArrowCursor, QCoreApplication.translate("QtCursorDatabase", "Arrow"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-arrow.png"))
        self.appendCursor(Qt.UpArrowCursor, QCoreApplication.translate("QtCursorDatabase", "Up Arrow"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-uparrow.png"))
        self.appendCursor(Qt.CrossCursor, QCoreApplication.translate("QtCursorDatabase", "Cross"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-cross.png"))
        self.appendCursor(Qt.WaitCursor, QCoreApplication.translate("QtCursorDatabase", "Wait"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-wait.png"))
        self.appendCursor(Qt.IBeamCursor, QCoreApplication.translate("QtCursorDatabase", "IBeam"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-ibeam.png"))
        self.appendCursor(Qt.SizeVerCursor, QCoreApplication.translate("QtCursorDatabase", "Size Vertical"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizev.png"))
        self.appendCursor(Qt.SizeHorCursor, QCoreApplication.translate("QtCursorDatabase", "Size Horizontal"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeh.png"))
        self.appendCursor(Qt.SizeFDiagCursor, QCoreApplication.translate("QtCursorDatabase", "Size Backslash"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizef.png"))
        self.appendCursor(Qt.SizeBDiagCursor, QCoreApplication.translate("QtCursorDatabase", "Size Slash"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeb.png"))
        self.appendCursor(Qt.SizeAllCursor, QCoreApplication.translate("QtCursorDatabase", "Size All"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeall.png"))
        self.appendCursor(Qt.BlankCursor, QCoreApplication.translate("QtCursorDatabase", "Blank"),
                     QIcon())
        self.appendCursor(Qt.SplitVCursor, QCoreApplication.translate("QtCursorDatabase", "Split Vertical"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-vsplit.png"))
        self.appendCursor(Qt.SplitHCursor, QCoreApplication.translate("QtCursorDatabase", "Split Horizontal"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-hsplit.png"))
        self.appendCursor(Qt.PointingHandCursor, QCoreApplication.translate("QtCursorDatabase", "Pointing Hand"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-hand.png"))
        self.appendCursor(Qt.ForbiddenCursor, QCoreApplication.translate("QtCursorDatabase", "Forbidden"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-forbidden.png"))
        self.appendCursor(Qt.OpenHandCursor, QCoreApplication.translate("QtCursorDatabase", "Open Hand"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-openhand.png"))
        self.appendCursor(Qt.ClosedHandCursor, QCoreApplication.translate("QtCursorDatabase", "Closed Hand"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-closedhand.png"))
        self.appendCursor(Qt.WhatsThisCursor, QCoreApplication.translate("QtCursorDatabase", "What's This"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-whatsthis.png"))
        self.appendCursor(Qt.BusyCursor, QCoreApplication.translate("QtCursorDatabase", "Busy"),
                     QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-busy.png"))

    def clear(self):
        self.m_cursorNames.clear()
        self.m_cursorIcons.clear()
        self.m_valueToCursorShape.clear()
        self.m_cursorShapeToValue.clear()

    def appendCursor(self,shape, name, icon):
        if self.m_cursorShapeToValue.get(shape):
            return
        value = len(self.m_cursorNames)
        self.m_cursorNames.append(name)
        self.m_cursorIcons[value] = icon
        self.m_valueToCursorShape[value] = shape
        self.m_cursorShapeToValue[shape] = value

    def cursorShapeNames(self):
        return self.m_cursorNames

    def cursorShapeIcons(self):
        return self.m_cursorIcons

    def cursorToShapeName(self,cursor):
        val = self.cursorToValue(cursor)
        if val >= 0:
            return self.m_cursorNames[val]
        return ''

    def cursorToShapeIcon(self,cursor):
        val = self.cursorToValue(cursor)
        return self.m_cursorIcons[val]

    def cursorToValue(self,cursor):
        shape = cursor.shape()
        return self.m_cursorShapeToValue.get(shape, -1)

    def valueToCursor(self,value):
        if value in self.m_valueToCursorShape:
            return QCursor(self.m_valueToCursorShape[value])
        return QCursor()
Example #11
0
class Map(Object):

    ##
    # The orientation of the map determines how it should be rendered. An
    # Orthogonal map is using rectangular tiles that are aligned on a
    # straight grid. An Isometric map uses diamond shaped tiles that are
    # aligned on an isometric projected grid. A Hexagonal map uses hexagon
    # shaped tiles that fit into each other by shifting every other row.
    ##
    class Orientation(Enum):
        Unknown, Orthogonal, Isometric, Staggered, Hexagonal = range(5)

    ##
    # The different formats in which the tile layer data can be stored.
    ##
    class LayerDataFormat(Enum):
        XML = 0
        Base64 = 1
        Base64Gzip = 2
        Base64Zlib = 3
        CSV = 4

    ##
    # The order in which tiles are rendered on screen.
    ##
    class RenderOrder(Enum):
        RightDown = 0
        RightUp = 1
        LeftDown = 2
        LeftUp = 3

    ##
    # Which axis is staggered. Only used by the isometric staggered and
    # hexagonal map renderers.
    ##
    class StaggerAxis(Enum):
        StaggerX, StaggerY = range(2)

    ##
    # When staggering, specifies whether the odd or the even rows/columns are
    # shifted half a tile right/down. Only used by the isometric staggered and
    # hexagonal map renderers.
    ##
    class StaggerIndex(Enum):
        StaggerOdd = 0
        StaggerEven = 1

    def __init__(self, *args):
        self.mOrientation = 0
        self.mRenderOrder = 0
        self.mWidth = 0
        self.mHeight = 0
        self.mTileWidth = 0
        self.mTileHeight = 0
        self.mHexSideLength = 0
        self.mStaggerAxis = 0
        self.mStaggerIndex = 0
        self.mBackgroundColor = QColor()
        self.mDrawMargins = QMargins()
        self.mLayers = QList()
        self.mTilesets = QVector()
        self.mLayerDataFormat = None
        self.mNextObjectId = 0

        l = len(args)
        if l == 1:
            ##
            # Copy constructor. Makes sure that a deep-copy of the layers is created.
            ##
            map = args[0]
            super().__init__(map)

            self.mLayers = QList()
            self.mOrientation = map.mOrientation
            self.mRenderOrder = map.mRenderOrder
            self.mWidth = map.mWidth
            self.mHeight = map.mHeight
            self.mTileWidth = map.mTileWidth
            self.mTileHeight = map.mTileHeight
            self.mHexSideLength = map.mHexSideLength
            self.mStaggerAxis = map.mStaggerAxis
            self.mStaggerIndex = map.mStaggerIndex
            self.mBackgroundColor = map.mBackgroundColor
            self.mDrawMargins = map.mDrawMargins
            self.mTilesets = map.mTilesets
            self.mLayerDataFormat = map.mLayerDataFormat
            self.mNextObjectId = 1
            for layer in map.mLayers:
                clone = layer.clone()
                clone.setMap(self)
                self.mLayers.append(clone)
        elif l == 5:
            ##
            # Constructor, taking map orientation, size and tile size as parameters.
            ##
            orientation, width, height, tileWidth, tileHeight = args
            super().__init__(Object.MapType)

            self.mLayers = QList()
            self.mTilesets = QList()
            self.mOrientation = orientation
            self.mRenderOrder = Map.RenderOrder.RightDown
            self.mWidth = width
            self.mHeight = height
            self.mTileWidth = tileWidth
            self.mTileHeight = tileHeight
            self.mHexSideLength = 0
            self.mStaggerAxis = Map.StaggerAxis.StaggerY
            self.mStaggerIndex = Map.StaggerIndex.StaggerOdd
            self.mLayerDataFormat = Map.LayerDataFormat.Base64Zlib
            self.mNextObjectId = 1

    ##
    # Destructor.
    ##
    def __del__(self):
        self.mLayers.clear()

    ##
    # Returns the orientation of the map.
    ##
    def orientation(self):
        return self.mOrientation

    ##
    # Sets the orientation of the map.
    ##
    def setOrientation(self, orientation):
        self.mOrientation = orientation

    ##
    # Returns the render order of the map.
    ##
    def renderOrder(self):
        return self.mRenderOrder

    ##
    # Sets the render order of the map.
    ##
    def setRenderOrder(self, renderOrder):
        self.mRenderOrder = renderOrder

    ##
    # Returns the width of this map in tiles.
    ##
    def width(self):
        return self.mWidth

    ##
    # Sets the width of this map in tiles.
    ##
    def setWidth(self, width):
        self.mWidth = width

    ##
    # Returns the height of this map in tiles.
    ##
    def height(self):
        return self.mHeight

    ##
    # Sets the height of this map in tiles.
    ##
    def setHeight(self, height):
        self.mHeight = height

    ##
    # Returns the size of this map. Provided for convenience.
    ##
    def size(self):
        return QSize(self.mWidth, self.mHeight)

    ##
    # Returns the tile width of this map.
    ##
    def tileWidth(self):
        return self.mTileWidth

    ##
    # Sets the width of one tile.
    ##
    def setTileWidth(self, width):
        self.mTileWidth = width

    ##
    # Returns the tile height used by this map.
    ##
    def tileHeight(self):
        return self.mTileHeight

    ##
    # Sets the height of one tile.
    ##
    def setTileHeight(self, height):
        self.mTileHeight = height

    ##
    # Returns the size of one tile. Provided for convenience.
    ##
    def tileSize(self):
        return QSize(self.mTileWidth, self.mTileHeight)

    def hexSideLength(self):
        return self.mHexSideLength

    def setHexSideLength(self, hexSideLength):
        self.mHexSideLength = hexSideLength

    def staggerAxis(self):
        return self.mStaggerAxis

    def setStaggerAxis(self, staggerAxis):
        self.mStaggerAxis = staggerAxis

    def staggerIndex(self):
        return self.mStaggerIndex

    def setStaggerIndex(self, staggerIndex):
        self.mStaggerIndex = staggerIndex

    ##
    # Adjusts the draw margins to be at least as big as the given margins.
    # Called from tile layers when their tiles change.
    ##
    def adjustDrawMargins(self, margins):
        # The TileLayer includes the maximum tile size in its draw margins. So
        # we need to subtract the tile size of the map, since that part does not
        # contribute to additional margin.
        self.mDrawMargins = maxMargins(
            QMargins(margins.left(),
                     margins.top() - self.mTileHeight,
                     margins.right() - self.mTileWidth, margins.bottom()),
            self.mDrawMargins)

    ##
    # Computes the extra margins due to layer offsets. These need to be taken into
    # account when determining the bounding rect of the map for example.
    ##
    def computeLayerOffsetMargins(self):
        offsetMargins = QMargins()

        for layer in self.mLayers:
            offset = layer.offset()
            offsetMargins = maxMargins(
                QMargins(math.ceil(-offset.x()), math.ceil(-offset.y()),
                         math.ceil(offset.x()), math.ceil(offset.y())),
                offsetMargins)

        return offsetMargins

    ##
    # Returns the margins that have to be taken into account when figuring
    # out which part of the map to repaint after changing some tiles.
    #
    # @see TileLayer.drawMargins
    ##
    def drawMargins(self):
        return self.mDrawMargins

    ##
    # Recomputes the draw margins for this map and each of its tile layers. Needed
    # after the tile offset of a tileset has changed for example.
    #
    # \sa TileLayer.recomputeDrawMargins
    ##
    def recomputeDrawMargins(self):
        self.mDrawMargins = QMargins()
        for layer in self.mLayers:
            tileLayer = layer.asTileLayer()
            if tileLayer:
                tileLayer.recomputeDrawMargins()

    ##
    # Returns the number of layers of this map.
    ##
    def layerCount(self, *args):
        l = len(args)
        if l == 0:
            return self.mLayers.size()
        elif l == 1:
            ##
            # Convenience function that returns the number of layers of this map that
            # match the given \a type.
            ##
            tp = args[0]

            count = 0
            for layer in self.mLayers:
                if (layer.layerType() == tp):
                    count += 1
            return count

    def tileLayerCount(self):
        return self.layerCount(Layer.TileLayerType)

    def objectGroupCount(self):
        return self.layerCount(Layer.ObjectGroupType)

    def imageLayerCount(self):
        return self.layerCount(Layer.ImageLayerType)

    ##
    # Returns the layer at the specified index.
    ##
    def layerAt(self, index):
        return self.mLayers.at(index)

    ##
    # Returns the list of layers of this map. This is useful when you want to
    # use foreach.
    ##
    def layers(self, *args):
        l = len(args)
        if l == 0:
            return QList(self.mLayers)
        elif l == 1:
            tp = args[0]
            layers = QList()
            for layer in self.mLayers:
                if (layer.layerType() == tp):
                    layers.append(layer)
            return layers

    def objectGroups(self):
        layers = QList()
        for layer in self.mLayers:
            og = layer.asObjectGroup()
            if og:
                layers.append(og)
        return layers

    def tileLayers(self):
        layers = QList()
        for layer in self.mLayers:
            tl = layer.asTileLayer()
            if tl:
                layers.append(tl)
        return layers

    ##
    # Adds a layer to this map.
    ##
    def addLayer(self, layer):
        self.adoptLayer(layer)
        self.mLayers.append(layer)

    ##
    # Returns the index of the layer given by \a layerName, or -1 if no
    # layer with that name is found.
    #
    # The second optional parameter specifies the layer types which are
    # searched.
    ##
    def indexOfLayer(self, layerName, layertypes=Layer.AnyLayerType):
        for index in range(self.mLayers.size()):
            if (self.layerAt(index).name() == layerName
                    and (layertypes & self.layerAt(index).layerType())):
                return index
        return -1

    ##
    # Adds a layer to this map, inserting it at the given index.
    ##
    def insertLayer(self, index, layer):
        self.adoptLayer(layer)
        self.mLayers.insert(index, layer)

    ##
    # Removes the layer at the given index from this map and returns it.
    # The caller becomes responsible for the lifetime of this layer.
    ##
    def takeLayerAt(self, index):
        layer = self.mLayers.takeAt(index)
        layer.setMap(None)
        return layer

    ##
    # Adds a tileset to this map. The map does not take ownership over its
    # tilesets, this is merely for keeping track of which tilesets are used by
    # the map, and their saving order.
    #
    # @param tileset the tileset to add
    ##
    def addTileset(self, tileset):
        self.mTilesets.append(tileset)

    ##
    # Convenience function to be used together with Layer.usedTilesets()
    ##
    def addTilesets(self, tilesets):
        for tileset in tilesets:
            self.addTileset(tileset)

    ##
    # Inserts \a tileset at \a index in the list of tilesets used by this map.
    ##
    def insertTileset(self, index, tileset):
        self.mTilesets.insert(index, tileset)

    ##
    # Returns the index of the given \a tileset, or -1 if it is not used in
    # this map.
    ##
    def indexOfTileset(self, tileset):
        return self.mTilesets.indexOf(tileset)

    ##
    # Removes the tileset at \a index from this map.
    #
    # \warning Does not make sure that this map no longer refers to tiles from
    #          the removed tileset!
    #
    # \sa addTileset
    ##
    def removeTilesetAt(self, index):
        self.mTilesets.removeAt(index)

    ##
    # Replaces all tiles from \a oldTileset with tiles from \a newTileset.
    # Also replaces the old tileset with the new tileset in the list of
    # tilesets.
    ##
    def replaceTileset(self, oldTileset, newTileset):
        index = self.mTilesets.indexOf(oldTileset)
        for layer in self.mLayers:
            layer.replaceReferencesToTileset(oldTileset, newTileset)
        self.mTilesets[index] = newTileset

    ##
    # Returns the number of tilesets of this map.
    ##
    def tilesetCount(self):
        return self.mTilesets.size()

    ##
    # Returns the tileset at the given index.
    ##
    def tilesetAt(self, index):
        return self.mTilesets.at(index)

    ##
    # Returns the tilesets that the tiles on this map are using.
    ##
    def tilesets(self):
        return QList(self.mTilesets)

    ##
    # Returns the background color of this map.
    ##
    def backgroundColor(self):
        return QColor(self.mBackgroundColor)

    ##
    # Sets the background color of this map.
    ##
    def setBackgroundColor(self, color):
        self.mBackgroundColor = color

    ##
    # Returns whether the given \a tileset is used by any tile layer of this
    # map.
    ##
    def isTilesetUsed(self, tileset):
        for layer in self.mLayers:
            if (layer.referencesTileset(tileset)):
                return True
        return False

    ##
    # Creates a new map that contains the given \a layer. The map size will be
    # determined by the size of the layer.
    #
    # The orientation defaults to Unknown and the tile width and height will
    # default to 0. In case this map needs to be rendered, these properties
    # will need to be properly set.
    ##
    def fromLayer(layer):
        result = Map(Map.Orientation.Unknown, layer.width(), layer.height(), 0,
                     0)
        result.addLayer(layer)
        return result

    def layerDataFormat(self):
        return self.mLayerDataFormat

    def setLayerDataFormat(self, format):
        self.mLayerDataFormat = format

    ##
    # Sets the next id to be used for objects on this map.
    ##
    def setNextObjectId(self, nextId):
        self.mNextObjectId = nextId

    ##
    # Returns the next object id for this map.
    ##
    def nextObjectId(self):
        return self.mNextObjectId

    ##
    # Returns the next object id for this map and allocates a new one.
    ##
    def takeNextObjectId(self):
        return self.mNextObjectId + 1

    def adoptLayer(self, layer):
        layer.setMap(self)
        tileLayer = layer.asTileLayer()
        if tileLayer:
            self.adjustDrawMargins(tileLayer.drawMargins())
        group = layer.asObjectGroup()
        if group:
            for o in group.objects():
                if (o.id() == 0):
                    o.setId(self.takeNextObjectId())
Example #12
0
class MapObjectModel(QAbstractItemModel):
    objectsAdded = pyqtSignal(QList)
    objectsChanged = pyqtSignal(QList)
    objectsRemoved = pyqtSignal(QList)

    def __init__(self, parent):
        super().__init__(parent)

        self.mObjectGroups = QList()
        self.mObjects = QMap()
        self.mGroups = QMap()
        self.mMapDocument = None
        self.mMap = None
        self.mObject = None
        self.mObjectGroupIcon = ":/images/16x16/layer-object.png"

    def index(self, *args):
        l = len(args)
        if l>0:
            tp = type(args[0])
            if tp==int:
                if l==2:
                    args = (args[0], args[1], QModelIndex())
                row, column, parent = args
                if (not parent.isValid()):
                    if (row < self.mObjectGroups.count()):
                        return self.createIndex(row, column, self.mGroups[self.mObjectGroups.at(row)])
                    return QModelIndex()

                og = self.toObjectGroup(parent)
                # happens when deleting the last item in a parent
                if (row >= og.objectCount()):
                    return QModelIndex()
                # Paranoia: sometimes "fake" objects are in use (see createobjecttool)
                if (not self.mObjects.contains(og.objects().at(row))):
                    return QModelIndex()
                return self.createIndex(row, column, self.mObjects[og.objects()[row]])
            elif tp==ObjectGroup:
                og = args[0]
                row = self.mObjectGroups.indexOf(og)
                return self.createIndex(row, 0, self.mGroups[og])
            elif tp==MapObject:
                if l==1:
                    args = (args[0],0)
                o, column = args
                row = o.objectGroup().objects().indexOf(o)
                return self.createIndex(row, column, self.mObjects[o])

    def parent(self, index):
        mapObject = self.toMapObject(index)
        if mapObject:
            return self.index(mapObject.objectGroup())
        return QModelIndex()

    def rowCount(self, parent = QModelIndex()):
        if (not self.mMapDocument):
            return 0
        if (not parent.isValid()):
            return self.mObjectGroups.size()
        og = self.toObjectGroup(parent)
        if og:
            return og.objectCount()
        return 0

    def columnCount(self, parent = QModelIndex()):
        return 2 # MapObject name|type

    def headerData(self, section, orientation, role = Qt.DisplayRole):
        if (role == Qt.DisplayRole and orientation == Qt.Horizontal):
            x = section
            if x==0:
                return self.tr("Name")
            elif x==1:
                return self.tr("Type")

        return QVariant()

    def setData(self, index, value, role):
        mapObject = self.toMapObject(index)
        if mapObject:
            x = role
            if x==Qt.CheckStateRole:
                c = value
                visible = (c == Qt.Checked)
                if (visible != mapObject.isVisible()):
                    command = SetMapObjectVisible(self.mMapDocument, mapObject, visible)
                    self.mMapDocument.undoStack().push(command)
                return True
            elif x==Qt.EditRole:
                s = value
                if (index.column() == 0 and s != mapObject.name()):
                    undo = self.mMapDocument.undoStack()
                    undo.beginMacro(self.tr("Change Object Name"))
                    undo.push(ChangeMapObject(self.mMapDocument, mapObject, s, mapObject.type()))
                    undo.endMacro()

                if (index.column() == 1 and s != mapObject.type()):
                    undo = self.mMapDocument.undoStack()
                    undo.beginMacro(self.tr("Change Object Type"))
                    undo.push(ChangeMapObject(self.mMapDocument, mapObject, mapObject.name(), s))
                    undo.endMacro()

                return True

            return False

        objectGroup = self.toObjectGroup(index)
        if objectGroup:
            x = role
            if x==Qt.CheckStateRole:
                layerModel = self.mMapDocument.layerModel()
                layerIndex = self.mMap.layers().indexOf(objectGroup)
                row = layerModel.layerIndexToRow(layerIndex)
                layerModel.setData(layerModel.index(row), value, role)
                return True
            elif x==Qt.EditRole:
                newName = value
                if (objectGroup.name() != newName):
                    layerIndex = self.mMap.layers().indexOf(objectGroup)
                    rename = RenameLayer(self.mMapDocument, layerIndex,
                                                          newName)
                    self.mMapDocument.undoStack().push(rename)

                return True

            return False

        return False

    def data(self, index, role = Qt.DisplayRole):
        mapObject = self.toMapObject(index)
        if mapObject:
            x = role
            if x==Qt.DisplayRole or x==Qt.EditRole:
                if index.column():
                    _x = mapObject.type()
                else:
                    _x = mapObject.name()
                return _x
            elif x==Qt.DecorationRole:
                return QVariant() # no icon . maybe the color?
            elif x==Qt.CheckStateRole:
                if (index.column() > 0):
                    return QVariant()
                if mapObject.isVisible():
                    _x = Qt.Checked
                else:
                    _x = Qt.Unchecked
                return _x
            elif x==LayerModel.UserRoles.OpacityRole:
                return 1.0
            else:
                return QVariant()

        objectGroup = self.toObjectGroup(index)
        if objectGroup:
            x = role
            if x==Qt.DisplayRole or x==Qt.EditRole:
                if index.column():
                    _x = QVariant()
                else:
                    _x = objectGroup.name()
                return _x
            elif x==Qt.DecorationRole:
                if index.column():
                    _x = QVariant()
                else:
                    _x = self.mObjectGroupIcon
                return _x
            elif x==Qt.CheckStateRole:
                if (index.column() > 0):
                    return QVariant()
                if objectGroup.isVisible():
                    _x = Qt.Checked
                else:
                    _x = Qt.Unchecked
                return _x
            elif x==LayerModel.UserRoles.OpacityRole:
                return objectGroup.opacity()
            else:
                return QVariant()

        return QVariant()

    def flags(self, index):
        rc = super().flags(index)
        if (index.column() == 0):
            rc |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
        elif (index.parent().isValid()):
            rc |= Qt.ItemIsEditable # MapObject type
        return rc

    def toObjectGroup(self, index):
        if (not index.isValid()):
            return None
        oog = index.internalPointer()
        if oog:
            return oog.mGroup

    def toMapObject(self, index):
        if (not index.isValid()):
            return None
        oog = index.internalPointer()
        if oog:
            return oog.mObject

    def toLayer(self, index):
        if (not index.isValid()):
            return None
        oog = index.internalPointer()
        if oog:
            if oog.mGroup:
                _x = oog.mGroup
            else:
                _x = oog.mObject.objectGroup()
            return _x

    def setMapDocument(self, mapDocument):
        if (self.mMapDocument == mapDocument):
            return
        if (self.mMapDocument):
            self.mMapDocument.disconnect()
        self.beginResetModel()
        self.mMapDocument = mapDocument
        self.mMap = None
        self.mObjectGroups.clear()
        self.mGroups.clear()
        self.mGroups.clear()
        self.mObjects.clear()
        self.mObjects.clear()
        if (self.mMapDocument):
            self.mMap = self.mMapDocument.map()
            self.mMapDocument.layerAdded.connect(self.layerAdded)
            self.mMapDocument.layerChanged.connect(self.layerChanged)
            self.mMapDocument.layerAboutToBeRemoved.connect(self.layerAboutToBeRemoved)
            for og in self.mMap.objectGroups():
                if GROUPS_IN_DISPLAY_ORDER:
                    self.mObjectGroups.prepend(og)
                else:
                    self.mObjectGroups.append(og)
                self.mGroups.insert(og, ObjectOrGroup(og))
                for o in og.objects():
                    self.mObjects.insert(o, ObjectOrGroup(o))

        self.endResetModel()

    def insertObject(self, og, index, o):
        if (index >= 0):
            _x = index
        else:
            _x = og.objectCount()
        row = _x
        self.beginInsertRows(self.index(og), row, row)
        og.insertObject(row, o)
        self.mObjects.insert(o, ObjectOrGroup(o))
        self.endInsertRows()
        self.objectsAdded.emit(QList([o]))

    def removeObject(self, og, o):
        objects = QList()
        objects.append(o)
        row = og.objects().indexOf(o)
        self.beginRemoveRows(self.index(og), row, row)
        og.removeObjectAt(row)
        self.mObjects.remove(o)
        self.endRemoveRows()
        self.objectsRemoved.emit(objects)
        return row

    def moveObjects(self, og, _from, to, count):
        parent = self.index(og)
        if (not self.beginMoveRows(parent, _from, _from + count - 1, parent, to)):
            return

        og.moveObjects(_from, to, count)
        self.endMoveRows()

    # ObjectGroup color changed
    # FIXME: layerChanged should let the scene know that objects need redrawing
    def emitObjectsChanged(self, objects):
        if objects.isEmpty():
            return
        self.objectsChanged.emit(objects)

    def setObjectName(self, o, name):
        if o.name() == name:
            return
        o.setName(name)
        index = self.index(o)
        self.dataChanged.emit(index, index)
        self.objectsChanged.emit(QList([o]))

    def setObjectType(self, o, type):
        if o.type() == type:
            return
        o.setType(type)
        index = self.index(o, 1)
        self.dataChanged.emit(index, index)
        self.objectsChanged.emit(QList([o]))

    def setObjectPolygon(self, o, polygon):
        if o.polygon() == polygon:
            return
        o.setPolygon(polygon)
        self.objectsChanged.emit(QList([o]))

    def setObjectPosition(self, o, pos):
        if o.position() == pos:
            return
        o.setPosition(pos)
        self.objectsChanged.emit(QList([o]))

    def setObjectSize(self, o, size):
        if o.size() == size:
            return
        o.setSize(size)
        self.objectsChanged.emit(QList([o]))

    def setObjectRotation(self, o, rotation):
        if o.rotation() == rotation:
            return
        o.setRotation(rotation)
        self.objectsChanged.emit(QList([o]))

    def setObjectVisible(self, o, visible):
        if o.isVisible() == visible:
            return
        o.setVisible(visible)
        index = self.index(o)
        self.dataChanged.emit(index, index)
        self.objectsChanged.emit(QList([o]))

    def layerAdded(self, index):
        layer = self.mMap.layerAt(index)
        og = layer.asObjectGroup()
        if og:
            if (not self.mGroups.contains(og)):
                prev = None
                for index in range(index - 1, -1, -1):
                    prev = self.mMap.layerAt(index).asObjectGroup()
                    if prev:
                        break
                if GROUPS_IN_DISPLAY_ORDER:
                    if prev:
                        _x = self.mObjectGroups.indexOf(prev)
                    else:
                        _x = self.mObjectGroups.count()
                    index = _x
                else:
                    if prev:
                        index = self.mObjectGroups.indexOf(prev) + 1
                    else:
                        index = 0

                self.mObjectGroups.insert(index, og)
                row = self.mObjectGroups.indexOf(og)
                self.beginInsertRows(QModelIndex(), row, row)
                self.mGroups.insert(og, ObjectOrGroup(og))
                for o in og.objects():
                    if (not self.mObjects.contains(o)):
                        self.mObjects.insert(o, ObjectOrGroup(o))

                self.endInsertRows()

    def layerChanged(self, index):
        layer = self.mMap.layerAt(index)
        og = layer.asObjectGroup()
        if og:
            index = self.index(og)
            self.dataChanged.emit(index, index)

    def layerAboutToBeRemoved(self, index):
        layer = self.mMap.layerAt(index)
        og = layer.asObjectGroup()
        if og:
            row = self.mObjectGroups.indexOf(og)
            self.beginRemoveRows(QModelIndex(), row, row)
            self.mObjectGroups.removeAt(row)
            self.mGroups.remove(og)
            for o in og.objects():
                self.mObjects.remove(og)
            self.endRemoveRows()
Example #13
0
class RaiseLowerHelper():
    def __init__(self, mapScene):
        self.mSelectionRanges = RangeSet()

        self.mMapDocument = mapScene.mapDocument()
        self.mMapScene = mapScene
        # Context
        self.mObjectGroup = None
        self.mRelatedObjects = QList()

    def raise_(self):
        if (not self.initContext()):
            return
        # Iterate backwards over the ranges in order to keep the indexes valid
        size = len(self.mSelectionRanges)
        if size <= 0:  # no range
            return
        firstRange = self.mSelectionRanges.begin()
        it = self.mSelectionRanges.end()
        if (it == firstRange):  # no range
            return
        # For each range of objects, only the first will move
        commands = QList()

        lastIndex = len(self.mRelatedObjects) - 1
        for i in range(size - 1, -1, -1):
            it = self.mSelectionRanges.item(i)
            value = it[1]
            # The last range may be already at the top of the related items
            if value == lastIndex:
                continue
            movingItem = self.mRelatedObjects.at(value)
            targetItem = self.mRelatedObjects.at(value + 1)
            _from = int(movingItem.zValue())
            to = int(targetItem.zValue() + 1)
            commands.append(
                ChangeMapObjectsOrder(self.mMapDocument, self.mObjectGroup,
                                      _from, to, 1))
        self.push(commands,
                  QCoreApplication.translate("Undo Commands", "Raise Object"))

    def lower(self):
        if (not self.initContext()):
            return

        # For each range of objects, only the first will move
        commands = QList()
        for it in self.mSelectionRanges:
            value = it[0]
            # The first range may be already at the bottom of the related items
            if (value == 0):
                continue
            movingItem = self.mRelatedObjects.at(value)
            targetItem = self.mRelatedObjects.at(value - 1)
            _from = int(movingItem.zValue())
            to = int(targetItem.zValue())
            commands.append(
                ChangeMapObjectsOrder(self.mMapDocument, self.mObjectGroup,
                                      _from, to, 1))

        self.push(commands,
                  QCoreApplication.translate("Undo Commands", "Lower Object"))

    def raiseToTop(self):
        selectedItems = self.mMapScene.selectedObjectItems()
        objectGroup = RaiseLowerHelper.sameObjectGroup(selectedItems)
        if (not objectGroup):
            return
        if (objectGroup.drawOrder() != ObjectGroup.DrawOrder.IndexOrder):
            return
        ranges = RangeSet()
        for item in selectedItems:
            ranges.insert(int(item.zValue()))

        # Iterate backwards over the ranges in order to keep the indexes valid
        size = len(ranges)
        if size <= 0:  # no range
            return

        commands = QList()
        to = objectGroup.objectCount()
        for i in range(size - 1, -1, -1):
            it = ranges.item(i)
            first = it[0]
            last = it[1]
            count = last - first + 1
            if (last + 1 == to):
                to -= count
                continue

            _from = first
            commands.append(
                ChangeMapObjectsOrder(self.mMapDocument, objectGroup, _from,
                                      to, count))
            to -= count

        self.push(
            commands,
            QCoreApplication.translate("Undo Commands", "Raise Object To Top"))

    def lowerToBottom(self):
        selectedItems = self.mMapScene.selectedObjectItems()
        objectGroup = RaiseLowerHelper.sameObjectGroup(selectedItems)
        if (not objectGroup):
            return
        if (objectGroup.drawOrder() != ObjectGroup.DrawOrder.IndexOrder):
            return
        ranges = RangeSet()
        for item in selectedItems:
            ranges.insert(int(item.zValue()))

        commands = QList()
        to = 0
        for it in ranges:
            first = it[0]
            _from = first
            count = it[1] - first + 1
            if (_from == to):
                to += count
                continue

            commands.append(
                ChangeMapObjectsOrder(self.mMapDocument, objectGroup, _from,
                                      to, count))
            to += count

        self.push(
            commands,
            QCoreApplication.translate("Undo Commands",
                                       "Lower Object To Bottom"))

    def sameObjectGroup(items):
        if (items.isEmpty()):
            return None
        # All selected objects need to be in the same group
        group = items.begin().mapObject().objectGroup()
        for item in items:
            if (item.mapObject().objectGroup() != group):
                return None
        return group

    ##
    # Initializes the context in which objects are being raised or lowered. Only
    # used for single-step raising and lowering, since the context is not relevant
    # when raising to the top or lowering to the bottom.
    #
    # Returns whether the operation can be performed.
    ##
    def initContext(self):
        self.mObjectGroup = None
        self.mRelatedObjects.clear()
        self.mSelectionRanges.clear()
        selectedItems = self.mMapScene.selectedObjectItems()
        if (selectedItems.isEmpty()):
            return False
        # All selected objects need to be in the same group
        self.mObjectGroup = selectedItems.begin().mapObject().objectGroup()
        if (self.mObjectGroup.drawOrder() != ObjectGroup.DrawOrder.IndexOrder):
            return False
        shape = QPainterPath()
        for item in selectedItems:
            if (item.mapObject().objectGroup() != self.mObjectGroup):
                return False
            shape |= item.mapToScene(item.shape())

        # The list of related items are all items from the same object group
        # that share space with the selected items.
        items = self.mMapScene.items(shape, Qt.IntersectsItemShape,
                                     Qt.AscendingOrder)
        for item in items:
            if type(item) == MapObjectItem:
                if (item.mapObject().objectGroup() == self.mObjectGroup):
                    self.mRelatedObjects.append(item)

        for item in selectedItems:
            index = self.mRelatedObjects.indexOf(item)
            self.mSelectionRanges.insert(index)

        return True

    def push(self, commands, text):
        if (commands.isEmpty()):
            return
        undoStack = self.mMapDocument.undoStack()
        undoStack.beginMacro(text)
        for command in commands:
            undoStack.push(command)
        undoStack.endMacro()
Example #14
0
class TileStampModel(QAbstractItemModel):
    stampAdded = pyqtSignal(TileStamp)
    stampRenamed = pyqtSignal(TileStamp)
    stampChanged = pyqtSignal(TileStamp)
    stampRemoved = pyqtSignal(TileStamp)

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

        self.mStamps = QList()

    def index(self, *args):
        l = len(args)
        if l==1:
            stamp = args[0]
            i = self.mStamps.indexOf(stamp)
            if i == -1:
                return QModelIndex()
            else:
                return TileStampModel.index(i, 0)
        elif l==2 or l==3:
            if l==2:
                row, column = args
            elif l==3:
                row, column, parent = args
                
            if (not self.hasIndex(row, column, parent)):
                return QModelIndex()
            if (not parent.isValid()):
                return self.createIndex(row, column)
            elif (self.isStamp(parent)):
                return self.createIndex(row, column, parent.row() + 1)
            return QModelIndex()
    
    def parent(self, index):
        id = index.internalId()
        if id:
            return self.createIndex(id - 1, 0)
        return QModelIndex()
    
    def rowCount(self, parent = QModelIndex()):
        if (not parent.isValid()):
            return self.mStamps.size()
        elif (self.isStamp(parent)):
            stamp = self.mStamps.at(parent.row())
            count = stamp.variations().size()
            # it does not make much sense to expand single variations
            if count==1:
                return 0
            else:
                return count        
        return 0
    
    def columnCount(self, parent = QModelIndex()):
        return 2 # stamp | probability
    
    def headerData(self, section, orientation, role = Qt.DisplayRole):
        if (role == Qt.DisplayRole and orientation == Qt.Horizontal):
            x = section
            if x==0:
                return self.tr("Stamp")
            elif x==1:
                return self.tr("Probability")

        return QVariant()
    
    def setData(self, index, value, role = Qt.EditRole):
        if self.isStamp(index):
            stamp = self.mStamps[index.row()]
            if (index.column() == 0):      # stamp name
                x = role
                if x==Qt.EditRole:
                    stamp.setName(value.toString())
                    self.dataChanged.emit(index, index)
                    self.stampRenamed.emit(stamp)
                    self.stampChanged.emit(stamp)
                    return True
                else:
                    pass
        elif (index.column() == 1):   # variation probability
            parent = index.parent()
            if self.isStamp(parent):
                stamp = self.mStamps[parent.row()]
                stamp.setProbability(index.row(), value.toReal())
                self.dataChanged.emit(index, index)
                probabilitySumIndex = TileStampModel.index(parent.row(), 1)
                self.dataChanged.emit(probabilitySumIndex, probabilitySumIndex)
                self.stampChanged.emit(stamp)
                return True

        return False
    
    def data(self, index, role = Qt.DisplayRole):
        if (self.isStamp(index)):
            stamp = self.mStamps.at(index.row())
            if (index.column() == 0):          # preview and name
                x = role
                if x==Qt.DisplayRole or x==Qt.EditRole:
                    return stamp.name()
                elif x==Qt.DecorationRole:
                    map = stamp.variations().first().map
                    thumbnail = self.mThumbnailCache.value(map)
                    if (thumbnail.isNull()):
                        renderer = ThumbnailRenderer(map)
                        thumbnail = renderThumbnail(renderer)
                        self.mThumbnailCache.insert(map, thumbnail)
                    
                    return thumbnail

            elif (index.column() == 1):   # sum of probabilities
                x = role
                if x==Qt.DisplayRole:
                    if (stamp.variations().size() > 1):
                        sum = 0
                        for variation in stamp.variations():
                            sum += variation.probability
                        return sum
        else:
            variation = self.variationAt(index)
            if variation:
                if (index.column() == 0):
                    x = role
                    if x==Qt.DecorationRole:
                        map = variation.map
                        thumbnail = self.mThumbnailCache.value(map)
                        if (thumbnail.isNull()):
                            renderer = ThumbnailRenderer(map)
                            thumbnail = renderThumbnail(renderer)
                            self.mThumbnailCache.insert(map, thumbnail)
                        
                        return thumbnail

                elif (index.column() == 1):
                    x = role
                    if x==Qt.DisplayRole or x==Qt.EditRole:
                        return variation.probability
        
        return QVariant()
    
    def flags(self, index):
        rc = QAbstractItemModel.flags(index)
        validParent = index.parent().isValid()
        if ((not validParent and index.column() == 0) or    # can edit stamp names
                (validParent and index.column() == 1)):  # and variation probability
            rc |= Qt.ItemIsEditable
        return rc
        
    def removeRows(self, row, count, parent):
        if (parent.isValid()):
            # removing variations
            stamp = self.mStamps[parent.row()]
            # if only one variation is left, we make all variation rows disappear
            if (stamp.variations().size() - count == 1):
                self.beginRemoveRows(parent, 0, count)
            else:
                self.beginRemoveRows(parent, row, row + count - 1)
            
            for x in range(count, 0, -1):
                self.mThumbnailCache.remove(stamp.variations().at(row).map)
                stamp.deleteVariation(row)
            
            self.endRemoveRows()
            if (stamp.variations().isEmpty()):
                # remove stamp since all its variations were removed
                self.beginRemoveRows(QModelIndex(), parent.row(), parent.row())
                self.stampRemoved.emit(stamp)
                self.mStamps.removeAt(parent.row())
                self.endRemoveRows()
            else :
                if (row == 0):
                    # preview on stamp and probability sum need update
                    # (while technically I think this is correct, it triggers a
                    # repainting issue in QTreeView)
                    #emit dataChanged(index(parent.row(), 0),
                    #                 self.index(parent.row(), 1))
                    pass
                self.stampChanged.emit(stamp)
            
        else :
            # removing stamps
            self.beginRemoveRows(parent, row, row + count - 1)
            for x in range(count, 0, -1):
                for variation in self.mStamps.at(row).variations():
                    self.mThumbnailCache.remove(variation.map)
                self.stampRemoved.emit(self.mStamps.at(row))
                self.mStamps.removeAt(row)
            
            self.endRemoveRows()
        
        return True
    
    ##
    # Returns the stamp at the given \a index.
    ##
    def stampAt(self, index):
        return self.mStamps.at(index.row())

    def isStamp(self, index):
        return index.isValid() \
                and not index.parent().isValid() \
                and index.row() < self.mStamps.size()
    
    def variationAt(self, index):
        if (not index.isValid()):
            return None
        parent = index.parent()
        if (self.isStamp(parent)):
            stamp = self.mStamps.at(parent.row())
            return stamp.variations().at(index.row())
        
        return None
        
    def stamps(self):
        return self.mStamps

    def addStamp(self, stamp):
        if (self.mStamps.contains(stamp)):
            return
        self.beginInsertRows(QModelIndex(), self.mStamps.size(), self.mStamps.size())
        self.mStamps.append(stamp)
        self.stampAdded.emit(stamp)
        self.endInsertRows()
    
    def removeStamp(self, stamp):
        index = self.mStamps.indexOf(stamp)
        if (index == -1):
            return
        self.beginRemoveRows(QModelIndex(), index, index)
        self.mStamps.removeAt(index)
        self.endRemoveRows()
        for variation in stamp.variations():
            self.mThumbnailCache.remove(variation.map)
        self.stampRemoved.emit(stamp)
    
    def addVariation(self, stamp, variation):
        index = self.mStamps.indexOf(stamp)
        if (index == -1):
            return
        variationCount = stamp.variations().size()
        if (variationCount == 1):
            self.beginInsertRows(TileStampModel.index(index, 0), 0, 1)
        else:
            self.beginInsertRows(TileStampModel.index(index, 0),
                            variationCount, variationCount)
        self.mStamps[index].addVariation(variation)
        self.endInsertRows()
        probabilitySumIndex = TileStampModel.index(index, 1)
        self.dataChanged.emit(probabilitySumIndex, probabilitySumIndex)
        self.stampChanged.emit(stamp)
    
    def clear(self):
        self.beginResetModel()
        self.mStamps.clear()
        self.mThumbnailCache.clear()
        self.endResetModel()
Example #15
0
class MapObjectModel(QAbstractItemModel):
    objectsAdded = pyqtSignal(QList)
    objectsChanged = pyqtSignal(QList)
    objectsRemoved = pyqtSignal(QList)

    def __init__(self, parent):
        super().__init__(parent)

        self.mObjectGroups = QList()
        self.mObjects = QMap()
        self.mGroups = QMap()
        self.mMapDocument = None
        self.mMap = None
        self.mObject = None
        self.mObjectGroupIcon = ":/images/16x16/layer-object.png"

    def index(self, *args):
        l = len(args)
        if l > 0:
            tp = type(args[0])
            if tp == int:
                if l == 2:
                    args = (args[0], args[1], QModelIndex())
                row, column, parent = args
                if (not parent.isValid()):
                    if (row < self.mObjectGroups.count()):
                        return self.createIndex(
                            row, column,
                            self.mGroups[self.mObjectGroups.at(row)])
                    return QModelIndex()

                og = self.toObjectGroup(parent)
                # happens when deleting the last item in a parent
                if (row >= og.objectCount()):
                    return QModelIndex()
                # Paranoia: sometimes "fake" objects are in use (see createobjecttool)
                if (not self.mObjects.contains(og.objects().at(row))):
                    return QModelIndex()
                return self.createIndex(row, column,
                                        self.mObjects[og.objects()[row]])
            elif tp == ObjectGroup:
                og = args[0]
                row = self.mObjectGroups.indexOf(og)
                return self.createIndex(row, 0, self.mGroups[og])
            elif tp == MapObject:
                if l == 1:
                    args = (args[0], 0)
                o, column = args
                row = o.objectGroup().objects().indexOf(o)
                return self.createIndex(row, column, self.mObjects[o])

    def parent(self, index):
        mapObject = self.toMapObject(index)
        if mapObject:
            return self.index(mapObject.objectGroup())
        return QModelIndex()

    def rowCount(self, parent=QModelIndex()):
        if (not self.mMapDocument):
            return 0
        if (not parent.isValid()):
            return self.mObjectGroups.size()
        og = self.toObjectGroup(parent)
        if og:
            return og.objectCount()
        return 0

    def columnCount(self, parent=QModelIndex()):
        return 2  # MapObject name|type

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if (role == Qt.DisplayRole and orientation == Qt.Horizontal):
            x = section
            if x == 0:
                return self.tr("Name")
            elif x == 1:
                return self.tr("Type")

        return QVariant()

    def setData(self, index, value, role):
        mapObject = self.toMapObject(index)
        if mapObject:
            x = role
            if x == Qt.CheckStateRole:
                c = value
                visible = (c == Qt.Checked)
                if (visible != mapObject.isVisible()):
                    command = SetMapObjectVisible(self.mMapDocument, mapObject,
                                                  visible)
                    self.mMapDocument.undoStack().push(command)
                return True
            elif x == Qt.EditRole:
                s = value
                if (index.column() == 0 and s != mapObject.name()):
                    undo = self.mMapDocument.undoStack()
                    undo.beginMacro(self.tr("Change Object Name"))
                    undo.push(
                        ChangeMapObject(self.mMapDocument, mapObject, s,
                                        mapObject.type()))
                    undo.endMacro()

                if (index.column() == 1 and s != mapObject.type()):
                    undo = self.mMapDocument.undoStack()
                    undo.beginMacro(self.tr("Change Object Type"))
                    undo.push(
                        ChangeMapObject(self.mMapDocument, mapObject,
                                        mapObject.name(), s))
                    undo.endMacro()

                return True

            return False

        objectGroup = self.toObjectGroup(index)
        if objectGroup:
            x = role
            if x == Qt.CheckStateRole:
                layerModel = self.mMapDocument.layerModel()
                layerIndex = self.mMap.layers().indexOf(objectGroup)
                row = layerModel.layerIndexToRow(layerIndex)
                layerModel.setData(layerModel.index(row), value, role)
                return True
            elif x == Qt.EditRole:
                newName = value
                if (objectGroup.name() != newName):
                    layerIndex = self.mMap.layers().indexOf(objectGroup)
                    rename = RenameLayer(self.mMapDocument, layerIndex,
                                         newName)
                    self.mMapDocument.undoStack().push(rename)

                return True

            return False

        return False

    def data(self, index, role=Qt.DisplayRole):
        mapObject = self.toMapObject(index)
        if mapObject:
            x = role
            if x == Qt.DisplayRole or x == Qt.EditRole:
                if index.column():
                    _x = mapObject.type()
                else:
                    _x = mapObject.name()
                return _x
            elif x == Qt.DecorationRole:
                return QVariant()  # no icon . maybe the color?
            elif x == Qt.CheckStateRole:
                if (index.column() > 0):
                    return QVariant()
                if mapObject.isVisible():
                    _x = Qt.Checked
                else:
                    _x = Qt.Unchecked
                return _x
            elif x == LayerModel.UserRoles.OpacityRole:
                return 1.0
            else:
                return QVariant()

        objectGroup = self.toObjectGroup(index)
        if objectGroup:
            x = role
            if x == Qt.DisplayRole or x == Qt.EditRole:
                if index.column():
                    _x = QVariant()
                else:
                    _x = objectGroup.name()
                return _x
            elif x == Qt.DecorationRole:
                if index.column():
                    _x = QVariant()
                else:
                    _x = self.mObjectGroupIcon
                return _x
            elif x == Qt.CheckStateRole:
                if (index.column() > 0):
                    return QVariant()
                if objectGroup.isVisible():
                    _x = Qt.Checked
                else:
                    _x = Qt.Unchecked
                return _x
            elif x == LayerModel.UserRoles.OpacityRole:
                return objectGroup.opacity()
            else:
                return QVariant()

        return QVariant()

    def flags(self, index):
        rc = super().flags(index)
        if (index.column() == 0):
            rc |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
        elif (index.parent().isValid()):
            rc |= Qt.ItemIsEditable  # MapObject type
        return rc

    def toObjectGroup(self, index):
        if (not index.isValid()):
            return None
        oog = index.internalPointer()
        if oog:
            return oog.mGroup

    def toMapObject(self, index):
        if (not index.isValid()):
            return None
        oog = index.internalPointer()
        if oog:
            return oog.mObject

    def toLayer(self, index):
        if (not index.isValid()):
            return None
        oog = index.internalPointer()
        if oog:
            if oog.mGroup:
                _x = oog.mGroup
            else:
                _x = oog.mObject.objectGroup()
            return _x

    def setMapDocument(self, mapDocument):
        if (self.mMapDocument == mapDocument):
            return
        if (self.mMapDocument):
            self.mMapDocument.disconnect()
        self.beginResetModel()
        self.mMapDocument = mapDocument
        self.mMap = None
        self.mObjectGroups.clear()
        self.mGroups.clear()
        self.mGroups.clear()
        self.mObjects.clear()
        self.mObjects.clear()
        if (self.mMapDocument):
            self.mMap = self.mMapDocument.map()
            self.mMapDocument.layerAdded.connect(self.layerAdded)
            self.mMapDocument.layerChanged.connect(self.layerChanged)
            self.mMapDocument.layerAboutToBeRemoved.connect(
                self.layerAboutToBeRemoved)
            for og in self.mMap.objectGroups():
                if GROUPS_IN_DISPLAY_ORDER:
                    self.mObjectGroups.prepend(og)
                else:
                    self.mObjectGroups.append(og)
                self.mGroups.insert(og, ObjectOrGroup(og))
                for o in og.objects():
                    self.mObjects.insert(o, ObjectOrGroup(o))

        self.endResetModel()

    def insertObject(self, og, index, o):
        if (index >= 0):
            _x = index
        else:
            _x = og.objectCount()
        row = _x
        self.beginInsertRows(self.index(og), row, row)
        og.insertObject(row, o)
        self.mObjects.insert(o, ObjectOrGroup(o))
        self.endInsertRows()
        self.objectsAdded.emit(QList([o]))

    def removeObject(self, og, o):
        objects = QList()
        objects.append(o)
        row = og.objects().indexOf(o)
        self.beginRemoveRows(self.index(og), row, row)
        og.removeObjectAt(row)
        self.mObjects.remove(o)
        self.endRemoveRows()
        self.objectsRemoved.emit(objects)
        return row

    def moveObjects(self, og, _from, to, count):
        parent = self.index(og)
        if (not self.beginMoveRows(parent, _from, _from + count - 1, parent,
                                   to)):
            return

        og.moveObjects(_from, to, count)
        self.endMoveRows()

    # ObjectGroup color changed
    # FIXME: layerChanged should let the scene know that objects need redrawing
    def emitObjectsChanged(self, objects):
        if objects.isEmpty():
            return
        self.objectsChanged.emit(objects)

    def setObjectName(self, o, name):
        if o.name() == name:
            return
        o.setName(name)
        index = self.index(o)
        self.dataChanged.emit(index, index)
        self.objectsChanged.emit(QList([o]))

    def setObjectType(self, o, type):
        if o.type() == type:
            return
        o.setType(type)
        index = self.index(o, 1)
        self.dataChanged.emit(index, index)
        self.objectsChanged.emit(QList([o]))

    def setObjectPolygon(self, o, polygon):
        if o.polygon() == polygon:
            return
        o.setPolygon(polygon)
        self.objectsChanged.emit(QList([o]))

    def setObjectPosition(self, o, pos):
        if o.position() == pos:
            return
        o.setPosition(pos)
        self.objectsChanged.emit(QList([o]))

    def setObjectSize(self, o, size):
        if o.size() == size:
            return
        o.setSize(size)
        self.objectsChanged.emit(QList([o]))

    def setObjectRotation(self, o, rotation):
        if o.rotation() == rotation:
            return
        o.setRotation(rotation)
        self.objectsChanged.emit(QList([o]))

    def setObjectVisible(self, o, visible):
        if o.isVisible() == visible:
            return
        o.setVisible(visible)
        index = self.index(o)
        self.dataChanged.emit(index, index)
        self.objectsChanged.emit(QList([o]))

    def layerAdded(self, index):
        layer = self.mMap.layerAt(index)
        og = layer.asObjectGroup()
        if og:
            if (not self.mGroups.contains(og)):
                prev = None
                for index in range(index - 1, -1, -1):
                    prev = self.mMap.layerAt(index).asObjectGroup()
                    if prev:
                        break
                if GROUPS_IN_DISPLAY_ORDER:
                    if prev:
                        _x = self.mObjectGroups.indexOf(prev)
                    else:
                        _x = self.mObjectGroups.count()
                    index = _x
                else:
                    if prev:
                        index = self.mObjectGroups.indexOf(prev) + 1
                    else:
                        index = 0

                self.mObjectGroups.insert(index, og)
                row = self.mObjectGroups.indexOf(og)
                self.beginInsertRows(QModelIndex(), row, row)
                self.mGroups.insert(og, ObjectOrGroup(og))
                for o in og.objects():
                    if (not self.mObjects.contains(o)):
                        self.mObjects.insert(o, ObjectOrGroup(o))

                self.endInsertRows()

    def layerChanged(self, index):
        layer = self.mMap.layerAt(index)
        og = layer.asObjectGroup()
        if og:
            index = self.index(og)
            self.dataChanged.emit(index, index)

    def layerAboutToBeRemoved(self, index):
        layer = self.mMap.layerAt(index)
        og = layer.asObjectGroup()
        if og:
            row = self.mObjectGroups.indexOf(og)
            self.beginRemoveRows(QModelIndex(), row, row)
            self.mObjectGroups.removeAt(row)
            self.mGroups.remove(og)
            for o in og.objects():
                self.mObjects.remove(og)
            self.endRemoveRows()
Example #16
0
class TileStampModel(QAbstractItemModel):
    stampAdded = pyqtSignal(TileStamp)
    stampRenamed = pyqtSignal(TileStamp)
    stampChanged = pyqtSignal(TileStamp)
    stampRemoved = pyqtSignal(TileStamp)

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

        self.mStamps = QList()

    def index(self, *args):
        l = len(args)
        if l == 1:
            stamp = args[0]
            i = self.mStamps.indexOf(stamp)
            if i == -1:
                return QModelIndex()
            else:
                return TileStampModel.index(i, 0)
        elif l == 2 or l == 3:
            if l == 2:
                row, column = args
            elif l == 3:
                row, column, parent = args

            if (not self.hasIndex(row, column, parent)):
                return QModelIndex()
            if (not parent.isValid()):
                return self.createIndex(row, column)
            elif (self.isStamp(parent)):
                return self.createIndex(row, column, parent.row() + 1)
            return QModelIndex()

    def parent(self, index):
        id = index.internalId()
        if id:
            return self.createIndex(id - 1, 0)
        return QModelIndex()

    def rowCount(self, parent=QModelIndex()):
        if (not parent.isValid()):
            return self.mStamps.size()
        elif (self.isStamp(parent)):
            stamp = self.mStamps.at(parent.row())
            count = stamp.variations().size()
            # it does not make much sense to expand single variations
            if count == 1:
                return 0
            else:
                return count
        return 0

    def columnCount(self, parent=QModelIndex()):
        return 2  # stamp | probability

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if (role == Qt.DisplayRole and orientation == Qt.Horizontal):
            x = section
            if x == 0:
                return self.tr("Stamp")
            elif x == 1:
                return self.tr("Probability")

        return QVariant()

    def setData(self, index, value, role=Qt.EditRole):
        if self.isStamp(index):
            stamp = self.mStamps[index.row()]
            if (index.column() == 0):  # stamp name
                x = role
                if x == Qt.EditRole:
                    stamp.setName(value.toString())
                    self.dataChanged.emit(index, index)
                    self.stampRenamed.emit(stamp)
                    self.stampChanged.emit(stamp)
                    return True
                else:
                    pass
        elif (index.column() == 1):  # variation probability
            parent = index.parent()
            if self.isStamp(parent):
                stamp = self.mStamps[parent.row()]
                stamp.setProbability(index.row(), value.toReal())
                self.dataChanged.emit(index, index)
                probabilitySumIndex = TileStampModel.index(parent.row(), 1)
                self.dataChanged.emit(probabilitySumIndex, probabilitySumIndex)
                self.stampChanged.emit(stamp)
                return True

        return False

    def data(self, index, role=Qt.DisplayRole):
        if (self.isStamp(index)):
            stamp = self.mStamps.at(index.row())
            if (index.column() == 0):  # preview and name
                x = role
                if x == Qt.DisplayRole or x == Qt.EditRole:
                    return stamp.name()
                elif x == Qt.DecorationRole:
                    map = stamp.variations().first().map
                    thumbnail = self.mThumbnailCache.value(map)
                    if (thumbnail.isNull()):
                        renderer = ThumbnailRenderer(map)
                        thumbnail = renderThumbnail(renderer)
                        self.mThumbnailCache.insert(map, thumbnail)

                    return thumbnail

            elif (index.column() == 1):  # sum of probabilities
                x = role
                if x == Qt.DisplayRole:
                    if (stamp.variations().size() > 1):
                        sum = 0
                        for variation in stamp.variations():
                            sum += variation.probability
                        return sum
        else:
            variation = self.variationAt(index)
            if variation:
                if (index.column() == 0):
                    x = role
                    if x == Qt.DecorationRole:
                        map = variation.map
                        thumbnail = self.mThumbnailCache.value(map)
                        if (thumbnail.isNull()):
                            renderer = ThumbnailRenderer(map)
                            thumbnail = renderThumbnail(renderer)
                            self.mThumbnailCache.insert(map, thumbnail)

                        return thumbnail

                elif (index.column() == 1):
                    x = role
                    if x == Qt.DisplayRole or x == Qt.EditRole:
                        return variation.probability

        return QVariant()

    def flags(self, index):
        rc = QAbstractItemModel.flags(index)
        validParent = index.parent().isValid()
        if ((not validParent and index.column() == 0)
                or  # can edit stamp names
            (validParent
             and index.column() == 1)):  # and variation probability
            rc |= Qt.ItemIsEditable
        return rc

    def removeRows(self, row, count, parent):
        if (parent.isValid()):
            # removing variations
            stamp = self.mStamps[parent.row()]
            # if only one variation is left, we make all variation rows disappear
            if (stamp.variations().size() - count == 1):
                self.beginRemoveRows(parent, 0, count)
            else:
                self.beginRemoveRows(parent, row, row + count - 1)

            for x in range(count, 0, -1):
                self.mThumbnailCache.remove(stamp.variations().at(row).map)
                stamp.deleteVariation(row)

            self.endRemoveRows()
            if (stamp.variations().isEmpty()):
                # remove stamp since all its variations were removed
                self.beginRemoveRows(QModelIndex(), parent.row(), parent.row())
                self.stampRemoved.emit(stamp)
                self.mStamps.removeAt(parent.row())
                self.endRemoveRows()
            else:
                if (row == 0):
                    # preview on stamp and probability sum need update
                    # (while technically I think this is correct, it triggers a
                    # repainting issue in QTreeView)
                    #emit dataChanged(index(parent.row(), 0),
                    #                 self.index(parent.row(), 1))
                    pass
                self.stampChanged.emit(stamp)

        else:
            # removing stamps
            self.beginRemoveRows(parent, row, row + count - 1)
            for x in range(count, 0, -1):
                for variation in self.mStamps.at(row).variations():
                    self.mThumbnailCache.remove(variation.map)
                self.stampRemoved.emit(self.mStamps.at(row))
                self.mStamps.removeAt(row)

            self.endRemoveRows()

        return True

    ##
    # Returns the stamp at the given \a index.
    ##
    def stampAt(self, index):
        return self.mStamps.at(index.row())

    def isStamp(self, index):
        return index.isValid() \
                and not index.parent().isValid() \
                and index.row() < self.mStamps.size()

    def variationAt(self, index):
        if (not index.isValid()):
            return None
        parent = index.parent()
        if (self.isStamp(parent)):
            stamp = self.mStamps.at(parent.row())
            return stamp.variations().at(index.row())

        return None

    def stamps(self):
        return self.mStamps

    def addStamp(self, stamp):
        if (self.mStamps.contains(stamp)):
            return
        self.beginInsertRows(QModelIndex(), self.mStamps.size(),
                             self.mStamps.size())
        self.mStamps.append(stamp)
        self.stampAdded.emit(stamp)
        self.endInsertRows()

    def removeStamp(self, stamp):
        index = self.mStamps.indexOf(stamp)
        if (index == -1):
            return
        self.beginRemoveRows(QModelIndex(), index, index)
        self.mStamps.removeAt(index)
        self.endRemoveRows()
        for variation in stamp.variations():
            self.mThumbnailCache.remove(variation.map)
        self.stampRemoved.emit(stamp)

    def addVariation(self, stamp, variation):
        index = self.mStamps.indexOf(stamp)
        if (index == -1):
            return
        variationCount = stamp.variations().size()
        if (variationCount == 1):
            self.beginInsertRows(TileStampModel.index(index, 0), 0, 1)
        else:
            self.beginInsertRows(TileStampModel.index(index, 0),
                                 variationCount, variationCount)
        self.mStamps[index].addVariation(variation)
        self.endInsertRows()
        probabilitySumIndex = TileStampModel.index(index, 1)
        self.dataChanged.emit(probabilitySumIndex, probabilitySumIndex)
        self.stampChanged.emit(stamp)

    def clear(self):
        self.beginResetModel()
        self.mStamps.clear()
        self.mThumbnailCache.clear()
        self.endResetModel()
Example #17
0
class QtButtonPropertyBrowserPrivate():
    def __init__(self):
        self.q_ptr = None
        self.WidgetItem = WidgetItem()
        self.m_indexToItem = QMap()
        self.m_itemToIndex = QMap()
        self.m_widgetToItem = QMap()
        self.m_buttonToItem = QMap()
        self.m_mainLayout = None
        self.m_children = QList()
        self.m_recreateQueue = QList()

    def createEditor(self, property, parent):
        return self.q_ptr.createEditor(property, parent)

    def createButton(self, parent=None):
        button = QToolButton(parent)
        button.setCheckable(True)
        button.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed))
        button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        button.setArrowType(Qt.DownArrow)
        button.setIconSize(QSize(3, 16))
        ###
        #QIcon icon
        #icon.addPixmap(self.style().standardPixmap(QStyle.SP_ArrowDown), QIcon.Normal, QIcon.Off)
        #icon.addPixmap(self.style().standardPixmap(QStyle.SP_ArrowUp), QIcon.Normal, QIcon.On)
        #button.setIcon(icon)
        ###
        return button

    def gridRow(self, item):
        siblings = QList()
        if (item.parent):
            siblings = item.parent.children
        else:
            siblings = self.m_children

        row = 0
        for sibling in siblings:
            if (sibling == item):
                return row
            row += self.gridSpan(sibling)

        return -1

    def gridSpan(self, item):
        if (item.container and item.expanded):
            return 2
        return 1

    def init(self, parent):
        self.m_mainLayout = QGridLayout()
        parent.setLayout(self.m_mainLayout)
        item = QSpacerItem(0, 0, QSizePolicy.Fixed, QSizePolicy.Expanding)
        self.m_mainLayout.addItem(item, 0, 0)

    def slotEditorDestroyed(self):
        editor = self.q_ptr.sender()
        if (not editor):
            return
        if not self.m_widgetToItem.get(editor):
            return
        self.m_widgetToItem[editor].widget = 0
        self.m_widgetToItem.remove(editor)

    def slotUpdate(self):
        for item in self.m_recreateQueue:
            parent = item.parent
            w = 0
            l = 0
            oldRow = self.gridRow(item)
            if (parent):
                w = parent.container
                l = parent.layout
            else:
                w = self.q_ptr
                l = self.m_mainLayout

            span = 1
            if (not item.widget and not item.widgetLabel):
                span = 2
            item.label = QLabel(w)
            item.label.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
            l.addWidget(item.label, oldRow, 0, 1, span)

            self.updateItem(item)

        self.m_recreateQueue.clear()

    def setExpanded(self, item, expanded):
        if (item.expanded == expanded):
            return

        if (not item.container):
            return

        item.expanded = expanded
        row = self.gridRow(item)
        parent = item.parent
        l = 0
        if (parent):
            l = parent.layout
        else:
            l = self.m_mainLayout

        if (expanded):
            self.insertRow(l, row + 1)
            l.addWidget(item.container, row + 1, 0, 1, 2)
            item.container.show()
        else:
            l.removeWidget(item.container)
            item.container.hide()
            self.removeRow(l, row + 1)

        item.button.setChecked(expanded)
        if expanded:
            item.button.setArrowType(Qt.UpArrow)
        else:
            item.button.setArrowType(Qt.DownArrow)

    def slotToggled(self, checked):
        item = self.m_buttonToItem[self.q_ptr.sender()]
        if (not item):
            return

        self.setExpanded(item, checked)

        if (checked):
            self.q_ptr.expandedSignal.emit(self.m_itemToIndex[item])
        else:
            self.q_ptr.collapsedSignal.emit(self.m_itemToIndex[item])

    def updateLater(self):
        QTimer.singleShot(0, self.slotUpdate)

    def propertyInserted(self, index, afterIndex):
        afterItem = self.m_indexToItem[afterIndex]
        parentItem = self.m_indexToItem.value(index.parent())

        newItem = WidgetItem()
        newItem.parent = parentItem

        layout = 0
        parentWidget = 0
        row = -1
        if (not afterItem):
            row = 0
            if (parentItem):
                parentItem.children.insert(0, newItem)
            else:
                self.m_children.insert(0, newItem)
        else:
            row = self.gridRow(afterItem) + self.gridSpan(afterItem)
            if (parentItem):
                parentItem.children.insert(parentItem.children.indexOf(afterItem) + 1, newItem)
            else:
                self.m_children.insert(self.m_children.indexOf(afterItem) + 1, newItem)

        if (not parentItem):
            layout = self.m_mainLayout
            parentWidget = self.q_ptr
        else:
            if (not parentItem.container):
                self.m_recreateQueue.removeAll(parentItem)
                grandParent = parentItem.parent
                l = 0
                oldRow = self.gridRow(parentItem)
                if (grandParent):
                    l = grandParent.layout
                else:
                    l = self.m_mainLayout

                container = QFrame()
                container.setFrameShape(QFrame.Panel)
                container.setFrameShadow(QFrame.Raised)
                parentItem.container = container
                parentItem.button = self.createButton()
                self.m_buttonToItem[parentItem.button] = parentItem
                parentItem.button.toggled.connect(self.slotToggled)
                parentItem.layout = QGridLayout()
                container.setLayout(parentItem.layout)
                if (parentItem.label):
                    l.removeWidget(parentItem.label)
                    parentItem.label.close()
                    parentItem.label = 0

                span = 1
                if (not parentItem.widget and not parentItem.widgetLabel):
                    span = 2
                l.addWidget(parentItem.button, oldRow, 0, 1, span)
                self.updateItem(parentItem)

            layout = parentItem.layout
            parentWidget = parentItem.container

        newItem.label = QLabel(parentWidget)
        newItem.label.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        newItem.widget = self.createEditor(index.property(), parentWidget)
        if (newItem.widget):
            newItem.widget.destroyed.connect(self.slotEditorDestroyed)
            self.m_widgetToItem[newItem.widget] = newItem
        elif (index.property().hasValue()):
            newItem.widgetLabel = QLabel(parentWidget)
            newItem.widgetLabel.setSizePolicy(QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed))

        self.insertRow(layout, row)
        span = 1
        if (newItem.widget):
            layout.addWidget(newItem.widget, row, 1)
        elif (newItem.widgetLabel):
            layout.addWidget(newItem.widgetLabel, row, 1)
        else:
            span = 2
        layout.addWidget(newItem.label, row, 0, span, 1)

        self.m_itemToIndex[newItem] = index
        self.m_indexToItem[index] = newItem

        self.updateItem(newItem)

    def propertyRemoved(self, index):
        item = self.m_indexToItem[index]

        self.m_indexToItem.remove(index)
        self.m_itemToIndex.remove(item)

        parentItem = item.parent

        row = self.gridRow(item)

        if (parentItem):
            parentItem.children.removeAt(parentItem.children.indexOf(item))
        else:
            self.m_children.removeAt(self.m_children.indexOf(item))

        colSpan = self.gridSpan(item)

        self.m_buttonToItem.remove(item.button)

        if (item.widget):
            item.widget.close()
            del item.widget
        if (item.label):
            item.label.close()
            del item.label
        if (item.widgetLabel):
            item.widgetLabel.close()
            del item.widgetLabel
        if (item.button):
            item.button.close()
            del item.button
        if (item.container):
            item.container.close()
            del item.container

        if (not parentItem):
            self.removeRow(self.m_mainLayout, row)
            if (colSpan > 1):
                self.removeRow(self.m_mainLayout, row)
        elif (len(parentItem.children) != 0):
            self.removeRow(parentItem.layout, row)
            if (colSpan > 1):
                self.removeRow(parentItem.layout, row)
        else:
            grandParent = parentItem.parent
            l = 0
            if (grandParent):
                l = grandParent.layout
            else:
                l = self.m_mainLayout

            parentRow = self.gridRow(parentItem)
            parentSpan = self.gridSpan(parentItem)

            l.removeWidget(parentItem.button)
            l.removeWidget(parentItem.container)
            parentItem.button.close()
            del parentItem.button
            parentItem.container.close()
            del parentItem.container
            parentItem.button = 0
            parentItem.container = 0
            parentItem.layout = 0
            if (not parentItem in self.m_recreateQueue):
                 self.m_recreateQueue.append(parentItem)
            if (parentSpan > 1):
                self.removeRow(l, parentRow + 1)

            self.updateLater()

        self.m_recreateQueue.removeAll(item)

        del item

    def insertRow(self, layout, row):
        itemToPos = QMap()
        idx = 0
        while (idx < len(layout)):
            r, c, rs, cs = layout.getItemPosition(idx)
            if (r >= row):
                itemToPos[layout.takeAt(idx)] = QRect(r + 1, c, rs, cs)
            else:
                idx += 1

        for k in itemToPos.keys():
            r = itemToPos[k]
            layout.addItem(k, r.x(), r.y(), r.width(), r.height())

    def removeRow(self, layout, row):
        itemToPos = QMap()
        idx = 0
        while (idx < len(layout)):
            r, c, rs, cs = layout.getItemPosition(idx)
            if (r > row):
                itemToPos[layout.takeAt(idx)] = QRect(r - 1, c, rs, cs)
            else:
                idx += 1

        for k in itemToPos.keys():
            r = itemToPos[k]
            layout.addItem(k, r.x(), r.y(), r.width(), r.height())

    def propertyChanged(self, index):
        item = self.m_indexToItem[index]

        self.updateItem(item)

    def updateItem(self, item):
        property = self.m_itemToIndex[item].property()
        if (item.button):
            font = item.button.font()
            font.setUnderline(property.isModified())
            item.button.setFont(font)
            item.button.setText(property.propertyName())
            item.button.setToolTip(property.toolTip())
            item.button.setStatusTip(property.statusTip())
            item.button.setWhatsThis(property.whatsThis())
            item.button.setEnabled(property.isEnabled())

        if (item.label):
            font = item.label.font()
            font.setUnderline(property.isModified())
            item.label.setFont(font)
            item.label.setText(property.propertyName())
            item.label.setToolTip(property.toolTip())
            item.label.setStatusTip(property.statusTip())
            item.label.setWhatsThis(property.whatsThis())
            item.label.setEnabled(property.isEnabled())

        if (item.widgetLabel):
            font = item.widgetLabel.font()
            font.setUnderline(False)
            item.widgetLabel.setFont(font)
            item.widgetLabel.setText(property.valueText())
            item.widgetLabel.setToolTip(property.valueText())
            item.widgetLabel.setEnabled(property.isEnabled())

        if (item.widget):
            font = item.widget.font()
            font.setUnderline(False)
            item.widget.setFont(font)
            item.widget.setEnabled(property.isEnabled())
            item.widget.setToolTip(property.valueText())
Example #18
0
class ObjectGroup(Layer):
    ##
    # Objects within an object group can either be drawn top down (sorted
    # by their y-coordinate) or by index (manual stacking order).
    #
    # The default is top down.
    ##
    class DrawOrder():
        UnknownOrder = -1
        TopDownOrder = 1
        IndexOrder = 2

    ##
    # Default constructor.
    ##
    def __init__(self, *args):
        self.mObjects = QList()
        self.mColor = QColor()

        l = len(args)
        if l == 0:
            super().__init__(Layer.ObjectGroupType, QString(), 0, 0, 0, 0)
        elif l == 5:
            ##
            # Constructor with some parameters.
            ##
            name, x, y, width, height = args

            super().__init__(Layer.ObjectGroupType, name, x, y, width, height)
        else:
            pass
        self.mDrawOrder = ObjectGroup.DrawOrder.IndexOrder

    ##
    # Destructor.
    ##
    def __del__(self):
        self.mObjects.clear()

    ##
    # Returns a pointer to the list of objects in this object group.
    ##
    def objects(self):
        return QList(self.mObjects)

    ##
    # Returns the number of objects in this object group.
    ##
    def objectCount(self):
        return self.mObjects.size()

    ##
    # Returns the object at the specified index.
    ##
    def objectAt(self, index):
        return self.mObjects.at(index)

    ##
    # Adds an object to this object group.
    ##
    def addObject(self, object):
        self.mObjects.append(object)
        object.setObjectGroup(self)
        if (self.mMap and object.id() == 0):
            object.setId(self.mMap.takeNextObjectId())

    ##
    # Inserts an object at the specified index. This is only used for undoing
    # the removal of an object at the moment, to make sure not to change the
    # saved order of the objects.
    ##
    def insertObject(self, index, object):
        self.mObjects.insert(index, object)
        object.setObjectGroup(self)
        if (self.mMap and object.id() == 0):
            object.setId(self.mMap.takeNextObjectId())

    ##
    # Removes an object from this object group. Ownership of the object is
    # transferred to the caller.
    #
    # @return the index at which the specified object was removed
    ##
    def removeObject(self, object):
        index = self.mObjects.indexOf(object)
        self.mObjects.removeAt(index)
        object.setObjectGroup(None)
        return index

    ##
    # Removes the object at the given index. Ownership of the object is
    # transferred to the caller.
    #
    # This is faster than removeObject when you've already got the index.
    #
    # @param index the index at which to remove an object
    ##
    def removeObjectAt(self, index):
        object = self.mObjects.takeAt(index)
        object.setObjectGroup(None)

    ##
    # Moves \a count objects starting at \a from to the index given by \a to.
    #
    # The \a to index may not lie within the range of objects that is
    # being moved.
    ##
    def moveObjects(self, _from, to, count):
        # It's an error when 'to' lies within the moving range of objects
        # Nothing to be done when 'to' is the start or the end of the range, or
        # when the number of objects to be moved is 0.
        if (to == _from or to == _from + count or count == 0):
            return
        movingObjects = self.mObjects[_from:_from + count]
        self.mObjects.erase(_from, _from + count)
        if (to > _from):
            to -= count
        for i in range(count):
            self.mObjects.insert(to + i, movingObjects[i])

    ##
    # Returns the bounding rect around all objects in this object group.
    ##
    def objectsBoundingRect(self):
        boundingRect = QRectF()
        for object in self.mObjects:
            boundingRect = boundingRect.united(object.bounds())
        return boundingRect

    ##
    # Returns whether this object group contains any objects.
    ##
    def isEmpty(self):
        return self.mObjects.isEmpty()

    ##
    # Computes and returns the set of tilesets used by this object group.
    ##
    def usedTilesets(self):
        tilesets = QSet()
        for object in self.mObjects:
            tile = object.cell().tile
            if tile:
                tilesets.insert(tile.sharedTileset())
        return tilesets

    ##
    # Returns whether any tile objects in this object group reference tiles
    # in the given tileset.
    ##
    def referencesTileset(self, tileset):
        for object in self.mObjects:
            tile = object.cell().tile
            if (tile and tile.tileset() == tileset):
                return True

        return False

    ##
    # Replaces all references to tiles from \a oldTileset with tiles from
    # \a newTileset.
    ##
    def replaceReferencesToTileset(self, oldTileset, newTileset):
        for object in self.mObjects:
            tile = object.cell().tile
            if (tile and tile.tileset() == oldTileset):
                cell = object.cell()
                cell.tile = Tileset.tileAt(tile.id())
                object.setCell(cell)

    ##
    # Offsets all objects within the group by the \a offset given in pixel
    # coordinates, and optionally wraps them. The object's center must be
    # within \a bounds, and wrapping occurs if the displaced center is out of
    # the bounds.
    #
    # \sa TileLayer.offset()
    ##
    def offsetObjects(self, offset, bounds, wrapX, wrapY):
        for object in self.mObjects:
            objectCenter = object.bounds().center()
            if (not bounds.contains(objectCenter)):
                continue
            newCenter = QPointF(objectCenter + offset)
            if (wrapX and bounds.width() > 0):
                nx = math.fmod(newCenter.x() - bounds.left(), bounds.width())
                if nx < 0:
                    x = bounds.width() + nx
                else:
                    x = nx
                newCenter.setX(bounds.left() + x)

            if (wrapY and bounds.height() > 0):
                ny = math.fmod(newCenter.y() - bounds.top(), bounds.height())
                if ny < 0:
                    x = bounds.height() + ny
                else:
                    x = ny
                newCenter.setY(bounds.top() + x)

            object.setPosition(object.position() + (newCenter - objectCenter))

    def canMergeWith(self, other):
        return other.isObjectGroup()

    def mergedWith(self, other):
        og = other
        merged = self.clone()
        for mapObject in og.objects():
            merged.addObject(mapObject.clone())
        return merged

    ##
    # Returns the color of the object group, or an invalid color if no color
    # is set.
    ##
    def color(self):
        return self.mColor

    ##
    # Sets the display color of the object group.
    ##
    def setColor(self, color):
        if type(color) != QColor:
            color = QColor(color)
        self.mColor = color

    ##
    # Returns the draw order for the objects in this group.
    #
    # \sa ObjectGroup.DrawOrder
    ##
    def drawOrder(self):
        return self.mDrawOrder

    ##
    # Sets the draw order for the objects in this group.
    #
    # \sa ObjectGroup.DrawOrder
    ##
    def setDrawOrder(self, drawOrder):
        self.mDrawOrder = drawOrder

    ##
    # Returns a duplicate of this ObjectGroup.
    #
    # \sa Layer.clone()
    ##
    def clone(self):
        return self.initializeClone(
            ObjectGroup(self.mName, self.mX, self.mY, self.mWidth,
                        self.mHeight))

    def initializeClone(self, clone):
        super().initializeClone(clone)
        for object in self.mObjects:
            clone.addObject(object.clone())
        clone.setColor(self.mColor)
        clone.setDrawOrder(self.mDrawOrder)
        return clone