Пример #1
0
 def __init__(self, parent=None):
     super().__init__(self.tr("Bucket Fill Tool"),
                      QIcon(":images/22x22/stock-tool-bucket-fill.png"),
                      QKeySequence(self.tr("F")), parent)
     self.mStamp = TileStamp()
     self.mFillOverlay = None
     self.mFillRegion = QRegion()
     self.mMissingTilesets = QVector()
     self.mIsActive = False
     self.mLastShiftStatus = False
     ##
     # Indicates if the tool is using the random mode.
     ##
     self.mIsRandom = False
     ##
     # Contains the value of mIsRandom at that time, when the latest call of
     # tilePositionChanged() took place.
     # This variable is needed to detect if the random mode was changed during
     # mFillOverlay being brushed at an area.
     ##
     self.mLastRandomStatus = False
     ##
     # Contains all used random cells to use in random mode.
     # The same cell can be in the list multiple times to make different
     # random weights possible.
     ##
     self.mRandomCellPicker = RandomPicker()
Пример #2
0
    def paintEvent(self, event):
        """
        Called when the widget is painted on the window.
        """
        dpr = self.devicePixelRatioF()
        buffer = QPixmap(self.width() * dpr, self.height() * dpr)
        buffer.setDevicePixelRatio(dpr)
        buffer.fill(Qt.transparent)

        painter = QPainter(buffer)
        region = QRegion(
            QRect(0, 0,
                  self._textWidget.sizeHint().width(),
                  self._textWidget.sizeHint().height()))
        self._textWidget.render(painter, QPoint(0, 0), region)
        region = QRegion(
            QRect(0, 0,
                  self._iconWidget.sizeHint().width(),
                  self._iconWidget.sizeHint().height()))
        x = self._textWidget.sizeHint().width() + 3
        self._iconWidget.render(painter, QPoint(x, 0), region)
        painter.end()

        painter = QPainter(self)
        painter.drawPixmap(event.rect(), buffer, buffer.rect())
        painter.end()
Пример #3
0
    def __init__(self, parent=None):
        super(CuriWidget,
              self).__init__(parent,
                             Qt.FramelessWindowHint | Qt.WindowSystemMenuHint)
        self.addCustomAction()
        w = qApp.desktop().screenGeometry().width()
        h = qApp.desktop().screenGeometry().height()

        side = round((8 / 9) * min(w / cols, h / rows))
        self.setFixedSize(side * QSize(cols, rows))

        self.thread = SoundThread(self)
        self.dragPosition = QPoint()
        self.button = None

        self.setWindowIcon(QIcon(":curielements"))

        region = QRegion(QRect(0, 0, 2 * side, 2 * side), QRegion.Ellipse)
        region += QRegion(QRect(side, 0, 8 * side, 15 * side))
        region += QRegion(QRect(0, side, side, 13 * side))
        region += QRegion(QRect(0, 13 * side, 2 * side, 2 * side),
                          QRegion.Ellipse)
        region += QRegion(QRect(9 * side, side, side, 14 * side))
        region += QRegion(QRect(8 * side, 0, 2 * side, 2 * side),
                          QRegion.Ellipse)
        region += QRegion(QRect(10 * side, 2 * side, 19 * side, 13 * side))
        region += QRegion(QRect(28 * side, 2 * side, 2 * side, 2 * side),
                          QRegion.Ellipse)
        region += QRegion(QRect(29 * side, 3 * side, side, 11 * side))
        region += QRegion(QRect(28 * side, 13 * side, 2 * side, 2 * side),
                          QRegion.Ellipse)

        self.setMask(region)

        self.atoms = Atoms(self)
        self.atoms.setGeometry(
            QRect(1.5 * side, 1.5 * side, 7 * side, 7 * side))

        offset = QPoint(10 * side, 3 * side)
        file = QFile(":elements")
        file.open(QFile.ReadOnly | QFile.Text)
        colors = [blue, yellow]
        self.btns = []
        while not file.atEnd():
            x, y, name, symbol, electron, description, description2, _ = file.readLine(
            ).split(',')
            coordinate = QPoint(int(x), int(y))
            text = bytearray(name).decode()
            btn = ElementButton(QSize(side, side), colors, int(electron),
                                bytearray(symbol).decode(), text, self)
            btn.move(offset + coordinate * side)
            btn.clicked.connect(self.button_clicked)
            self.btns.append(btn)
        self.imageDescription = DescriptionButton(side * QSize(7, 4.5), blue,
                                                  self)
        self.imageDescription.move(1.5 * side, 9 * side)
        btnSound = DescriptionButton(side * QSize(2, 2), blue, self)
        btnSound.move(11 * side, 12 * side)
        btnSound.updateBackground(":soundOn")
        btnSound.clicked.connect(self.sound_clicled)
Пример #4
0
    def doPaint(self, flags=0):
        preview = self.mPreviewLayer
        if not preview:
            return QRegion()

        # This method shouldn't be called when current layer is not a tile layer
        tileLayer = self.currentTileLayer()
        if (not tileLayer.bounds().intersects(
                QRect(preview.x(), preview.y(), preview.width(),
                      preview.height()))):
            return QRegion()

        paint = PaintTileLayer(self.mapDocument(), tileLayer, preview.x(),
                               preview.y(), preview)

        if not self.mMissingTilesets.isEmpty():
            for tileset in self.mMissingTilesets:
                AddTileset(self.mapDocument(), tileset, paint)

            self.mMissingTilesets.clear()

        paint.setMergeable(flags & PaintFlags.Mergeable)
        self.mapDocument().undoStack().push(paint)

        editedRegion = preview.region()
        if (not (flags & PaintFlags.SuppressRegionEdited)):
            self.mapDocument().emitRegionEdited(editedRegion, tileLayer)
        return editedRegion
Пример #5
0
    def itemRegion(self, index):
        if not index.isValid():
            return QRegion()

        if index.column() != 1:
            return QRegion(self.itemRect(index))

        if self.model().data(index) <= 0.0:
            return QRegion()

        startAngle = 0.0
        for row in range(self.model().rowCount(self.rootIndex())):

            sliceIndex = self.model().index(row, 1, self.rootIndex())
            value = self.model().data(sliceIndex)

            if value > 0.0:
                angle = 360 * value / self.totalValue

                if sliceIndex == index:
                    slicePath = QPainterPath()
                    slicePath.moveTo(self.totalSize / 2, self.totalSize / 2)
                    slicePath.arcTo(self.margin, self.margin,
                                    self.margin + self.pieSize,
                                    self.margin + self.pieSize, startAngle,
                                    angle)
                    slicePath.closeSubpath()

                    return QRegion(slicePath.toFillPolygon().toPolygon())

                startAngle += angle

        return QRegion()
Пример #6
0
    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
Пример #7
0
    def removeTab(self, index):
        """
        Remove a tab at `index`.
        """
        if index >= 0 and index < self.count():
            tab = self.__tabs.pop(index)
            layout_index = self.layout().indexOf(tab.button)
            if layout_index != -1:
                self.layout().takeAt(layout_index)

            self.__group.removeButton(tab.button)

            tab.button.removeEventFilter(self)

            if tab.button is self.__sloppyButton:
                self.__sloppyButton = None
                self.__sloppyRegion = QRegion()

            tab.button.deleteLater()
            tab.button.setParent(None)

            if self.currentIndex() == index:
                if self.count():
                    self.setCurrentIndex(max(index - 1, 0))
                else:
                    self.setCurrentIndex(-1)
Пример #8
0
    def __init__(self, parent=None, **kwargs):
        QWidget.__init__(self, parent, **kwargs)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.setLayout(layout)

        self.setSizePolicy(QSizePolicy.Fixed,
                           QSizePolicy.Expanding)
        self.__tabs = []

        self.__currentIndex = -1
        self.__changeOnHover = False

        self.__iconSize = QSize(26, 26)

        self.__group = QButtonGroup(self, exclusive=True)
        self.__group.buttonPressed[QAbstractButton].connect(
            self.__onButtonPressed
        )
        self.setMouseTracking(True)

        self.__sloppyButton = None
        self.__sloppyRegion = QRegion()
        self.__sloppyTimer = QTimer(self, singleShot=True)
        self.__sloppyTimer.timeout.connect(self.__onSloppyTimeout)
Пример #9
0
	def paintEvent(self, anEvent):
		self.painter = QPainter(self)
		self.painter.setRenderHint(QPainter.Antialiasing, on=True)

		self.diskDiameter = min(self.size().width(), self.size().height()) - 10
		self.origin.setX(self.size().width()/2)
		self.origin.setY(self.size().height()/2)

		self.painter.setPen(QPen(QColor(122, 0, 127, 255), 3))
		radius = self.diskDiameter/2
		x = self.origin.x()
		y = self.origin.y()
		diskRect = QRect(x - radius, y - radius, self.diskDiameter, self.diskDiameter)
		self.diskRegion = QRegion(diskRect, QRegion.Ellipse)
		self.painter.setClipRegion(self.diskRegion)
		
		if self.tilesToUpdate:
			for tile in self.tiles:
				tile.update(self.painter)
			self.tilesToUpdate = False
		else:
			self.drawTiling()
		
		self.painter.setClipping(False)

		self.painter.setPen(QPen(QColor(5, 0, 127, 255), 3))
		self.painter.drawEllipse(diskRect)
		self.painter.drawPoint(self.origin)
		
		self.painter.end()
Пример #10
0
    def paint(self, page, painter, rect, callback=None):
        """Reimplemented to paint all the sub pages on top of each other."""
        # make the call back return with the original page, not the overlay page
        newcallback = CallBack(callback, page) if callback else None

        # get the device pixel ratio to paint for
        try:
            ratio = painter.device().devicePixelRatioF()
        except AttributeError:
            ratio = painter.device().devicePixelRatio()

        pixmaps = []
        covered = QRegion()
        for p, overlayrect in page.visiblePagesAt(rect):
            pixmap = QPixmap(overlayrect.size() * ratio)
            if not pixmap.isNull():
                pixmap.setDevicePixelRatio(ratio)
                pt = QPainter(pixmap)
                pt.translate(p.pos() - overlayrect.topLeft())
                p.paint(pt, overlayrect.translated(-p.pos()), newcallback)
                pt.end()
            # even an empty pixmap is appended, otherwise the layer count when
            # compositing goes awry
            pos = overlayrect.topLeft()
            pixmaps.append((pos, pixmap))
            covered += overlayrect

        if QRegion(rect).subtracted(covered):
            painter.fillRect(rect, page.paperColor or self.paperColor)

        self.combine(painter, pixmaps)
Пример #11
0
    def draw(self, painter: QPainter):
        assert self.crop, 'crop must be set'

        # Compute painter regions for the crop and the blur
        all_region = QRegion(painter.viewport())
        crop_region = QRegion(self.crop)
        blur_region = all_region.subtracted(crop_region)

        # Let the QGraphicsBlurEffect only paint in blur_region
        painter.setClipRegion(blur_region)

        # Fill with black and set opacity so that the blurred region is drawn darker
        if self.BLUR_DARKEN > 0.0:
            painter.fillRect(painter.viewport(), Qt.black)
            painter.setOpacity(1 - self.BLUR_DARKEN)

        # Draw the blur effect
        super().draw(painter)

        # Restore clipping and opacity
        painter.setClipping(False)
        painter.setOpacity(1.0)

        # Get the source pixmap
        pixmap, offset = self.sourcePixmap(Qt.DeviceCoordinates, QGraphicsEffect.NoPad)
        painter.setWorldTransform(QTransform())

        # Get the source by adding the offset to the crop location
        source = self.crop
        if self.CROP_OFFSET_ENABLED:
            source = source.translated(self.CROP_OFFSET)
        painter.drawPixmap(self.crop.topLeft() + offset, pixmap, source)
Пример #12
0
    def __init__(self):
        super().__init__()

        self.mMapDocument = None
        self.mBoundingRect = QRectF()
        self.mRegion = QRegion()
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
Пример #13
0
 def __init__(self):
     super(Menu, self).__init__()
     self.background_picture = QGraphicsView()
     self.scene = QGraphicsScene()
     self.grp = QGroupBox("Mon Groupe")
     self.palette = QPalette()
     self.proxy = QGraphicsProxyWidget()
     self.background_picture2 = QPixmap()
     self.fenetre_creer_une_partie = None
     self.bouton_creer = QPushButton(self)
     self.bouton_rejoindre = QPushButton(self)
     self.bouton_quitter = QPushButton(self)
     self.bouton_help = QPushButton(self)
     self.label = QLabel(self)
     self.init_ui()
     self.setWindowTitle("Bienvenue dans Skip-Bo!")
     self.hauteur = int
     self.quart_hauteur = int
     self.longueur = int
     self.quart_longueur = int
     self.test_rect = QRect()
     self.test_region = QRegion()
     self.rsp = None
     self.rsp2 = None
     self.rsp3 = None
     self.join = QDialog()
     self.verification = QDialog()
     self.quitter_icon = QIcon()
     self.join_icon = QIcon()
     self.creer_icon = QIcon()
     self.help_icon = QIcon()
     self.msg_help = QMessageBox()
