Esempio n. 1
0
class CellRenderer():
    BottomLeft, BottomCenter, TopLeft = range(3)

    def __init__(self, painter):
        self.mPainter = painter
        self.mTile = None
        self.mIsOpenGL = hasOpenGLEngine(painter)

        self.mFragments = QVector()

    def __del__(self):
        self.flush()

    ##
    # Renders a \a cell with the given \a origin at \a pos, taking into account
    # the flipping and tile offset.
    #
    # For performance reasons, the actual drawing is delayed until a different
    # kind of tile has to be drawn. For this reason it is necessary to call
    # flush when finished doing drawCell calls. This function is also called by
    # the destructor so usually an explicit call is not needed.
    ##
    def render(self, cell, pos, cellSize, origin):
        if (self.mTile != cell.tile):
            self.flush()
        image = cell.tile.currentFrameImage()
        size = image.size()
        if cellSize == QSizeF(0, 0):
            objectSize = size
        else:
            objectSize = cellSize
        scale = QSizeF(objectSize.width() / size.width(),
                       objectSize.height() / size.height())
        offset = cell.tile.offset()
        sizeHalf = QPointF(objectSize.width() / 2, objectSize.height() / 2)
        fragment = QPainter.PixmapFragment()
        fragment.x = pos.x() + (offset.x() * scale.width()) + sizeHalf.x()
        fragment.y = pos.y() + (
            offset.y() * scale.height()) + sizeHalf.y() - objectSize.height()
        fragment.sourceLeft = 0
        fragment.sourceTop = 0
        fragment.width = size.width()
        fragment.height = size.height()

        if cell.flippedHorizontally:
            fragment.scaleX = -1
        else:
            fragment.scaleX = 1
        if cell.flippedVertically:
            fragment.scaleY = -1
        else:
            fragment.scaleY = 1

        fragment.rotation = 0
        fragment.opacity = 1
        flippedHorizontally = cell.flippedHorizontally
        flippedVertically = cell.flippedVertically
        if (origin == CellRenderer.BottomCenter):
            fragment.x -= sizeHalf.x()
        if (cell.flippedAntiDiagonally):
            fragment.rotation = 90
            flippedHorizontally = cell.flippedVertically
            flippedVertically = not cell.flippedHorizontally
            # Compensate for the swap of image dimensions
            halfDiff = sizeHalf.y() - sizeHalf.x()
            fragment.y += halfDiff
            if (origin != CellRenderer.BottomCenter):
                fragment.x += halfDiff

        if flippedHorizontally:
            x = -1
        else:
            x = 1
        fragment.scaleX = scale.width() * x

        if flippedVertically:
            x = -1
        else:
            x = 1
        fragment.scaleY = scale.height() * x
        if (self.mIsOpenGL or (fragment.scaleX > 0 and fragment.scaleY > 0)):
            self.mTile = cell.tile
            self.mFragments.append(fragment)
            return

        # The Raster paint engine as of Qt 4.8.4 / 5.0.2 does not support
        # drawing fragments with a negative scaling factor.
        self.flush()  # make sure we drew all tiles so far
        oldTransform = self.mPainter.transform()
        transform = oldTransform
        transform.translate(fragment.x, fragment.y)
        transform.rotate(fragment.rotation)
        transform.scale(fragment.scaleX, fragment.scaleY)
        target = QRectF(fragment.width * -0.5, fragment.height * -0.5,
                        fragment.width, fragment.height)
        source = QRectF(0, 0, fragment.width, fragment.height)
        self.mPainter.setTransform(transform)
        self.mPainter.drawPixmap(target, image, source)
        self.mPainter.setTransform(oldTransform)

    def flush(self):
        if (not self.mTile):
            return
        self.mPainter.drawPixmapFragments(self.mFragments,
                                          self.mTile.currentFrameImage())
        self.mTile = None
        self.mFragments.resize(0)
Esempio n. 2
0
class CellRenderer():
    BottomLeft, BottomCenter, TopLeft = range(3)

    def __init__(self, painter):
        self.mPainter = painter
        self.mTile = None
        self.mIsOpenGL = hasOpenGLEngine(painter)

        self.mFragments = QVector()

    def __del__(self):
        self.flush()

    ##
    # Renders a \a cell with the given \a origin at \a pos, taking into account
    # the flipping and tile offset.
    #
    # For performance reasons, the actual drawing is delayed until a different
    # kind of tile has to be drawn. For this reason it is necessary to call
    # flush when finished doing drawCell calls. This function is also called by
    # the destructor so usually an explicit call is not needed.
    ##
    def render(self, cell, pos, cellSize, origin):
        if (self.mTile != cell.tile):
            self.flush()
        image = cell.tile.currentFrameImage()
        size = image.size()
        if cellSize == QSizeF(0,0):
            objectSize = size
        else:
            objectSize = cellSize
        scale = QSizeF(objectSize.width() / size.width(), objectSize.height() / size.height())
        offset = cell.tile.offset()
        sizeHalf = QPointF(objectSize.width() / 2, objectSize.height() / 2)
        fragment = QPainter.PixmapFragment()
        fragment.x = pos.x() + (offset.x() * scale.width()) + sizeHalf.x()
        fragment.y = pos.y() + (offset.y() * scale.height()) + sizeHalf.y() - objectSize.height()
        fragment.sourceLeft = 0
        fragment.sourceTop = 0
        fragment.width = size.width()
        fragment.height = size.height()
        
        if cell.flippedHorizontally:
            fragment.scaleX = -1
        else:
            fragment.scaleX = 1
        if cell.flippedVertically:
            fragment.scaleY = -1
        else:
            fragment.scaleY = 1

        fragment.rotation = 0
        fragment.opacity = 1
        flippedHorizontally = cell.flippedHorizontally
        flippedVertically = cell.flippedVertically
        if (origin == CellRenderer.BottomCenter):
            fragment.x -= sizeHalf.x()
        if (cell.flippedAntiDiagonally):
            fragment.rotation = 90
            flippedHorizontally = cell.flippedVertically
            flippedVertically = not cell.flippedHorizontally
            # Compensate for the swap of image dimensions
            halfDiff = sizeHalf.y() - sizeHalf.x()
            fragment.y += halfDiff
            if (origin != CellRenderer.BottomCenter):
                fragment.x += halfDiff

        if flippedHorizontally:
            x = -1
        else:
            x = 1
        fragment.scaleX = scale.width() * x

        if flippedVertically:
            x = -1
        else:
            x = 1
        fragment.scaleY = scale.height() * x
        if (self.mIsOpenGL or (fragment.scaleX > 0 and fragment.scaleY > 0)):
            self.mTile = cell.tile
            self.mFragments.append(fragment)
            return

        # The Raster paint engine as of Qt 4.8.4 / 5.0.2 does not support
        # drawing fragments with a negative scaling factor.
        self.flush() # make sure we drew all tiles so far
        oldTransform = self.mPainter.transform()
        transform = oldTransform
        transform.translate(fragment.x, fragment.y)
        transform.rotate(fragment.rotation)
        transform.scale(fragment.scaleX, fragment.scaleY)
        target = QRectF(fragment.width * -0.5, fragment.height * -0.5, fragment.width, fragment.height)
        source = QRectF(0, 0, fragment.width, fragment.height)
        self.mPainter.setTransform(transform)
        self.mPainter.drawPixmap(target, image, source)
        self.mPainter.setTransform(oldTransform)

    def flush(self):
        if (not self.mTile):
            return
        self.mPainter.drawPixmapFragments(self.mFragments, self.mTile.currentFrameImage())
        self.mTile = None
        self.mFragments.resize(0)
