Beispiel #1
0
void LayerModel::toggleOtherLayers(int layerIndex)
{
    if (mMap->layerCount() <= 1) // No other layers
        return;

    bool visibility = true;
    for (int i = 0; i < mMap->layerCount(); i++) {
        if (i == layerIndex)
            continue;

        Layer *layer = mMap->layerAt(i);
        if (layer->isVisible()) {
            visibility = false;
            break;
        }
    }

    QUndoStack *undoStack = mMapDocument->undoStack();
    if (visibility)
        undoStack->beginMacro(tr("Show Other Layers"));
    else
        undoStack->beginMacro(tr("Hide Other Layers"));

    for (int i = 0; i < mMap->layerCount(); i++) {
        if (i == layerIndex)
            continue;

        if (visibility != mMap->layerAt(i)->isVisible())
            undoStack->push(new SetLayerVisible(mMapDocument, i, visibility));
    }

    undoStack->endMacro();
}
Beispiel #2
0
void ButtonTaskMenu::addToGroup(QAction *a)
{
    QButtonGroup *bg = qvariant_cast<QButtonGroup *>(a->data());
    Q_ASSERT(bg);

    QDesignerFormWindowInterface *fw = formWindow();
    const ButtonList bl = buttonList(fw->cursor());
    // Do we need to remove the buttons from an existing group?
    QUndoCommand *removeCmd = 0;
    if (bl.front()->group()) {
        removeCmd = createRemoveButtonsCommand(fw, bl);
        if (!removeCmd)
            return;
    }
    AddButtonsToGroupCommand *addCmd = new AddButtonsToGroupCommand(fw);
    addCmd->init(bl, bg);

    QUndoStack *history = fw->commandHistory();
    if (removeCmd) {
        history->beginMacro(addCmd->text());
        history->push(removeCmd);
        history->push(addCmd);
        history->endMacro();
    } else {
        history->push(addCmd);
    }
}
void MapDocumentActionHandler::delete_()
{
    if (!mMapDocument)
        return;

    Layer *currentLayer = mMapDocument->currentLayer();
    if (!currentLayer)
        return;

    TileLayer *tileLayer = dynamic_cast<TileLayer*>(currentLayer);
    const QRegion &selectedArea = mMapDocument->selectedArea();
    const QList<MapObject*> &selectedObjects = mMapDocument->selectedObjects();

    QUndoStack *undoStack = mMapDocument->undoStack();
    undoStack->beginMacro(tr("Delete"));

    if (tileLayer && !selectedArea.isEmpty()) {
        undoStack->push(new EraseTiles(mMapDocument, tileLayer, selectedArea));
    } else if (!selectedObjects.isEmpty()) {
        foreach (MapObject *mapObject, selectedObjects)
            undoStack->push(new RemoveMapObject(mMapDocument, mapObject));
    }

    selectNone();
    undoStack->endMacro();
}
Beispiel #4
0
void PropertiesDock::removeProperties()
{
    Object *object = mDocument->currentObject();
    if (!object)
        return;

    const QList<QtBrowserItem*> items = mPropertyBrowser->selectedItems();
    if (items.isEmpty() || !mPropertyBrowser->allCustomPropertyItems(items))
        return;

    QStringList propertyNames;
    for (QtBrowserItem *item : items)
        propertyNames.append(item->property()->propertyName());

    QUndoStack *undoStack = mDocument->undoStack();
    undoStack->beginMacro(tr("Remove Property/Properties", nullptr,
                             propertyNames.size()));

    for (const QString &name : propertyNames) {
        undoStack->push(new RemoveProperty(mDocument,
                                           mDocument->currentObjects(),
                                           name));
    }

    undoStack->endMacro();
}
void MapPropertiesDialog::accept()
{
    int format = mLayerDataCombo->currentIndex();
    if (format == -1) {
        // this shouldn't happen!
        format = 0;
    }

    Map::LayerDataFormat newLayerDataFormat = static_cast<Map::LayerDataFormat>(format - 1);

    QUndoStack *undoStack = mMapDocument->undoStack();

    QColor newColor = mColorButton->color();
    if (newColor == Qt::darkGray)
        newColor = QColor();

    const Map *map = mMapDocument->map();
    bool localChanges = newColor != map->backgroundColor() ||
            newLayerDataFormat != map->layerDataFormat();

    if (localChanges) {
        undoStack->beginMacro(QCoreApplication::translate(
            "Undo Commands",
            "Change Map Properties"));

        undoStack->push(new ChangeMapProperties(mMapDocument,
                                                newColor,
                                                newLayerDataFormat));
    }

    PropertiesDialog::accept();

    if (localChanges)
        undoStack->endMacro();
}
Beispiel #6
0
void AddShape::performAction(Block* block) const
{
	Node* node = static_cast<Node*>(block);

	GenerateShape dialog(_generator, PantinEngine::MainWindow());

	if(dialog.createShape())
	{
		K_ASSERT( node->instance()->fastInherits<Model>() ); // The instance HAS to be a model in Nigel. Only models can contain geometry.

		Model* model = static_cast<Model*>(node->instance());

		QUndoStack* undoStack = PantinEngine::InstanceUndoStack( model );
		undoStack->setActive(true);

		undoStack->beginMacro(
				tr("Adding new %1 to node %2")
				.arg(_generator->shape()->blockName(), node->blockName())
			);

		AddBlock* addGeometry = new AddBlock(model->geometries(), dialog.geometry());
		undoStack->push(addGeometry);

		AddBlock* addReference = new AddBlock(node, dialog.geometryInstance());
		undoStack->push(addReference);

		undoStack->endMacro();
	}
}
Beispiel #7
0
void EditPolygonTool::splitSegments()
{
    if (mSelectedHandles.size() < 2)
        return;

    const PointIndexesByObject p = groupIndexesByObject(mSelectedHandles);
    QMapIterator<MapObject*, RangeSet<int> > i(p);

    QUndoStack *undoStack = mapDocument()->undoStack();
    bool macroStarted = false;

    while (i.hasNext()) {
        MapObject *object = i.next().key();
        const RangeSet<int> &indexRanges = i.value();

        const bool closed = object->shape() == MapObject::Polygon;
        QPolygonF oldPolygon = object->polygon();
        QPolygonF newPolygon = splitPolygonSegments(oldPolygon, indexRanges,
                                                    closed);

        if (newPolygon.size() > oldPolygon.size()) {
            if (!macroStarted) {
                undoStack->beginMacro(tr("Split Segments"));
                macroStarted = true;
            }

            undoStack->push(new ChangePolygon(mapDocument(), object,
                                              newPolygon,
                                              oldPolygon));
        }
    }

    if (macroStarted)
        undoStack->endMacro();
}
Beispiel #8
0
void ButtonTaskMenu::createGroup()
{
    QDesignerFormWindowInterface *fw = formWindow();
    const ButtonList bl = buttonList(fw->cursor());
    // Do we need to remove the buttons from an existing group?
    QUndoCommand *removeCmd = 0;
    if (bl.front()->group()) {
        removeCmd = createRemoveButtonsCommand(fw, bl);
        if (!removeCmd)
            return;
    }
    // Add cmd
    CreateButtonGroupCommand *addCmd = new CreateButtonGroupCommand(fw);
    if (!addCmd->init(bl)) {
        qWarning("** WARNING Failed to initialize CreateButtonGroupCommand!");
        delete addCmd;
        return;
    }
    // Need a macro [even if we only have the add command] since the command might trigger additional commands
    QUndoStack *history = fw->commandHistory();
    history->beginMacro(addCmd->text());
    if (removeCmd)
        history->push(removeCmd);
    history->push(addCmd);
    history->endMacro();
}
void ImageLayerPropertiesDialog::accept()
{
    QUndoStack *undoStack = mMapDocument->undoStack();

    const QColor newColor = mColorButton->color() != Qt::gray
        ? mColorButton->color()
        : QColor();

    const QString newPath = mImage->text();
    const bool localChanges = newColor != mImageLayer->transparentColor() ||
            newPath != mImageLayer->imageSource();

    if (localChanges) {
        undoStack->beginMacro(QCoreApplication::translate(
            "Undo Commands",
            "Change Image Layer Properties"));

        undoStack->push(new ChangeImageLayerProperties(
                            mMapDocument,
                            mImageLayer,
                            newColor,
                            newPath));
    }

    PropertiesDialog::accept();

    if (localChanges)
        undoStack->endMacro();
}
Beispiel #10
0
void PropertiesDock::pasteProperties()
{
    auto clipboardManager = ClipboardManager::instance();

    Properties pastedProperties = clipboardManager->properties();
    if (pastedProperties.isEmpty())
        return;

    const QList<Object *> objects = mDocument->currentObjects();
    if (objects.isEmpty())
        return;

    QList<QUndoCommand*> commands;

    for (Object *object : objects) {
        Properties properties = object->properties();
        properties.merge(pastedProperties);

        if (object->properties() != properties) {
            commands.append(new ChangeProperties(mDocument, QString(), object,
                                                 properties));
        }
    }

    if (!commands.isEmpty()) {
        QUndoStack *undoStack = mDocument->undoStack();
        undoStack->beginMacro(tr("Paste Property/Properties", nullptr,
                                 pastedProperties.size()));

        for (QUndoCommand *command : commands)
            undoStack->push(command);

        undoStack->endMacro();
    }
}
Beispiel #11
0
/**
 * Convenience method that deals with some of the logic related to pasting
 * a group of objects.
 */