Пример #14
0
    def __updateGeometry(self):
        """
        Update the shadow geometry to fit the widget's changed
        geometry.

        """
        widget = self.__widget
        parent = self.__widgetParent
        radius = self.radius_
        pos = widget.pos()
        if parent != widget.parentWidget():
            pos = widget.parentWidget().mapTo(parent, pos)

        geom = QRect(pos, widget.size())
        geom.adjust(-radius, -radius, radius, radius)
        if geom != self.geometry():
            self.setGeometry(geom)

        # Set the widget mask (punch a hole through to the `widget` instance.
        rect = self.rect()

        mask = QRegion(rect)
        transparent = QRegion(rect.adjusted(radius, radius, -radius, -radius))

        mask = mask.subtracted(transparent)
        self.setMask(mask)
Пример #15
0
    def mousePressEvent(self, event):
        ellipse = QRegion(self.rect, QRegion.Ellipse)

        if ellipse.contains(event.pos()):
            self.state = not self.state

        self.update()  # call paintEvent()
Пример #16
0
    def init_ui(self):
        # --------------- Paramètres de la fenêtre --------------------
        self.resize(1280, 720)
        self.setMaximumSize(1280, 720)
        self.setMinimumSize(1280, 720)
        self.center()
        self.background_picture2.load('backgroundmenu3.png')
        self.palette.setBrush(self.backgroundRole(),
                              QBrush(self.background_picture2))
        self.setPalette(self.palette)

        # --------------- Éléments présents dans la fenêtre ----------
        self.hauteur = int(self.frameGeometry().height())
        self.quart_hauteur = int(self.hauteur / 4)
        self.longueur = int(self.frameGeometry().width())
        self.quart_longueur = int(self.longueur / 4)
        self.label.setText("* Skip-Bo est une marque déposée de Mattel Inc.")
        self.label.move(20, 690)
        self.label.adjustSize()
        self.bouton_creer.setText("")
        self.creer_icon = QIcon("bouton_creer.png")
        self.bouton_creer.setIcon(self.creer_icon)
        self.bouton_creer.setIconSize(QSize(320, 90))
        self.bouton_creer.setGeometry((self.quart_longueur - 160),
                                      (3 * self.quart_hauteur),
                                      self.quart_longueur,
                                      int(self.quart_hauteur / 2))
        self.bouton_creer.clicked.connect(self.creer_nouvelle_partie)
        self.bouton_rejoindre.setText("")
        self.join_icon = QIcon("bouton_join.png")
        self.bouton_rejoindre.setIcon(self.join_icon)
        self.bouton_rejoindre.setIconSize(QSize(318, 88))
        self.bouton_rejoindre.setGeometry(((2 * self.quart_longueur) - 160),
                                          (3 * self.quart_hauteur),
                                          self.quart_longueur,
                                          int(self.quart_hauteur / 2))
        self.bouton_rejoindre.clicked.connect(self.joindre_partie)
        self.bouton_help.setText("")
        self.help_icon = QIcon("bouton_help.png")
        self.bouton_help.setIcon(self.help_icon)
        self.bouton_help.setIconSize(QSize(65, 65))
        self.bouton_help.setFixedHeight(100)
        self.bouton_help.setFixedWidth(100)
        self.bouton_help.move(
            self.longueur - int(self.quart_longueur / (4 / 2)), -10)
        self.bouton_help.clicked.connect(self.help)
        self.test_rect = QRect(20, 20, 60, 60)
        self.test_region = QRegion(self.test_rect, QRegion.Ellipse)
        self.test_region.boundingRect().size()
        self.bouton_help.setMask(self.test_region)
        self.bouton_quitter.setText("")
        self.quitter_icon = QIcon("bouton_quitter.png")
        self.bouton_quitter.setIcon(self.quitter_icon)
        self.bouton_quitter.setIconSize(QSize(318, 88))
        self.bouton_quitter.setGeometry(((3 * self.quart_longueur) - 160),
                                        (3 * self.quart_hauteur),
                                        self.quart_longueur,
                                        int(self.quart_hauteur / 2))
        self.bouton_quitter.clicked.connect(self.quitter)
Пример #17
0
    def clearOverlay(self):
        # Clear connections before clearing overlay so there is no
        # risk of getting a callback and causing an infinite loop
        self.clearConnections(self.mapDocument())
        self.brushItem().clear()
        self.mFillOverlay = None

        self.mFillRegion = QRegion()
Пример #18
0
    def setTileLayer(self, tileLayer):
        self.mTileLayer = tileLayer
        if (tileLayer):
            self.mRegion = tileLayer.region()
        else:
            self.mRegion = QRegion()

        self.updateBoundingRect()
        self.update()
Пример #19
0
 def mask_label_region(self):
     """
     Mask the webcam label region to avoid mouse events
     :return:
     """
     region = QRegion(self.webcamDisplayLabel.frameGeometry())
     region -= QRegion(self.webcamDisplayLabel.geometry())
     region += self.webcamDisplayLabel.childrenRegion()
     self.webcamDisplayLabel.setMask(region)
Пример #20
0
    def __init__(self, map, fileName=QString()):
        super().__init__()

        ##
        # The filename of a plugin is unique. So it can be used to determine
        # the right plugin to be used for saving or reloading the map.
        # The nameFilter of a plugin can not be used, since it's translatable.
        # The filename of a plugin must not change while maps are open using this
        # plugin.
        ##
        self.mReaderFormat = None
        self.mWriterFormat = None
        self.mExportFormat = None
        self.mSelectedArea = QRegion()
        self.mSelectedObjects = QList()
        self.mSelectedTiles = QList()
        self.mCurrentLayerIndex = 0
        self.mLastSaved = QDateTime()
        self.mLastExportFileName = ''

        self.mFileName = fileName
        self.mMap = map
        self.mLayerModel = LayerModel(self)
        self.mCurrentObject = map  ## Current properties object. ##
        self.mRenderer = None
        self.mMapObjectModel = MapObjectModel(self)
        self.mTerrainModel = TerrainModel(self, self)
        self.mUndoStack = QUndoStack(self)
        self.createRenderer()
        if (map.layerCount() == 0):
            _x = -1
        else:
            _x = 0
        self.mCurrentLayerIndex = _x
        self.mLayerModel.setMapDocument(self)
        # Forward signals emitted from the layer model
        self.mLayerModel.layerAdded.connect(self.onLayerAdded)
        self.mLayerModel.layerAboutToBeRemoved.connect(
            self.onLayerAboutToBeRemoved)
        self.mLayerModel.layerRemoved.connect(self.onLayerRemoved)
        self.mLayerModel.layerChanged.connect(self.layerChanged)
        # Forward signals emitted from the map object model
        self.mMapObjectModel.setMapDocument(self)
        self.mMapObjectModel.objectsAdded.connect(self.objectsAdded)
        self.mMapObjectModel.objectsChanged.connect(self.objectsChanged)
        self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved)
        self.mMapObjectModel.rowsInserted.connect(
            self.onMapObjectModelRowsInserted)
        self.mMapObjectModel.rowsRemoved.connect(
            self.onMapObjectModelRowsInsertedOrRemoved)
        self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved)
        self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved)
        self.mUndoStack.cleanChanged.connect(self.modifiedChanged)
        # Register tileset references
        tilesetManager = TilesetManager.instance()
        tilesetManager.addReferences(self.mMap.tilesets())
Пример #21
0
 def setCells(self, x, y, layer, mask=QRegion()):
     # Determine the overlapping area
     area = QRegion(QRect(x, y, layer.width(), layer.height()))
     area &= QRect(0, 0, self.width(), self.height())
     if (not mask.isEmpty()):
         area &= mask
     for rect in area.rects():
         for _x in range(rect.left(), rect.right() + 1):
             for _y in range(rect.top(), rect.bottom() + 1):
                 self.setCell(_x, _y, layer.cellAt(_x - x, _y - y))
Пример #22
0
 def setCells(self, x, y, layer, mask = QRegion()):
     # Determine the overlapping area
     area = QRegion(QRect(x, y, layer.width(), layer.height()))
     area &= QRect(0, 0, self.width(), self.height())
     if (not mask.isEmpty()):
         area &= mask
     for rect in area.rects():
         for _x in range(rect.left(), rect.right()+1):
             for _y in range(rect.top(), rect.bottom()+1):
                 self.setCell(_x, _y, layer.cellAt(_x - x, _y - y))
Пример #23
0
    def updateActions(self):
        map = None
        currentLayerIndex = -1
        selection = QRegion()
        selectedObjectsCount = 0
        canMergeDown = False
        if (self.mMapDocument):
            map = self.mMapDocument.map()
            currentLayerIndex = self.mMapDocument.currentLayerIndex()
            selection = self.mMapDocument.selectedArea()
            selectedObjectsCount = self.mMapDocument.selectedObjects().count()
            if (currentLayerIndex > 0):
                upper = map.layerAt(currentLayerIndex)
                lower = map.layerAt(currentLayerIndex - 1)
                canMergeDown = lower.canMergeWith(upper)

        self.mActionSelectAll.setEnabled(bool(map))
        self.mActionSelectNone.setEnabled(not selection.isEmpty())
        self.mActionCropToSelection.setEnabled(not selection.isEmpty())
        self.mActionAddTileLayer.setEnabled(bool(map))
        self.mActionAddObjectGroup.setEnabled(bool(map))
        self.mActionAddImageLayer.setEnabled(bool(map))
        if map:
            _x = map.layerCount()
        else:
            _x = 0
        layerCount = _x
        hasPreviousLayer = currentLayerIndex >= 0 and currentLayerIndex < layerCount - 1
        hasNextLayer = currentLayerIndex > 0
        self.mActionDuplicateLayer.setEnabled(currentLayerIndex >= 0)
        self.mActionMergeLayerDown.setEnabled(canMergeDown)
        self.mActionSelectPreviousLayer.setEnabled(hasPreviousLayer)
        self.mActionSelectNextLayer.setEnabled(hasNextLayer)
        self.mActionMoveLayerUp.setEnabled(hasPreviousLayer)
        self.mActionMoveLayerDown.setEnabled(hasNextLayer)
        self.mActionToggleOtherLayers.setEnabled(layerCount > 1)
        self.mActionRemoveLayer.setEnabled(currentLayerIndex >= 0)
        self.mActionLayerProperties.setEnabled(currentLayerIndex >= 0)
        self.mActionDuplicateObjects.setEnabled(selectedObjectsCount > 0)
        self.mActionRemoveObjects.setEnabled(selectedObjectsCount > 0)
        duplicateText = QString()
        removeText = QString()
        if (selectedObjectsCount > 0):
            duplicateText = self.tr("Duplicate %n Object(s)", "",
                                    selectedObjectsCount)
            removeText = self.tr("Remove %n Object(s)", "",
                                 selectedObjectsCount)
        else:
            duplicateText = self.tr("Duplicate Objects")
            removeText = self.tr("Remove Objects")

        self.mActionDuplicateObjects.setText(duplicateText)
        self.mActionRemoveObjects.setText(removeText)
Пример #24
0
 def paintableRegion(self, *args):
     l = len(args)
     if l==4:
         x, y, width, height = args
         return self.paintableRegion(QRect(x, y, width, height))
     elif l==1:
         region = args[0]
         bounds = QRegion(self.mTileLayer.bounds())
         intersection = bounds.intersected(region)
         selection = self.mMapDocument.selectedArea()
         if (not selection.isEmpty()):
             intersection &= selection
         return intersection
