mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-25 22:42:04 +02:00
PlayerSelectionDialog implementation + code cleanup
This commit is contained in:
@@ -20,6 +20,7 @@ set(editor_SRCS
|
||||
mapsettings/eventsettings.cpp
|
||||
mapsettings/rumorsettings.cpp
|
||||
mapsettings/translations.cpp
|
||||
PlayerSelectionDialog.cpp
|
||||
playersettings.cpp
|
||||
playerparams.cpp
|
||||
scenelayer.cpp
|
||||
@@ -70,6 +71,7 @@ set(editor_HEADERS
|
||||
mapsettings/eventsettings.h
|
||||
mapsettings/rumorsettings.h
|
||||
mapsettings/translations.h
|
||||
PlayerSelectionDialog.h
|
||||
playersettings.h
|
||||
playerparams.h
|
||||
scenelayer.h
|
||||
|
||||
156
mapeditor/PlayerSelectionDialog.cpp
Normal file
156
mapeditor/PlayerSelectionDialog.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* PlayerSelectionDialog.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "PlayerSelectionDialog.h"
|
||||
#include "mainwindow.h"
|
||||
#include "../lib/mapping/CMap.h"
|
||||
|
||||
PlayerSelectionDialog::PlayerSelectionDialog(QWidget * parent)
|
||||
: QDialog(parent), selectedPlayer(PlayerColor::NEUTRAL)
|
||||
{
|
||||
auto main = qobject_cast<MainWindow *>(parent);
|
||||
assert(main);
|
||||
|
||||
setupDialogComponents();
|
||||
|
||||
int maxPlayers = 0;
|
||||
if(main && main->controller.map())
|
||||
maxPlayers = main->controller.map()->players.size();
|
||||
|
||||
bool defaultIsChecked = false;
|
||||
|
||||
for(int i = 0; i < maxPlayers; ++i)
|
||||
{
|
||||
PlayerColor player(i);
|
||||
QAction * action = main->getActionPlayer(player);
|
||||
|
||||
addCheckbox(action, player, main->controller.map()->players.at(i).canAnyonePlay(), & defaultIsChecked);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerSelectionDialog::onCheckboxToggled(bool checked)
|
||||
{
|
||||
if(!checked)
|
||||
return;
|
||||
|
||||
QCheckBox * senderCheckBox = qobject_cast<QCheckBox *>(sender());
|
||||
if(!senderCheckBox)
|
||||
return;
|
||||
|
||||
for(int i = 0; i < checkboxes.size(); ++i)
|
||||
{
|
||||
QCheckBox * cb = checkboxes[i];
|
||||
if(cb == senderCheckBox)
|
||||
{
|
||||
selectedPlayer = PlayerColor(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
cb->blockSignals(true);
|
||||
cb->setChecked(false);
|
||||
cb->blockSignals(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PlayerColor PlayerSelectionDialog::getSelectedPlayer() const
|
||||
{
|
||||
return selectedPlayer;
|
||||
}
|
||||
|
||||
void PlayerSelectionDialog::setupDialogComponents()
|
||||
{
|
||||
setWindowTitle(tr("Select Player"));
|
||||
setFixedSize(sizeHint());
|
||||
setWindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);
|
||||
font.setPointSize(10);
|
||||
setFont(font);
|
||||
|
||||
mainLayout.setSpacing(10);
|
||||
mainLayout.setContentsMargins(20, 20, 80, 20);
|
||||
checkboxLayout.setContentsMargins(0, 10, 0, 20);
|
||||
mainLayout.addSpacing(4);
|
||||
|
||||
QLabel * errorLabel = new QLabel(tr("Hero cannot be created as NEUTRAL"), this);
|
||||
font.setBold(true);
|
||||
errorLabel->setFont(font);
|
||||
mainLayout.addWidget(errorLabel);
|
||||
|
||||
QLabel * instructionLabel = new QLabel(tr("Switch to one of the available players:"), this);
|
||||
font.setBold(false);
|
||||
instructionLabel->setFont(font);
|
||||
mainLayout.addWidget(instructionLabel);
|
||||
|
||||
QWidget * checkboxContainer = new QWidget(this);
|
||||
checkboxContainer->setLayout(& checkboxLayout);
|
||||
mainLayout.addWidget(checkboxContainer);
|
||||
|
||||
QDialogButtonBox * box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
connect(box, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
mainLayout.addWidget(box);
|
||||
|
||||
setLayout(& mainLayout);
|
||||
}
|
||||
|
||||
void PlayerSelectionDialog::addCheckbox(QAction * checkboxAction, PlayerColor player, bool isEnabled, bool * isDefault)
|
||||
{
|
||||
QHBoxLayout * rowLayout = new QHBoxLayout();
|
||||
auto * checkbox = new QCheckBox(checkboxAction->text(), this);
|
||||
|
||||
QLabel * shortcutLabel = new QLabel(checkboxAction->shortcut().toString(), this);
|
||||
QFont shortcutFont = font;
|
||||
shortcutFont.setPointSize(9);
|
||||
shortcutFont.setItalic(true);
|
||||
shortcutLabel->setFont(shortcutFont);
|
||||
shortcutLabel->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
|
||||
|
||||
QWidget * checkboxContainer = new QWidget(this);
|
||||
QHBoxLayout * cbLayout = new QHBoxLayout(checkboxContainer);
|
||||
cbLayout->setContentsMargins(0, 0, 0, 0);
|
||||
cbLayout->addWidget(checkbox, 0, Qt::AlignCenter);
|
||||
|
||||
rowLayout->addWidget(checkboxContainer, 1);
|
||||
rowLayout->addWidget(shortcutLabel, 1);
|
||||
|
||||
checkbox->setEnabled(isEnabled);
|
||||
if(checkbox->isEnabled() && !*isDefault)
|
||||
{
|
||||
checkbox->setChecked(true);
|
||||
selectedPlayer = player;
|
||||
*isDefault = true;
|
||||
}
|
||||
|
||||
checkboxLayout.addLayout(rowLayout);
|
||||
|
||||
connect(checkbox, &QCheckBox::clicked, this, [=]()
|
||||
{
|
||||
selectedPlayer = player;
|
||||
|
||||
// Radio-style logic: uncheck other boxes
|
||||
for(auto * box : findChildren<QCheckBox *>())
|
||||
{
|
||||
if(box != checkbox)
|
||||
box->setChecked(false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Add action to the dialog for shortcut support
|
||||
addAction(checkboxAction);
|
||||
|
||||
// Connect action trigger to simulate checkbox click
|
||||
connect(checkboxAction, &QAction::triggered, this, [=]()
|
||||
{
|
||||
if(checkbox->isEnabled())
|
||||
checkbox->click();
|
||||
});
|
||||
}
|
||||
44
mapeditor/PlayerSelectionDialog.h
Normal file
44
mapeditor/PlayerSelectionDialog.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* PlayerSelectionDialog.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include "../source/lib/constants/EntityIdentifiers.h"
|
||||
|
||||
/// Dialog shown when a hero cannot be placed as NEUTRAL.
|
||||
/// Allows the user to select a valid player via checkboxes,
|
||||
/// or using the existing keyboard shortcuts from MainWindow's player QActions.
|
||||
class PlayerSelectionDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlayerSelectionDialog(QWidget * parent = nullptr);
|
||||
PlayerColor getSelectedPlayer() const;
|
||||
|
||||
private slots:
|
||||
void onCheckboxToggled(bool checked);
|
||||
|
||||
private:
|
||||
std::vector<QCheckBox *> checkboxes;
|
||||
PlayerColor selectedPlayer;
|
||||
|
||||
QFont font;
|
||||
QVBoxLayout mainLayout;
|
||||
QVBoxLayout checkboxLayout;
|
||||
|
||||
void setupDialogComponents();
|
||||
void addCheckbox(QAction * checkboxAction, PlayerColor player, bool isEnabled, bool * isDefault);
|
||||
|
||||
};
|
||||
@@ -25,7 +25,7 @@ namespace Ui
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
const QString mainWindowSizeSetting = "MainWindow/Size";
|
||||
const QString mainWindowPositionSetting = "MainWindow/Position";
|
||||
@@ -42,8 +42,8 @@ class MainWindow : public QMainWindow
|
||||
std::unique_ptr<CBasicLogConfigurator> logConfig;
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
void initializeMap(bool isNew);
|
||||
|
||||
@@ -62,6 +62,11 @@ public:
|
||||
|
||||
void loadTranslation();
|
||||
|
||||
QAction * getActionPlayer(const PlayerColor &);
|
||||
|
||||
public slots:
|
||||
void switchDefaultPlayer(const PlayerColor &);
|
||||
|
||||
private slots:
|
||||
void on_actionOpen_triggered();
|
||||
|
||||
@@ -111,8 +116,6 @@ private slots:
|
||||
|
||||
void on_actionRecreate_obstacles_triggered();
|
||||
|
||||
void switchDefaultPlayer(const PlayerColor &);
|
||||
|
||||
void on_actionCut_triggered();
|
||||
|
||||
void on_actionCopy_triggered();
|
||||
@@ -170,8 +173,6 @@ private:
|
||||
void addGroupIntoCatalog(const QString & groupName, bool staticOnly);
|
||||
void addGroupIntoCatalog(const QString & groupName, bool useCustomName, bool staticOnly, int ID);
|
||||
|
||||
QAction * getActionPlayer(const PlayerColor &);
|
||||
|
||||
void changeBrushState(int idx);
|
||||
void setTitle();
|
||||
|
||||
@@ -187,7 +188,7 @@ private:
|
||||
void updateRecentMenu(const QString & filenameSelect);
|
||||
|
||||
private:
|
||||
Ui::MainWindow * ui;
|
||||
Ui::MainWindow * ui;
|
||||
ObjectBrowserProxyModel * objectBrowser = nullptr;
|
||||
QGraphicsScene * scenePreview;
|
||||
MapSettings * mapSettings = nullptr;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "mainwindow.h"
|
||||
#include "inspector/inspector.h"
|
||||
#include "GameLibrary.h"
|
||||
#include "PlayerSelectionDialog.h"
|
||||
|
||||
MapController::MapController(QObject * parent)
|
||||
: QObject(parent)
|
||||
@@ -371,7 +372,7 @@ void MapController::pasteFromClipboard(int level)
|
||||
{
|
||||
auto obj = CMemorySerializer::deepCopyShared(*objUniquePtr);
|
||||
QString errorMsg;
|
||||
if (!canPlaceObject(level, obj.get(), errorMsg))
|
||||
if(!canPlaceObject(obj.get(), errorMsg))
|
||||
{
|
||||
errors.push_back(std::move(errorMsg));
|
||||
continue;
|
||||
@@ -542,36 +543,59 @@ void MapController::commitObjectCreate(int level)
|
||||
main->mapChanged();
|
||||
}
|
||||
|
||||
bool MapController::canPlaceObject(int level, CGObjectInstance * newObj, QString & error) const
|
||||
bool MapController::canPlaceObject(const CGObjectInstance * newObj, QString & error) const
|
||||
{
|
||||
if(newObj->ID == Obj::GRAIL) //special case for grail
|
||||
{
|
||||
//find all objects of such type
|
||||
int objCounter = 0;
|
||||
for(auto o : _map->objects)
|
||||
{
|
||||
if(o->ID == newObj->ID && o->subID == newObj->subID)
|
||||
{
|
||||
++objCounter;
|
||||
}
|
||||
}
|
||||
|
||||
if(objCounter >= 1)
|
||||
{
|
||||
error = QObject::tr("There can only be one grail object on the map.");
|
||||
return false; //maplimit reached
|
||||
}
|
||||
}
|
||||
return canPlaceGrail(newObj, error);
|
||||
|
||||
if(defaultPlayer == PlayerColor::NEUTRAL && (newObj->ID == Obj::HERO || newObj->ID == Obj::RANDOM_HERO))
|
||||
return canPlaceHero(newObj, error);
|
||||
|
||||
return checkRequiredMods(newObj, error);
|
||||
}
|
||||
|
||||
bool MapController::canPlaceGrail(const CGObjectInstance * grailObj, QString & error) const
|
||||
{
|
||||
assert(grailObj->ID == Obj::GRAIL);
|
||||
|
||||
//find all objects of such type
|
||||
int objCounter = 0;
|
||||
for(auto o : _map->objects)
|
||||
{
|
||||
error = QObject::tr("Hero %1 cannot be created as NEUTRAL.").arg(QString::fromStdString(newObj->instanceName));
|
||||
return false;
|
||||
if(o->ID == grailObj->ID && o->subID == grailObj->subID)
|
||||
{
|
||||
++objCounter;
|
||||
}
|
||||
}
|
||||
|
||||
// check if object's mod is in required mods in map settings
|
||||
if(objCounter >= 1)
|
||||
{
|
||||
error = QObject::tr("There can only be one grail object on the map.");
|
||||
return false; //maplimit reached
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MapController::canPlaceHero(const CGObjectInstance * heroObj, QString & error) const
|
||||
{
|
||||
assert(heroObj->ID == Obj::HERO || heroObj->ID == Obj::RANDOM_HERO);
|
||||
|
||||
PlayerSelectionDialog dialog(main);
|
||||
if(dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
main->switchDefaultPlayer(dialog.getSelectedPlayer());
|
||||
return true;
|
||||
}
|
||||
|
||||
error = QObject::tr("Hero %1 cannot be created as NEUTRAL.").arg(QString::fromStdString(heroObj->instanceName));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MapController::checkRequiredMods(const CGObjectInstance * obj, QString & error) const
|
||||
{
|
||||
ModCompatibilityInfo modsInfo;
|
||||
modAssessmentObject(newObj, modsInfo);
|
||||
modAssessmentObject(obj, modsInfo);
|
||||
|
||||
for(auto & mod : modsInfo)
|
||||
{
|
||||
@@ -582,23 +606,22 @@ bool MapController::canPlaceObject(int level, CGObjectInstance * newObj, QString
|
||||
submod = " (" + tr("submod of") + " " + QString::fromStdString(mod.second.parent) + ")";
|
||||
|
||||
auto reply = QMessageBox::question(main,
|
||||
tr("Required Mod Missing"),
|
||||
tr("This object is from the mod '%1'%2.\n"
|
||||
"The mod is currently not in the map's required modifications list.\n"
|
||||
"Do you want to add this mod to the required modifications ?")
|
||||
tr("Missing Required Mod"),
|
||||
tr("This object is from the mod '%1'%2.\n\n"
|
||||
"The mod is currently not in the map's required modifications list.\n\n"
|
||||
"Do you want to add this mod to the required modifications ?\n")
|
||||
.arg(QString::fromStdString(LIBRARY->modh->getModInfo(mod.first).getVerificationInfo().name), submod),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if(reply == QMessageBox::Yes)
|
||||
requestModsUpdate(modsInfo, true); // emit signal for MapSettings
|
||||
/* emit */ requestModsUpdate(modsInfo, true); // signal for MapSettings
|
||||
else
|
||||
{
|
||||
error = tr("The object's mod is mandatory for map to remain valid.");
|
||||
error = tr("This object's mod is mandatory for map to remain valid.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -634,7 +657,7 @@ ModCompatibilityInfo MapController::modAssessmentAll()
|
||||
return result;
|
||||
}
|
||||
|
||||
void MapController::modAssessmentObject(CGObjectInstance * obj, ModCompatibilityInfo & result)
|
||||
void MapController::modAssessmentObject(const CGObjectInstance * obj, ModCompatibilityInfo & result)
|
||||
{
|
||||
auto extractEntityMod = [&result](const auto & entity)
|
||||
{
|
||||
@@ -650,7 +673,7 @@ void MapController::modAssessmentObject(CGObjectInstance * obj, ModCompatibility
|
||||
|
||||
if(obj->ID == Obj::TOWN || obj->ID == Obj::RANDOM_TOWN)
|
||||
{
|
||||
auto town = dynamic_cast<CGTownInstance *>(obj);
|
||||
auto town = dynamic_cast<const CGTownInstance *>(obj);
|
||||
for(const auto & spellID : town->possibleSpells)
|
||||
{
|
||||
if(spellID == SpellID::PRESET)
|
||||
@@ -682,7 +705,7 @@ void MapController::modAssessmentObject(CGObjectInstance * obj, ModCompatibility
|
||||
|
||||
if(obj->ID == Obj::HERO || obj->ID == Obj::RANDOM_HERO)
|
||||
{
|
||||
auto hero = dynamic_cast<CGHeroInstance *>(obj);
|
||||
auto hero = dynamic_cast<const CGHeroInstance *>(obj);
|
||||
for(const auto & spellID : hero->getSpellsInSpellbook())
|
||||
{
|
||||
if(spellID == SpellID::PRESET || spellID == SpellID::SPELLBOOK_PRESET)
|
||||
|
||||
@@ -63,9 +63,16 @@ public:
|
||||
|
||||
bool discardObject(int level) const;
|
||||
void createObject(int level, std::shared_ptr<CGObjectInstance> obj) const;
|
||||
bool canPlaceObject(int level, CGObjectInstance * obj, QString & error) const;
|
||||
bool canPlaceObject(const CGObjectInstance * obj, QString & error) const;
|
||||
bool canPlaceGrail(const CGObjectInstance * grailObj, QString & error) const;
|
||||
bool canPlaceHero(const CGObjectInstance * heroObj, QString & error) const;
|
||||
|
||||
static void modAssessmentObject(CGObjectInstance * obj, ModCompatibilityInfo & result);
|
||||
/// Ensures that the object's mod is listed in the map's required mods.
|
||||
/// If the mod is missing, prompts the user to add it. Returns false if the user declines,
|
||||
/// making the object invalid for placement.
|
||||
bool checkRequiredMods(const CGObjectInstance * obj, QString & error) const;
|
||||
|
||||
static void modAssessmentObject(const CGObjectInstance * obj, ModCompatibilityInfo & result);
|
||||
static ModCompatibilityInfo modAssessmentAll();
|
||||
static ModCompatibilityInfo modAssessmentMap(const CMap & map);
|
||||
|
||||
|
||||
@@ -614,7 +614,7 @@ void MapView::dropEvent(QDropEvent * event)
|
||||
if(sc->selectionObjectsView.newObject)
|
||||
{
|
||||
QString errorMsg;
|
||||
if(controller->canPlaceObject(sc->level, sc->selectionObjectsView.newObject.get(), errorMsg))
|
||||
if(controller->canPlaceObject(sc->selectionObjectsView.newObject, errorMsg))
|
||||
{
|
||||
auto obj = sc->selectionObjectsView.newObject;
|
||||
controller->commitObjectCreate(sc->level);
|
||||
|
||||
Reference in New Issue
Block a user