Esempio n. 3
0
class MapScene(QGraphicsScene):
    selectedObjectItemsChanged = pyqtSignal()

    ##
    # Constructor.
    ##
    def __init__(self, parent):
        super().__init__(parent)
        self.mMapDocument = None
        self.mSelectedTool = None
        self.mActiveTool = None
        self.mObjectSelectionItem = None
        self.mUnderMouse = False
        self.mCurrentModifiers = Qt.NoModifier,
        self.mDarkRectangle = QGraphicsRectItem()
        self.mDefaultBackgroundColor = Qt.darkGray

        self.mLayerItems = QVector()
        self.mObjectItems = QMap()
        self.mObjectLineWidth = 0.0
        self.mSelectedObjectItems = QSet()
        self.mLastMousePos = QPointF()
        self.mShowTileObjectOutlines = False
        self.mHighlightCurrentLayer = False
        self.mGridVisible = False

        self.setBackgroundBrush(self.mDefaultBackgroundColor)
        tilesetManager = TilesetManager.instance()
        tilesetManager.tilesetChanged.connect(self.tilesetChanged)
        tilesetManager.repaintTileset.connect(self.tilesetChanged)
        prefs = preferences.Preferences.instance()
        prefs.showGridChanged.connect(self.setGridVisible)
        prefs.showTileObjectOutlinesChanged.connect(self.setShowTileObjectOutlines)
        prefs.objectTypesChanged.connect(self.syncAllObjectItems)
        prefs.highlightCurrentLayerChanged.connect(self.setHighlightCurrentLayer)
        prefs.gridColorChanged.connect(self.update)
        prefs.objectLineWidthChanged.connect(self.setObjectLineWidth)
        self.mDarkRectangle.setPen(QPen(Qt.NoPen))
        self.mDarkRectangle.setBrush(Qt.black)
        self.mDarkRectangle.setOpacity(darkeningFactor)
        self.addItem(self.mDarkRectangle)
        self.mGridVisible = prefs.showGrid()
        self.mObjectLineWidth = prefs.objectLineWidth()
        self.mShowTileObjectOutlines = prefs.showTileObjectOutlines()
        self.mHighlightCurrentLayer = prefs.highlightCurrentLayer()
        # Install an event filter so that we can get key events on behalf of the
        # active tool without having to have the current focus.
        QCoreApplication.instance().installEventFilter(self)

    ##
    # Destructor.
    ##
    def __del__(self):
        if QCoreApplication.instance():
            QCoreApplication.instance().removeEventFilter(self)

    ##
    # Returns the map document this scene is displaying.
    ##
    def mapDocument(self):
        return self.mMapDocument

    ##
    # Sets the map this scene displays.
    ##
    def setMapDocument(self, mapDocument):
        if (self.mMapDocument):
            self.mMapDocument.disconnect()
            if (not self.mSelectedObjectItems.isEmpty()):
                self.mSelectedObjectItems.clear()
                self.selectedObjectItemsChanged.emit()

        self.mMapDocument = mapDocument
        if (self.mMapDocument):
            renderer = self.mMapDocument.renderer()
            renderer.setObjectLineWidth(self.mObjectLineWidth)
            renderer.setFlag(RenderFlag.ShowTileObjectOutlines, self.mShowTileObjectOutlines)
            self.mMapDocument.mapChanged.connect(self.mapChanged)
            self.mMapDocument.regionChanged.connect(self.repaintRegion)
            self.mMapDocument.tileLayerDrawMarginsChanged.connect(self.tileLayerDrawMarginsChanged)
            self.mMapDocument.layerAdded.connect(self.layerAdded)
            self.mMapDocument.layerRemoved.connect(self.layerRemoved)
            self.mMapDocument.layerChanged.connect(self.layerChanged)
            self.mMapDocument.objectGroupChanged.connect(self.objectGroupChanged)
            self.mMapDocument.imageLayerChanged.connect(self.imageLayerChanged)
            self.mMapDocument.currentLayerIndexChanged.connect(self.currentLayerIndexChanged)
            self.mMapDocument.tilesetTileOffsetChanged.connect(self.tilesetTileOffsetChanged)
            self.mMapDocument.objectsInserted.connect(self.objectsInserted)
            self.mMapDocument.objectsRemoved.connect(self.objectsRemoved)
            self.mMapDocument.objectsChanged.connect(self.objectsChanged)
            self.mMapDocument.objectsIndexChanged.connect(self.objectsIndexChanged)
            self.mMapDocument.selectedObjectsChanged.connect(self.updateSelectedObjectItems)

        self.refreshScene()

    ##
    # Returns whether the tile grid is visible.
    ##
    def isGridVisible(self):
        return self.mGridVisible

    ##
    # Returns the set of selected map object items.
    ##
    def selectedObjectItems(self):
        return QSet(self.mSelectedObjectItems)

    ##
    # Sets the set of selected map object items. This translates to a call to
    # MapDocument.setSelectedObjects.
    ##
    def setSelectedObjectItems(self, items):
        # Inform the map document about the newly selected objects
        selectedObjects = QList()
        #selectedObjects.reserve(items.size())
        for item in items:
            selectedObjects.append(item.mapObject())
        self.mMapDocument.setSelectedObjects(selectedObjects)

    ##
    # Returns the MapObjectItem associated with the given \a mapObject.
    ##
    def itemForObject(self, object):
        return self.mObjectItems[object]

    ##
    # Enables the selected tool at this map scene.
    # Therefore it tells that tool, that this is the active map scene.
    ##
    def enableSelectedTool(self):
        if (not self.mSelectedTool or not self.mMapDocument):
            return
        self.mActiveTool = self.mSelectedTool
        self.mActiveTool.activate(self)
        self.mCurrentModifiers = QApplication.keyboardModifiers()
        if (self.mCurrentModifiers != Qt.NoModifier):
            self.mActiveTool.modifiersChanged(self.mCurrentModifiers)
        if (self.mUnderMouse):
            self.mActiveTool.mouseEntered()
            self.mActiveTool.mouseMoved(self.mLastMousePos, Qt.KeyboardModifiers())

    def disableSelectedTool(self):
        if (not self.mActiveTool):
            return
        if (self.mUnderMouse):
            self.mActiveTool.mouseLeft()
        self.mActiveTool.deactivate(self)
        self.mActiveTool = None

    ##
    # Sets the currently selected tool.
    ##
    def setSelectedTool(self, tool):
        self.mSelectedTool = tool

    ##
    # QGraphicsScene.drawForeground override that draws the tile grid.
    ##
    def drawForeground(self, painter, rect):
        if (not self.mMapDocument or not self.mGridVisible):
            return
            
        offset = QPointF()

        # Take into account the offset of the current layer
        layer = self.mMapDocument.currentLayer()
        if layer:
            offset = layer.offset()
            painter.translate(offset)
        
        prefs = preferences.Preferences.instance()
        self.mMapDocument.renderer().drawGrid(painter, rect.translated(-offset), prefs.gridColor())

    ##
    # Override for handling enter and leave events.
    ##
    def event(self, event):
        x = event.type()
        if x==QEvent.Enter:
            self.mUnderMouse = True
            if (self.mActiveTool):
                self.mActiveTool.mouseEntered()
        elif x==QEvent.Leave:
            self.mUnderMouse = False
            if (self.mActiveTool):
                self.mActiveTool.mouseLeft()
        else:
            pass

        return super().event(event)

    def keyPressEvent(self, event):
        if (self.mActiveTool):
            self.mActiveTool.keyPressed(event)
        if (not (self.mActiveTool and event.isAccepted())):
            super().keyPressEvent(event)

    def mouseMoveEvent(self, mouseEvent):
        self.mLastMousePos = mouseEvent.scenePos()
        if (not self.mMapDocument):
            return
        super().mouseMoveEvent(mouseEvent)
        if (mouseEvent.isAccepted()):
            return
        if (self.mActiveTool):
            self.mActiveTool.mouseMoved(mouseEvent.scenePos(), mouseEvent.modifiers())
            mouseEvent.accept()

    def mousePressEvent(self, mouseEvent):
        super().mousePressEvent(mouseEvent)
        if (mouseEvent.isAccepted()):
            return
        if (self.mActiveTool):
            mouseEvent.accept()
            self.mActiveTool.mousePressed(mouseEvent)

    def mouseReleaseEvent(self, mouseEvent):
        super().mouseReleaseEvent(mouseEvent)
        if (mouseEvent.isAccepted()):
            return
        if (self.mActiveTool):
            mouseEvent.accept()
            self.mActiveTool.mouseReleased(mouseEvent)

    ##
    # Override to ignore drag enter events.
    ##
    def dragEnterEvent(self, event):
        event.ignore()

    ##
    # Sets whether the tile grid is visible.
    ##
    def setGridVisible(self, visible):
        if (self.mGridVisible == visible):
            return
        self.mGridVisible = visible
        self.update()

    def setObjectLineWidth(self, lineWidth):
        if (self.mObjectLineWidth == lineWidth):
            return
        self.mObjectLineWidth = lineWidth
        if (self.mMapDocument):
            self.mMapDocument.renderer().setObjectLineWidth(lineWidth)
            # Changing the line width can change the size of the object items
            if (not self.mObjectItems.isEmpty()):
                for item in self.mObjectItems:
                    item[1].syncWithMapObject()
                self.update()

    def setShowTileObjectOutlines(self, enabled):
        if (self.mShowTileObjectOutlines == enabled):
            return
        self.mShowTileObjectOutlines = enabled
        if (self.mMapDocument):
            self.mMapDocument.renderer().setFlag(RenderFlag.ShowTileObjectOutlines, enabled)
            if (not self.mObjectItems.isEmpty()):
                self.update()

    ##
    # Sets whether the current layer should be highlighted.
    ##
    def setHighlightCurrentLayer(self, highlightCurrentLayer):
        if (self.mHighlightCurrentLayer == highlightCurrentLayer):
            return
        self.mHighlightCurrentLayer = highlightCurrentLayer
        self.updateCurrentLayerHighlight()

    ##
    # Refreshes the map scene.
    ##
    def refreshScene(self):
        self.mLayerItems.clear()
        self.mObjectItems.clear()
        self.removeItem(self.mDarkRectangle)
        self.clear()
        self.addItem(self.mDarkRectangle)
        if (not self.mMapDocument):
            self.setSceneRect(QRectF())
            return

        self.updateSceneRect()
        
        map = self.mMapDocument.map()
        self.mLayerItems.resize(map.layerCount())
        if (map.backgroundColor().isValid()):
            self.setBackgroundBrush(map.backgroundColor())
        else:
            self.setBackgroundBrush(self.mDefaultBackgroundColor)
        layerIndex = 0
        for layer in map.layers():
            layerItem = self.createLayerItem(layer)
            layerItem.setZValue(layerIndex)
            self.addItem(layerItem)
            self.mLayerItems[layerIndex] = layerItem
            layerIndex += 1

        tileSelectionItem = TileSelectionItem(self.mMapDocument)
        tileSelectionItem.setZValue(10000 - 2)
        self.addItem(tileSelectionItem)
        self.mObjectSelectionItem = ObjectSelectionItem(self.mMapDocument)
        self.mObjectSelectionItem.setZValue(10000 - 1)
        self.addItem(self.mObjectSelectionItem)
        self.updateCurrentLayerHighlight()

    ##
    # Repaints the specified region. The region is in tile coordinates.
    ##
    def repaintRegion(self, region, layer):
        renderer = self.mMapDocument.renderer()
        margins = self.mMapDocument.map().drawMargins()
        for r in region.rects():
            boundingRect = QRectF(renderer.boundingRect(r))
            self.update(QRectF(renderer.boundingRect(r).adjusted(-margins.left(),
                                                      -margins.top(),
                                                      margins.right(),
                                                      margins.bottom())))
            boundingRect.translate(layer.offset())
            self.update(boundingRect)

    def currentLayerIndexChanged(self):
        self.updateCurrentLayerHighlight()
        # New layer may have a different offset, affecting the grid
        if self.mGridVisible:
            self.update()
        
    ##
    # Adapts the scene, layers and objects to new map size, orientation or
    # background color.
    ##
    def mapChanged(self):
        self.updateSceneRect()
        for item in self.mLayerItems:
            tli = item
            if type(tli) == TileLayerItem:
                tli.syncWithTileLayer()

        for item in self.mObjectItems.values():
            item.syncWithMapObject()
        map = self.mMapDocument.map()
        if (map.backgroundColor().isValid()):
            self.setBackgroundBrush(map.backgroundColor())
        else:
            self.setBackgroundBrush(self.mDefaultBackgroundColor)

    def tilesetChanged(self, tileset):
        if (not self.mMapDocument):
            return
        if (contains(self.mMapDocument.map().tilesets(), tileset)):
            self.update()

    def tileLayerDrawMarginsChanged(self, tileLayer):
        index = self.mMapDocument.map().layers().indexOf(tileLayer)
        item = self.mLayerItems.at(index)
        item.syncWithTileLayer()

    def layerAdded(self, index):
        layer = self.mMapDocument.map().layerAt(index)
        layerItem = self.createLayerItem(layer)
        self.addItem(layerItem)
        self.mLayerItems.insert(index, layerItem)
        z = 0
        for item in self.mLayerItems:
            item.setZValue(z)
            z += 1

    def layerRemoved(self, index):
        self.mLayerItems.remove(index)

    ##
    # A layer has changed. This can mean that the layer visibility, opacity or
    # offset changed.
    ##
    def layerChanged(self, index):
        layer = self.mMapDocument.map().layerAt(index)
        layerItem = self.mLayerItems.at(index)
        layerItem.setVisible(layer.isVisible())
        multiplier = 1
        if (self.mHighlightCurrentLayer and self.mMapDocument.currentLayerIndex() < index):
            multiplier = opacityFactor
        layerItem.setOpacity(layer.opacity() * multiplier)
        layerItem.setPos(layer.offset())

        # Layer offset may have changed, affecting the scene rect and grid
        self.updateSceneRect()
        if self.mGridVisible:
            self.update()

    ##
    # When an object group has changed it may mean its color or drawing order
    # changed, which affects all its objects.
    ##
    def objectGroupChanged(self, objectGroup):
        self.objectsChanged(objectGroup.objects())
        self.objectsIndexChanged(objectGroup, 0, objectGroup.objectCount() - 1)

    ##
    # When an image layer has changed, it may change size and it may look
    # differently.
    ##
    def imageLayerChanged(self, imageLayer):
        index = self.mMapDocument.map().layers().indexOf(imageLayer)
        item = self.mLayerItems.at(index)
        item.syncWithImageLayer()
        item.update()

    ##
    # When the tile offset of a tileset has changed, it can affect the bounding
    # rect of all tile layers and tile objects. It also requires a full repaint.
    ##
    def tilesetTileOffsetChanged(self, tileset):
        self.update()
        for item in self.mLayerItems:
            tli = item
            if type(tli) == TileLayerItem:
                tli.syncWithTileLayer()
        for item in self.mObjectItems:
            cell = item.mapObject().cell()
            if (not cell.isEmpty() and cell.tile.tileset() == tileset):
                item.syncWithMapObject()

    ##
    # Inserts map object items for the given objects.
    ##
    def objectsInserted(self, objectGroup, first, last):
        ogItem = None
        # Find the object group item for the object group
        for item in self.mLayerItems:
            ogi = item
            if type(ogi)==ObjectGroupItem:
                if (ogi.objectGroup() == objectGroup):
                    ogItem = ogi
                    break

        drawOrder = objectGroup.drawOrder()
        for i in range(first, last+1):
            object = objectGroup.objectAt(i)
            item = MapObjectItem(object, self.mMapDocument, ogItem)
            if (drawOrder == ObjectGroup.DrawOrder.TopDownOrder):
                item.setZValue(item.y())
            else:
                item.setZValue(i)
            self.mObjectItems.insert(object, item)

    ##
    # Removes the map object items related to the given objects.
    ##
    def objectsRemoved(self, objects):
        for o in objects:
            i = self.mObjectItems.find(o)
            self.mSelectedObjectItems.remove(i)
            # python would not force delete QGraphicsItem
            self.removeItem(i)
            self.mObjectItems.erase(o)

    ##
    # Updates the map object items related to the given objects.
    ##
    def objectsChanged(self, objects):
        for object in objects:
            item = self.itemForObject(object)
            item.syncWithMapObject()

    ##
    # Updates the Z value of the objects when appropriate.
    ##
    def objectsIndexChanged(self, objectGroup, first, last):
        if (objectGroup.drawOrder() != ObjectGroup.DrawOrder.IndexOrder):
            return
        for i in range(first, last+1):
            item = self.itemForObject(objectGroup.objectAt(i))
            item.setZValue(i)

    def updateSelectedObjectItems(self):
        objects = self.mMapDocument.selectedObjects()
        items = QSet()
        for object in objects:
            item = self.itemForObject(object)
            if item:
                items.insert(item)

        self.mSelectedObjectItems = items
        self.selectedObjectItemsChanged.emit()

    def syncAllObjectItems(self):
        for item in self.mObjectItems:
            item.syncWithMapObject()

    def createLayerItem(self, layer):
        layerItem = None
        tl = layer.asTileLayer()
        if tl:
            layerItem = TileLayerItem(tl, self.mMapDocument)
        else:
            og = layer.asObjectGroup()
            if og:
                drawOrder = og.drawOrder()
                ogItem = ObjectGroupItem(og)
                objectIndex = 0
                for object in og.objects():
                    item = MapObjectItem(object, self.mMapDocument, ogItem)
                    if (drawOrder == ObjectGroup.DrawOrder.TopDownOrder):
                        item.setZValue(item.y())
                    else:
                        item.setZValue(objectIndex)
                    self.mObjectItems.insert(object, item)
                    objectIndex += 1

                layerItem = ogItem
            else:
                il = layer.asImageLayer()
                if il:
                    layerItem = ImageLayerItem(il, self.mMapDocument)

        layerItem.setVisible(layer.isVisible())
        return layerItem

    def updateSceneRect(self):
        mapSize = self.mMapDocument.renderer().mapSize()
        sceneRect = QRectF(0, 0, mapSize.width(), mapSize.height())

        margins = self.mMapDocument.map().computeLayerOffsetMargins()
        sceneRect.adjust(-margins.left(),
                         -margins.top(),
                         margins.right(),
                         margins.bottom())

        self.setSceneRect(sceneRect)
        self.mDarkRectangle.setRect(sceneRect)
        
    def updateCurrentLayerHighlight(self):
        if (not self.mMapDocument):
            return
        currentLayerIndex = self.mMapDocument.currentLayerIndex()
        if (not self.mHighlightCurrentLayer or currentLayerIndex == -1):
            self.mDarkRectangle.setVisible(False)
            # Restore opacity for all layers
            for i in range(self.mLayerItems.size()):
                layer = self.mMapDocument.map().layerAt(i)
                self.mLayerItems.at(i).setOpacity(layer.opacity())

            return

        # Darken layers below the current layer
        self.mDarkRectangle.setZValue(currentLayerIndex - 0.5)
        self.mDarkRectangle.setVisible(True)
        # Set layers above the current layer to half opacity
        for i in range(1, self.mLayerItems.size()):
            layer = self.mMapDocument.map().layerAt(i)
            if currentLayerIndex < i:
                _x = opacityFactor
            else:
                _x = 1
            multiplier = _x
            self.mLayerItems.at(i).setOpacity(layer.opacity() * multiplier)

    def eventFilter(self, object, event):
        x = event.type()
        if x==QEvent.KeyPress or x==QEvent.KeyRelease:
                keyEvent = event
                newModifiers = keyEvent.modifiers()
                if (self.mActiveTool and newModifiers != self.mCurrentModifiers):
                    self.mActiveTool.modifiersChanged(newModifiers)
                    self.mCurrentModifiers = newModifiers
        else:
            pass

        return False