Пример #25
0
    def __init__(self, mask_path: Path):
        mask_pix = QPixmap()
        if not mask_pix.load(str(mask_path)):
            raise ValueError(f"Failed to load mask from {mask_path}")

        self.size = mask_pix.size()
        logger.debug("size: %s", self.size)

        self.clip_region = QRegion(
            mask_pix.createMaskFromColor(QColor("white"), Qt.MaskInColor)
        )
        logger.debug("clip_region: %s", self.clip_region)
        logger.debug("clip_region bounding rect: %s", self.clip_region.boundingRect())
    def updateActions(self):
        map = None
        currentLayerIndex = -1
        selection = QRegion()
        selectedObjectsCount = 0
        canMergeDown = False
        if (self.mMapDocument):
            map = self.mMapDocument.map()
            currentLayerIndex = self.mMapDocument.currentLayerIndex()
            selection = self.mMapDocument.selectedArea()
            selectedObjectsCount = self.mMapDocument.selectedObjects().count()
            if (currentLayerIndex > 0):
                upper = map.layerAt(currentLayerIndex)
                lower = map.layerAt(currentLayerIndex - 1)
                canMergeDown = lower.canMergeWith(upper)

        self.mActionSelectAll.setEnabled(bool(map))
        self.mActionSelectNone.setEnabled(not selection.isEmpty())
        self.mActionCropToSelection.setEnabled(not selection.isEmpty())
        self.mActionAddTileLayer.setEnabled(bool(map))
        self.mActionAddObjectGroup.setEnabled(bool(map))
        self.mActionAddImageLayer.setEnabled(bool(map))
        if map:
            _x = map.layerCount()
        else:
            _x = 0
        layerCount = _x
        hasPreviousLayer = currentLayerIndex >= 0 and currentLayerIndex < layerCount - 1
        hasNextLayer = currentLayerIndex > 0
        self.mActionDuplicateLayer.setEnabled(currentLayerIndex >= 0)
        self.mActionMergeLayerDown.setEnabled(canMergeDown)
        self.mActionSelectPreviousLayer.setEnabled(hasPreviousLayer)
        self.mActionSelectNextLayer.setEnabled(hasNextLayer)
        self.mActionMoveLayerUp.setEnabled(hasPreviousLayer)
        self.mActionMoveLayerDown.setEnabled(hasNextLayer)
        self.mActionToggleOtherLayers.setEnabled(layerCount > 1)
        self.mActionRemoveLayer.setEnabled(currentLayerIndex >= 0)
        self.mActionLayerProperties.setEnabled(currentLayerIndex >= 0)
        self.mActionDuplicateObjects.setEnabled(selectedObjectsCount > 0)
        self.mActionRemoveObjects.setEnabled(selectedObjectsCount > 0)
        duplicateText = QString()
        removeText = QString()
        if (selectedObjectsCount > 0):
            duplicateText = self.tr("Duplicate %n Object(s)", "", selectedObjectsCount)
            removeText = self.tr("Remove %n Object(s)", "", selectedObjectsCount)
        else:
            duplicateText = self.tr("Duplicate Objects")
            removeText = self.tr("Remove Objects")

        self.mActionDuplicateObjects.setText(duplicateText)
        self.mActionRemoveObjects.setText(removeText)
Пример #27
0
    def setCurrentIndex(self, index):
        """
        Set the current tab index.
        """
        if self.__currentIndex != index:
            self.__currentIndex = index

            self.__sloppyRegion = QRegion()
            self.__sloppyButton = None

            if index != -1:
                self.__tabs[index].button.setChecked(True)

            self.currentChanged.emit(index)
Пример #28
0
 def update_cursor_blocks(self):
     # Update the areas of the previously selected and currently selected blocks in the editor to avoid artifacts
     block = self.textCursor().block()
     if self._last_selected_block:
         currentBlockRect = self.document().documentLayout(
         ).blockBoundingRect(block).toRect()
         lastBlockRect = self.document().documentLayout().blockBoundingRect(
             self._last_selected_block).toRect()
         viewportoffset = self.verticalScrollBar().value()
         currentBlockRect.moveTop(currentBlockRect.y() - viewportoffset)
         lastBlockRect.moveTop(lastBlockRect.y() - viewportoffset)
         region = QRegion(currentBlockRect).united(QRegion(lastBlockRect))
         self.viewport().update(region)
     self._last_selected_block = block
Пример #29
0
    def doErase(self, continuation):
        tileLayer = self.currentTileLayer()
        tilePos = self.tilePosition()
        eraseRegion = QRegion(tilePos.x(), tilePos.y(), 1, 1)
        if (continuation):
            for p in pointsOnLine(self.mLastTilePos, tilePos):
                eraseRegion |= QRegion(p.x(), p.y(), 1, 1)

        self.mLastTilePos = self.tilePosition()
        if (not tileLayer.bounds().intersects(eraseRegion.boundingRect())):
            return
        erase = EraseTiles(self.mapDocument(), tileLayer, eraseRegion)
        erase.setMergeable(continuation)
        self.mapDocument().undoStack().push(erase)
        self.mapDocument().emitRegionEdited(eraseRegion, tileLayer)
Пример #30
0
    def __init__(self):
        super().__init__()

        self.mMapDocument = None
        self.mBoundingRect = QRectF()
        self.mRegion = QRegion()
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
Пример #31
0
def take_screenshot(window, screenshots_dir):
    timestamp = int(time.time())
    pixmap = QPixmap(window.rect().size())
    window.render(pixmap, QPoint(), QRegion(window.rect()))
    screenshots_dir.mkdir(exist_ok=True)
    img_name = 'exception_screenshot_%d.jpg' % timestamp
    pixmap.save(str(screenshots_dir / img_name))
Пример #32
0
 def __init__(self, parent = None):
     super().__init__(self.tr("Bucket Fill Tool"),
                        QIcon(":images/22x22/stock-tool-bucket-fill.png"),
                        QKeySequence(self.tr("F")),
                        parent)
     self.mStamp = TileStamp()
     self.mFillOverlay = None
     self.mFillRegion = QRegion()
     self.mMissingTilesets = QVector()
     self.mIsActive = False
     self.mLastShiftStatus = False
     ##
     # Indicates if the tool is using the random mode.
     ##
     self.mIsRandom = False
     ##
     # Contains the value of mIsRandom at that time, when the latest call of
     # tilePositionChanged() took place.
     # This variable is needed to detect if the random mode was changed during
     # mFillOverlay being brushed at an area.
     ##
     self.mLastRandomStatus = False
     ##
     # Contains all used random cells to use in random mode.
     # The same cell can be in the list multiple times to make different
     # random weights possible.
     ##
     self.mRandomCellPicker = RandomPicker()
Пример #33
0
    def __updateCurve(self):
        curveData = self.__d_curve.data() 
        curveData.values().lock()

        numPoints = curveData.size()
        if ( numPoints > self.__d_paintedPoints ):
            doClip = not self.canvas().testAttribute( Qt.WA_PaintOnScreen )
            if ( doClip ):
        
            
            #    Depending on the platform setting a clip might be an important
            #    performance issue. F.e. for Qt Embedded this reduces the
            #    part of the backing store that has to be copied out - maybe
            #    to an unaccelerated frame buffer device.
            

                xMap = self.canvasMap( self.__d_curve.xAxis() )
                yMap = self.canvasMap( self.__d_curve.yAxis() )

                br = Qwt.qwtBoundingRect( curveData,
                    self.__d_paintedPoints - 1, numPoints - 1 )

                clipRect = Qwt.QwtScaleMap.transform( xMap, yMap, br ).toRect()
                self.__d_directPainter.setClipRegion( QRegion(clipRect) )
        

            self.__d_directPainter.drawSeries( self.__d_curve,
                self.__d_paintedPoints - 1, numPoints - 1 )
            self.__d_paintedPoints = numPoints
    

        curveData.values().unlock()
Пример #34
0
    def resizeEvent(self, event):
        side = min(self.width(), self.height())

        maskedRegion = QRegion(self.width() / 2 - side / 2,
                               self.height() / 2 - side / 2, side, side,
                               QRegion.Ellipse)
        self.setMask(maskedRegion)
Пример #35
0
    def clearOverlay(self):
        # Clear connections before clearing overlay so there is no
        # risk of getting a callback and causing an infinite loop
        self.clearConnections(self.mapDocument())
        self.brushItem().clear()
        self.mFillOverlay = None

        self.mFillRegion = QRegion()
Пример #36
0
 def __handleMask(self, maskType):
     """
     Private method to calculate the handle mask.
     
     @param maskType type of the mask to be used
         (SnapshotRegionGrabber.FillMask or
         SnapshotRegionGrabber.StrokeMask)
     @return calculated mask (QRegion)
     """
     mask = QRegion()
     for rect in self.__handles:
         if maskType == SnapshotRegionGrabber.StrokeMask:
             r = QRegion(rect)
             mask += r.subtracted(QRegion(rect.adjusted(1, 1, -1, -1)))
         else:
             mask += QRegion(rect.adjusted(1, 1, -1, -1))
     return mask
Пример #37
0
    def setTileLayer(self, tileLayer):
        self.mTileLayer = tileLayer
        if (tileLayer):
            self.mRegion = tileLayer.region()
        else:
            self.mRegion = QRegion()

        self.updateBoundingRect()
        self.update()
Пример #38
0
    def __init__(self, map, fileName = QString()):
        super().__init__()

        ##
        # The filename of a plugin is unique. So it can be used to determine
        # the right plugin to be used for saving or reloading the map.
        # The nameFilter of a plugin can not be used, since it's translatable.
        # The filename of a plugin must not change while maps are open using this
        # plugin.
        ##
        self.mReaderFormat = None
        self.mWriterFormat = None
        self.mExportFormat = None
        self.mSelectedArea = QRegion()
        self.mSelectedObjects = QList()
        self.mSelectedTiles = QList()
        self.mCurrentLayerIndex = 0
        self.mLastSaved = QDateTime()
        self.mLastExportFileName = ''

        self.mFileName = fileName
        self.mMap = map
        self.mLayerModel = LayerModel(self)
        self.mCurrentObject = map ## Current properties object. ##
        self.mRenderer = None
        self.mMapObjectModel = MapObjectModel(self)
        self.mTerrainModel = TerrainModel(self, self)
        self.mUndoStack = QUndoStack(self)
        self.createRenderer()
        if (map.layerCount() == 0):
            _x = -1
        else:
            _x = 0
        self.mCurrentLayerIndex = _x
        self.mLayerModel.setMapDocument(self)
        # Forward signals emitted from the layer model
        self.mLayerModel.layerAdded.connect(self.onLayerAdded)
        self.mLayerModel.layerAboutToBeRemoved.connect(self.onLayerAboutToBeRemoved)
        self.mLayerModel.layerRemoved.connect(self.onLayerRemoved)
        self.mLayerModel.layerChanged.connect(self.layerChanged)
        # Forward signals emitted from the map object model
        self.mMapObjectModel.setMapDocument(self)
        self.mMapObjectModel.objectsAdded.connect(self.objectsAdded)
        self.mMapObjectModel.objectsChanged.connect(self.objectsChanged)
        self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved)
        self.mMapObjectModel.rowsInserted.connect(self.onMapObjectModelRowsInserted)
        self.mMapObjectModel.rowsRemoved.connect(self.onMapObjectModelRowsInsertedOrRemoved)
        self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved)
        self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved)
        self.mUndoStack.cleanChanged.connect(self.modifiedChanged)
        # Register tileset references
        tilesetManager = TilesetManager.instance()
        tilesetManager.addReferences(self.mMap.tilesets())
Пример #39
0
    def updatePreview(self, *args):
        l = len(args)
        if l==0:
            ##
            # Updates the position of the brush item based on the mouse position.
            ##
            self.updatePreview(self.tilePosition())
        elif l==1:
            tilePos = args[0]
            
            if not self.mapDocument():
                return

            tileRegion = QRegion()

            if self.mBrushBehavior == BrushBehavior.Capture:
                self.mPreviewLayer = None
                tileRegion = self.capturedArea()
            elif self.mStamp.isEmpty():
                self.mPreviewLayer = None
                tileRegion = QRect(tilePos, QSize(1, 1))
            else:
                if self.mBrushBehavior == BrushBehavior.LineStartSet:
                    self.drawPreviewLayer(pointsOnLine(self.mStampReference, tilePos))
                elif self.mBrushBehavior == BrushBehavior.CircleMidSet:
                    self.drawPreviewLayer(pointsOnEllipse(self.mStampReference, tilePos))
                elif self.mBrushBehavior == BrushBehavior.Capture:
                    # already handled above
                    pass
                elif self.mBrushBehavior == BrushBehavior.Circle:
                    # while finding the mid point, there is no need to show
                    # the (maybe bigger than 1x1) stamp
                    self.mPreviewLayer.clear()
                    tileRegion = QRect(tilePos, QSize(1, 1))
                elif self.mBrushBehavior==BrushBehavior.Line or self.mBrushBehavior==BrushBehavior.Free or self.mBrushBehavior==BrushBehavior.Paint:
                    self.drawPreviewLayer(QVector(tilePos))

            self.brushItem().setTileLayer(self.mPreviewLayer)
            if not tileRegion.isEmpty():
                self.brushItem().setTileRegion(tileRegion)
Пример #40
0
    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))
