2017-07-13 10:26:03 +02:00
/*
* cmodlistview_moc . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
2013-08-22 17:22:49 +03:00
# include "StdInc.h"
2013-08-24 23:11:51 +03:00
# include "cmodlistview_moc.h"
# include "ui_cmodlistview_moc.h"
2014-03-29 15:46:41 +03:00
# include "imageviewer_moc.h"
2019-07-30 09:28:42 +02:00
# include "../mainwindow_moc.h"
2013-08-22 17:22:49 +03:00
# include <QJsonArray>
# include <QCryptographicHash>
2022-05-28 15:32:20 +02:00
# include <QRegularExpression>
2013-08-22 17:22:49 +03:00
2013-08-24 23:11:51 +03:00
# include "cmodlistmodel_moc.h"
2013-08-22 17:22:49 +03:00
# include "cmodmanager.h"
2013-08-24 23:11:51 +03:00
# include "cdownloadmanager_moc.h"
2024-08-30 21:17:18 +02:00
# include "chroniclesextractor.h"
2024-04-21 01:32:31 +02:00
# include "../settingsView/csettingsview_moc.h"
2013-09-02 20:26:52 +03:00
# include "../launcherdirs.h"
2022-09-29 15:38:03 +02:00
# include "../jsonutils.h"
2024-04-21 16:56:39 +02:00
# include "../helper.h"
2013-08-22 17:22:49 +03:00
2024-04-21 01:32:31 +02:00
# include "../../lib/VCMIDirs.h"
2013-09-02 20:26:52 +03:00
# include "../../lib/CConfigHandler.h"
2024-07-20 14:55:17 +02:00
# include "../../lib/texts/Languages.h"
2023-09-01 02:12:41 +02:00
# include "../../lib/modding/CModVersion.h"
2024-08-31 00:44:20 +02:00
# include "../../lib/filesystem/Filesystem.h"
2013-08-22 17:22:49 +03:00
2024-08-31 23:44:36 +02:00
# include <future>
2023-09-02 12:11:56 +02:00
static double mbToBytes ( double mb )
2023-09-01 22:18:23 +02:00
{
return mb * 1024 * 1024 ;
}
2013-08-22 17:22:49 +03:00
void CModListView : : setupModModel ( )
{
2018-04-28 10:56:01 +02:00
modModel = new CModListModel ( this ) ;
2022-12-07 23:36:20 +02:00
manager = std : : make_unique < CModManager > ( modModel ) ;
2013-08-22 17:22:49 +03:00
}
2022-12-25 13:19:16 +02:00
void CModListView : : changeEvent ( QEvent * event )
{
2022-12-29 16:37:38 +02:00
if ( event - > type ( ) = = QEvent : : LanguageChange )
2022-12-25 13:19:16 +02:00
{
ui - > retranslateUi ( this ) ;
modModel - > reloadRepositories ( ) ;
}
2022-12-28 17:58:39 +02:00
QWidget : : changeEvent ( event ) ;
2022-12-25 13:19:16 +02:00
}
2023-12-25 19:53:02 +02:00
void CModListView : : dragEnterEvent ( QDragEnterEvent * event )
{
2023-12-25 23:41:15 +02:00
if ( event - > mimeData ( ) - > hasUrls ( ) )
for ( const auto & url : event - > mimeData ( ) - > urls ( ) )
2024-08-30 21:17:18 +02:00
for ( const auto & ending : QStringList ( { " .zip " , " .h3m " , " .h3c " , " .vmap " , " .vcmp " , " .json " , " .exe " } ) )
2023-12-25 23:41:15 +02:00
if ( url . fileName ( ) . endsWith ( ending , Qt : : CaseInsensitive ) )
{
event - > acceptProposedAction ( ) ;
return ;
}
2023-12-25 19:53:02 +02:00
}
void CModListView : : dropEvent ( QDropEvent * event )
{
const QMimeData * mimeData = event - > mimeData ( ) ;
if ( mimeData - > hasUrls ( ) )
{
2023-12-25 20:30:40 +02:00
const QList < QUrl > urlList = mimeData - > urls ( ) ;
2023-12-25 23:41:15 +02:00
for ( const auto & url : urlList )
2024-07-08 15:10:44 +02:00
manualInstallFile ( url . toLocalFile ( ) ) ;
2023-12-25 19:53:02 +02:00
}
}
2013-08-22 17:22:49 +03:00
void CModListView : : setupFilterModel ( )
{
2018-04-28 10:56:01 +02:00
filterModel = new CModFilterModel ( modModel , this ) ;
2013-08-22 17:22:49 +03:00
filterModel - > setFilterKeyColumn ( - 1 ) ; // filter across all columns
filterModel - > setSortCaseSensitivity ( Qt : : CaseInsensitive ) ; // to make it more user-friendly
2014-03-20 20:06:25 +03:00
filterModel - > setDynamicSortFilter ( true ) ;
2013-08-22 17:22:49 +03:00
}
void CModListView : : setupModsView ( )
{
ui - > allModsView - > setModel ( filterModel ) ;
// input data is not sorted - sort it before display
ui - > allModsView - > sortByColumn ( ModFields : : TYPE , Qt : : AscendingOrder ) ;
2014-03-20 20:06:25 +03:00
ui - > allModsView - > header ( ) - > setSectionResizeMode ( ModFields : : STATUS_ENABLED , QHeaderView : : Fixed ) ;
2018-04-13 07:34:58 +02:00
ui - > allModsView - > header ( ) - > setSectionResizeMode ( ModFields : : STATUS_UPDATE , QHeaderView : : Fixed ) ;
2014-03-20 20:06:25 +03:00
2019-07-30 09:28:42 +02:00
QSettings s ( Ui : : teamName , Ui : : appName ) ;
auto state = s . value ( " AllModsView/State " ) . toByteArray ( ) ;
2019-07-30 11:00:44 +02:00
if ( ! state . isNull ( ) ) //read last saved settings
2019-07-30 09:28:42 +02:00
{
ui - > allModsView - > header ( ) - > restoreState ( state ) ;
}
else //default //TODO: default high-DPI scaling
{
2024-06-08 18:00:45 +02:00
ui - > allModsView - > setColumnWidth ( ModFields : : NAME , 220 ) ;
2019-07-30 09:28:42 +02:00
ui - > allModsView - > setColumnWidth ( ModFields : : TYPE , 75 ) ;
}
2023-03-27 19:11:41 +02:00
ui - > allModsView - > resizeColumnToContents ( ModFields : : STATUS_ENABLED ) ;
ui - > allModsView - > resizeColumnToContents ( ModFields : : STATUS_UPDATE ) ;
2019-07-30 09:28:42 +02:00
2014-03-20 20:06:25 +03:00
ui - > allModsView - > setUniformRowHeights ( true ) ;
2018-04-13 07:34:58 +02:00
connect ( ui - > allModsView - > selectionModel ( ) , SIGNAL ( currentRowChanged ( const QModelIndex & , const QModelIndex & ) ) ,
this , SLOT ( modSelected ( const QModelIndex & , const QModelIndex & ) ) ) ;
2013-08-22 17:22:49 +03:00
2018-04-13 07:34:58 +02:00
connect ( filterModel , SIGNAL ( modelReset ( ) ) ,
this , SLOT ( modelReset ( ) ) ) ;
2013-08-24 23:11:51 +03:00
2018-04-13 07:34:58 +02:00
connect ( modModel , SIGNAL ( dataChanged ( QModelIndex , QModelIndex ) ) ,
this , SLOT ( dataChanged ( QModelIndex , QModelIndex ) ) ) ;
2013-08-22 17:22:49 +03:00
}
2018-04-13 07:34:58 +02:00
CModListView : : CModListView ( QWidget * parent )
2023-03-12 18:33:29 +02:00
: QWidget ( parent )
, ui ( new Ui : : CModListView )
2013-08-22 17:22:49 +03:00
{
ui - > setupUi ( this ) ;
2023-12-25 19:53:02 +02:00
setAcceptDrops ( true ) ;
2024-03-02 23:24:00 +02:00
ui - > uninstallButton - > setIcon ( QIcon { " :/icons/mod-delete.png " } ) ;
ui - > enableButton - > setIcon ( QIcon { " :/icons/mod-enabled.png " } ) ;
ui - > disableButton - > setIcon ( QIcon { " :/icons/mod-disabled.png " } ) ;
ui - > updateButton - > setIcon ( QIcon { " :/icons/mod-update.png " } ) ;
ui - > installButton - > setIcon ( QIcon { " :/icons/mod-download.png " } ) ;
2024-07-16 16:13:58 +02:00
ui - > splitter - > setStyleSheet ( " QSplitter::handle {background: palette('window');} " ) ;
2013-08-22 17:22:49 +03:00
setupModModel ( ) ;
setupFilterModel ( ) ;
setupModsView ( ) ;
ui - > progressWidget - > setVisible ( false ) ;
dlManager = nullptr ;
2023-03-11 00:57:55 +02:00
2018-04-13 07:34:58 +02:00
if ( settings [ " launcher " ] [ " autoCheckRepositories " ] . Bool ( ) )
2014-11-03 17:47:37 +02:00
{
loadRepositories ( ) ;
}
2016-08-30 22:16:52 +02:00
else
{
manager - > resetRepositories ( ) ;
}
2024-05-01 20:05:01 +02:00
# ifdef VCMI_MOBILE
2022-11-22 01:36:27 +02:00
for ( auto * scrollWidget : {
( QAbstractItemView * ) ui - > allModsView ,
( QAbstractItemView * ) ui - > screenshotsList } )
{
2024-05-01 20:05:01 +02:00
Helper : : enableScrollBySwiping ( scrollWidget ) ;
2022-11-22 01:36:27 +02:00
scrollWidget - > setVerticalScrollMode ( QAbstractItemView : : ScrollPerPixel ) ;
scrollWidget - > setVerticalScrollBarPolicy ( Qt : : ScrollBarAlwaysOff ) ;
}
# endif
2013-08-24 23:11:51 +03:00
}
2013-08-22 17:22:49 +03:00
2013-08-24 23:11:51 +03:00
void CModListView : : loadRepositories ( )
{
manager - > resetRepositories ( ) ;
2023-06-29 14:41:12 +02:00
QStringList repositories ;
if ( settings [ " launcher " ] [ " defaultRepositoryEnabled " ] . Bool ( ) )
repositories . push_back ( QString : : fromStdString ( settings [ " launcher " ] [ " defaultRepositoryURL " ] . String ( ) ) ) ;
if ( settings [ " launcher " ] [ " extraRepositoryEnabled " ] . Bool ( ) )
repositories . push_back ( QString : : fromStdString ( settings [ " launcher " ] [ " extraRepositoryURL " ] . String ( ) ) ) ;
for ( auto entry : repositories )
2013-08-22 17:22:49 +03:00
{
2023-06-29 14:41:12 +02:00
if ( entry . isEmpty ( ) )
continue ;
2013-08-22 17:22:49 +03:00
// URL must be encoded to something else to get rid of symbols illegal in file names
2023-06-29 14:41:12 +02:00
auto hashed = QCryptographicHash : : hash ( entry . toUtf8 ( ) , QCryptographicHash : : Md5 ) ;
2013-08-22 17:22:49 +03:00
auto hashedStr = QString : : fromUtf8 ( hashed . toHex ( ) ) ;
2024-05-01 12:46:39 +02:00
downloadFile ( hashedStr + " .json " , entry , tr ( " mods repository index " ) ) ;
2013-08-22 17:22:49 +03:00
}
}
CModListView : : ~ CModListView ( )
{
2019-07-30 09:28:42 +02:00
QSettings s ( Ui : : teamName , Ui : : appName ) ;
s . setValue ( " AllModsView/State " , ui - > allModsView - > header ( ) - > saveState ( ) ) ;
2013-08-22 17:22:49 +03:00
delete ui ;
}
static QString replaceIfNotEmpty ( QVariant value , QString pattern )
{
2018-04-13 07:34:58 +02:00
if ( value . canConvert < QStringList > ( ) )
2013-08-22 17:22:49 +03:00
return pattern . arg ( value . toStringList ( ) . join ( " , " ) ) ;
2018-04-13 07:34:58 +02:00
if ( value . canConvert < QString > ( ) )
2013-08-22 17:22:49 +03:00
return pattern . arg ( value . toString ( ) ) ;
// all valid types of data should have been filtered by code above
assert ( ! value . isValid ( ) ) ;
return " " ;
}
static QString replaceIfNotEmpty ( QStringList value , QString pattern )
{
2018-04-13 07:34:58 +02:00
if ( ! value . empty ( ) )
2013-08-22 17:22:49 +03:00
return pattern . arg ( value . join ( " , " ) ) ;
return " " ;
}
2018-04-13 07:34:58 +02:00
QString CModListView : : genChangelogText ( CModEntry & mod )
2014-03-23 15:08:01 +03:00
{
QString headerTemplate = " <p><span style= \" font-weight:600; \" >%1: </span></p> " ;
QString entryBegin = " <p align= \" justify \" ><ul> " ;
QString entryEnd = " </ul></p> " ;
QString entryLine = " <li>%1</li> " ;
//QString versionSeparator = "<hr/>";
QString result ;
QVariantMap changelog = mod . getValue ( " changelog " ) . toMap ( ) ;
QList < QString > versions = changelog . keys ( ) ;
std : : sort ( versions . begin ( ) , versions . end ( ) , [ ] ( QString lesser , QString greater )
{
2023-09-01 02:12:41 +02:00
return CModVersion : : fromString ( lesser . toStdString ( ) ) < CModVersion : : fromString ( greater . toStdString ( ) ) ;
2014-03-23 15:08:01 +03:00
} ) ;
2023-05-27 22:12:03 +02:00
std : : reverse ( versions . begin ( ) , versions . end ( ) ) ;
2014-03-23 15:08:01 +03:00
2018-04-13 07:34:58 +02:00
for ( auto & version : versions )
2014-03-23 15:08:01 +03:00
{
result + = headerTemplate . arg ( version ) ;
result + = entryBegin ;
2018-04-13 07:34:58 +02:00
for ( auto & line : changelog . value ( version ) . toStringList ( ) )
2014-03-23 15:08:01 +03:00
result + = entryLine . arg ( line ) ;
result + = entryEnd ;
}
return result ;
}
2023-03-26 23:32:24 +02:00
QStringList CModListView : : getModNames ( QStringList input )
{
QStringList result ;
2023-03-27 00:10:44 +02:00
for ( const auto & modID : input )
2023-03-26 23:32:24 +02:00
{
2023-11-19 20:44:28 +02:00
auto mod = modModel - > getMod ( modID . toLower ( ) ) ;
2023-03-26 23:32:24 +02:00
QString modName = mod . getValue ( " name " ) . toString ( ) ;
if ( modName . isEmpty ( ) )
2023-11-19 20:44:28 +02:00
result + = modID . toLower ( ) ;
2023-03-26 23:32:24 +02:00
else
result + = modName ;
}
return result ;
}
2018-04-13 07:34:58 +02:00
QString CModListView : : genModInfoText ( CModEntry & mod )
2013-08-22 17:22:49 +03:00
{
QString prefix = " <p><span style= \" font-weight:600; \" >%1: </span> " ; // shared prefix
2022-09-29 16:21:29 +02:00
QString redPrefix = " <p><span style= \" font-weight:600; color:red \" >%1: </span> " ; // shared prefix
2013-08-22 17:22:49 +03:00
QString lineTemplate = prefix + " %2</p> " ;
2018-04-13 07:34:58 +02:00
QString urlTemplate = prefix + " <a href= \" %2 \" >%3</a></p> " ;
2013-08-22 17:22:49 +03:00
QString textTemplate = prefix + " </p><p align= \" justify \" >%2</p> " ;
2014-03-20 20:06:25 +03:00
QString listTemplate = " <p align= \" justify \" >%1: %2</p> " ;
QString noteTemplate = " <p align= \" justify \" >%1</p> " ;
2022-12-25 13:19:16 +02:00
QString incompatibleString = redPrefix + tr ( " Mod is incompatible " ) + " </p> " ;
2022-09-29 16:21:29 +02:00
QString supportedVersions = redPrefix + " %2 %3 %4</p> " ;
2013-08-22 17:22:49 +03:00
QString result ;
2014-03-20 20:06:25 +03:00
result + = replaceIfNotEmpty ( mod . getValue ( " name " ) , lineTemplate . arg ( tr ( " Mod name " ) ) ) ;
result + = replaceIfNotEmpty ( mod . getValue ( " installedVersion " ) , lineTemplate . arg ( tr ( " Installed version " ) ) ) ;
result + = replaceIfNotEmpty ( mod . getValue ( " latestVersion " ) , lineTemplate . arg ( tr ( " Latest version " ) ) ) ;
2014-03-23 15:08:01 +03:00
2023-09-02 12:15:24 +02:00
if ( mod . getValue ( " localSizeBytes " ) . isValid ( ) )
2023-09-09 02:14:27 +02:00
result + = replaceIfNotEmpty ( CModEntry : : sizeToString ( mod . getValue ( " localSizeBytes " ) . toDouble ( ) ) , lineTemplate . arg ( tr ( " Size " ) ) ) ;
2023-09-07 19:57:57 +02:00
if ( ( mod . isAvailable ( ) | | mod . isUpdateable ( ) ) & & mod . getValue ( " downloadSize " ) . isValid ( ) )
result + = replaceIfNotEmpty ( CModEntry : : sizeToString ( mbToBytes ( mod . getValue ( " downloadSize " ) . toDouble ( ) ) ) , lineTemplate . arg ( tr ( " Download size " ) ) ) ;
2023-09-01 16:43:26 +02:00
2014-03-20 20:06:25 +03:00
result + = replaceIfNotEmpty ( mod . getValue ( " author " ) , lineTemplate . arg ( tr ( " Authors " ) ) ) ;
2014-03-23 15:08:01 +03:00
2018-04-13 07:34:58 +02:00
if ( mod . getValue ( " licenseURL " ) . isValid ( ) )
2014-03-23 15:08:01 +03:00
result + = urlTemplate . arg ( tr ( " License " ) ) . arg ( mod . getValue ( " licenseURL " ) . toString ( ) ) . arg ( mod . getValue ( " licenseName " ) . toString ( ) ) ;
2018-04-13 07:34:58 +02:00
if ( mod . getValue ( " contact " ) . isValid ( ) )
2023-01-26 01:01:41 +02:00
result + = urlTemplate . arg ( tr ( " Contact " ) ) . arg ( mod . getValue ( " contact " ) . toString ( ) ) . arg ( mod . getValue ( " contact " ) . toString ( ) ) ;
2014-03-23 15:08:01 +03:00
2022-09-29 16:21:29 +02:00
//compatibility info
2023-03-26 23:26:58 +02:00
if ( ! mod . isCompatible ( ) )
2022-09-29 16:21:29 +02:00
{
auto compatibilityInfo = mod . getValue ( " compatibility " ) . toMap ( ) ;
auto minStr = compatibilityInfo . value ( " min " ) . toString ( ) ;
auto maxStr = compatibilityInfo . value ( " max " ) . toString ( ) ;
result + = incompatibleString . arg ( tr ( " Compatibility " ) ) ;
if ( minStr = = maxStr )
result + = supportedVersions . arg ( tr ( " Required VCMI version " ) , minStr , " " , " " ) ;
else
{
if ( minStr . isEmpty ( ) | | maxStr . isEmpty ( ) )
{
if ( minStr . isEmpty ( ) )
2024-05-01 11:43:20 +02:00
result + = supportedVersions . arg ( tr ( " Supported VCMI version " ) , maxStr , " , " , tr ( " please upgrade mod " ) ) ;
2022-09-29 16:21:29 +02:00
else
2024-05-01 12:46:39 +02:00
result + = supportedVersions . arg ( tr ( " Required VCMI version " ) , minStr , " " , tr ( " or newer " ) ) ;
2022-09-29 16:21:29 +02:00
}
else
result + = supportedVersions . arg ( tr ( " Supported VCMI versions " ) , minStr , " - " , maxStr ) ;
}
}
2023-03-26 23:26:58 +02:00
QStringList supportedLanguages ;
QVariant baseLanguageVariant = mod . getBaseValue ( " language " ) ;
QString baseLanguageID = baseLanguageVariant . isValid ( ) ? baseLanguageVariant . toString ( ) : " english " ;
bool needToShowSupportedLanguages = false ;
for ( const auto & language : Languages : : getLanguageList ( ) )
{
QString languageID = QString : : fromStdString ( language . identifier ) ;
if ( languageID ! = baseLanguageID & & ! mod . getValue ( languageID ) . isValid ( ) )
continue ;
if ( languageID ! = baseLanguageID )
needToShowSupportedLanguages = true ;
supportedLanguages + = QApplication : : translate ( " Language " , language . nameEnglish . c_str ( ) ) ;
}
if ( needToShowSupportedLanguages )
result + = replaceIfNotEmpty ( supportedLanguages , lineTemplate . arg ( tr ( " Languages " ) ) ) ;
2023-11-19 20:44:28 +02:00
result + = replaceIfNotEmpty ( getModNames ( mod . getDependencies ( ) ) , lineTemplate . arg ( tr ( " Required mods " ) ) ) ;
result + = replaceIfNotEmpty ( getModNames ( mod . getConflicts ( ) ) , lineTemplate . arg ( tr ( " Conflicting mods " ) ) ) ;
2023-12-10 19:16:45 +02:00
result + = replaceIfNotEmpty ( mod . getValue ( " description " ) , textTemplate . arg ( tr ( " Description " ) ) ) ;
2013-08-22 17:22:49 +03:00
result + = " <p></p> " ; // to get some empty space
2023-03-30 22:23:14 +02:00
QString unknownDeps = tr ( " This mod can not be installed or enabled because the following dependencies are not present " ) ;
QString blockingMods = tr ( " This mod can not be enabled because the following mods are incompatible with it " ) ;
QString hasActiveDependentMods = tr ( " This mod cannot be disabled because it is required by the following mods " ) ;
QString hasDependentMods = tr ( " This mod cannot be uninstalled or updated because it is required by the following mods " ) ;
QString thisIsSubmod = tr ( " This is a submod and it cannot be installed or uninstalled separately from its parent mod " ) ;
2013-08-22 17:22:49 +03:00
QString notes ;
2023-03-26 23:32:24 +02:00
notes + = replaceIfNotEmpty ( getModNames ( findInvalidDependencies ( mod . getName ( ) ) ) , listTemplate . arg ( unknownDeps ) ) ;
notes + = replaceIfNotEmpty ( getModNames ( findBlockingMods ( mod . getName ( ) ) ) , listTemplate . arg ( blockingMods ) ) ;
2018-04-13 07:34:58 +02:00
if ( mod . isEnabled ( ) )
2023-03-26 23:32:24 +02:00
notes + = replaceIfNotEmpty ( getModNames ( findDependentMods ( mod . getName ( ) , true ) ) , listTemplate . arg ( hasActiveDependentMods ) ) ;
2018-04-13 07:34:58 +02:00
if ( mod . isInstalled ( ) )
2023-03-26 23:32:24 +02:00
notes + = replaceIfNotEmpty ( getModNames ( findDependentMods ( mod . getName ( ) , false ) ) , listTemplate . arg ( hasDependentMods ) ) ;
2014-03-20 20:06:25 +03:00
2023-10-21 22:55:20 +02:00
if ( mod . isSubmod ( ) )
2014-03-20 20:06:25 +03:00
notes + = noteTemplate . arg ( thisIsSubmod ) ;
2013-08-22 17:22:49 +03:00
2018-04-13 07:34:58 +02:00
if ( notes . size ( ) )
2014-03-20 20:06:25 +03:00
result + = textTemplate . arg ( tr ( " Notes " ) ) . arg ( notes ) ;
2013-08-22 17:22:49 +03:00
return result ;
}
void CModListView : : disableModInfo ( )
{
2014-08-04 14:03:57 +03:00
ui - > disableButton - > setVisible ( false ) ;
ui - > enableButton - > setVisible ( false ) ;
ui - > installButton - > setVisible ( false ) ;
ui - > uninstallButton - > setVisible ( false ) ;
ui - > updateButton - > setVisible ( false ) ;
2013-08-22 17:22:49 +03:00
}
2014-03-20 20:06:25 +03:00
void CModListView : : dataChanged ( const QModelIndex & topleft , const QModelIndex & bottomRight )
2013-08-22 17:22:49 +03:00
{
2014-03-20 20:06:25 +03:00
selectMod ( ui - > allModsView - > currentIndex ( ) ) ;
}
void CModListView : : selectMod ( const QModelIndex & index )
{
2018-04-13 07:34:58 +02:00
if ( ! index . isValid ( ) )
2013-08-22 17:22:49 +03:00
{
disableModInfo ( ) ;
}
else
{
2024-03-02 23:24:00 +02:00
const auto modName = index . data ( ModRoles : : ModNameRole ) . toString ( ) ;
auto mod = modModel - > getMod ( modName ) ;
2013-08-22 17:22:49 +03:00
2014-03-23 15:08:01 +03:00
ui - > modInfoBrowser - > setHtml ( genModInfoText ( mod ) ) ;
ui - > changelogBrowser - > setHtml ( genChangelogText ( mod ) ) ;
2013-08-22 17:22:49 +03:00
2024-05-01 20:05:01 +02:00
Helper : : enableScrollBySwiping ( ui - > modInfoBrowser ) ;
Helper : : enableScrollBySwiping ( ui - > changelogBrowser ) ;
2024-03-02 23:24:00 +02:00
bool hasInvalidDeps = ! findInvalidDependencies ( modName ) . empty ( ) ;
bool hasBlockingMods = ! findBlockingMods ( modName ) . empty ( ) ;
bool hasDependentMods = ! findDependentMods ( modName , true ) . empty ( ) ;
2013-08-22 17:22:49 +03:00
ui - > disableButton - > setVisible ( mod . isEnabled ( ) ) ;
ui - > enableButton - > setVisible ( mod . isDisabled ( ) ) ;
2023-10-21 22:55:20 +02:00
ui - > installButton - > setVisible ( mod . isAvailable ( ) & & ! mod . isSubmod ( ) ) ;
ui - > uninstallButton - > setVisible ( mod . isInstalled ( ) & & ! mod . isSubmod ( ) ) ;
2013-08-22 17:22:49 +03:00
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.
2022-09-10 18:30:41 +02:00
ui - > disableButton - > setEnabled ( ! hasDependentMods & & ! mod . isEssential ( ) ) ;
2013-08-22 17:22:49 +03:00
ui - > enableButton - > setEnabled ( ! hasBlockingMods & & ! hasInvalidDeps ) ;
ui - > installButton - > setEnabled ( ! hasInvalidDeps ) ;
2022-09-10 18:30:41 +02:00
ui - > uninstallButton - > setEnabled ( ! hasDependentMods & & ! mod . isEssential ( ) ) ;
2013-08-22 17:22:49 +03:00
ui - > updateButton - > setEnabled ( ! hasInvalidDeps & & ! hasDependentMods ) ;
2014-03-23 15:08:01 +03:00
loadScreenshots ( ) ;
2013-08-22 17:22:49 +03:00
}
}
2018-04-13 07:34:58 +02:00
void CModListView : : modSelected ( const QModelIndex & current , const QModelIndex & )
2013-08-22 17:22:49 +03:00
{
2014-03-20 20:06:25 +03:00
selectMod ( current ) ;
2013-08-22 17:22:49 +03:00
}
2018-04-13 07:34:58 +02:00
void CModListView : : on_allModsView_activated ( const QModelIndex & index )
2013-08-22 17:22:49 +03:00
{
2014-03-20 20:06:25 +03:00
selectMod ( index ) ;
2023-01-26 01:01:41 +02:00
loadScreenshots ( ) ;
2013-08-22 17:22:49 +03:00
}
2018-04-13 07:34:58 +02:00
void CModListView : : on_lineEdit_textChanged ( const QString & arg1 )
2013-08-22 17:22:49 +03:00
{
2022-05-28 15:32:20 +02:00
# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto baseStr = QRegularExpression : : wildcardToRegularExpression ( arg1 , QRegularExpression : : UnanchoredWildcardConversion ) ;
# else
auto baseStr = QRegularExpression : : wildcardToRegularExpression ( arg1 ) ;
//Hack due to lack QRegularExpression::UnanchoredWildcardConversion in Qt5
baseStr . chop ( 3 ) ;
baseStr . remove ( 0 , 5 ) ;
# endif
QRegularExpression regExp { baseStr , QRegularExpression : : CaseInsensitiveOption } ;
filterModel - > setFilterRegularExpression ( regExp ) ;
2013-08-22 17:22:49 +03:00
}
void CModListView : : on_comboBox_currentIndexChanged ( int index )
{
2018-04-13 07:34:58 +02:00
switch ( index )
2013-08-22 17:22:49 +03:00
{
2018-04-13 07:34:58 +02:00
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 ) ;
break ;
2013-08-22 17:22:49 +03:00
}
}
QStringList CModListView : : findInvalidDependencies ( QString mod )
{
QStringList ret ;
2024-04-20 22:48:54 +02:00
for ( QString requirement : modModel - > getRequirements ( mod ) )
2013-08-22 17:22:49 +03:00
{
2024-04-20 23:05:37 +02:00
if ( ! modModel - > hasMod ( requirement ) & & ! modModel - > hasMod ( requirement . split ( QChar ( ' . ' ) ) [ 0 ] ) )
2024-04-20 22:48:54 +02:00
ret + = requirement ;
2013-08-22 17:22:49 +03:00
}
return ret ;
}
2018-10-29 17:56:14 +02:00
QStringList CModListView : : findBlockingMods ( QString modUnderTest )
2013-08-22 17:22:49 +03:00
{
QStringList ret ;
2018-10-29 17:56:14 +02:00
auto required = modModel - > getRequirements ( modUnderTest ) ;
2013-08-22 17:22:49 +03:00
2018-04-13 07:34:58 +02:00
for ( QString name : modModel - > getModList ( ) )
2013-08-22 17:22:49 +03:00
{
auto mod = modModel - > getMod ( name ) ;
2018-04-13 07:34:58 +02:00
if ( mod . isEnabled ( ) )
2013-08-22 17:22:49 +03:00
{
// one of enabled mods have requirement (or this mod) marked as conflict
2023-11-19 20:44:28 +02:00
for ( auto conflict : mod . getConflicts ( ) )
2018-04-13 07:34:58 +02:00
{
if ( required . contains ( conflict ) )
2013-08-22 17:22:49 +03:00
ret . push_back ( name ) ;
2018-04-13 07:34:58 +02:00
}
2013-08-22 17:22:49 +03:00
}
}
return ret ;
}
QStringList CModListView : : findDependentMods ( QString mod , bool excludeDisabled )
{
QStringList ret ;
2018-04-13 07:34:58 +02:00
for ( QString modName : modModel - > getModList ( ) )
2013-08-22 17:22:49 +03:00
{
auto current = modModel - > getMod ( modName ) ;
2024-01-23 12:57:38 +02:00
if ( ! current . isInstalled ( ) | | ! current . isVisible ( ) )
2013-09-08 16:02:34 +03:00
continue ;
2023-11-19 20:44:28 +02:00
if ( current . getDependencies ( ) . contains ( mod , Qt : : CaseInsensitive ) )
2018-04-13 07:34:58 +02:00
{
if ( ! ( current . isDisabled ( ) & & excludeDisabled ) )
ret + = modName ;
}
2013-08-22 17:22:49 +03:00
}
return ret ;
}
void CModListView : : on_enableButton_clicked ( )
{
2014-03-20 20:06:25 +03:00
QString modName = ui - > allModsView - > currentIndex ( ) . data ( ModRoles : : ModNameRole ) . toString ( ) ;
2022-12-26 02:34:10 +02:00
enableModByName ( modName ) ;
checkManagerErrors ( ) ;
}
2013-08-22 17:22:49 +03:00
2022-12-26 02:34:10 +02:00
void CModListView : : enableModByName ( QString modName )
{
2013-08-22 17:22:49 +03:00
assert ( findBlockingMods ( modName ) . empty ( ) ) ;
assert ( findInvalidDependencies ( modName ) . empty ( ) ) ;
2018-04-13 07:34:58 +02:00
for ( auto & name : modModel - > getRequirements ( modName ) )
{
if ( modModel - > getMod ( name ) . isDisabled ( ) )
2013-08-22 17:22:49 +03:00
manager - > enableMod ( name ) ;
2018-04-13 07:34:58 +02:00
}
2022-12-26 02:34:10 +02:00
emit modsChanged ( ) ;
2013-08-22 17:22:49 +03:00
}
void CModListView : : on_disableButton_clicked ( )
{
2014-03-20 20:06:25 +03:00
QString modName = ui - > allModsView - > currentIndex ( ) . data ( ModRoles : : ModNameRole ) . toString ( ) ;
2013-08-22 17:22:49 +03:00
2022-12-26 02:34:10 +02:00
disableModByName ( modName ) ;
checkManagerErrors ( ) ;
}
void CModListView : : disableModByName ( QString modName )
{
2018-04-13 07:34:58 +02:00
if ( modModel - > hasMod ( modName ) & & modModel - > getMod ( modName ) . isEnabled ( ) )
manager - > disableMod ( modName ) ;
2013-09-08 21:35:09 +03:00
2022-12-26 02:34:10 +02:00
emit modsChanged ( ) ;
2013-08-22 17:22:49 +03:00
}
void CModListView : : on_updateButton_clicked ( )
{
2014-03-20 20:06:25 +03:00
QString modName = ui - > allModsView - > currentIndex ( ) . data ( ModRoles : : ModNameRole ) . toString ( ) ;
2013-08-22 17:22:49 +03:00
assert ( findInvalidDependencies ( modName ) . empty ( ) ) ;
2018-04-13 07:34:58 +02:00
for ( auto & name : modModel - > getRequirements ( modName ) )
2013-08-22 17:22:49 +03:00
{
auto mod = modModel - > getMod ( name ) ;
// update required mod, install missing (can be new dependency)
2018-04-13 07:34:58 +02:00
if ( mod . isUpdateable ( ) | | ! mod . isInstalled ( ) )
2024-05-01 11:43:20 +02:00
downloadFile ( name + " .zip " , mod . getValue ( " download " ) . toString ( ) , name , mbToBytes ( mod . getValue ( " downloadSize " ) . toDouble ( ) ) ) ;
2013-08-22 17:22:49 +03:00
}
}
void CModListView : : on_uninstallButton_clicked ( )
{
2014-03-20 20:06:25 +03:00
QString modName = ui - > allModsView - > currentIndex ( ) . data ( ModRoles : : ModNameRole ) . toString ( ) ;
2013-08-22 17:22:49 +03:00
// NOTE: perhaps add "manually installed" flag and uninstall those dependencies that don't have it?
2018-04-13 07:34:58 +02:00
if ( modModel - > hasMod ( modName ) & & modModel - > getMod ( modName ) . isInstalled ( ) )
2013-08-22 17:22:49 +03:00
{
2018-04-13 07:34:58 +02:00
if ( modModel - > getMod ( modName ) . isEnabled ( ) )
2013-11-14 16:21:09 +03:00
manager - > disableMod ( modName ) ;
2013-08-22 17:22:49 +03:00
manager - > uninstallMod ( modName ) ;
}
2022-12-26 02:34:10 +02:00
emit modsChanged ( ) ;
2013-09-06 00:25:03 +03:00
checkManagerErrors ( ) ;
2013-08-22 17:22:49 +03:00
}
void CModListView : : on_installButton_clicked ( )
{
2014-03-20 20:06:25 +03:00
QString modName = ui - > allModsView - > currentIndex ( ) . data ( ModRoles : : ModNameRole ) . toString ( ) ;
2013-08-22 17:22:49 +03:00
assert ( findInvalidDependencies ( modName ) . empty ( ) ) ;
2018-04-13 07:34:58 +02:00
for ( auto & name : modModel - > getRequirements ( modName ) )
2013-08-22 17:22:49 +03:00
{
auto mod = modModel - > getMod ( name ) ;
2024-06-07 17:03:45 +02:00
if ( mod . isAvailable ( ) )
2024-05-01 11:43:20 +02:00
downloadFile ( name + " .zip " , mod . getValue ( " download " ) . toString ( ) , name , mbToBytes ( mod . getValue ( " downloadSize " ) . toDouble ( ) ) ) ;
2024-04-20 22:48:54 +02:00
else if ( ! mod . isEnabled ( ) )
enableModByName ( name ) ;
}
for ( auto & name : modModel - > getMod ( modName ) . getConflicts ( ) )
{
auto mod = modModel - > getMod ( name ) ;
if ( mod . isEnabled ( ) )
{
//TODO: consider reverse dependencies disabling
//TODO: consider if it may be possible for subdependencies to block disabling conflicting mod?
//TODO: consider if it may be possible to get subconflicts that will block disabling conflicting mod?
disableModByName ( name ) ;
}
2013-08-22 17:22:49 +03:00
}
}
2024-04-20 00:26:58 +02:00
void CModListView : : on_installFromFileButton_clicked ( )
{
2024-07-10 20:41:26 +02:00
// iOS can't display modal dialogs when called directly on button press
// https://bugreports.qt.io/browse/QTBUG-98651
QTimer : : singleShot ( 0 , this , [ this ]
2024-04-20 00:26:58 +02:00
{
2024-09-02 22:51:30 +02:00
QString filter = tr ( " All supported files " ) + " (*.h3m *.vmap *.h3c *.vcmp *.zip *.json *.exe);; " +
tr ( " Maps " ) + " (*.h3m *.vmap);; " +
tr ( " Campaigns " ) + " (*.h3c *.vcmp);; " +
tr ( " Configs " ) + " (*.json);; " +
tr ( " Mods " ) + " (*.zip);; " +
tr ( " Gog files " ) + " (*.exe) " ;
2024-09-01 13:39:40 +02:00
# if defined(VCMI_MOBILE)
filter = tr ( " All files (*.*) " ) ; //Workaround for sometimes incorrect mime for some extensions (e.g. for exe)
# endif
2024-08-30 21:17:18 +02:00
QStringList files = QFileDialog : : getOpenFileNames ( this , tr ( " Select files (configs, mods, maps, campaigns, gog files) to install... " ) , QDir : : homePath ( ) , filter ) ;
2024-07-10 20:41:26 +02:00
for ( const auto & file : files )
{
manualInstallFile ( file ) ;
}
} ) ;
2024-04-20 00:26:58 +02:00
}
2024-07-08 15:10:44 +02:00
void CModListView : : manualInstallFile ( QString filePath )
2024-04-21 01:32:31 +02:00
{
2024-07-08 15:10:44 +02:00
QString fileName = QFileInfo { filePath } . fileName ( ) ;
if ( filePath . endsWith ( " .zip " , Qt : : CaseInsensitive ) )
2024-04-21 01:32:31 +02:00
downloadFile ( fileName . toLower ( )
// mod name currently comes from zip file -> remove suffixes from github zip download
. replace ( QRegularExpression ( " -[0-9a-f]{40} " ) , " " )
. replace ( QRegularExpression ( " -vcmi-.+ \\ .zip " ) , " .zip " )
. replace ( " -main.zip " , " .zip " )
2024-07-08 15:10:44 +02:00
, QUrl : : fromLocalFile ( filePath ) , " mods " ) ;
else if ( filePath . endsWith ( " .json " , Qt : : CaseInsensitive ) )
2024-04-21 01:32:31 +02:00
{
QDir configDir ( QString : : fromStdString ( VCMIDirs : : get ( ) . userConfigPath ( ) . string ( ) ) ) ;
2024-04-21 12:13:50 +02:00
QStringList configFile = configDir . entryList ( { fileName } , QDir : : Filter : : Files ) ; // case insensitive check
2024-04-21 01:32:31 +02:00
if ( ! configFile . empty ( ) )
{
2024-04-21 16:56:39 +02:00
auto dialogResult = QMessageBox : : warning ( this , tr ( " Replace config file? " ) , tr ( " Do you want to replace %1? " ) . arg ( configFile [ 0 ] ) , QMessageBox : : Yes | QMessageBox : : No , QMessageBox : : No ) ;
if ( dialogResult = = QMessageBox : : Yes )
2024-04-21 01:32:31 +02:00
{
2024-04-21 16:56:39 +02:00
const auto configFilePath = configDir . filePath ( configFile [ 0 ] ) ;
QFile : : remove ( configFilePath ) ;
2024-07-08 15:10:44 +02:00
QFile : : copy ( filePath , configFilePath ) ;
2024-04-21 01:32:31 +02:00
// reload settings
2024-04-21 16:56:39 +02:00
Helper : : loadSettings ( ) ;
2024-04-21 12:13:50 +02:00
for ( auto widget : qApp - > allWidgets ( ) )
if ( auto settingsView = qobject_cast < CSettingsView * > ( widget ) )
2024-04-21 01:32:31 +02:00
settingsView - > loadSettings ( ) ;
manager - > loadMods ( ) ;
manager - > loadModSettings ( ) ;
}
}
}
else
2024-07-08 15:10:44 +02:00
downloadFile ( fileName , QUrl : : fromLocalFile ( filePath ) , fileName ) ;
2024-04-21 01:32:31 +02:00
}
2023-09-01 16:43:26 +02:00
void CModListView : : downloadFile ( QString file , QString url , QString description , qint64 size )
2024-07-08 15:10:44 +02:00
{
downloadFile ( file , QUrl { url } , description , size ) ;
}
void CModListView : : downloadFile ( QString file , QUrl url , QString description , qint64 size )
2013-08-22 17:22:49 +03:00
{
2018-04-13 07:34:58 +02:00
if ( ! dlManager )
2013-08-22 17:22:49 +03:00
{
dlManager = new CDownloadManager ( ) ;
ui - > progressWidget - > setVisible ( true ) ;
connect ( dlManager , SIGNAL ( downloadProgress ( qint64 , qint64 ) ) ,
2018-04-13 07:34:58 +02:00
this , SLOT ( downloadProgress ( qint64 , qint64 ) ) ) ;
2013-08-22 17:22:49 +03:00
connect ( dlManager , SIGNAL ( finished ( QStringList , QStringList , QStringList ) ) ,
2018-04-13 07:34:58 +02:00
this , SLOT ( downloadFinished ( QStringList , QStringList , QStringList ) ) ) ;
2024-07-08 15:10:44 +02:00
2023-09-19 23:38:00 +02:00
connect ( manager . get ( ) , SIGNAL ( extractionProgress ( qint64 , qint64 ) ) ,
2023-12-18 00:34:00 +02:00
this , SLOT ( extractionProgress ( qint64 , qint64 ) ) ) ;
2013-08-22 17:22:49 +03:00
2024-07-08 15:10:44 +02:00
connect ( modModel , & CModListModel : : dataChanged , filterModel , & QAbstractItemModel : : dataChanged ) ;
2013-08-22 17:22:49 +03:00
2024-07-08 15:10:44 +02:00
const auto progressBarFormat = tr ( " Downloading %1. %p% (%v MB out of %m MB) finished " ) . arg ( description ) ;
2013-08-22 17:22:49 +03:00
ui - > progressBar - > setFormat ( progressBarFormat ) ;
}
2024-07-08 15:10:44 +02:00
dlManager - > downloadFile ( url , file , size ) ;
2013-08-22 17:22:49 +03:00
}
void CModListView : : downloadProgress ( qint64 current , qint64 max )
{
2023-09-01 22:18:23 +02:00
// display progress, in megabytes
2023-09-19 23:38:00 +02:00
ui - > progressBar - > setVisible ( true ) ;
2023-09-01 22:18:23 +02:00
ui - > progressBar - > setMaximum ( max / ( 1024 * 1024 ) ) ;
ui - > progressBar - > setValue ( current / ( 1024 * 1024 ) ) ;
2013-08-22 17:22:49 +03:00
}
2023-12-18 00:34:00 +02:00
void CModListView : : extractionProgress ( qint64 current , qint64 max )
{
// display progress, in extracted files
ui - > progressBar - > setVisible ( true ) ;
ui - > progressBar - > setMaximum ( max ) ;
ui - > progressBar - > setValue ( current ) ;
}
2013-08-22 17:22:49 +03:00
void CModListView : : downloadFinished ( QStringList savedFiles , QStringList failedFiles , QStringList errors )
{
2023-09-01 22:18:23 +02:00
QString title = tr ( " Download failed " ) ;
QString firstLine = tr ( " Unable to download all files. \n \n Encountered errors: \n \n " ) ;
QString lastLine = tr ( " \n \n Install successfully downloaded? " ) ;
2022-09-29 15:38:03 +02:00
bool doInstallFiles = false ;
2013-08-22 17:22:49 +03:00
// if all files were d/loaded there should be no errors. And on failure there must be an error
assert ( failedFiles . empty ( ) = = errors . empty ( ) ) ;
2018-04-13 07:34:58 +02:00
if ( savedFiles . empty ( ) )
2013-08-22 17:22:49 +03:00
{
// no successfully downloaded mods
2018-04-13 07:34:58 +02:00
QMessageBox : : warning ( this , title , firstLine + errors . join ( " \n " ) , QMessageBox : : Ok , QMessageBox : : Ok ) ;
2013-08-22 17:22:49 +03:00
}
2018-04-13 07:34:58 +02:00
else if ( ! failedFiles . empty ( ) )
2013-08-22 17:22:49 +03:00
{
// some mods were not downloaded
int result = QMessageBox : : warning ( this , title , firstLine + errors . join ( " \n " ) + lastLine ,
QMessageBox : : Yes | QMessageBox : : No , QMessageBox : : No ) ;
2018-04-13 07:34:58 +02:00
if ( result = = QMessageBox : : Yes )
2022-09-29 15:38:03 +02:00
doInstallFiles = true ;
2013-08-22 17:22:49 +03:00
}
else
{
// everything OK
2022-09-29 15:38:03 +02:00
doInstallFiles = true ;
2013-08-22 17:22:49 +03:00
}
dlManager - > deleteLater ( ) ;
dlManager = nullptr ;
2023-09-19 23:38:00 +02:00
ui - > progressBar - > setMaximum ( 0 ) ;
ui - > progressBar - > setValue ( 0 ) ;
2022-09-29 15:38:03 +02:00
if ( doInstallFiles )
installFiles ( savedFiles ) ;
2022-12-26 02:34:10 +02:00
2023-09-19 23:38:00 +02:00
hideProgressBar ( ) ;
2022-12-26 02:34:10 +02:00
emit modsChanged ( ) ;
2013-08-22 17:22:49 +03:00
}
void CModListView : : hideProgressBar ( )
{
2018-04-13 07:34:58 +02:00
if ( dlManager = = nullptr ) // it was not recreated meanwhile
2013-08-22 17:22:49 +03:00
{
ui - > progressWidget - > setVisible ( false ) ;
ui - > progressBar - > setMaximum ( 0 ) ;
ui - > progressBar - > setValue ( 0 ) ;
}
}
void CModListView : : installFiles ( QStringList files )
{
QStringList mods ;
2023-12-25 19:53:02 +02:00
QStringList maps ;
2014-03-23 15:08:01 +03:00
QStringList images ;
2024-08-30 21:17:18 +02:00
QStringList exe ;
2023-09-05 12:33:26 +02:00
QVector < QVariantMap > repositories ;
2013-08-22 17:22:49 +03:00
// TODO: some better way to separate zip's with mods and downloaded repository files
2018-04-13 07:34:58 +02:00
for ( QString filename : files )
2013-08-22 17:22:49 +03:00
{
2023-12-25 23:41:15 +02:00
if ( filename . endsWith ( " .zip " , Qt : : CaseInsensitive ) )
2013-08-22 17:22:49 +03:00
mods . push_back ( filename ) ;
2023-12-25 23:41:15 +02:00
else if ( filename . endsWith ( " .h3m " , Qt : : CaseInsensitive ) | | filename . endsWith ( " .h3c " , Qt : : CaseInsensitive ) | | filename . endsWith ( " .vmap " , Qt : : CaseInsensitive ) | | filename . endsWith ( " .vcmp " , Qt : : CaseInsensitive ) )
2023-12-25 19:53:02 +02:00
maps . push_back ( filename ) ;
2024-08-30 21:17:18 +02:00
if ( filename . endsWith ( " .exe " , Qt : : CaseInsensitive ) )
exe . push_back ( filename ) ;
2023-12-25 23:41:15 +02:00
else if ( filename . endsWith ( " .json " , Qt : : CaseInsensitive ) )
2022-09-29 15:38:03 +02:00
{
//download and merge additional files
2023-09-05 12:33:26 +02:00
auto repoData = JsonUtils : : JsonFromFile ( filename ) . toMap ( ) ;
if ( repoData . value ( " name " ) . isNull ( ) )
2022-09-29 15:38:03 +02:00
{
2023-09-05 12:33:26 +02:00
for ( const auto & key : repoData . keys ( ) )
2022-09-29 15:38:03 +02:00
{
2023-09-05 12:33:26 +02:00
auto modjson = repoData [ key ] . toMap ( ) . value ( " mod " ) ;
2022-09-29 15:38:03 +02:00
if ( ! modjson . isNull ( ) )
{
2024-05-01 12:46:39 +02:00
downloadFile ( key + " .json " , modjson . toString ( ) , tr ( " mods repository index " ) ) ;
2022-09-29 15:38:03 +02:00
}
}
}
else
{
auto modn = QFileInfo ( filename ) . baseName ( ) ;
QVariantMap temp ;
2023-09-05 12:33:26 +02:00
temp [ modn ] = repoData ;
repoData = temp ;
2022-09-29 15:38:03 +02:00
}
2023-09-05 12:33:26 +02:00
repositories . push_back ( repoData ) ;
2022-09-29 15:38:03 +02:00
}
2023-12-25 23:41:15 +02:00
else if ( filename . endsWith ( " .png " , Qt : : CaseInsensitive ) )
2014-03-23 15:08:01 +03:00
images . push_back ( filename ) ;
2013-08-22 17:22:49 +03:00
}
2023-09-05 12:33:26 +02:00
2024-04-22 11:34:25 +02:00
if ( ! repositories . empty ( ) )
manager - > loadRepositories ( repositories ) ;
2023-09-05 12:33:26 +02:00
2018-04-13 07:34:58 +02:00
if ( ! mods . empty ( ) )
2013-08-22 17:22:49 +03:00
installMods ( mods ) ;
2014-03-23 15:08:01 +03:00
2023-12-25 19:53:02 +02:00
if ( ! maps . empty ( ) )
installMaps ( maps ) ;
2024-08-30 21:17:18 +02:00
if ( ! exe . empty ( ) )
2024-08-30 22:20:33 +02:00
{
2024-08-31 18:46:45 +02:00
ui - > progressBar - > setFormat ( tr ( " Installing chronicles " ) ) ;
2024-08-31 00:44:20 +02:00
2024-08-31 18:46:45 +02:00
float prog = 0.0 ;
auto futureExtract = std : : async ( std : : launch : : async , [ this , exe , & prog ] ( )
{
2024-08-31 22:05:36 +02:00
ChroniclesExtractor ce ( this , [ & prog ] ( float progress ) { prog = progress ; } ) ;
2024-08-31 18:46:45 +02:00
ce . installChronicles ( exe ) ;
return true ;
} ) ;
while ( futureExtract . wait_for ( std : : chrono : : milliseconds ( 10 ) ) ! = std : : future_status : : ready )
{
2024-09-01 02:16:03 +02:00
emit extractionProgress ( static_cast < int > ( prog * 1000.f ) , 1000 ) ;
2024-08-31 18:46:45 +02:00
qApp - > processEvents ( ) ;
}
if ( futureExtract . get ( ) )
{
//update
CResourceHandler : : get ( " initial " ) - > updateFilteredFiles ( [ ] ( const std : : string & ) { return true ; } ) ;
manager - > loadMods ( ) ;
modModel - > reloadRepositories ( ) ;
emit modsChanged ( ) ;
}
2024-08-30 22:20:33 +02:00
}
2024-08-30 21:17:18 +02:00
2018-04-13 07:34:58 +02:00
if ( ! images . empty ( ) )
2014-03-23 15:08:01 +03:00
loadScreenshots ( ) ;
2013-08-22 17:22:49 +03:00
}
void CModListView : : installMods ( QStringList archives )
{
QStringList modNames ;
2018-04-13 07:34:58 +02:00
for ( QString archive : archives )
2013-08-22 17:22:49 +03:00
{
// get basename out of full file name
// remove path remove extension
QString modName = archive . section ( ' / ' , - 1 , - 1 ) . section ( ' . ' , 0 , 0 ) ;
modNames . push_back ( modName ) ;
}
2013-12-08 13:07:06 +03:00
QStringList modsToEnable ;
2013-08-22 17:22:49 +03:00
// disable mod(s), to properly recalculate dependencies, if changed
2018-04-13 07:34:58 +02:00
for ( QString mod : boost : : adaptors : : reverse ( modNames ) )
2013-09-06 00:25:03 +03:00
{
2013-12-08 13:07:06 +03:00
CModEntry entry = modModel - > getMod ( mod ) ;
2018-04-13 07:34:58 +02:00
if ( entry . isInstalled ( ) )
2013-12-08 13:07:06 +03:00
{
// enable mod if installed and enabled
2018-04-13 07:34:58 +02:00
if ( entry . isEnabled ( ) )
2013-12-08 13:07:06 +03:00
modsToEnable . push_back ( mod ) ;
}
else
{
// enable mod if m
2018-04-13 07:34:58 +02:00
if ( settings [ " launcher " ] [ " enableInstalledMods " ] . Bool ( ) )
2013-12-08 13:07:06 +03:00
modsToEnable . push_back ( mod ) ;
}
2013-09-06 00:25:03 +03:00
}
2013-08-22 17:22:49 +03:00
// uninstall old version of mod, if installed
2018-04-13 07:34:58 +02:00
for ( QString mod : boost : : adaptors : : reverse ( modNames ) )
2013-09-06 00:25:03 +03:00
{
2018-04-13 07:34:58 +02:00
if ( modModel - > getMod ( mod ) . isInstalled ( ) )
2013-09-06 00:25:03 +03:00
manager - > uninstallMod ( mod ) ;
}
2013-08-22 17:22:49 +03:00
2018-04-13 07:34:58 +02:00
for ( int i = 0 ; i < modNames . size ( ) ; i + + )
2023-09-19 23:38:00 +02:00
{
ui - > progressBar - > setFormat ( tr ( " Installing mod %1 " ) . arg ( modNames [ i ] ) ) ;
2013-08-22 17:22:49 +03:00
manager - > installMod ( modNames [ i ] , archives [ i ] ) ;
2023-09-19 23:38:00 +02:00
}
2013-08-22 17:22:49 +03:00
2014-03-23 19:36:16 +03:00
std : : function < void ( QString ) > enableMod ;
enableMod = [ & ] ( QString modName )
2014-03-23 15:08:01 +03:00
{
auto mod = modModel - > getMod ( modName ) ;
2018-04-13 07:34:58 +02:00
if ( mod . isInstalled ( ) & & ! mod . getValue ( " keepDisabled " ) . toBool ( ) )
2014-03-23 15:08:01 +03:00
{
2024-06-07 17:05:02 +02:00
for ( auto const & dependencyName : mod . getDependencies ( ) )
{
auto dependency = modModel - > getMod ( dependencyName ) ;
if ( dependency . isDisabled ( ) )
manager - > enableMod ( dependencyName ) ;
}
2022-09-10 18:30:41 +02:00
if ( mod . isDisabled ( ) & & manager - > enableMod ( modName ) )
2014-03-23 15:08:01 +03:00
{
2018-04-13 07:34:58 +02:00
for ( QString child : modModel - > getChildren ( modName ) )
2014-03-23 15:08:01 +03:00
enableMod ( child ) ;
}
}
} ;
2018-04-13 07:34:58 +02:00
for ( QString mod : modsToEnable )
2014-03-23 15:08:01 +03:00
{
enableMod ( mod ) ;
}
2013-08-22 17:22:49 +03:00
2023-09-11 17:10:07 +02:00
checkManagerErrors ( ) ;
2018-04-13 07:34:58 +02:00
for ( QString archive : archives )
2013-08-22 17:22:49 +03:00
QFile : : remove ( archive ) ;
}
2023-12-25 23:41:15 +02:00
void CModListView : : installMaps ( QStringList maps )
2023-12-25 19:53:02 +02:00
{
2024-02-06 00:33:47 +02:00
const auto destDir = CLauncherDirs : : mapsPath ( ) + QChar { ' / ' } ;
2023-12-25 19:53:02 +02:00
2023-12-25 23:41:15 +02:00
for ( QString map : maps )
2023-12-25 19:53:02 +02:00
{
2023-12-25 23:41:15 +02:00
QFile ( map ) . rename ( destDir + map . section ( ' / ' , - 1 , - 1 ) ) ;
2023-12-25 19:53:02 +02:00
}
}
2014-11-03 17:47:37 +02:00
void CModListView : : on_refreshButton_clicked ( )
{
loadRepositories ( ) ;
}
2013-08-22 17:22:49 +03:00
void CModListView : : on_pushButton_clicked ( )
{
delete dlManager ;
dlManager = nullptr ;
hideProgressBar ( ) ;
}
void CModListView : : modelReset ( )
{
2023-01-26 01:01:41 +02:00
selectMod ( filterModel - > rowCount ( ) > 0 ? filterModel - > index ( 0 , 0 ) : QModelIndex ( ) ) ;
2013-08-24 23:11:51 +03:00
}
2013-09-06 00:25:03 +03:00
void CModListView : : checkManagerErrors ( )
{
QString errors = manager - > getErrors ( ) . join ( ' \n ' ) ;
2018-04-13 07:34:58 +02:00
if ( errors . size ( ) ! = 0 )
2013-09-06 00:25:03 +03:00
{
2023-09-01 22:18:23 +02:00
QString title = tr ( " Operation failed " ) ;
QString description = tr ( " Encountered errors: \n " ) + errors ;
2018-04-13 07:34:58 +02:00
QMessageBox : : warning ( this , title , description , QMessageBox : : Ok , QMessageBox : : Ok ) ;
2013-09-06 00:25:03 +03:00
}
}
2014-03-23 15:08:01 +03:00
void CModListView : : on_tabWidget_currentChanged ( int index )
{
loadScreenshots ( ) ;
}
void CModListView : : loadScreenshots ( )
{
2023-01-26 01:01:41 +02:00
if ( ui - > tabWidget - > currentIndex ( ) = = 2 )
2014-03-23 15:08:01 +03:00
{
2024-07-19 12:38:42 +02:00
if ( ! ui - > allModsView - > currentIndex ( ) . isValid ( ) )
2024-07-18 22:28:47 +02:00
{
// select the first mod, so we can access its data
ui - > allModsView - > setCurrentIndex ( filterModel - > index ( 0 , 0 ) ) ;
}
2014-03-23 15:08:01 +03:00
ui - > screenshotsList - > clear ( ) ;
QString modName = ui - > allModsView - > currentIndex ( ) . data ( ModRoles : : ModNameRole ) . toString ( ) ;
assert ( modModel - > hasMod ( modName ) ) ; //should be filtered out by check above
2024-02-06 00:33:47 +02:00
for ( QString url : modModel - > getMod ( modName ) . getValue ( " screenshots " ) . toStringList ( ) )
2014-03-23 15:08:01 +03:00
{
// URL must be encoded to something else to get rid of symbols illegal in file names
2024-02-06 00:33:47 +02:00
const auto hashed = QCryptographicHash : : hash ( url . toUtf8 ( ) , QCryptographicHash : : Md5 ) ;
const auto fileName = QString { QLatin1String { " %1.png " } } . arg ( QLatin1String { hashed . toHex ( ) } ) ;
2014-03-23 15:08:01 +03:00
2024-02-06 00:33:47 +02:00
const auto fullPath = QString { QLatin1String { " %1/%2 " } } . arg ( CLauncherDirs : : downloadsPath ( ) , fileName ) ;
2014-03-23 15:08:01 +03:00
QPixmap pixmap ( fullPath ) ;
2018-04-13 07:34:58 +02:00
if ( pixmap . isNull ( ) )
2014-03-23 15:08:01 +03:00
{
// image file not exists or corrupted - try to redownload
2024-05-01 11:43:20 +02:00
downloadFile ( fileName , url , tr ( " screenshots " ) ) ;
2014-03-23 15:08:01 +03:00
}
else
{
// managed to load cached image
QIcon icon ( pixmap ) ;
2024-01-16 23:40:48 +02:00
auto * item = new QListWidgetItem ( icon , QString ( tr ( " Screenshot %1 " ) ) . arg ( ui - > screenshotsList - > count ( ) + 1 ) ) ;
2014-03-23 15:08:01 +03:00
ui - > screenshotsList - > addItem ( item ) ;
}
}
}
}
2018-04-13 07:34:58 +02:00
void CModListView : : on_screenshotsList_clicked ( const QModelIndex & index )
2014-03-23 15:08:01 +03:00
{
2018-04-13 07:34:58 +02:00
if ( index . isValid ( ) )
2014-03-23 15:08:01 +03:00
{
QIcon icon = ui - > screenshotsList - > item ( index . row ( ) ) - > icon ( ) ;
auto pixmap = icon . pixmap ( icon . availableSizes ( ) [ 0 ] ) ;
ImageViewer : : showPixmap ( pixmap , this ) ;
}
}
2014-08-04 14:03:57 +03:00
2022-11-16 21:53:54 +02:00
const CModList & CModListView : : getModList ( ) const
{
assert ( modModel ) ;
return * modModel ;
}
2023-03-11 00:57:55 +02:00
void CModListView : : doInstallMod ( const QString & modName )
{
assert ( findInvalidDependencies ( modName ) . empty ( ) ) ;
for ( auto & name : modModel - > getRequirements ( modName ) )
{
auto mod = modModel - > getMod ( name ) ;
if ( ! mod . isInstalled ( ) )
2024-05-01 11:43:20 +02:00
downloadFile ( name + " .zip " , mod . getValue ( " download " ) . toString ( ) , name , mbToBytes ( mod . getValue ( " downloadSize " ) . toDouble ( ) ) ) ;
2023-03-11 00:57:55 +02:00
}
}
2023-03-26 19:11:59 +02:00
bool CModListView : : isModAvailable ( const QString & modName )
2023-03-11 00:57:55 +02:00
{
auto mod = modModel - > getMod ( modName ) ;
2023-03-26 19:11:59 +02:00
return mod . isAvailable ( ) ;
2023-03-11 00:57:55 +02:00
}
2023-03-14 13:37:22 +02:00
bool CModListView : : isModEnabled ( const QString & modName )
{
auto mod = modModel - > getMod ( modName ) ;
return mod . isEnabled ( ) ;
}
2024-09-14 21:34:39 +02:00
bool CModListView : : isModInstalled ( const QString & modName )
{
auto mod = modModel - > getMod ( modName ) ;
return mod . isInstalled ( ) ;
}
2023-03-11 00:57:55 +02:00
QString CModListView : : getTranslationModName ( const QString & language )
{
2023-03-12 18:33:29 +02:00
for ( const auto & modName : modModel - > getModList ( ) )
2023-03-11 00:57:55 +02:00
{
auto mod = modModel - > getMod ( modName ) ;
2023-03-14 15:59:33 +02:00
if ( ! mod . isTranslation ( ) )
2023-03-11 00:57:55 +02:00
continue ;
2023-03-14 13:37:22 +02:00
if ( mod . getBaseValue ( " language " ) . toString ( ) ! = language )
2023-03-11 00:57:55 +02:00
continue ;
return modName ;
}
return QString ( ) ;
}
2023-09-01 01:29:50 +02:00
void CModListView : : on_allModsView_doubleClicked ( const QModelIndex & index )
{
if ( ! index . isValid ( ) )
return ;
2023-09-01 15:40:52 +02:00
auto modName = index . data ( ModRoles : : ModNameRole ) . toString ( ) ;
auto mod = modModel - > getMod ( modName ) ;
bool hasInvalidDeps = ! findInvalidDependencies ( modName ) . empty ( ) ;
bool hasBlockingMods = ! findBlockingMods ( modName ) . empty ( ) ;
bool hasDependentMods = ! findDependentMods ( modName , true ) . empty ( ) ;
2023-10-21 22:55:20 +02:00
if ( ! hasInvalidDeps & & mod . isAvailable ( ) & & ! mod . isSubmod ( ) )
2023-09-01 01:29:50 +02:00
{
on_installButton_clicked ( ) ;
return ;
}
2023-09-01 15:40:52 +02:00
if ( ! hasInvalidDeps & & ! hasDependentMods & & mod . isUpdateable ( ) & & index . column ( ) = = ModFields : : STATUS_UPDATE )
2023-09-01 01:29:50 +02:00
{
on_updateButton_clicked ( ) ;
2023-09-01 15:40:52 +02:00
return ;
}
if ( index . column ( ) = = ModFields : : NAME )
{
if ( ui - > allModsView - > isExpanded ( index ) )
ui - > allModsView - > collapse ( index ) ;
else
ui - > allModsView - > expand ( index ) ;
return ;
2023-09-01 01:29:50 +02:00
}
2023-09-01 15:40:52 +02:00
if ( ! hasBlockingMods & & ! hasInvalidDeps & & mod . isDisabled ( ) )
2023-09-01 01:29:50 +02:00
{
on_enableButton_clicked ( ) ;
return ;
}
2023-09-01 15:40:52 +02:00
if ( ! hasDependentMods & & ! mod . isEssential ( ) & & mod . isEnabled ( ) )
2023-09-01 01:29:50 +02:00
{
on_disableButton_clicked ( ) ;
2023-09-01 15:40:52 +02:00
return ;
2023-09-01 01:29:50 +02:00
}
}