Esempio n. 4
0
    def drawGrid(self, painter, exposed, gridColor):
        rect = exposed.toAlignedRect()
        if (rect.isNull()):
            return
        p = RenderParams(self.map())
        # Determine the tile and pixel coordinates to start at
        startTile = self.screenToTileCoords_(rect.topLeft()).toPoint()
        startPos = self.tileToScreenCoords_(startTile).toPoint()
        ## Determine in which half of the tile the top-left corner of the area we
        # need to draw is. If we're in the upper half, we need to start one row
        # up due to those tiles being visible as well. How we go up one row
        # depends on whether we're in the left or right half of the tile.
        ##
        inUpperHalf = rect.y() - startPos.y() < p.sideOffsetY
        inLeftHalf = rect.x() - startPos.x() < p.sideOffsetX
        if (inUpperHalf):
            startTile.setY(startTile.y() - 1)
        if (inLeftHalf):
            startTile.setX(startTile.x() - 1)
        startTile.setX(max(0, startTile.x()))
        startTile.setY(max(0, startTile.y()))
        startPos = self.tileToScreenCoords_(startTile).toPoint()
        oct = [
            QPoint(0, p.tileHeight - p.sideOffsetY),
            QPoint(0, p.sideOffsetY),
            QPoint(p.sideOffsetX, 0),
            QPoint(p.tileWidth - p.sideOffsetX, 0),
            QPoint(p.tileWidth, p.sideOffsetY),
            QPoint(p.tileWidth, p.tileHeight - p.sideOffsetY),
            QPoint(p.tileWidth - p.sideOffsetX, p.tileHeight),
            QPoint(p.sideOffsetX, p.tileHeight)]

        lines = QVector()
        #lines.reserve(8)
        gridColor.setAlpha(128)
        gridPen = QPen(gridColor)
        gridPen.setCosmetic(True)
        _x = QVector()
        _x.append(2)
        _x.append(2)
        gridPen.setDashPattern(_x)
        painter.setPen(gridPen)
        if (p.staggerX):
            # Odd row shifting is applied in the rendering loop, so un-apply it here
            if (p.doStaggerX(startTile.x())):
                startPos.setY(startPos.y() - p.rowHeight)
            while(startPos.x() <= rect.right() and startTile.x() < self.map().width()):
                rowTile = QPoint(startTile)
                rowPos = QPoint(startPos)
                if (p.doStaggerX(startTile.x())):
                    rowPos.setY(rowPos.y() + p.rowHeight)
                while(rowPos.y() <= rect.bottom() and rowTile.y() < self.map().height()):
                    lines.append(QLineF(rowPos + oct[1], rowPos + oct[2]))
                    lines.append(QLineF(rowPos + oct[2], rowPos + oct[3]))
                    lines.append(QLineF(rowPos + oct[3], rowPos + oct[4]))
                    isStaggered = p.doStaggerX(startTile.x())
                    lastRow = rowTile.y() == self.map().height() - 1
                    lastColumn = rowTile.x() == self.map().width() - 1
                    bottomLeft = rowTile.x() == 0 or (lastRow and isStaggered)
                    bottomRight = lastColumn or (lastRow and isStaggered)
                    if (bottomRight):
                        lines.append(QLineF(rowPos + oct[5], rowPos + oct[6]))
                    if (lastRow):
                        lines.append(QLineF(rowPos + oct[6], rowPos + oct[7]))
                    if (bottomLeft):
                        lines.append(QLineF(rowPos + oct[7], rowPos + oct[0]))
                    painter.drawLines(lines)
                    lines.resize(0)
                    rowPos.setY(rowPos.y() + p.tileHeight + p.sideLengthY)
                    rowTile.setY(rowTile.y() + 1)

                startPos.setX(startPos.x() + p.columnWidth)
                startTile.setX(startTile.x() + 1)
        else:
            # Odd row shifting is applied in the rendering loop, so un-apply it here
            if (p.doStaggerY(startTile.y())):
                startPos.setX(startPos.x() - p.columnWidth)
            while(startPos.y() <= rect.bottom() and startTile.y() < self.map().height()):
                rowTile = QPoint(startTile)
                rowPos = QPoint(startPos)
                if (p.doStaggerY(startTile.y())):
                    rowPos.setX(rowPos.x() + p.columnWidth)
                while(rowPos.x() <= rect.right() and rowTile.x() < self.map().width()):
                    lines.append(QLineF(rowPos + oct[0], rowPos + oct[1]))
                    lines.append(QLineF(rowPos + oct[1], rowPos + oct[2]))
                    lines.append(QLineF(rowPos + oct[3], rowPos + oct[4]))
                    isStaggered = p.doStaggerY(startTile.y())
                    lastRow = rowTile.y() == self.map().height() - 1
                    lastColumn = rowTile.x() == self.map().width() - 1
                    bottomLeft = lastRow or (rowTile.x() == 0 and not isStaggered)
                    bottomRight = lastRow or (lastColumn and isStaggered)
                    if (lastColumn):
                        lines.append(QLineF(rowPos + oct[4], rowPos + oct[5]))
                    if (bottomRight):
                        lines.append(QLineF(rowPos + oct[5], rowPos + oct[6]))
                    if (bottomLeft):
                        lines.append(QLineF(rowPos + oct[7], rowPos + oct[0]))
                    painter.drawLines(lines)
                    lines.resize(0)
                    rowPos.setX(rowPos.x() + p.tileWidth + p.sideLengthX)
                    rowTile.setX(rowTile.x() + 1)

                startPos.setY(startPos.y() + p.rowHeight)
                startTile.setY(startTile.y() + 1)
