From 9b750c4cbefef8c10c9b4807643bcf425d88aab9 Mon Sep 17 00:00:00 2001 From: Laserlicht <13953785+Laserlicht@users.noreply.github.com> Date: Sat, 10 Jan 2026 03:32:13 +0100 Subject: [PATCH] Campaign editor: support drag'n'drop --- mapeditor/campaigneditor/campaigneditor.cpp | 95 ++++++++++++++++++--- mapeditor/campaigneditor/campaigneditor.h | 4 + mapeditor/campaigneditor/campaignview.cpp | 50 +++++++++++ mapeditor/campaigneditor/campaignview.h | 8 ++ mapeditor/mainwindow.cpp | 11 +++ mapeditor/mainwindow.h | 1 + 6 files changed, 159 insertions(+), 10 deletions(-) diff --git a/mapeditor/campaigneditor/campaigneditor.cpp b/mapeditor/campaigneditor/campaigneditor.cpp index 4e52c19d7..4a6559725 100644 --- a/mapeditor/campaigneditor/campaigneditor.cpp +++ b/mapeditor/campaigneditor/campaigneditor.cpp @@ -34,6 +34,8 @@ CampaignEditor::CampaignEditor(): { ui->setupUi(this); + setAcceptDrops(true); + setWindowIcon(QIcon{":/icons/menu-game.png"}); ui->actionOpen->setIcon(QIcon{":/icons/document-open.png"}); ui->actionSave->setIcon(QIcon{":/icons/document-save.png"}); @@ -47,6 +49,21 @@ CampaignEditor::CampaignEditor(): campaignScene.reset(new CampaignScene()); ui->campaignView->setScene(campaignScene.get()); + + // Connect the fileDropped signal from campaignView to handle file drops + connect(ui->campaignView, &CampaignView::fileDropped, this, [this](const QString & filename) { + if(!getAnswerAboutUnsavedChanges()) + return; + + try + { + loadCampaignFile(filename); + } + catch(const std::exception & e) + { + QMessageBox::critical(this, tr("Failed to open campaign"), tr(e.what())); + } + }); redraw(); @@ -173,17 +190,32 @@ void CampaignEditor::showCampaignEditor(QWidget *parent) dialog->setAttribute(Qt::WA_DeleteOnClose); } -void CampaignEditor::on_actionOpen_triggered() +void CampaignEditor::showCampaignEditor(QWidget *parent, const QString &campaignFile) +{ + auto * dialog = new CampaignEditor(); + + dialog->move(parent->geometry().center() - dialog->rect().center()); + + dialog->setAttribute(Qt::WA_DeleteOnClose); + + try + { + dialog->loadCampaignFile(campaignFile); + if(!dialog->campaignState) + { + dialog->close(); + return; + } + } + catch(const std::exception & e) + { + QMessageBox::critical(dialog, QObject::tr("Failed to open campaign"), QObject::tr(e.what())); + dialog->close(); + } +} + +void CampaignEditor::loadCampaignFile(const QString & filenameSelect) { - if(!getAnswerAboutUnsavedChanges()) - return; - - auto filenameSelect = QFileDialog::getOpenFileName(this, tr("Open map"), - QString::fromStdString(VCMIDirs::get().userDataPath().make_preferred().string()), - tr("All supported campaigns (*.vcmp *.h3c);;VCMI campaigns(*.vcmp);;HoMM3 campaigns(*.h3c)")); - if(filenameSelect.isEmpty()) - return; - campaignState = Helper::openCampaignInternal(filenameSelect); selectedScenario = *campaignState->allScenarios().begin(); @@ -203,6 +235,20 @@ void CampaignEditor::on_actionOpen_triggered() redraw(); } +void CampaignEditor::on_actionOpen_triggered() +{ + if(!getAnswerAboutUnsavedChanges()) + return; + + auto filenameSelect = QFileDialog::getOpenFileName(this, tr("Open map"), + QString::fromStdString(VCMIDirs::get().userDataPath().make_preferred().string()), + tr("All supported campaigns (*.vcmp *.h3c);;VCMI campaigns(*.vcmp);;HoMM3 campaigns(*.h3c)")); + if(filenameSelect.isEmpty()) + return; + + loadCampaignFile(filenameSelect); +} + void CampaignEditor::on_actionOpenSet_triggered() { if(!getAnswerAboutUnsavedChanges()) @@ -326,6 +372,35 @@ void CampaignEditor::closeEvent(QCloseEvent *event) event->ignore(); } +void CampaignEditor::dragEnterEvent(QDragEnterEvent *event) +{ + if(event->mimeData()->hasUrls()) + event->acceptProposedAction(); +} + +void CampaignEditor::dropEvent(QDropEvent *event) +{ + if(!getAnswerAboutUnsavedChanges()) + return; + + for(const QUrl& url : event->mimeData()->urls()) + { + QString path = url.toLocalFile(); + if(path.endsWith(".h3c", Qt::CaseInsensitive) || path.endsWith(".vcmp", Qt::CaseInsensitive)) + { + try + { + loadCampaignFile(path); + } + catch(const std::exception & e) + { + QMessageBox::critical(this, tr("Failed to open campaign"), tr(e.what())); + } + break; + } + } +} + std::unique_ptr CampaignEditor::tryToOpenMap(QWidget* parent, std::shared_ptr state, CampaignScenarioID scenario) { try diff --git a/mapeditor/campaigneditor/campaigneditor.h b/mapeditor/campaigneditor/campaigneditor.h index 37d574f60..ca53c267f 100644 --- a/mapeditor/campaigneditor/campaigneditor.h +++ b/mapeditor/campaigneditor/campaigneditor.h @@ -33,6 +33,7 @@ public: void redraw(); static void showCampaignEditor(QWidget *parent); + static void showCampaignEditor(QWidget *parent, const QString &campaignFile); static std::unique_ptr tryToOpenMap(QWidget* parent, std::shared_ptr state, CampaignScenarioID scenario); private slots: @@ -50,8 +51,11 @@ private: void changed(); bool validate(); void saveCampaign(); + void loadCampaignFile(const QString & filenameSelect); void closeEvent(QCloseEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; Ui::CampaignEditor *ui; diff --git a/mapeditor/campaigneditor/campaignview.cpp b/mapeditor/campaigneditor/campaignview.cpp index 08f25fbcf..da4a67840 100644 --- a/mapeditor/campaigneditor/campaignview.cpp +++ b/mapeditor/campaigneditor/campaignview.cpp @@ -19,6 +19,56 @@ CampaignScene::CampaignScene(): CampaignView::CampaignView(QWidget * parent): QGraphicsView(parent) { + setAcceptDrops(true); +} + +void CampaignView::dragEnterEvent(QDragEnterEvent *event) +{ + if(event->mimeData()->hasUrls()) + { + for(const QUrl& url : event->mimeData()->urls()) + { + QString path = url.toLocalFile(); + if(path.endsWith(".h3c", Qt::CaseInsensitive) || path.endsWith(".vcmp", Qt::CaseInsensitive)) + { + event->acceptProposedAction(); + return; + } + } + } +} + +void CampaignView::dragMoveEvent(QDragMoveEvent *event) +{ + if(event->mimeData()->hasUrls()) + { + for(const QUrl& url : event->mimeData()->urls()) + { + QString path = url.toLocalFile(); + if(path.endsWith(".h3c", Qt::CaseInsensitive) || path.endsWith(".vcmp", Qt::CaseInsensitive)) + { + event->acceptProposedAction(); + return; + } + } + } +} + +void CampaignView::dropEvent(QDropEvent *event) +{ + if(event->mimeData()->hasUrls()) + { + for(const QUrl& url : event->mimeData()->urls()) + { + QString path = url.toLocalFile(); + if(path.endsWith(".h3c", Qt::CaseInsensitive) || path.endsWith(".vcmp", Qt::CaseInsensitive)) + { + Q_EMIT fileDropped(path); + event->acceptProposedAction(); + return; + } + } + } } ClickablePixmapItem::ClickablePixmapItem(const QPixmap &pixmap, const std::function & clickedCallback, const std::function & doubleClickedCallback, const std::function & contextMenuCallback): diff --git a/mapeditor/campaigneditor/campaignview.h b/mapeditor/campaigneditor/campaignview.h index 03ab5ab0b..dc12eec7d 100644 --- a/mapeditor/campaigneditor/campaignview.h +++ b/mapeditor/campaigneditor/campaignview.h @@ -26,6 +26,14 @@ class CampaignView : public QGraphicsView public: CampaignView(QWidget * parent); + +protected: + void dragEnterEvent(QDragEnterEvent *event) override; + void dropEvent(QDropEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + +signals: + void fileDropped(const QString & filename); }; class ClickablePixmapItem : public QGraphicsPixmapItem { diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index ebf5c8754..e5bec7a3d 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -180,6 +180,12 @@ void MainWindow::dropEvent(QDropEvent* event) openMap(path); break; } + else if (path.endsWith(".h3c", Qt::CaseInsensitive) || path.endsWith(".vcmp", Qt::CaseInsensitive)) + { + hide(); + openCampaign(path); + break; + } } } @@ -498,6 +504,11 @@ bool MainWindow::openMap(const QString & filenameSelect) return true; } +void MainWindow::openCampaign(const QString & filenameSelect) +{ + CampaignEditor::showCampaignEditor(this, filenameSelect); +} + void MainWindow::updateRecentMenu(const QString & filenameSelect) { QSettings s = CLauncherDirs::getSettings(Ui::appName); QStringList recentFiles = s.value(recentlyOpenedFilesSetting).toStringList(); diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index 9f29d3fcc..b5546d59d 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -51,6 +51,7 @@ public: void saveMap(); bool openMap(const QString &); + void openCampaign(const QString &); //MapView * mapView();