1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Support for new fields in mod format including:

- screenshots, repository-only field, will be used by launcher to d/l
screeenshots
- screenshots will be viewed as thumbnails with switch to full view on
click
- changelog that is now visible in launcher
- somewhat better handling of submods by launcher
This commit is contained in:
Ivan Savenko 2014-03-23 12:08:01 +00:00
parent 760ae7d44a
commit 9dda194ed3
13 changed files with 483 additions and 79 deletions

View File

@ -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 <modname>/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)

View File

@ -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",

View File

@ -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
)

View File

@ -97,7 +97,7 @@
<bool>false</bool>
</property>
<property name="currentRow">
<number>-1</number>
<number>0</number>
</property>
<item>
<property name="text">

View File

@ -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;
}

View File

@ -271,3 +271,16 @@ QVector<QString> CModList::getModList() const
}
return modList;
}
QVector<QString> CModList::getChildren(QString parent) const
{
QVector<QString> children;
int depth = parent.count('.') + 1;
for (const QString & mod : getModList())
{
if (mod.count('.') == depth && mod.startsWith(parent))
children.push_back(mod);
}
return children;
}

View File

@ -79,4 +79,6 @@ public:
// returns list of all available mods
QVector<QString> getModList() const;
QVector<QString> getChildren(QString parent) const;
};

View File

@ -1,6 +1,7 @@
#include "StdInc.h"
#include "cmodlistview_moc.h"
#include "ui_cmodlistview_moc.h"
#include "imageviewer.h"
#include <QJsonArray>
#include <QCryptographicHash>
@ -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 = "<p><span style=\" font-weight:600;\">%1: </span></p>";
QString entryBegin = "<p align=\"justify\"><ul>";
QString entryEnd = "</ul></p>";
QString entryLine = "<li>%1</li>";
//QString versionSeparator = "<hr/>";
QString result;
QVariantMap changelog = mod.getValue("changelog").toMap();
QList<QString> 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 = "<p><span style=\" font-weight:600;\">%1: </span>"; // shared prefix
QString lineTemplate = prefix + "%2</p>";
QString urlTemplate = prefix + "<a href=\"%2\"><span style=\" text-decoration: underline; color:#0000ff;\">%2</span></a></p>";
QString urlTemplate = prefix + "<a href=\"%2\">%3</a></p>";
QString textTemplate = prefix + "</p><p align=\"justify\">%2</p>";
QString listTemplate = "<p align=\"justify\">%1: %2</p>";
QString noteTemplate = "<p align=\"justify\">%1</p>";
QString result;
result += "<html><body>";
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 += "</body></html>";
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; i<modNames.size(); i++)
manager->installMod(modNames[i], archives[i]);
std::function<void(QString)> 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);
}
}

View File

@ -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;
};

View File

@ -202,50 +202,8 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0" colspan="6">
<widget class="QTextBrowser" name="textBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="modButtonSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="enableButton">
<item row="1" column="4">
<widget class="QPushButton" name="uninstallButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -265,7 +223,7 @@ p, li { white-space: pre-wrap; }
</size>
</property>
<property name="text">
<string>Enable</string>
<string>Uninstall</string>
</property>
</widget>
</item>
@ -319,30 +277,21 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QPushButton" name="uninstallButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="1" column="0">
<spacer name="modButtonSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="minimumSize">
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>51</width>
<height>0</height>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Uninstall</string>
</property>
</widget>
</spacer>
</item>
<item row="1" column="5">
<widget class="QPushButton" name="installButton">
@ -369,6 +318,149 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="enableButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>51</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="6">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabInfo">
<attribute name="title">
<string>Description</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QTextBrowser" name="modInfoBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabChangelog">
<attribute name="title">
<string>Changelog</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QTextBrowser" name="changelogBrowser"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabScreenshots">
<attribute name="title">
<string>Screenshots</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_6">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item row="0" column="0">
<widget class="QListWidget" name="screenshotsList">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>240</width>
<height>180</height>
</size>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
@ -390,6 +482,9 @@ p, li { white-space: pre-wrap; }
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="sizePolicy">
@ -437,7 +532,6 @@ p, li { white-space: pre-wrap; }
<tabstop>lineEdit</tabstop>
<tabstop>comboBox</tabstop>
<tabstop>allModsView</tabstop>
<tabstop>textBrowser</tabstop>
<tabstop>hideModInfoButton</tabstop>
<tabstop>enableButton</tabstop>
<tabstop>disableButton</tabstop>

View File

@ -0,0 +1,55 @@
#include "StdInc.h"
#include <QDesktopWidget>
#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
}

View File

@ -0,0 +1,31 @@
#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H
#include <QDialog>
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

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ImageViewer</class>
<widget class="QDialog" name="ImageViewer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>480</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Image Viewer</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>