Esempio n. 5
0
    def drawGrid(self, painter, exposed, gridColor):
        rect = exposed.toAlignedRect()
        if (rect.isNull()):
            return
        p = RenderParams(self.map())
        # Determine the tile and pixel coordinates to start at
        startTile = self.screenToTileCoords_(rect.topLeft()).toPoint()
        startPos = self.tileToScreenCoords_(startTile).toPoint()
        ## Determine in which half of the tile the top-left corner of the area we
        # need to draw is. If we're in the upper half, we need to start one row
        # up due to those tiles being visible as well. How we go up one row
        # depends on whether we're in the left or right half of the tile.
        ##
        inUpperHalf = rect.y() - startPos.y() < p.sideOffsetY
        inLeftHalf = rect.x() - startPos.x() < p.sideOffsetX
        if (inUpperHalf):
            startTile.setY(startTile.y() - 1)
        if (inLeftHalf):
            startTile.setX(startTile.x() - 1)
        startTile.setX(max(0, startTile.x()))
        startTile.setY(max(0, startTile.y()))
        startPos = self.tileToScreenCoords_(startTile).toPoint()
        oct = [
            QPoint(0, p.tileHeight - p.sideOffsetY),
            QPoint(0, p.sideOffsetY),
            QPoint(p.sideOffsetX, 0),
            QPoint(p.tileWidth - p.sideOffsetX, 0),
            QPoint(p.tileWidth, p.sideOffsetY),
            QPoint(p.tileWidth, p.tileHeight - p.sideOffsetY),
            QPoint(p.tileWidth - p.sideOffsetX, p.tileHeight),
            QPoint(p.sideOffsetX, p.tileHeight)
        ]

        lines = QVector()
        #lines.reserve(8)
        gridColor.setAlpha(128)
        gridPen = QPen(gridColor)
        gridPen.setCosmetic(True)
        _x = QVector()
        _x.append(2)
        _x.append(2)
        gridPen.setDashPattern(_x)
        painter.setPen(gridPen)
        if (p.staggerX):
            # Odd row shifting is applied in the rendering loop, so un-apply it here
            if (p.doStaggerX(startTile.x())):
                startPos.setY(startPos.y() - p.rowHeight)
            while (startPos.x() <= rect.right()
                   and startTile.x() < self.map().width()):
                rowTile = QPoint(startTile)
                rowPos = QPoint(startPos)
                if (p.doStaggerX(startTile.x())):
                    rowPos.setY(rowPos.y() + p.rowHeight)
                while (rowPos.y() <= rect.bottom()
                       and rowTile.y() < self.map().height()):
                    lines.append(QLineF(rowPos + oct[1], rowPos + oct[2]))
                    lines.append(QLineF(rowPos + oct[2], rowPos + oct[3]))
                    lines.append(QLineF(rowPos + oct[3], rowPos + oct[4]))
                    isStaggered = p.doStaggerX(startTile.x())
                    lastRow = rowTile.y() == self.map().height() - 1
                    lastColumn = rowTile.x() == self.map().width() - 1
                    bottomLeft = rowTile.x() == 0 or (lastRow and isStaggered)
                    bottomRight = lastColumn or (lastRow and isStaggered)
                    if (bottomRight):
                        lines.append(QLineF(rowPos + oct[5], rowPos + oct[6]))
                    if (lastRow):
                        lines.append(QLineF(rowPos + oct[6], rowPos + oct[7]))
                    if (bottomLeft):
                        lines.append(QLineF(rowPos + oct[7], rowPos + oct[0]))
                    painter.drawLines(lines)
                    lines.resize(0)
                    rowPos.setY(rowPos.y() + p.tileHeight + p.sideLengthY)
                    rowTile.setY(rowTile.y() + 1)

                startPos.setX(startPos.x() + p.columnWidth)
                startTile.setX(startTile.x() + 1)
        else:
            # Odd row shifting is applied in the rendering loop, so un-apply it here
            if (p.doStaggerY(startTile.y())):
                startPos.setX(startPos.x() - p.columnWidth)
            while (startPos.y() <= rect.bottom()
                   and startTile.y() < self.map().height()):
                rowTile = QPoint(startTile)
                rowPos = QPoint(startPos)
                if (p.doStaggerY(startTile.y())):
                    rowPos.setX(rowPos.x() + p.columnWidth)
                while (rowPos.x() <= rect.right()
                       and rowTile.x() < self.map().width()):
                    lines.append(QLineF(rowPos + oct[0], rowPos + oct[1]))
                    lines.append(QLineF(rowPos + oct[1], rowPos + oct[2]))
                    lines.append(QLineF(rowPos + oct[3], rowPos + oct[4]))
                    isStaggered = p.doStaggerY(startTile.y())
                    lastRow = rowTile.y() == self.map().height() - 1
                    lastColumn = rowTile.x() == self.map().width() - 1
                    bottomLeft = lastRow or (rowTile.x() == 0
                                             and not isStaggered)
                    bottomRight = lastRow or (lastColumn and isStaggered)
                    if (lastColumn):
                        lines.append(QLineF(rowPos + oct[4], rowPos + oct[5]))
                    if (bottomRight):
                        lines.append(QLineF(rowPos + oct[5], rowPos + oct[6]))
                    if (bottomLeft):
                        lines.append(QLineF(rowPos + oct[7], rowPos + oct[0]))
                    painter.drawLines(lines)
                    lines.resize(0)
                    rowPos.setX(rowPos.x() + p.tileWidth + p.sideLengthX)
                    rowTile.setX(rowTile.x() + 1)

                startPos.setY(startPos.y() + p.rowHeight)
                startTile.setY(startTile.y() + 1)
