2013-02-25 21:22:07 +01:00
# include "datafilespage.hpp"
2021-08-01 23:35:45 +02:00
# include "maindialog.hpp"
2013-02-25 21:22:07 +01:00
2014-04-16 16:54:55 +02:00
# include <QDebug>
2013-03-12 01:29:13 +01:00
# include <QPushButton>
# include <QMessageBox>
2020-04-26 15:31:39 +02:00
# include <QFileDialog>
2022-06-16 21:29:55 +02:00
2018-05-27 17:15:36 -05:00
# include <thread>
2018-06-03 16:59:27 -05:00
# include <mutex>
2021-08-01 23:35:45 +02:00
# include <algorithm>
2011-04-08 00:04:09 +02:00
2018-05-27 17:15:36 -05:00
# include <apps/launcher/utils/cellnameloader.hpp>
2012-01-21 01:14:35 +01:00
# include <components/files/configurationmanager.hpp>
2011-04-08 00:04:09 +02:00
2013-09-21 23:06:29 -05:00
# include <components/contentselector/model/esmfile.hpp>
2013-12-25 00:50:25 +01:00
# include <components/contentselector/view/contentselector.hpp>
# include <components/config/gamesettings.hpp>
# include <components/config/launchersettings.hpp>
2013-10-22 21:52:35 -05:00
2022-07-11 20:41:07 +04:00
# include <components/settings/settings.hpp>
2020-04-26 15:31:39 +02:00
# include <components/bsa/compressedbsafile.hpp>
2021-12-19 22:49:41 +01:00
# include <components/navmeshtool/protocol.hpp>
2020-04-26 15:31:39 +02:00
# include <components/vfs/bsaarchive.hpp>
2012-10-10 22:58:04 +02:00
2021-12-19 22:49:41 +01:00
# include "utils/textinputdialog.hpp"
2020-04-26 15:31:39 +02:00
# include "utils/profilescombobox.hpp"
# include "ui_directorypicker.h"
2015-02-06 12:24:06 +13:00
const char * Launcher : : DataFilesPage : : mDefaultContentListName = " Default " ;
2020-04-26 15:31:39 +02:00
namespace
{
void contentSubdirs ( const QString & path , QStringList & dirs )
{
QStringList fileFilter { " *.esm " , " *.esp " , " *.omwaddon " , " *.bsa " } ;
QStringList dirFilter { " bookart " , " icons " , " meshes " , " music " , " sound " , " textures " } ;
QDir currentDir ( path ) ;
if ( ! currentDir . entryInfoList ( fileFilter , QDir : : Files ) . empty ( )
| | ! currentDir . entryInfoList ( dirFilter , QDir : : Dirs | QDir : : NoDotAndDotDot ) . empty ( ) )
2022-04-27 19:51:54 +00:00
dirs . push_back ( currentDir . canonicalPath ( ) ) ;
2020-04-26 15:31:39 +02:00
for ( const auto & subdir : currentDir . entryInfoList ( QDir : : Dirs | QDir : : NoDotAndDotDot ) )
2022-04-27 19:51:54 +00:00
contentSubdirs ( subdir . canonicalFilePath ( ) , dirs ) ;
2020-04-26 15:31:39 +02:00
}
}
2021-12-19 22:49:41 +01:00
namespace Launcher
{
namespace
{
struct HandleNavMeshToolMessage
{
int mCellsCount ;
int mExpectedMaxProgress ;
int mMaxProgress ;
int mProgress ;
HandleNavMeshToolMessage operator ( ) ( NavMeshTool : : ExpectedCells & & message ) const
{
return HandleNavMeshToolMessage {
static_cast < int > ( message . mCount ) ,
mExpectedMaxProgress ,
static_cast < int > ( message . mCount ) * 100 ,
mProgress
} ;
}
HandleNavMeshToolMessage operator ( ) ( NavMeshTool : : ProcessedCells & & message ) const
{
return HandleNavMeshToolMessage {
mCellsCount ,
mExpectedMaxProgress ,
mMaxProgress ,
std : : max ( mProgress , static_cast < int > ( message . mCount ) )
} ;
}
HandleNavMeshToolMessage operator ( ) ( NavMeshTool : : ExpectedTiles & & message ) const
{
const int expectedMaxProgress = mCellsCount + static_cast < int > ( message . mCount ) ;
return HandleNavMeshToolMessage {
mCellsCount ,
expectedMaxProgress ,
std : : max ( mMaxProgress , expectedMaxProgress ) ,
mProgress
} ;
}
HandleNavMeshToolMessage operator ( ) ( NavMeshTool : : GeneratedTiles & & message ) const
{
int progress = mCellsCount + static_cast < int > ( message . mCount ) ;
if ( mExpectedMaxProgress < mMaxProgress )
progress + = static_cast < int > ( std : : round (
( mMaxProgress - mExpectedMaxProgress )
* ( static_cast < float > ( progress ) / static_cast < float > ( mExpectedMaxProgress ) )
) ) ;
return HandleNavMeshToolMessage {
mCellsCount ,
mExpectedMaxProgress ,
mMaxProgress ,
std : : max ( mProgress , progress )
} ;
}
} ;
2022-04-07 16:35:18 +02:00
int getMaxNavMeshDbFileSizeMiB ( )
{
return static_cast < int > ( Settings : : Manager : : getInt64 ( " max navmeshdb file size " , " Navigator " ) / ( 1024 * 1024 ) ) ;
}
2021-12-19 22:49:41 +01:00
}
}
2021-08-01 23:35:45 +02:00
Launcher : : DataFilesPage : : DataFilesPage ( Files : : ConfigurationManager & cfg , Config : : GameSettings & gameSettings ,
Config : : LauncherSettings & launcherSettings , MainDialog * parent )
2015-04-25 13:37:42 -05:00
: QWidget ( parent )
2021-08-01 23:35:45 +02:00
, mMainDialog ( parent )
2015-04-25 13:37:42 -05:00
, mCfgMgr ( cfg )
2013-01-27 16:39:51 +01:00
, mGameSettings ( gameSettings )
2013-02-11 15:01:00 +01:00
, mLauncherSettings ( launcherSettings )
2021-08-01 23:35:45 +02:00
, mNavMeshToolInvoker ( new Process : : ProcessInvoker ( this ) )
2011-04-08 00:04:09 +02:00
{
2013-10-22 21:52:35 -05:00
ui . setupUi ( this ) ;
setObjectName ( " DataFilesPage " ) ;
2021-10-29 20:09:47 +02:00
mSelector = new ContentSelectorView : : ContentSelector ( ui . contentSelectorWidget , /*showOMWScripts=*/ true ) ;
2018-09-24 14:09:24 +04:00
const QString encoding = mGameSettings . value ( " encoding " , " win1252 " ) ;
mSelector - > setEncoding ( encoding ) ;
2012-10-23 01:47:07 +02:00
2019-05-15 17:53:18 +03:00
mNewProfileDialog = new TextInputDialog ( tr ( " New Content List " ) , tr ( " Content List name: " ) , this ) ;
mCloneProfileDialog = new TextInputDialog ( tr ( " Clone Content List " ) , tr ( " Content List name: " ) , this ) ;
2014-04-17 02:17:18 +02:00
2022-08-23 14:35:54 -03:00
connect ( mNewProfileDialog - > lineEdit ( ) , & LineEdit : : textChanged ,
this , & DataFilesPage : : updateNewProfileOkButton ) ;
connect ( mCloneProfileDialog - > lineEdit ( ) , & LineEdit : : textChanged ,
this , & DataFilesPage : : updateCloneProfileOkButton ) ;
2022-06-12 08:00:11 +00:00
connect ( ui . directoryAddSubdirsButton , & QPushButton : : released , this , [ this ] ( ) { this - > addSubdirectories ( true ) ; } ) ;
connect ( ui . directoryInsertButton , & QPushButton : : released , this , [ this ] ( ) { this - > addSubdirectories ( false ) ; } ) ;
connect ( ui . directoryUpButton , & QPushButton : : released , this , [ this ] ( ) { this - > moveDirectory ( - 1 ) ; } ) ;
connect ( ui . directoryDownButton , & QPushButton : : released , this , [ this ] ( ) { this - > moveDirectory ( 1 ) ; } ) ;
connect ( ui . directoryRemoveButton , & QPushButton : : released , this , [ this ] ( ) { this - > removeDirectory ( ) ; } ) ;
connect ( ui . archiveUpButton , & QPushButton : : released , this , [ this ] ( ) { this - > moveArchive ( - 1 ) ; } ) ;
connect ( ui . archiveDownButton , & QPushButton : : released , this , [ this ] ( ) { this - > moveArchive ( 1 ) ; } ) ;
connect ( ui . directoryListWidget - > model ( ) , & QAbstractItemModel : : rowsMoved , this , [ this ] ( ) { this - > sortDirectories ( ) ; } ) ;
2014-04-17 02:17:18 +02:00
2013-10-22 21:52:35 -05:00
buildView ( ) ;
2014-05-30 02:12:48 +02:00
loadSettings ( ) ;
2018-05-26 20:35:28 -05:00
// Connect signal and slot after the settings have been loaded. We only care about the user changing
// the addons and don't want to get signals of the system doing it during startup.
2022-08-23 14:35:54 -03:00
connect ( mSelector , & ContentSelectorView : : ContentSelector : : signalAddonDataChanged ,
this , & DataFilesPage : : slotAddonDataChanged ) ;
2018-06-03 16:58:18 -05:00
// Call manually to indicate all changes to addon data during startup.
slotAddonDataChanged ( ) ;
2013-02-11 15:01:00 +01:00
}
2014-04-17 00:01:19 +02:00
void Launcher : : DataFilesPage : : buildView ( )
{
2020-04-26 15:31:39 +02:00
QToolButton * refreshButton = mSelector - > refreshButton ( ) ;
2020-03-21 19:35:20 +00:00
2014-04-17 00:01:19 +02:00
//tool buttons
2015-01-10 15:04:45 +13:00
ui . newProfileButton - > setToolTip ( " Create a new Content List " ) ;
2019-05-15 17:53:18 +03:00
ui . cloneProfileButton - > setToolTip ( " Clone the current Content List " ) ;
2015-01-10 15:04:45 +13:00
ui . deleteProfileButton - > setToolTip ( " Delete an existing Content List " ) ;
2014-04-17 00:01:19 +02:00
//combo box
2015-02-06 12:24:06 +13:00
ui . profilesComboBox - > addItem ( mDefaultContentListName ) ;
2015-01-10 15:04:45 +13:00
ui . profilesComboBox - > setPlaceholderText ( QString ( " Select a Content List... " ) ) ;
2015-02-06 12:24:06 +13:00
ui . profilesComboBox - > setCurrentIndex ( ui . profilesComboBox - > findText ( QLatin1String ( mDefaultContentListName ) ) ) ;
2014-04-17 00:01:19 +02:00
// Add the actions to the toolbuttons
ui . newProfileButton - > setDefaultAction ( ui . newProfileAction ) ;
2019-05-15 17:53:18 +03:00
ui . cloneProfileButton - > setDefaultAction ( ui . cloneProfileAction ) ;
2014-04-17 00:01:19 +02:00
ui . deleteProfileButton - > setDefaultAction ( ui . deleteProfileAction ) ;
2020-03-21 19:35:20 +00:00
refreshButton - > setDefaultAction ( ui . refreshDataFilesAction ) ;
2014-04-17 00:01:19 +02:00
//establish connections
2022-08-23 14:35:54 -03:00
connect ( ui . profilesComboBox , qOverload < int > ( & : : ProfilesComboBox : : currentIndexChanged ) ,
this , & DataFilesPage : : slotProfileChanged ) ;
2014-04-17 00:01:19 +02:00
2022-08-23 14:35:54 -03:00
connect ( ui . profilesComboBox , & : : ProfilesComboBox : : profileRenamed ,
this , & DataFilesPage : : slotProfileRenamed ) ;
2014-04-17 00:01:19 +02:00
2022-08-23 14:35:54 -03:00
connect ( ui . profilesComboBox , qOverload < const QString & , const QString & > ( & : : ProfilesComboBox : : signalProfileChanged ) ,
this , & DataFilesPage : : slotProfileChangedByUser ) ;
2020-03-21 19:35:20 +00:00
2022-08-23 14:35:54 -03:00
connect ( ui . refreshDataFilesAction , & QAction : : triggered ,
this , & DataFilesPage : : slotRefreshButtonClicked ) ;
2021-08-01 23:35:45 +02:00
2022-08-23 14:35:54 -03:00
connect ( ui . updateNavMeshButton , & QPushButton : : clicked , this , & DataFilesPage : : startNavMeshTool ) ;
connect ( ui . cancelNavMeshButton , & QPushButton : : clicked , this , & DataFilesPage : : killNavMeshTool ) ;
2021-08-01 23:35:45 +02:00
2022-08-23 14:35:54 -03:00
connect ( mNavMeshToolInvoker - > getProcess ( ) , & QProcess : : readyReadStandardOutput , this , & DataFilesPage : : readNavMeshToolStdout ) ;
connect ( mNavMeshToolInvoker - > getProcess ( ) , & QProcess : : readyReadStandardError , this , & DataFilesPage : : readNavMeshToolStderr ) ;
connect ( mNavMeshToolInvoker - > getProcess ( ) , qOverload < int , QProcess : : ExitStatus > ( & QProcess : : finished ) , this , & DataFilesPage : : navMeshToolFinished ) ;
2014-04-17 00:01:19 +02:00
}
2014-05-30 02:12:48 +02:00
bool Launcher : : DataFilesPage : : loadSettings ( )
2014-04-17 00:01:19 +02:00
{
2022-04-07 16:35:18 +02:00
ui . navMeshMaxSizeSpinBox - > setValue ( getMaxNavMeshDbFileSizeMiB ( ) ) ;
2015-01-10 18:46:47 +13:00
QStringList profiles = mLauncherSettings . getContentLists ( ) ;
QString currentProfile = mLauncherSettings . getCurrentContentListName ( ) ;
2014-04-17 00:01:19 +02:00
2015-11-25 13:17:03 -05:00
qDebug ( ) < < " The current profile is: " < < currentProfile ;
2013-10-01 21:29:45 -05:00
2019-10-06 13:39:27 +02:00
for ( const QString & item : profiles )
2014-04-17 00:01:19 +02:00
addProfile ( item , false ) ;
// Hack: also add the current profile
if ( ! currentProfile . isEmpty ( ) )
addProfile ( currentProfile , true ) ;
2013-02-15 01:20:48 +01:00
2014-12-30 17:25:19 +13:00
return true ;
}
2015-01-26 18:46:18 +13:00
void Launcher : : DataFilesPage : : populateFileViews ( const QString & contentModelName )
{
2020-04-26 15:31:39 +02:00
mSelector - > clearFiles ( ) ;
ui . archiveListWidget - > clear ( ) ;
ui . directoryListWidget - > clear ( ) ;
2015-01-26 18:46:18 +13:00
2020-04-26 15:31:39 +02:00
QStringList directories = mLauncherSettings . getDataDirectoryList ( contentModelName ) ;
if ( directories . isEmpty ( ) )
directories = mGameSettings . getDataDirs ( ) ;
2015-01-26 18:46:18 +13:00
2020-04-26 15:31:39 +02:00
mDataLocal = mGameSettings . getDataLocal ( ) ;
2015-01-26 18:46:18 +13:00
if ( ! mDataLocal . isEmpty ( ) )
2020-04-26 15:31:39 +02:00
directories . insert ( 0 , mDataLocal ) ;
2019-05-18 17:21:27 +03:00
2022-06-19 13:28:33 +02:00
const auto globalDataDir = QString ( mGameSettings . getGlobalDataDir ( ) . string ( ) . c_str ( ) ) ; //TODO(Project579): This will probably break in windows with unicode paths
2020-04-26 15:31:39 +02:00
if ( ! globalDataDir . isEmpty ( ) )
directories . insert ( 0 , globalDataDir ) ;
2020-03-21 19:35:20 +00:00
2022-04-27 19:51:54 +00:00
// normalize user supplied directories: resolve symlink, convert to native separator, make absolute
for ( auto & currentDir : directories )
currentDir = QDir ( QDir : : cleanPath ( currentDir ) ) . canonicalPath ( ) ;
2020-04-26 15:31:39 +02:00
// add directories, archives and content files
directories . removeDuplicates ( ) ;
for ( const auto & currentDir : directories )
{
// add new achives files presents in current directory
addArchivesFromDir ( currentDir ) ;
QString tooltip ;
// add content files presents in current directory
mSelector - > addFiles ( currentDir , mNewDataDirs . contains ( currentDir ) ) ;
// add current directory to list
ui . directoryListWidget - > addItem ( currentDir ) ;
auto row = ui . directoryListWidget - > count ( ) - 1 ;
auto * item = ui . directoryListWidget - > item ( row ) ;
2022-04-28 15:19:54 +02:00
// Display new content with green background
if ( mNewDataDirs . contains ( currentDir ) )
{
tooltip + = " Will be added to the current profile \n " ;
item - > setBackground ( Qt : : green ) ;
item - > setForeground ( Qt : : black ) ;
}
2020-04-26 15:31:39 +02:00
// deactivate data-local and global data directory: they are always included
if ( currentDir = = mDataLocal | | currentDir = = globalDataDir )
{
auto flags = item - > flags ( ) ;
item - > setFlags ( flags & ~ ( Qt : : ItemIsDragEnabled | Qt : : ItemIsDropEnabled | Qt : : ItemIsEnabled ) ) ;
}
// Add a "data file" icon if the directory contains a content file
if ( mSelector - > containsDataFiles ( currentDir ) )
{
item - > setIcon ( QIcon ( " :/images/openmw-plugin.png " ) ) ;
tooltip + = " Contains content file(s) " ;
}
else
{
// Pad to correct vertical alignment
QPixmap pixmap ( QSize ( 200 , 200 ) ) ; // Arbitrary big number, will be scaled down to widget size
2022-04-28 15:19:54 +02:00
pixmap . fill ( ui . directoryListWidget - > palette ( ) . base ( ) . color ( ) ) ;
2020-04-26 15:31:39 +02:00
auto emptyIcon = QIcon ( pixmap ) ;
item - > setIcon ( emptyIcon ) ;
}
item - > setToolTip ( tooltip ) ;
}
2021-11-07 14:15:30 +01:00
mSelector - > sortFiles ( ) ;
2015-01-26 18:46:18 +13:00
2020-04-26 15:31:39 +02:00
QStringList selectedArchives = mLauncherSettings . getArchiveList ( contentModelName ) ;
if ( selectedArchives . isEmpty ( ) )
selectedArchives = mGameSettings . getArchiveList ( ) ;
// sort and tick BSA according to profile
int row = 0 ;
for ( const auto & archive : selectedArchives )
{
const auto match = ui . archiveListWidget - > findItems ( archive , Qt : : MatchExactly ) ;
if ( match . isEmpty ( ) )
continue ;
const auto name = match [ 0 ] - > text ( ) ;
const auto oldrow = ui . archiveListWidget - > row ( match [ 0 ] ) ;
ui . archiveListWidget - > takeItem ( oldrow ) ;
ui . archiveListWidget - > insertItem ( row , name ) ;
ui . archiveListWidget - > item ( row ) - > setCheckState ( Qt : : Checked ) ;
row + + ;
}
PathIterator pathIterator ( directories ) ;
2015-01-26 18:46:18 +13:00
mSelector - > setProfileContent ( filesInProfile ( contentModelName , pathIterator ) ) ;
}
2014-12-30 17:25:19 +13:00
QStringList Launcher : : DataFilesPage : : filesInProfile ( const QString & profileName , PathIterator & pathIterator )
{
2015-01-10 18:46:47 +13:00
QStringList files = mLauncherSettings . getContentListFiles ( profileName ) ;
2013-11-03 21:36:41 -06:00
QStringList filepaths ;
2013-10-01 21:29:45 -05:00
2019-10-06 13:39:27 +02:00
for ( const QString & file : files )
2013-11-03 00:02:46 -05:00
{
2015-01-10 18:46:47 +13:00
QString filepath = pathIterator . findFirstPath ( file ) ;
2013-11-03 00:02:46 -05:00
2013-11-03 21:36:41 -06:00
if ( ! filepath . isEmpty ( ) )
filepaths < < filepath ;
2013-11-03 00:02:46 -05:00
}
2014-12-30 17:25:19 +13:00
return filepaths ;
2013-02-11 15:01:00 +01:00
}
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : saveSettings ( const QString & profile )
2013-02-11 15:01:00 +01:00
{
2022-04-07 16:35:18 +02:00
if ( const int value = ui . navMeshMaxSizeSpinBox - > value ( ) ; value ! = getMaxNavMeshDbFileSizeMiB ( ) )
Settings : : Manager : : setInt64 ( " max navmeshdb file size " , " Navigator " , static_cast < std : : int64_t > ( value ) * 1024 * 1024 ) ;
2013-10-01 21:29:45 -05:00
2022-04-07 16:35:18 +02:00
QString profileName = profile ;
2013-02-24 03:10:27 +01:00
2022-04-07 16:35:18 +02:00
if ( profileName . isEmpty ( ) )
profileName = ui . profilesComboBox - > currentText ( ) ;
2020-04-26 15:31:39 +02:00
//retrieve the data paths
auto dirList = selectedDirectoriesPaths ( ) ;
2022-04-07 16:35:18 +02:00
//retrieve the files selected for the profile
ContentSelectorModel : : ContentFileList items = mSelector - > selectedFiles ( ) ;
2013-02-15 01:20:48 +01:00
2013-10-06 21:13:47 -05:00
//set the value of the current profile (not necessarily the profile being saved!)
2015-01-10 18:46:47 +13:00
mLauncherSettings . setCurrentContentListName ( ui . profilesComboBox - > currentText ( ) ) ;
2013-02-18 23:10:50 +01:00
2015-01-10 18:46:47 +13:00
QStringList fileNames ;
2019-10-06 13:39:27 +02:00
for ( const ContentSelectorModel : : EsmFile * item : items )
{
2015-01-10 18:46:47 +13:00
fileNames . append ( item - > fileName ( ) ) ;
2013-02-11 15:01:00 +01:00
}
2020-04-26 15:31:39 +02:00
mLauncherSettings . setContentList ( profileName , dirList , selectedArchivePaths ( ) , fileNames ) ;
mGameSettings . setContentList ( dirList , selectedArchivePaths ( ) , fileNames ) ;
}
QStringList Launcher : : DataFilesPage : : selectedDirectoriesPaths ( ) const
{
QStringList dirList ;
for ( int i = 0 ; i < ui . directoryListWidget - > count ( ) ; + + i )
{
2022-04-27 19:51:54 +00:00
if ( ui . directoryListWidget - > item ( i ) - > flags ( ) & Qt : : ItemIsEnabled )
2020-04-26 15:31:39 +02:00
dirList . append ( ui . directoryListWidget - > item ( i ) - > text ( ) ) ;
}
return dirList ;
2011-04-08 00:04:09 +02:00
}
2020-04-26 15:31:39 +02:00
QStringList Launcher : : DataFilesPage : : selectedArchivePaths ( bool all ) const
{
QStringList archiveList ;
for ( int i = 0 ; i < ui . archiveListWidget - > count ( ) ; + + i )
{
const auto * item = ui . archiveListWidget - > item ( i ) ;
const auto archive = ui . archiveListWidget - > item ( i ) - > text ( ) ;
if ( all | | item - > checkState ( ) = = Qt : : Checked )
archiveList . append ( item - > text ( ) ) ;
}
return archiveList ;
}
QStringList Launcher : : DataFilesPage : : selectedFilePaths ( ) const
2018-04-07 21:27:36 -05:00
{
//retrieve the files selected for the profile
ContentSelectorModel : : ContentFileList items = mSelector - > selectedFiles ( ) ;
QStringList filePaths ;
2019-10-06 13:39:27 +02:00
for ( const ContentSelectorModel : : EsmFile * item : items )
{
2020-03-21 19:35:20 +00:00
QFile file ( item - > filePath ( ) ) ;
if ( file . exists ( ) )
filePaths . append ( item - > filePath ( ) ) ;
2018-04-07 21:27:36 -05:00
}
return filePaths ;
}
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : removeProfile ( const QString & profile )
2012-10-23 01:47:07 +02:00
{
2015-01-10 18:46:47 +13:00
mLauncherSettings . removeContentList ( profile ) ;
2011-06-07 20:21:01 +02:00
}
2013-10-25 11:17:26 -05:00
QAbstractItemModel * Launcher : : DataFilesPage : : profilesModel ( ) const
{
return ui . profilesComboBox - > model ( ) ;
}
int Launcher : : DataFilesPage : : profilesIndex ( ) const
{
return ui . profilesComboBox - > currentIndex ( ) ;
}
void Launcher : : DataFilesPage : : setProfile ( int index , bool savePrevious )
2011-04-24 23:29:32 +02:00
{
2013-10-22 21:52:35 -05:00
if ( index > = - 1 & & index < ui . profilesComboBox - > count ( ) )
{
2014-12-10 21:47:04 +01:00
QString previous = mPreviousProfile ;
2013-10-22 21:52:35 -05:00
QString current = ui . profilesComboBox - > itemText ( index ) ;
2012-10-23 01:47:07 +02:00
2014-12-10 21:47:04 +01:00
mPreviousProfile = current ;
2013-10-22 21:52:35 -05:00
setProfile ( previous , current , savePrevious ) ;
}
}
2013-02-11 15:01:00 +01:00
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : setProfile ( const QString & previous , const QString & current , bool savePrevious )
2013-10-22 21:52:35 -05:00
{
//abort if no change (poss. duplicate signal)
if ( previous = = current )
return ;
2013-10-06 21:13:47 -05:00
2013-10-22 21:52:35 -05:00
if ( ! previous . isEmpty ( ) & & savePrevious )
saveSettings ( previous ) ;
2012-10-23 01:47:07 +02:00
2013-11-03 14:02:41 -06:00
ui . profilesComboBox - > setCurrentProfile ( ui . profilesComboBox - > findText ( current ) ) ;
2011-04-24 23:03:21 +02:00
2020-04-26 15:31:39 +02:00
mNewDataDirs . clear ( ) ;
mKnownArchives . clear ( ) ;
2015-01-26 18:46:18 +13:00
populateFileViews ( current ) ;
2012-10-23 01:47:07 +02:00
2020-04-26 15:31:39 +02:00
// save list of "old" bsa to be able to display "new" bsa in a different colour
for ( int i = 0 ; i < ui . archiveListWidget - > count ( ) ; + + i )
{
auto * item = ui . archiveListWidget - > item ( i ) ;
mKnownArchives . push_back ( item - > text ( ) ) ;
}
2013-10-22 21:52:35 -05:00
checkForDefaultProfile ( ) ;
2013-10-06 21:13:47 -05:00
}
2012-10-23 01:47:07 +02:00
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : slotProfileDeleted ( const QString & item )
2013-10-06 21:13:47 -05:00
{
removeProfile ( item ) ;
}
2012-10-23 01:47:07 +02:00
2020-03-21 19:35:20 +00:00
void Launcher : : DataFilesPage : : refreshDataFilesView ( )
{
QString currentProfile = ui . profilesComboBox - > currentText ( ) ;
saveSettings ( currentProfile ) ;
populateFileViews ( currentProfile ) ;
}
void Launcher : : DataFilesPage : : slotRefreshButtonClicked ( )
{
refreshDataFilesView ( ) ;
}
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : slotProfileChangedByUser ( const QString & previous , const QString & current )
2013-10-06 21:13:47 -05:00
{
2013-10-22 21:52:35 -05:00
setProfile ( previous , current , true ) ;
emit signalProfileChanged ( ui . profilesComboBox - > findText ( current ) ) ;
2012-10-23 01:47:07 +02:00
}
2013-09-21 23:06:29 -05:00
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : slotProfileRenamed ( const QString & previous , const QString & current )
2013-09-21 23:06:29 -05:00
{
2013-10-06 21:13:47 -05:00
if ( previous . isEmpty ( ) )
return ;
2013-09-21 23:06:29 -05:00
2013-10-06 21:13:47 -05:00
// Save the new profile name
saveSettings ( ) ;
2013-09-21 23:06:29 -05:00
2013-10-06 21:13:47 -05:00
// Remove the old one
removeProfile ( previous ) ;
2013-10-01 21:29:45 -05:00
2013-10-06 21:13:47 -05:00
loadSettings ( ) ;
2013-09-21 23:06:29 -05:00
}
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : slotProfileChanged ( int index )
2013-09-21 23:06:29 -05:00
{
2014-12-11 15:59:27 +01:00
// in case the event was triggered externally
if ( ui . profilesComboBox - > currentIndex ( ) ! = index )
ui . profilesComboBox - > setCurrentIndex ( index ) ;
2013-10-22 21:52:35 -05:00
setProfile ( index , true ) ;
2013-09-21 23:06:29 -05:00
}
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : on_newProfileAction_triggered ( )
2013-10-06 21:13:47 -05:00
{
2019-05-15 17:53:18 +03:00
if ( mNewProfileDialog - > exec ( ) ! = QDialog : : Accepted )
2013-10-22 21:52:35 -05:00
return ;
2019-05-15 17:53:18 +03:00
QString profile = mNewProfileDialog - > lineEdit ( ) - > text ( ) ;
2013-10-22 21:52:35 -05:00
if ( profile . isEmpty ( ) )
2014-04-17 02:17:18 +02:00
return ;
2013-10-22 21:52:35 -05:00
saveSettings ( ) ;
2015-01-10 18:46:47 +13:00
mLauncherSettings . setCurrentContentListName ( profile ) ;
2013-10-22 21:52:35 -05:00
addProfile ( profile , true ) ;
}
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : addProfile ( const QString & profile , bool setAsCurrent )
2013-10-22 21:52:35 -05:00
{
if ( profile . isEmpty ( ) )
return ;
2014-12-10 21:10:14 +01:00
if ( ui . profilesComboBox - > findText ( profile ) = = - 1 )
ui . profilesComboBox - > addItem ( profile ) ;
2013-10-22 21:52:35 -05:00
if ( setAsCurrent )
setProfile ( ui . profilesComboBox - > findText ( profile ) , false ) ;
2013-10-06 21:13:47 -05:00
}
2019-05-15 17:53:18 +03:00
void Launcher : : DataFilesPage : : on_cloneProfileAction_triggered ( )
{
if ( mCloneProfileDialog - > exec ( ) ! = QDialog : : Accepted )
return ;
QString profile = mCloneProfileDialog - > lineEdit ( ) - > text ( ) ;
if ( profile . isEmpty ( ) )
return ;
2020-04-26 15:31:39 +02:00
mLauncherSettings . setContentList ( profile , selectedDirectoriesPaths ( ) , selectedArchivePaths ( ) , selectedFilePaths ( ) ) ;
2019-05-15 17:53:18 +03:00
addProfile ( profile , true ) ;
}
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : on_deleteProfileAction_triggered ( )
2013-10-06 21:13:47 -05:00
{
2013-10-22 21:52:35 -05:00
QString profile = ui . profilesComboBox - > currentText ( ) ;
if ( profile . isEmpty ( ) )
return ;
if ( ! showDeleteMessageBox ( profile ) )
return ;
2014-12-10 21:47:04 +01:00
// this should work since the Default profile can't be deleted and is always index 0
int next = ui . profilesComboBox - > currentIndex ( ) - 1 ;
2015-01-26 18:46:18 +13:00
// changing the profile forces a reload of plugin file views.
2014-12-10 21:47:04 +01:00
ui . profilesComboBox - > setCurrentIndex ( next ) ;
2013-10-22 21:52:35 -05:00
2013-11-03 14:02:41 -06:00
removeProfile ( profile ) ;
2014-12-10 21:47:04 +01:00
ui . profilesComboBox - > removeItem ( ui . profilesComboBox - > findText ( profile ) ) ;
2013-11-03 14:02:41 -06:00
2013-10-22 21:52:35 -05:00
checkForDefaultProfile ( ) ;
}
2019-05-15 17:53:18 +03:00
void Launcher : : DataFilesPage : : updateNewProfileOkButton ( const QString & text )
2014-04-17 02:17:18 +02:00
{
// We do this here because we need the profiles combobox text
2019-05-15 17:53:18 +03:00
mNewProfileDialog - > setOkButtonEnabled ( ! text . isEmpty ( ) & & ui . profilesComboBox - > findText ( text ) = = - 1 ) ;
}
2014-04-17 02:17:18 +02:00
2019-05-15 17:53:18 +03:00
void Launcher : : DataFilesPage : : updateCloneProfileOkButton ( const QString & text )
{
// We do this here because we need the profiles combobox text
mCloneProfileDialog - > setOkButtonEnabled ( ! text . isEmpty ( ) & & ui . profilesComboBox - > findText ( text ) = = - 1 ) ;
2014-04-17 02:17:18 +02:00
}
2020-04-26 15:31:39 +02:00
QString Launcher : : DataFilesPage : : selectDirectory ( )
{
QFileDialog fileDialog ( this ) ;
fileDialog . setFileMode ( QFileDialog : : Directory ) ;
fileDialog . setOptions ( QFileDialog : : Option : : ShowDirsOnly | QFileDialog : : Option : : ReadOnly ) ;
if ( fileDialog . exec ( ) = = QDialog : : Rejected )
return { } ;
2022-04-27 19:51:54 +00:00
return QDir ( fileDialog . selectedFiles ( ) [ 0 ] ) . canonicalPath ( ) ;
2020-04-26 15:31:39 +02:00
}
void Launcher : : DataFilesPage : : addSubdirectories ( bool append )
{
int selectedRow = append ? ui . directoryListWidget - > count ( ) : ui . directoryListWidget - > currentRow ( ) ;
if ( selectedRow = = - 1 )
return ;
const auto rootDir = selectDirectory ( ) ;
if ( rootDir . isEmpty ( ) )
return ;
QStringList subdirs ;
contentSubdirs ( rootDir , subdirs ) ;
if ( subdirs . empty ( ) )
{
// we didn't find anything that looks like a content directory, add directory selected by user
if ( ui . directoryListWidget - > findItems ( rootDir , Qt : : MatchFixedString ) . isEmpty ( ) )
{
ui . directoryListWidget - > addItem ( rootDir ) ;
mNewDataDirs . push_back ( rootDir ) ;
refreshDataFilesView ( ) ;
}
return ;
}
QDialog dialog ;
Ui : : SelectSubdirs select ;
select . setupUi ( & dialog ) ;
for ( const auto & dir : subdirs )
{
if ( ! ui . directoryListWidget - > findItems ( dir , Qt : : MatchFixedString ) . isEmpty ( ) )
continue ;
const auto lastRow = select . dirListWidget - > count ( ) ;
select . dirListWidget - > addItem ( dir ) ;
select . dirListWidget - > item ( lastRow ) - > setCheckState ( Qt : : Unchecked ) ;
}
dialog . show ( ) ;
if ( dialog . exec ( ) = = QDialog : : Rejected )
return ;
for ( int i = 0 ; i < select . dirListWidget - > count ( ) ; + + i )
{
const auto * dir = select . dirListWidget - > item ( i ) ;
if ( dir - > checkState ( ) = = Qt : : Checked )
{
ui . directoryListWidget - > insertItem ( selectedRow + + , dir - > text ( ) ) ;
mNewDataDirs . push_back ( dir - > text ( ) ) ;
}
}
refreshDataFilesView ( ) ;
}
void Launcher : : DataFilesPage : : sortDirectories ( )
{
// Ensure disabled entries (aka default directories) are always at the top.
for ( auto i = 1 ; i < ui . directoryListWidget - > count ( ) ; + + i )
{
if ( ! ( ui . directoryListWidget - > item ( i ) - > flags ( ) & Qt : : ItemIsEnabled ) & &
( ui . directoryListWidget - > item ( i - 1 ) - > flags ( ) & Qt : : ItemIsEnabled ) )
{
const auto item = ui . directoryListWidget - > takeItem ( i ) ;
ui . directoryListWidget - > insertItem ( i - 1 , item ) ;
ui . directoryListWidget - > setCurrentRow ( i ) ;
}
}
}
void Launcher : : DataFilesPage : : moveDirectory ( int step )
{
int selectedRow = ui . directoryListWidget - > currentRow ( ) ;
int newRow = selectedRow + step ;
if ( selectedRow = = - 1 | | newRow < 0 | | newRow > ui . directoryListWidget - > count ( ) - 1 )
return ;
if ( ! ( ui . directoryListWidget - > item ( newRow ) - > flags ( ) & Qt : : ItemIsEnabled ) )
return ;
const auto item = ui . directoryListWidget - > takeItem ( selectedRow ) ;
ui . directoryListWidget - > insertItem ( newRow , item ) ;
ui . directoryListWidget - > setCurrentRow ( newRow ) ;
}
void Launcher : : DataFilesPage : : removeDirectory ( )
{
for ( const auto & path : ui . directoryListWidget - > selectedItems ( ) )
ui . directoryListWidget - > takeItem ( ui . directoryListWidget - > row ( path ) ) ;
refreshDataFilesView ( ) ;
}
void Launcher : : DataFilesPage : : moveArchive ( int step )
{
int selectedRow = ui . archiveListWidget - > currentRow ( ) ;
int newRow = selectedRow + step ;
if ( selectedRow = = - 1 | | newRow < 0 | | newRow > ui . archiveListWidget - > count ( ) - 1 )
return ;
const auto * item = ui . archiveListWidget - > takeItem ( selectedRow ) ;
addArchive ( item - > text ( ) , item - > checkState ( ) , newRow ) ;
ui . archiveListWidget - > setCurrentRow ( newRow ) ;
}
void Launcher : : DataFilesPage : : addArchive ( const QString & name , Qt : : CheckState selected , int row )
{
if ( row = = - 1 )
row = ui . archiveListWidget - > count ( ) ;
ui . archiveListWidget - > insertItem ( row , name ) ;
ui . archiveListWidget - > item ( row ) - > setCheckState ( selected ) ;
if ( mKnownArchives . filter ( name ) . isEmpty ( ) ) // XXX why contains doesn't work here ???
2022-04-28 15:19:54 +02:00
{
2020-04-26 15:31:39 +02:00
ui . archiveListWidget - > item ( row ) - > setBackground ( Qt : : green ) ;
2022-04-28 15:19:54 +02:00
ui . archiveListWidget - > item ( row ) - > setForeground ( Qt : : black ) ;
}
2020-04-26 15:31:39 +02:00
}
void Launcher : : DataFilesPage : : addArchivesFromDir ( const QString & path )
{
QDir dir ( path , " *.bsa " ) ;
for ( const auto & fileinfo : dir . entryInfoList ( ) )
{
const auto absPath = fileinfo . absoluteFilePath ( ) ;
if ( Bsa : : CompressedBSAFile : : detectVersion ( absPath . toStdString ( ) ) = = Bsa : : BSAVER_UNKNOWN )
continue ;
const auto fileName = fileinfo . fileName ( ) ;
const auto currentList = selectedArchivePaths ( true ) ;
if ( ! currentList . contains ( fileName , Qt : : CaseInsensitive ) )
addArchive ( fileName , Qt : : Unchecked ) ;
}
}
2013-10-25 11:17:26 -05:00
void Launcher : : DataFilesPage : : checkForDefaultProfile ( )
2013-10-22 21:52:35 -05:00
{
//don't allow deleting "Default" profile
2015-02-06 12:24:06 +13:00
bool success = ( ui . profilesComboBox - > currentText ( ) ! = mDefaultContentListName ) ;
2013-10-22 21:52:35 -05:00
ui . deleteProfileAction - > setEnabled ( success ) ;
ui . profilesComboBox - > setEditEnabled ( success ) ;
}
2013-10-25 11:17:26 -05:00
bool Launcher : : DataFilesPage : : showDeleteMessageBox ( const QString & text )
2013-10-22 21:52:35 -05:00
{
QMessageBox msgBox ( this ) ;
2015-01-10 15:04:45 +13:00
msgBox . setWindowTitle ( tr ( " Delete Content List " ) ) ;
2013-10-22 21:52:35 -05:00
msgBox . setIcon ( QMessageBox : : Warning ) ;
msgBox . setStandardButtons ( QMessageBox : : Cancel ) ;
2015-01-11 08:33:33 +13:00
msgBox . setText ( tr ( " Are you sure you want to delete <b>%1</b>? " ) . arg ( text ) ) ;
2013-10-22 21:52:35 -05:00
QAbstractButton * deleteButton =
msgBox . addButton ( tr ( " Delete " ) , QMessageBox : : ActionRole ) ;
msgBox . exec ( ) ;
return ( msgBox . clickedButton ( ) = = deleteButton ) ;
2013-10-06 21:13:47 -05:00
}
2018-05-22 20:50:31 -05:00
2018-05-26 20:35:28 -05:00
void Launcher : : DataFilesPage : : slotAddonDataChanged ( )
2018-05-22 20:50:31 -05:00
{
2018-05-26 20:35:28 -05:00
QStringList selectedFiles = selectedFilePaths ( ) ;
if ( previousSelectedFiles ! = selectedFiles ) {
previousSelectedFiles = selectedFiles ;
2018-05-27 17:15:36 -05:00
// Loading cells for core Morrowind + Expansions takes about 0.2 seconds, which is enough to cause a
// barely perceptible UI lag. Splitting into its own thread to alleviate that.
std : : thread loadCellsThread ( & DataFilesPage : : reloadCells , this , selectedFiles ) ;
2018-06-02 17:29:35 -05:00
loadCellsThread . detach ( ) ;
2018-05-26 20:35:28 -05:00
}
2018-05-27 17:15:36 -05:00
}
2018-06-03 16:32:12 -05:00
// Mutex lock to run reloadCells synchronously.
2022-07-06 13:01:03 +02:00
static std : : mutex reloadCellsMutex ;
2018-06-03 16:32:12 -05:00
2018-05-27 17:15:36 -05:00
void Launcher : : DataFilesPage : : reloadCells ( QStringList selectedFiles )
{
2018-06-03 16:32:12 -05:00
// Use a mutex lock so that we can prevent two threads from executing the rest of this code at the same time
// Based on https://stackoverflow.com/a/5429695/531762
2022-07-06 13:01:03 +02:00
std : : unique_lock < std : : mutex > lock ( reloadCellsMutex ) ;
2018-06-03 16:32:12 -05:00
// The following code will run only if there is not another thread currently running it
2018-05-27 17:15:36 -05:00
CellNameLoader cellNameLoader ;
2019-12-28 21:38:49 +04:00
# if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
QSet < QString > set = cellNameLoader . getCellNames ( selectedFiles ) ;
QStringList cellNamesList ( set . begin ( ) , set . end ( ) ) ;
# else
2018-05-27 17:15:36 -05:00
QStringList cellNamesList = QStringList : : fromSet ( cellNameLoader . getCellNames ( selectedFiles ) ) ;
2019-12-28 21:38:49 +04:00
# endif
2018-05-27 17:15:36 -05:00
std : : sort ( cellNamesList . begin ( ) , cellNamesList . end ( ) ) ;
2018-06-08 19:18:23 -05:00
emit signalLoadedCellsChanged ( cellNamesList ) ;
2018-09-24 14:09:24 +04:00
}
2021-08-01 23:35:45 +02:00
void Launcher : : DataFilesPage : : startNavMeshTool ( )
{
mMainDialog - > writeSettings ( ) ;
ui . navMeshLogPlainTextEdit - > clear ( ) ;
ui . navMeshProgressBar - > setValue ( 0 ) ;
ui . navMeshProgressBar - > setMaximum ( 1 ) ;
2021-12-19 22:49:41 +01:00
mNavMeshToolProgress = NavMeshToolProgress { } ;
2022-04-07 15:54:39 +02:00
QStringList arguments ( { " --write-binary-log " } ) ;
if ( ui . navMeshRemoveUnusedTilesCheckBox - > checkState ( ) = = Qt : : Checked )
arguments . append ( " --remove-unused-tiles " ) ;
if ( ! mNavMeshToolInvoker - > startProcess ( QLatin1String ( " openmw-navmeshtool " ) , arguments ) )
2021-08-01 23:35:45 +02:00
return ;
ui . cancelNavMeshButton - > setEnabled ( true ) ;
ui . navMeshProgressBar - > setEnabled ( true ) ;
}
void Launcher : : DataFilesPage : : killNavMeshTool ( )
{
mNavMeshToolInvoker - > killProcess ( ) ;
}
2021-12-19 22:49:41 +01:00
void Launcher : : DataFilesPage : : readNavMeshToolStderr ( )
{
updateNavMeshProgress ( 4096 ) ;
}
void Launcher : : DataFilesPage : : updateNavMeshProgress ( int minDataSize )
2021-08-01 23:35:45 +02:00
{
QProcess & process = * mNavMeshToolInvoker - > getProcess ( ) ;
2021-12-19 22:49:41 +01:00
mNavMeshToolProgress . mMessagesData . append ( process . readAllStandardError ( ) ) ;
if ( mNavMeshToolProgress . mMessagesData . size ( ) < minDataSize )
return ;
const std : : byte * const begin = reinterpret_cast < const std : : byte * > ( mNavMeshToolProgress . mMessagesData . constData ( ) ) ;
const std : : byte * const end = begin + mNavMeshToolProgress . mMessagesData . size ( ) ;
const std : : byte * position = begin ;
HandleNavMeshToolMessage handle {
mNavMeshToolProgress . mCellsCount ,
mNavMeshToolProgress . mExpectedMaxProgress ,
ui . navMeshProgressBar - > maximum ( ) ,
ui . navMeshProgressBar - > value ( ) ,
} ;
while ( true )
2021-08-01 23:35:45 +02:00
{
2021-12-19 22:49:41 +01:00
NavMeshTool : : Message message ;
const std : : byte * const nextPosition = NavMeshTool : : deserialize ( position , end , message ) ;
if ( nextPosition = = position )
break ;
position = nextPosition ;
handle = std : : visit ( handle , NavMeshTool : : decode ( message ) ) ;
2021-08-01 23:35:45 +02:00
}
2021-12-19 22:49:41 +01:00
if ( position ! = begin )
mNavMeshToolProgress . mMessagesData = mNavMeshToolProgress . mMessagesData . mid ( position - begin ) ;
mNavMeshToolProgress . mCellsCount = handle . mCellsCount ;
mNavMeshToolProgress . mExpectedMaxProgress = handle . mExpectedMaxProgress ;
ui . navMeshProgressBar - > setMaximum ( handle . mMaxProgress ) ;
ui . navMeshProgressBar - > setValue ( handle . mProgress ) ;
}
void Launcher : : DataFilesPage : : readNavMeshToolStdout ( )
{
QProcess & process = * mNavMeshToolInvoker - > getProcess ( ) ;
QByteArray & logData = mNavMeshToolProgress . mLogData ;
logData . append ( process . readAllStandardOutput ( ) ) ;
const int lineEnd = logData . lastIndexOf ( ' \n ' ) ;
if ( lineEnd = = - 1 )
2021-08-01 23:35:45 +02:00
return ;
2021-12-19 22:49:41 +01:00
const int size = logData . size ( ) > = lineEnd & & logData [ lineEnd - 1 ] = = ' \r ' ? lineEnd - 1 : lineEnd ;
ui . navMeshLogPlainTextEdit - > appendPlainText ( QString : : fromUtf8 ( logData . data ( ) , size ) ) ;
logData = logData . mid ( lineEnd + 1 ) ;
2021-08-01 23:35:45 +02:00
}
void Launcher : : DataFilesPage : : navMeshToolFinished ( int exitCode , QProcess : : ExitStatus exitStatus )
{
2021-12-19 22:49:41 +01:00
updateNavMeshProgress ( 0 ) ;
ui . navMeshLogPlainTextEdit - > appendPlainText ( QString : : fromUtf8 ( mNavMeshToolInvoker - > getProcess ( ) - > readAllStandardOutput ( ) ) ) ;
2021-08-01 23:35:45 +02:00
if ( exitCode = = 0 & & exitStatus = = QProcess : : ExitStatus : : NormalExit )
ui . navMeshProgressBar - > setValue ( ui . navMeshProgressBar - > maximum ( ) ) ;
ui . cancelNavMeshButton - > setEnabled ( false ) ;
ui . navMeshProgressBar - > setEnabled ( false ) ;
}