Racer: Force-enable solid land
Otherwise, the land could degrade after each round, making the game progressively easier for later players
/*
* Hedgewars, a free turn based strategy game
* Copyright (c) 2006-2012 Igor Ulyanov <iulyanov@gmail.com>
*
* This program 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; version 2 of the License
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <QBitmap>
#include <QBuffer>
#include <QColor>
#include <QDebug>
#include <QFile>
#include <QFileDialog>
#include <QIcon>
#include <QInputDialog>
#include <QLabel>
#include <QLinearGradient>
#include <QLineEdit>
#include <QListView>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMessageBox>
#include <QPainter>
#include <QPushButton>
#include <QSlider>
#include <QStringListModel>
#include <QTextStream>
#include <QUuid>
#include <QVBoxLayout>
#include "hwconsts.h"
#include "mapContainer.h"
#include "themeprompt.h"
#include "seedprompt.h"
#include "igbox.h"
#include "HWApplication.h"
#include "ThemeModel.h"
HWMapContainer::HWMapContainer(QWidget * parent) :
QWidget(parent),
mainLayout(this),
pMap(0),
mapgen(MAPGEN_REGULAR),
m_previewSize(256, 128)
{
// don't show preview anything until first show event
m_previewEnabled = false;
m_missionsViewSetup = false;
m_staticViewSetup = false;
m_script = QString();
m_prevMapFeatureSize = 12;
m_mapFeatureSize = 12;
m_withoutDLC = false;
m_missingMap = false;
hhSmall.load(":/res/hh_small.png");
hhLimit = 18;
templateFilter = 0;
m_master = true;
linearGradNormal = QLinearGradient(QPoint(128, 0), QPoint(128, 128));
linearGradNormal.setColorAt(1, QColor(0, 0, 192));
linearGradNormal.setColorAt(0, QColor(66, 115, 225));
linearGradLoading = QLinearGradient(QPoint(128, 0), QPoint(128, 128));
linearGradLoading.setColorAt(1, QColor(58, 58, 137));
linearGradLoading.setColorAt(0, QColor(90, 109, 153));
linearGradMapError = QLinearGradient(QPoint(128, 0), QPoint(128, 128));
linearGradMapError.setColorAt(1, QColor(255, 1, 0));
linearGradMapError.setColorAt(0, QColor(255, 119, 0));
linearGradNoPreview = QLinearGradient(QPoint(128, 0), QPoint(128, 128));
linearGradNoPreview.setColorAt(1, QColor(15, 9, 72));
linearGradNoPreview.setColorAt(0, QColor(15, 9, 72));
mainLayout.setContentsMargins(HWApplication::style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
10,
HWApplication::style()->pixelMetric(QStyle::PM_LayoutRightMargin),
HWApplication::style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
m_staticMapModel = DataManager::instance().staticMapModel();
m_missionMapModel = DataManager::instance().missionMapModel();
m_themeModel = DataManager::instance().themeModel();
/* Layouts */
QWidget * topWidget = new QWidget();
QHBoxLayout * topLayout = new QHBoxLayout(topWidget);
topWidget->setContentsMargins(0, 0, 0, 0);
topLayout->setContentsMargins(0, 0, 0, 0);
twoColumnLayout = new QHBoxLayout();
QVBoxLayout * leftLayout = new QVBoxLayout();
leftLayout->setAlignment(Qt::AlignLeft);
QVBoxLayout * rightLayout = new QVBoxLayout();
twoColumnLayout->addLayout(leftLayout, 0);
twoColumnLayout->addLayout(rightLayout, 0);
QVBoxLayout * drawnControls = new QVBoxLayout();
/* Map type label */
QLabel* lblMapType = new QLabel(tr("Map type:"));
topLayout->setSpacing(10);
topLayout->addWidget(lblMapType, 0);
m_childWidgets << lblMapType;
/* Map type combobox */
cType = new QComboBox(this);
topLayout->addWidget(cType, 1);
cType->insertItem(0, tr("Image map"), MapModel::StaticMap);
cType->insertItem(1, tr("Mission map"), MapModel::MissionMap);
cType->insertItem(2, tr("Hand-drawn"), MapModel::HandDrawnMap);
cType->insertItem(3, tr("Randomly generated"), MapModel::GeneratedMap);
cType->insertItem(4, tr("Random maze"), MapModel::GeneratedMaze);
cType->insertItem(5, tr("Random perlin"), MapModel::GeneratedPerlin);
cType->insertItem(6, tr("Forts"), MapModel::FortsMap);
connect(cType, SIGNAL(currentIndexChanged(int)), this, SLOT(mapTypeChanged(int)));
m_childWidgets << cType;
/* Randomize button */
topLayout->addStretch(1);
const QIcon& lp = QIcon(":/res/dice.png");
QSize sz = lp.actualSize(QSize(65535, 65535));
btnRandomize = new QPushButton();
btnRandomize->setText(tr("Random"));
btnRandomize->setIcon(lp);
btnRandomize->setFixedHeight(30);
btnRandomize->setIconSize(sz);
btnRandomize->setFlat(true);
btnRandomize->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
connect(btnRandomize, SIGNAL(clicked()), this, SLOT(setRandomMap()));
m_childWidgets << btnRandomize;
btnRandomize->setStyleSheet("padding: 5px;");
btnRandomize->setFixedHeight(cType->height());
topLayout->addWidget(btnRandomize, 1);
/* Seed button */
btnSeed = new QPushButton(parentWidget()->parentWidget());
//: Refers to the "random seed"; the source of randomness in the game
btnSeed->setText(tr("Seed"));
btnSeed->setWhatsThis(tr("View and edit the seed, the source of randomness in the game"));
btnSeed->setStyleSheet("padding: 5px;");
btnSeed->setFixedHeight(cType->height());
connect(btnSeed, SIGNAL(clicked()), this, SLOT(showSeedPrompt()));
topLayout->addWidget(btnSeed, 0);
/* Map preview label */
QLabel * lblMapPreviewText = new QLabel(this);
lblMapPreviewText->setText(tr("Map preview:"));
leftLayout->addWidget(lblMapPreviewText, 0);
m_childWidgets << lblMapPreviewText;
/* Map Preview */
mapPreview = new QPushButton(this);
mapPreview->setObjectName("mapPreview");
mapPreview->setFlat(true);
mapPreview->setFixedSize(256 + 6, 128 + 6);
mapPreview->setContentsMargins(0, 0, 0, 0);
leftLayout->addWidget(mapPreview, 0);
connect(mapPreview, SIGNAL(clicked()), this, SLOT(previewClicked()));
m_childWidgets << mapPreview;
/* Bottom-Left layout */
QVBoxLayout * bottomLeftLayout = new QVBoxLayout();
leftLayout->addLayout(bottomLeftLayout, 1);
/* Map list label */
lblMapList = new QLabel(this);
lblMapList->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
lblMapList->setAlignment(Qt::AlignTop | Qt::AlignLeft);
rightLayout->addWidget(lblMapList, 0);
m_childWidgets << lblMapList;
/* Static maps list */
staticMapList = new QListView(this);
rightLayout->addWidget(staticMapList, 1);
m_childWidgets << staticMapList;
/* Mission maps list */
missionMapList = new QListView(this);
rightLayout->addWidget(missionMapList, 1);
m_childWidgets << missionMapList;
/* Map name (when not room master) */
/* We use a QTextEdit instead of QLabel because it is able
to wrap at any character. */
teMapName = new QTextEdit(this);
teMapName->setObjectName("mapName");
teMapName->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
teMapName->setAlignment(Qt::AlignTop | Qt::AlignLeft);
/* Boilerplate to emulate a QLabel */
teMapName->setReadOnly(true);
teMapName->setAcceptRichText(false);
teMapName->setFrameStyle(QFrame::NoFrame);
teMapName->setStyleSheet("background-color: transparent");
teMapName->setLineWrapMode(QTextEdit::WidgetWidth);
teMapName->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
rightLayout->addWidget(teMapName, 1);
m_childWidgets << teMapName;
/* Map load and edit buttons */
drawnControls->addStretch(1);
QPixmap pmLoad(":/res/Load.png");
QIcon iconLoad = QIcon(pmLoad);
sz = iconLoad.actualSize(QSize(48, 48));
btnLoadMap = new QPushButton(tr("Load"));
btnLoadMap->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
btnLoadMap->setWhatsThis(tr("Load map drawing"));
btnLoadMap->setStyleSheet("padding: 5px;");
btnLoadMap->setFixedHeight(50);
btnLoadMap->setIcon(iconLoad);
btnLoadMap->setIconSize(sz);
btnLoadMap->setFlat(true);
drawnControls->addWidget(btnLoadMap, 0);
m_childWidgets << btnLoadMap;
connect(btnLoadMap, SIGNAL(clicked()), this, SLOT(loadDrawing()));
QPixmap pmEdit(":/res/edit.png");
QIcon iconEdit = QIcon(pmEdit);
sz = iconEdit.actualSize(QSize(48, 48));
btnEditMap = new QPushButton(tr("Edit"));
btnEditMap->setWhatsThis(tr("Edit map drawing"));
btnEditMap->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
btnEditMap->setStyleSheet("padding: 5px;");
btnEditMap->setFixedHeight(50);
btnEditMap->setIcon(iconEdit);
btnEditMap->setIconSize(sz);
btnEditMap->setFlat(true);
drawnControls->addWidget(btnEditMap, 0);
m_childWidgets << btnEditMap;
connect(btnEditMap, SIGNAL(clicked()), this, SIGNAL(drawMapRequested()));
drawnControls->addStretch(1);
rightLayout->addLayout(drawnControls);
/* Generator style list */
generationStyles = new QListWidget(this);
new QListWidgetItem(tr("All"), generationStyles);
new QListWidgetItem(tr("Small"), generationStyles);
new QListWidgetItem(tr("Medium"), generationStyles);
new QListWidgetItem(tr("Large"), generationStyles);
new QListWidgetItem(tr("Cavern"), generationStyles);
new QListWidgetItem(tr("Wacky"), generationStyles);
connect(generationStyles, SIGNAL(currentRowChanged(int)), this, SLOT(setTemplateFilter(int)));
m_childWidgets << generationStyles;
rightLayout->addWidget(generationStyles, 1);
/* Maze style list */
mazeStyles = new QListWidget(this);
new QListWidgetItem(tr("Small tunnels"), mazeStyles);
new QListWidgetItem(tr("Medium tunnels"), mazeStyles);
new QListWidgetItem(tr("Large tunnels"), mazeStyles);
new QListWidgetItem(tr("Small islands"), mazeStyles);
new QListWidgetItem(tr("Medium islands"), mazeStyles);
new QListWidgetItem(tr("Large islands"), mazeStyles);
connect(mazeStyles, SIGNAL(currentRowChanged(int)), this, SLOT(setMazeSize(int)));
m_childWidgets << mazeStyles;
rightLayout->addWidget(mazeStyles, 1);
mapFeatureSize = new QSlider(Qt::Horizontal, this);
mapFeatureSize->setObjectName("mapFeatureSize");
//mapFeatureSize->setTickPosition(QSlider::TicksBelow);
mapFeatureSize->setMaximum(25);
mapFeatureSize->setMinimum(1);
//mapFeatureSize->setFixedWidth(259);
mapFeatureSize->setPageStep(5);
mapFeatureSize->setValue(m_mapFeatureSize);
mapFeatureSize->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
bottomLeftLayout->addWidget(mapFeatureSize, 0);
connect(mapFeatureSize, SIGNAL(valueChanged(int)), this, SLOT(setFeatureSize(int)));
m_childWidgets << mapFeatureSize;
/* Mission description */
lblDesc = new QLabel();
lblDesc->setWordWrap(true);
lblDesc->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
lblDesc->setAlignment(Qt::AlignBottom | Qt::AlignLeft);
lblDesc->setStyleSheet("font: 10px;");
bottomLeftLayout->addWidget(lblDesc, 100);
/* Add stretch above theme button */
bottomLeftLayout->addStretch(1);
/* Theme chooser */
QHBoxLayout * themeHBox = new QHBoxLayout();
btnRandTheme = new QPushButton();
btnRandTheme->setWhatsThis(tr("Randomize the theme"));
btnRandTheme->setIcon(lp);
btnRandTheme->setIconSize(QSize(24, 24));
btnRandTheme->setFixedHeight(30);
btnRandTheme->setFixedWidth(30);
connect(btnRandTheme, SIGNAL(clicked()), this, SLOT(setRandomTheme()));
m_childWidgets << btnRandTheme;
themeHBox->addWidget(btnRandTheme, 0);
btnTheme = new QPushButton(this);
btnTheme->setWhatsThis(tr("Choose a theme"));
btnTheme->setFlat(true);
btnTheme->setIconSize(QSize(30, 30));
btnTheme->setFixedHeight(30);
btnTheme->setMaximumWidth(222);
connect(btnTheme, SIGNAL(clicked()), this, SLOT(showThemePrompt()));
m_childWidgets << btnTheme;
themeHBox->addWidget(btnTheme, 1);
bottomLeftLayout->addLayout(themeHBox);
/* Add everything to main layout */
mainLayout.addWidget(topWidget, 0);
mainLayout.addLayout(twoColumnLayout, 1);
/* Set defaults */
setRandomSeed();
setMazeSize(0);
setTemplateFilter(0);
staticMapChanged(m_staticMapModel->index(0, 0));
missionMapChanged(m_missionMapModel->index(0, 0));
changeMapType(MapModel::GeneratedMap);
}
void HWMapContainer::onImageReceived(const QPixmap &newImage)
{
// When image received from the engine.
switch (m_mapInfo.type)
{
case MapModel::GeneratedMap:
case MapModel::GeneratedMaze:
case MapModel::GeneratedPerlin:
case MapModel::HandDrawnMap:
case MapModel::FortsMap:
setImage(newImage);
break;
// Throw away image if we have switched the map mode in the meantime
default:
return;
}
}
void HWMapContainer::setImage(const QPixmap &newImage)
{
addInfoToPreview(newImage);
pMap = 0;
cType->setEnabled(isMaster());
}
void HWMapContainer::setImage(const QPixmap &newImage, const QLinearGradient &linearGrad, bool showHHLimit)
{
addInfoToPreview(newImage, linearGrad, showHHLimit);
pMap = 0;
cType->setEnabled(isMaster());
}
void HWMapContainer::setHHLimit(int newHHLimit)
{
hhLimit = newHHLimit;
}
void HWMapContainer::addInfoToPreview(const QPixmap &image)
{
addInfoToPreview(image, linearGradNormal, true);
}
// Should this add text to identify map size?
void HWMapContainer::addInfoToPreview(const QPixmap &image, const QLinearGradient &linearGrad, bool drawHHLimit)
{
QPixmap finalImage = QPixmap(image.size());
QPainter p(&finalImage);
p.fillRect(finalImage.rect(), linearGrad);
p.drawPixmap(finalImage.rect(), image);
if (drawHHLimit)
{
p.setPen(QColor(0xff,0xcc,0x00));
p.setBrush(QColor(0, 0, 0));
p.setFont(QFont("MS Shell Dlg", 10));
p.drawRect(finalImage.rect().width() - hhSmall.rect().width() - 28, 3, 40, 20);
QString text = (hhLimit > 0) ? QString::number(hhLimit) : "?";
p.drawText(finalImage.rect().width() - hhSmall.rect().width() - 14 - (hhLimit > 9 ? 10 : 0), 18, text);
p.drawPixmap(finalImage.rect().width() - hhSmall.rect().width() - 5, 5, hhSmall.rect().width(), hhSmall.rect().height(), hhSmall);
}
// Set the map preview image. Make sure it is always colored the same,
// no matter if disabled or not.
QIcon mapPreviewIcon = QIcon();
mapPreviewIcon.addPixmap(finalImage, QIcon::Normal);
mapPreviewIcon.addPixmap(finalImage, QIcon::Disabled);
mapPreview->setIcon(mapPreviewIcon);
mapPreview->setIconSize(finalImage.size());
}
void HWMapContainer::askForGeneratedPreview()
{
pMap = new HWMap(this);
connect(pMap, SIGNAL(ImageReceived(QPixmap)), this, SLOT(onImageReceived(const QPixmap)));
connect(pMap, SIGNAL(HHLimitReceived(int)), this, SLOT(setHHLimit(int)));
connect(pMap, SIGNAL(destroyed(QObject *)), this, SLOT(onPreviewMapDestroyed(QObject *)));
pMap->getImage(m_seed,
getTemplateFilter(),
get_mapgen(),
getMazeSize(),
getDrawnMapData(),
m_script,
m_scriptparam,
m_mapFeatureSize
);
setHHLimit(0);
QPixmap waitImage(m_previewSize);
waitImage.fill(Qt::transparent);
QPainter p(&waitImage);
const QPixmap waitIcon(":/res/iconTime.png");
int x = (waitImage.width() - waitIcon.width()) / 2;
int y = (waitImage.height() - waitIcon.height()) / 2;
p.drawPixmap(QPoint(x, y), waitIcon);
setImage(waitImage, linearGradLoading, false);
cType->setEnabled(false);
}
void HWMapContainer::previewClicked()
{
if (isMaster()) // should only perform these if master, but disabling the button when not, causes an unattractive preview.
switch (m_mapInfo.type)
{
case MapModel::HandDrawnMap:
emit drawMapRequested();
break;
default:
setRandomMap();
break;
}
}
QString HWMapContainer::getCurrentSeed() const
{
return m_seed;
}
QString HWMapContainer::getCurrentMap() const
{
switch (m_mapInfo.type)
{
case MapModel::StaticMap:
case MapModel::MissionMap:
return m_curMap;
default:
return QString();
}
}
QString HWMapContainer::getCurrentTheme() const
{
return(m_theme);
}
bool HWMapContainer::getCurrentIsMission() const
{
return(m_mapInfo.type == MapModel::MissionMap);
}
int HWMapContainer::getCurrentHHLimit() const
{
return hhLimit;
}
QString HWMapContainer::getCurrentScheme() const
{
return(m_mapInfo.scheme);
}
QString HWMapContainer::getCurrentWeapons() const
{
return(m_mapInfo.weapons);
}
quint32 HWMapContainer::getTemplateFilter() const
{
return generationStyles->currentRow();
}
quint32 HWMapContainer::getFeatureSize() const
{
return m_mapFeatureSize;
}
void HWMapContainer::resizeEvent ( QResizeEvent * event )
{
Q_UNUSED(event);
}
void HWMapContainer::intSetSeed(const QString & seed)
{
m_seed = seed;
}
void HWMapContainer::setSeed(const QString & seed)
{
intSetSeed(seed);
if ((m_mapInfo.type == MapModel::GeneratedMap)
|| (m_mapInfo.type == MapModel::GeneratedMaze)
|| (m_mapInfo.type == MapModel::GeneratedPerlin))
updatePreview();
}
void HWMapContainer::setScript(const QString & script, const QString & scriptparam)
{
m_script = script;
m_scriptparam = scriptparam;
if ((m_mapInfo.type == MapModel::GeneratedMap)
|| (m_mapInfo.type == MapModel::GeneratedMaze)
|| (m_mapInfo.type == MapModel::GeneratedPerlin)
|| (m_mapInfo.type == MapModel::HandDrawnMap))
updatePreview();
}
void HWMapContainer::intSetMap(const QString & map)
{
if (map == "+rnd+")
{
//changeMapType(MapModel::GeneratedMap);
}
else if (map == "+maze+")
{
//changeMapType(MapModel::GeneratedMaze);
}
else if (map == "+perlin+")
{
//changeMapType(MapModel::GeneratedPerlin);
}
else if (map == "+drawn+")
{
//changeMapType(MapModel::HandDrawnMap);
}
else if (map == "+forts+")
{
//nuffin
}
else if (m_staticMapModel->mapExists(map))
{
m_missingMap = false;
changeMapType(MapModel::StaticMap, m_staticMapModel->index(m_staticMapModel->findMap(map), 0));
}
else if (m_missionMapModel->mapExists(map))
{
m_missingMap = false;
changeMapType(MapModel::MissionMap, m_missionMapModel->index(m_missionMapModel->findMap(map), 0));
} else
{
qDebug() << "HWMapContainer::intSetMap: Map doesn't exist: " << map;
m_missingMap = true;
m_curMap = map;
m_mapInfo.name = map;
setMapNameLabel(map, false);
if (m_mapInfo.type == MapModel::StaticMap)
setupStaticMapsView(m_curMap);
else if (m_mapInfo.type == MapModel::MissionMap)
setupMissionMapsView(m_curMap);
else
{
m_mapInfo.type = MapModel::StaticMap;
setupStaticMapsView(m_curMap);
changeMapType(m_mapInfo.type, QModelIndex());
}
updatePreview();
}
}
void HWMapContainer::setMap(const QString & map)
{
if ((m_mapInfo.type == MapModel::Invalid) || (map != m_mapInfo.name) || m_missingMap)
intSetMap(map);
}
void HWMapContainer::setTheme(const QString & theme)
{
QModelIndexList mdl = m_themeModel->match(m_themeModel->index(0), ThemeModel::ActualNameRole, theme);
if(mdl.size())
updateTheme(mdl.at(0));
else
setMissingTheme(theme);
}
void HWMapContainer::setRandomMap()
{
if (!m_master) return;
setRandomSeed();
QSortFilterProxyModel * mmodel = NULL;
switch(m_mapInfo.type)
{
case MapModel::GeneratedMap:
case MapModel::GeneratedMaze:
case MapModel::GeneratedPerlin:
case MapModel::FortsMap:
setRandomTheme();
break;
case MapModel::MissionMap:
if (m_withoutDLC)
{
mmodel = m_missionMapModel->withoutDLC();
missionMapChanged(mmodel->mapToSource(mmodel->index(rand() % mmodel->rowCount(),0)));
}
else
missionMapChanged(m_missionMapModel->index(rand() % m_missionMapModel->rowCount(),0));
break;
case MapModel::StaticMap:
if (m_withoutDLC)
{
mmodel = m_staticMapModel->withoutDLC();
staticMapChanged(mmodel->mapToSource(mmodel->index(rand() % mmodel->rowCount(),0)));
}
else
staticMapChanged(m_staticMapModel->index(rand() % m_staticMapModel->rowCount(),0));
break;
default:
break;
}
}
void HWMapContainer::setRandomSeed()
{
setSeed(QUuid::createUuid().toString());
emit seedChanged(m_seed);
}
void HWMapContainer::setRandomWithoutDLC(bool withoutDLC)
{
m_withoutDLC = withoutDLC;
}
void HWMapContainer::setRandomTheme()
{
QAbstractItemModel * tmodel;
if (m_withoutDLC)
tmodel = m_themeModel->withoutDLCOrHidden();
else
tmodel = m_themeModel->withoutHidden();
if(!tmodel->rowCount()) return;
quint32 themeNum = rand() % tmodel->rowCount();
updateTheme(tmodel->index(themeNum,0));
emit themeChanged(m_theme);
}
void HWMapContainer::intSetTemplateFilter(int filter)
{
generationStyles->setCurrentRow(filter);
emit newTemplateFilter(filter);
}
void HWMapContainer::setTemplateFilter(int filter)
{
intSetTemplateFilter(filter);
if (m_mapInfo.type == MapModel::GeneratedMap)
updatePreview();
}
MapGenerator HWMapContainer::get_mapgen(void) const
{
return mapgen;
}
int HWMapContainer::getMazeSize(void) const
{
return mazeStyles->currentRow();
}
void HWMapContainer::intSetMazeSize(int size)
{
mazeStyles->setCurrentRow(size);
emit mazeSizeChanged(size);
}
void HWMapContainer::setMazeSize(int size)
{
intSetMazeSize(size);
if (m_mapInfo.type == MapModel::GeneratedMaze)
updatePreview();
}
void HWMapContainer::intSetMapgen(MapGenerator m)
{
if (mapgen != m)
{
mapgen = m;
bool f = false;
switch (m)
{
case MAPGEN_REGULAR:
m_mapInfo.type = MapModel::GeneratedMap;
f = true;
break;
case MAPGEN_MAZE:
m_mapInfo.type = MapModel::GeneratedMaze;
f = true;
break;
case MAPGEN_PERLIN:
m_mapInfo.type = MapModel::GeneratedPerlin;
f = true;
break;
case MAPGEN_DRAWN:
m_mapInfo.type = MapModel::HandDrawnMap;
f = true;
break;
case MAPGEN_FORTS:
m_mapInfo.type = MapModel::FortsMap;
f = true;
break;
case MAPGEN_MAP:
switch (m_mapInfo.type)
{
case MapModel::GeneratedMap:
case MapModel::GeneratedMaze:
case MapModel::GeneratedPerlin:
case MapModel::HandDrawnMap:
case MapModel::FortsMap:
m_mapInfo.type = MapModel::Invalid;
default:
break;
}
break;
}
if(f)
changeMapType(m_mapInfo.type, QModelIndex());
}
}
void HWMapContainer::setMapgen(MapGenerator m)
{
intSetMapgen(m);
if(m != MAPGEN_MAP)
updatePreview();
}
void HWMapContainer::setDrawnMapData(const QByteArray & ar)
{
drawMapScene.decode(ar);
updatePreview();
}
QByteArray HWMapContainer::getDrawnMapData()
{
return drawMapScene.encode();
}
void HWMapContainer::setNewSeed(const QString & newSeed)
{
setSeed(newSeed);
emit seedChanged(newSeed);
}
DrawMapScene * HWMapContainer::getDrawMapScene()
{
return &drawMapScene;
}
void HWMapContainer::mapDrawingFinished()
{
emit drawnMapChanged(getDrawnMapData());
updatePreview();
}
void HWMapContainer::showEvent(QShowEvent * event)
{
if (!m_previewEnabled)
{
m_previewEnabled = true;
setRandomTheme();
updatePreview();
}
QWidget::showEvent(event);
}
void HWMapContainer::updatePreview()
{
// abort if the widget isn't supposed to show anything yet
if (!m_previewEnabled)
return;
if (pMap)
{
disconnect(pMap, 0, this, SLOT(onImageReceived(const QPixmap)));
disconnect(pMap, 0, this, SLOT(setHHLimit(int)));
disconnect(pMap, 0, this, SLOT(onPreviewMapDestroyed(QObject *)));
pMap = 0;
}
QPixmap failPixmap;
QIcon failIcon;
switch(m_mapInfo.type)
{
case MapModel::Invalid:
// Map error image
failPixmap = QPixmap(":/res/missingMap.png");
setImage(failPixmap, linearGradMapError, false);
lblDesc->clear();
break;
case MapModel::GeneratedMap:
case MapModel::GeneratedMaze:
case MapModel::GeneratedPerlin:
case MapModel::HandDrawnMap:
case MapModel::FortsMap:
askForGeneratedPreview();
break;
default:
// For maps loaded from image
if(m_missingMap)
{
// Map error image due to missing map
failPixmap = QPixmap(":/res/missingMap.png");
setImage(failPixmap, linearGradMapError, false);
lblDesc->clear();
break;
}
else
{
// Draw map preview
QPixmap mapImage;
bool success = mapImage.load("physfs://Maps/" + m_mapInfo.name + "/preview.png");
setHHLimit(m_mapInfo.limit);
if(!success)
{
// Missing preview image
QPixmap empty = QPixmap(m_previewSize);
empty.fill(Qt::transparent);
setImage(empty, linearGradNoPreview, true);
return;
}
setImage(mapImage);
}
}
}
void HWMapContainer::setAllMapParameters(const QString &map, MapGenerator m, int mazesize, const QString &seed, int tmpl, int featureSize)
{
intSetMapgen(m);
intSetMazeSize(mazesize);
intSetSeed(seed);
intSetTemplateFilter(tmpl);
// this one last because it will refresh the preview
intSetMap(map);
intSetMazeSize(mazesize);
intSetFeatureSize(featureSize);
}
void HWMapContainer::updateModelViews()
{
// restore theme selection
// do this before map selection restore, because map may overwrite theme
if (!m_theme.isNull() && !m_theme.isEmpty())
{
QModelIndexList mdl = m_themeModel->match(m_themeModel->index(0), Qt::DisplayRole, m_theme);
if (mdl.size() > 0)
updateTheme(mdl.at(0));
else
setRandomTheme();
}
// restore map selection
if (!m_curMap.isNull() && !m_curMap.isEmpty())
intSetMap(m_curMap);
else
updatePreview();
}
void HWMapContainer::onPreviewMapDestroyed(QObject * map)
{
if (map == pMap)
pMap = 0;
}
void HWMapContainer::mapTypeChanged(int index)
{
changeMapType((MapModel::MapType)cType->itemData(index).toInt());
}
void HWMapContainer::updateHelpTexts(MapModel::MapType type)
{
QString randomAll = tr("Randomize the map, theme and seed");
QString randomNoMap = tr("Randomize the theme and seed");
QString randomSeed = tr("Randomize the seed");
QString randomAllPrev = tr("Click to randomize the map, theme and seed");
QString randomNoMapPrev = tr("Click to randomize the theme and seed");
QString mfsComplex = QString(tr("Adjust the complexity of the generated map"));
QString mfsFortsDistance = QString(tr("Adjust the distance between forts"));
QString mfsDrawnMap = QString(tr("Scale size of the drawn map"));
switch (type)
{
case MapModel::GeneratedMap:
case MapModel::GeneratedPerlin:
case MapModel::GeneratedMaze:
mapPreview->setWhatsThis(randomAllPrev);
mapFeatureSize->setWhatsThis(mfsComplex);
btnRandomize->setWhatsThis(randomAll);
break;
case MapModel::MissionMap:
case MapModel::StaticMap:
mapPreview->setWhatsThis(randomAllPrev);
btnRandomize->setWhatsThis(randomAll);
break;
case MapModel::HandDrawnMap:
mapPreview->setWhatsThis(tr("Click to edit"));
btnRandomize->setWhatsThis(randomSeed);
mapFeatureSize->setWhatsThis(mfsDrawnMap);
break;
case MapModel::FortsMap:
mapPreview->setWhatsThis(randomNoMapPrev);
mapFeatureSize->setWhatsThis(mfsFortsDistance);
btnRandomize->setWhatsThis(randomNoMap);
break;
default:
break;
}
}
void HWMapContainer::changeMapType(MapModel::MapType type, const QModelIndex & newMap)
{
staticMapList->hide();
missionMapList->hide();
teMapName->hide();
lblMapList->hide();
generationStyles->hide();
mazeStyles->hide();
lblDesc->hide();
btnLoadMap->hide();
btnEditMap->hide();
mapFeatureSize->show();
switch (type)
{
case MapModel::GeneratedMap:
mapgen = MAPGEN_REGULAR;
setMapInfo(MapModel::MapInfoRandom);
lblMapList->setText(tr("Map size:"));
lblMapList->show();
generationStyles->show();
break;
case MapModel::GeneratedMaze:
mapgen = MAPGEN_MAZE;
setMapInfo(MapModel::MapInfoMaze);
lblMapList->setText(tr("Maze style:"));
lblMapList->show();
mazeStyles->show();
break;
case MapModel::GeneratedPerlin:
mapgen = MAPGEN_PERLIN;
setMapInfo(MapModel::MapInfoPerlin);
lblMapList->setText(tr("Style:"));
lblMapList->show();
mazeStyles->show();
break;
case MapModel::HandDrawnMap:
mapgen = MAPGEN_DRAWN;
setMapInfo(MapModel::MapInfoDrawn);
btnLoadMap->show();
//mapFeatureSize->hide();
btnEditMap->show();
break;
case MapModel::MissionMap:
setupMissionMapsView();
mapgen = MAPGEN_MAP;
missionMapChanged(newMap.isValid() ? newMap : missionMapList->currentIndex());
lblMapList->setText(tr("Mission:"));
lblMapList->show();
setMapNameLabel(m_curMap, !m_missingMap);
if(m_master)
{
missionMapList->show();
}
else
{
teMapName->show();
}
mapFeatureSize->hide();
lblDesc->setText(m_mapInfo.desc);
lblDesc->show();
emit mapChanged(m_curMap);
break;
case MapModel::StaticMap:
setupStaticMapsView();
mapgen = MAPGEN_MAP;
staticMapChanged(newMap.isValid() ? newMap : staticMapList->currentIndex());
lblMapList->setText(tr("Map:"));
lblMapList->show();
setMapNameLabel(m_curMap, !m_missingMap);
if(m_master)
{
staticMapList->show();
}
else
{
teMapName->show();
}
mapFeatureSize->hide();
emit mapChanged(m_curMap);
break;
case MapModel::FortsMap:
mapgen = MAPGEN_FORTS;
setMapInfo(MapModel::MapInfoForts);
lblMapList->hide();
break;
default:
break;
}
// Update theme button size
updateThemeButtonSize();
// Update “What's This?” help texts
updateHelpTexts(type);
// Update cType combobox
for (int i = 0; i < cType->count(); i++)
{
if ((MapModel::MapType)cType->itemData(i).toInt() == type)
{
cType->setCurrentIndex(i);
break;
}
}
repaint();
emit mapgenChanged(mapgen);
}
void HWMapContainer::intSetFeatureSize(int val)
{
mapFeatureSize->setValue(val);
updateHelpTexts((MapModel::MapType)cType->itemData(cType->currentIndex()).toInt());
emit mapFeatureSizeChanged(val);
}
void HWMapContainer::setFeatureSize(int val)
{
m_mapFeatureSize = val;
intSetFeatureSize(val);
m_prevMapFeatureSize = m_mapFeatureSize;
updatePreview();
}
// unused because I needed the space for the slider
void HWMapContainer::updateThemeButtonSize()
{
if (m_mapInfo.type != MapModel::StaticMap)
{
btnTheme->setIconSize(QSize(30, 30));
btnTheme->setFixedHeight(30);
btnRandTheme->setFixedHeight(30);
btnRandTheme->setIconSize(QSize(24, 24));
}
else
{
QSize iconSize = btnTheme->icon().actualSize(QSize(65535, 65535));
btnRandTheme->setFixedHeight(64);
btnTheme->setFixedHeight(64);
btnTheme->setIconSize(iconSize);
btnRandTheme->setIconSize(QSize(32, 32));
}
repaint();
}
void HWMapContainer::showThemePrompt()
{
ThemePrompt prompt(m_themeID, this);
int theme = prompt.exec() - 1; // Since 0 means canceled, so all indexes are +1'd
if (theme < 0) return;
QModelIndex current = m_themeModel->index(theme, 0);
updateTheme(current);
emit themeChanged(m_theme);
}
void HWMapContainer::updateTheme(const QModelIndex & current)
{
m_theme = selectedTheme = current.data(ThemeModel::ActualNameRole).toString();
m_themeID = current.row();
QIcon icon = current.data(Qt::DecorationRole).value<QIcon>();
btnTheme->setIcon(icon);
QString themeLabel = tr("Theme: %1").arg(current.data(Qt::DisplayRole).toString());
btnTheme->setText(themeLabel);
updateThemeButtonSize();
}
void HWMapContainer::staticMapChanged(const QModelIndex & map, const QModelIndex & old)
{
mapChanged(map, 0, old);
}
void HWMapContainer::missionMapChanged(const QModelIndex & map, const QModelIndex & old)
{
mapChanged(map, 1, old);
}
// Type: 0 = static, 1 = mission
void HWMapContainer::mapChanged(const QModelIndex & map, int type, const QModelIndex & old)
{
QListView * mapList;
if (type == 0)
{
mapList = staticMapList;
m_mapInfo.type = MapModel::StaticMap;
}
else if (type == 1)
{
mapList = missionMapList;
m_mapInfo.type = MapModel::MissionMap;
}
else
return;
// Make sure it is a valid index
if (!map.isValid())
{
// Make sure there's always a valid selection in the map list
if (old.isValid())
{
mapList->setCurrentIndex(old);
mapList->scrollTo(old);
}
m_mapInfo.type = MapModel::Invalid;
m_missingMap = true;
updatePreview();
return;
}
// If map changed, update list selection
if (mapList->currentIndex() != map)
{
mapList->setCurrentIndex(map);
mapList->scrollTo(map);
}
if (m_missingMap)
{
m_missingMap = false;
updatePreview();
}
Q_ASSERT(map.data(Qt::UserRole + 1).canConvert<MapModel::MapInfo>()); // Houston, we have a problem.
setMapInfo(map.data(Qt::UserRole + 1).value<MapModel::MapInfo>());
}
void HWMapContainer::setMapInfo(MapModel::MapInfo mapInfo)
{
m_mapInfo = mapInfo;
m_curMap = m_mapInfo.name;
// the map has no pre-defined theme, so let's use the selected one
if (m_mapInfo.theme.isNull() || m_mapInfo.theme.isEmpty())
{
if (!selectedTheme.isNull() && !selectedTheme.isEmpty())
{
// Fall back to a default theme if current theme is a background theme or hidden
QModelIndexList mdl = m_themeModel->match(m_themeModel->index(0), ThemeModel::ActualNameRole, m_theme);
if (mdl.size() > 0)
{
if ((mdl.at(0).data(ThemeModel::IsBackgroundThemeRole).toBool() == true) || (mdl.at(0).data(ThemeModel::IsHiddenRole).toBool() == true))
{
selectedTheme = "Nature";
}
}
setTheme(selectedTheme);
emit themeChanged(selectedTheme);
}
}
else
{
setTheme(m_mapInfo.theme);
emit themeChanged(m_mapInfo.theme);
}
lblDesc->setText(mapInfo.desc);
updatePreview();
emit mapChanged(m_curMap);
}
void HWMapContainer::loadDrawing()
{
QString loadDir = QDir(cfgdir->absolutePath() + "/DrawnMaps").absolutePath();
QString fileName = QFileDialog::getOpenFileName(this, tr("Load drawn map"), loadDir, tr("Drawn Maps") + " (*.hwmap);;" + tr("All files") + " (*)");
if(fileName.isEmpty()) return;
QFile f(fileName);
if(!f.open(QIODevice::ReadOnly))
{
QMessageBox errorMsg(parentWidget());
errorMsg.setIcon(QMessageBox::Warning);
errorMsg.setWindowTitle(QMessageBox::tr("File error"));
errorMsg.setText(QMessageBox::tr("Cannot open '%1' for reading").arg(fileName));
errorMsg.setTextFormat(Qt::PlainText);
errorMsg.setWindowModality(Qt::WindowModal);
errorMsg.exec();
}
else
{
drawMapScene.decode(qUncompress(QByteArray::fromBase64(f.readAll())));
mapDrawingFinished();
}
}
void HWMapContainer::showSeedPrompt()
{
SeedPrompt prompt(parentWidget()->parentWidget(), getCurrentSeed(), isMaster());
connect(&prompt, SIGNAL(seedSelected(const QString &)), this, SLOT(setNewSeed(const QString &)));
prompt.exec();
}
bool HWMapContainer::isMaster()
{
return m_master;
}
void HWMapContainer::setMaster(bool master)
{
if (master == m_master) return;
m_master = master;
foreach (QWidget *widget, m_childWidgets)
widget->setEnabled(master);
if(m_mapInfo.type == MapModel::StaticMap)
{
teMapName->setHidden(master);
staticMapList->setVisible(master);
}
else if(m_mapInfo.type == MapModel::MissionMap)
{
teMapName->setHidden(master);
missionMapList->setVisible(master);
}
if(master)
{
// Room delegation cleanup if we get room control.
if(m_missingMap)
{
// Reset map if we don't have the host's map
m_missingMap = false;
if(m_mapInfo.type == MapModel::MissionMap)
{
missionMapList->selectionModel()->setCurrentIndex(m_missionMapModel->index(0, 0), QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent);
}
else
{
if(m_mapInfo.type != MapModel::StaticMap)
{
changeMapType(MapModel::StaticMap);
}
staticMapList->selectionModel()->setCurrentIndex(m_staticMapModel->index(0, 0), QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent);
}
}
else
{
// Set random theme if we don't have it
QModelIndexList mdl = m_themeModel->match(m_themeModel->index(0), ThemeModel::ActualNameRole, m_theme);
if(!mdl.size())
setRandomTheme();
}
}
else
{
setMapNameLabel(m_curMap, true);
}
}
void HWMapContainer::setMissingTheme(const QString & name)
{
if (name.isNull() || name.isEmpty()) return;
m_theme = name;
QPixmap pixMissing = QPixmap(":/res/missingTheme@2x.png");
QIcon iconMissing = QIcon();
iconMissing.addPixmap(pixMissing, QIcon::Normal);
iconMissing.addPixmap(pixMissing, QIcon::Disabled);
btnTheme->setIcon(iconMissing);
// Question mark in front of theme name denotes it's missing
btnTheme->setText(tr("Theme: %1").arg("?" + name));
updateThemeButtonSize();
}
void HWMapContainer::setupMissionMapsView(const QString & initialMap)
{
if(m_missionsViewSetup) return;
m_missionsViewSetup = true;
m_missionMapModel->loadMaps();
missionMapList->setModel(m_missionMapModel);
missionMapList->setEditTriggers(QAbstractItemView::NoEditTriggers);
QItemSelectionModel * missionSelectionModel = missionMapList->selectionModel();
connect(missionSelectionModel,
SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)),
this,
SLOT(missionMapChanged(const QModelIndex &, const QModelIndex &)));
int m = 0;
if(!initialMap.isNull())
m = m_missionMapModel->findMap(initialMap);
missionSelectionModel->setCurrentIndex(m_missionMapModel->index(m, 0), QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent);
}
void HWMapContainer::setupStaticMapsView(const QString & initialMap)
{
if(m_staticViewSetup) return;
m_staticViewSetup = true;
m_staticMapModel->loadMaps();
staticMapList->setModel(m_staticMapModel);
staticMapList->setEditTriggers(QAbstractItemView::NoEditTriggers);
QItemSelectionModel * staticSelectionModel = staticMapList->selectionModel();
connect(staticSelectionModel,
SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)),
this,
SLOT(staticMapChanged(const QModelIndex &, const QModelIndex &)));
int m = 0;
if(!initialMap.isNull())
m = m_staticMapModel->findMap(initialMap);
staticSelectionModel->setCurrentIndex(m_staticMapModel->index(m, 0), QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent);
}
// Call this function instead of setting the text of the map name label
// directly.
void HWMapContainer::setMapNameLabel(QString mapName, bool validMap)
{
// Cut off insanely long names to be displayed
if(mapName.length() >= 90)
{
mapName.truncate(84);
mapName.append(" (...)");
}
teMapName->setPlainText(mapName);
if(validMap)
teMapName->setStyleSheet("background-color: transparent;");
else
teMapName->setStyleSheet("background-color: transparent; color: #b50000;");
}