VCMI Launcher/Mod manager. See forum post for details.
@@ -229,14 +229,18 @@
|
|||||||
"type" : "object",
|
"type" : "object",
|
||||||
"default": {},
|
"default": {},
|
||||||
"additionalProperties" : false,
|
"additionalProperties" : false,
|
||||||
"required" : [ "repositoryURL" ],
|
"required" : [ "repositoryURL", "enableInstalledMods" ],
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"repositoryURL" : {
|
"repositoryURL" : {
|
||||||
"type" : "array",
|
"type" : "array",
|
||||||
"default" : [ ],
|
"default" : [ ],
|
||||||
"items" : {
|
"items" : {
|
||||||
"type" : "string"
|
"type" : "string"
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
"enableInstalledMods" : {
|
||||||
|
"type" : "boolean",
|
||||||
|
"default" : true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
58
launcher/CMakeLists.txt
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
project(vcmilauncher)
|
||||||
|
cmake_minimum_required(VERSION 2.8.7)
|
||||||
|
|
||||||
|
include_directories(${CMAKE_HOME_DIRECTORY} ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
include_directories(${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
set(launcher_modmanager_SRCS
|
||||||
|
modManager/cdownloadmanager.cpp
|
||||||
|
modManager/cmodlist.cpp
|
||||||
|
modManager/cmodlistmodel.cpp
|
||||||
|
modManager/cmodlistview.cpp
|
||||||
|
modManager/cmodmanager.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(launcher_settingsview_SRCS
|
||||||
|
settingsView/csettingsview.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(launcher_SRCS
|
||||||
|
${launcher_modmanager_SRCS}
|
||||||
|
${launcher_settingsview_SRCS}
|
||||||
|
main.cpp
|
||||||
|
mainwindow.cpp
|
||||||
|
launcherdirs.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
set(launcher_FORMS
|
||||||
|
modManager/cmodlistview.ui
|
||||||
|
settingsView/csettingsview.ui
|
||||||
|
mainwindow.ui
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tell CMake to run moc when necessary:
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
# As moc files are generated in the binary dir, tell CMake
|
||||||
|
# to always look for includes there:
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
|
||||||
|
# We need add -DQT_WIDGETS_LIB when using QtWidgets in Qt 5.
|
||||||
|
add_definitions(${Qt5Widgets_DEFINITIONS})
|
||||||
|
add_definitions(${Qt5Network_DEFINITIONS})
|
||||||
|
|
||||||
|
# Executables fail to build with Qt 5 in the default configuration
|
||||||
|
# without -fPIE. We add that here.
|
||||||
|
set(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS} ${CMAKE_CXX_FLAGS}")
|
||||||
|
|
||||||
|
qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS})
|
||||||
|
|
||||||
|
add_executable(vcmilauncher ${launcher_SRCS} ${launcher_UI_HEADERS})
|
||||||
|
|
||||||
|
# The Qt5Widgets_LIBRARIES variable also includes QtGui and QtCore
|
||||||
|
target_link_libraries(vcmilauncher vcmi ${Qt5Widgets_LIBRARIES} ${Qt5Network_LIBRARIES})
|
||||||
|
|
||||||
|
if (NOT APPLE) # Already inside bundle
|
||||||
|
install(TARGETS vcmilauncher DESTINATION ${BIN_DIR})
|
||||||
|
endif()
|
||||||
|
|
1
launcher/StdInc.cpp
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#include "StdInc.h"
|
11
launcher/StdInc.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../Global.h"
|
||||||
|
|
||||||
|
#include <QtWidgets>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
#include <QFile>
|
BIN
launcher/icons/menu-game.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
launcher/icons/menu-mods.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
launcher/icons/menu-settings.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
launcher/icons/mod-delete.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
launcher/icons/mod-disabled.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
launcher/icons/mod-download.png
Normal file
After Width: | Height: | Size: 895 B |
BIN
launcher/icons/mod-enabled.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
launcher/icons/mod-update.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
26
launcher/launcherdirs.cpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "StdInc.h"
|
||||||
|
#include "launcherdirs.h"
|
||||||
|
|
||||||
|
#include "../lib/VCMIDirs.h"
|
||||||
|
|
||||||
|
static CLauncherDirs launcherDirsGlobal;
|
||||||
|
|
||||||
|
CLauncherDirs::CLauncherDirs()
|
||||||
|
{
|
||||||
|
QDir().mkdir(downloadsPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
CLauncherDirs & CLauncherDirs::get()
|
||||||
|
{
|
||||||
|
return launcherDirsGlobal;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CLauncherDirs::downloadsPath()
|
||||||
|
{
|
||||||
|
return QString::fromUtf8(VCMIDirs::get().userCachePath().c_str()) + "/downloads";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CLauncherDirs::modsPath()
|
||||||
|
{
|
||||||
|
return QString::fromUtf8(VCMIDirs::get().userCachePath().c_str()) + "/Mods";
|
||||||
|
}
|
13
launcher/launcherdirs.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// similar to lib/VCMIDirs, controls where all launcher-related data will be stored
|
||||||
|
class CLauncherDirs
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CLauncherDirs();
|
||||||
|
|
||||||
|
static CLauncherDirs & get();
|
||||||
|
|
||||||
|
QString downloadsPath();
|
||||||
|
QString modsPath();
|
||||||
|
};
|
11
launcher/main.cpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include "mainwindow.h"
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
MainWindow w;
|
||||||
|
w.show();
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
77
launcher/mainwindow.cpp
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include "StdInc.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "ui_mainwindow.h"
|
||||||
|
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QDir>
|
||||||
|
|
||||||
|
#include "../lib/CConfigHandler.h"
|
||||||
|
#include "../lib/VCMIDirs.h"
|
||||||
|
#include "../lib/filesystem/Filesystem.h"
|
||||||
|
#include "../lib/logging/CBasicLogConfigurator.h"
|
||||||
|
|
||||||
|
void MainWindow::load()
|
||||||
|
{
|
||||||
|
console = new CConsoleHandler;
|
||||||
|
CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() + "/VCMI_Launcher_log.txt", console);
|
||||||
|
logConfig.configureDefault();
|
||||||
|
|
||||||
|
CResourceHandler::initialize();
|
||||||
|
CResourceHandler::loadMainFileSystem("config/filesystem.json");
|
||||||
|
|
||||||
|
for (auto & string : VCMIDirs::get().dataPaths())
|
||||||
|
QDir::addSearchPath("icons", QString::fromUtf8(string.c_str()) + "/launcher/icons");
|
||||||
|
QDir::addSearchPath("icons", QString::fromUtf8(VCMIDirs::get().userDataPath().c_str()) + "/launcher/icons");
|
||||||
|
|
||||||
|
settings.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::MainWindow(QWidget *parent) :
|
||||||
|
QMainWindow(parent),
|
||||||
|
ui(new Ui::MainWindow)
|
||||||
|
{
|
||||||
|
load(); // load FS before UI
|
||||||
|
|
||||||
|
ui->setupUi(this);
|
||||||
|
ui->tabListWidget->setCurrentIndex(0);
|
||||||
|
|
||||||
|
connect(ui->tabSelectList, SIGNAL(currentRowChanged(int)),
|
||||||
|
ui->tabListWidget, SLOT(setCurrentIndex(int)));
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::on_startGameButon_clicked()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
QString clientName = "VCMI_Client.exe";
|
||||||
|
#else
|
||||||
|
// TODO: Right now launcher will only start vcmi from system-default locations
|
||||||
|
QString clientName = "vcmiclient";
|
||||||
|
#endif
|
||||||
|
startExecutable(clientName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::startExecutable(QString name)
|
||||||
|
{
|
||||||
|
QProcess process;
|
||||||
|
|
||||||
|
// Start the executable
|
||||||
|
if (process.startDetached(name))
|
||||||
|
{
|
||||||
|
close(); // exit launcher
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this,
|
||||||
|
"Error starting executable",
|
||||||
|
"Failed to start " + name + ": " + process.errorString(),
|
||||||
|
QMessageBox::Ok,
|
||||||
|
QMessageBox::Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
launcher/mainwindow.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QMainWindow>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class MainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
class QTableWidgetItem;
|
||||||
|
|
||||||
|
class MainWindow : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
void load();
|
||||||
|
void startExecutable(QString name);
|
||||||
|
public:
|
||||||
|
explicit MainWindow(QWidget *parent = 0);
|
||||||
|
~MainWindow();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void on_startGameButon_clicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MainWindow *ui;
|
||||||
|
};
|
206
launcher/mainwindow.ui
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>800</width>
|
||||||
|
<height>480</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>VCMI Launcher</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>icons:menu-game.png</normaloff>icons:menu-game.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralWidget">
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QListWidget" name="tabSelectList">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>65</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="verticalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="showDropIndicator" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="dragDropMode">
|
||||||
|
<enum>QAbstractItemView::NoDragDrop</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>48</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="movement">
|
||||||
|
<enum>QListView::Static</enum>
|
||||||
|
</property>
|
||||||
|
<property name="resizeMode">
|
||||||
|
<enum>QListView::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="gridSize">
|
||||||
|
<size>
|
||||||
|
<width>64</width>
|
||||||
|
<height>64</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="viewMode">
|
||||||
|
<enum>QListView::IconMode</enum>
|
||||||
|
</property>
|
||||||
|
<property name="uniformItemSizes">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="selectionRectVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="currentRow">
|
||||||
|
<number>-1</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Mods</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>icons:menu-mods.png</normaloff>icons:menu-mods.png</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Settings</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>icons:menu-settings.png</normaloff>icons:menu-settings.png</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QToolButton" name="startGameButon">
|
||||||
|
<property name="text">
|
||||||
|
<string>Play</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>icons:menu-game.png</normaloff>icons:menu-game.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>60</width>
|
||||||
|
<height>60</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="toolButtonStyle">
|
||||||
|
<enum>Qt::ToolButtonIconOnly</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="startGameTitle">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Start game</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1" rowspan="3">
|
||||||
|
<widget class="QStackedWidget" name="tabListWidget">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<widget class="CModListView" name="stackedWidgetPage2"/>
|
||||||
|
<widget class="CSettingsView" name="stackedWidgetPage3"/>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>CModListView</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>modManager/cmodlistview.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>CSettingsView</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>settingsView/csettingsview.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>tabSelectList</tabstop>
|
||||||
|
<tabstop>startGameButon</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
114
launcher/modManager/cdownloadmanager.cpp
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#include "StdInc.h"
|
||||||
|
#include "cdownloadmanager.h"
|
||||||
|
|
||||||
|
#include "launcherdirs.h"
|
||||||
|
|
||||||
|
CDownloadManager::CDownloadManager()
|
||||||
|
{
|
||||||
|
connect(&manager, SIGNAL(finished(QNetworkReply*)),
|
||||||
|
SLOT(downloadFinished(QNetworkReply*)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDownloadManager::downloadFile(const QUrl &url, const QString &file)
|
||||||
|
{
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
FileEntry entry;
|
||||||
|
entry.file.reset(new QFile(CLauncherDirs::get().downloadsPath() + '/' + file));
|
||||||
|
entry.bytesReceived = 0;
|
||||||
|
entry.totalSize = 0;
|
||||||
|
|
||||||
|
if (entry.file->open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||||
|
{
|
||||||
|
entry.status = FileEntry::IN_PROGRESS;
|
||||||
|
entry.reply = manager.get(request);
|
||||||
|
|
||||||
|
connect(entry.reply, SIGNAL(downloadProgress(qint64, qint64)),
|
||||||
|
SLOT(downloadProgressChanged(qint64, qint64)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
entry.status = FileEntry::FAILED;
|
||||||
|
entry.reply = nullptr;
|
||||||
|
encounteredErrors += entry.file->errorString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// even if failed - add it into list to report it in finished() call
|
||||||
|
currentDownloads.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
CDownloadManager::FileEntry & CDownloadManager::getEntry(QNetworkReply * reply)
|
||||||
|
{
|
||||||
|
assert(reply);
|
||||||
|
for (auto & entry : currentDownloads)
|
||||||
|
{
|
||||||
|
if (entry.reply == reply)
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
assert(0);
|
||||||
|
static FileEntry errorValue;
|
||||||
|
return errorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDownloadManager::downloadFinished(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
FileEntry & file = getEntry(reply);
|
||||||
|
|
||||||
|
if (file.reply->error())
|
||||||
|
{
|
||||||
|
encounteredErrors += file.reply->errorString();
|
||||||
|
file.file->remove();
|
||||||
|
file.status = FileEntry::FAILED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file.file->write(file.reply->readAll());
|
||||||
|
file.file->close();
|
||||||
|
file.status = FileEntry::FINISHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.reply->deleteLater();
|
||||||
|
|
||||||
|
bool downloadComplete = true;
|
||||||
|
for (auto & entry : currentDownloads)
|
||||||
|
{
|
||||||
|
if (entry.status == FileEntry::IN_PROGRESS)
|
||||||
|
{
|
||||||
|
downloadComplete = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList successful;
|
||||||
|
QStringList failed;
|
||||||
|
|
||||||
|
for (auto & entry : currentDownloads)
|
||||||
|
{
|
||||||
|
if (entry.status == FileEntry::FINISHED)
|
||||||
|
successful += entry.file->fileName();
|
||||||
|
else
|
||||||
|
failed += entry.file->fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadComplete)
|
||||||
|
emit finished(successful, failed, encounteredErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDownloadManager::downloadProgressChanged(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
|
{
|
||||||
|
auto reply = dynamic_cast<QNetworkReply *>(sender());
|
||||||
|
FileEntry & entry = getEntry(reply);
|
||||||
|
|
||||||
|
entry.file->write(entry.reply->readAll());
|
||||||
|
entry.bytesReceived = bytesReceived;
|
||||||
|
entry.totalSize = bytesTotal;
|
||||||
|
|
||||||
|
quint64 total = 0;
|
||||||
|
for (auto & entry : currentDownloads)
|
||||||
|
total += entry.totalSize > 0 ? entry.totalSize : 0;
|
||||||
|
|
||||||
|
quint64 received = 0;
|
||||||
|
for (auto & entry : currentDownloads)
|
||||||
|
received += entry.bytesReceived > 0 ? entry.bytesReceived : 0;
|
||||||
|
|
||||||
|
emit downloadProgress(received, total);
|
||||||
|
}
|
56
launcher/modManager/cdownloadmanager.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
|
||||||
|
class QFile;
|
||||||
|
|
||||||
|
class CDownloadManager: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
struct FileEntry
|
||||||
|
{
|
||||||
|
enum Status
|
||||||
|
{
|
||||||
|
IN_PROGRESS,
|
||||||
|
FINISHED,
|
||||||
|
FAILED
|
||||||
|
};
|
||||||
|
|
||||||
|
QNetworkReply * reply;
|
||||||
|
QSharedPointer<QFile> file;
|
||||||
|
Status status;
|
||||||
|
qint64 bytesReceived;
|
||||||
|
qint64 totalSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
QStringList encounteredErrors;
|
||||||
|
|
||||||
|
QNetworkAccessManager manager;
|
||||||
|
|
||||||
|
QList<FileEntry> currentDownloads;
|
||||||
|
|
||||||
|
FileEntry & getEntry(QNetworkReply * reply);
|
||||||
|
public:
|
||||||
|
CDownloadManager();
|
||||||
|
|
||||||
|
// returns true if download with such URL is in progress/queued
|
||||||
|
// FIXME: not sure what's right place for "mod download in progress" check
|
||||||
|
bool downloadInProgress(const QUrl &url);
|
||||||
|
|
||||||
|
// returns network reply so caller can connect to required signals
|
||||||
|
void downloadFile(const QUrl &url, const QString &file);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void downloadFinished(QNetworkReply *reply);
|
||||||
|
void downloadProgressChanged(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// for status bar updates. Merges all queued downloads into one
|
||||||
|
void downloadProgress(qint64 currentAmount, qint64 maxAmount);
|
||||||
|
|
||||||
|
// called when all files were downloaded and manager goes to idle state
|
||||||
|
// Lists contains files that were successfully downloaded / failed to download
|
||||||
|
void finished(QStringList savedFiles, QStringList failedFiles, QStringList errors);
|
||||||
|
};
|
204
launcher/modManager/cmodlist.cpp
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
#include "StdInc.h"
|
||||||
|
#include "cmodlist.h"
|
||||||
|
|
||||||
|
bool CModEntry::compareVersions(QString lesser, QString greater)
|
||||||
|
{
|
||||||
|
static const int maxSections = 3; // versions consist from up to 3 sections, major.minor.patch
|
||||||
|
|
||||||
|
QStringList lesserList = lesser.split(".");
|
||||||
|
QStringList greaterList = greater.split(".");
|
||||||
|
|
||||||
|
assert(lesserList.size() <= maxSections);
|
||||||
|
assert(greaterList.size() <= maxSections);
|
||||||
|
|
||||||
|
for (int i=0; i< maxSections; i++)
|
||||||
|
{
|
||||||
|
if (greaterList.size() <= i) // 1.1.1 > 1.1
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (lesserList.size() <= i) // 1.1 < 1.1.1
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (lesserList[i].toInt() != greaterList[i].toInt())
|
||||||
|
return lesserList[i].toInt() < greaterList[i].toInt(); // 1.1 < 1.2
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CModEntry::CModEntry(QJsonObject repository, QJsonObject localData, QJsonValue modSettings, QString modname):
|
||||||
|
repository(repository),
|
||||||
|
localData(localData),
|
||||||
|
modSettings(modSettings),
|
||||||
|
modname(modname)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModEntry::isEnabled() const
|
||||||
|
{
|
||||||
|
if (!isInstalled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return modSettings.toBool(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModEntry::isDisabled() const
|
||||||
|
{
|
||||||
|
if (!isInstalled())
|
||||||
|
return false;
|
||||||
|
return !isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModEntry::isAvailable() const
|
||||||
|
{
|
||||||
|
if (isInstalled())
|
||||||
|
return false;
|
||||||
|
return !repository.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModEntry::isUpdateable() const
|
||||||
|
{
|
||||||
|
if (!isInstalled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QString installedVer = localData["installedVersion"].toString();
|
||||||
|
QString availableVer = repository["latestVersion"].toString();
|
||||||
|
|
||||||
|
if (compareVersions(installedVer, availableVer))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModEntry::isInstalled() const
|
||||||
|
{
|
||||||
|
return !localData.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CModEntry::getModStatus() const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(isEnabled() ? ModStatus::ENABLED : 0) |
|
||||||
|
(isInstalled() ? ModStatus::INSTALLED : 0) |
|
||||||
|
(isUpdateable()? ModStatus::UPDATEABLE : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CModEntry::getName() const
|
||||||
|
{
|
||||||
|
return modname;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant CModEntry::getValue(QString value) const
|
||||||
|
{
|
||||||
|
if (repository.contains(value))
|
||||||
|
return repository[value].toVariant();
|
||||||
|
|
||||||
|
if (localData.contains(value))
|
||||||
|
return localData[value].toVariant();
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject CModList::copyField(QJsonObject data, QString from, QString to)
|
||||||
|
{
|
||||||
|
QJsonObject renamed;
|
||||||
|
|
||||||
|
for (auto it = data.begin(); it != data.end(); it++)
|
||||||
|
{
|
||||||
|
QJsonObject object = it.value().toObject();
|
||||||
|
|
||||||
|
object.insert(to, object.value(from));
|
||||||
|
renamed.insert(it.key(), QJsonValue(object));
|
||||||
|
}
|
||||||
|
return renamed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModList::addRepository(QJsonObject data)
|
||||||
|
{
|
||||||
|
repositores.push_back(copyField(data, "version", "latestVersion"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModList::setLocalModList(QJsonObject data)
|
||||||
|
{
|
||||||
|
localModList = copyField(data, "version", "installedVersion");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModList::setModSettings(QJsonObject data)
|
||||||
|
{
|
||||||
|
modSettings = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
CModEntry CModList::getMod(QString modname) const
|
||||||
|
{
|
||||||
|
assert(hasMod(modname));
|
||||||
|
|
||||||
|
QJsonObject repo;
|
||||||
|
QJsonObject local = localModList[modname].toObject();
|
||||||
|
QJsonValue settings = modSettings[modname];
|
||||||
|
|
||||||
|
for (auto entry : repositores)
|
||||||
|
{
|
||||||
|
if (entry.contains(modname))
|
||||||
|
{
|
||||||
|
if (repo.empty())
|
||||||
|
repo = entry[modname].toObject();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (CModEntry::compareVersions(repo["version"].toString(),
|
||||||
|
entry[modname].toObject()["version"].toString()))
|
||||||
|
repo = entry[modname].toObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CModEntry(repo, local, settings, modname);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModList::hasMod(QString modname) const
|
||||||
|
{
|
||||||
|
if (localModList.contains(modname))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto entry : repositores)
|
||||||
|
if (entry.contains(modname))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList CModList::getRequirements(QString modname)
|
||||||
|
{
|
||||||
|
QStringList ret;
|
||||||
|
|
||||||
|
if (hasMod(modname))
|
||||||
|
{
|
||||||
|
auto mod = getMod(modname);
|
||||||
|
|
||||||
|
for (auto entry : mod.getValue("depends").toStringList())
|
||||||
|
ret += getRequirements(entry);
|
||||||
|
}
|
||||||
|
ret += modname;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<QString> CModList::getModList() const
|
||||||
|
{
|
||||||
|
QSet<QString> knownMods;
|
||||||
|
QVector<QString> modList;
|
||||||
|
for (auto repo : repositores)
|
||||||
|
{
|
||||||
|
for (auto it = repo.begin(); it != repo.end(); it++)
|
||||||
|
{
|
||||||
|
knownMods.insert(it.key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto it = localModList.begin(); it != localModList.end(); it++)
|
||||||
|
{
|
||||||
|
knownMods.insert(it.key());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto entry : knownMods)
|
||||||
|
{
|
||||||
|
modList.push_back(entry);
|
||||||
|
}
|
||||||
|
return modList;
|
||||||
|
}
|
77
launcher/modManager/cmodlist.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
namespace ModStatus
|
||||||
|
{
|
||||||
|
enum EModStatus
|
||||||
|
{
|
||||||
|
MASK_NONE = 0,
|
||||||
|
ENABLED = 1,
|
||||||
|
INSTALLED = 2,
|
||||||
|
UPDATEABLE = 4,
|
||||||
|
MASK_ALL = 255
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class CModEntry
|
||||||
|
{
|
||||||
|
// repository contains newest version only (if multiple are available)
|
||||||
|
QJsonObject repository;
|
||||||
|
QJsonObject localData;
|
||||||
|
QJsonValue modSettings;
|
||||||
|
|
||||||
|
QString modname;
|
||||||
|
public:
|
||||||
|
CModEntry(QJsonObject repository, QJsonObject localData, QJsonValue modSettings, QString modname);
|
||||||
|
|
||||||
|
// installed and enabled
|
||||||
|
bool isEnabled() const;
|
||||||
|
// installed but disabled
|
||||||
|
bool isDisabled() const;
|
||||||
|
// available in any of repositories but not installed
|
||||||
|
bool isAvailable() const;
|
||||||
|
// installed and greater version exists in repository
|
||||||
|
bool isUpdateable() const;
|
||||||
|
// installed
|
||||||
|
bool isInstalled() const;
|
||||||
|
|
||||||
|
// see ModStatus enum
|
||||||
|
int getModStatus() const;
|
||||||
|
|
||||||
|
QString getName() const;
|
||||||
|
|
||||||
|
// get value of some field in mod structure. Returns empty optional if value is not present
|
||||||
|
QVariant getValue(QString value) const;
|
||||||
|
|
||||||
|
// returns true if less < greater comparing versions section by section
|
||||||
|
static bool compareVersions(QString lesser, QString greater);
|
||||||
|
};
|
||||||
|
|
||||||
|
class CModList
|
||||||
|
{
|
||||||
|
QVector<QJsonObject> repositores;
|
||||||
|
QJsonObject localModList;
|
||||||
|
QJsonObject modSettings;
|
||||||
|
|
||||||
|
QJsonObject copyField(QJsonObject data, QString from, QString to);
|
||||||
|
public:
|
||||||
|
virtual void addRepository(QJsonObject data);
|
||||||
|
virtual void setLocalModList(QJsonObject data);
|
||||||
|
virtual void setModSettings(QJsonObject data);
|
||||||
|
|
||||||
|
// returns mod by name. Note: mod MUST exist
|
||||||
|
CModEntry getMod(QString modname) const;
|
||||||
|
|
||||||
|
// returns list of all mods necessary to run selected one, including mod itself
|
||||||
|
// order is: first mods in list don't have any dependencies, last mod is modname
|
||||||
|
// note: may include mods not present in list
|
||||||
|
QStringList getRequirements(QString modname);
|
||||||
|
|
||||||
|
bool hasMod(QString modname) const;
|
||||||
|
|
||||||
|
// returns list of all available mods
|
||||||
|
QVector<QString> getModList() const;
|
||||||
|
};
|
193
launcher/modManager/cmodlistmodel.cpp
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#include "StdInc.h"
|
||||||
|
#include "cmodlistmodel.h"
|
||||||
|
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
|
namespace ModFields
|
||||||
|
{
|
||||||
|
static const QString names [ModFields::COUNT] =
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"modType",
|
||||||
|
"name",
|
||||||
|
"version",
|
||||||
|
"size",
|
||||||
|
"author"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const QString header [ModFields::COUNT] =
|
||||||
|
{
|
||||||
|
"", // status icon
|
||||||
|
"", // status icon
|
||||||
|
"Type",
|
||||||
|
"Name",
|
||||||
|
"Version",
|
||||||
|
"Size (KB)",
|
||||||
|
"Author"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ModStatus
|
||||||
|
{
|
||||||
|
static const QString iconDelete = "icons:mod-delete.png";
|
||||||
|
static const QString iconDisabled = "icons:mod-disabled.png";
|
||||||
|
static const QString iconDownload = "icons:mod-download.png";
|
||||||
|
static const QString iconEnabled = "icons:mod-enabled.png";
|
||||||
|
static const QString iconUpdate = "icons:mod-update.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
CModListModel::CModListModel(QObject *parent) :
|
||||||
|
QAbstractTableModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CModListModel::modIndexToName(int index) const
|
||||||
|
{
|
||||||
|
return indexToName[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant CModListModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (index.isValid())
|
||||||
|
{
|
||||||
|
auto mod = getMod(modIndexToName(index.row()));
|
||||||
|
|
||||||
|
if (index.column() == ModFields::STATUS_ENABLED)
|
||||||
|
{
|
||||||
|
if (role == Qt::DecorationRole)
|
||||||
|
{
|
||||||
|
if (mod.isEnabled())
|
||||||
|
return QIcon(ModStatus::iconEnabled);
|
||||||
|
|
||||||
|
if (mod.isDisabled())
|
||||||
|
return QIcon(ModStatus::iconDisabled);
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index.column() == ModFields::STATUS_UPDATE)
|
||||||
|
{
|
||||||
|
if (role == Qt::DecorationRole)
|
||||||
|
{
|
||||||
|
if (mod.isUpdateable())
|
||||||
|
return QIcon(ModStatus::iconUpdate);
|
||||||
|
|
||||||
|
if (!mod.isInstalled())
|
||||||
|
return QIcon(ModStatus::iconDownload);
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == Qt::DisplayRole)
|
||||||
|
{
|
||||||
|
return mod.getValue(ModFields::names[index.column()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CModListModel::rowCount(const QModelIndex &) const
|
||||||
|
{
|
||||||
|
return indexToName.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
int CModListModel::columnCount(const QModelIndex &) const
|
||||||
|
{
|
||||||
|
return ModFields::COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags CModListModel::flags(const QModelIndex &) const
|
||||||
|
{
|
||||||
|
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant CModListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
{
|
||||||
|
if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
|
||||||
|
return ModFields::header[section];
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListModel::addRepository(QJsonObject data)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
CModList::addRepository(data);
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListModel::setLocalModList(QJsonObject data)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
CModList::setLocalModList(data);
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListModel::setModSettings(QJsonObject data)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
CModList::setModSettings(data);
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListModel::endResetModel()
|
||||||
|
{
|
||||||
|
indexToName = getModList();
|
||||||
|
QAbstractItemModel::endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModFilterModel::setTypeFilter(int filteredType, int filterMask)
|
||||||
|
{
|
||||||
|
this->filterMask = filterMask;
|
||||||
|
this->filteredType = filteredType;
|
||||||
|
invalidateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModFilterModel::filterMatches(int modIndex) const
|
||||||
|
{
|
||||||
|
CModEntry mod = base->getMod(base->modIndexToName(modIndex));
|
||||||
|
|
||||||
|
return (mod.getModStatus() & filterMask) == filteredType;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||||
|
{
|
||||||
|
if (filterMatches(source_row))
|
||||||
|
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||||
|
{
|
||||||
|
assert(left.column() == right.column());
|
||||||
|
|
||||||
|
CModEntry mod = base->getMod(base->modIndexToName(left.row()));
|
||||||
|
|
||||||
|
switch (left.column())
|
||||||
|
{
|
||||||
|
case ModFields::STATUS_ENABLED:
|
||||||
|
{
|
||||||
|
return (mod.getModStatus() & (ModStatus::ENABLED | ModStatus::INSTALLED))
|
||||||
|
< (mod.getModStatus() & (ModStatus::ENABLED | ModStatus::INSTALLED));
|
||||||
|
}
|
||||||
|
case ModFields::STATUS_UPDATE:
|
||||||
|
{
|
||||||
|
return (mod.getModStatus() & (ModStatus::UPDATEABLE | ModStatus::INSTALLED))
|
||||||
|
< (mod.getModStatus() & (ModStatus::UPDATEABLE | ModStatus::INSTALLED));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return QSortFilterProxyModel::lessThan(left, right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CModFilterModel::CModFilterModel(CModListModel * model, QObject * parent):
|
||||||
|
QSortFilterProxyModel(parent),
|
||||||
|
base(model),
|
||||||
|
filteredType(ModStatus::MASK_NONE),
|
||||||
|
filterMask(ModStatus::MASK_NONE)
|
||||||
|
{
|
||||||
|
setSourceModel(model);
|
||||||
|
}
|
68
launcher/modManager/cmodlistmodel.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cmodlist.h"
|
||||||
|
|
||||||
|
#include <QAbstractTableModel>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
namespace ModFields
|
||||||
|
{
|
||||||
|
enum EModFields
|
||||||
|
{
|
||||||
|
STATUS_ENABLED,
|
||||||
|
STATUS_UPDATE,
|
||||||
|
TYPE,
|
||||||
|
NAME,
|
||||||
|
VERSION,
|
||||||
|
SIZE,
|
||||||
|
AUTHOR,
|
||||||
|
COUNT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class CModListModel : public QAbstractTableModel, public CModList
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
QVector<QString> indexToName;
|
||||||
|
|
||||||
|
void endResetModel();
|
||||||
|
public:
|
||||||
|
/// CModListContainer overrides
|
||||||
|
void addRepository(QJsonObject data);
|
||||||
|
void setLocalModList(QJsonObject data);
|
||||||
|
void setModSettings(QJsonObject data);
|
||||||
|
|
||||||
|
QString modIndexToName(int index) const;
|
||||||
|
|
||||||
|
explicit CModListModel(QObject *parent = 0);
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex &index, int role) const;
|
||||||
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex &parent) const;
|
||||||
|
int columnCount(const QModelIndex &parent) const;
|
||||||
|
|
||||||
|
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||||
|
signals:
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class CModFilterModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
CModListModel * base;
|
||||||
|
int filteredType;
|
||||||
|
int filterMask;
|
||||||
|
|
||||||
|
bool filterMatches(int modIndex) const;
|
||||||
|
|
||||||
|
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
|
||||||
|
|
||||||
|
bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||||
|
public:
|
||||||
|
void setTypeFilter(int filteredType, int filterMask);
|
||||||
|
|
||||||
|
CModFilterModel(CModListModel * model, QObject *parent = 0);
|
||||||
|
};
|
518
launcher/modManager/cmodlistview.cpp
Normal file
@@ -0,0 +1,518 @@
|
|||||||
|
#include "StdInc.h"
|
||||||
|
#include "cmodlistview.h"
|
||||||
|
#include "ui_cmodlistview.h"
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
|
||||||
|
#include "cmodlistmodel.h"
|
||||||
|
#include "cmodmanager.h"
|
||||||
|
#include "cdownloadmanager.h"
|
||||||
|
#include "launcherdirs.h"
|
||||||
|
|
||||||
|
#include "../lib/CConfigHandler.h"
|
||||||
|
|
||||||
|
void CModListView::setupModModel()
|
||||||
|
{
|
||||||
|
modModel = new CModListModel();
|
||||||
|
manager = new CModManager(modModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::setupFilterModel()
|
||||||
|
{
|
||||||
|
filterModel = new CModFilterModel(modModel);
|
||||||
|
|
||||||
|
filterModel->setFilterKeyColumn(-1); // filter across all columns
|
||||||
|
filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); // to make it more user-friendly
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::setupModsView()
|
||||||
|
{
|
||||||
|
ui->allModsView->setModel(filterModel);
|
||||||
|
// input data is not sorted - sort it before display
|
||||||
|
ui->allModsView->sortByColumn(ModFields::TYPE, Qt::AscendingOrder);
|
||||||
|
ui->allModsView->setColumnWidth(ModFields::STATUS_ENABLED, 30);
|
||||||
|
ui->allModsView->setColumnWidth(ModFields::STATUS_UPDATE, 30);
|
||||||
|
ui->allModsView->setColumnWidth(ModFields::NAME, 120);
|
||||||
|
ui->allModsView->setColumnWidth(ModFields::SIZE, 60);
|
||||||
|
ui->allModsView->setColumnWidth(ModFields::VERSION, 60);
|
||||||
|
|
||||||
|
connect( ui->allModsView->selectionModel(), SIGNAL( currentRowChanged( const QModelIndex &, const QModelIndex & )),
|
||||||
|
this, SLOT( modSelected( const QModelIndex &, const QModelIndex & )));
|
||||||
|
|
||||||
|
connect( filterModel, SIGNAL( modelReset()),
|
||||||
|
this, SLOT( modelReset()));
|
||||||
|
}
|
||||||
|
|
||||||
|
CModListView::CModListView(QWidget *parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
ui(new Ui::CModListView)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
setupModModel();
|
||||||
|
setupFilterModel();
|
||||||
|
setupModsView();
|
||||||
|
|
||||||
|
ui->progressWidget->setVisible(false);
|
||||||
|
dlManager = nullptr;
|
||||||
|
|
||||||
|
// hide mod description on start. looks better this way
|
||||||
|
hideModInfo();
|
||||||
|
|
||||||
|
for (auto entry : settings["launcher"]["repositoryURL"].Vector())
|
||||||
|
{
|
||||||
|
QString str = QString::fromUtf8(entry.String().c_str());
|
||||||
|
|
||||||
|
// URL must be encoded to something else to get rid of symbols illegal in file names
|
||||||
|
auto hashed = QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Md5);
|
||||||
|
auto hashedStr = QString::fromUtf8(hashed.toHex());
|
||||||
|
|
||||||
|
downloadFile(hashedStr + ".json", str, "repository index");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CModListView::~CModListView()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::showModInfo()
|
||||||
|
{
|
||||||
|
ui->modInfoWidget->show();
|
||||||
|
ui->hideModInfoButton->setArrowType(Qt::RightArrow);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::hideModInfo()
|
||||||
|
{
|
||||||
|
ui->modInfoWidget->hide();
|
||||||
|
ui->hideModInfoButton->setArrowType(Qt::LeftArrow);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString replaceIfNotEmpty(QVariant value, QString pattern)
|
||||||
|
{
|
||||||
|
if (value.canConvert<QStringList>())
|
||||||
|
return pattern.arg(value.toStringList().join(", "));
|
||||||
|
|
||||||
|
if (value.canConvert<QString>())
|
||||||
|
return pattern.arg(value.toString());
|
||||||
|
|
||||||
|
// all valid types of data should have been filtered by code above
|
||||||
|
assert(!value.isValid());
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static QVariant sizeToString(QVariant value)
|
||||||
|
{
|
||||||
|
if (value.canConvert<QString>())
|
||||||
|
{
|
||||||
|
static QString symbols = "kMGTPE";
|
||||||
|
auto number = value.toUInt();
|
||||||
|
size_t i=0;
|
||||||
|
|
||||||
|
while (number >= 1000)
|
||||||
|
{
|
||||||
|
number /= 1000;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return QVariant(QString("%1 %2B").arg(number).arg(symbols.at(i)));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString replaceIfNotEmpty(QStringList value, QString pattern)
|
||||||
|
{
|
||||||
|
if (!value.empty())
|
||||||
|
return pattern.arg(value.join(", "));
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
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 textTemplate = prefix + "</p><p align=\"justify\">%2</p>";
|
||||||
|
QString noteTemplate = "<p align=\"justify\">%1: %2</p>";
|
||||||
|
|
||||||
|
QString result;
|
||||||
|
|
||||||
|
result += "<html><body>";
|
||||||
|
result += replaceIfNotEmpty(mod.getValue("name"), lineTemplate.arg("Mod name"));
|
||||||
|
result += replaceIfNotEmpty(mod.getValue("installedVersion"), lineTemplate.arg("Installed version"));
|
||||||
|
result += replaceIfNotEmpty(mod.getValue("latestVersion"), lineTemplate.arg("Latest version"));
|
||||||
|
result += replaceIfNotEmpty(sizeToString(mod.getValue("size")), lineTemplate.arg("Download size"));
|
||||||
|
result += replaceIfNotEmpty(mod.getValue("author"), lineTemplate.arg("Authors"));
|
||||||
|
result += replaceIfNotEmpty(mod.getValue("contact"), urlTemplate.arg("Home"));
|
||||||
|
result += replaceIfNotEmpty(mod.getValue("depends"), lineTemplate.arg("Required mods"));
|
||||||
|
result += replaceIfNotEmpty(mod.getValue("conflicts"), lineTemplate.arg("Conflicting mods"));
|
||||||
|
result += replaceIfNotEmpty(mod.getValue("description"), textTemplate.arg("Description"));
|
||||||
|
|
||||||
|
result += "<p></p>"; // to get some empty space
|
||||||
|
|
||||||
|
QString unknownDeps = "This mod can not be installed or enabled because following dependencies are not present";
|
||||||
|
QString blockingMods = "This mod can not be enabled because following mods are incompatible with this mod";
|
||||||
|
QString hasActiveDependentMods = "This mod can not be disabled because it is required to run following mods";
|
||||||
|
QString hasDependentMods = "This mod can not be uninstalled or updated because it is required to run following mods";
|
||||||
|
|
||||||
|
QString notes;
|
||||||
|
|
||||||
|
notes += replaceIfNotEmpty(findInvalidDependencies(mod.getName()), noteTemplate.arg(unknownDeps));
|
||||||
|
notes += replaceIfNotEmpty(findBlockingMods(mod.getName()), noteTemplate.arg(blockingMods));
|
||||||
|
if (mod.isEnabled())
|
||||||
|
notes += replaceIfNotEmpty(findDependentMods(mod.getName(), true), noteTemplate.arg(hasActiveDependentMods));
|
||||||
|
if (mod.isInstalled())
|
||||||
|
notes += replaceIfNotEmpty(findDependentMods(mod.getName(), false), noteTemplate.arg(hasDependentMods));
|
||||||
|
|
||||||
|
if (notes.size())
|
||||||
|
result += textTemplate.arg("Notes").arg(notes);
|
||||||
|
|
||||||
|
result += "</body></html>";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::enableModInfo()
|
||||||
|
{
|
||||||
|
ui->hideModInfoButton->setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::disableModInfo()
|
||||||
|
{
|
||||||
|
hideModInfo();
|
||||||
|
ui->hideModInfoButton->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::selectMod(int index)
|
||||||
|
{
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
disableModInfo();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enableModInfo();
|
||||||
|
|
||||||
|
auto mod = modModel->getMod(modModel->modIndexToName(index));
|
||||||
|
|
||||||
|
ui->textBrowser->setHtml(genModInfoText(mod));
|
||||||
|
|
||||||
|
bool hasInvalidDeps = !findInvalidDependencies(modModel->modIndexToName(index)).empty();
|
||||||
|
bool hasBlockingMods = !findBlockingMods(modModel->modIndexToName(index)).empty();
|
||||||
|
bool hasDependentMods = !findDependentMods(modModel->modIndexToName(index), true).empty();
|
||||||
|
|
||||||
|
ui->disableButton->setVisible(mod.isEnabled());
|
||||||
|
ui->enableButton->setVisible(mod.isDisabled());
|
||||||
|
ui->installButton->setVisible(mod.isAvailable());
|
||||||
|
ui->uninstallButton->setVisible(mod.isInstalled());
|
||||||
|
ui->updateButton->setVisible(mod.isUpdateable());
|
||||||
|
|
||||||
|
// Block buttons if action is not allowed at this time
|
||||||
|
// TODO: automate handling of some of these cases instead of forcing player
|
||||||
|
// to resolve all conflicts manually.
|
||||||
|
ui->disableButton->setEnabled(!hasDependentMods);
|
||||||
|
ui->enableButton->setEnabled(!hasBlockingMods && !hasInvalidDeps);
|
||||||
|
ui->installButton->setEnabled(!hasInvalidDeps);
|
||||||
|
ui->uninstallButton->setEnabled(!hasDependentMods);
|
||||||
|
ui->updateButton->setEnabled(!hasInvalidDeps && !hasDependentMods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::keyPressEvent(QKeyEvent * event)
|
||||||
|
{
|
||||||
|
if (event->key() == Qt::Key_Escape && ui->modInfoWidget->isVisible() )
|
||||||
|
{
|
||||||
|
ui->modInfoWidget->hide();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return QWidget::keyPressEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::modSelected(const QModelIndex & current, const QModelIndex & )
|
||||||
|
{
|
||||||
|
selectMod(filterModel->mapToSource(current).row());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_hideModInfoButton_clicked()
|
||||||
|
{
|
||||||
|
if (ui->modInfoWidget->isVisible())
|
||||||
|
hideModInfo();
|
||||||
|
else
|
||||||
|
showModInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_allModsView_doubleClicked(const QModelIndex &index)
|
||||||
|
{
|
||||||
|
showModInfo();
|
||||||
|
selectMod(filterModel->mapToSource(index).row());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_lineEdit_textChanged(const QString &arg1)
|
||||||
|
{
|
||||||
|
QRegExp regExp(arg1, Qt::CaseInsensitive, QRegExp::Wildcard);
|
||||||
|
filterModel->setFilterRegExp(regExp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_comboBox_currentIndexChanged(int index)
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
break; case 0: filterModel->setTypeFilter(ModStatus::MASK_NONE, ModStatus::MASK_NONE);
|
||||||
|
break; case 1: filterModel->setTypeFilter(ModStatus::MASK_NONE, ModStatus::INSTALLED);
|
||||||
|
break; case 2: filterModel->setTypeFilter(ModStatus::INSTALLED, ModStatus::INSTALLED);
|
||||||
|
break; case 3: filterModel->setTypeFilter(ModStatus::UPDATEABLE, ModStatus::UPDATEABLE);
|
||||||
|
break; case 4: filterModel->setTypeFilter(ModStatus::ENABLED | ModStatus::INSTALLED, ModStatus::ENABLED | ModStatus::INSTALLED);
|
||||||
|
break; case 5: filterModel->setTypeFilter(ModStatus::INSTALLED, ModStatus::ENABLED | ModStatus::INSTALLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList CModListView::findInvalidDependencies(QString mod)
|
||||||
|
{
|
||||||
|
QStringList ret;
|
||||||
|
for (QString requrement : modModel->getRequirements(mod))
|
||||||
|
{
|
||||||
|
if (!modModel->hasMod(requrement))
|
||||||
|
ret += requrement;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList CModListView::findBlockingMods(QString mod)
|
||||||
|
{
|
||||||
|
QStringList ret;
|
||||||
|
auto required = modModel->getRequirements(mod);
|
||||||
|
|
||||||
|
for (QString name : modModel->getModList())
|
||||||
|
{
|
||||||
|
auto mod = modModel->getMod(name);
|
||||||
|
|
||||||
|
if (mod.isEnabled())
|
||||||
|
{
|
||||||
|
// one of enabled mods have requirement (or this mod) marked as conflict
|
||||||
|
for (auto conflict : mod.getValue("conflicts").toStringList())
|
||||||
|
if (required.contains(conflict))
|
||||||
|
ret.push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled)
|
||||||
|
{
|
||||||
|
QStringList ret;
|
||||||
|
for (QString modName : modModel->getModList())
|
||||||
|
{
|
||||||
|
auto current = modModel->getMod(modName);
|
||||||
|
|
||||||
|
if (current.getValue("depends").toStringList().contains(mod) &&
|
||||||
|
!(current.isDisabled() && excludeDisabled))
|
||||||
|
ret += modName;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_enableButton_clicked()
|
||||||
|
{
|
||||||
|
QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
|
||||||
|
|
||||||
|
assert(findBlockingMods(modName).empty());
|
||||||
|
assert(findInvalidDependencies(modName).empty());
|
||||||
|
|
||||||
|
for (auto & name : modModel->getRequirements(modName))
|
||||||
|
if (modModel->getMod(name).isDisabled())
|
||||||
|
manager->enableMod(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_disableButton_clicked()
|
||||||
|
{
|
||||||
|
QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
|
||||||
|
|
||||||
|
for (auto & name : modModel->getRequirements(modName))
|
||||||
|
if (modModel->hasMod(name) &&
|
||||||
|
modModel->getMod(name).isEnabled())
|
||||||
|
manager->disableMod(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_updateButton_clicked()
|
||||||
|
{
|
||||||
|
QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
|
||||||
|
|
||||||
|
assert(findInvalidDependencies(modName).empty());
|
||||||
|
|
||||||
|
for (auto & name : modModel->getRequirements(modName))
|
||||||
|
{
|
||||||
|
auto mod = modModel->getMod(name);
|
||||||
|
// update required mod, install missing (can be new dependency)
|
||||||
|
if (mod.isUpdateable() || !mod.isInstalled())
|
||||||
|
downloadFile(name + ".zip", mod.getValue("download").toString(), "mods");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_uninstallButton_clicked()
|
||||||
|
{
|
||||||
|
QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
|
||||||
|
// NOTE: perhaps add "manually installed" flag and uninstall those dependencies that don't have it?
|
||||||
|
|
||||||
|
if (modModel->hasMod(modName) &&
|
||||||
|
modModel->getMod(modName).isInstalled())
|
||||||
|
{
|
||||||
|
manager->disableMod(modName);
|
||||||
|
manager->uninstallMod(modName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_installButton_clicked()
|
||||||
|
{
|
||||||
|
QString modName = modModel->modIndexToName(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
|
||||||
|
|
||||||
|
assert(findInvalidDependencies(modName).empty());
|
||||||
|
|
||||||
|
for (auto & name : modModel->getRequirements(modName))
|
||||||
|
{
|
||||||
|
auto mod = modModel->getMod(name);
|
||||||
|
if (!mod.isInstalled())
|
||||||
|
downloadFile(name + ".zip", mod.getValue("download").toString(), "mods");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::downloadFile(QString file, QString url, QString description)
|
||||||
|
{
|
||||||
|
if (!dlManager)
|
||||||
|
{
|
||||||
|
dlManager = new CDownloadManager();
|
||||||
|
ui->progressWidget->setVisible(true);
|
||||||
|
connect(dlManager, SIGNAL(downloadProgress(qint64,qint64)),
|
||||||
|
this, SLOT(downloadProgress(qint64,qint64)));
|
||||||
|
|
||||||
|
connect(dlManager, SIGNAL(finished(QStringList,QStringList,QStringList)),
|
||||||
|
this, SLOT(downloadFinished(QStringList,QStringList,QStringList)));
|
||||||
|
|
||||||
|
|
||||||
|
QString progressBarFormat = "Downloading %s%. %p% (%v KB out of %m KB) finished";
|
||||||
|
|
||||||
|
progressBarFormat.replace("%s%", description);
|
||||||
|
ui->progressBar->setFormat(progressBarFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
dlManager->downloadFile(QUrl(url), file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::downloadProgress(qint64 current, qint64 max)
|
||||||
|
{
|
||||||
|
// display progress, in kilobytes
|
||||||
|
ui->progressBar->setValue(current/1024);
|
||||||
|
ui->progressBar->setMaximum(max/1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors)
|
||||||
|
{
|
||||||
|
QString title = "Download failed";
|
||||||
|
QString firstLine = "Unable to download all files.\n\nEncountered errors:\n\n";
|
||||||
|
QString lastLine = "\n\nInstall successfully downloaded?";
|
||||||
|
|
||||||
|
// if all files were d/loaded there should be no errors. And on failure there must be an error
|
||||||
|
assert(failedFiles.empty() == errors.empty());
|
||||||
|
|
||||||
|
if (savedFiles.empty())
|
||||||
|
{
|
||||||
|
// no successfully downloaded mods
|
||||||
|
QMessageBox::warning(this, title, firstLine + errors.join("\n"), QMessageBox::Ok, QMessageBox::Ok );
|
||||||
|
}
|
||||||
|
else if (!failedFiles.empty())
|
||||||
|
{
|
||||||
|
// some mods were not downloaded
|
||||||
|
int result = QMessageBox::warning (this, title, firstLine + errors.join("\n") + lastLine,
|
||||||
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No );
|
||||||
|
|
||||||
|
if (result == QMessageBox::Yes)
|
||||||
|
installFiles(savedFiles);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// everything OK
|
||||||
|
installFiles(savedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove progress bar after some delay so user can see that download was complete and not interrupted.
|
||||||
|
QTimer::singleShot(1000, this, SLOT(hideProgressBar()));
|
||||||
|
|
||||||
|
dlManager->deleteLater();
|
||||||
|
dlManager = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::hideProgressBar()
|
||||||
|
{
|
||||||
|
if (dlManager == nullptr) // it was not recreated meanwhile
|
||||||
|
{
|
||||||
|
ui->progressWidget->setVisible(false);
|
||||||
|
ui->progressBar->setMaximum(0);
|
||||||
|
ui->progressBar->setValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::installFiles(QStringList files)
|
||||||
|
{
|
||||||
|
QStringList mods;
|
||||||
|
|
||||||
|
// TODO: some better way to separate zip's with mods and downloaded repository files
|
||||||
|
for (QString filename : files)
|
||||||
|
{
|
||||||
|
if (filename.contains(".zip"))
|
||||||
|
mods.push_back(filename);
|
||||||
|
if (filename.contains(".json"))
|
||||||
|
manager->loadRepository(filename);
|
||||||
|
}
|
||||||
|
if (!mods.empty())
|
||||||
|
installMods(mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::installMods(QStringList archives)
|
||||||
|
{
|
||||||
|
//TODO: check return status of all calls to manager!!!
|
||||||
|
|
||||||
|
QStringList modNames;
|
||||||
|
|
||||||
|
for (QString archive : archives)
|
||||||
|
{
|
||||||
|
// get basename out of full file name
|
||||||
|
// remove path remove extension
|
||||||
|
QString modName = archive.section('/', -1, -1).section('.', 0, 0);
|
||||||
|
|
||||||
|
modNames.push_back(modName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable mod(s), to properly recalculate dependencies, if changed
|
||||||
|
for (QString mod : boost::adaptors::reverse(modNames))
|
||||||
|
manager->disableMod(mod);
|
||||||
|
|
||||||
|
// uninstall old version of mod, if installed
|
||||||
|
for (QString mod : boost::adaptors::reverse(modNames))
|
||||||
|
manager->uninstallMod(mod);
|
||||||
|
|
||||||
|
for (int i=0; i<modNames.size(); i++)
|
||||||
|
manager->installMod(modNames[i], archives[i]);
|
||||||
|
|
||||||
|
if (settings["launcher"]["enableInstalledMods"].Bool())
|
||||||
|
{
|
||||||
|
for (QString mod : modNames)
|
||||||
|
manager->enableMod(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (QString archive : archives)
|
||||||
|
QFile::remove(archive);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::on_pushButton_clicked()
|
||||||
|
{
|
||||||
|
delete dlManager;
|
||||||
|
dlManager = nullptr;
|
||||||
|
hideProgressBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModListView::modelReset()
|
||||||
|
{
|
||||||
|
selectMod(filterModel->mapToSource(ui->allModsView->currentIndex()).row());
|
||||||
|
}
|
84
launcher/modManager/cmodlistview.h
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class CModListView;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CModManager;
|
||||||
|
class CModListModel;
|
||||||
|
class CModFilterModel;
|
||||||
|
class CDownloadManager;
|
||||||
|
class QTableWidgetItem;
|
||||||
|
|
||||||
|
class CModEntry;
|
||||||
|
|
||||||
|
class CModListView : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
CModManager * manager;
|
||||||
|
CModListModel * modModel;
|
||||||
|
CModFilterModel * filterModel;
|
||||||
|
CDownloadManager * dlManager;
|
||||||
|
|
||||||
|
void keyPressEvent(QKeyEvent * event);
|
||||||
|
|
||||||
|
void setupModModel();
|
||||||
|
void setupFilterModel();
|
||||||
|
void setupModsView();
|
||||||
|
|
||||||
|
// find mods unknown to mod list (not present in repo and not installed)
|
||||||
|
QStringList findInvalidDependencies(QString mod);
|
||||||
|
// find mods that block enabling of this mod: conflicting with this mod or one of required mods
|
||||||
|
QStringList findBlockingMods(QString mod);
|
||||||
|
// find mods that depend on this one
|
||||||
|
QStringList findDependentMods(QString mod, bool excludeDisabled);
|
||||||
|
|
||||||
|
void downloadFile(QString file, QString url, QString description);
|
||||||
|
|
||||||
|
void installMods(QStringList archives);
|
||||||
|
void installFiles(QStringList mods);
|
||||||
|
|
||||||
|
QString genModInfoText(CModEntry & mod);
|
||||||
|
public:
|
||||||
|
explicit CModListView(QWidget *parent = 0);
|
||||||
|
~CModListView();
|
||||||
|
|
||||||
|
void showModInfo();
|
||||||
|
void hideModInfo();
|
||||||
|
|
||||||
|
void enableModInfo();
|
||||||
|
void disableModInfo();
|
||||||
|
|
||||||
|
void selectMod(int index);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void modSelected(const QModelIndex & current, const QModelIndex & previous);
|
||||||
|
void downloadProgress(qint64 current, qint64 max);
|
||||||
|
void downloadFinished(QStringList savedFiles, QStringList failedFiles, QStringList errors);
|
||||||
|
void modelReset ();
|
||||||
|
void hideProgressBar();
|
||||||
|
|
||||||
|
void on_hideModInfoButton_clicked();
|
||||||
|
|
||||||
|
void on_allModsView_doubleClicked(const QModelIndex &index);
|
||||||
|
|
||||||
|
void on_lineEdit_textChanged(const QString &arg1);
|
||||||
|
|
||||||
|
void on_comboBox_currentIndexChanged(int index);
|
||||||
|
|
||||||
|
void on_enableButton_clicked();
|
||||||
|
|
||||||
|
void on_disableButton_clicked();
|
||||||
|
|
||||||
|
void on_updateButton_clicked();
|
||||||
|
|
||||||
|
void on_uninstallButton_clicked();
|
||||||
|
|
||||||
|
void on_installButton_clicked();
|
||||||
|
|
||||||
|
void on_pushButton_clicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::CModListView *ui;
|
||||||
|
};
|
453
launcher/modManager/cmodlistview.ui
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>CModListView</class>
|
||||||
|
<widget class="QWidget" name="CModListView">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>596</width>
|
||||||
|
<height>342</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<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">
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLineEdit" name="lineEdit">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Filter</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>All mods</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Downloadable</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Installed</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Updatable</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Active</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Inactive</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0" colspan="2">
|
||||||
|
<widget class="QTableView" name="allModsView">
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="verticalScrollMode">
|
||||||
|
<enum>QAbstractItemView::ScrollPerItem</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollMode">
|
||||||
|
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sortingEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="horizontalHeaderStretchLastSection">
|
||||||
|
<bool>true</bool>
|
||||||
|
</attribute>
|
||||||
|
<attribute name="verticalHeaderVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QToolButton" name="hideModInfoButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16</width>
|
||||||
|
<height>100</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="autoRaise">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="arrowType">
|
||||||
|
<enum>Qt::RightArrow</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QWidget" name="modInfoWidget" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="layoutDirection">
|
||||||
|
<enum>Qt::LeftToRight</enum>
|
||||||
|
</property>
|
||||||
|
<property name="autoFillBackground">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<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" 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><!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:11pt; 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;"><br /></p></body></html></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">
|
||||||
|
<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="1" column="2">
|
||||||
|
<widget class="QPushButton" name="disableButton">
|
||||||
|
<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>Disable</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="3">
|
||||||
|
<widget class="QPushButton" name="updateButton">
|
||||||
|
<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>Update</string>
|
||||||
|
</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>
|
||||||
|
</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>Uninstall</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="5">
|
||||||
|
<widget class="QPushButton" name="installButton">
|
||||||
|
<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>Install</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0" colspan="3">
|
||||||
|
<widget class="QWidget" name="progressWidget" native="true">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="textVisible">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="invertedAppearance">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="format">
|
||||||
|
<string> %p% (%v KB out of %m KB)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Abort</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>lineEdit</tabstop>
|
||||||
|
<tabstop>comboBox</tabstop>
|
||||||
|
<tabstop>allModsView</tabstop>
|
||||||
|
<tabstop>textBrowser</tabstop>
|
||||||
|
<tabstop>hideModInfoButton</tabstop>
|
||||||
|
<tabstop>enableButton</tabstop>
|
||||||
|
<tabstop>disableButton</tabstop>
|
||||||
|
<tabstop>updateButton</tabstop>
|
||||||
|
<tabstop>uninstallButton</tabstop>
|
||||||
|
<tabstop>installButton</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
256
launcher/modManager/cmodmanager.cpp
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
#include "StdInc.h"
|
||||||
|
#include "cmodmanager.h"
|
||||||
|
|
||||||
|
#include "../lib/VCMIDirs.h"
|
||||||
|
#include "../lib/filesystem/Filesystem.h"
|
||||||
|
#include "../lib/filesystem/CZipLoader.h"
|
||||||
|
|
||||||
|
#include "../launcherdirs.h"
|
||||||
|
|
||||||
|
static QJsonObject JsonFromFile(QString filename)
|
||||||
|
{
|
||||||
|
QFile file(filename);
|
||||||
|
file.open(QFile::ReadOnly);
|
||||||
|
|
||||||
|
return QJsonDocument::fromJson(file.readAll()).object();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void JsonToFile(QString filename, QJsonObject object)
|
||||||
|
{
|
||||||
|
QFile file(filename);
|
||||||
|
file.open(QFile::WriteOnly);
|
||||||
|
file.write(QJsonDocument(object).toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString detectModArchive(QString path, QString modName)
|
||||||
|
{
|
||||||
|
auto files = ZipArchive::listFiles(path.toUtf8().data());
|
||||||
|
|
||||||
|
QString modDirName;
|
||||||
|
|
||||||
|
for (auto file : files)
|
||||||
|
{
|
||||||
|
QString filename = QString::fromUtf8(file.c_str());
|
||||||
|
if (filename.toLower().startsWith(modName))
|
||||||
|
{
|
||||||
|
// archive must contain mod.json file
|
||||||
|
if (filename.toLower() == modName + "/mod.json")
|
||||||
|
modDirName = filename.section('/', 0, 0);
|
||||||
|
}
|
||||||
|
else // all files must be in <modname> directory
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return modDirName;
|
||||||
|
}
|
||||||
|
|
||||||
|
CModManager::CModManager(CModList * modList):
|
||||||
|
modList(modList)
|
||||||
|
{
|
||||||
|
loadMods();
|
||||||
|
loadModSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CModManager::settingsPath()
|
||||||
|
{
|
||||||
|
return QString::fromUtf8(VCMIDirs::get().userConfigPath().c_str()) + "/modSettings.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModManager::loadModSettings()
|
||||||
|
{
|
||||||
|
modSettings = JsonFromFile(settingsPath());
|
||||||
|
modList->setModSettings(modSettings["activeMods"].toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModManager::loadRepository(QString file)
|
||||||
|
{
|
||||||
|
modList->addRepository(JsonFromFile(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CModManager::loadMods()
|
||||||
|
{
|
||||||
|
auto installedMods = CResourceHandler::getAvailableMods();
|
||||||
|
|
||||||
|
for (auto modname : installedMods)
|
||||||
|
{
|
||||||
|
ResourceID resID("Mods/" + modname + "/mod.json");
|
||||||
|
|
||||||
|
if (CResourceHandler::get()->existsResource(resID))
|
||||||
|
{
|
||||||
|
auto data = CResourceHandler::get()->load(resID)->readAll();
|
||||||
|
auto array = QByteArray(reinterpret_cast<char *>(data.first.get()), data.second);
|
||||||
|
|
||||||
|
auto mod = QJsonDocument::fromJson(array);
|
||||||
|
assert (mod.isObject()); // TODO: use JsonNode from vcmi code here - QJsonNode parser is just too pedantic
|
||||||
|
|
||||||
|
localMods.insert(QString::fromUtf8(modname.c_str()).toLower(), QJsonValue(mod.object()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modList->setLocalModList(localMods);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::installMod(QString modname, QString archivePath)
|
||||||
|
{
|
||||||
|
return canInstallMod(modname) && doInstallMod(modname, archivePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::uninstallMod(QString modname)
|
||||||
|
{
|
||||||
|
return canUninstallMod(modname) && doUninstallMod(modname);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::enableMod(QString modname)
|
||||||
|
{
|
||||||
|
return canEnableMod(modname) && doEnableMod(modname, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::disableMod(QString modname)
|
||||||
|
{
|
||||||
|
return canDisableMod(modname) && doEnableMod(modname, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::canInstallMod(QString modname)
|
||||||
|
{
|
||||||
|
auto mod = modList->getMod(modname);
|
||||||
|
|
||||||
|
if (mod.isInstalled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!mod.isAvailable())
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::canUninstallMod(QString modname)
|
||||||
|
{
|
||||||
|
auto mod = modList->getMod(modname);
|
||||||
|
|
||||||
|
if (!mod.isInstalled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (mod.isEnabled())
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::canEnableMod(QString modname)
|
||||||
|
{
|
||||||
|
auto mod = modList->getMod(modname);
|
||||||
|
|
||||||
|
if (mod.isEnabled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!mod.isInstalled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (auto modEntry : mod.getValue("depends").toStringList())
|
||||||
|
{
|
||||||
|
if (!modList->hasMod(modEntry)) // required mod is not available
|
||||||
|
return false;
|
||||||
|
if (!modList->getMod(modEntry).isEnabled())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (QString name : modList->getModList())
|
||||||
|
{
|
||||||
|
auto mod = modList->getMod(name);
|
||||||
|
|
||||||
|
if (mod.isEnabled() && mod.getValue("conflicts").toStringList().contains(modname))
|
||||||
|
return false; // "reverse conflict" - enabled mod has this one as conflict
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto modEntry : mod.getValue("conflicts").toStringList())
|
||||||
|
{
|
||||||
|
if (modList->hasMod(modEntry) &&
|
||||||
|
modList->getMod(modEntry).isEnabled()) // conflicting mod installed and enabled
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::canDisableMod(QString modname)
|
||||||
|
{
|
||||||
|
auto mod = modList->getMod(modname);
|
||||||
|
|
||||||
|
if (mod.isDisabled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!mod.isInstalled())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (QString modEntry : modList->getModList())
|
||||||
|
{
|
||||||
|
auto current = modList->getMod(modEntry);
|
||||||
|
|
||||||
|
if (current.getValue("depends").toStringList().contains(modname) &&
|
||||||
|
!current.isDisabled())
|
||||||
|
return false; // this mod must be disabled first
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::doEnableMod(QString mod, bool on)
|
||||||
|
{
|
||||||
|
QJsonValue value(on);
|
||||||
|
QJsonObject list = modSettings["activeMods"].toObject();
|
||||||
|
|
||||||
|
list.insert(mod, value);
|
||||||
|
modSettings.insert("activeMods", list);
|
||||||
|
|
||||||
|
modList->setModSettings(modSettings["activeMods"].toObject());
|
||||||
|
|
||||||
|
JsonToFile(settingsPath(), modSettings);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::doInstallMod(QString modname, QString archivePath)
|
||||||
|
{
|
||||||
|
QString destDir = CLauncherDirs::get().modsPath() + "/";
|
||||||
|
|
||||||
|
if (!QFile(archivePath).exists())
|
||||||
|
return false; // archive with mod data exists
|
||||||
|
|
||||||
|
if (QDir(destDir + modname).exists()) // FIXME: recheck wog/vcmi data behavior - they have bits of data in our trunk
|
||||||
|
return false; // no mod with such name installed
|
||||||
|
|
||||||
|
if (localMods.contains(modname))
|
||||||
|
return false; // no installed data known
|
||||||
|
|
||||||
|
QString modDirName = detectModArchive(archivePath, modname);
|
||||||
|
if (!modDirName.size())
|
||||||
|
return false; // archive content looks like mod FS
|
||||||
|
|
||||||
|
if (!ZipArchive::extract(archivePath.toUtf8().data(), destDir.toUtf8().data()))
|
||||||
|
{
|
||||||
|
QDir(destDir + modDirName).removeRecursively();
|
||||||
|
return false; // extraction failed
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject json = JsonFromFile(destDir + modDirName + "/mod.json");
|
||||||
|
|
||||||
|
localMods.insert(modname, json);
|
||||||
|
modList->setLocalModList(localMods);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CModManager::doUninstallMod(QString modname)
|
||||||
|
{
|
||||||
|
ResourceID resID(std::string("Mods/") + modname.toUtf8().data(), EResType::DIRECTORY);
|
||||||
|
// Get location of the mod, in case-insensitive way
|
||||||
|
QString modDir = QString::fromUtf8(CResourceHandler::get()->getResourceName(resID)->c_str());
|
||||||
|
|
||||||
|
if (!QDir(modDir).exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!localMods.contains(modname))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!QDir(modDir).removeRecursively())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
localMods.remove(modname);
|
||||||
|
modList->setLocalModList(localMods);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
38
launcher/modManager/cmodmanager.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cmodlist.h"
|
||||||
|
|
||||||
|
class CModManager
|
||||||
|
{
|
||||||
|
CModList * modList;
|
||||||
|
|
||||||
|
QString settingsPath();
|
||||||
|
|
||||||
|
// check-free version of public method
|
||||||
|
bool doEnableMod(QString mod, bool on);
|
||||||
|
bool doInstallMod(QString mod, QString archivePath);
|
||||||
|
bool doUninstallMod(QString mod);
|
||||||
|
|
||||||
|
QJsonObject modSettings;
|
||||||
|
QJsonObject localMods;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CModManager(CModList * modList);
|
||||||
|
|
||||||
|
void loadRepository(QString filename);
|
||||||
|
void loadModSettings();
|
||||||
|
void loadMods();
|
||||||
|
|
||||||
|
/// mod management functions. Return true if operation was successful
|
||||||
|
|
||||||
|
/// installs mod from zip archive located at archivePath
|
||||||
|
bool installMod(QString mod, QString archivePath);
|
||||||
|
bool uninstallMod(QString mod);
|
||||||
|
bool enableMod(QString mod);
|
||||||
|
bool disableMod(QString mod);
|
||||||
|
|
||||||
|
bool canInstallMod(QString mod);
|
||||||
|
bool canUninstallMod(QString mod);
|
||||||
|
bool canEnableMod(QString mod);
|
||||||
|
bool canDisableMod(QString mod);
|
||||||
|
};
|
112
launcher/settingsView/csettingsview.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#include "StdInc.h"
|
||||||
|
#include "csettingsview.h"
|
||||||
|
#include "ui_csettingsview.h"
|
||||||
|
|
||||||
|
#include "../lib/CConfigHandler.h"
|
||||||
|
#include "../lib/VCMIDirs.h"
|
||||||
|
|
||||||
|
void CSettingsView::loadSettings()
|
||||||
|
{
|
||||||
|
int resX = settings["video"]["screenRes"]["width"].Float();
|
||||||
|
int resY = settings["video"]["screenRes"]["height"].Float();
|
||||||
|
|
||||||
|
int resIndex = ui->comboBoxResolution->findText(QString("%1x%2").arg(resX).arg(resY));
|
||||||
|
|
||||||
|
ui->comboBoxResolution->setCurrentIndex(resIndex);
|
||||||
|
ui->comboBoxFullScreen->setCurrentIndex(settings["video"]["fullscreen"].Bool());
|
||||||
|
|
||||||
|
int neutralAIIndex = ui->comboBoxNeutralAI->findText(QString::fromUtf8(settings["server"]["neutralAI"].String().c_str()));
|
||||||
|
int playerAIIndex = ui->comboBoxPlayerAI->findText(QString::fromUtf8(settings["server"]["playerAI"].String().c_str()));
|
||||||
|
|
||||||
|
ui->comboBoxNeutralAI->setCurrentIndex(neutralAIIndex);
|
||||||
|
ui->comboBoxPlayerAI->setCurrentIndex(playerAIIndex);
|
||||||
|
|
||||||
|
ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Float());
|
||||||
|
|
||||||
|
ui->comboBoxEnableMods->setCurrentIndex(settings["launcher"]["enableInstalledMods"].Bool());
|
||||||
|
|
||||||
|
// all calls to plainText will trigger textChanged() signal overwriting config. Create backup before editing widget
|
||||||
|
JsonNode urls = settings["launcher"]["repositoryURL"];
|
||||||
|
|
||||||
|
ui->plainTextEditRepos->clear();
|
||||||
|
for (auto entry : urls.Vector())
|
||||||
|
ui->plainTextEditRepos->appendPlainText(QString::fromUtf8(entry.String().c_str()));
|
||||||
|
|
||||||
|
ui->lineEditUserDataDir->setText(QString::fromUtf8(VCMIDirs::get().userDataPath().c_str()));
|
||||||
|
QStringList dataDirs;
|
||||||
|
for (auto string : VCMIDirs::get().dataPaths())
|
||||||
|
dataDirs += QString::fromUtf8(string.c_str());
|
||||||
|
ui->lineEditGameDir->setText(dataDirs.join(':'));
|
||||||
|
}
|
||||||
|
|
||||||
|
CSettingsView::CSettingsView(QWidget *parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
ui(new Ui::CSettingsView)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
CSettingsView::~CSettingsView()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSettingsView::on_comboBoxResolution_currentIndexChanged(const QString &arg1)
|
||||||
|
{
|
||||||
|
QStringList list = arg1.split("x");
|
||||||
|
|
||||||
|
Settings node = settings.write["video"]["screenRes"];
|
||||||
|
node["width"].Float() = list[0].toInt();
|
||||||
|
node["height"].Float() = list[1].toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSettingsView::on_comboBoxFullScreen_currentIndexChanged(int index)
|
||||||
|
{
|
||||||
|
Settings node = settings.write["video"]["fullscreen"];
|
||||||
|
node->Bool() = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSettingsView::on_comboBoxPlayerAI_currentIndexChanged(const QString &arg1)
|
||||||
|
{
|
||||||
|
Settings node = settings.write["server"]["playerAI"];
|
||||||
|
node->String() = arg1.toUtf8().data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSettingsView::on_comboBoxNeutralAI_currentIndexChanged(const QString &arg1)
|
||||||
|
{
|
||||||
|
Settings node = settings.write["server"]["neutralAI"];
|
||||||
|
node->String() = arg1.toUtf8().data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSettingsView::on_comboBoxEnableMods_currentIndexChanged(int index)
|
||||||
|
{
|
||||||
|
Settings node = settings.write["launcher"]["enableInstalledMods"];
|
||||||
|
node->Bool() = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSettingsView::on_spinBoxNetworkPort_valueChanged(int arg1)
|
||||||
|
{
|
||||||
|
Settings node = settings.write["server"]["port"];
|
||||||
|
node->Float() = arg1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSettingsView::on_plainTextEditRepos_textChanged()
|
||||||
|
{
|
||||||
|
Settings node = settings.write["launcher"]["repositoryURL"];
|
||||||
|
|
||||||
|
QStringList list = ui->plainTextEditRepos->toPlainText().split('\n');
|
||||||
|
|
||||||
|
node->Vector().clear();
|
||||||
|
for (QString line : list)
|
||||||
|
{
|
||||||
|
if (line.trimmed().size() > 0)
|
||||||
|
{
|
||||||
|
JsonNode entry;
|
||||||
|
entry.String() = line.trimmed().toUtf8().data();
|
||||||
|
node->Vector().push_back(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
34
launcher/settingsView/csettingsview.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class CSettingsView;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CSettingsView : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CSettingsView(QWidget *parent = 0);
|
||||||
|
~CSettingsView();
|
||||||
|
|
||||||
|
void loadSettings();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void on_comboBoxResolution_currentIndexChanged(const QString &arg1);
|
||||||
|
|
||||||
|
void on_comboBoxFullScreen_currentIndexChanged(int index);
|
||||||
|
|
||||||
|
void on_comboBoxPlayerAI_currentIndexChanged(const QString &arg1);
|
||||||
|
|
||||||
|
void on_comboBoxNeutralAI_currentIndexChanged(const QString &arg1);
|
||||||
|
|
||||||
|
void on_comboBoxEnableMods_currentIndexChanged(int index);
|
||||||
|
|
||||||
|
void on_spinBoxNetworkPort_valueChanged(int arg1);
|
||||||
|
|
||||||
|
void on_plainTextEditRepos_textChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::CSettingsView *ui;
|
||||||
|
};
|
367
launcher/settingsView/csettingsview.ui
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>CSettingsView</class>
|
||||||
|
<widget class="QWidget" name="CSettingsView">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>700</width>
|
||||||
|
<height>303</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="1" column="4">
|
||||||
|
<widget class="QLineEdit" name="lineEditGameDir">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>150</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>/usr/share/vcmi</string>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
|
<spacer name="spacerRepos">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>8</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="labelResolution">
|
||||||
|
<property name="text">
|
||||||
|
<string>Resolution</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<spacer name="spacerSections">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>56</width>
|
||||||
|
<height>8</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="labelFullScreen">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fullscreen</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="4">
|
||||||
|
<widget class="QSpinBox" name="spinBoxNetworkPort">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1024</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>65535</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>3030</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBoxPlayerAI">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>VCAI</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="3">
|
||||||
|
<widget class="QLabel" name="labelNetworkPort">
|
||||||
|
<property name="text">
|
||||||
|
<string>Network port</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBoxResolution">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>800x600</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1024x600</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1024x768</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1280x800</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1280x960</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1280x1024</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1366x768</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1440x900</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1600x1200</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1680x1050</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>1920x1080</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="3">
|
||||||
|
<widget class="QLabel" name="labelUserDataDir">
|
||||||
|
<property name="text">
|
||||||
|
<string>User data directory</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="4">
|
||||||
|
<widget class="QComboBox" name="comboBoxEnableMods">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Off</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>On</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="labelRepositories">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Repositories</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="0" colspan="5">
|
||||||
|
<widget class="QPlainTextEdit" name="plainTextEditRepos">
|
||||||
|
<property name="lineWrapMode">
|
||||||
|
<enum>QPlainTextEdit::NoWrap</enum>
|
||||||
|
</property>
|
||||||
|
<property name="plainText">
|
||||||
|
<string>http://downloads.vcmi.eu/Mods/repository.json</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="labelPlayerAI">
|
||||||
|
<property name="text">
|
||||||
|
<string>Player AI</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<spacer name="spacerColumns">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>8</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBoxFullScreen">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Off</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>On</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="3">
|
||||||
|
<widget class="QLabel" name="labelEnableMods">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable mods on install</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="4">
|
||||||
|
<widget class="QLineEdit" name="lineEditUserDataDir">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>150</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>/home/user/.vcmi</string>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="labelNeutralAI">
|
||||||
|
<property name="text">
|
||||||
|
<string>Neutral AI</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBoxNeutralAI">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>StupidAI</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>BattleAI</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="3">
|
||||||
|
<widget class="QLabel" name="labelGameDir">
|
||||||
|
<property name="text">
|
||||||
|
<string>Game directory</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="labelAISettings">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>AI Settings</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="labelVideo">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Video</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3" colspan="2">
|
||||||
|
<widget class="QLabel" name="labelDataDirs">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Data Directories (unchangeable)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="3" colspan="2">
|
||||||
|
<widget class="QLabel" name="labelGeneral">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>General</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|