void ClipboardManager::pasteObjectGroup(const ObjectGroup *objectGroup,
                                        MapDocument *mapDocument,
                                        const MapView *view,
                                        PasteFlags flags)
{
    Layer *currentLayer = mapDocument->currentLayer();
    if (!currentLayer)
        return;

    ObjectGroup *currentObjectGroup = currentLayer->asObjectGroup();
    if (!currentObjectGroup)
        return;

    QPointF insertPos;

    if (!(flags & PasteInPlace)) {
        // Determine where to insert the objects
        const MapRenderer *renderer = mapDocument->renderer();
        const QPointF center = objectGroup->objectsBoundingRect().center();

        // Take the mouse position if the mouse is on the view, otherwise
        // take the center of the view.
        QPoint viewPos;
        if (view->underMouse())
            viewPos = view->mapFromGlobal(QCursor::pos());
        else
            viewPos = QPoint(view->width() / 2, view->height() / 2);

        const QPointF scenePos = view->mapToScene(viewPos);

        insertPos = renderer->screenToPixelCoords(scenePos) - center;
        SnapHelper(renderer).snap(insertPos);
    }

    QUndoStack *undoStack = mapDocument->undoStack();
    QList<MapObject*> pastedObjects;
    pastedObjects.reserve(objectGroup->objectCount());

    undoStack->beginMacro(tr("Paste Objects"));
    for (const MapObject *mapObject : objectGroup->objects()) {
        if (flags & PasteNoTileObjects && !mapObject->cell().isEmpty())
            continue;

        MapObject *objectClone = mapObject->clone();
        objectClone->resetId();
        objectClone->setPosition(objectClone->position() + insertPos);
        pastedObjects.append(objectClone);
        undoStack->push(new AddMapObject(mapDocument,
                                         currentObjectGroup,
                                         objectClone));
    }
    undoStack->endMacro();

    mapDocument->setSelectedObjects(pastedObjects);
}
Beispiel #12
0
void TilesetDock::removeTiles()
{
    TilesetView *view = currentTilesetView();
    if (!view)
        return;
    if (!view->selectionModel()->hasSelection())
        return;

    const QModelIndexList indexes = view->selectionModel()->selectedIndexes();
    const TilesetModel *model = view->tilesetModel();
    QList<Tile*> tiles;

    for (const QModelIndex &index : indexes)
        if (Tile *tile = model->tileAt(index))
            tiles.append(tile);

    auto matchesAnyTile = [&tiles] (const Cell &cell) {
        if (Tile *tile = cell.tile)
            return tiles.contains(tile);
        return false;
    };
    const bool inUse = hasTileReferences(mMapDocument, matchesAnyTile);

    // If the tileset is in use, warn the user and confirm removal
    if (inUse) {
        QMessageBox warning(QMessageBox::Warning,
                            tr("Remove Tiles"),
                            tr("One or more of the tiles to be removed are "
                               "still in use by the map!"),
                            QMessageBox::Yes | QMessageBox::No,
                            this);
        warning.setDefaultButton(QMessageBox::Yes);
        warning.setInformativeText(tr("Remove all references to these tiles?"));

        if (warning.exec() != QMessageBox::Yes)
            return;
    }

    QUndoStack *undoStack = mMapDocument->undoStack();
    undoStack->beginMacro(tr("Remove Tiles"));

    if (inUse)
        removeTileReferences(mMapDocument, matchesAnyTile);

    Tileset *tileset = view->tilesetModel()->tileset();
    undoStack->push(new RemoveTiles(mMapDocument, tileset, tiles));

    undoStack->endMacro();

    // Clear the current tiles, will be referencing the removed tiles
    setCurrentTiles(nullptr);
    setCurrentTile(nullptr);
}
Beispiel #13
0
void RaiseLowerHelper::push(const QList<QUndoCommand*> &commands,
                            const QString &text)
{
    if (commands.isEmpty())
        return;

    QUndoStack *undoStack = mMapDocument->undoStack();
    undoStack->beginMacro(text);
    foreach (QUndoCommand *command, commands)
        undoStack->push(command);
    undoStack->endMacro();
}
Beispiel #14
0
void ButtonGroupMenu::breakGroup()
{
    BreakButtonGroupCommand *cmd = new BreakButtonGroupCommand(m_formWindow);
    if (cmd->init(m_buttonGroup)) {
        // Need a macro since the command might trigger additional commands
        QUndoStack *history = m_formWindow->commandHistory();
        history->beginMacro(cmd->text());
        history->push(cmd);
        history->endMacro();
    } else {
        qWarning("** WARNING Failed to initialize BreakButtonGroupCommand!");
        delete cmd;
    }
}
Beispiel #15
0
void TileCollisionDock::delete_(Operation operation)
{
    if (!mDummyMapDocument)
        return;

    const QList<MapObject*> selectedObjects = mDummyMapDocument->selectedObjects();
    if (selectedObjects.isEmpty())
        return;

    QUndoStack *undoStack = mDummyMapDocument->undoStack();
    undoStack->beginMacro(operation == Delete ? tr("Delete") : tr("Cut"));

    for (MapObject *mapObject : selectedObjects)
        undoStack->push(new RemoveMapObject(mDummyMapDocument, mapObject));

    undoStack->endMacro();
}
Beispiel #16
0
/**
 * Removes the tileset at the given index. Prompting the user when the tileset
 * is in use by the map.
 */