Esempio n. 6
0
class MapScene(QGraphicsScene):
    selectedObjectItemsChanged = pyqtSignal()

    ##
    # Constructor.
    ##
    def __init__(self, parent):
        super().__init__(parent)
        self.mMapDocument = None
        self.mSelectedTool = None
        self.mActiveTool = None
        self.mObjectSelectionItem = None
        self.mUnderMouse = False
        self.mCurrentModifiers = Qt.NoModifier,
        self.mDarkRectangle = QGraphicsRectItem()
        self.mDefaultBackgroundColor = Qt.darkGray

        self.mLayerItems = QVector()
        self.mObjectItems = QMap()
        self.mObjectLineWidth = 0.0
        self.mSelectedObjectItems = QSet()
        self.mLastMousePos = QPointF()
        self.mShowTileObjectOutlines = False
        self.mHighlightCurrentLayer = False
        self.mGridVisible = False

        self.setBackgroundBrush(self.mDefaultBackgroundColor)
        tilesetManager = TilesetManager.instance()
        tilesetManager.tilesetChanged.connect(self.tilesetChanged)
        tilesetManager.repaintTileset.connect(self.tilesetChanged)
        prefs = preferences.Preferences.instance()
        prefs.showGridChanged.connect(self.setGridVisible)
        prefs.showTileObjectOutlinesChanged.connect(
            self.setShowTileObjectOutlines)
        prefs.objectTypesChanged.connect(self.syncAllObjectItems)
        prefs.highlightCurrentLayerChanged.connect(
            self.setHighlightCurrentLayer)
        prefs.gridColorChanged.connect(self.update)
        prefs.objectLineWidthChanged.connect(self.setObjectLineWidth)
        self.mDarkRectangle.setPen(QPen(Qt.NoPen))
        self.mDarkRectangle.setBrush(Qt.black)
        self.mDarkRectangle.setOpacity(darkeningFactor)
        self.addItem(self.mDarkRectangle)
        self.mGridVisible = prefs.showGrid()
        self.mObjectLineWidth = prefs.objectLineWidth()
        self.mShowTileObjectOutlines = prefs.showTileObjectOutlines()
        self.mHighlightCurrentLayer = prefs.highlightCurrentLayer()
        # Install an event filter so that we can get key events on behalf of the
        # active tool without having to have the current focus.
        QCoreApplication.instance().installEventFilter(self)

    ##
    # Destructor.
    ##
    def __del__(self):
        if QCoreApplication.instance():
            QCoreApplication.instance().removeEventFilter(self)

    ##
    # Returns the map document this scene is displaying.
    ##
    def mapDocument(self):
        return self.mMapDocument

    ##
    # Sets the map this scene displays.
    ##
    def setMapDocument(self, mapDocument):
        if (self.mMapDocument):
            self.mMapDocument.disconnect()
            if (not self.mSelectedObjectItems.isEmpty()):
                self.mSelectedObjectItems.clear()
                self.selectedObjectItemsChanged.emit()

        self.mMapDocument = mapDocument
        if (self.mMapDocument):
            renderer = self.mMapDocument.renderer()
            renderer.setObjectLineWidth(self.mObjectLineWidth)
            renderer.setFlag(RenderFlag.ShowTileObjectOutlines,
                             self.mShowTileObjectOutlines)
            self.mMapDocument.mapChanged.connect(self.mapChanged)
            self.mMapDocument.regionChanged.connect(self.repaintRegion)
            self.mMapDocument.tileLayerDrawMarginsChanged.connect(
                self.tileLayerDrawMarginsChanged)
            self.mMapDocument.layerAdded.connect(self.layerAdded)
            self.mMapDocument.layerRemoved.connect(self.layerRemoved)
            self.mMapDocument.layerChanged.connect(self.layerChanged)
            self.mMapDocument.objectGroupChanged.connect(
                self.objectGroupChanged)
            self.mMapDocument.imageLayerChanged.connect(self.imageLayerChanged)
            self.mMapDocument.currentLayerIndexChanged.connect(
                self.currentLayerIndexChanged)
            self.mMapDocument.tilesetTileOffsetChanged.connect(
                self.tilesetTileOffsetChanged)
            self.mMapDocument.objectsInserted.connect(self.objectsInserted)
            self.mMapDocument.objectsRemoved.connect(self.objectsRemoved)
            self.mMapDocument.objectsChanged.connect(self.objectsChanged)
            self.mMapDocument.objectsIndexChanged.connect(
                self.objectsIndexChanged)
            self.mMapDocument.selectedObjectsChanged.connect(
                self.updateSelectedObjectItems)

        self.refreshScene()

    ##
    # Returns whether the tile grid is visible.
    ##
    def isGridVisible(self):
        return self.mGridVisible

    ##
    # Returns the set of selected map object items.
    ##
    def selectedObjectItems(self):
        return QSet(self.mSelectedObjectItems)

    ##
    # Sets the set of selected map object items. This translates to a call to
    # MapDocument.setSelectedObjects.
    ##
    def setSelectedObjectItems(self, items):
        # Inform the map document about the newly selected objects
        selectedObjects = QList()
        #selectedObjects.reserve(items.size())
        for item in items:
            selectedObjects.append(item.mapObject())
        self.mMapDocument.setSelectedObjects(selectedObjects)

    ##
    # Returns the MapObjectItem associated with the given \a mapObject.
    ##
    def itemForObject(self, object):
        return self.mObjectItems[object]

    ##
    # Enables the selected tool at this map scene.
    # Therefore it tells that tool, that this is the active map scene.
    ##
    def enableSelectedTool(self):
        if (not self.mSelectedTool or not self.mMapDocument):
            return
        self.mActiveTool = self.mSelectedTool
        self.mActiveTool.activate(self)
        self.mCurrentModifiers = QApplication.keyboardModifiers()
        if (self.mCurrentModifiers != Qt.NoModifier):
            self.mActiveTool.modifiersChanged(self.mCurrentModifiers)
        if (self.mUnderMouse):
            self.mActiveTool.mouseEntered()
            self.mActiveTool.mouseMoved(self.mLastMousePos,
                                        Qt.KeyboardModifiers())

    def disableSelectedTool(self):
        if (not self.mActiveTool):
            return
        if (self.mUnderMouse):
            self.mActiveTool.mouseLeft()
        self.mActiveTool.deactivate(self)
        self.mActiveTool = None

    ##
    # Sets the currently selected tool.
    ##
    def setSelectedTool(self, tool):
        self.mSelectedTool = tool

    ##
    # QGraphicsScene.drawForeground override that draws the tile grid.
    ##
    def drawForeground(self, painter, rect):
        if (not self.mMapDocument or not self.mGridVisible):
            return

        offset = QPointF()

        # Take into account the offset of the current layer
        layer = self.mMapDocument.currentLayer()
        if layer:
            offset = layer.offset()
            painter.translate(offset)

        prefs = preferences.Preferences.instance()
        self.mMapDocument.renderer().drawGrid(painter,
                                              rect.translated(-offset),
                                              prefs.gridColor())

    ##
    # Override for handling enter and leave events.
    ##
    def event(self, event):
        x = event.type()
        if x == QEvent.Enter:
            self.mUnderMouse = True
            if (self.mActiveTool):
                self.mActiveTool.mouseEntered()
        elif x == QEvent.Leave:
            self.mUnderMouse = False
            if (self.mActiveTool):
                self.mActiveTool.mouseLeft()
        else:
            pass

        return super().event(event)

    def keyPressEvent(self, event):
        if (self.mActiveTool):
            self.mActiveTool.keyPressed(event)
        if (not (self.mActiveTool and event.isAccepted())):
            super().keyPressEvent(event)

    def mouseMoveEvent(self, mouseEvent):
        self.mLastMousePos = mouseEvent.scenePos()
        if (not self.mMapDocument):
            return
        super().mouseMoveEvent(mouseEvent)
        if (mouseEvent.isAccepted()):
            return
        if (self.mActiveTool):
            self.mActiveTool.mouseMoved(mouseEvent.scenePos(),
                                        mouseEvent.modifiers())
            mouseEvent.accept()

    def mousePressEvent(self, mouseEvent):
        super().mousePressEvent(mouseEvent)
        if (mouseEvent.isAccepted()):
            return
        if (self.mActiveTool):
            mouseEvent.accept()
            self.mActiveTool.mousePressed(mouseEvent)

    def mouseReleaseEvent(self, mouseEvent):
        super().mouseReleaseEvent(mouseEvent)
        if (mouseEvent.isAccepted()):
            return
        if (self.mActiveTool):
            mouseEvent.accept()
            self.mActiveTool.mouseReleased(mouseEvent)

    ##
    # Override to ignore drag enter events.
    ##
    def dragEnterEvent(self, event):
        event.ignore()

    ##
    # Sets whether the tile grid is visible.
    ##
    def setGridVisible(self, visible):
        if (self.mGridVisible == visible):
            return
        self.mGridVisible = visible
        self.update()

    def setObjectLineWidth(self, lineWidth):
        if (self.mObjectLineWidth == lineWidth):
            return
        self.mObjectLineWidth = lineWidth
        if (self.mMapDocument):
            self.mMapDocument.renderer().setObjectLineWidth(lineWidth)
            # Changing the line width can change the size of the object items
            if (not self.mObjectItems.isEmpty()):
                for item in self.mObjectItems:
                    item[1].syncWithMapObject()
                self.update()

    def setShowTileObjectOutlines(self, enabled):
        if (self.mShowTileObjectOutlines == enabled):
            return
        self.mShowTileObjectOutlines = enabled
        if (self.mMapDocument):
            self.mMapDocument.renderer().setFlag(
                RenderFlag.ShowTileObjectOutlines, enabled)
            if (not self.mObjectItems.isEmpty()):
                self.update()

    ##
    # Sets whether the current layer should be highlighted.
    ##
    def setHighlightCurrentLayer(self, highlightCurrentLayer):
        if (self.mHighlightCurrentLayer == highlightCurrentLayer):
            return
        self.mHighlightCurrentLayer = highlightCurrentLayer
        self.updateCurrentLayerHighlight()

    ##
    # Refreshes the map scene.
    ##
    def refreshScene(self):
        self.mLayerItems.clear()
        self.mObjectItems.clear()
        self.removeItem(self.mDarkRectangle)
        self.clear()
        self.addItem(self.mDarkRectangle)
        if (not self.mMapDocument):
            self.setSceneRect(QRectF())
            return

        self.updateSceneRect()

        map = self.mMapDocument.map()
        self.mLayerItems.resize(map.layerCount())
        if (map.backgroundColor().isValid()):
            self.setBackgroundBrush(map.backgroundColor())
        else:
            self.setBackgroundBrush(self.mDefaultBackgroundColor)
        layerIndex = 0
        for layer in map.layers():
            layerItem = self.createLayerItem(layer)
            layerItem.setZValue(layerIndex)
            self.addItem(layerItem)
            self.mLayerItems[layerIndex] = layerItem
            layerIndex += 1

        tileSelectionItem = TileSelectionItem(self.mMapDocument)
        tileSelectionItem.setZValue(10000 - 2)
        self.addItem(tileSelectionItem)
        self.mObjectSelectionItem = ObjectSelectionItem(self.mMapDocument)
        self.mObjectSelectionItem.setZValue(10000 - 1)
        self.addItem(self.mObjectSelectionItem)
        self.updateCurrentLayerHighlight()

    ##
    # Repaints the specified region. The region is in tile coordinates.
    ##
    def repaintRegion(self, region, layer):
        renderer = self.mMapDocument.renderer()
        margins = self.mMapDocument.map().drawMargins()
        for r in region.rects():
            boundingRect = QRectF(renderer.boundingRect(r))
            self.update(
                QRectF(
                    renderer.boundingRect(r).adjusted(-margins.left(),
                                                      -margins.top(),
                                                      margins.right(),
                                                      margins.bottom())))
            boundingRect.translate(layer.offset())
            self.update(boundingRect)

    def currentLayerIndexChanged(self):
        self.updateCurrentLayerHighlight()
        # New layer may have a different offset, affecting the grid
        if self.mGridVisible:
            self.update()

    ##
    # Adapts the scene, layers and objects to new map size, orientation or
    # background color.
    ##
    def mapChanged(self):
        self.updateSceneRect()
        for item in self.mLayerItems:
            tli = item
            if type(tli) == TileLayerItem:
                tli.syncWithTileLayer()

        for item in self.mObjectItems.values():
            item.syncWithMapObject()
        map = self.mMapDocument.map()
        if (map.backgroundColor().isValid()):
            self.setBackgroundBrush(map.backgroundColor())
        else:
            self.setBackgroundBrush(self.mDefaultBackgroundColor)

    def tilesetChanged(self, tileset):
        if (not self.mMapDocument):
            return
        if (contains(self.mMapDocument.map().tilesets(), tileset)):
            self.update()

    def tileLayerDrawMarginsChanged(self, tileLayer):
        index = self.mMapDocument.map().layers().indexOf(tileLayer)
        item = self.mLayerItems.at(index)
        item.syncWithTileLayer()

    def layerAdded(self, index):
        layer = self.mMapDocument.map().layerAt(index)
        layerItem = self.createLayerItem(layer)
        self.addItem(layerItem)
        self.mLayerItems.insert(index, layerItem)
        z = 0
        for item in self.mLayerItems:
            item.setZValue(z)
            z += 1

    def layerRemoved(self, index):
        self.mLayerItems.remove(index)

    ##
    # A layer has changed. This can mean that the layer visibility, opacity or
    # offset changed.
    ##
    def layerChanged(self, index):
        layer = self.mMapDocument.map().layerAt(index)
        layerItem = self.mLayerItems.at(index)
        layerItem.setVisible(layer.isVisible())
        multiplier = 1
        if (self.mHighlightCurrentLayer
                and self.mMapDocument.currentLayerIndex() < index):
            multiplier = opacityFactor
        layerItem.setOpacity(layer.opacity() * multiplier)
        layerItem.setPos(layer.offset())

        # Layer offset may have changed, affecting the scene rect and grid
        self.updateSceneRect()
        if self.mGridVisible:
            self.update()

    ##
    # When an object group has changed it may mean its color or drawing order
    # changed, which affects all its objects.
    ##
    def objectGroupChanged(self, objectGroup):
        self.objectsChanged(objectGroup.objects())
        self.objectsIndexChanged(objectGroup, 0, objectGroup.objectCount() - 1)

    ##
    # When an image layer has changed, it may change size and it may look
    # differently.
    ##
    def imageLayerChanged(self, imageLayer):
        index = self.mMapDocument.map().layers().indexOf(imageLayer)
        item = self.mLayerItems.at(index)
        item.syncWithImageLayer()
        item.update()

    ##
    # When the tile offset of a tileset has changed, it can affect the bounding
    # rect of all tile layers and tile objects. It also requires a full repaint.
    ##
    def tilesetTileOffsetChanged(self, tileset):
        self.update()
        for item in self.mLayerItems:
            tli = item
            if type(tli) == TileLayerItem:
                tli.syncWithTileLayer()
        for item in self.mObjectItems:
            cell = item.mapObject().cell()
            if (not cell.isEmpty() and cell.tile.tileset() == tileset):
                item.syncWithMapObject()

    ##
    # Inserts map object items for the given objects.
    ##
    def objectsInserted(self, objectGroup, first, last):
        ogItem = None
        # Find the object group item for the object group
        for item in self.mLayerItems:
            ogi = item
            if type(ogi) == ObjectGroupItem:
                if (ogi.objectGroup() == objectGroup):
                    ogItem = ogi
                    break

        drawOrder = objectGroup.drawOrder()
        for i in range(first, last + 1):
            object = objectGroup.objectAt(i)
            item = MapObjectItem(object, self.mMapDocument, ogItem)
            if (drawOrder == ObjectGroup.DrawOrder.TopDownOrder):
                item.setZValue(item.y())
            else:
                item.setZValue(i)
            self.mObjectItems.insert(object, item)

    ##
    # Removes the map object items related to the given objects.
    ##
    def objectsRemoved(self, objects):
        for o in objects:
            i = self.mObjectItems.find(o)
            self.mSelectedObjectItems.remove(i)
            # python would not force delete QGraphicsItem
            self.removeItem(i)
            self.mObjectItems.erase(o)

    ##
    # Updates the map object items related to the given objects.
    ##
    def objectsChanged(self, objects):
        for object in objects:
            item = self.itemForObject(object)
            item.syncWithMapObject()

    ##
    # Updates the Z value of the objects when appropriate.
    ##
    def objectsIndexChanged(self, objectGroup, first, last):
        if (objectGroup.drawOrder() != ObjectGroup.DrawOrder.IndexOrder):
            return
        for i in range(first, last + 1):
            item = self.itemForObject(objectGroup.objectAt(i))
            item.setZValue(i)

    def updateSelectedObjectItems(self):
        objects = self.mMapDocument.selectedObjects()
        items = QSet()
        for object in objects:
            item = self.itemForObject(object)
            if item:
                items.insert(item)

        self.mSelectedObjectItems = items
        self.selectedObjectItemsChanged.emit()

    def syncAllObjectItems(self):
        for item in self.mObjectItems:
            item.syncWithMapObject()

    def createLayerItem(self, layer):
        layerItem = None
        tl = layer.asTileLayer()
        if tl:
            layerItem = TileLayerItem(tl, self.mMapDocument)
        else:
            og = layer.asObjectGroup()
            if og:
                drawOrder = og.drawOrder()
                ogItem = ObjectGroupItem(og)
                objectIndex = 0
                for object in og.objects():
                    item = MapObjectItem(object, self.mMapDocument, ogItem)
                    if (drawOrder == ObjectGroup.DrawOrder.TopDownOrder):
                        item.setZValue(item.y())
                    else:
                        item.setZValue(objectIndex)
                    self.mObjectItems.insert(object, item)
                    objectIndex += 1

                layerItem = ogItem
            else:
                il = layer.asImageLayer()
                if il:
                    layerItem = ImageLayerItem(il, self.mMapDocument)

        layerItem.setVisible(layer.isVisible())
        return layerItem

    def updateSceneRect(self):
        mapSize = self.mMapDocument.renderer().mapSize()
        sceneRect = QRectF(0, 0, mapSize.width(), mapSize.height())

        margins = self.mMapDocument.map().computeLayerOffsetMargins()
        sceneRect.adjust(-margins.left(), -margins.top(), margins.right(),
                         margins.bottom())

        self.setSceneRect(sceneRect)
        self.mDarkRectangle.setRect(sceneRect)

    def updateCurrentLayerHighlight(self):
        if (not self.mMapDocument):
            return
        currentLayerIndex = self.mMapDocument.currentLayerIndex()
        if (not self.mHighlightCurrentLayer or currentLayerIndex == -1):
            self.mDarkRectangle.setVisible(False)
            # Restore opacity for all layers
            for i in range(self.mLayerItems.size()):
                layer = self.mMapDocument.map().layerAt(i)
                self.mLayerItems.at(i).setOpacity(layer.opacity())

            return

        # Darken layers below the current layer
        self.mDarkRectangle.setZValue(currentLayerIndex - 0.5)
        self.mDarkRectangle.setVisible(True)
        # Set layers above the current layer to half opacity
        for i in range(1, self.mLayerItems.size()):
            layer = self.mMapDocument.map().layerAt(i)
            if currentLayerIndex < i:
                _x = opacityFactor
            else:
                _x = 1
            multiplier = _x
            self.mLayerItems.at(i).setOpacity(layer.opacity() * multiplier)

    def eventFilter(self, object, event):
        x = event.type()
        if x == QEvent.KeyPress or x == QEvent.KeyRelease:
            keyEvent = event
            newModifiers = keyEvent.modifiers()
            if (self.mActiveTool and newModifiers != self.mCurrentModifiers):
                self.mActiveTool.modifiersChanged(newModifiers)
                self.mCurrentModifiers = newModifiers
        else:
            pass

        return False