Пример #41
0
def drawRect(painter, rect, outline, fill=QColor()):
    """
    Module function to draw a rectangle with the given parameters.
    
    @param painter reference to the painter to be used (QPainter)
    @param rect rectangle to be drawn (QRect)
    @param outline color of the outline (QColor)
    @param fill fill color (QColor)
    """
    clip = QRegion(rect)
    clip = clip.subtracted(QRegion(rect.adjusted(1, 1, -1, -1)))
    
    painter.save()
    painter.setClipRegion(clip)
    painter.setPen(Qt.NoPen)
    painter.setBrush(outline)
    painter.drawRect(rect)
    if fill.isValid():
        painter.setClipping(False)
        painter.setBrush(fill)
        painter.drawRect(rect.adjusted(1, 1, -1, -1))
    painter.restore()
Пример #42
0
    def copy(self, *args):
        l = len(args)
        if l==1:
            region = args[0]
            if type(region) != QRegion:
                region = QRegion(region)
            area = region.intersected(QRect(0, 0, self.width(), self.height()))
            bounds = region.boundingRect()
            areaBounds = area.boundingRect()
            offsetX = max(0, areaBounds.x() - bounds.x())
            offsetY = max(0, areaBounds.y() - bounds.y())
            copied = TileLayer(QString(), 0, 0, bounds.width(), bounds.height())
            for rect in area.rects():
                for x in range(rect.left(), rect.right()+1):
                    for y in range(rect.top(), rect.bottom()+1):
                        copied.setCell(x - areaBounds.x() + offsetX,
                                        y - areaBounds.y() + offsetY,
                                        self.cellAt(x, y))
            return copied
        elif l==4:
            x, y, width, height = args

            return self.copy(QRegion(x, y, width, height))
Пример #43
0
    def __init__(self, mapDocument, target, x, y, source):
        super().__init__()
        
        self.mMapDocument = mapDocument
        self.mTarget = target
        self.mSource = source.clone()
        self.mX = x
        self.mY = y
        self.mPaintedRegion = QRegion(x, y, source.width(), source.height())
        self.mMergeable = False

        self.mErased = self.mTarget.copy(self.mX - self.mTarget.x(),
                                self.mY - self.mTarget.y(),
                                self.mSource.width(), self.mSource.height())
        self.setText(QCoreApplication.translate("Undo Commands", "Paint"))
Пример #44
0
 def resizeEvent(self, event):
     super(RoundImageWidget, self).resizeEvent(event)
     width = self._imageLabel.width()
     height = self._imageLabel.height()
     radius = min(width, height) / 10 / 2
     verticalRegion = QRegion(0, radius, width, height - 2 * radius)
     horizontalRegion = QRegion(radius, 0, width - 2 * radius, height)
     circle = QRegion(0, 0, 2 * radius, 2 * radius, QRegion.Ellipse)
     region = verticalRegion.united(horizontalRegion)
     region = region.united(circle)
     region = region.united(circle.translated(width - 2 * radius, 0))
     region = region.united(circle.translated(width - 2 * radius, height - 2 * radius))
     region = region.united(circle.translated(0, height - 2 * radius))
     self._imageLabel.setMask(region)
Пример #45
0
 def __grabRect(self):
     """
     Private method to grab the selected rectangle (i.e. do the snapshot).
     """
     if self.__mode == SnapshotRegionGrabber.Ellipse:
         ell = QRegion(self.__selection, QRegion.Ellipse)
         if not ell.isEmpty():
             self.__grabbing = True
             
             xOffset = self.__pixmap.rect().x() - ell.boundingRect().x()
             yOffset = self.__pixmap.rect().y() - ell.boundingRect().y()
             translatedEll = ell.translated(xOffset, yOffset)
             
             pixmap2 = QPixmap(ell.boundingRect().size())
             pixmap2.fill(Qt.transparent)
             
             pt = QPainter()
             pt.begin(pixmap2)
             if pt.paintEngine().hasFeature(QPaintEngine.PorterDuff):
                 pt.setRenderHints(
                     QPainter.Antialiasing |
                     QPainter.HighQualityAntialiasing |
                     QPainter.SmoothPixmapTransform,
                     True)
                 pt.setBrush(Qt.black)
                 pt.setPen(QPen(QBrush(Qt.black), 0.5))
                 pt.drawEllipse(translatedEll.boundingRect())
                 pt.setCompositionMode(QPainter.CompositionMode_SourceIn)
             else:
                 pt.setClipRegion(translatedEll)
                 pt.setCompositionMode(QPainter.CompositionMode_Source)
             
             pt.drawPixmap(pixmap2.rect(), self.__pixmap,
                           ell.boundingRect())
             pt.end()
             
             self.grabbed.emit(pixmap2)
     else:
         r = QRect(self.__selection)
         if not r.isNull() and r.isValid():
             self.__grabbing = True
             self.grabbed.emit(self.__pixmap.copy(r))
Пример #46
0
 def deactivate(self, scene):
     super().deactivate(scene)
     self.mFillRegion = QRegion()
     self.mIsActive = False
Пример #47
0
    def drawPreviewLayer(self, _list):
        self.mPreviewLayer = None

        if self.mStamp.isEmpty():
            return

        if self.mIsRandom:
            if self.mRandomCellPicker.isEmpty():
                return

            paintedRegion = QRegion()
            for p in _list:
                paintedRegion += QRect(p, QSize(1, 1))

            bounds = paintedRegion.boundingRect()
            preview = TileLayer(QString(),
                                  bounds.x(), bounds.y(),
                                  bounds.width(), bounds.height())

            for p in _list:
                cell = self.mRandomCellPicker.pick()
                preview.setCell(p.x() - bounds.left(),
                                 p.y() - bounds.top(),
                                 cell)

            self.mPreviewLayer = preview
        else:
            self.mMissingTilesets.clear()

            paintedRegion = QRegion()
            operations = QVector()
            regionCache = QHash()

            for p in _list:
                variation = self.mStamp.randomVariation()
                self.mapDocument().unifyTilesets(variation.map, self.mMissingTilesets)

                stamp = variation.tileLayer()

                stampRegion = QRegion()
                if regionCache.contains(stamp):
                    stampRegion = regionCache.value(stamp)
                else:
                    stampRegion = stamp.region()
                    regionCache.insert(stamp, stampRegion)

                centered = QPoint(p.x() - int(stamp.width() / 2), p.y() - int(stamp.height() / 2))

                region = stampRegion.translated(centered.x(), centered.y())
                if not paintedRegion.intersects(region):
                    paintedRegion += region

                    op = PaintOperation(centered, stamp)
                    operations.append(op)

            bounds = paintedRegion.boundingRect()
            preview = TileLayer(QString(),
                                      bounds.x(), bounds.y(),
                                      bounds.width(), bounds.height())

            for op in operations:
                preview.merge(op.pos - bounds.topLeft(), op.stamp)
            
            self.mPreviewLayer = preview
Пример #48
0
 def paintEvent(self, evt):
     """
     Protected method handling paint events.
     
     @param evt paint event (QPaintEvent)
     """
     if self.__grabbing:     # grabWindow() should just get the background
         return
     
     painter = QPainter(self)
     pal = QPalette(QToolTip.palette())
     font = QToolTip.font()
     
     handleColor = pal.color(QPalette.Active, QPalette.Highlight)
     handleColor.setAlpha(160)
     overlayColor = QColor(0, 0, 0, 160)
     textColor = pal.color(QPalette.Active, QPalette.Text)
     textBackgroundColor = pal.color(QPalette.Active, QPalette.Base)
     painter.drawPixmap(0, 0, self.__pixmap)
     painter.setFont(font)
     
     r = QRect(self.__selection)
     if not self.__selection.isNull():
         grey = QRegion(self.rect())
         if self.__mode == SnapshotRegionGrabber.Ellipse:
             reg = QRegion(r, QRegion.Ellipse)
         else:
             reg = QRegion(r)
         grey = grey.subtracted(reg)
         painter.setClipRegion(grey)
         painter.setPen(Qt.NoPen)
         painter.setBrush(overlayColor)
         painter.drawRect(self.rect())
         painter.setClipRect(self.rect())
         drawRect(painter, r, handleColor)
     
     if self.__showHelp:
         painter.setPen(textColor)
         painter.setBrush(textBackgroundColor)
         self.__helpTextRect = painter.boundingRect(
             self.rect().adjusted(2, 2, -2, -2),
             Qt.TextWordWrap, self.__helpText).translated(
             -self.__desktop.x(), -self.__desktop.y())
         self.__helpTextRect.adjust(-2, -2, 4, 2)
         drawRect(painter, self.__helpTextRect, textColor,
                  textBackgroundColor)
         painter.drawText(
             self.__helpTextRect.adjusted(3, 3, -3, -3),
             Qt.TextWordWrap, self.__helpText)
     
     if self.__selection.isNull():
         return
     
     # The grabbed region is everything which is covered by the drawn
     # rectangles (border included). This means that there is no 0px
     # selection, since a 0px wide rectangle will always be drawn as a line.
     txt = "{0:n}, {1:n} ({2:n} x {3:n})".format(
         self.__selection.x(), self.__selection.y(),
         self.__selection.width(), self.__selection.height())
     textRect = painter.boundingRect(self.rect(), Qt.AlignLeft, txt)
     boundingRect = textRect.adjusted(-4, 0, 0, 0)
     
     if textRect.width() < r.width() - 2 * self.__handleSize and \
        textRect.height() < r.height() - 2 * self.__handleSize and \
        r.width() > 100 and \
        r.height() > 100:
         # center, unsuitable for small selections
         boundingRect.moveCenter(r.center())
         textRect.moveCenter(r.center())
     elif r.y() - 3 > textRect.height() and \
             r.x() + textRect.width() < self.rect().width():
         # on top, left aligned
         boundingRect.moveBottomLeft(QPoint(r.x(), r.y() - 3))
         textRect.moveBottomLeft(QPoint(r.x() + 2, r.y() - 3))
     elif r.x() - 3 > textRect.width():
         # left, top aligned
         boundingRect.moveTopRight(QPoint(r.x() - 3, r.y()))
         textRect.moveTopRight(QPoint(r.x() - 5, r.y()))
     elif r.bottom() + 3 + textRect.height() < self.rect().bottom() and \
             r.right() > textRect.width():
         # at bottom, right aligned
         boundingRect.moveTopRight(QPoint(r.right(), r.bottom() + 3))
         textRect.moveTopRight(QPoint(r.right() - 2, r.bottom() + 3))
     elif r.right() + textRect.width() + 3 < self.rect().width():
         # right, bottom aligned
         boundingRect.moveBottomLeft(QPoint(r.right() + 3, r.bottom()))
         textRect.moveBottomLeft(QPoint(r.right() + 5, r.bottom()))
     
     # If the above didn't catch it, you are running on a very
     # tiny screen...
     drawRect(painter, boundingRect, textColor, textBackgroundColor)
     painter.drawText(textRect, Qt.AlignHCenter, txt)
     
     if (r.height() > self.__handleSize * 2 and
         r.width() > self.__handleSize * 2) or \
        not self.__mouseDown:
         self.__updateHandles()
         painter.setPen(Qt.NoPen)
         painter.setBrush(handleColor)
         painter.setClipRegion(
             self.__handleMask(SnapshotRegionGrabber.StrokeMask))
         painter.drawRect(self.rect())
         handleColor.setAlpha(60)
         painter.setBrush(handleColor)
         painter.setClipRegion(
             self.__handleMask(SnapshotRegionGrabber.FillMask))
         painter.drawRect(self.rect())