void TilesetDock::removeTileset(int index)
{
    auto &sharedTileset = mTilesets.at(index);

    int mapTilesetIndex = mMapDocument->map()->tilesets().indexOf(sharedTileset);
    if (mapTilesetIndex == -1)
        return;

    Tileset *tileset = sharedTileset.data();
    const bool inUse = mMapDocument->map()->isTilesetUsed(tileset);

    // If the tileset is in use, warn the user and confirm removal
    if (inUse) {
        QMessageBox warning(QMessageBox::Warning,
                            tr("Remove Tileset"),
                            tr("The tileset \"%1\" is still in use by the "
                               "map!").arg(tileset->name()),
                            QMessageBox::Yes | QMessageBox::No,
                            this);
        warning.setDefaultButton(QMessageBox::Yes);
        warning.setInformativeText(tr("Remove this tileset and all references "
                                      "to the tiles in this tileset?"));

        if (warning.exec() != QMessageBox::Yes)
            return;
    }

    QUndoCommand *remove = new RemoveTileset(mMapDocument, mapTilesetIndex);
    QUndoStack *undoStack = mMapDocument->undoStack();

    if (inUse) {
        // Remove references to tiles in this tileset from the current map
        auto referencesTileset = [tileset] (const Cell &cell) {
            return cell.tileset() == tileset;
        };
        undoStack->beginMacro(remove->text());
        removeTileReferences(mMapDocument, referencesTileset);
    }
    undoStack->push(remove);
    if (inUse)
        undoStack->endMacro();
}
Beispiel #17
0
void TilesetEditor::removeTerrainType()
{
    Terrain *terrain = mTerrainDock->currentTerrain();
    if (!terrain)
        return;

    RemoveTerrain *removeTerrain = new RemoveTerrain(mCurrentTilesetDocument,
                                                     terrain);

    /*
     * Clear any references to the terrain that is about to be removed with
     * an undo command, as a way of preserving them when undoing the removal
     * of the terrain.
     */
    ChangeTileTerrain::Changes changes;

    for (Tile *tile : terrain->tileset()->tiles()) {
        unsigned tileTerrain = tile->terrain();

        for (int corner = 0; corner < 4; ++corner) {
            if (tile->cornerTerrainId(corner) == terrain->id())
                tileTerrain = setTerrainCorner(tileTerrain, corner, 0xFF);
        }

        if (tileTerrain != tile->terrain()) {
            changes.insert(tile, ChangeTileTerrain::Change(tile->terrain(),
                                                           tileTerrain));
        }
    }

    QUndoStack *undoStack = mCurrentTilesetDocument->undoStack();

    if (!changes.isEmpty()) {
        undoStack->beginMacro(removeTerrain->text());
        undoStack->push(new ChangeTileTerrain(mCurrentTilesetDocument, changes));
    }

    mCurrentTilesetDocument->undoStack()->push(removeTerrain);

    if (!changes.isEmpty())
        undoStack->endMacro();
}
Beispiel #18
0
void EditPolygonTool::deleteNodes()
{
    if (mSelectedHandles.isEmpty())
        return;

    PointIndexesByObject p = groupIndexesByObject(mSelectedHandles);
    QMapIterator<MapObject*, RangeSet<int> > i(p);

    QUndoStack *undoStack = mapDocument()->undoStack();

    QString delText = tr("Delete %n Node(s)", "", mSelectedHandles.size());
    undoStack->beginMacro(delText);

    while (i.hasNext()) {
        MapObject *object = i.next().key();
        const RangeSet<int> &indexRanges = i.value();

        QPolygonF oldPolygon = object->polygon();
        QPolygonF newPolygon = oldPolygon;

        // Remove points, back to front to keep the indexes valid
        RangeSet<int>::Range it = indexRanges.end();
        RangeSet<int>::Range begin = indexRanges.begin();
        // assert: end != begin, since there is at least one entry
        do {
            --it;
            newPolygon.remove(it.first(), it.length());
        } while (it != begin);

        if (newPolygon.size() < 2) {
            // We've removed the entire object
            undoStack->push(new RemoveMapObject(mapDocument(), object));
        } else {
            undoStack->push(new ChangePolygon(mapDocument(), object,
                                              newPolygon,
                                              oldPolygon));
        }
    }

    undoStack->endMacro();
}
Beispiel #19
0
void MapPropertiesDialog::accept()
{
    QUndoStack *undoStack = mMapDocument->undoStack();

    const QColor newColor = mColorButton->color() != Qt::gray ? mColorButton->color() : QColor();

    const bool localChanges = newColor != mMapDocument->map()->backgroundColor();

    if (localChanges) {
        undoStack->beginMacro(QCoreApplication::translate(
            "Undo Commands",
            "Change Map Properties"));

        undoStack->push(new ChangeMapProperties(mMapDocument, mColorButton->color()));
    }

    PropertiesDialog::accept();

    if (localChanges)
        undoStack->endMacro();
}
void ObjectPropertiesDialog::accept()
{
    const QString newName = mUi->name->text();
    const QString newType = mUi->type->text();

    const qreal newPosX = mUi->x->value();
    const qreal newPosY = mUi->y->value();
    const qreal newWidth = mUi->width->value();
    const qreal newHeight = mUi->height->value();

    bool changed = false;
    changed |= mMapObject->name() != newName;
    changed |= mMapObject->type() != newType;
    changed |= mMapObject->x() != newPosX;
    changed |= mMapObject->y() != newPosY;
    changed |= mMapObject->width() != newWidth;
    changed |= mMapObject->height() != newHeight;

    if (changed) {
        QUndoStack *undo = mMapDocument->undoStack();
        undo->beginMacro(tr("Change Object"));
        undo->push(new ChangeMapObject(mMapDocument, mMapObject,
                                       newName, newType));

        const QPointF oldPos = mMapObject->position();
        mMapObject->setX(newPosX);
        mMapObject->setY(newPosY);
        undo->push(new MoveMapObject(mMapDocument, mMapObject, oldPos));

        const QSizeF oldSize = mMapObject->size();
        mMapObject->setWidth(newWidth);
        mMapObject->setHeight(newHeight);
        undo->push(new ResizeMapObject(mMapDocument, mMapObject, oldSize));

        PropertiesDialog::accept(); // Let PropertiesDialog add its command
        undo->endMacro();
    } else {
        PropertiesDialog::accept();
    }
}
Beispiel #21
0
static void removeTileReferences(MapDocument *mapDocument,
                                 std::function<bool(const Cell &)> condition)
{
    QUndoStack *undoStack = mapDocument->undoStack();
    undoStack->beginMacro(QCoreApplication::translate("Undo Commands", "Remove Tiles"));

    for (Layer *layer : mapDocument->map()->layers()) {
        if (TileLayer *tileLayer = layer->asTileLayer()) {
            const QRegion refs = tileLayer->region(condition);
            if (!refs.isEmpty())
                undoStack->push(new EraseTiles(mapDocument, tileLayer, refs));

        } else if (ObjectGroup *objectGroup = layer->asObjectGroup()) {
            for (MapObject *object : *objectGroup) {
                if (condition(object->cell()))
                    undoStack->push(new RemoveMapObject(mapDocument, object));
            }
        }
    }

    undoStack->endMacro();
}
Beispiel #22
0
void AbstractObjectTool::resetTileSize()
{
    QList<QUndoCommand*> commands;

    for (auto mapObject : mapDocument()->selectedObjects()) {
        if (!isResizedTileObject(mapObject))
            continue;

        commands << new ResizeMapObject(mapDocument(),
                                        mapObject,
                                        mapObject->cell().tile()->size(),
                                        mapObject->size());
    }

    if (!commands.isEmpty()) {
        QUndoStack *undoStack = mapDocument()->undoStack();
        undoStack->beginMacro(tr("Reset Tile Size"));
        for (auto command : commands)
            undoStack->push(command);
        undoStack->endMacro();
    }
}
Beispiel #23
0
void EditPolygonTool::finishMoving(const QPointF &pos)
{
    Q_ASSERT(mMode == Moving);
    mMode = NoMode;

    if (mStart == pos || mOldPolygons.isEmpty()) // Move is a no-op
        return;

    QUndoStack *undoStack = mapDocument()->undoStack();
    undoStack->beginMacro(tr("Move %n Point(s)", "", mSelectedHandles.size()));

    // TODO: This isn't really optimal. Would be better to have a single undo
    // command that supports changing multiple map objects.
    QMapIterator<MapObject*, QPolygonF> i(mOldPolygons);
    while (i.hasNext()) {
        i.next();
        undoStack->push(new ChangePolygon(mapDocument(), i.key(), i.value()));
    }

    undoStack->endMacro();

    mOldHandlePositions.clear();
    mOldPolygons.clear();
}
Beispiel #24
0
void PropertiesDock::showContextMenu(const QPoint& pos)
{
    const Object *object = mDocument->currentObject();
    if (!object)
        return;

    const QList<QtBrowserItem *> items = mPropertyBrowser->selectedItems();
    const bool customPropertiesSelected = !items.isEmpty() && mPropertyBrowser->allCustomPropertyItems(items);

    bool currentObjectHasAllProperties = true;
    QStringList propertyNames;
    for (QtBrowserItem *item : items) {
        const QString propertyName = item->property()->propertyName();
        propertyNames.append(propertyName);

        if (!object->hasProperty(propertyName))
            currentObjectHasAllProperties = false;
    }

    QMenu contextMenu(mPropertyBrowser);
    QAction *cutAction = contextMenu.addAction(tr("Cu&t"));
    QAction *copyAction = contextMenu.addAction(tr("&Copy"));
    QAction *pasteAction = contextMenu.addAction(tr("&Paste"));
    contextMenu.addSeparator();
    QMenu *convertMenu = contextMenu.addMenu(tr("Convert To"));
    QAction *renameAction = contextMenu.addAction(tr("Rename..."));
    QAction *removeAction = contextMenu.addAction(tr("Remove"));

    cutAction->setShortcuts(QKeySequence::Cut);
    cutAction->setIcon(QIcon(QLatin1String(":/images/16x16/edit-cut.png")));
    cutAction->setEnabled(customPropertiesSelected && currentObjectHasAllProperties);
    copyAction->setShortcuts(QKeySequence::Copy);
    copyAction->setIcon(QIcon(QLatin1String(":/images/16x16/edit-copy.png")));
    copyAction->setEnabled(customPropertiesSelected && currentObjectHasAllProperties);
    pasteAction->setShortcuts(QKeySequence::Paste);
    pasteAction->setIcon(QIcon(QLatin1String(":/images/16x16/edit-paste.png")));
    pasteAction->setEnabled(ClipboardManager::instance()->hasProperties());
    renameAction->setEnabled(mActionRenameProperty->isEnabled());
    renameAction->setIcon(mActionRenameProperty->icon());
    removeAction->setEnabled(mActionRemoveProperty->isEnabled());
    removeAction->setShortcuts(mActionRemoveProperty->shortcuts());
    removeAction->setIcon(mActionRemoveProperty->icon());

    Utils::setThemeIcon(cutAction, "edit-cut");
    Utils::setThemeIcon(copyAction, "edit-copy");
    Utils::setThemeIcon(pasteAction, "edit-paste");
    Utils::setThemeIcon(removeAction, "remove");


    if (customPropertiesSelected) {
        const int convertTo[] = {
            QVariant::Bool,
            QVariant::Color,
            QVariant::Double,
            filePathTypeId(),
            QVariant::Int,
            QVariant::String
        };

        for (int toType : convertTo) {
            bool someDifferentType = false;
            bool allCanConvert = true;

            for (const QString &propertyName : propertyNames) {
                QVariant propertyValue = object->property(propertyName);

                if (propertyValue.userType() != toType)
                    someDifferentType = true;

                if (!propertyValue.convert(toType)) {
                    allCanConvert = false;
                    break;
                }
            }

            if (someDifferentType && allCanConvert) {
                QAction *action = convertMenu->addAction(typeToName(toType));
                action->setData(toType);
            }
        }
    }

    convertMenu->setEnabled(!convertMenu->actions().isEmpty());

    connect(cutAction, &QAction::triggered, this, &PropertiesDock::cutProperties);
    connect(copyAction, &QAction::triggered, this, &PropertiesDock::copyProperties);
    connect(pasteAction, &QAction::triggered, this, &PropertiesDock::pasteProperties);
    connect(renameAction, &QAction::triggered, this, &PropertiesDock::renameProperty);
    connect(removeAction, &QAction::triggered, this, &PropertiesDock::removeProperties);

    const QPoint globalPos = mPropertyBrowser->mapToGlobal(pos);
    const QAction *selectedItem = contextMenu.exec(globalPos);

    if (selectedItem && selectedItem->parentWidget() == convertMenu) {
        QUndoStack *undoStack = mDocument->undoStack();
        undoStack->beginMacro(tr("Convert Property/Properties", nullptr, items.size()));

        for (const QString &propertyName : propertyNames) {
            QVariant propertyValue = object->property(propertyName);

            int toType = selectedItem->data().toInt();
            propertyValue.convert(toType);

            undoStack->push(new SetProperty(mDocument,
                                            mDocument->currentObjects(),
                                            propertyName, propertyValue));
        }

        undoStack->endMacro();
    }
}
void AbstractAspect::endMacro()
{
	QUndoStack *stack = undoStack();
	if (stack)
		stack->endMacro();
}