mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-20 15:40:32 +00:00
Merge branch 'launcher-datadirs' into 'master'
Make launcher handle data dirs #2858 and BSA See merge request OpenMW/openmw!192
This commit is contained in:
commit
66a96bfa5e
@ -119,6 +119,7 @@
|
||||
Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record
|
||||
Feature #2766: Warn user if their version of Morrowind is not the latest.
|
||||
Feature #2780: A way to see current OpenMW version in the console
|
||||
Feature #2858: Add a tab to the launcher for handling datafolders
|
||||
Feature #3245: Grid and angle snapping for the OpenMW-CS
|
||||
Feature #3616: Allow Zoom levels on the World Map
|
||||
Feature #4297: Implement APPLIED_ONCE flag for magic effects
|
||||
|
@ -44,6 +44,7 @@ set(LAUNCHER_UI
|
||||
${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
|
||||
${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui
|
||||
${CMAKE_SOURCE_DIR}/files/ui/advancedpage.ui
|
||||
${CMAKE_SOURCE_DIR}/files/ui/directorypicker.ui
|
||||
)
|
||||
|
||||
source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER})
|
||||
|
@ -7,6 +7,9 @@
|
||||
#include <QMessageBox>
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QFileDialog>
|
||||
#include <QTreeView>
|
||||
#include <qnamespace.h>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
@ -20,12 +23,34 @@
|
||||
#include <components/config/gamesettings.hpp>
|
||||
#include <components/config/launchersettings.hpp>
|
||||
|
||||
#include <components/bsa/compressedbsafile.hpp>
|
||||
#include <components/navmeshtool/protocol.hpp>
|
||||
#include <components/vfs/bsaarchive.hpp>
|
||||
|
||||
#include "utils/textinputdialog.hpp"
|
||||
#include "utils/profilescombobox.hpp"
|
||||
|
||||
#include "ui_directorypicker.h"
|
||||
|
||||
const char *Launcher::DataFilesPage::mDefaultContentListName = "Default";
|
||||
|
||||
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())
|
||||
dirs.push_back(currentDir.absolutePath());
|
||||
|
||||
for (const auto& subdir : currentDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
contentSubdirs(subdir.absoluteFilePath(), dirs);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Launcher
|
||||
{
|
||||
namespace
|
||||
@ -114,6 +139,14 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
|
||||
this, SLOT(updateNewProfileOkButton(QString)));
|
||||
connect(mCloneProfileDialog->lineEdit(), SIGNAL(textChanged(QString)),
|
||||
this, SLOT(updateCloneProfileOkButton(QString)));
|
||||
connect(ui.directoryAddSubdirsButton, &QPushButton::released, this, [=]() { this->addSubdirectories(true); });
|
||||
connect(ui.directoryInsertButton, &QPushButton::released, this, [=]() { this->addSubdirectories(false); });
|
||||
connect(ui.directoryUpButton, &QPushButton::released, this, [=]() { this->moveDirectory(-1); });
|
||||
connect(ui.directoryDownButton, &QPushButton::released, this, [=]() { this->moveDirectory(1); });
|
||||
connect(ui.directoryRemoveButton, &QPushButton::released, this, [=]() { this->removeDirectory(); });
|
||||
connect(ui.archiveUpButton, &QPushButton::released, this, [=]() { this->moveArchive(-1); });
|
||||
connect(ui.archiveDownButton, &QPushButton::released, this, [=]() { this->moveArchive(1); });
|
||||
connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [=]() { this->sortDirectories(); });
|
||||
|
||||
buildView();
|
||||
loadSettings();
|
||||
@ -128,13 +161,12 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config:
|
||||
|
||||
void Launcher::DataFilesPage::buildView()
|
||||
{
|
||||
QToolButton * refreshButton = mSelector->refreshButton();
|
||||
QToolButton * refreshButton = mSelector->refreshButton();
|
||||
|
||||
//tool buttons
|
||||
ui.newProfileButton->setToolTip ("Create a new Content List");
|
||||
ui.cloneProfileButton->setToolTip ("Clone the current Content List");
|
||||
ui.deleteProfileButton->setToolTip ("Delete an existing Content List");
|
||||
refreshButton->setToolTip("Refresh Data Files");
|
||||
|
||||
//combo box
|
||||
ui.profilesComboBox->addItem(mDefaultContentListName);
|
||||
@ -188,20 +220,94 @@ bool Launcher::DataFilesPage::loadSettings()
|
||||
|
||||
void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||
{
|
||||
QStringList paths = mGameSettings.getDataDirs();
|
||||
mSelector->clearFiles();
|
||||
ui.archiveListWidget->clear();
|
||||
ui.directoryListWidget->clear();
|
||||
|
||||
QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName);
|
||||
if (directories.isEmpty())
|
||||
directories = mGameSettings.getDataDirs();
|
||||
|
||||
mDataLocal = mGameSettings.getDataLocal();
|
||||
|
||||
if (!mDataLocal.isEmpty())
|
||||
paths.insert(0, mDataLocal);
|
||||
directories.insert(0, mDataLocal);
|
||||
|
||||
mSelector->clearFiles();
|
||||
const auto globalDataDir = QString(mGameSettings.getGlobalDataDir().c_str());
|
||||
if (!globalDataDir.isEmpty())
|
||||
directories.insert(0, globalDataDir);
|
||||
|
||||
for (const QString &path : paths)
|
||||
mSelector->addFiles(path);
|
||||
// add directories, archives and content files
|
||||
directories.removeDuplicates();
|
||||
for (const auto& currentDir : directories)
|
||||
{
|
||||
// add new achives files presents in current directory
|
||||
addArchivesFromDir(currentDir);
|
||||
|
||||
// Display new content with green background
|
||||
QColor background;
|
||||
QString tooltip;
|
||||
if (mNewDataDirs.contains(currentDir))
|
||||
{
|
||||
tooltip += "Will be added to the current profile\n";
|
||||
background = Qt::green;
|
||||
}
|
||||
else
|
||||
background = Qt::white;
|
||||
|
||||
// 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);
|
||||
item->setBackground(background);
|
||||
|
||||
// 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
|
||||
pixmap.fill(background);
|
||||
auto emptyIcon = QIcon(pixmap);
|
||||
item->setIcon(emptyIcon);
|
||||
}
|
||||
item->setToolTip(tooltip);
|
||||
}
|
||||
mSelector->sortFiles();
|
||||
|
||||
PathIterator pathIterator(paths);
|
||||
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);
|
||||
|
||||
mSelector->setProfileContent(filesInProfile(contentModelName, pathIterator));
|
||||
}
|
||||
@ -232,6 +338,9 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
|
||||
if (profileName.isEmpty())
|
||||
profileName = ui.profilesComboBox->currentText();
|
||||
|
||||
//retrieve the data paths
|
||||
auto dirList = selectedDirectoriesPaths();
|
||||
|
||||
//retrieve the files selected for the profile
|
||||
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
|
||||
|
||||
@ -243,11 +352,36 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile)
|
||||
{
|
||||
fileNames.append(item->fileName());
|
||||
}
|
||||
mLauncherSettings.setContentList(profileName, fileNames);
|
||||
mGameSettings.setContentList(fileNames);
|
||||
mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames);
|
||||
mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames);
|
||||
}
|
||||
|
||||
QStringList Launcher::DataFilesPage::selectedFilePaths()
|
||||
QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const
|
||||
{
|
||||
QStringList dirList;
|
||||
for (int i = 0; i < ui.directoryListWidget->count(); ++i)
|
||||
{
|
||||
if (ui.directoryListWidget->item(i)->background() != Qt::gray)
|
||||
dirList.append(ui.directoryListWidget->item(i)->text());
|
||||
}
|
||||
return dirList;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
//retrieve the files selected for the profile
|
||||
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
|
||||
@ -255,15 +389,8 @@ QStringList Launcher::DataFilesPage::selectedFilePaths()
|
||||
for (const ContentSelectorModel::EsmFile *item : items)
|
||||
{
|
||||
QFile file(item->filePath());
|
||||
|
||||
if(file.exists())
|
||||
{
|
||||
filePaths.append(item->filePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
slotRefreshButtonClicked();
|
||||
}
|
||||
}
|
||||
return filePaths;
|
||||
}
|
||||
@ -307,8 +434,18 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString
|
||||
|
||||
ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current));
|
||||
|
||||
mNewDataDirs.clear();
|
||||
mKnownArchives.clear();
|
||||
populateFileViews(current);
|
||||
|
||||
// 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());
|
||||
item->setBackground(Qt::white);
|
||||
}
|
||||
|
||||
checkForDefaultProfile();
|
||||
}
|
||||
|
||||
@ -397,7 +534,7 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered()
|
||||
if (profile.isEmpty())
|
||||
return;
|
||||
|
||||
mLauncherSettings.setContentList(profile, selectedFilePaths());
|
||||
mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths());
|
||||
addProfile(profile, true);
|
||||
}
|
||||
|
||||
@ -435,6 +572,155 @@ void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString &text)
|
||||
mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1);
|
||||
}
|
||||
|
||||
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 {};
|
||||
|
||||
return fileDialog.selectedFiles()[0];
|
||||
|
||||
}
|
||||
|
||||
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 ???
|
||||
ui.archiveListWidget->item(row)->setBackground(Qt::green);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Launcher::DataFilesPage::checkForDefaultProfile()
|
||||
{
|
||||
//don't allow deleting "Default" profile
|
||||
|
@ -43,12 +43,6 @@ namespace Launcher
|
||||
void saveSettings(const QString &profile = "");
|
||||
bool loadSettings();
|
||||
|
||||
/**
|
||||
* Returns the file paths of all selected content files
|
||||
* @return the file paths of all selected content files
|
||||
*/
|
||||
QStringList selectedFilePaths();
|
||||
|
||||
signals:
|
||||
void signalProfileChanged (int index);
|
||||
void signalLoadedCellsChanged(QStringList selectedFiles);
|
||||
@ -66,6 +60,11 @@ namespace Launcher
|
||||
|
||||
void updateNewProfileOkButton(const QString &text);
|
||||
void updateCloneProfileOkButton(const QString &text);
|
||||
void addSubdirectories(bool append);
|
||||
void sortDirectories();
|
||||
void removeDirectory();
|
||||
void moveArchive(int step);
|
||||
void moveDirectory(int step);
|
||||
|
||||
void on_newProfileAction_triggered();
|
||||
void on_cloneProfileAction_triggered();
|
||||
@ -103,10 +102,14 @@ namespace Launcher
|
||||
QString mPreviousProfile;
|
||||
QStringList previousSelectedFiles;
|
||||
QString mDataLocal;
|
||||
QStringList mKnownArchives;
|
||||
QStringList mNewDataDirs;
|
||||
|
||||
Process::ProcessInvoker* mNavMeshToolInvoker;
|
||||
NavMeshToolProgress mNavMeshToolProgress;
|
||||
|
||||
void addArchive(const QString& name, Qt::CheckState selected, int row = -1);
|
||||
void addArchivesFromDir(const QString& dir);
|
||||
void buildView();
|
||||
void setProfile (int index, bool savePrevious);
|
||||
void setProfile (const QString &previous, const QString ¤t, bool savePrevious);
|
||||
@ -118,6 +121,15 @@ namespace Launcher
|
||||
void reloadCells(QStringList selectedFiles);
|
||||
void refreshDataFilesView ();
|
||||
void updateNavMeshProgress(int minDataSize);
|
||||
QString selectDirectory();
|
||||
|
||||
/**
|
||||
* Returns the file paths of all selected content files
|
||||
* @return the file paths of all selected content files
|
||||
*/
|
||||
QStringList selectedFilePaths() const;
|
||||
QStringList selectedArchivePaths(bool all=false) const;
|
||||
QStringList selectedDirectoriesPaths() const;
|
||||
|
||||
class PathIterator
|
||||
{
|
||||
|
@ -7,7 +7,9 @@
|
||||
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
|
||||
const char Config::GameSettings::sArchiveKey[] = "fallback-archive";
|
||||
const char Config::GameSettings::sContentKey[] = "content";
|
||||
const char Config::GameSettings::sDirectoryKey[] = "data";
|
||||
|
||||
Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg)
|
||||
: mCfgMgr(cfg)
|
||||
@ -63,6 +65,14 @@ void Config::GameSettings::validatePaths()
|
||||
}
|
||||
}
|
||||
|
||||
std::string Config::GameSettings::getGlobalDataDir() const
|
||||
{
|
||||
// global data dir may not exists if OpenMW is not installed (ie if run from build directory)
|
||||
if (boost::filesystem::exists(mCfgMgr.getGlobalDataPath()))
|
||||
return boost::filesystem::canonical(mCfgMgr.getGlobalDataPath()).string();
|
||||
return {};
|
||||
}
|
||||
|
||||
QStringList Config::GameSettings::values(const QString &key, const QStringList &defaultValues) const
|
||||
{
|
||||
if (!mSettings.values(key).isEmpty())
|
||||
@ -475,13 +485,29 @@ bool Config::GameSettings::hasMaster()
|
||||
return result;
|
||||
}
|
||||
|
||||
void Config::GameSettings::setContentList(const QStringList& fileNames)
|
||||
void Config::GameSettings::setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames)
|
||||
{
|
||||
remove(sContentKey);
|
||||
for (const QString& fileName : fileNames)
|
||||
auto const reset = [this](const char* key, const QStringList& list)
|
||||
{
|
||||
setMultiValue(sContentKey, fileName);
|
||||
}
|
||||
remove(key);
|
||||
for (auto const& item : list)
|
||||
setMultiValue(key, item);
|
||||
};
|
||||
|
||||
reset(sDirectoryKey, dirNames);
|
||||
reset(sArchiveKey, archiveNames);
|
||||
reset(sContentKey, fileNames);
|
||||
}
|
||||
|
||||
QStringList Config::GameSettings::getDataDirs() const
|
||||
{
|
||||
return Config::LauncherSettings::reverse(mDataDirs);
|
||||
}
|
||||
|
||||
QStringList Config::GameSettings::getArchiveList() const
|
||||
{
|
||||
// QMap returns multiple rows in LIFO order, so need to reverse
|
||||
return Config::LauncherSettings::reverse(values(sArchiveKey));
|
||||
}
|
||||
|
||||
QStringList Config::GameSettings::getContentList() const
|
||||
|
@ -53,7 +53,8 @@ namespace Config
|
||||
mUserSettings.remove(key);
|
||||
}
|
||||
|
||||
inline QStringList getDataDirs() const { return mDataDirs; }
|
||||
QStringList getDataDirs() const;
|
||||
std::string getGlobalDataDir() const;
|
||||
|
||||
inline void removeDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.removeAll(dir); }
|
||||
inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); }
|
||||
@ -70,7 +71,8 @@ namespace Config
|
||||
bool writeFile(QTextStream &stream);
|
||||
bool writeFileWithComments(QFile &file);
|
||||
|
||||
void setContentList(const QStringList& fileNames);
|
||||
QStringList getArchiveList() const;
|
||||
void setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames);
|
||||
QStringList getContentList() const;
|
||||
|
||||
void clear();
|
||||
@ -85,7 +87,9 @@ namespace Config
|
||||
QStringList mDataDirs;
|
||||
QString mDataLocal;
|
||||
|
||||
static const char sArchiveKey[];
|
||||
static const char sContentKey[];
|
||||
static const char sDirectoryKey[];
|
||||
|
||||
static bool isOrderedLine(const QString& line) ;
|
||||
};
|
||||
|
@ -7,9 +7,15 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
|
||||
#include <components/files/configurationmanager.hpp>
|
||||
|
||||
const char Config::LauncherSettings::sCurrentContentListKey[] = "Profiles/currentprofile";
|
||||
const char Config::LauncherSettings::sLauncherConfigFileName[] = "launcher.cfg";
|
||||
const char Config::LauncherSettings::sContentListsSectionPrefix[] = "Profiles/";
|
||||
const char Config::LauncherSettings::sDirectoryListSuffix[] = "/data";
|
||||
const char Config::LauncherSettings::sArchiveListSuffix[] = "/fallback-archive";
|
||||
const char Config::LauncherSettings::sContentListSuffix[] = "/content";
|
||||
|
||||
QStringList Config::LauncherSettings::subKeys(const QString &key)
|
||||
@ -86,6 +92,16 @@ QStringList Config::LauncherSettings::getContentLists()
|
||||
return subKeys(QString(sContentListsSectionPrefix));
|
||||
}
|
||||
|
||||
QString Config::LauncherSettings::makeDirectoryListKey(const QString& contentListName)
|
||||
{
|
||||
return QString(sContentListsSectionPrefix) + contentListName + QString(sDirectoryListSuffix);
|
||||
}
|
||||
|
||||
QString Config::LauncherSettings::makeArchiveListKey(const QString& contentListName)
|
||||
{
|
||||
return QString(sContentListsSectionPrefix) + contentListName + QString(sArchiveListSuffix);
|
||||
}
|
||||
|
||||
QString Config::LauncherSettings::makeContentListKey(const QString& contentListName)
|
||||
{
|
||||
return QString(sContentListsSectionPrefix) + contentListName + QString(sContentListSuffix);
|
||||
@ -94,18 +110,28 @@ QString Config::LauncherSettings::makeContentListKey(const QString& contentListN
|
||||
void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
||||
{
|
||||
// obtain content list from game settings (if present)
|
||||
QStringList dirs(gameSettings.getDataDirs());
|
||||
const QStringList archives(gameSettings.getArchiveList());
|
||||
const QStringList files(gameSettings.getContentList());
|
||||
|
||||
// if openmw.cfg has no content, exit so we don't create an empty content list.
|
||||
if (files.isEmpty())
|
||||
if (dirs.isEmpty() || files.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// global and local data directories are not part of any profile
|
||||
const auto globalDataDir = QString(gameSettings.getGlobalDataDir().c_str());
|
||||
const auto dataLocal = gameSettings.getDataLocal();
|
||||
dirs.removeAll(globalDataDir);
|
||||
dirs.removeAll(dataLocal);
|
||||
|
||||
// if any existing profile in launcher matches the content list, make that profile the default
|
||||
for (const QString &listName : getContentLists())
|
||||
{
|
||||
if (isEqual(files, getContentListFiles(listName)))
|
||||
if (isEqual(files, getContentListFiles(listName)) &&
|
||||
isEqual(archives, getArchiveList(listName)) &&
|
||||
isEqual(dirs, getDataDirectoryList(listName)))
|
||||
{
|
||||
setCurrentContentListName(listName);
|
||||
return;
|
||||
@ -115,11 +141,13 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings)
|
||||
// otherwise, add content list
|
||||
QString newContentListName(makeNewContentListName());
|
||||
setCurrentContentListName(newContentListName);
|
||||
setContentList(newContentListName, files);
|
||||
setContentList(newContentListName, dirs, archives, files);
|
||||
}
|
||||
|
||||
void Config::LauncherSettings::removeContentList(const QString &contentListName)
|
||||
{
|
||||
remove(makeDirectoryListKey(contentListName));
|
||||
remove(makeArchiveListKey(contentListName));
|
||||
remove(makeContentListKey(contentListName));
|
||||
}
|
||||
|
||||
@ -129,14 +157,18 @@ void Config::LauncherSettings::setCurrentContentListName(const QString &contentL
|
||||
setValue(QString(sCurrentContentListKey), contentListName);
|
||||
}
|
||||
|
||||
void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& fileNames)
|
||||
void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames)
|
||||
{
|
||||
removeContentList(contentListName);
|
||||
QString key = makeContentListKey(contentListName);
|
||||
for (const QString& fileName : fileNames)
|
||||
auto const assign = [this](const QString key, const QStringList& list)
|
||||
{
|
||||
setMultiValue(key, fileName);
|
||||
}
|
||||
for (auto const& item : list)
|
||||
setMultiValue(key, item);
|
||||
};
|
||||
|
||||
removeContentList(contentListName);
|
||||
assign(makeDirectoryListKey(contentListName), dirNames);
|
||||
assign(makeArchiveListKey(contentListName), archiveNames);
|
||||
assign(makeContentListKey(contentListName), fileNames);
|
||||
}
|
||||
|
||||
QString Config::LauncherSettings::getCurrentContentListName() const
|
||||
@ -144,6 +176,17 @@ QString Config::LauncherSettings::getCurrentContentListName() const
|
||||
return value(QString(sCurrentContentListKey));
|
||||
}
|
||||
|
||||
QStringList Config::LauncherSettings::getDataDirectoryList(const QString& contentListName) const
|
||||
{
|
||||
// QMap returns multiple rows in LIFO order, so need to reverse
|
||||
return reverse(getSettings().values(makeDirectoryListKey(contentListName)));
|
||||
}
|
||||
|
||||
QStringList Config::LauncherSettings::getArchiveList(const QString& contentListName) const
|
||||
{
|
||||
// QMap returns multiple rows in LIFO order, so need to reverse
|
||||
return reverse(getSettings().values(makeArchiveListKey(contentListName)));
|
||||
}
|
||||
QStringList Config::LauncherSettings::getContentListFiles(const QString& contentListName) const
|
||||
{
|
||||
// QMap returns multiple rows in LIFO order, so need to reverse
|
||||
|
@ -18,7 +18,7 @@ namespace Config
|
||||
void setContentList(const GameSettings& gameSettings);
|
||||
|
||||
/// Create a Content List (or replace if it already exists)
|
||||
void setContentList(const QString& contentListName, const QStringList& fileNames);
|
||||
void setContentList(const QString& contentListName, const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames);
|
||||
|
||||
void removeContentList(const QString &contentListName);
|
||||
|
||||
@ -26,15 +26,23 @@ namespace Config
|
||||
|
||||
QString getCurrentContentListName() const;
|
||||
|
||||
QStringList getDataDirectoryList(const QString& contentListName) const;
|
||||
QStringList getArchiveList(const QString& contentListName) const;
|
||||
QStringList getContentListFiles(const QString& contentListName) const;
|
||||
|
||||
/// \return new list that is reversed order of input
|
||||
static QStringList reverse(const QStringList& toReverse);
|
||||
|
||||
static const char sLauncherConfigFileName[];
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/// \return key to use to get/set the files in the specified data Directory List
|
||||
static QString makeDirectoryListKey(const QString& contentListName);
|
||||
|
||||
/// \return key to use to get/set the files in the specified Archive List
|
||||
static QString makeArchiveListKey(const QString& contentListName);
|
||||
|
||||
/// \return key to use to get/set the files in the specified Content List
|
||||
static QString makeContentListKey(const QString& contentListName);
|
||||
|
||||
@ -51,6 +59,8 @@ namespace Config
|
||||
/// section of launcher.cfg holding the Content Lists
|
||||
static const char sContentListsSectionPrefix[];
|
||||
|
||||
static const char sDirectoryListSuffix[];
|
||||
static const char sArchiveListSuffix[];
|
||||
static const char sContentListSuffix[];
|
||||
};
|
||||
}
|
||||
|
@ -160,6 +160,15 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int
|
||||
return isLoadOrderError(file) ? mWarningIcon : QVariant();
|
||||
}
|
||||
|
||||
case Qt::BackgroundRole:
|
||||
{
|
||||
if (isNew(file->fileName()))
|
||||
{
|
||||
return QVariant(QColor(Qt::green));
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::EditRole:
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
@ -413,7 +422,7 @@ void ContentSelectorModel::ContentModel::addFile(EsmFile *file)
|
||||
emit dataChanged (idx, idx);
|
||||
}
|
||||
|
||||
void ContentSelectorModel::ContentModel::addFiles(const QString &path)
|
||||
void ContentSelectorModel::ContentModel::addFiles(const QString &path, bool newfiles)
|
||||
{
|
||||
QDir dir(path);
|
||||
QStringList filters;
|
||||
@ -471,6 +480,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path)
|
||||
|
||||
// Put the file in the table
|
||||
addFile(file);
|
||||
setNew(file->fileName(), newfiles);
|
||||
|
||||
} catch(std::runtime_error &e) {
|
||||
// An error occurred while reading the .esp
|
||||
@ -481,6 +491,16 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path)
|
||||
}
|
||||
}
|
||||
|
||||
bool ContentSelectorModel::ContentModel::containsDataFiles(const QString &path)
|
||||
{
|
||||
QDir dir(path);
|
||||
QStringList filters;
|
||||
filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon";
|
||||
dir.setNameFilters(filters);
|
||||
|
||||
return dir.entryList().count() != 0;
|
||||
}
|
||||
|
||||
void ContentSelectorModel::ContentModel::clearFiles()
|
||||
{
|
||||
const int filesCount = mFiles.count();
|
||||
@ -553,6 +573,28 @@ bool ContentSelectorModel::ContentModel::isEnabled (const QModelIndex& index) co
|
||||
return (flags(index) & Qt::ItemIsEnabled);
|
||||
}
|
||||
|
||||
bool ContentSelectorModel::ContentModel::isNew(const QString& filepath) const
|
||||
{
|
||||
if (mNewFiles.contains(filepath))
|
||||
return mNewFiles[filepath];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ContentSelectorModel::ContentModel::setNew(const QString &filepath, bool isNew)
|
||||
{
|
||||
if (filepath.isEmpty())
|
||||
return;
|
||||
|
||||
const EsmFile *file = item(filepath);
|
||||
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
mNewFiles[filepath] = isNew;
|
||||
}
|
||||
|
||||
|
||||
bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile *file) const
|
||||
{
|
||||
return mPluginsWithLoadOrderError.contains(file->filePath());
|
||||
|
@ -43,8 +43,9 @@ namespace ContentSelectorModel
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
|
||||
void addFiles(const QString &path);
|
||||
void addFiles(const QString &path, bool newfiles);
|
||||
void sortFiles();
|
||||
bool containsDataFiles(const QString &path);
|
||||
void clearFiles();
|
||||
|
||||
QModelIndex indexFromItem(const EsmFile *item) const;
|
||||
@ -56,6 +57,8 @@ namespace ContentSelectorModel
|
||||
bool isEnabled (const QModelIndex& index) const;
|
||||
bool isChecked(const QString &filepath) const;
|
||||
bool setCheckState(const QString &filepath, bool isChecked);
|
||||
bool isNew(const QString &filepath) const;
|
||||
void setNew(const QString &filepath, bool isChecked);
|
||||
void setContentList(const QStringList &fileList);
|
||||
ContentFileList checkedItems() const;
|
||||
void uncheckAll();
|
||||
@ -79,7 +82,9 @@ namespace ContentSelectorModel
|
||||
QString toolTip(const EsmFile *file) const;
|
||||
|
||||
ContentFileList mFiles;
|
||||
QStringList mArchives;
|
||||
QHash<QString, Qt::CheckState> mCheckStates;
|
||||
QHash<QString, bool> mNewFiles;
|
||||
QSet<QString> mPluginsWithLoadOrderError;
|
||||
QString mEncoding;
|
||||
QIcon mWarningIcon;
|
||||
|
@ -153,9 +153,9 @@ ContentSelectorModel::ContentFileList
|
||||
return mContentModel->checkedItems();
|
||||
}
|
||||
|
||||
void ContentSelectorView::ContentSelector::addFiles(const QString &path)
|
||||
void ContentSelectorView::ContentSelector::addFiles(const QString &path, bool newfiles)
|
||||
{
|
||||
mContentModel->addFiles(path);
|
||||
mContentModel->addFiles(path, newfiles);
|
||||
|
||||
// add any game files to the combo box
|
||||
for (const QString& gameFileName : mContentModel->gameFiles())
|
||||
@ -178,6 +178,11 @@ void ContentSelectorView::ContentSelector::sortFiles()
|
||||
mContentModel->sortFiles();
|
||||
}
|
||||
|
||||
bool ContentSelectorView::ContentSelector::containsDataFiles(const QString &path)
|
||||
{
|
||||
return mContentModel->containsDataFiles(path);
|
||||
}
|
||||
|
||||
void ContentSelectorView::ContentSelector::clearFiles()
|
||||
{
|
||||
mContentModel->clearFiles();
|
||||
|
@ -27,8 +27,9 @@ namespace ContentSelectorView
|
||||
|
||||
QString currentFile() const;
|
||||
|
||||
void addFiles(const QString &path);
|
||||
void addFiles(const QString &path, bool newfiles = false);
|
||||
void sortFiles();
|
||||
bool containsDataFiles(const QString &path);
|
||||
void clearFiles();
|
||||
void setProfileContent (const QStringList &fileList);
|
||||
|
||||
|
@ -17,16 +17,141 @@
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<widget class="QWidget" name="dirTab">
|
||||
<attribute name="title">
|
||||
<string>Data Files</string>
|
||||
<string>Data Directories</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="dirTabLayout">
|
||||
<item row="0" column="0" rowspan="26">
|
||||
<widget class="QListWidget" name="directoryListWidget">
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::InternalMove</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="directoryAddSubdirsButton">
|
||||
<property name="toolTip">
|
||||
<string>Scan directories for likely data directories and append them at the end of the list.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Append</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="directoryInsertButton">
|
||||
<property name="toolTip">
|
||||
<string>Scan directories for likely data directories and insert them above the selected position</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Insert Above</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="directoryUpButton">
|
||||
<property name="toolTip">
|
||||
<string>Move selected directory one position up</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Move Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="directoryDownButton">
|
||||
<property name="toolTip">
|
||||
<string>Move selected directory one position down</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Move Down</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="directoryRemoveButton">
|
||||
<property name="toolTip">
|
||||
<string>Remove selected directory</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="27" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-style:italic;">note: directories that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="archiveTab">
|
||||
<attribute name="title">
|
||||
<string>Archive Files</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="archiveTabLayout">
|
||||
<item row="0" column="0" rowspan="26">
|
||||
<widget class="QListWidget" name="archiveListWidget">
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::InternalMove</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="archiveUpButton">
|
||||
<property name="toolTip">
|
||||
<string>Move selected archive one position up</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Move Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="archiveDownButton">
|
||||
<property name="toolTip">
|
||||
<string>Move selected archive one position down</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Move Down</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="27" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-style:italic;">note: archives that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="dataTab">
|
||||
<attribute name="title">
|
||||
<string>Content Files</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="dataTabLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QWidget" name="contentSelectorWidget" native="true"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p><span style=" font-style:italic;">note: content files that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
|
47
files/ui/directorypicker.ui
Normal file
47
files/ui/directorypicker.ui
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SelectSubdirs</class>
|
||||
<widget class="QDialog" name="SelectSubdirs">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select directories you wish to add</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QDialogButtonBox" name="confirmButton">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="dirListWidget"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>confirmButton</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>SelectSubdirs</receiver>
|
||||
<slot>accept()</slot>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>confirmButton</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>SelectSubdirs</receiver>
|
||||
<slot>reject()</slot>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
Loading…
x
Reference in New Issue
Block a user