Пример #49
0
class BrushItem(QGraphicsItem):
    ##
    # Constructor.
    ##
    def __init__(self):
        super().__init__()

        self.mMapDocument = None
        self.mBoundingRect = QRectF()
        self.mRegion = QRegion()
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption)
        
    ##
    # Sets the map document this brush is operating on.
    ##
    def setMapDocument(self, mapDocument):
        if (self.mMapDocument == mapDocument):
            return
        self.mMapDocument = mapDocument
        # The tiles in the stamp may no longer be valid
        self.clear()

    ##
    # Clears the tile layer and region set on this item.
    ##
    def clear(self):
        self.setTileLayer(None)
        
    ##
    # Sets a tile layer representing this brush. When no tile layer is set,
    # the brush only draws the selection color.
    #
    # The BrushItem does not take ownership over the tile layer, but makes a
    # personal copy of the tile layer.
    ##
    def setTileLayer(self, tileLayer):
        self.mTileLayer = tileLayer
        if (tileLayer):
            self.mRegion = tileLayer.region()
        else:
            self.mRegion = QRegion()

        self.updateBoundingRect()
        self.update()

    ##
    # Returns the current tile layer.
    ##
    def tileLayer(self):
        return self.mTileLayer

    ##
    # Changes the position of the tile layer, if one is set.
    ##
    def setTileLayerPosition(self, pos):
        if (not self.mTileLayer):
            return
        oldPosition = QPoint(self.mTileLayer.x(), self.mTileLayer.y())
        if (oldPosition == pos):
            return

        self.mRegion.translate(pos - oldPosition)
        self.mTileLayer.setX(pos.x())
        self.mTileLayer.setY(pos.y())
        self.updateBoundingRect()

    ##
    # Sets the region of tiles that this brush item occupies.
    ##
    def setTileRegion(self, region):
        if type(region)!=QRegion:
            region = QRegion(region)
        if (self.mRegion == region):
            return
        self.mRegion = region
        self.updateBoundingRect()

    ##
    # Sets the layer offset used by the currently active layer.
    ##
    def setLayerOffset(self, offset):
        self.setPos(offset)
        
    ##
    # Returns the region of the current tile layer or the region that was set
    # using setTileRegion.
    ##
    def tileRegion(self):
        return self.mRegion

    # QGraphicsItem
    def boundingRect(self):
        return self.mBoundingRect

    def paint(self, painter, option, widget = None):
        insideMapHighlight = QApplication.palette().highlight().color()
        insideMapHighlight.setAlpha(64)
        outsideMapHighlight = QColor(255, 0, 0, 64)
        mapWidth = self.mMapDocument.map().width()
        mapHeight = self.mMapDocument.map().height()
        mapRegion = QRegion(0, 0, mapWidth, mapHeight)
        insideMapRegion = self.mRegion.intersected(mapRegion)
        outsideMapRegion = self.mRegion.subtracted(mapRegion)
        renderer = self.mMapDocument.renderer()
        if (self.mTileLayer):
            opacity = painter.opacity()
            painter.setOpacity(0.75)
            renderer.drawTileLayer(painter, self.mTileLayer, option.exposedRect)
            painter.setOpacity(opacity)
        renderer.drawTileSelection(painter, insideMapRegion, insideMapHighlight, option.exposedRect)
        renderer.drawTileSelection(painter, outsideMapRegion, outsideMapHighlight, option.exposedRect)

    def updateBoundingRect(self):
        self.prepareGeometryChange()
        if (not self.mMapDocument):
            self.mBoundingRect = QRectF()
            return

        bounds = self.mRegion.boundingRect()
        self.mBoundingRect = QRectF(self.mMapDocument.renderer().boundingRect(bounds))
        # Adjust for amount of pixels tiles extend at the top and to the right
        if (self.mTileLayer):
            map = self.mMapDocument.map()
            drawMargins = self.mTileLayer.drawMargins()
            drawMargins.setTop(drawMargins.top() - map.tileHeight())
            drawMargins.setRight(drawMargins.right() - map.tileWidth())
            # Since we're also drawing a tile selection, we should not apply
            # negative margins
            self.mBoundingRect.adjust(min(0, -drawMargins.left()),
                                 min(0, -drawMargins.top()),
                                 max(0, drawMargins.right()),
                                 max(0, drawMargins.bottom()))
Пример #50
0
class BucketFillTool(AbstractTileTool):
    def tr(self, sourceText, disambiguation = '', n = -1):
        return QCoreApplication.translate('BucketFillTool', sourceText, disambiguation, n)

    def __init__(self, parent = None):
        super().__init__(self.tr("Bucket Fill Tool"),
                           QIcon(":images/22x22/stock-tool-bucket-fill.png"),
                           QKeySequence(self.tr("F")),
                           parent)
        self.mStamp = TileStamp()
        self.mFillOverlay = None
        self.mFillRegion = QRegion()
        self.mMissingTilesets = QVector()
        self.mIsActive = False
        self.mLastShiftStatus = False
        ##
        # Indicates if the tool is using the random mode.
        ##
        self.mIsRandom = False
        ##
        # Contains the value of mIsRandom at that time, when the latest call of
        # tilePositionChanged() took place.
        # This variable is needed to detect if the random mode was changed during
        # mFillOverlay being brushed at an area.
        ##
        self.mLastRandomStatus = False
        ##
        # Contains all used random cells to use in random mode.
        # The same cell can be in the list multiple times to make different
        # random weights possible.
        ##
        self.mRandomCellPicker = RandomPicker()

    def __del__(self):
        pass

    def activate(self, scene):
        super().activate(scene)
        self.mIsActive = True
        self.tilePositionChanged(self.tilePosition())

    def deactivate(self, scene):
        super().deactivate(scene)
        self.mFillRegion = QRegion()
        self.mIsActive = False

    def mousePressed(self, event):
        if (event.button() != Qt.LeftButton or self.mFillRegion.isEmpty()):
            return
        if (not self.brushItem().isVisible()):
            return
        
        preview = self.mFillOverlay
        if not preview:
            return

        paint = PaintTileLayer(self.mapDocument(),
                                       self.currentTileLayer(),
                                       preview.x(),
                                       preview.y(),
                                       preview)

        paint.setText(QCoreApplication.translate("Undo Commands", "Fill Area"))

        if not self.mMissingTilesets.isEmpty():
            for tileset in self.mMissingTilesets:
                AddTileset(self.mapDocument(), tileset, paint)

            self.mMissingTilesets.clear()
            
        fillRegion = QRegion(self.mFillRegion)
        self.mapDocument().undoStack().push(paint)
        self.mapDocument().emitRegionEdited(fillRegion, self.currentTileLayer())

    def mouseReleased(self, event):
        pass

    def modifiersChanged(self, modifiers):
        # Don't need to recalculate fill region if there was no fill region
        if (not self.mFillOverlay):
            return
        self.tilePositionChanged(self.tilePosition())

    def languageChanged(self):
        self.setName(self.tr("Bucket Fill Tool"))
        self.setShortcut(QKeySequence(self.tr("F")))

    ##
    # Sets the stamp that is drawn when filling. The BucketFillTool takes
    # ownership over the stamp layer.
    ##
    def setStamp(self, stamp):
        # Clear any overlay that we presently have with an old stamp
        self.clearOverlay()
        self.mStamp = stamp
        self.updateRandomListAndMissingTilesets()
        if (self.mIsActive and self.brushItem().isVisible()):
            self.tilePositionChanged(self.tilePosition())

    ##
    # This returns the actual tile layer which is used to define the current
    # state.
    ##
    def stamp(self):
        return TileStamp(self.mStamp)

    def setRandom(self, value):
        if (self.mIsRandom == value):
            return
        self.mIsRandom = value
        self.updateRandomListAndMissingTilesets()
        
        # Don't need to recalculate fill region if there was no fill region
        if (not self.mFillOverlay):
            return
        self.tilePositionChanged(self.tilePosition())

    def tilePositionChanged(self, tilePos):
        # Skip filling if the stamp is empty
        if  self.mStamp.isEmpty():
            return
            
        # Make sure that a tile layer is selected
        tileLayer = self.currentTileLayer()
        if (not tileLayer):
            return
        
        shiftPressed = QApplication.keyboardModifiers() & Qt.ShiftModifier
        fillRegionChanged = False
        
        regionComputer = TilePainter(self.mapDocument(), tileLayer)
        # If the stamp is a single tile, ignore it when making the region
        if (not shiftPressed and self.mStamp.variations().size() == 1):
            variation = self.mStamp.variations().first()
            stampLayer = variation.tileLayer()
            if (stampLayer.size() == QSize(1, 1) and stampLayer.cellAt(0, 0) == regionComputer.cellAt(tilePos)):
                return
            
        # This clears the connections so we don't get callbacks
        self.clearConnections(self.mapDocument())
        # Optimization: we don't need to recalculate the fill area
        # if the new mouse position is still over the filled region
        # and the shift modifier hasn't changed.
        if (not self.mFillRegion.contains(tilePos) or shiftPressed != self.mLastShiftStatus):
            # Clear overlay to make way for a new one
            self.clearOverlay()
            # Cache information about how the fill region was created
            self.mLastShiftStatus = shiftPressed
            # Get the new fill region
            if (not shiftPressed):
                # If not holding shift, a region is generated from the current pos
                self.mFillRegion = regionComputer.computePaintableFillRegion(tilePos)
            else:
                # If holding shift, the region is the selection bounds
                self.mFillRegion = self.mapDocument().selectedArea()
                # Fill region is the whole map if there is no selection
                if (self.mFillRegion.isEmpty()):
                    self.mFillRegion = tileLayer.bounds()
                # The mouse needs to be in the region
                if (not self.mFillRegion.contains(tilePos)):
                    self.mFillRegion = QRegion()

            fillRegionChanged = True

        # Ensure that a fill region was created before making an overlay layer
        if (self.mFillRegion.isEmpty()):
            return
        if (self.mLastRandomStatus != self.mIsRandom):
            self.mLastRandomStatus = self.mIsRandom
            fillRegionChanged = True

        if (not self.mFillOverlay):
            # Create a new overlay region
            fillBounds = self.mFillRegion.boundingRect()
            self.mFillOverlay = TileLayer(QString(),
                                         fillBounds.x(),
                                         fillBounds.y(),
                                         fillBounds.width(),
                                         fillBounds.height())

        # Paint the new overlay
        if (not self.mIsRandom):
            if (fillRegionChanged or self.mStamp.variations().size() > 1):
                fillWithStamp(self.mFillOverlay, self.mStamp, self.mFillRegion.translated(-self.mFillOverlay.position()))
                fillRegionChanged = True
        else:
            self.randomFill(self.mFillOverlay, self.mFillRegion)
            fillRegionChanged = True

        if (fillRegionChanged):
            # Update the brush item to draw the overlay
            self.brushItem().setTileLayer(self.mFillOverlay)

        # Create connections to know when the overlay should be cleared
        self.makeConnections()

    def mapDocumentChanged(self, oldDocument, newDocument):
        super().mapDocumentChanged(oldDocument, newDocument)
        self.clearConnections(oldDocument)
        # Reset things that are probably invalid now
        if newDocument:
            self.updateRandomListAndMissingTilesets()

        self.clearOverlay()

    def clearOverlay(self):
        # Clear connections before clearing overlay so there is no
        # risk of getting a callback and causing an infinite loop
        self.clearConnections(self.mapDocument())
        self.brushItem().clear()
        self.mFillOverlay = None

        self.mFillRegion = QRegion()

    def makeConnections(self):
        if (not self.mapDocument()):
            return
        # Overlay may need to be cleared if a region changed
        self.mapDocument().regionChanged.connect(self.clearOverlay)
        # Overlay needs to be cleared if we switch to another layer
        self.mapDocument().currentLayerIndexChanged.connect(self.clearOverlay)
        # Overlay needs be cleared if the selection changes, since
        # the overlay may be bound or may need to be bound to the selection
        self.mapDocument().selectedAreaChanged.connect(self.clearOverlay)

    def clearConnections(self, mapDocument):
        if (not mapDocument):
            return
        try:
            mapDocument.regionChanged.disconnect(self.clearOverlay)
            mapDocument.currentLayerIndexChanged.disconnect(self.clearOverlay)
            mapDocument.selectedAreaChanged.disconnect(self.clearOverlay)
        except:
            pass

    ##
    # Updates the list of random cells.
    # This is done by taking all non-null tiles from the original stamp mStamp.
    ##
    def updateRandomListAndMissingTilesets(self):
        self.mRandomCellPicker.clear()
        self.mMissingTilesets.clear()
        
        for variation in self.mStamp.variations():
            self.mapDocument().unifyTilesets(variation.map, self.mMissingTilesets)

            if self.mIsRandom:
                for cell in variation.tileLayer():
                    if not cell.isEmpty():
                        self.mRandomCellPicker.add(cell, cell.tile.probability())

    ##
    # Fills the given \a region in the given \a tileLayer with random tiles.
    ##
    def randomFill(self, tileLayer, region):
        if (region.isEmpty() or self.mRandomList.empty()):
            return
        for rect in region.translated(-tileLayer.position()).rects():
            for _x in range(rect.left(), rect.right()+1):
                for _y in range(rect.top(), rect.bottom()+1):
                    tileLayer.setCell(_x, _y, self.mRandomCellPicker.pick())
Пример #51
0
    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
