diff --git a/ChangeLog b/ChangeLog
index ddc418551..27ce67539 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -12,6 +12,8 @@ SPELLS:
* New configuration format: http://wiki.vcmi.eu/index.php?title=Spell_Format
MODS:
+* Support for submods - mod may have their own "submods" located in /Mods directory
+* Mods may provide their own changelogs and screenshots that will be visible in Launcher
* Mods cas now add new (offensive, buffs, debuffs) spells and change existing
0.94 -> 0.95 (Mar 01 2014)
diff --git a/config/schemas/mod.json b/config/schemas/mod.json
index e93b5d215..be0e5b9fd 100644
--- a/config/schemas/mod.json
+++ b/config/schemas/mod.json
@@ -31,6 +31,16 @@
"description": "Author of the mod. Can be nickname, real name or name of team"
},
+ "licenseName" : {
+ "type":"string",
+ "description": "Name of the license, recommended is Creative Commons Attribution-ShareAlike"
+ },
+
+ "licenseURL" : {
+ "type":"string",
+ "description": "Url to license text, e.g. http://creativecommons.org/licenses/by-sa/4.0/deed"
+ },
+
"contact" : {
"type":"string",
"description": "Home page of mod or link to forum thread"
@@ -47,6 +57,11 @@
"items": { "type":"string" }
},
+ "keepDisabled" : {
+ "type":"boolean",
+ "description": "If set to true, mod will not be enabled automatically on install"
+ },
+
"artifacts": {
"type":"array",
"description": "List of configuration files for artifacts",
@@ -72,12 +87,21 @@
"description": "List of configuration files for heroes",
"items": { "type":"string", "format" : "textFile" }
},
- "spells": {
+ "spells": {
"type":"array",
"description": "List of configuration files for spells",
"items": { "type":"string", "format" : "textFile" }
},
+ "changelog" : {
+ "type":"object",
+ "description": "List of changes/new features in each version",
+ "additionalProperties" : {
+ "type" : "array",
+ "items" : { "type":"string" }
+ }
+ },
+
"filesystem": {
"type":"object",
"description": "Optional, description on how files are organized in your mod. In most cases you do not need to use this field",
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index ecad516ce..0fe324636 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -10,6 +10,7 @@ set(launcher_modmanager_SRCS
modManager/cmodlistmodel_moc.cpp
modManager/cmodlistview_moc.cpp
modManager/cmodmanager.cpp
+ modManager/imageviewer.cpp
)
set(launcher_settingsview_SRCS
@@ -28,6 +29,7 @@ set(launcher_SRCS
set(launcher_FORMS
modManager/cmodlistview_moc.ui
+ modManager/imageviewer.ui
settingsView/csettingsview_moc.ui
mainwindow_moc.ui
)
diff --git a/launcher/mainwindow_moc.ui b/launcher/mainwindow_moc.ui
index 7f746adcc..2aa753c8b 100644
--- a/launcher/mainwindow_moc.ui
+++ b/launcher/mainwindow_moc.ui
@@ -97,7 +97,7 @@
false
- -1
+ 0
-
diff --git a/launcher/modManager/cdownloadmanager_moc.cpp b/launcher/modManager/cdownloadmanager_moc.cpp
index ce71f1248..b25c42d7d 100644
--- a/launcher/modManager/cdownloadmanager_moc.cpp
+++ b/launcher/modManager/cdownloadmanager_moc.cpp
@@ -113,3 +113,13 @@ void CDownloadManager::downloadProgressChanged(qint64 bytesReceived, qint64 byte
emit downloadProgress(received, total);
}
+
+bool CDownloadManager::downloadInProgress(const QUrl &url)
+{
+ for (auto & entry : currentDownloads)
+ {
+ if (entry.reply->url() == url)
+ return true;
+ }
+ return false;
+}
diff --git a/launcher/modManager/cmodlist.cpp b/launcher/modManager/cmodlist.cpp
index 628e58c05..e5f71e131 100644
--- a/launcher/modManager/cmodlist.cpp
+++ b/launcher/modManager/cmodlist.cpp
@@ -271,3 +271,16 @@ QVector CModList::getModList() const
}
return modList;
}
+
+QVector CModList::getChildren(QString parent) const
+{
+ QVector children;
+
+ int depth = parent.count('.') + 1;
+ for (const QString & mod : getModList())
+ {
+ if (mod.count('.') == depth && mod.startsWith(parent))
+ children.push_back(mod);
+ }
+ return children;
+}
diff --git a/launcher/modManager/cmodlist.h b/launcher/modManager/cmodlist.h
index f28a6e516..38b1eeeda 100644
--- a/launcher/modManager/cmodlist.h
+++ b/launcher/modManager/cmodlist.h
@@ -79,4 +79,6 @@ public:
// returns list of all available mods
QVector getModList() const;
+
+ QVector getChildren(QString parent) const;
};
diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp
index 9ff609beb..8ed2001c8 100644
--- a/launcher/modManager/cmodlistview_moc.cpp
+++ b/launcher/modManager/cmodlistview_moc.cpp
@@ -1,6 +1,7 @@
#include "StdInc.h"
#include "cmodlistview_moc.h"
#include "ui_cmodlistview_moc.h"
+#include "imageviewer.h"
#include
#include
@@ -68,7 +69,7 @@ CModListView::CModListView(QWidget *parent) :
ui->progressWidget->setVisible(false);
dlManager = nullptr;
- //loadRepositories();
+ loadRepositories();
hideModInfo();
}
@@ -106,6 +107,7 @@ void CModListView::showModInfo()
{
ui->modInfoWidget->show();
ui->hideModInfoButton->setArrowType(Qt::RightArrow);
+ loadScreenshots();
}
void CModListView::hideModInfo()
@@ -135,25 +137,60 @@ static QString replaceIfNotEmpty(QStringList value, QString pattern)
return "";
}
+QString CModListView::genChangelogText(CModEntry &mod)
+{
+ QString headerTemplate = "
%1:
";
+ QString entryBegin = "";
+ QString entryEnd = "
";
+ QString entryLine = "%1";
+ //QString versionSeparator = "
";
+
+ QString result;
+
+ QVariantMap changelog = mod.getValue("changelog").toMap();
+ QList versions = changelog.keys();
+
+ std::sort(versions.begin(), versions.end(), [](QString lesser, QString greater)
+ {
+ return !CModEntry::compareVersions(lesser, greater);
+ });
+
+ for (auto & version : versions)
+ {
+ result += headerTemplate.arg(version);
+ result += entryBegin;
+ for (auto & line : changelog.value(version).toStringList())
+ result += entryLine.arg(line);
+ result += entryEnd;
+ }
+ return result;
+}
+
QString CModListView::genModInfoText(CModEntry &mod)
{
QString prefix = "%1: "; // shared prefix
QString lineTemplate = prefix + "%2
";
- QString urlTemplate = prefix + "%2";
+ QString urlTemplate = prefix + "%3";
QString textTemplate = prefix + "%2
";
QString listTemplate = "%1: %2
";
QString noteTemplate = "%1
";
QString result;
- result += "";
result += replaceIfNotEmpty(mod.getValue("name"), lineTemplate.arg(tr("Mod name")));
result += replaceIfNotEmpty(mod.getValue("installedVersion"), lineTemplate.arg(tr("Installed version")));
result += replaceIfNotEmpty(mod.getValue("latestVersion"), lineTemplate.arg(tr("Latest version")));
- if (mod.getValue("size").toDouble() != 0)
+
+ if (mod.getValue("size").isValid())
result += replaceIfNotEmpty(CModEntry::sizeToString(mod.getValue("size").toDouble()), lineTemplate.arg(tr("Download size")));
result += replaceIfNotEmpty(mod.getValue("author"), lineTemplate.arg(tr("Authors")));
- result += replaceIfNotEmpty(mod.getValue("contact"), urlTemplate.arg(tr("Home")));
+
+ if (mod.getValue("licenseURL").isValid())
+ result += urlTemplate.arg(tr("License")).arg(mod.getValue("licenseURL").toString()).arg(mod.getValue("licenseName").toString());
+
+ if (mod.getValue("contact").isValid())
+ result += urlTemplate.arg(tr("Home")).arg(mod.getValue("contact").toString()).arg(mod.getValue("contact").toString());
+
result += replaceIfNotEmpty(mod.getValue("depends"), lineTemplate.arg(tr("Required mods")));
result += replaceIfNotEmpty(mod.getValue("conflicts"), lineTemplate.arg(tr("Conflicting mods")));
result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg(tr("Description")));
@@ -181,7 +218,6 @@ QString CModListView::genModInfoText(CModEntry &mod)
if (notes.size())
result += textTemplate.arg(tr("Notes")).arg(notes);
- result += "";
return result;
}
@@ -212,7 +248,8 @@ void CModListView::selectMod(const QModelIndex & index)
{
auto mod = modModel->getMod(index.data(ModRoles::ModNameRole).toString());
- ui->textBrowser->setHtml(genModInfoText(mod));
+ ui->modInfoBrowser->setHtml(genModInfoText(mod));
+ ui->changelogBrowser->setHtml(genChangelogText(mod));
bool hasInvalidDeps = !findInvalidDependencies(index.data(ModRoles::ModNameRole).toString()).empty();
bool hasBlockingMods = !findBlockingMods(index.data(ModRoles::ModNameRole).toString()).empty();
@@ -232,6 +269,8 @@ void CModListView::selectMod(const QModelIndex & index)
ui->installButton->setEnabled(!hasInvalidDeps);
ui->uninstallButton->setEnabled(!hasDependentMods);
ui->updateButton->setEnabled(!hasInvalidDeps && !hasDependentMods);
+
+ loadScreenshots();
}
}
@@ -239,7 +278,7 @@ void CModListView::keyPressEvent(QKeyEvent * event)
{
if (event->key() == Qt::Key_Escape && ui->modInfoWidget->isVisible() )
{
- ui->modInfoWidget->hide();
+ hideModInfo();
}
else
{
@@ -480,17 +519,23 @@ void CModListView::hideProgressBar()
void CModListView::installFiles(QStringList files)
{
QStringList mods;
+ QStringList images;
// TODO: some better way to separate zip's with mods and downloaded repository files
for (QString filename : files)
{
- if (filename.contains(".zip"))
+ if (filename.endsWith(".zip"))
mods.push_back(filename);
- if (filename.contains(".json"))
+ if (filename.endsWith(".json"))
manager->loadRepository(filename);
+ if (filename.endsWith(".png"))
+ images.push_back(filename);
}
if (!mods.empty())
installMods(mods);
+
+ if (!images.empty())
+ loadScreenshots();
}
void CModListView::installMods(QStringList archives)
@@ -536,8 +581,23 @@ void CModListView::installMods(QStringList archives)
for (int i=0; iinstallMod(modNames[i], archives[i]);
+ std::function enableMod = [&](QString modName)
+ {
+ auto mod = modModel->getMod(modName);
+ if (mod.isInstalled() && !mod.getValue("keepDisabled").toBool())
+ {
+ if (manager->enableMod(modName))
+ {
+ for (QString child : modModel->getChildren(modName))
+ enableMod(child);
+ }
+ }
+ };
+
for (QString mod : modsToEnable)
- manager->enableMod(mod);
+ {
+ enableMod(mod);
+ }
for (QString archive : archives)
QFile::remove(archive);
@@ -568,3 +628,50 @@ void CModListView::checkManagerErrors()
QMessageBox::warning(this, title, description, QMessageBox::Ok, QMessageBox::Ok );
}
}
+
+void CModListView::on_tabWidget_currentChanged(int index)
+{
+ loadScreenshots();
+}
+
+void CModListView::loadScreenshots()
+{
+ if (ui->tabWidget->currentIndex() == 2 && ui->modInfoWidget->isVisible())
+ {
+ ui->screenshotsList->clear();
+ QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString();
+ assert(modModel->hasMod(modName)); //should be filtered out by check above
+
+ for (QString & url : modModel->getMod(modName).getValue("screenshots").toStringList())
+ {
+ // URL must be encoded to something else to get rid of symbols illegal in file names
+ auto hashed = QCryptographicHash::hash(url.toUtf8(), QCryptographicHash::Md5);
+ auto hashedStr = QString::fromUtf8(hashed.toHex());
+
+ QString fullPath = CLauncherDirs::get().downloadsPath() + '/' + hashedStr + ".png";
+ QPixmap pixmap(fullPath);
+ if (pixmap.isNull())
+ {
+ // image file not exists or corrupted - try to redownload
+ downloadFile(hashedStr + ".png", url, "screenshots");
+ }
+ else
+ {
+ // managed to load cached image
+ QIcon icon(pixmap);
+ QListWidgetItem * item = new QListWidgetItem(icon, QString(tr("Screenshot %1")).arg(ui->screenshotsList->count() + 1));
+ ui->screenshotsList->addItem(item);
+ }
+ }
+ }
+}
+
+void CModListView::on_screenshotsList_clicked(const QModelIndex &index)
+{
+ if (index.isValid())
+ {
+ QIcon icon = ui->screenshotsList->item(index.row())->icon();
+ auto pixmap = icon.pixmap(icon.availableSizes()[0]);
+ ImageViewer::showPixmap(pixmap, this);
+ }
+}
diff --git a/launcher/modManager/cmodlistview_moc.h b/launcher/modManager/cmodlistview_moc.h
index 332f469db..7803fc4d9 100644
--- a/launcher/modManager/cmodlistview_moc.h
+++ b/launcher/modManager/cmodlistview_moc.h
@@ -50,6 +50,7 @@ class CModListView : public QWidget
void installMods(QStringList archives);
void installFiles(QStringList mods);
+ QString genChangelogText(CModEntry & mod);
QString genModInfoText(CModEntry & mod);
public:
explicit CModListView(QWidget *parent = 0);
@@ -57,6 +58,7 @@ public:
void showModInfo();
void hideModInfo();
+ void loadScreenshots();
void enableModInfo();
void disableModInfo();
@@ -91,6 +93,10 @@ private slots:
void on_allModsView_activated(const QModelIndex &index);
+ void on_tabWidget_currentChanged(int index);
+
+ void on_screenshotsList_clicked(const QModelIndex &index);
+
private:
Ui::CModListView *ui;
};
diff --git a/launcher/modManager/cmodlistview_moc.ui b/launcher/modManager/cmodlistview_moc.ui
index c9d9f74f2..d46cebe1b 100644
--- a/launcher/modManager/cmodlistview_moc.ui
+++ b/launcher/modManager/cmodlistview_moc.ui
@@ -202,50 +202,8 @@
0
- -
-
-
-
- 0
- 0
-
-
-
- true
-
-
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
-<html><head><meta name="qrichtext" content="1" /><style type="text/css">
-p, li { white-space: pre-wrap; }
-</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;">
-<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:11pt;"><br /></p></body></html>
-
-
- true
-
-
- true
-
-
-
- -
-
-
- Qt::Horizontal
-
-
- QSizePolicy::MinimumExpanding
-
-
-
- 0
- 20
-
-
-
-
- -
-
+
-
+
0
@@ -265,7 +223,7 @@ p, li { white-space: pre-wrap; }
- Enable
+ Uninstall
@@ -319,30 +277,21 @@ p, li { white-space: pre-wrap; }
- -
-
-
-
- 0
- 0
-
+
-
+
+
+ Qt::Horizontal
-
+
+ QSizePolicy::MinimumExpanding
+
+
- 51
- 0
+ 0
+ 20
-
-
- 100
- 16777215
-
-
-
- Uninstall
-
-
+
-
@@ -369,6 +318,149 @@ p, li { white-space: pre-wrap; }
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 51
+ 0
+
+
+
+
+ 100
+ 16777215
+
+
+
+ Enable
+
+
+
+ -
+
+
+ 0
+
+
+
+ Description
+
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ true
+
+
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html><head><meta name="qrichtext" content="1" /><style type="text/css">
+p, li { white-space: pre-wrap; }
+</style></head><body style=" font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;">
+<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:11pt;"><br /></p></body></html>
+
+
+ true
+
+
+ true
+
+
+
+
+
+
+
+ Changelog
+
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+ -
+
+
+
+
+
+
+ Screenshots
+
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+ -
+
+
+ Qt::ScrollBarAlwaysOff
+
+
+ QAbstractItemView::NoSelection
+
+
+ QAbstractItemView::SelectRows
+
+
+
+ 240
+ 180
+
+
+
+ QListView::IconMode
+
+
+ true
+
+
+
+
+
+
+
@@ -390,6 +482,9 @@ p, li { white-space: pre-wrap; }
+
+ 9
+
-
@@ -437,7 +532,6 @@ p, li { white-space: pre-wrap; }
lineEdit
comboBox
allModsView
- textBrowser
hideModInfoButton
enableButton
disableButton
diff --git a/launcher/modManager/imageviewer.cpp b/launcher/modManager/imageviewer.cpp
new file mode 100644
index 000000000..fe89a00a8
--- /dev/null
+++ b/launcher/modManager/imageviewer.cpp
@@ -0,0 +1,55 @@
+#include "StdInc.h"
+
+#include
+
+#include "imageviewer.h"
+#include "ui_imageviewer.h"
+
+ImageViewer::ImageViewer(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ImageViewer)
+{
+ ui->setupUi(this);
+}
+
+ImageViewer::~ImageViewer()
+{
+ delete ui;
+}
+
+QSize ImageViewer::calculateWindowSize()
+{
+ QDesktopWidget desktop;
+ return desktop.availableGeometry(desktop.primaryScreen()).size() * 0.8;
+}
+
+void ImageViewer::showPixmap(QPixmap & pixmap, QWidget *parent)
+{
+ assert(!pixmap.isNull());
+
+ ImageViewer * iw = new ImageViewer(parent);
+
+ QSize size = pixmap.size();
+ size.scale(iw->calculateWindowSize(), Qt::KeepAspectRatio);
+ iw->resize(size);
+
+ iw->setPixmap(pixmap);
+ iw->setAttribute(Qt::WA_DeleteOnClose, true);
+ iw->setModal(Qt::WindowModal);
+ iw->show();
+}
+
+void ImageViewer::setPixmap(QPixmap & pixmap)
+{
+ ui->label->setPixmap(pixmap);
+}
+
+void ImageViewer::mousePressEvent(QMouseEvent * event)
+{
+ close();
+}
+
+void ImageViewer::keyPressEvent(QKeyEvent * event)
+{
+ close(); // FIXME: it also closes on pressing modifiers (e.g. Ctrl/Alt). Not exactly expected
+}
diff --git a/launcher/modManager/imageviewer.h b/launcher/modManager/imageviewer.h
new file mode 100644
index 000000000..a09ce9557
--- /dev/null
+++ b/launcher/modManager/imageviewer.h
@@ -0,0 +1,31 @@
+#ifndef IMAGEVIEWER_H
+#define IMAGEVIEWER_H
+
+#include
+
+namespace Ui {
+ class ImageViewer;
+}
+
+class ImageViewer : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit ImageViewer(QWidget *parent = 0);
+ ~ImageViewer();
+
+ void setPixmap(QPixmap & pixmap);
+
+ static void showPixmap(QPixmap & pixmap, QWidget *parent = 0);
+protected:
+ void mousePressEvent(QMouseEvent * event);
+ void keyPressEvent(QKeyEvent * event);
+ QSize calculateWindowSize();
+
+
+private:
+ Ui::ImageViewer *ui;
+};
+
+#endif // IMAGEVIEWER_H
diff --git a/launcher/modManager/imageviewer.ui b/launcher/modManager/imageviewer.ui
new file mode 100644
index 000000000..52c26fc66
--- /dev/null
+++ b/launcher/modManager/imageviewer.ui
@@ -0,0 +1,58 @@
+
+
+ ImageViewer
+
+
+
+ 0
+ 0
+ 640
+ 480
+
+
+
+
+ 0
+ 0
+
+
+
+ Image Viewer
+
+
+
+ QLayout::SetNoConstraint
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+