Git commit 8e68940e2cc526b87594ba6a0573e72c645672bc by Sebastian Voecking. Committed on 08/05/2011 at 13:57. Pushed by voecking into branch 'master'.
Added copy-and-paste support FEATURE: 183919 GUI: M +1 -0 step/CMakeLists.txt A +195 -0 step/clipboard.cc [License: GPL (v2+)] A +56 -0 step/clipboard.h [License: GPL (v2+)] M +17 -0 step/mainwindow.cc M +3 -0 step/mainwindow.h M +4 -8 step/worldgraphics.cc M +0 -3 step/worldgraphics.h M +73 -0 step/worldmodel.cc M +9 -0 step/worldmodel.h http://commits.kde.org/step/8e68940e2cc526b87594ba6a0573e72c645672bc diff --git a/step/CMakeLists.txt b/step/CMakeLists.txt index 4d571ea..03d9f75 100644 --- a/step/CMakeLists.txt +++ b/step/CMakeLists.txt @@ -1,5 +1,6 @@ set(step_SRCS arrow.cc + clipboard.cc mainwindow.cc worldmodel.cc worldscene.cc diff --git a/step/clipboard.cc b/step/clipboard.cc new file mode 100644 index 0000000..19aa48c --- /dev/null +++ b/step/clipboard.cc @@ -0,0 +1,195 @@ +/* This file is part of Step. + * Copyright (C) 2007 Vladimir Kuznetsov <ks.vladimir at gmail.com> + * + * Step is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Step is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Step; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "clipboard.h" + +#include <QApplication> +#include <QBuffer> +#include <QClipboard> +#include <QMimeData> + +#include <KDebug> + +#include <stepcore/factory.h> +#include <stepcore/world.h> +#include <stepcore/xmlfile.h> + +namespace +{ +class CopyHelper +{ +public: + void addItem(const StepCore::Item* item); + StepCore::World* createWorld(); + +private: + void fillMap(const StepCore::Object* item, StepCore::Object* copy); + void fixItemLinks(StepCore::Item* item); + + QHash<const StepCore::Object*, StepCore::Object*> _copyMap; + QList<StepCore::Item*> _items; +}; + +void CopyHelper::addItem(const StepCore::Item* item) +{ + StepCore::Object *copy = item->metaObject()->cloneObject(*item); + + _items << static_cast<StepCore::Item*>(copy); + fillMap(item, copy); +} + +StepCore::World* CopyHelper::createWorld() +{ + StepCore::World *world = new StepCore::World; + + foreach (StepCore::Item* item, _items) { + world->addItem(item); + } + + StepCore::ItemList items; + world->allItems(&items); + foreach (StepCore::Item* item, items) { + fixItemLinks(item); + } + + _items.clear(); + _copyMap.clear(); + + return world; +} + +void CopyHelper::fillMap(const StepCore::Object* item, StepCore::Object* copy) +{ + _copyMap.insert(item, copy); + + if (item->metaObject()->inherits<StepCore::ItemGroup>()) { + const StepCore::ItemGroup* group = + static_cast<const StepCore::ItemGroup*>(item); + StepCore::ItemGroup* copiedGroup = + static_cast<StepCore::ItemGroup*>(copy); + + StepCore::ItemList items; + group->allItems(&items); + StepCore::ItemList copiedItems; + copiedGroup->allItems(&copiedItems); + + for (StepCore::ItemList::size_type n = 0; n < items.size(); ++n) { + _copyMap.insert(items[n], copiedItems[n]); + } + } +} + +void CopyHelper::fixItemLinks(StepCore::Item* item) +{ + const StepCore::MetaObject* mobj = item->metaObject(); + + for (int i = 0; i < mobj->propertyCount(); ++i) { + const StepCore::MetaProperty* pr = mobj->property(i); + + if (pr->userTypeId() == qMetaTypeId<StepCore::Object*>()) { + QVariant v = pr->readVariant(item); + StepCore::Object *obj = v.value<StepCore::Object*>(); + StepCore::Object *copy = _copyMap.value(obj, 0); + pr->writeVariant(item, QVariant::fromValue(copy)); + } + } +} +} + +Clipboard::Clipboard(QObject* parent) : QObject(parent), _canPaste(hasData()) +{ + connect(QApplication::clipboard(), SIGNAL(dataChanged()), + this, SLOT(dataChanged())); +} + + +void Clipboard::copy(const QList<StepCore::Item*>& items) +{ + CopyHelper helper; + + foreach (const StepCore::Item* item, items) { + helper.addItem(item); + } + + QScopedPointer<StepCore::World> world(helper.createWorld()); + + QBuffer buffer; + buffer.open(QBuffer::WriteOnly); + StepCore::XmlFile xmlfile(&buffer); + if (!xmlfile.save(world.data())) { + // Serialization of items failed + kWarning() << xmlfile.errorString(); + return; + } + + QMimeData *mimedata = new QMimeData; + mimedata->setData("application/x-step", buffer.data()); + + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setMimeData(mimedata); +} + +QList<StepCore::Item*> Clipboard::paste(const StepCore::Factory* factory) +{ + QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimedata = clipboard->mimeData(); + + if (!mimedata->hasFormat("application/x-step")) { + // No Step data available + kWarning() << "No Step data on the clipboard"; + return QList<StepCore::Item*>(); + } + + QByteArray data(mimedata->data("application/x-step")); + QBuffer buffer(&data); + buffer.open(QBuffer::ReadOnly); + StepCore::XmlFile xmlfile(&buffer); + + StepCore::World world; + if (!xmlfile.load(&world, factory)) { + // Deserialization of items failed + kError() << xmlfile.errorString(); + return QList<StepCore::Item*>(); + } + + QList<StepCore::Item*> qitems; + foreach (StepCore::Item* item, world.items()) { + world.removeItem(item); + qitems << item; + } + + return qitems; +} + +void Clipboard::dataChanged() +{ + bool canPaste = hasData(); + + if (canPaste != _canPaste) { + _canPaste = canPaste; + emit canPasteChanged(canPaste); + } +} + +bool Clipboard::hasData() const +{ + QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimedata = clipboard->mimeData(); + + return mimedata->hasFormat("application/x-step"); +} diff --git a/step/clipboard.h b/step/clipboard.h new file mode 100644 index 0000000..831991c --- /dev/null +++ b/step/clipboard.h @@ -0,0 +1,56 @@ +/* This file is part of Step. + * Copyright (C) 2007 Vladimir Kuznetsov <ks.vladimir at gmail.com> + * + * Step is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Step is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Step; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef STEP_CLIPBOARD_H +#define STEP_CLIPBOARD_H + +#include <QList> +#include <QObject> +#include <QVector> + +namespace StepCore +{ + class Factory; + class Item; +} + +class Clipboard : public QObject +{ +public: + Clipboard(QObject* parent = 0); + + void copy(const QList<StepCore::Item*>& items); + QList<StepCore::Item*> paste(const StepCore::Factory* factory); + + bool canPaste() const { return _canPaste; } + +signals: + void canPasteChanged(bool value); + +private slots: + void dataChanged(); + +private: + bool hasData() const; + + bool _canPaste; + + Q_OBJECT +}; + +#endif diff --git a/step/mainwindow.cc b/step/mainwindow.cc index fdc0e50..cb3d575 100644 --- a/step/mainwindow.cc +++ b/step/mainwindow.cc @@ -21,6 +21,7 @@ #include "ui_configure_step_general.h" +#include "clipboard.h" #include "worldmodel.h" #include "worldscene.h" #include "worldbrowser.h" @@ -181,6 +182,18 @@ void MainWindow::setupActions() this, SLOT(undoTextChanged(const QString&))); connect(worldModel->undoStack(), SIGNAL(redoTextChanged(const QString&)), this, SLOT(redoTextChanged(const QString&))); + + actionCut = KStandardAction::cut(worldModel, SLOT(cutSelectedItems()), + actionCollection()); + actionCopy = KStandardAction::copy(worldModel, SLOT(copySelectedItems()), + actionCollection()); + actionPaste = KStandardAction::paste(worldModel, SLOT(pasteItems()), + actionCollection()); + actionCut->setEnabled(false); + actionCopy->setEnabled(false); + actionPaste->setEnabled(worldModel->clipboard()->canPaste()); + connect(worldModel->clipboard(), SIGNAL(canPasteChanged(bool)), + actionPaste, SLOT(setEnabled(bool))); actionDelete = actionCollection()->add<KAction>("edit_delete", worldModel, SLOT(deleteSelectedItems())); actionDelete->setText(i18n("&Delete")); @@ -556,11 +569,15 @@ void MainWindow::worldSelectionChanged() worldModel->selectionModel()->selection().indexes()) { if (index != worldModel->worldIndex() && worldModel->item(index)) { actionDelete->setEnabled(true); + actionCut->setEnabled(true); + actionCopy->setEnabled(true); return; } } } actionDelete->setEnabled(false); + actionCut->setEnabled(false); + actionCopy->setEnabled(false); } void MainWindow::configureStep() diff --git a/step/mainwindow.h b/step/mainwindow.h index dea9918..5e46245 100644 --- a/step/mainwindow.h +++ b/step/mainwindow.h @@ -114,6 +114,9 @@ protected: KAction* actionUndo; KAction* actionRedo; KAction* actionDelete; + KAction* actionCut; + KAction* actionCopy; + KAction* actionPaste; KRecentFilesAction* actionRecentFiles; diff --git a/step/worldgraphics.cc b/step/worldgraphics.cc index 825c8b3..c6edf92 100644 --- a/step/worldgraphics.cc +++ b/step/worldgraphics.cc @@ -763,14 +763,10 @@ ItemMenuHandler::ItemMenuHandler(StepCore::Object* object, WorldModel* worldMode void ItemMenuHandler::populateMenu(QMenu* menu, KActionCollection* actions) { StepCore::Item* item = dynamic_cast<StepCore::Item*>(_object); - if(item && item->world() != item) { + + if (item && item->world() != item) { + menu->addAction(actions->action("edit_cut")); + menu->addAction(actions->action("edit_copy")); menu->addAction(actions->action("edit_delete")); - //menu->addAction(KIcon("edit-delete"), i18n("&Delete"), this, SLOT(deleteItem())); } } - -void ItemMenuHandler::deleteItem() -{ - _worldModel->deleteItem(static_cast<StepCore::Item*>(_object)); -} - diff --git a/step/worldgraphics.h b/step/worldgraphics.h index da5d9d5..9fcf63e 100644 --- a/step/worldgraphics.h +++ b/step/worldgraphics.h @@ -433,9 +433,6 @@ public: * Default implementation adds delete action. */ virtual void populateMenu(QMenu* menu, KActionCollection* actions); -protected slots: - void deleteItem(); - protected: StepCore::Object* _object; WorldModel* _worldModel; diff --git a/step/worldmodel.cc b/step/worldmodel.cc index 513135b..7051905 100644 --- a/step/worldmodel.cc +++ b/step/worldmodel.cc @@ -17,6 +17,8 @@ */ #include "worldmodel.h" + +#include "clipboard.h" #include "simulationthread.h" #include "worldgraphics.h" #include "worldmodel.moc" @@ -309,6 +311,7 @@ WorldModel::WorldModel(QObject* parent) { _selectionModel = new QItemSelectionModel(this, this); _undoStack = new KUndoStack(this); + _clipboard = new Clipboard(this); _worldFactory = new WorldFactory(); _world = new StepCore::World(); @@ -1015,3 +1018,73 @@ void WorldModel::simulationFrameEnd(int result) } } +QList<StepCore::Item*> WorldModel::selectedItems() +{ + QList<StepCore::Item*> items; + foreach (QModelIndex index, selectionModel()->selectedIndexes()) { + // Do not delete world item + if (index == worldIndex()) continue; + + StepCore::Item* it = item(index); + if (it) items << it; + } + + foreach (StepCore::Item* it, items) { + for (StepCore::Item* it1 = it->group(); it1 != 0 && it1 != _world; it1 = it1->group()) { + if (items.contains(it1)) { + items.removeOne(it); + break; + } + } + } + + return items; +} + +void WorldModel::cutSelectedItems() +{ + simulationPause(); + + QList<StepCore::Item*> items = selectedItems(); + + _clipboard->copy(items); + + if (!items.isEmpty()) { + beginMacro(items.count() == 1 ? i18n("Cut %1", items[0]->name()) : + i18n("Cut several items")); + foreach (StepCore::Item* it, items) deleteItem(it); + endMacro(); + } +} + +void WorldModel::copySelectedItems() +{ + simulationPause(); + + QList<StepCore::Item*> items = selectedItems(); + + _clipboard->copy(items); +} + +void WorldModel::pasteItems() +{ + simulationPause(); + + QList<StepCore::Item*> items = _clipboard->paste(_worldFactory); + if (items.isEmpty()) return; + + beginMacro(items.count() == 1 ? i18n("Pasted %1", items[0]->name()) : + i18n("Pasted several items")); + QItemSelection selection; + foreach (StepCore::Item* item, items) { + QString className = item->metaObject()->className(); + item->setName(getUniqueName(className)); + addItem(item, _world); + QModelIndex index = objectIndex(item); + selection.select(index, index); + } + if (!selection.isEmpty()) { + selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); + } + endMacro(); +} diff --git a/step/worldmodel.h b/step/worldmodel.h index a441edd..1306826 100644 --- a/step/worldmodel.h +++ b/step/worldmodel.h @@ -40,6 +40,8 @@ namespace StepCore { class QItemSelectionModel; class QTimer; class QMenu; + +class Clipboard; class WorldFactory; class CommandSimulate; class SimulationThread; @@ -126,6 +128,8 @@ public: void pushCommand(QUndoCommand* command); ///< Push new undo command void beginMacro(const QString& text); ///< Begin undo macro void endMacro(); ///< End undo macro + + Clipboard* clipboard() const { return _clipboard; } // Property edit /** Modify object property. @@ -209,6 +213,9 @@ public slots: void simulationStart(); ///< Start simulation void simulationStop(); ///< Stop simulation + void cutSelectedItems(); + void copySelectedItems(); + void pasteItems(); void deleteSelectedItems(); ///< Delete all selected items protected slots: @@ -233,6 +240,7 @@ protected: void addCreatedItem(StepCore::Item* item, StepCore::ItemGroup* parent = 0); void removeCreatedItem(StepCore::Item* item); StepCore::Solver* swapSolver(StepCore::Solver* solver); + QList<StepCore::Item*> selectedItems(); // Only for UndoCommand* classes //void objectChanged(const StepCore::Object* object); @@ -241,6 +249,7 @@ protected: StepCore::World* _world; QItemSelectionModel* _selectionModel; KUndoStack* _undoStack; + Clipboard* _clipboard; const WorldFactory* _worldFactory; QString _errorString;
