Merge branch 'develop' into mrpack-export

Signed-off-by: Kode <TheKodeToad@proton.me>
This commit is contained in:
Kode
2023-05-13 19:07:03 +01:00
committed by GitHub
153 changed files with 5252 additions and 2647 deletions

View File

@ -39,7 +39,6 @@
#include <QFileInfo>
#include <QMimeData>
#include <QPushButton>
#include <QMimeData>
#include <QStandardPaths>
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
@ -89,11 +88,11 @@ void BlockedModsDialog::dragEnterEvent(QDragEnterEvent* e)
void BlockedModsDialog::dropEvent(QDropEvent* e)
{
for (QUrl& url : e->mimeData()->urls()) {
if (url.scheme().isEmpty()) { // ensure isLocalFile() works correctly
if (url.scheme().isEmpty()) { // ensure isLocalFile() works correctly
url.setScheme("file");
}
if (!url.isLocalFile()) { // can't drop external files here.
if (!url.isLocalFile()) { // can't drop external files here.
continue;
}
@ -172,7 +171,7 @@ void BlockedModsDialog::update()
}
}
/// @brief Signal fired when a watched direcotry has changed
/// @brief Signal fired when a watched directory has changed
/// @param path the path to the changed directory
void BlockedModsDialog::directoryChanged(QString path)
{
@ -186,8 +185,29 @@ void BlockedModsDialog::setupWatch()
{
const QString downloadsFolder = APPLICATION->settings()->get("DownloadsDir").toString();
const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString();
m_watcher.addPath(downloadsFolder);
m_watcher.addPath(modsFolder);
const bool downloadsFolderWatchRecursive = APPLICATION->settings()->get("DownloadsDirWatchRecursive").toBool();
watchPath(downloadsFolder, downloadsFolderWatchRecursive);
watchPath(modsFolder, true);
}
void BlockedModsDialog::watchPath(QString path, bool watch_recursive)
{
auto to_watch = QFileInfo(path);
auto to_watch_path = to_watch.canonicalFilePath();
if (m_watcher.directories().contains(to_watch_path))
return; // don't watch the same path twice (no loops!)
qDebug() << "[Blocked Mods Dialog] Adding Watch Path:" << path;
m_watcher.addPath(to_watch_path);
if (!to_watch.isDir() || !watch_recursive)
return;
QDirIterator it(to_watch_path, QDir::Filter::Dirs | QDir::Filter::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
while (it.hasNext()) {
QString watch_dir = QDir(it.next()).canonicalPath(); // resolve symlinks and relative paths
watchPath(watch_dir, watch_recursive);
}
}
/// @brief scan all watched folder
@ -221,7 +241,7 @@ void BlockedModsDialog::scanPath(QString path, bool start_task)
}
}
/// @brief add a hashing task for the file located at path, add the path to the pending set if the hasing task is already running
/// @brief add a hashing task for the file located at path, add the path to the pending set if the hashing task is already running
/// @param path the path to the local file being hashed
void BlockedModsDialog::addHashTask(QString path)
{
@ -281,11 +301,35 @@ bool BlockedModsDialog::checkValidPath(QString path)
{
const QFileInfo file = QFileInfo(path);
const QString filename = file.fileName();
QString laxFilename(filename);
laxFilename.replace('+', ' ');
auto compare = [](QString fsfilename, QString metadataFilename) {
return metadataFilename.compare(fsfilename, Qt::CaseInsensitive) == 0;
auto compare = [](QString fsFilename, QString metadataFilename) {
return metadataFilename.compare(fsFilename, Qt::CaseInsensitive) == 0;
};
// super lax compare (but not fuzzy)
// convert to lowercase
// convert all speratores to whitespace
// simplify sequence of internal whitespace to a single space
// efectivly compare two strings ignoring all separators and case
auto laxCompare = [](QString fsfilename, QString metadataFilename) {
// allowed character seperators
QList<QChar> allowedSeperators = { '-', '+', '.' , '_'};
// copy in lowercase
auto fsName = fsfilename.toLower();
auto metaName = metadataFilename.toLower();
// replace all potential allowed seperatores with whitespace
for (auto sep : allowedSeperators) {
fsName = fsName.replace(sep, ' ');
metaName = metaName.replace(sep, ' ');
}
// remove extraneous whitespace
fsName = fsName.simplified();
metaName = metaName.simplified();
return fsName.compare(metaName) == 0;
};
for (auto& mod : m_mods) {
@ -293,7 +337,7 @@ bool BlockedModsDialog::checkValidPath(QString path)
qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
return true;
}
if (compare(laxFilename, mod.name)) {
if (laxCompare(filename, mod.name)) {
qDebug() << "[Blocked Mods Dialog] Lax name match found:" << mod.name << "| From path:" << path;
return true;
}
@ -328,7 +372,7 @@ void BlockedModsDialog::validateMatchedMods()
}
}
/// @brief run hash task or mark a pending run if it is already runing
/// @brief run hash task or mark a pending run if it is already running
void BlockedModsDialog::runHashTask()
{
if (!m_hashing_task->isRunning()) {

View File

@ -79,6 +79,7 @@ class BlockedModsDialog : public QDialog {
void update();
void directoryChanged(QString path);
void setupWatch();
void watchPath(QString path, bool watch_recursive = false);
void scanPaths();
void scanPath(QString path, bool start_task);
void addHashTask(QString path);

View File

@ -37,18 +37,21 @@
#include <QPushButton>
#include "Application.h"
#include "BuildConfig.h"
#include "CopyInstanceDialog.h"
#include "ui_CopyInstanceDialog.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "BaseVersion.h"
#include "icons/IconList.h"
#include "BaseInstance.h"
#include "BaseVersion.h"
#include "DesktopServices.h"
#include "FileSystem.h"
#include "InstanceList.h"
#include "icons/IconList.h"
CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
:QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget* parent)
: QDialog(parent), ui(new Ui::CopyInstanceDialog), m_original(original)
{
ui->setupUi(this);
resize(minimumSizeHint());
@ -71,8 +74,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
groupList.push_front("");
ui->groupBox->addItems(groupList);
int index = groupList.indexOf(APPLICATION->instances()->getInstanceGroup(m_original->id()));
if(index == -1)
{
if (index == -1) {
index = 0;
}
ui->groupBox->setCurrentIndex(index);
@ -85,6 +87,35 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled());
ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled());
ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled());
ui->symbolicLinksCheckbox->setChecked(m_selectedOptions.isUseSymLinksEnabled());
ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
ui->recursiveLinkCheckbox->setChecked(m_selectedOptions.isLinkRecursivelyEnabled());
ui->dontLinkSavesCheckbox->setChecked(m_selectedOptions.isDontLinkSavesEnabled());
auto detectedFS = FS::statFS(m_original->instanceRoot()).fsType;
m_cloneSupported = FS::canCloneOnFS(detectedFS);
m_linkSupported = FS::canLinkOnFS(detectedFS);
if (m_cloneSupported) {
ui->cloneSupportedLabel->setText(tr("Reflinks are supported on %1").arg(FS::getFilesystemTypeName(detectedFS)));
} else {
ui->cloneSupportedLabel->setText(tr("Reflinks aren't supported on %1").arg(FS::getFilesystemTypeName(detectedFS)));
}
#if defined(Q_OS_WIN)
ui->symbolicLinksCheckbox->setIcon(style()->standardIcon(QStyle::SP_VistaShield));
ui->symbolicLinksCheckbox->setToolTip(tr("Use symbolic links instead of copying files.") +
"\n" + tr("On Windows, symbolic links may require admin permission to create."));
#endif
updateLinkOptions();
updateUseCloneCheckbox();
auto HelpButton = ui->buttonBox->button(QDialogButtonBox::Help);
connect(HelpButton, &QPushButton::clicked, this, &CopyInstanceDialog::help);
}
CopyInstanceDialog::~CopyInstanceDialog()
@ -96,8 +127,7 @@ void CopyInstanceDialog::updateDialogState()
{
auto allowOK = !instName().isEmpty();
auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok);
if(OkButton->isEnabled() != allowOK)
{
if (OkButton->isEnabled() != allowOK) {
OkButton->setEnabled(allowOK);
}
}
@ -105,8 +135,7 @@ void CopyInstanceDialog::updateDialogState()
QString CopyInstanceDialog::instName() const
{
auto result = ui->instNameTextBox->text().trimmed();
if(result.size())
{
if (result.size()) {
return result;
}
return QString();
@ -127,6 +156,11 @@ const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const
return m_selectedOptions;
}
void CopyInstanceDialog::help()
{
DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("instance-copy")));
}
void CopyInstanceDialog::checkAllCheckboxes(const bool& b)
{
ui->keepPlaytimeCheckbox->setChecked(b);
@ -147,20 +181,46 @@ void CopyInstanceDialog::updateSelectAllCheckbox()
ui->selectAllCheckbox->blockSignals(false);
}
void CopyInstanceDialog::updateUseCloneCheckbox()
{
ui->useCloneCheckbox->setEnabled(m_cloneSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->hardLinksCheckbox->isChecked());
ui->useCloneCheckbox->setChecked(m_cloneSupported && m_selectedOptions.isUseCloneEnabled() && !ui->symbolicLinksCheckbox->isChecked() &&
!ui->hardLinksCheckbox->isChecked());
}
void CopyInstanceDialog::updateLinkOptions()
{
ui->symbolicLinksCheckbox->setEnabled(m_linkSupported && !ui->hardLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked());
ui->hardLinksCheckbox->setEnabled(m_linkSupported && !ui->symbolicLinksCheckbox->isChecked() && !ui->useCloneCheckbox->isChecked());
ui->symbolicLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseSymLinksEnabled() &&
!ui->useCloneCheckbox->isChecked());
ui->hardLinksCheckbox->setChecked(m_linkSupported && m_selectedOptions.isUseHardLinksEnabled() && !ui->useCloneCheckbox->isChecked());
bool linksInUse = (ui->symbolicLinksCheckbox->isChecked() || ui->hardLinksCheckbox->isChecked());
ui->recursiveLinkCheckbox->setEnabled(m_linkSupported && linksInUse && !ui->hardLinksCheckbox->isChecked());
ui->dontLinkSavesCheckbox->setEnabled(m_linkSupported && linksInUse);
ui->recursiveLinkCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isLinkRecursivelyEnabled());
ui->dontLinkSavesCheckbox->setChecked(m_linkSupported && linksInUse && m_selectedOptions.isDontLinkSavesEnabled());
#if defined(Q_OS_WIN)
auto OkButton = ui->buttonBox->button(QDialogButtonBox::Ok);
OkButton->setIcon(m_selectedOptions.isUseSymLinksEnabled() ? style()->standardIcon(QStyle::SP_VistaShield) : QIcon());
#endif
}
void CopyInstanceDialog::on_iconButton_clicked()
{
IconPickerDialog dlg(this);
dlg.execWithSelection(InstIconKey);
if (dlg.result() == QDialog::Accepted)
{
if (dlg.result() == QDialog::Accepted) {
InstIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
}
}
void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1)
void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString& arg1)
{
updateDialogState();
}
@ -175,10 +235,10 @@ void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state)
void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
{
m_selectedOptions.enableCopySaves(state == Qt::Checked);
ui->dontLinkSavesCheckbox->setChecked((state == Qt::Checked) && ui->dontLinkSavesCheckbox->isChecked());
updateSelectAllCheckbox();
}
void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state)
{
m_selectedOptions.enableKeepPlaytime(state == Qt::Checked);
@ -220,3 +280,38 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
m_selectedOptions.enableCopyScreenshots(state == Qt::Checked);
updateSelectAllCheckbox();
}
void CopyInstanceDialog::on_symbolicLinksCheckbox_stateChanged(int state)
{
m_selectedOptions.enableUseSymLinks(state == Qt::Checked);
updateUseCloneCheckbox();
updateLinkOptions();
}
void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
{
m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
if (state == Qt::Checked && !ui->recursiveLinkCheckbox->isChecked()) {
ui->recursiveLinkCheckbox->setChecked(true);
}
updateUseCloneCheckbox();
updateLinkOptions();
}
void CopyInstanceDialog::on_recursiveLinkCheckbox_stateChanged(int state)
{
m_selectedOptions.enableLinkRecursively(state == Qt::Checked);
updateLinkOptions();
}
void CopyInstanceDialog::on_dontLinkSavesCheckbox_stateChanged(int state)
{
m_selectedOptions.enableDontLinkSaves(state == Qt::Checked);
}
void CopyInstanceDialog::on_useCloneCheckbox_stateChanged(int state)
{
m_selectedOptions.enableUseClone(m_cloneSupported && (state == Qt::Checked));
updateUseCloneCheckbox();
updateLinkOptions();
}

