diff --git a/editor/mainwindow.cpp b/editor/mainwindow.cpp index 076d2f7..e132cb8 100644 --- a/editor/mainwindow.cpp +++ b/editor/mainwindow.cpp @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include "common.h" #include "editorcommon.h" @@ -21,7 +24,8 @@ #include "physics/simulation.h" #include "objects/part.h" #include "qfiledialog.h" -#include "qitemselectionmodel.h" +#include "qclipboard.h" +#include "qmimedata.h" #include "qobject.h" #include "qsysinfo.h" @@ -37,7 +41,7 @@ MainWindow::MainWindow(QWidget *parent) timer.start(33, this); setMouseTracking(true); - ConnectSelectionChangeHandler(); + ui->explorerView->buildContextMenu(); connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = SelectedTool::SELECT; updateToolbars(); }); connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? SelectedTool::MOVE : SelectedTool::SELECT; updateToolbars(); }); @@ -82,7 +86,78 @@ MainWindow::MainWindow(QWidget *parent) delete ui->explorerView->selectionModel(); ui->explorerView->reset(); ui->explorerView->setModel(new ExplorerModel(dataModel)); - ConnectSelectionChangeHandler(); + }); + + connect(ui->actionDelete, &QAction::triggered, this, [&]() { + for (InstanceRefWeak inst : getSelection()) { + if (inst.expired()) continue; + inst.lock()->SetParent(std::nullopt); + } + setSelection(std::vector {}); + }); + + connect(ui->actionCopy, &QAction::triggered, this, [&]() { + pugi::xml_document rootDoc; + for (InstanceRefWeak inst : getSelection()) { + if (inst.expired()) continue; + inst.lock()->Serialize(&rootDoc); + } + + std::ostringstream encoded; + rootDoc.save(encoded); + + QMimeData* mimeData = new QMimeData; + mimeData->setData("application/xml", QByteArray::fromStdString(encoded.str())); + QApplication::clipboard()->setMimeData(mimeData); + }); + connect(ui->actionCut, &QAction::triggered, this, [&]() { + pugi::xml_document rootDoc; + for (InstanceRefWeak inst : getSelection()) { + if (inst.expired()) continue; + inst.lock()->Serialize(&rootDoc); + inst.lock()->SetParent(std::nullopt); + } + + std::ostringstream encoded; + rootDoc.save(encoded); + + QMimeData* mimeData = new QMimeData; + mimeData->setData("application/xml", QByteArray::fromStdString(encoded.str())); + QApplication::clipboard()->setMimeData(mimeData); + }); + + connect(ui->actionPaste, &QAction::triggered, this, [&]() { + const QMimeData* mimeData = QApplication::clipboard()->mimeData(); + if (!mimeData || !mimeData->hasFormat("application/xml")) return; + QByteArray bytes = mimeData->data("application/xml"); + std::string encoded = bytes.toStdString(); + + pugi::xml_document rootDoc; + rootDoc.load_string(encoded.c_str()); + + for (pugi::xml_node instNode : rootDoc.children()) { + InstanceRef inst = Instance::Deserialize(&instNode); + workspace()->AddChild(inst); + } + }); + + connect(ui->actionPasteInto, &QAction::triggered, this, [&]() { + if (getSelection().size() != 1 || getSelection()[0].expired()) return; + + InstanceRef selectedParent = getSelection()[0].lock(); + + const QMimeData* mimeData = QApplication::clipboard()->mimeData(); + if (!mimeData || !mimeData->hasFormat("application/xml")) return; + QByteArray bytes = mimeData->data("application/xml"); + std::string encoded = bytes.toStdString(); + + pugi::xml_document rootDoc; + rootDoc.load_string(encoded.c_str()); + + for (pugi::xml_node instNode : rootDoc.children()) { + InstanceRef inst = Instance::Deserialize(&instNode); + selectedParent->AddChild(inst); + } }); // Update handles @@ -127,17 +202,6 @@ MainWindow::MainWindow(QWidget *parent) syncPartPhysics(ui->mainWidget->lastPart); } -void MainWindow::ConnectSelectionChangeHandler() { - // connect(ui->explorerView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) { - // if (selected.count() == 0) return; - - // std::optional inst = selected.count() == 0 ? std::nullopt - // : std::make_optional(((Instance*)selected.indexes()[0].internalPointer())->shared_from_this()); - - // ui->propertiesView->setSelected(inst); - // }); -} - static std::chrono::time_point lastTime = std::chrono::steady_clock::now(); void MainWindow::timerEvent(QTimerEvent* evt) { if (evt->timerId() != timer.timerId()) { diff --git a/editor/mainwindow.h b/editor/mainwindow.h index 0bad39e..064828c 100644 --- a/editor/mainwindow.h +++ b/editor/mainwindow.h @@ -44,6 +44,5 @@ private: void updateToolbars(); void timerEvent(QTimerEvent*) override; - void ConnectSelectionChangeHandler(); }; #endif // MAINWINDOW_H diff --git a/editor/mainwindow.ui b/editor/mainwindow.ui index af91020..0efb9c7 100644 --- a/editor/mainwindow.ui +++ b/editor/mainwindow.ui @@ -115,6 +115,12 @@ + + + + + + @@ -315,6 +321,91 @@ QAction::MenuRole::NoRole + + + + + + Copy + + + Copy objects to clipboard + + + Ctrl+C + + + QAction::MenuRole::NoRole + + + + + + + + Cut + + + Cut objects into clipboard + + + Ctrl+X + + + QAction::MenuRole::NoRole + + + + + + + + Paste + + + Paste objects from clipboard + + + Ctrl+V + + + QAction::MenuRole::NoRole + + + + + + + + Paste Into + + + Paste objects from clipboard into selected object + + + Ctrl+Shift+V + + + QAction::MenuRole::NoRole + + + + + + + + Delete Object + + + Delete selected objects + + + Del + + + QAction::MenuRole::NoRole + + diff --git a/editor/panes/explorerview.cpp b/editor/panes/explorerview.cpp index 77542da..61f1ae4 100644 --- a/editor/panes/explorerview.cpp +++ b/editor/panes/explorerview.cpp @@ -1,13 +1,16 @@ #include "explorerview.h" #include "explorermodel.h" +#include "mainwindow.h" +#include "../ui_mainwindow.h" #include "common.h" #include "objects/base/instance.h" -#include "objects/workspace.h" #include "qabstractitemmodel.h" -#include "qaction.h" -#include "qnamespace.h" +#include +#include #include +#define M_mainWindow dynamic_cast(window()) + ExplorerView::ExplorerView(QWidget* parent): QTreeView(parent), model(ExplorerModel(std::dynamic_pointer_cast(dataModel))) { @@ -57,8 +60,6 @@ ExplorerView::ExplorerView(QWidget* parent): this->selectionModel()->select(index, QItemSelectionModel::SelectionFlag::Select); } }); - - buildContextMenu(); } ExplorerView::~ExplorerView() { @@ -67,19 +68,17 @@ ExplorerView::~ExplorerView() { void ExplorerView::keyPressEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_Delete: - actionDelete->trigger(); + M_mainWindow->ui->actionDelete->trigger(); break; } } -void ExplorerView::buildContextMenu() { - // This will leak memory. Anyway... - contextMenu.addAction(this->actionDelete = new QAction(QIcon("assets/icons/editor/delete"), "Delete")); - connect(actionDelete, &QAction::triggered, this, [&]() { - QModelIndexList selectedIndexes = this->selectionModel()->selectedIndexes(); - for (QModelIndex index : selectedIndexes) { - model.fromIndex(index)->SetParent(std::nullopt); - } - }); +void ExplorerView::buildContextMenu() { + contextMenu.addAction(M_mainWindow->ui->actionDelete); + contextMenu.addSeparator(); + contextMenu.addAction(M_mainWindow->ui->actionCopy); + contextMenu.addAction(M_mainWindow->ui->actionCut); + contextMenu.addAction(M_mainWindow->ui->actionPaste); + contextMenu.addAction(M_mainWindow->ui->actionPasteInto); } \ No newline at end of file diff --git a/editor/panes/explorerview.h b/editor/panes/explorerview.h index 3bd063d..54a5eef 100644 --- a/editor/panes/explorerview.h +++ b/editor/panes/explorerview.h @@ -20,18 +20,9 @@ public: void keyPressEvent(QKeyEvent*) override; // void dropEvent(QDropEvent*) override; + + void buildContextMenu(); private: ExplorerModel model; QMenu contextMenu; - - // TODO: Move these to a separate top-level namespace so these can be - // accessed from multiple locations - QAction* actionDelete; - QAction* actionCopy; - QAction* actionCut; - QAction* actionPaste; - QAction* actionPasteInto; - QAction* actionSelectChildren; - - void buildContextMenu(); }; \ No newline at end of file