Пример #52
0
    def tilePositionChanged(self, tilePos):
        # Skip filling if the stamp is empty
        if  self.mStamp.isEmpty():
            return
            
        # Make sure that a tile layer is selected
        tileLayer = self.currentTileLayer()
        if (not tileLayer):
            return
        
        shiftPressed = QApplication.keyboardModifiers() & Qt.ShiftModifier
        fillRegionChanged = False
        
        regionComputer = TilePainter(self.mapDocument(), tileLayer)
        # If the stamp is a single tile, ignore it when making the region
        if (not shiftPressed and self.mStamp.variations().size() == 1):
            variation = self.mStamp.variations().first()
            stampLayer = variation.tileLayer()
            if (stampLayer.size() == QSize(1, 1) and stampLayer.cellAt(0, 0) == regionComputer.cellAt(tilePos)):
                return
            
        # This clears the connections so we don't get callbacks
        self.clearConnections(self.mapDocument())
        # Optimization: we don't need to recalculate the fill area
        # if the new mouse position is still over the filled region
        # and the shift modifier hasn't changed.
        if (not self.mFillRegion.contains(tilePos) or shiftPressed != self.mLastShiftStatus):
            # Clear overlay to make way for a new one
            self.clearOverlay()
            # Cache information about how the fill region was created
            self.mLastShiftStatus = shiftPressed
            # Get the new fill region
            if (not shiftPressed):
                # If not holding shift, a region is generated from the current pos
                self.mFillRegion = regionComputer.computePaintableFillRegion(tilePos)
            else:
                # If holding shift, the region is the selection bounds
                self.mFillRegion = self.mapDocument().selectedArea()
                # Fill region is the whole map if there is no selection
                if (self.mFillRegion.isEmpty()):
                    self.mFillRegion = tileLayer.bounds()
                # The mouse needs to be in the region
                if (not self.mFillRegion.contains(tilePos)):
                    self.mFillRegion = QRegion()

            fillRegionChanged = True

        # Ensure that a fill region was created before making an overlay layer
        if (self.mFillRegion.isEmpty()):
            return
        if (self.mLastRandomStatus != self.mIsRandom):
            self.mLastRandomStatus = self.mIsRandom
            fillRegionChanged = True

        if (not self.mFillOverlay):
            # Create a new overlay region
            fillBounds = self.mFillRegion.boundingRect()
            self.mFillOverlay = TileLayer(QString(),
                                         fillBounds.x(),
                                         fillBounds.y(),
                                         fillBounds.width(),
                                         fillBounds.height())

        # Paint the new overlay
        if (not self.mIsRandom):
            if (fillRegionChanged or self.mStamp.variations().size() > 1):
                fillWithStamp(self.mFillOverlay, self.mStamp, self.mFillRegion.translated(-self.mFillOverlay.position()))
                fillRegionChanged = True
        else:
            self.randomFill(self.mFillOverlay, self.mFillRegion)
            fillRegionChanged = True

        if (fillRegionChanged):
            # Update the brush item to draw the overlay
            self.brushItem().setTileLayer(self.mFillOverlay)

        # Create connections to know when the overlay should be cleared
        self.makeConnections()
Пример #53
0
class PaintTileLayer(QUndoCommand):

    ##
    # Constructor.
    #
    # @param mapDocument the map document that's being edited
    # @param target      the target layer to paint on
    # @param x           the x position of the paint location
    # @param y           the y position of the paint location
    # @param source      the source layer to paint on the target layer
    ##
    def __init__(self, mapDocument, target, x, y, source):
        super().__init__()
        
        self.mMapDocument = mapDocument
        self.mTarget = target
        self.mSource = source.clone()
        self.mX = x
        self.mY = y
        self.mPaintedRegion = QRegion(x, y, source.width(), source.height())
        self.mMergeable = False

        self.mErased = self.mTarget.copy(self.mX - self.mTarget.x(),
                                self.mY - self.mTarget.y(),
                                self.mSource.width(), self.mSource.height())
        self.setText(QCoreApplication.translate("Undo Commands", "Paint"))

    def __del__(self):
        del self.mSource
        del self.mErased

    ##
    # Sets whether this undo command can be merged with an existing command.
    ##
    def setMergeable(self, mergeable):
        self.mMergeable = mergeable

    def undo(self):
        painter = TilePainter(self.mMapDocument, self.mTarget)
        painter.setCells(self.mX, self.mY, self.mErased, self.mPaintedRegion)
        
        super().undo() # undo child commands

    def redo(self):
        super().redo() # redo child commands
        
        painter = TilePainter(self.mMapDocument, self.mTarget)
        painter.drawCells(self.mX, self.mY, self.mSource)

    def id(self):
        return UndoCommands.Cmd_PaintTileLayer

    def mergeWith(self, other):
        o = other
        if (not (self.mMapDocument == o.mMapDocument and self.mTarget == o.mTarget and o.mMergeable)):
            return False
        newRegion = o.mPaintedRegion.subtracted(self.mPaintedRegion)
        combinedRegion = self.mPaintedRegion.united(o.mPaintedRegion)
        bounds = QRect(self.mX, self.mY, self.mSource.width(), self.mSource.height())
        combinedBounds = combinedRegion.boundingRect()
        # Resize the erased tiles and source layers when necessary
        if (bounds != combinedBounds):
            shift = bounds.topLeft() - combinedBounds.topLeft()
            self.mErased.resize(combinedBounds.size(), shift)
            self.mSource.resize(combinedBounds.size(), shift)

        mX = combinedBounds.left()
        self.mY = combinedBounds.top()
        self.mPaintedRegion = combinedRegion
        # Copy the painted tiles from the other command over
        pos = QPoint(o.mX, o.mY) - combinedBounds.topLeft()
        self.mSource.merge(pos, o.mSource)
        # Copy the newly erased tiles from the other command over
        for rect in newRegion.rects():
            for y in range(rect.top(), rect.bottom()+1):
                for x in range(rect.left(), rect.right()+1):
                    self.mErased.setCell(x - mX,
                                     y - self.mY,
                                     o.mErased.cellAt(x - o.mX, y - o.mY))
        return True