View File

@ -16,22 +16,21 @@
#pragma once
#include <QDialog>
#include "BaseInstance.h"
#include "BaseVersion.h"
#include "InstanceCopyPrefs.h"
class BaseInstance;
namespace Ui
{
namespace Ui {
class CopyInstanceDialog;
}
class CopyInstanceDialog : public QDialog
{
class CopyInstanceDialog : public QDialog {
Q_OBJECT
public:
explicit CopyInstanceDialog(InstancePtr original, QWidget *parent = 0);
public:
explicit CopyInstanceDialog(InstancePtr original, QWidget* parent = 0);
~CopyInstanceDialog();
void updateDialogState();
@ -41,10 +40,12 @@ public:
QString iconKey() const;
const InstanceCopyPrefs& getChosenOptions() const;
private
slots:
public slots:
void help();
private slots:
void on_iconButton_clicked();
void on_instNameTextBox_textChanged(const QString &arg1);
void on_instNameTextBox_textChanged(const QString& arg1);
// Checkboxes
void on_selectAllCheckbox_stateChanged(int state);
void on_copySavesCheckbox_stateChanged(int state);
@ -55,13 +56,23 @@ slots:
void on_copyServersCheckbox_stateChanged(int state);
void on_copyModsCheckbox_stateChanged(int state);
void on_copyScreenshotsCheckbox_stateChanged(int state);
void on_symbolicLinksCheckbox_stateChanged(int state);
void on_hardLinksCheckbox_stateChanged(int state);
void on_recursiveLinkCheckbox_stateChanged(int state);
void on_dontLinkSavesCheckbox_stateChanged(int state);
void on_useCloneCheckbox_stateChanged(int state);
private:
private:
void checkAllCheckboxes(const bool& b);
void updateSelectAllCheckbox();
void updateUseCloneCheckbox();
void updateLinkOptions();
/* data */
Ui::CopyInstanceDialog *ui;
Ui::CopyInstanceDialog* ui;
QString InstIconKey;
InstancePtr m_original;
InstanceCopyPrefs m_selectedOptions;
bool m_cloneSupported = false;
bool m_linkSupported = false;
};

View File

@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>341</width>
<height>399</height>
<width>575</width>
<height>695</height>
</rect>
</property>
<property name="windowTitle">
@ -113,93 +113,268 @@
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="selectAllButtonLayout">
<widget class="QGroupBox" name="copyOptionsGroup">
<property name="title">
<string>Instance Copy Options</string>
</property>
<layout class="QGridLayout" name="copyOptionsLayout">
<item row="1" column="0">
<widget class="QCheckBox" name="keepPlaytimeCheckbox">
<property name="text">
<string>Keep play time</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="copyModsCheckbox">
<property name="toolTip">
<string>Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.</string>
</property>
<property name="text">
<string>Copy mods</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="copyResPacksCheckbox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Copy resource packs</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="copyGameOptionsCheckbox">
<property name="toolTip">
<string>Copy the in-game options like FOV, max framerate, etc.</string>
</property>
<property name="text">
<string>Copy game options</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="copyShaderPacksCheckbox">
<property name="text">
<string>Copy shader packs</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="copyServersCheckbox">
<property name="text">
<string>Copy servers</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="copySavesCheckbox">
<property name="text">
<string>Copy saves</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="copyScreenshotsCheckbox">
<property name="text">
<string>Copy screenshots</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="selectAllCheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Select all</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="advancedOptionsLabel">
<property name="text">
<string>Advanced Copy Options</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="copyModeLayout">
<item>
<widget class="QCheckBox" name="selectAllCheckbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QGroupBox" name="linkFilesGroup">
<property name="toolTip">
<string>Use symbolic or hard links instead of copying files.</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
<property name="title">
<string>Symbolic and Hard Link Options</string>
</property>
<property name="text">
<string>Select all</string>
<property name="flat">
<bool>false</bool>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="linkOptionsLayout">
<item>
<widget class="QLabel" name="linkOptionsLabel">
<property name="text">
<string>Links are supported on most filesystems except FAT</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="linkOptionsGridLayout" rowstretch="0,0,0,0" columnstretch="0,0" rowminimumheight="0,0,0,0" columnminimumwidth="0,0">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item row="2" column="1">
<widget class="QCheckBox" name="recursiveLinkCheckbox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Link each resource individually instead of linking whole folders at once</string>
</property>
<property name="text">
<string>Link files recursively</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="dontLinkSavesCheckbox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>If &quot;copy saves&quot; is selected world save data will be copied instead of linked and thus not shared between instances.</string>
</property>
<property name="text">
<string>Don't link saves</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="hardLinksCheckbox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Use hard links instead of copying files.</string>
</property>
<property name="text">
<string>Use hard links</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="symbolicLinksCheckbox">
<property name="toolTip">
<string>Use symbolic links instead of copying files.</string>
</property>
<property name="text">
<string>Use symbolic links</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="copyOptionsLayout">
<item row="6" column="1">
<widget class="QCheckBox" name="copyModsCheckbox">
<property name="toolTip">
<string>Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.</string>
</property>
<property name="text">
<string>Copy mods</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="copyGameOptionsCheckbox">
<property name="toolTip">
<string>Copy the in-game options like FOV, max framerate, etc.</string>
</property>
<property name="text">
<string>Copy game options</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="copySavesCheckbox">
<property name="text">
<string>Copy saves</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="copyShaderPacksCheckbox">
<property name="text">
<string>Copy shader packs</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="copyServersCheckbox">
<property name="text">
<string>Copy servers</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="copyResPacksCheckbox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Copy resource packs</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="keepPlaytimeCheckbox">
<property name="text">
<string>Keep play time</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="copyScreenshotsCheckbox">
<property name="text">
<string>Copy screenshots</string>
<item>
<widget class="QGroupBox" name="horizontalGroupBox">
<property name="title">
<string>CoW (Copy-on-Write) Options</string>
</property>
<layout class="QHBoxLayout" name="useCloneLayout">
<item>
<widget class="QCheckBox" name="useCloneCheckbox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Files cloned with reflinks take up no extra space until they are modified.</string>
</property>
<property name="text">
<string>Clone instead of copying</string>
</property>
</widget>
</item>
<item>
<spacer name="CoWSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="cloneSupportedLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Your filesystem and/or OS doesn't support reflinks</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="margin">
<number>4</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
@ -210,7 +385,7 @@
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
@ -220,10 +395,21 @@
<tabstop>iconButton</tabstop>
<tabstop>instNameTextBox</tabstop>
<tabstop>groupBox</tabstop>
<tabstop>keepPlaytimeCheckbox</tabstop>
<tabstop>copyScreenshotsCheckbox</tabstop>
<tabstop>copySavesCheckbox</tabstop>
<tabstop>copyShaderPacksCheckbox</tabstop>
<tabstop>copyGameOptionsCheckbox</tabstop>
<tabstop>copyServersCheckbox</tabstop>
<tabstop>copyResPacksCheckbox</tabstop>
<tabstop>copyModsCheckbox</tabstop>
<tabstop>symbolicLinksCheckbox</tabstop>
<tabstop>recursiveLinkCheckbox</tabstop>
<tabstop>hardLinksCheckbox</tabstop>
<tabstop>dontLinkSavesCheckbox</tabstop>
<tabstop>useCloneCheckbox</tabstop>
</tabstops>
<resources>
<include location="../../graphics.qrc"/>
</resources>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
@ -232,8 +418,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>254</x>
<y>316</y>
<x>269</x>
<y>692</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@ -248,8 +434,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>322</x>
<y>316</y>
<x>337</x>
<y>692</y>
</hint>
<hint type="destinationlabel">
<x>286</x>

View File

@ -45,6 +45,8 @@
#include <QDebug>
#include <QSaveFile>
#include <QStack>
#include <QFileInfo>
#include "StringUtils.h"
#include "SeparatorPrefixTree.h"
#include "Application.h"
#include <icons/IconList.h>
@ -138,7 +140,8 @@ bool ExportInstanceDialog::doExport()
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
return false;
}
if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files))
if (!MMCZip::compressDirFiles(output, m_instance->instanceRoot(), files, true))
{
QMessageBox::warning(this, tr("Error"), tr("Unable to export instance"));
return false;

View File

@ -56,7 +56,6 @@
#include "ui/widgets/PageContainer.h"
#include "ui/pages/modplatform/VanillaPage.h"
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
#include "ui/pages/modplatform/ftb/FtbPage.h"
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/ImportPage.h"
@ -168,7 +167,6 @@ QList<BasePage *> NewInstanceDialog::getPages()
pages.append(new AtlPage(this));
if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(new FlamePage(this));
pages.append(new FtbPage(this));
pages.append(new LegacyFTB::Page(this));
pages.append(new ModrinthPage(this));
pages.append(new TechnicPage(this));

View File

@ -1,29 +1,69 @@
/* Copyright 2013-2021 MultiMC Contributors
/// SPDX-License-Identifier: GPL-3.0-only
/*
* PrismLaucher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* http://www.apache.org/licenses/LICENSE-2.0
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ProgressDialog.h"
#include "ui_ProgressDialog.h"
#include <limits>
#include <QDebug>
#include <QKeyEvent>
#include "tasks/Task.h"
#include "ui/widgets/SubTaskProgressBar.h"
// map a value in a numeric range of an arbitrary type to between 0 and INT_MAX
// for getting the best precision out of the qt progress bar
template<typename T, std::enable_if_t<std::is_arithmetic_v<T>, bool> = true>
std::tuple<int, int> map_int_zero_max(T current, T range_max, T range_min)
{
int int_max = std::numeric_limits<int>::max();
auto type_range = range_max - range_min;
double percentage = static_cast<double>(current - range_min) / static_cast<double>(type_range);
int mapped_current = percentage * int_max;
return {mapped_current, int_max};
}
ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProgressDialog)
{
ui->setupUi(this);
ui->taskProgressScrollArea->setHidden(true);
this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true);
setSkipButton(false);
@ -54,10 +94,24 @@ ProgressDialog::~ProgressDialog()
}
void ProgressDialog::updateSize()
{
{
QSize lastSize = this->size();
QSize qSize = QSize(480, minimumSizeHint().height());
resize(qSize);
setFixedSize(qSize);
// if the current window is too small
if ((lastSize != qSize) && (lastSize.height() < qSize.height()))
{
resize(qSize);
// keep the dialog in the center after a resize
this->move(
this->parentWidget()->x() + (this->parentWidget()->width() - this->width()) / 2,
this->parentWidget()->y() + (this->parentWidget()->height() - this->height()) / 2
);
}
setMinimumSize(qSize);
}
int ProgressDialog::execWithTask(Task* task)
@ -79,17 +133,15 @@ int ProgressDialog::execWithTask(Task* task)
connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed);
connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded);
connect(task, &Task::status, this, &ProgressDialog::changeStatus);
connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus);
connect(task, &Task::details, this, &ProgressDialog::changeStatus);
connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress);
connect(task, &Task::progress, this, &ProgressDialog::changeProgress);
connect(task, &Task::aborted, this, &ProgressDialog::hide);
connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled);
m_is_multi_step = task->isMultiStep();
if (!m_is_multi_step) {
ui->globalStatusLabel->setHidden(true);
ui->globalProgressBar->setHidden(true);
}
ui->taskProgressScrollArea->setHidden(!m_is_multi_step);
updateSize();
// It's a good idea to start the task after we entered the dialog's event loop :^)
if (!task->isRunning()) {
@ -149,23 +201,53 @@ void ProgressDialog::onTaskSucceeded()
void ProgressDialog::changeStatus(const QString& status)
{
ui->globalStatusLabel->setText(task->getStatus());
ui->statusLabel->setText(task->getStepStatus());
ui->globalStatusDetailsLabel->setText(task->getDetails());
updateSize();
}
void ProgressDialog::addTaskProgress(TaskStepProgress const& progress)
{
SubTaskProgressBar* task_bar = new SubTaskProgressBar(this);
taskProgress.insert(progress.uid, task_bar);
ui->taskProgressLayout->addWidget(task_bar);
}
void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress)
{
m_is_multi_step = true;
if(ui->taskProgressScrollArea->isHidden()) {
ui->taskProgressScrollArea->setHidden(false);
updateSize();
}
if (!taskProgress.contains(task_progress.uid))
addTaskProgress(task_progress);
auto task_bar = taskProgress.value(task_progress.uid);
auto const [mapped_current, mapped_total] = map_int_zero_max<qint64>(task_progress.current, task_progress.total, 0);
if (task_progress.total <= 0) {
task_bar->setRange(0, 0);
} else {
task_bar->setRange(0, mapped_total);
}
task_bar->setValue(mapped_current);
task_bar->setStatus(task_progress.status);
task_bar->setDetails(task_progress.details);
if (task_progress.isDone()) {
task_bar->setVisible(false);
}
}
void ProgressDialog::changeProgress(qint64 current, qint64 total)
{
ui->globalProgressBar->setMaximum(total);
ui->globalProgressBar->setValue(current);
if (!m_is_multi_step) {
ui->taskProgressBar->setMaximum(total);
ui->taskProgressBar->setValue(current);
} else {
ui->taskProgressBar->setMaximum(task->getStepProgress());
ui->taskProgressBar->setValue(task->getStepTotalProgress());
}
}
void ProgressDialog::keyPressEvent(QKeyEvent* e)

View File

@ -1,22 +1,50 @@
/* Copyright 2013-2021 MultiMC Contributors
/// SPDX-License-Identifier: GPL-3.0-only
/*
* PrismLaucher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* http://www.apache.org/licenses/LICENSE-2.0
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QDialog>
#include <memory>
#include <QHash>
#include <QUuid>
#include "QObjectPtr.h"
#include "tasks/Task.h"
#include "ui/widgets/SubTaskProgressBar.h"
class Task;
class SequentialTask;
@ -52,6 +80,7 @@ slots:
void changeStatus(const QString &status);
void changeProgress(qint64 current, qint64 total);
void changeStepProgress(TaskStepProgress const& task_progress);
private
@ -64,6 +93,7 @@ protected:
private:
bool handleImmediateResult(QDialog::DialogCode &result);
void addTaskProgress(TaskStepProgress const& progress);
private:
Ui::ProgressDialog *ui;
@ -71,4 +101,8 @@ private:
Task *task;
bool m_is_multi_step = false;
QHash<QUuid, SubTaskProgressBar*> taskProgress;
};

View File

@ -2,26 +2,129 @@
<ui version="4.0">
<class>ProgressDialog</class>
<widget class="QDialog" name="ProgressDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>210</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>16777215</height>
<width>480</width>
<height>210</height>
</size>
</property>
<property name="windowTitle">
<string>Please wait...</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0">
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<item>
<widget class="QLabel" name="globalStatusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>15</height>
</size>
</property>
<property name="text">
<string>Global Task Status...</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="globalStatusDetailsLabel">
<property name="text">
<string>Global Status Details...</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QProgressBar" name="globalProgressBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="taskProgressScrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="taskProgressContainer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>464</width>
<height>96</height>
</rect>
</property>
<layout class="QVBoxLayout" name="taskProgressLayout">
<property name="spacing">
<number>2</number>
</property>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QPushButton" name="skipButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -31,49 +134,6 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="globalStatusLabel">
<property name="text">
<string>Global Task Status...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="statusLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Task Status...</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QProgressBar" name="taskProgressBar">
<property name="value">
<number>24</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QProgressBar" name="globalProgressBar">
<property name="enabled">
<bool>true</bool>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>