Пример #54
0
class MapDocument(QObject):
    fileNameChanged = pyqtSignal(str, str)
    modifiedChanged = pyqtSignal()
    saved = pyqtSignal()
    ##
    # Emitted when the selected tile region changes. Sends the currently
    # selected region and the previously selected region.
    ##
    selectedAreaChanged = pyqtSignal(QRegion, QRegion)
    ##
    # Emitted when the list of selected objects changes.
    ##
    selectedObjectsChanged = pyqtSignal()
    ##
    # Emitted when the list of selected tiles from the dock changes.
    ##
    selectedTilesChanged = pyqtSignal()
    currentObjectChanged = pyqtSignal(list)
    ##
    # Emitted when the map size or its tile size changes.
    ##
    mapChanged = pyqtSignal()
    layerAdded = pyqtSignal(int)
    layerAboutToBeRemoved = pyqtSignal(int)
    layerRenamed = pyqtSignal(int)
    layerRemoved = pyqtSignal(int)
    layerChanged = pyqtSignal(int)
    ##
    # Emitted after a new layer was added and the name should be edited.
    # Applies to the current layer.
    ##
    editLayerNameRequested = pyqtSignal()
    editCurrentObject = pyqtSignal()
    ##
    # Emitted when the current layer index changes.
    ##
    currentLayerIndexChanged = pyqtSignal(int)
    ##
    # Emitted when a certain region of the map changes. The region is given in
    # tile coordinates.
    ##
    regionChanged = pyqtSignal(QRegion, Layer)
    ##
    # Emitted when a certain region of the map was edited by user input.
    # The region is given in tile coordinates.
    # If multiple layers have been edited, multiple signals will be emitted.
    ##
    regionEdited = pyqtSignal(QRegion, Layer)
    tileLayerDrawMarginsChanged = pyqtSignal(TileLayer)
    tileTerrainChanged = pyqtSignal(QList)
    tileProbabilityChanged = pyqtSignal(Tile)
    tileObjectGroupChanged = pyqtSignal(Tile)
    tileAnimationChanged = pyqtSignal(Tile)
    objectGroupChanged = pyqtSignal(ObjectGroup)
    imageLayerChanged = pyqtSignal(ImageLayer)
    tilesetAboutToBeAdded = pyqtSignal(int)
    tilesetAdded = pyqtSignal(int, Tileset)
    tilesetAboutToBeRemoved = pyqtSignal(int)
    tilesetRemoved = pyqtSignal(Tileset)
    tilesetMoved = pyqtSignal(int, int)
    tilesetFileNameChanged = pyqtSignal(Tileset)
    tilesetNameChanged = pyqtSignal(Tileset)
    tilesetTileOffsetChanged = pyqtSignal(Tileset)
    tilesetChanged = pyqtSignal(Tileset)
    objectsAdded = pyqtSignal(QList)
    objectsInserted = pyqtSignal(ObjectGroup, int, int)
    objectsRemoved = pyqtSignal(QList)
    objectsChanged = pyqtSignal(QList)
    objectsIndexChanged = pyqtSignal(ObjectGroup, int, int)
    propertyAdded = pyqtSignal(Object, str)
    propertyRemoved = pyqtSignal(Object, str)
    propertyChanged = pyqtSignal(Object, str)
    propertiesChanged = pyqtSignal(Object)

    ##
    # Constructs a map document around the given map. The map document takes
    # ownership of the map.
    ##
    def __init__(self, map, fileName = QString()):
        super().__init__()

        ##
        # The filename of a plugin is unique. So it can be used to determine
        # the right plugin to be used for saving or reloading the map.
        # The nameFilter of a plugin can not be used, since it's translatable.
        # The filename of a plugin must not change while maps are open using this
        # plugin.
        ##
        self.mReaderFormat = None
        self.mWriterFormat = None
        self.mExportFormat = None
        self.mSelectedArea = QRegion()
        self.mSelectedObjects = QList()
        self.mSelectedTiles = QList()
        self.mCurrentLayerIndex = 0
        self.mLastSaved = QDateTime()
        self.mLastExportFileName = ''

        self.mFileName = fileName
        self.mMap = map
        self.mLayerModel = LayerModel(self)
        self.mCurrentObject = map ## Current properties object. ##
        self.mRenderer = None
        self.mMapObjectModel = MapObjectModel(self)
        self.mTerrainModel = TerrainModel(self, self)
        self.mUndoStack = QUndoStack(self)
        self.createRenderer()
        if (map.layerCount() == 0):
            _x = -1
        else:
            _x = 0
        self.mCurrentLayerIndex = _x
        self.mLayerModel.setMapDocument(self)
        # Forward signals emitted from the layer model
        self.mLayerModel.layerAdded.connect(self.onLayerAdded)
        self.mLayerModel.layerAboutToBeRemoved.connect(self.onLayerAboutToBeRemoved)
        self.mLayerModel.layerRemoved.connect(self.onLayerRemoved)
        self.mLayerModel.layerChanged.connect(self.layerChanged)
        # Forward signals emitted from the map object model
        self.mMapObjectModel.setMapDocument(self)
        self.mMapObjectModel.objectsAdded.connect(self.objectsAdded)
        self.mMapObjectModel.objectsChanged.connect(self.objectsChanged)
        self.mMapObjectModel.objectsRemoved.connect(self.onObjectsRemoved)
        self.mMapObjectModel.rowsInserted.connect(self.onMapObjectModelRowsInserted)
        self.mMapObjectModel.rowsRemoved.connect(self.onMapObjectModelRowsInsertedOrRemoved)
        self.mMapObjectModel.rowsMoved.connect(self.onObjectsMoved)
        self.mTerrainModel.terrainRemoved.connect(self.onTerrainRemoved)
        self.mUndoStack.cleanChanged.connect(self.modifiedChanged)
        # Register tileset references
        tilesetManager = TilesetManager.instance()
        tilesetManager.addReferences(self.mMap.tilesets())

    ##
    # Destructor.
    ##
    def __del__(self):
        # Unregister tileset references
        tilesetManager = TilesetManager.instance()
        tilesetManager.removeReferences(self.mMap.tilesets())
        del self.mRenderer
        del self.mMap

    ##
    # Saves the map to its current file name. Returns whether or not the file
    # was saved successfully. If not, <i>error</i> will be set to the error
    # message if it is not 0.
    ##
    def save(self, *args):
        l = len(args)
        if l==0:
            args = ('')
        if l==1:
            arg = args[0]
            file = QFileInfo(arg)
            if not file.isFile():
                fileName = self.fileName()
                error = args[0]
            else:
                fileName = arg
                error = ''
            return self.save(fileName, error)
        if l==2:
            ##
            # Saves the map to the file at \a fileName. Returns whether or not the
            # file was saved successfully. If not, <i>error</i> will be set to the
            # error message if it is not 0.
            #
            # If the save was successful, the file name of this document will be set
            # to \a fileName.
            #
            # The map format will be the same as this map was opened with.
            ##
            fileName, error = args
            mapFormat = self.mWriterFormat
            
            tmxMapFormat = TmxMapFormat()
            if (not mapFormat):
                mapFormat = tmxMapFormat
            if (not mapFormat.write(self.map(), fileName)):
                if (error):
                   error = mapFormat.errorString()
                return False

            self.undoStack().setClean()
            self.setFileName(fileName)
            self.mLastSaved = QFileInfo(fileName).lastModified()
            self.saved.emit()
            return True

    ##
    # Loads a map and returns a MapDocument instance on success. Returns 0
    # on error and sets the \a error message.
    ##
    def load(fileName, mapFormat = None):
        error = ''
        tmxMapFormat = TmxMapFormat()
        
        if (not mapFormat and not tmxMapFormat.supportsFile(fileName)):
            # Try to find a plugin that implements support for this format
            formats = PluginManager.objects()
            for format in formats:
                if (format.supportsFile(fileName)):
                    mapFormat = format
                    break

        map = None
        errorString = ''

        if mapFormat:
            map = mapFormat.read(fileName)
            errorString = mapFormat.errorString()
        else:
            map = tmxMapFormat.read(fileName)
            errorString = tmxMapFormat.errorString()

        if (not map):
            error = errorString
            return None, error

        mapDocument = MapDocument(map, fileName)
        if mapFormat:
            mapDocument.setReaderFormat(mapFormat)
            if mapFormat.hasCapabilities(MapFormat.Write):
                mapDocument.setWriterFormat(mapFormat)

        return mapDocument, error

    def fileName(self):
        return self.mFileName

    def lastExportFileName(self):
        return self.mLastExportFileName

    def setLastExportFileName(self, fileName):
        self.mLastExportFileName = fileName

    def readerFormat(self):
        return self.mReaderFormat

    def setReaderFormat(self, format):
        self.mReaderFormat = format

    def writerFormat(self):
        return self.mWriterFormat

    def setWriterFormat(self, format):
        self.mWriterFormat = format

    def exportFormat(self):
        return self.mExportFormat

    def setExportFormat(self, format):
        self.mExportFormat = format
        
    ##
    # Returns the name with which to display this map. It is the file name without
    # its path, or 'untitled.tmx' when the map has no file name.
    ##
    def displayName(self):
        displayName = QFileInfo(self.mFileName).fileName()
        if len(displayName)==0:
            displayName = self.tr("untitled.tmx")
        return displayName

    ##
    # Returns whether the map has unsaved changes.
    ##
    def isModified(self):
        return not self.mUndoStack.isClean()

    def lastSaved(self):
        return self.mLastSaved
        
    ##
    # Returns the map instance. Be aware that directly modifying the map will
    # not allow the GUI to update itself appropriately.
    ##
    def map(self):
        return self.mMap

    ##
    # Sets the current layer to the given index.
    ##
    def setCurrentLayerIndex(self, index):
        changed = self.mCurrentLayerIndex != index
        self.mCurrentLayerIndex = index
        ## This function always sends the following signal, even if the index
        # didn't actually change. This is because the selected index in the layer
        # table view might be out of date anyway, and would otherwise not be
        # properly updated.
        #
        # This problem happens due to the selection model not sending signals
        # about changes to its current index when it is due to insertion/removal
        # of other items. The selected item doesn't change in that case, but our
        # layer index does.
        ##
        self.currentLayerIndexChanged.emit(self.mCurrentLayerIndex)
        if (changed and self.mCurrentLayerIndex != -1):
            self.setCurrentObject(self.currentLayer())

    ##
    # Returns the index of the currently selected layer. Returns -1 if no
    # layer is currently selected.
    ##
    def currentLayerIndex(self):
        return self.mCurrentLayerIndex

    ##
    # Returns the currently selected layer, or 0 if no layer is currently
    # selected.
    ##
    def currentLayer(self):
        if (self.mCurrentLayerIndex == -1):
            return None
        return self.mMap.layerAt(self.mCurrentLayerIndex)

    ##
    # Resize this map to the given \a size, while at the same time shifting
    # the contents by \a offset.
    ##
    def resizeMap(self, size, offset):
        movedSelection = self.mSelectedArea.translated(offset)
        newArea = QRect(-offset, size)
        visibleArea = self.mRenderer.boundingRect(newArea)
        origin = self.mRenderer.tileToPixelCoords_(QPointF())
        newOrigin = self.mRenderer.tileToPixelCoords_(-offset)
        pixelOffset = origin - newOrigin
        # Resize the map and each layer
        self.mUndoStack.beginMacro(self.tr("Resize Map"))
        for i in range(self.mMap.layerCount()):
            layer = self.mMap.layerAt(i)
            x = layer.layerType()
            if x==Layer.TileLayerType:
                tileLayer = layer
                self.mUndoStack.push(ResizeTileLayer(self, tileLayer, size, offset))
            elif x==Layer.ObjectGroupType:
                objectGroup = layer
                # Remove objects that will fall outside of the map
                for o in objectGroup.objects():
                    if (not visibleIn(visibleArea, o, self.mRenderer)):
                        self.mUndoStack.push(RemoveMapObject(self, o))
                    else:
                        oldPos = o.position()
                        newPos = oldPos + pixelOffset
                        self.mUndoStack.push(MoveMapObject(self, newPos, oldPos))
            elif x==Layer.ImageLayerType:
                # Currently not adjusted when resizing the map
                break

        self.mUndoStack.push(ResizeMap(self, size))
        self.mUndoStack.push(ChangeSelectedArea(self, movedSelection))
        self.mUndoStack.endMacro()
        # TODO: Handle layers that don't match the map size correctly

    ##
    # Offsets the layers at \a layerIndexes by \a offset, within \a bounds,
    # and optionally wraps on the X or Y axis.
    ##
    def offsetMap(self, layerIndexes, offset, bounds, wrapX, wrapY):
        if (layerIndexes.empty()):
            return
        if (layerIndexes.size() == 1):
            self.mUndoStack.push(OffsetLayer(self, layerIndexes.first(), offset,
                                             bounds, wrapX, wrapY))
        else:
            self.mUndoStack.beginMacro(self.tr("Offset Map"))
            for layerIndex in layerIndexes:
                self.mUndoStack.push(OffsetLayer(self, layerIndex, offset,
                                                 bounds, wrapX, wrapY))

            self.mUndoStack.endMacro()

    ##
    # Flips the selected objects in the given \a direction.
    ##
    def flipSelectedObjects(self, direction):
        if (self.mSelectedObjects.isEmpty()):
            return
        self.mUndoStack.push(FlipMapObjects(self, self.mSelectedObjects, direction))

    ##
    # Rotates the selected objects.
    ##
    def rotateSelectedObjects(self, direction):
        if (self.mSelectedObjects.isEmpty()):
            return
        self.mUndoStack.beginMacro(self.tr("Rotate %n Object(s)", "", self.mSelectedObjects.size()))
        # TODO: Rotate them properly as a group
        for mapObject in self.mSelectedObjects:
            oldRotation = mapObject.rotation()
            newRotation = oldRotation
            if (direction == RotateDirection.RotateLeft):
                newRotation -= 90
                if (newRotation < -180):
                    newRotation += 360
            else:
                newRotation += 90
                if (newRotation > 180):
                    newRotation -= 360

            self.mUndoStack.push(RotateMapObject(self, mapObject, newRotation, oldRotation))

        self.mUndoStack.endMacro()

    ##
    # Adds a layer of the given type to the top of the layer stack. After adding
    # the new layer, emits editLayerNameRequested().
    ##
    def addLayer(self, layerType):
        layer = None
        name = QString()
        x = layerType
        if x==Layer.TileLayerType:
            name = self.tr("Tile Layer %d"%(self.mMap.tileLayerCount() + 1))
            layer = TileLayer(name, 0, 0, self.mMap.width(), self.mMap.height())
        elif x==Layer.ObjectGroupType:
            name = self.tr("Object Layer %d"%(self.mMap.objectGroupCount() + 1))
            layer = ObjectGroup(name, 0, 0, self.mMap.width(), self.mMap.height())
        elif x==Layer.ImageLayerType:
            name = self.tr("Image Layer %d"%(self.mMap.imageLayerCount() + 1))
            layer = ImageLayer(name, 0, 0, self.mMap.width(), self.mMap.height())

        index = self.mMap.layerCount()
        self.mUndoStack.push(AddLayer(self, index, layer))
        self.setCurrentLayerIndex(index)
        self.editLayerNameRequested.emit()

    ##
    # Duplicates the currently selected layer.
    ##
    def duplicateLayer(self):
        if (self.mCurrentLayerIndex == -1):
            return
        duplicate = self.mMap.layerAt(self.mCurrentLayerIndex).clone()
        duplicate.setName(self.tr("Copy of %s"%duplicate.name()))
        index = self.mCurrentLayerIndex + 1
        cmd = AddLayer(self, index, duplicate)
        cmd.setText(self.tr("Duplicate Layer"))
        self.mUndoStack.push(cmd)
        self.setCurrentLayerIndex(index)

    ##
    # Merges the currently selected layer with the layer below. This only works
    # when the layers can be merged.
    #
    # \see Layer.canMergeWith
    ##
    def mergeLayerDown(self):
        if (self.mCurrentLayerIndex < 1):
            return
        upperLayer = self.mMap.layerAt(self.mCurrentLayerIndex)
        lowerLayer = self.mMap.layerAt(self.mCurrentLayerIndex - 1)
        if (not lowerLayer.canMergeWith(upperLayer)):
            return
        merged = lowerLayer.mergedWith(upperLayer)
        self.mUndoStack.beginMacro(self.tr("Merge Layer Down"))
        self.mUndoStack.push(AddLayer(self, self.mCurrentLayerIndex - 1, merged))
        self.mUndoStack.push(RemoveLayer(self, self.mCurrentLayerIndex))
        self.mUndoStack.push(RemoveLayer(self, self.mCurrentLayerIndex))
        self.mUndoStack.endMacro()

    ##
    # Moves the given layer up. Does nothing when no valid layer index is
    # given.
    ##
    def moveLayerUp(self, index):
        if index<0 or index>=self.mMap.layerCount() - 1:
            return
        self.mUndoStack.push(MoveLayer(self, index, MoveLayer.Up))

    ##
    # Moves the given layer down. Does nothing when no valid layer index is
    # given.
    ##
    def moveLayerDown(self, index):
        if index<1 or index>=self.mMap.layerCount():
            return
        self.mUndoStack.push(MoveLayer(self, index, MoveLayer.Down))

    ##
    # Removes the given layer.
    ##
    def removeLayer(self, index):
        if index<0 or index>=self.mMap.layerCount():
            return
        self.mUndoStack.push(RemoveLayer(self, index))

    ##
    # Show or hide all other layers except the layer at the given index.
    # If any other layer is visible then all layers will be hidden, otherwise
    # the layers will be shown.
    ##
    def toggleOtherLayers(self, index):
        self.mLayerModel.toggleOtherLayers(index)

    ##
    # Adds a tileset to this map at the given \a index. Emits the appropriate
    # signal.
    ##
    def insertTileset(self, index, tileset):
        self.tilesetAboutToBeAdded.emit(index)
        self.mMap.insertTileset(index, tileset)
        tilesetManager = TilesetManager.instance()
        tilesetManager.addReference(tileset)
        self.tilesetAdded.emit(index, tileset)

    ##
    # Removes the tileset at the given \a index from this map. Emits the
    # appropriate signal.
    #
    # \warning Does not make sure that any references to tiles in the removed
    #          tileset are cleared.
    ##
    def removeTilesetAt(self, index):
        self.tilesetAboutToBeRemoved.emit(index)
        tileset = self.mMap.tilesets().at(index)
        if (tileset == self.mCurrentObject or isFromTileset(self.mCurrentObject, tileset)):
            self.setCurrentObject(None)
        self.mMap.removeTilesetAt(index)
        self.tilesetRemoved.emit(tileset)
        tilesetManager = TilesetManager.instance()
        tilesetManager.removeReference(tileset)

    def moveTileset(self, _from, to):
        if (_from == to):
            return
        tileset = self.mMap.tilesets().at(_from)
        self.mMap.removeTilesetAt(_from)
        self.mMap.insertTileset(to, tileset)
        self.tilesetMoved.emit(_from, to)

    def setTilesetFileName(self, tileset, fileName):
        tileset.setFileName(fileName)
        self.tilesetFileNameChanged.emit(tileset)

    def setTilesetName(self, tileset, name):
        tileset.setName(name)
        self.tilesetNameChanged.emit(tileset)

    def setTilesetTileOffset(self, tileset, tileOffset):
        tileset.setTileOffset(tileOffset)
        self.mMap.recomputeDrawMargins()
        self.tilesetTileOffsetChanged.emit(tileset)

    def duplicateObjects(self, objects):
        if (objects.isEmpty()):
            return
        self.mUndoStack.beginMacro(self.tr("Duplicate %n Object(s)", "", objects.size()))
        clones = QList()
        for mapObject in objects:
            clone = mapObject.clone()
            clones.append(clone)
            self.mUndoStack.push(AddMapObject(self,
                                              mapObject.objectGroup(),
                                              clone))

        self.mUndoStack.endMacro()
        self.setSelectedObjects(clones)

    def removeObjects(self, objects):
        if (objects.isEmpty()):
            return
        self.mUndoStack.beginMacro(self.tr("Remove %n Object(s)", "", objects.size()))
        for mapObject in objects:
            self.mUndoStack.push(RemoveMapObject(self, mapObject))
        self.mUndoStack.endMacro()

    def moveObjectsToGroup(self, objects, objectGroup):
        if (objects.isEmpty()):
            return
        self.mUndoStack.beginMacro(self.tr("Move %n Object(s) to Layer", "",
                                  objects.size()))
        for mapObject in objects:
            if (mapObject.objectGroup() == objectGroup):
                continue
            self.mUndoStack.push(MoveMapObjectToGroup(self,
                                                      mapObject,
                                                      objectGroup))

        self.mUndoStack.endMacro()

    def setProperty(self, object, name, value):
        hadProperty = object.hasProperty(name)
        object.setProperty(name, value)
        if (hadProperty):
            self.propertyChanged.emit(object, name)
        else:
            self.propertyAdded.emit(object, name)

    def setProperties(self, object, properties):
        object.setProperties(properties)
        self.propertiesChanged.emit(object)

    def removeProperty(self, object, name):
        object.removeProperty(name)
        self.propertyRemoved.emit(object, name)

    ##
    # Returns the layer model. Can be used to modify the layer stack of the
    # map, and to display the layer stack in a view.
    ##
    def layerModel(self):
        return self.mLayerModel

    def mapObjectModel(self):
        return self.mMapObjectModel

    def terrainModel(self):
        return self.mTerrainModel

    ##
    # Returns the map renderer.
    ##
    def renderer(self):
        return self.mRenderer

    ##
    # Creates the map renderer. Should be called after changing the map
    # orientation.
    ##
    def createRenderer(self):
        if (self.mRenderer):
            del self.mRenderer
        x = self.mMap.orientation()
        if x==Map.Orientation.Isometric:
            self.mRenderer = IsometricRenderer(self.mMap)
        elif x==Map.Orientation.Staggered:
            self.mRenderer = StaggeredRenderer(self.mMap)
        elif x==Map.Orientation.Hexagonal:
            self.mRenderer = HexagonalRenderer(self.mMap)
        else:
            self.mRenderer = OrthogonalRenderer(self.mMap)

    ##
    # Returns the undo stack of this map document. Should be used to push any
    # commands on that modify the map.
    ##
    def undoStack(self):
        return self.mUndoStack

    ##
    # Returns the selected area of tiles.
    ##
    def selectedArea(self):
        return QRegion(self.mSelectedArea)

    ##
    # Sets the selected area of tiles.
    ##
    def setSelectedArea(self, selection):
        if (self.mSelectedArea != selection):
            oldSelectedArea = self.mSelectedArea
            self.mSelectedArea = selection
            self.selectedAreaChanged.emit(self.mSelectedArea, oldSelectedArea)

    ##
    # Returns the list of selected objects.
    ##
    def selectedObjects(self):
        return self.mSelectedObjects

    ##
    # Sets the list of selected objects, emitting the selectedObjectsChanged
    # signal.
    ##
    def setSelectedObjects(self, selectedObjects):
        if selectedObjects.nequal(self.mSelectedObjects):
            self.mSelectedObjects = selectedObjects
            self.selectedObjectsChanged.emit()
            if (selectedObjects.size() == 1):
                self.setCurrentObject(selectedObjects.first())

    ##
    # Returns the list of selected tiles.
    ##
    def selectedTiles(self):
        return self.mSelectedTiles

    def setSelectedTiles(self, selectedTiles):
        self.mSelectedTiles = selectedTiles
        self.selectedTilesChanged.emit()

    def currentObject(self):
        return self.mCurrentObject

    def setCurrentObject(self, object):
        if (object == self.mCurrentObject):
            return
        self.mCurrentObject = object
        self.currentObjectChanged.emit([object])

    def currentObjects(self):
        objects = QList()
        if (self.mCurrentObject):
            if (self.mCurrentObject.typeId() == Object.MapObjectType and not self.mSelectedObjects.isEmpty()):
                for mapObj in self.mSelectedObjects:
                    objects.append(mapObj)
            elif (self.mCurrentObject.typeId() == Object.TileType and not self.mSelectedTiles.isEmpty()):
                for tile in self.mSelectedTiles:
                    objects.append(tile)

            else:
                objects.append(self.mCurrentObject)

        return objects

    def unifyTilesets(self, *args):
        l = len(args)
        if l==1:
            ##
            # Makes sure the all tilesets which are used at the given \a map will be
            # present in the map document.
            #
            # To reach the aim, all similar tilesets will be replaced by the version
            # in the current map document and all missing tilesets will be added to
            # the current map document.
            #
            # \warning This method assumes that the tilesets in \a map are managed by
            #          the TilesetManager!
            ##
            map = args[0]
            undoCommands = QList()
            existingTilesets = self.mMap.tilesets()
            tilesetManager = TilesetManager.instance()
            # Add tilesets that are not yet part of this map
            for tileset in map.tilesets():
                if (existingTilesets.contains(tileset)):
                    continue
                replacement = tileset.findSimilarTileset(existingTilesets)
                if (not replacement):
                    undoCommands.append(AddTileset(self, tileset))
                    continue

                # Merge the tile properties
                sharedTileCount = min(tileset.tileCount(), replacement.tileCount())
                for i in range(sharedTileCount):
                    replacementTile = replacement.tileAt(i)
                    properties = replacementTile.properties()
                    properties.merge(tileset.tileAt(i).properties())
                    undoCommands.append(ChangeProperties(self,
                                                             self.tr("Tile"),
                                                             replacementTile,
                                                             properties))

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

            if (not undoCommands.isEmpty()):
                self.mUndoStack.beginMacro(self.tr("Tileset Changes"))
                for command in undoCommands:
                    self.mUndoStack.push(command)
                self.mUndoStack.endMacro()
        elif l==2:
            map, missingTilesets = args
            
            existingTilesets = self.mMap.tilesets()
            tilesetManager = TilesetManager.instance()

            for tileset in map.tilesets():
                # tileset already added
                if existingTilesets.contains(tileset):
                    continue

                replacement = tileset.findSimilarTileset(existingTilesets)

                # tileset not present and no replacement tileset found
                if not replacement:
                    if not missingTilesets.contains(tileset):
                        missingTilesets.append(tileset)
                    continue

                # replacement tileset found, change given map
                map.replaceTileset(tileset, replacement)

                tilesetManager.addReference(replacement)
                tilesetManager.removeReference(tileset)

    ##
    # Emits the map changed signal. This signal should be emitted after changing
    # the map size or its tile size.
    ##
    def emitMapChanged(self):
        self.mapChanged.emit()

    ##
    # Emits the region changed signal for the specified region. The region
    # should be in tile coordinates. This method is used by the TilePainter.
    ##
    def emitRegionChanged(self, region, layer):
        self.regionChanged.emit(region, layer)

    ##
    # Emits the region edited signal for the specified region and tile layer.
    # The region should be in tile coordinates. This should be called from
    # all map document changing classes which are triggered by user input.
    ##
    def emitRegionEdited(self, region, layer):
        self.regionEdited.emit(region, layer)

    def emitTileLayerDrawMarginsChanged(self, layer):
        self.tileLayerDrawMarginsChanged.emit(layer)

    ##
    # Emits the tileset changed signal. This signal is currently used when adding
    # or removing tiles from a tileset.
    #
    # @todo Emit more specific signals.
    ##
    def emitTilesetChanged(self, tileset):
        self.tilesetChanged.emit(tileset)

    ##
    # Emits the signal notifying about the terrain probability of a tile changing.
    ##
    def emitTileProbabilityChanged(self, tile):
        self.tileProbabilityChanged.emit(tile)

    ##
    # Emits the signal notifying tileset models about changes to tile terrain
    # information. All the \a tiles need to be from the same tileset.
    ##
    def emitTileTerrainChanged(self, tiles):
        if (not tiles.isEmpty()):
            self.tileTerrainChanged.emit(tiles)

    ##
    # Emits the signal notifying the TileCollisionEditor about the object group
    # of a tile changing.
    ##
    def emitTileObjectGroupChanged(self, tile):
        self.tileObjectGroupChanged.emit(tile)

    ##
    # Emits the signal notifying about the animation of a tile changing.
    ##
    def emitTileAnimationChanged(self, tile):
        self.tileAnimationChanged.emit(tile)

    ##
    # Emits the objectGroupChanged signal, should be called when changing the
    # color or drawing order of an object group.
    ##
    def emitObjectGroupChanged(self, objectGroup):
        self.objectGroupChanged.emit(objectGroup)

    ##
    # Emits the imageLayerChanged signal, should be called when changing the
    # image or the transparent color of an image layer.
    ##
    def emitImageLayerChanged(self, imageLayer):
        self.imageLayerChanged.emit(imageLayer)

    ##
    # Emits the editLayerNameRequested signal, to get renamed.
    ##
    def emitEditLayerNameRequested(self):
        self.editLayerNameRequested.emit()

    ##
    # Emits the editCurrentObject signal, which makes the Properties window become
    # visible and take focus.
    ##
    def emitEditCurrentObject(self):
        self.editCurrentObject.emit()

    ##
    # Before forwarding the signal, the objects are removed from the list of
    # selected objects, triggering a selectedObjectsChanged signal when
    # appropriate.
    ##
    def onObjectsRemoved(self, objects):
        self.deselectObjects(objects)
        self.objectsRemoved.emit(objects)

    def onMapObjectModelRowsInserted(self, parent, first, last):
        objectGroup = self.mMapObjectModel.toObjectGroup(parent)
        if (not objectGroup): # we're not dealing with insertion of objects
            return
        self.objectsInserted.emit(objectGroup, first, last)
        self.onMapObjectModelRowsInsertedOrRemoved(parent, first, last)

    def onMapObjectModelRowsInsertedOrRemoved(self, parent, first, last):
        objectGroup = self.mMapObjectModel.toObjectGroup(parent)
        if (not objectGroup):
            return
        # Inserting or removing objects changes the index of any that come after
        lastIndex = objectGroup.objectCount() - 1
        if (last < lastIndex):
            self.objectsIndexChanged.emit(objectGroup, last + 1, lastIndex)

    def onObjectsMoved(self, parent, start, end, destination, row):
        if (parent != destination):
            return
        objectGroup = self.mMapObjectModel.toObjectGroup(parent)
        # Determine the full range over which object indexes changed
        first = min(start, row)
        last = max(end, row - 1)
        self.objectsIndexChanged.emit(objectGroup, first, last)

    def onLayerAdded(self, index):
        self.layerAdded.emit(index)
        # Select the first layer that gets added to the map
        if (self.mMap.layerCount() == 1):
            self.setCurrentLayerIndex(0)

    def onLayerAboutToBeRemoved(self, index):
        layer = self.mMap.layerAt(index)
        if (layer == self.mCurrentObject):
            self.setCurrentObject(None)
        # Deselect any objects on this layer when necessary
        og = layer
        if type(og) == ObjectGroup:
            self.deselectObjects(og.objects())
        self.layerAboutToBeRemoved.emit(index)

    def onLayerRemoved(self, index):
        # Bring the current layer index to safety
        currentLayerRemoved = self.mCurrentLayerIndex == self.mMap.layerCount()
        if (currentLayerRemoved):
            self.mCurrentLayerIndex = self.mCurrentLayerIndex - 1
        self.layerRemoved.emit(index)
        # Emitted after the layerRemoved signal so that the MapScene has a chance
        # of synchronizing before adapting to the newly selected index
        if (currentLayerRemoved):
            self.currentLayerIndexChanged.emit(self.mCurrentLayerIndex)

    def onTerrainRemoved(self, terrain):
        if (terrain == self.mCurrentObject):
            self.setCurrentObject(None)

    def setFileName(self, fileName):
        if (self.mFileName == fileName):
            return
        oldFileName = self.mFileName
        self.mFileName = fileName
        self.fileNameChanged.emit(fileName, oldFileName)

    def deselectObjects(self, objects):
        # Unset the current object when it was part of this list of objects
        if (self.mCurrentObject and self.mCurrentObject.typeId() == Object.MapObjectType):
            if (objects.contains(self.mCurrentObject)):
                self.setCurrentObject(None)
        removedCount = 0
        for object in objects:
            removedCount += self.mSelectedObjects.removeAll(object)
        if (removedCount > 0):
            self.selectedObjectsChanged.emit()

    def disconnect(self):
        try:
            super().disconnect()
        except:
            pass