Trash instances instead of deleting (when possible) (#549)
Squashed because of :pofat: commit history
This commit is contained in:
parent
94a63e3859
commit
b15544c163
@ -35,25 +35,25 @@
|
|||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDebug>
|
#include <QSaveFile>
|
||||||
#include <QUrl>
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#include <windows.h>
|
|
||||||
#include <string>
|
|
||||||
#include <sys/utime.h>
|
|
||||||
#include <winnls.h>
|
|
||||||
#include <shobjidl.h>
|
|
||||||
#include <objbase.h>
|
#include <objbase.h>
|
||||||
#include <objidl.h>
|
#include <objidl.h>
|
||||||
#include <shlguid.h>
|
#include <shlguid.h>
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
|
#include <shobjidl.h>
|
||||||
|
#include <sys/utime.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winnls.h>
|
||||||
|
#include <string>
|
||||||
#else
|
#else
|
||||||
#include <utime.h>
|
#include <utime.h>
|
||||||
#endif
|
#endif
|
||||||
@ -62,10 +62,8 @@ namespace FS {
|
|||||||
|
|
||||||
void ensureExists(const QDir& dir)
|
void ensureExists(const QDir& dir)
|
||||||
{
|
{
|
||||||
if (!QDir().mkpath(dir.absolutePath()))
|
if (!QDir().mkpath(dir.absolutePath())) {
|
||||||
{
|
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + dir.absolutePath() + ")");
|
||||||
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" +
|
|
||||||
dir.absolutePath() + ")");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,38 +71,28 @@ void write(const QString &filename, const QByteArray &data)
|
|||||||
{
|
{
|
||||||
ensureExists(QFileInfo(filename).dir());
|
ensureExists(QFileInfo(filename).dir());
|
||||||
QSaveFile file(filename);
|
QSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly))
|
if (!file.open(QSaveFile::WriteOnly)) {
|
||||||
{
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
throw FileSystemException("Couldn't open " + filename + " for writing: " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
if (data.size() != file.write(data))
|
if (data.size() != file.write(data)) {
|
||||||
{
|
throw FileSystemException("Error writing data to " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error writing data to " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
if (!file.commit())
|
if (!file.commit()) {
|
||||||
{
|
throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error while committing data to " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray read(const QString& filename)
|
QByteArray read(const QString& filename)
|
||||||
{
|
{
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
if (!file.open(QFile::ReadOnly))
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
{
|
throw FileSystemException("Unable to open " + filename + " for reading: " + file.errorString());
|
||||||
throw FileSystemException("Unable to open " + filename + " for reading: " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
const qint64 size = file.size();
|
const qint64 size = file.size();
|
||||||
QByteArray data(int(size), 0);
|
QByteArray data(int(size), 0);
|
||||||
const qint64 ret = file.read(data.data(), size);
|
const qint64 ret = file.read(data.data(), size);
|
||||||
if (ret == -1 || ret != size)
|
if (ret == -1 || ret != size) {
|
||||||
{
|
throw FileSystemException("Error reading data from " + filename + ": " + file.errorString());
|
||||||
throw FileSystemException("Error reading data from " + filename + ": " +
|
|
||||||
file.errorString());
|
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -152,52 +140,39 @@ bool copy::operator()(const QString &offset)
|
|||||||
if (!currentSrc.exists())
|
if (!currentSrc.exists())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(!m_followSymlinks && currentSrc.isSymLink())
|
if (!m_followSymlinks && currentSrc.isSymLink()) {
|
||||||
{
|
|
||||||
qDebug() << "creating symlink" << src << " - " << dst;
|
qDebug() << "creating symlink" << src << " - " << dst;
|
||||||
if (!ensureFilePathExists(dst))
|
if (!ensureFilePathExists(dst)) {
|
||||||
{
|
|
||||||
qWarning() << "Cannot create path!";
|
qWarning() << "Cannot create path!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return QFile::link(currentSrc.symLinkTarget(), dst);
|
return QFile::link(currentSrc.symLinkTarget(), dst);
|
||||||
}
|
} else if (currentSrc.isFile()) {
|
||||||
else if(currentSrc.isFile())
|
|
||||||
{
|
|
||||||
qDebug() << "copying file" << src << " - " << dst;
|
qDebug() << "copying file" << src << " - " << dst;
|
||||||
if (!ensureFilePathExists(dst))
|
if (!ensureFilePathExists(dst)) {
|
||||||
{
|
|
||||||
qWarning() << "Cannot create path!";
|
qWarning() << "Cannot create path!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return QFile::copy(src, dst);
|
return QFile::copy(src, dst);
|
||||||
}
|
} else if (currentSrc.isDir()) {
|
||||||
else if(currentSrc.isDir())
|
|
||||||
{
|
|
||||||
qDebug() << "recursing" << offset;
|
qDebug() << "recursing" << offset;
|
||||||
if (!ensureFolderPathExists(dst))
|
if (!ensureFolderPathExists(dst)) {
|
||||||
{
|
|
||||||
qWarning() << "Cannot create path!";
|
qWarning() << "Cannot create path!";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QDir currentDir(src);
|
QDir currentDir(src);
|
||||||
for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
for (auto& f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) {
|
||||||
{
|
|
||||||
auto inner_offset = PathCombine(offset, f);
|
auto inner_offset = PathCombine(offset, f);
|
||||||
// ignore and skip stuff that matches the blacklist.
|
// ignore and skip stuff that matches the blacklist.
|
||||||
if(m_blacklist && m_blacklist->matches(inner_offset))
|
if (m_blacklist && m_blacklist->matches(inner_offset)) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(!operator()(inner_offset))
|
if (!operator()(inner_offset)) {
|
||||||
{
|
|
||||||
qWarning() << "Failed to copy" << inner_offset;
|
qWarning() << "Failed to copy" << inner_offset;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
|
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -214,49 +189,35 @@ bool deletePath(QString path)
|
|||||||
|
|
||||||
QDir dir(path);
|
QDir dir(path);
|
||||||
|
|
||||||
if (!dir.exists())
|
if (!dir.exists()) {
|
||||||
{
|
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
|
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
|
||||||
QDir::AllDirs | QDir::Files,
|
|
||||||
QDir::DirsFirst);
|
|
||||||
|
|
||||||
for(auto & info: allEntries)
|
for (auto& info : allEntries) {
|
||||||
{
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
|
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
|
||||||
auto wString = nativePath.toStdWString();
|
auto wString = nativePath.toStdWString();
|
||||||
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
|
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
|
||||||
// Windows: check for junctions, reparse points and other nasty things of that sort
|
// Windows: check for junctions, reparse points and other nasty things of that sort
|
||||||
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
|
if (dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||||
{
|
if (info.isFile()) {
|
||||||
if (info.isFile())
|
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
OK &= QFile::remove(info.absoluteFilePath());
|
||||||
}
|
} else if (info.isDir()) {
|
||||||
else if (info.isDir())
|
|
||||||
{
|
|
||||||
OK &= dir.rmdir(info.absoluteFilePath());
|
OK &= dir.rmdir(info.absoluteFilePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
|
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
|
||||||
if(info.isSymLink())
|
if (info.isSymLink()) {
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
OK &= QFile::remove(info.absoluteFilePath());
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
else if (info.isDir())
|
else if (info.isDir()) {
|
||||||
{
|
|
||||||
OK &= deletePath(info.absoluteFilePath());
|
OK &= deletePath(info.absoluteFilePath());
|
||||||
}
|
} else if (info.isFile()) {
|
||||||
else if (info.isFile())
|
|
||||||
{
|
|
||||||
OK &= QFile::remove(info.absoluteFilePath());
|
OK &= QFile::remove(info.absoluteFilePath());
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
OK = false;
|
OK = false;
|
||||||
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
|
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
|
||||||
}
|
}
|
||||||
@ -265,6 +226,14 @@ bool deletePath(QString path)
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool trash(QString path, QString *pathInTrash = nullptr)
|
||||||
|
{
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return QFile::moveToTrash(path, pathInTrash);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
QString PathCombine(const QString& path1, const QString& path2)
|
QString PathCombine(const QString& path1, const QString& path2)
|
||||||
{
|
{
|
||||||
@ -292,17 +261,14 @@ QString AbsolutePath(QString path)
|
|||||||
|
|
||||||
QString ResolveExecutable(QString path)
|
QString ResolveExecutable(QString path)
|
||||||
{
|
{
|
||||||
if (path.isEmpty())
|
if (path.isEmpty()) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if(!path.contains('/'))
|
if (!path.contains('/')) {
|
||||||
{
|
|
||||||
path = QStandardPaths::findExecutable(path);
|
path = QStandardPaths::findExecutable(path);
|
||||||
}
|
}
|
||||||
QFileInfo pathInfo(path);
|
QFileInfo pathInfo(path);
|
||||||
if(!pathInfo.exists() || !pathInfo.isExecutable())
|
if (!pathInfo.exists() || !pathInfo.isExecutable()) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
return pathInfo.absoluteFilePath();
|
return pathInfo.absoluteFilePath();
|
||||||
@ -322,12 +288,9 @@ QString NormalizePath(QString path)
|
|||||||
QDir b(path);
|
QDir b(path);
|
||||||
QString newAbsolute = b.absolutePath();
|
QString newAbsolute = b.absolutePath();
|
||||||
|
|
||||||
if (newAbsolute.startsWith(currentAbsolute))
|
if (newAbsolute.startsWith(currentAbsolute)) {
|
||||||
{
|
|
||||||
return a.relativeFilePath(newAbsolute);
|
return a.relativeFilePath(newAbsolute);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return newAbsolute;
|
return newAbsolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,10 +299,8 @@ QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
|
|||||||
|
|
||||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < string.length(); i++)
|
for (int i = 0; i < string.length(); i++) {
|
||||||
{
|
if (badFilenameChars.contains(string[i])) {
|
||||||
if (badFilenameChars.contains(string[i]))
|
|
||||||
{
|
|
||||||
string[i] = replaceWith;
|
string[i] = replaceWith;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -351,15 +312,12 @@ QString DirNameFromString(QString string, QString inDir)
|
|||||||
int num = 0;
|
int num = 0;
|
||||||
QString baseName = RemoveInvalidFilenameChars(string, '-');
|
QString baseName = RemoveInvalidFilenameChars(string, '-');
|
||||||
QString dirName;
|
QString dirName;
|
||||||
do
|
do {
|
||||||
{
|
if (num == 0) {
|
||||||
if(num == 0)
|
|
||||||
{
|
|
||||||
dirName = baseName;
|
dirName = baseName;
|
||||||
}
|
} else {
|
||||||
else
|
dirName = baseName + QString::number(num);
|
||||||
{
|
;
|
||||||
dirName = baseName + QString::number(num);;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's over 9000
|
// If it's over 9000
|
||||||
@ -383,36 +341,31 @@ bool checkProblemticPathJava(QDir folder)
|
|||||||
|
|
||||||
bool called_coinit = false;
|
bool called_coinit = false;
|
||||||
|
|
||||||
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
|
HRESULT CreateLink(LPCCH linkPath, LPCWSTR targetPath, LPCWSTR args)
|
||||||
{
|
{
|
||||||
HRESULT hres;
|
HRESULT hres;
|
||||||
|
|
||||||
if (!called_coinit)
|
if (!called_coinit) {
|
||||||
{
|
|
||||||
hres = CoInitialize(NULL);
|
hres = CoInitialize(NULL);
|
||||||
called_coinit = true;
|
called_coinit = true;
|
||||||
|
|
||||||
if (!SUCCEEDED(hres))
|
if (!SUCCEEDED(hres)) {
|
||||||
{
|
|
||||||
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
|
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
|
||||||
return hres;
|
return hres;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IShellLinkA *link;
|
IShellLink* link;
|
||||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&link);
|
||||||
(LPVOID *)&link);
|
|
||||||
|
|
||||||
if (SUCCEEDED(hres))
|
if (SUCCEEDED(hres)) {
|
||||||
{
|
|
||||||
IPersistFile* persistFile;
|
IPersistFile* persistFile;
|
||||||
|
|
||||||
link->SetPath(targetPath);
|
link->SetPath(targetPath);
|
||||||
link->SetArguments(args);
|
link->SetArguments(args);
|
||||||
|
|
||||||
hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile);
|
hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile);
|
||||||
if (SUCCEEDED(hres))
|
if (SUCCEEDED(hres)) {
|
||||||
{
|
|
||||||
WCHAR wstr[MAX_PATH];
|
WCHAR wstr[MAX_PATH];
|
||||||
|
|
||||||
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
|
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
|
||||||
@ -433,8 +386,7 @@ QString getDesktopDir()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cross-platform Shortcut creation
|
// Cross-platform Shortcut creation
|
||||||
bool createShortCut(QString location, QString dest, QStringList args, QString name,
|
bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon)
|
||||||
QString icon)
|
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||||
location = PathCombine(location, name + ".desktop");
|
location = PathCombine(location, name + ".desktop");
|
||||||
@ -459,8 +411,7 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na
|
|||||||
stream.flush();
|
stream.flush();
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
|
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||||
QFileDevice::ExeOther);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#elif defined Q_OS_WIN
|
#elif defined Q_OS_WIN
|
||||||
|
@ -41,11 +41,9 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
|
|
||||||
namespace FS
|
namespace FS {
|
||||||
{
|
|
||||||
|
|
||||||
class FileSystemException : public ::Exception
|
class FileSystemException : public ::Exception {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
FileSystemException(const QString& message) : Exception(message) {}
|
FileSystemException(const QString& message) : Exception(message) {}
|
||||||
};
|
};
|
||||||
@ -77,8 +75,7 @@ bool ensureFilePathExists(QString filenamepath);
|
|||||||
*/
|
*/
|
||||||
bool ensureFolderPathExists(QString filenamepath);
|
bool ensureFolderPathExists(QString filenamepath);
|
||||||
|
|
||||||
class copy
|
class copy {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
copy(const QString& src, const QString& dst)
|
copy(const QString& src, const QString& dst)
|
||||||
{
|
{
|
||||||
@ -95,10 +92,7 @@ public:
|
|||||||
m_blacklist = filter;
|
m_blacklist = filter;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
bool operator()()
|
bool operator()() { return operator()(QString()); }
|
||||||
{
|
|
||||||
return operator()(QString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool operator()(const QString& offset);
|
bool operator()(const QString& offset);
|
||||||
@ -115,6 +109,11 @@ private:
|
|||||||
*/
|
*/
|
||||||
bool deletePath(QString path);
|
bool deletePath(QString path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trash a folder / file
|
||||||
|
*/
|
||||||
|
bool trash(QString path, QString *pathInTrash);
|
||||||
|
|
||||||
QString PathCombine(const QString& path1, const QString& path2);
|
QString PathCombine(const QString& path1, const QString& path2);
|
||||||
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);
|
QString PathCombine(const QString& path1, const QString& path2, const QString& path3);
|
||||||
QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4);
|
QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4);
|
||||||
|
@ -33,30 +33,32 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QSet>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QThread>
|
|
||||||
#include <QTextStream>
|
|
||||||
#include <QXmlStreamReader>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QUuid>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QStack>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
#include "InstanceList.h"
|
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "InstanceTask.h"
|
|
||||||
#include "settings/INISettingsObject.h"
|
|
||||||
#include "NullInstance.h"
|
|
||||||
#include "minecraft/MinecraftInstance.h"
|
|
||||||
#include "FileSystem.h"
|
|
||||||
#include "ExponentialSeries.h"
|
#include "ExponentialSeries.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "InstanceList.h"
|
||||||
|
#include "InstanceTask.h"
|
||||||
|
#include "NullInstance.h"
|
||||||
#include "WatchLock.h"
|
#include "WatchLock.h"
|
||||||
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@ -69,8 +71,7 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir,
|
|||||||
{
|
{
|
||||||
resumeWatch();
|
resumeWatch();
|
||||||
// Create aand normalize path
|
// Create aand normalize path
|
||||||
if (!QDir::current().exists(instDir))
|
if (!QDir::current().exists(instDir)) {
|
||||||
{
|
|
||||||
QDir::current().mkpath(instDir);
|
QDir::current().mkpath(instDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,9 +84,7 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir,
|
|||||||
m_watcher->addPath(m_instDir);
|
m_watcher->addPath(m_instDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
InstanceList::~InstanceList()
|
InstanceList::~InstanceList() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::DropActions InstanceList::supportedDragActions() const
|
Qt::DropActions InstanceList::supportedDragActions() const
|
||||||
{
|
{
|
||||||
@ -130,7 +129,6 @@ QMimeData * InstanceList::mimeData(const QModelIndexList& indexes) const
|
|||||||
return mimeData;
|
return mimeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int InstanceList::rowCount(const QModelIndex& parent) const
|
int InstanceList::rowCount(const QModelIndex& parent) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
@ -147,8 +145,7 @@ QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent)
|
|||||||
|
|
||||||
QVariant InstanceList::data(const QModelIndex& index, int role) const
|
QVariant InstanceList::data(const QModelIndex& index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid()) {
|
||||||
{
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
||||||
@ -193,18 +190,15 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
|
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||||
{
|
{
|
||||||
if (!index.isValid())
|
if (!index.isValid()) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(role != Qt::EditRole)
|
if (role != Qt::EditRole) {
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
|
BaseInstance* pdata = static_cast<BaseInstance*>(index.internalPointer());
|
||||||
auto newName = value.toString();
|
auto newName = value.toString();
|
||||||
if(pdata->name() == newName)
|
if (pdata->name() == newName) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
pdata->setName(newName);
|
pdata->setName(newName);
|
||||||
@ -214,8 +208,7 @@ bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int
|
|||||||
Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
|
Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const
|
||||||
{
|
{
|
||||||
Qt::ItemFlags f;
|
Qt::ItemFlags f;
|
||||||
if (index.isValid())
|
if (index.isValid()) {
|
||||||
{
|
|
||||||
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||||
}
|
}
|
||||||
return f;
|
return f;
|
||||||
@ -224,13 +217,11 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
|
|||||||
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
return GroupId();
|
return GroupId();
|
||||||
}
|
}
|
||||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||||
if(iter != m_instanceGroupIndex.end())
|
if (iter != m_instanceGroupIndex.end()) {
|
||||||
{
|
|
||||||
return *iter;
|
return *iter;
|
||||||
}
|
}
|
||||||
return GroupId();
|
return GroupId();
|
||||||
@ -239,30 +230,24 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
|||||||
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
qDebug() << "Attempt to set a null instance's group";
|
qDebug() << "Attempt to set a null instance's group";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
auto iter = m_instanceGroupIndex.find(inst->id());
|
auto iter = m_instanceGroupIndex.find(inst->id());
|
||||||
if(iter != m_instanceGroupIndex.end())
|
if (iter != m_instanceGroupIndex.end()) {
|
||||||
{
|
if (*iter != name) {
|
||||||
if(*iter != name)
|
|
||||||
{
|
|
||||||
*iter = name;
|
*iter = name;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
changed = true;
|
changed = true;
|
||||||
m_instanceGroupIndex[id] = name;
|
m_instanceGroupIndex[id] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(changed)
|
if (changed) {
|
||||||
{
|
|
||||||
m_groupNameCache.insert(name);
|
m_groupNameCache.insert(name);
|
||||||
auto idx = getInstIndex(inst.get());
|
auto idx = getInstIndex(inst.get());
|
||||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||||
@ -279,24 +264,20 @@ void InstanceList::deleteGroup(const QString& name)
|
|||||||
{
|
{
|
||||||
bool removed = false;
|
bool removed = false;
|
||||||
qDebug() << "Delete group" << name;
|
qDebug() << "Delete group" << name;
|
||||||
for(auto & instance: m_instances)
|
for (auto& instance : m_instances) {
|
||||||
{
|
|
||||||
const auto& instID = instance->id();
|
const auto& instID = instance->id();
|
||||||
auto instGroupName = getInstanceGroup(instID);
|
auto instGroupName = getInstanceGroup(instID);
|
||||||
if(instGroupName == name)
|
if (instGroupName == name) {
|
||||||
{
|
|
||||||
m_instanceGroupIndex.remove(instID);
|
m_instanceGroupIndex.remove(instID);
|
||||||
qDebug() << "Remove" << instID << "from group" << name;
|
qDebug() << "Remove" << instID << "from group" << name;
|
||||||
removed = true;
|
removed = true;
|
||||||
auto idx = getInstIndex(instance.get());
|
auto idx = getInstIndex(instance.get());
|
||||||
if(idx > 0)
|
if (idx > 0) {
|
||||||
{
|
|
||||||
emit dataChanged(index(idx), index(idx), { GroupRole });
|
emit dataChanged(index(idx), index(idx), { GroupRole });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(removed)
|
if (removed) {
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,23 +287,75 @@ bool InstanceList::isGroupCollapsed(const QString& group)
|
|||||||
return m_collapsedGroups.contains(group);
|
return m_collapsedGroups.contains(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InstanceList::trashInstance(const InstanceId& id)
|
||||||
|
{
|
||||||
|
auto inst = getInstanceById(id);
|
||||||
|
if (!inst) {
|
||||||
|
qDebug() << "Cannot trash instance" << id << ". No such instance is present (deleted externally?).";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cachedGroupId = m_instanceGroupIndex[id];
|
||||||
|
|
||||||
|
qDebug() << "Will trash instance" << id;
|
||||||
|
QString trashedLoc;
|
||||||
|
|
||||||
|
if (m_instanceGroupIndex.remove(id)) {
|
||||||
|
saveGroupList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FS::trash(inst->instanceRoot(), &trashedLoc)) {
|
||||||
|
qDebug() << "Trash of instance" << id << "has not been completely successfully...";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Instance" << id << "has been trashed by the launcher.";
|
||||||
|
m_trashHistory.push({id, inst->instanceRoot(), trashedLoc, cachedGroupId});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceList::trashedSomething() {
|
||||||
|
return !m_trashHistory.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceList::undoTrashInstance() {
|
||||||
|
if (m_trashHistory.empty()) {
|
||||||
|
qWarning() << "Nothing to recover from trash.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto top = m_trashHistory.pop();
|
||||||
|
|
||||||
|
while (QDir(top.polyPath).exists()) {
|
||||||
|
top.id += "1";
|
||||||
|
top.polyPath += "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath;
|
||||||
|
QFile(top.trashPath).rename(top.polyPath);
|
||||||
|
|
||||||
|
m_instanceGroupIndex[top.id] = top.groupName;
|
||||||
|
m_groupNameCache.insert(top.groupName);
|
||||||
|
|
||||||
|
saveGroupList();
|
||||||
|
emit instancesChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void InstanceList::deleteInstance(const InstanceId& id)
|
void InstanceList::deleteInstance(const InstanceId& id)
|
||||||
{
|
{
|
||||||
auto inst = getInstanceById(id);
|
auto inst = getInstanceById(id);
|
||||||
if(!inst)
|
if (!inst) {
|
||||||
{
|
|
||||||
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_instanceGroupIndex.remove(id))
|
if (m_instanceGroupIndex.remove(id)) {
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Will delete instance" << id;
|
qDebug() << "Will delete instance" << id;
|
||||||
if(!FS::deletePath(inst->instanceRoot()))
|
if (!FS::deletePath(inst->instanceRoot())) {
|
||||||
{
|
|
||||||
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
|
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -334,11 +367,9 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &
|
|||||||
{
|
{
|
||||||
QMap<InstanceId, InstanceLocator> out;
|
QMap<InstanceId, InstanceLocator> out;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(auto & item: list)
|
for (auto& item : list) {
|
||||||
{
|
|
||||||
auto id = item->id();
|
auto id = item->id();
|
||||||
if(out.contains(id))
|
if (out.contains(id)) {
|
||||||
{
|
|
||||||
qWarning() << "Duplicate ID" << id << "in instance list";
|
qWarning() << "Duplicate ID" << id << "in instance list";
|
||||||
}
|
}
|
||||||
out[id] = std::make_pair(item, i);
|
out[id] = std::make_pair(item, i);
|
||||||
@ -352,19 +383,16 @@ QList< InstanceId > InstanceList::discoverInstances()
|
|||||||
qDebug() << "Discovering instances in" << m_instDir;
|
qDebug() << "Discovering instances in" << m_instDir;
|
||||||
QList<InstanceId> out;
|
QList<InstanceId> out;
|
||||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
||||||
while (iter.hasNext())
|
while (iter.hasNext()) {
|
||||||
{
|
|
||||||
QString subDir = iter.next();
|
QString subDir = iter.next();
|
||||||
QFileInfo dirInfo(subDir);
|
QFileInfo dirInfo(subDir);
|
||||||
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
||||||
continue;
|
continue;
|
||||||
// if it is a symlink, ignore it if it goes to the instance folder
|
// if it is a symlink, ignore it if it goes to the instance folder
|
||||||
if(dirInfo.isSymLink())
|
if (dirInfo.isSymLink()) {
|
||||||
{
|
|
||||||
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
||||||
QFileInfo instDirInfo(m_instDir);
|
QFileInfo instDirInfo(m_instDir);
|
||||||
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
|
if (targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) {
|
||||||
{
|
|
||||||
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -388,74 +416,56 @@ InstanceList::InstListError InstanceList::loadList()
|
|||||||
|
|
||||||
QList<InstancePtr> newList;
|
QList<InstancePtr> newList;
|
||||||
|
|
||||||
for(auto & id: discoverInstances())
|
for (auto& id : discoverInstances()) {
|
||||||
{
|
if (existingIds.contains(id)) {
|
||||||
if(existingIds.contains(id))
|
|
||||||
{
|
|
||||||
auto instPair = existingIds[id];
|
auto instPair = existingIds[id];
|
||||||
existingIds.remove(id);
|
existingIds.remove(id);
|
||||||
qDebug() << "Should keep and soft-reload" << id;
|
qDebug() << "Should keep and soft-reload" << id;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
InstancePtr instPtr = loadInstance(id);
|
InstancePtr instPtr = loadInstance(id);
|
||||||
if(instPtr)
|
if (instPtr) {
|
||||||
{
|
|
||||||
newList.append(instPtr);
|
newList.append(instPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
||||||
if(!existingIds.isEmpty())
|
if (!existingIds.isEmpty()) {
|
||||||
{
|
|
||||||
// get the list of removed instances and sort it by their original index, from last to first
|
// get the list of removed instances and sort it by their original index, from last to first
|
||||||
auto deadList = existingIds.values();
|
auto deadList = existingIds.values();
|
||||||
auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool
|
auto orderSortPredicate = [](const InstanceLocator& a, const InstanceLocator& b) -> bool { return a.second > b.second; };
|
||||||
{
|
|
||||||
return a.second > b.second;
|
|
||||||
};
|
|
||||||
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
|
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
|
||||||
// remove the contiguous ranges of rows
|
// remove the contiguous ranges of rows
|
||||||
int front_bookmark = -1;
|
int front_bookmark = -1;
|
||||||
int back_bookmark = -1;
|
int back_bookmark = -1;
|
||||||
int currentItem = -1;
|
int currentItem = -1;
|
||||||
auto removeNow = [&]()
|
auto removeNow = [&]() {
|
||||||
{
|
|
||||||
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
||||||
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
front_bookmark = -1;
|
front_bookmark = -1;
|
||||||
back_bookmark = currentItem;
|
back_bookmark = currentItem;
|
||||||
};
|
};
|
||||||
for(auto & removedItem: deadList)
|
for (auto& removedItem : deadList) {
|
||||||
{
|
|
||||||
auto instPtr = removedItem.first;
|
auto instPtr = removedItem.first;
|
||||||
instPtr->invalidate();
|
instPtr->invalidate();
|
||||||
currentItem = removedItem.second;
|
currentItem = removedItem.second;
|
||||||
if(back_bookmark == -1)
|
if (back_bookmark == -1) {
|
||||||
{
|
|
||||||
// no bookmark yet
|
// no bookmark yet
|
||||||
back_bookmark = currentItem;
|
back_bookmark = currentItem;
|
||||||
}
|
} else if (currentItem == front_bookmark - 1) {
|
||||||
else if(currentItem == front_bookmark - 1)
|
|
||||||
{
|
|
||||||
// part of contiguous sequence, continue
|
// part of contiguous sequence, continue
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// seam between previous and current item
|
// seam between previous and current item
|
||||||
removeNow();
|
removeNow();
|
||||||
}
|
}
|
||||||
front_bookmark = currentItem;
|
front_bookmark = currentItem;
|
||||||
}
|
}
|
||||||
if(back_bookmark != -1)
|
if (back_bookmark != -1) {
|
||||||
{
|
|
||||||
removeNow();
|
removeNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(newList.size())
|
if (newList.size()) {
|
||||||
{
|
|
||||||
add(newList);
|
add(newList);
|
||||||
}
|
}
|
||||||
m_dirty = false;
|
m_dirty = false;
|
||||||
@ -466,16 +476,14 @@ InstanceList::InstListError InstanceList::loadList()
|
|||||||
void InstanceList::updateTotalPlayTime()
|
void InstanceList::updateTotalPlayTime()
|
||||||
{
|
{
|
||||||
totalPlayTime = 0;
|
totalPlayTime = 0;
|
||||||
for(auto const& itr : m_instances)
|
for (auto const& itr : m_instances) {
|
||||||
{
|
|
||||||
totalPlayTime += itr.get()->totalTimePlayed();
|
totalPlayTime += itr.get()->totalTimePlayed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstanceList::saveNow()
|
void InstanceList::saveNow()
|
||||||
{
|
{
|
||||||
for(auto & item: m_instances)
|
for (auto& item : m_instances) {
|
||||||
{
|
|
||||||
item->saveNow();
|
item->saveNow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -484,8 +492,7 @@ void InstanceList::add(const QList<InstancePtr> &t)
|
|||||||
{
|
{
|
||||||
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
||||||
m_instances.append(t);
|
m_instances.append(t);
|
||||||
for(auto & ptr : t)
|
for (auto& ptr : t) {
|
||||||
{
|
|
||||||
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
|
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
|
||||||
}
|
}
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
@ -493,14 +500,12 @@ void InstanceList::add(const QList<InstancePtr> &t)
|
|||||||
|
|
||||||
void InstanceList::resumeWatch()
|
void InstanceList::resumeWatch()
|
||||||
{
|
{
|
||||||
if(m_watchLevel > 0)
|
if (m_watchLevel > 0) {
|
||||||
{
|
|
||||||
qWarning() << "Bad suspend level resume in instance list";
|
qWarning() << "Bad suspend level resume in instance list";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_watchLevel++;
|
m_watchLevel++;
|
||||||
if(m_watchLevel > 0 && m_dirty)
|
if (m_watchLevel > 0 && m_dirty) {
|
||||||
{
|
|
||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -513,8 +518,7 @@ void InstanceList::suspendWatch()
|
|||||||
void InstanceList::providerUpdated()
|
void InstanceList::providerUpdated()
|
||||||
{
|
{
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
if(m_watchLevel == 1)
|
if (m_watchLevel == 1) {
|
||||||
{
|
|
||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -523,10 +527,8 @@ InstancePtr InstanceList::getInstanceById(QString instId) const
|
|||||||
{
|
{
|
||||||
if (instId.isEmpty())
|
if (instId.isEmpty())
|
||||||
return InstancePtr();
|
return InstancePtr();
|
||||||
for(auto & inst: m_instances)
|
for (auto& inst : m_instances) {
|
||||||
{
|
if (inst->id() == instId) {
|
||||||
if (inst->id() == instId)
|
|
||||||
{
|
|
||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -541,10 +543,8 @@ QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
|
|||||||
int InstanceList::getInstIndex(BaseInstance* inst) const
|
int InstanceList::getInstIndex(BaseInstance* inst) const
|
||||||
{
|
{
|
||||||
int count = m_instances.count();
|
int count = m_instances.count();
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++) {
|
||||||
{
|
if (inst == m_instances[i].get()) {
|
||||||
if (inst == m_instances[i].get())
|
|
||||||
{
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -554,8 +554,7 @@ int InstanceList::getInstIndex(BaseInstance *inst) const
|
|||||||
void InstanceList::propertiesChanged(BaseInstance* inst)
|
void InstanceList::propertiesChanged(BaseInstance* inst)
|
||||||
{
|
{
|
||||||
int i = getInstIndex(inst);
|
int i = getInstIndex(inst);
|
||||||
if (i != -1)
|
if (i != -1) {
|
||||||
{
|
|
||||||
emit dataChanged(index(i), index(i));
|
emit dataChanged(index(i), index(i));
|
||||||
updateTotalPlayTime();
|
updateTotalPlayTime();
|
||||||
}
|
}
|
||||||
@ -563,8 +562,7 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
|
|||||||
|
|
||||||
InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
||||||
{
|
{
|
||||||
if(!m_groupsLoaded)
|
if (!m_groupsLoaded) {
|
||||||
{
|
|
||||||
loadGroupList();
|
loadGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,34 +590,28 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
|||||||
void InstanceList::saveGroupList()
|
void InstanceList::saveGroupList()
|
||||||
{
|
{
|
||||||
qDebug() << "Will save group list now.";
|
qDebug() << "Will save group list now.";
|
||||||
if(!m_instancesProbed)
|
if (!m_instancesProbed) {
|
||||||
{
|
|
||||||
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
|
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
WatchLock foo(m_watcher, m_instDir);
|
WatchLock foo(m_watcher, m_instDir);
|
||||||
QString groupFileName = m_instDir + "/instgroups.json";
|
QString groupFileName = m_instDir + "/instgroups.json";
|
||||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||||
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++)
|
for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) {
|
||||||
{
|
|
||||||
QString id = iter.key();
|
QString id = iter.key();
|
||||||
QString group = iter.value();
|
QString group = iter.value();
|
||||||
if (group.isEmpty())
|
if (group.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
if(!instanceSet.contains(id))
|
if (!instanceSet.contains(id)) {
|
||||||
{
|
|
||||||
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
|
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reverseGroupMap.count(group))
|
if (!reverseGroupMap.count(group)) {
|
||||||
{
|
|
||||||
QSet<QString> set;
|
QSet<QString> set;
|
||||||
set.insert(id);
|
set.insert(id);
|
||||||
reverseGroupMap[group] = set;
|
reverseGroupMap[group] = set;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
QSet<QString>& set = reverseGroupMap[group];
|
QSet<QString>& set = reverseGroupMap[group];
|
||||||
set.insert(id);
|
set.insert(id);
|
||||||
}
|
}
|
||||||
@ -627,15 +619,13 @@ void InstanceList::saveGroupList()
|
|||||||
QJsonObject toplevel;
|
QJsonObject toplevel;
|
||||||
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
||||||
QJsonObject groupsArr;
|
QJsonObject groupsArr;
|
||||||
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
|
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++) {
|
||||||
{
|
|
||||||
auto list = iter.value();
|
auto list = iter.value();
|
||||||
auto name = iter.key();
|
auto name = iter.key();
|
||||||
QJsonObject groupObj;
|
QJsonObject groupObj;
|
||||||
QJsonArray instanceArr;
|
QJsonArray instanceArr;
|
||||||
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
|
groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
|
||||||
for (auto item : list)
|
for (auto item : list) {
|
||||||
{
|
|
||||||
instanceArr.append(QJsonValue(item));
|
instanceArr.append(QJsonValue(item));
|
||||||
}
|
}
|
||||||
groupObj.insert("instances", instanceArr);
|
groupObj.insert("instances", instanceArr);
|
||||||
@ -643,13 +633,10 @@ void InstanceList::saveGroupList()
|
|||||||
}
|
}
|
||||||
toplevel.insert("groups", groupsArr);
|
toplevel.insert("groups", groupsArr);
|
||||||
QJsonDocument doc(toplevel);
|
QJsonDocument doc(toplevel);
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
FS::write(groupFileName, doc.toJson());
|
FS::write(groupFileName, doc.toJson());
|
||||||
qDebug() << "Group list saved.";
|
qDebug() << "Group list saved.";
|
||||||
}
|
} catch (const FS::FileSystemException& e) {
|
||||||
catch (const FS::FileSystemException &e)
|
|
||||||
{
|
|
||||||
qCritical() << "Failed to write instance group file :" << e.cause();
|
qCritical() << "Failed to write instance group file :" << e.cause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -665,12 +652,9 @@ void InstanceList::loadGroupList()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
QByteArray jsonData;
|
QByteArray jsonData;
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
jsonData = FS::read(groupFileName);
|
jsonData = FS::read(groupFileName);
|
||||||
}
|
} catch (const FS::FileSystemException& e) {
|
||||||
catch (const FS::FileSystemException &e)
|
|
||||||
{
|
|
||||||
qCritical() << "Failed to read instance group file :" << e.cause();
|
qCritical() << "Failed to read instance group file :" << e.cause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -679,8 +663,7 @@ void InstanceList::loadGroupList()
|
|||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
||||||
|
|
||||||
// if the json was bad, fail
|
// if the json was bad, fail
|
||||||
if (error.error != QJsonParseError::NoError)
|
if (error.error != QJsonParseError::NoError) {
|
||||||
{
|
|
||||||
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
||||||
.arg(error.errorString(), QString::number(error.offset))
|
.arg(error.errorString(), QString::number(error.offset))
|
||||||
.toUtf8();
|
.toUtf8();
|
||||||
@ -688,8 +671,7 @@ void InstanceList::loadGroupList()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if the root of the json wasn't an object, fail
|
// if the root of the json wasn't an object, fail
|
||||||
if (!jsonDoc.isObject())
|
if (!jsonDoc.isObject()) {
|
||||||
{
|
|
||||||
qWarning() << "Invalid group file. Root entry should be an object.";
|
qWarning() << "Invalid group file. Root entry should be an object.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -701,8 +683,7 @@ void InstanceList::loadGroupList()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Get the groups. if it's not an object, fail
|
// Get the groups. if it's not an object, fail
|
||||||
if (!rootObj.value("groups").isObject())
|
if (!rootObj.value("groups").isObject()) {
|
||||||
{
|
|
||||||
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -712,21 +693,20 @@ void InstanceList::loadGroupList()
|
|||||||
|
|
||||||
// Iterate through all the groups.
|
// Iterate through all the groups.
|
||||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
|
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) {
|
||||||
{
|
|
||||||
QString groupName = iter.key();
|
QString groupName = iter.key();
|
||||||
|
|
||||||
// If not an object, complain and skip to the next one.
|
// If not an object, complain and skip to the next one.
|
||||||
if (!iter.value().isObject())
|
if (!iter.value().isObject()) {
|
||||||
{
|
|
||||||
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject groupObj = iter.value().toObject();
|
QJsonObject groupObj = iter.value().toObject();
|
||||||
if (!groupObj.value("instances").isArray())
|
if (!groupObj.value("instances").isArray()) {
|
||||||
{
|
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.")
|
||||||
qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.").arg(groupName).toUtf8();
|
.arg(groupName)
|
||||||
|
.toUtf8();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,8 +721,7 @@ void InstanceList::loadGroupList()
|
|||||||
// Iterate through the list of instances in the group.
|
// Iterate through the list of instances in the group.
|
||||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||||
|
|
||||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
|
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) {
|
||||||
{
|
|
||||||
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
m_instanceGroupIndex[(*iter2).toString()] = groupName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -760,10 +739,8 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
|
|||||||
void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
|
void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
|
||||||
{
|
{
|
||||||
QString newInstDir = QDir(value.toString()).canonicalPath();
|
QString newInstDir = QDir(value.toString()).canonicalPath();
|
||||||
if(newInstDir != m_instDir)
|
if (newInstDir != m_instDir) {
|
||||||
{
|
if (m_groupsLoaded) {
|
||||||
if(m_groupsLoaded)
|
|
||||||
{
|
|
||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
m_instDir = newInstDir;
|
m_instDir = newInstDir;
|
||||||
@ -783,18 +760,13 @@ void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
|
|||||||
saveGroupList();
|
saveGroupList();
|
||||||
}
|
}
|
||||||
|
|
||||||
class InstanceStaging : public Task
|
class InstanceStaging : public Task {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
const unsigned minBackoff = 1;
|
const unsigned minBackoff = 1;
|
||||||
const unsigned maxBackoff = 16;
|
const unsigned maxBackoff = 16;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InstanceStaging (
|
InstanceStaging(InstanceList* parent, Task* child, const QString& stagingPath, const QString& instanceName, const QString& groupName)
|
||||||
InstanceList * parent,
|
|
||||||
Task * child,
|
|
||||||
const QString & stagingPath,
|
|
||||||
const QString& instanceName,
|
|
||||||
const QString& groupName )
|
|
||||||
: backoff(minBackoff, maxBackoff)
|
: backoff(minBackoff, maxBackoff)
|
||||||
{
|
{
|
||||||
m_parent = parent;
|
m_parent = parent;
|
||||||
@ -812,47 +784,36 @@ public:
|
|||||||
|
|
||||||
virtual ~InstanceStaging(){};
|
virtual ~InstanceStaging(){};
|
||||||
|
|
||||||
|
|
||||||
// FIXME/TODO: add ability to abort during instance commit retries
|
// FIXME/TODO: add ability to abort during instance commit retries
|
||||||
bool abort() override
|
bool abort() override
|
||||||
{
|
{
|
||||||
if(m_child && m_child->canAbort())
|
if (m_child && m_child->canAbort()) {
|
||||||
{
|
|
||||||
return m_child->abort();
|
return m_child->abort();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool canAbort() const override
|
bool canAbort() const override
|
||||||
{
|
{
|
||||||
if(m_child && m_child->canAbort())
|
if (m_child && m_child->canAbort()) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() override
|
virtual void executeTask() override { m_child->start(); }
|
||||||
{
|
QStringList warnings() const override { return m_child->warnings(); }
|
||||||
m_child->start();
|
|
||||||
}
|
|
||||||
QStringList warnings() const override
|
|
||||||
{
|
|
||||||
return m_child->warnings();
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void childSucceded()
|
void childSucceded()
|
||||||
{
|
{
|
||||||
unsigned sleepTime = backoff();
|
unsigned sleepTime = backoff();
|
||||||
if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName))
|
if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) {
|
||||||
{
|
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// we actually failed, retry?
|
// we actually failed, retry?
|
||||||
if(sleepTime == maxBackoff)
|
if (sleepTime == maxBackoff) {
|
||||||
{
|
|
||||||
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -895,8 +856,7 @@ QString InstanceList::getStagedInstancePath()
|
|||||||
QString relPath = FS::PathCombine(tempDir, key);
|
QString relPath = FS::PathCombine(tempDir, key);
|
||||||
QDir rootPath(m_instDir);
|
QDir rootPath(m_instDir);
|
||||||
auto path = FS::PathCombine(m_instDir, relPath);
|
auto path = FS::PathCombine(m_instDir, relPath);
|
||||||
if(!rootPath.mkpath(relPath))
|
if (!rootPath.mkpath(relPath)) {
|
||||||
{
|
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
@ -913,8 +873,7 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst
|
|||||||
{
|
{
|
||||||
WatchLock lock(m_watcher, m_instDir);
|
WatchLock lock(m_watcher, m_instDir);
|
||||||
QString destination = FS::PathCombine(m_instDir, instID);
|
QString destination = FS::PathCombine(m_instDir, instID);
|
||||||
if(!dir.rename(path, destination))
|
if (!dir.rename(path, destination)) {
|
||||||
{
|
|
||||||
qWarning() << "Failed to move" << path << "to" << destination;
|
qWarning() << "Failed to move" << path << "to" << destination;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -933,7 +892,8 @@ bool InstanceList::destroyStagingPath(const QString& keyPath)
|
|||||||
return FS::deletePath(keyPath);
|
return FS::deletePath(keyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
int InstanceList::getTotalPlayTime() {
|
int InstanceList::getTotalPlayTime()
|
||||||
|
{
|
||||||
updateTotalPlayTime();
|
updateTotalPlayTime();
|
||||||
return totalPlayTime;
|
return totalPlayTime;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QStack>
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
|
|
||||||
@ -46,6 +48,12 @@ enum class GroupsState
|
|||||||
Dirty
|
Dirty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TrashHistoryItem {
|
||||||
|
QString id;
|
||||||
|
QString polyPath;
|
||||||
|
QString trashPath;
|
||||||
|
QString groupName;
|
||||||
|
};
|
||||||
|
|
||||||
class InstanceList : public QAbstractListModel
|
class InstanceList : public QAbstractListModel
|
||||||
{
|
{
|
||||||
@ -102,6 +110,9 @@ public:
|
|||||||
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
||||||
|
|
||||||
void deleteGroup(const GroupId & name);
|
void deleteGroup(const GroupId & name);
|
||||||
|
bool trashInstance(const InstanceId &id);
|
||||||
|
bool trashedSomething();
|
||||||
|
void undoTrashInstance();
|
||||||
void deleteInstance(const InstanceId & id);
|
void deleteInstance(const InstanceId & id);
|
||||||
|
|
||||||
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
||||||
@ -180,4 +191,6 @@ private:
|
|||||||
QSet<InstanceId> instanceSet;
|
QSet<InstanceId> instanceSet;
|
||||||
bool m_groupsLoaded = false;
|
bool m_groupsLoaded = false;
|
||||||
bool m_instancesProbed = false;
|
bool m_instancesProbed = false;
|
||||||
|
|
||||||
|
QStack<TrashHistoryItem> m_trashHistory;
|
||||||
};
|
};
|
||||||
|
@ -252,6 +252,9 @@ public:
|
|||||||
TranslatedAction actionViewInstanceFolder;
|
TranslatedAction actionViewInstanceFolder;
|
||||||
TranslatedAction actionViewCentralModsFolder;
|
TranslatedAction actionViewCentralModsFolder;
|
||||||
|
|
||||||
|
QMenu * editMenu = nullptr;
|
||||||
|
TranslatedAction actionUndoTrashInstance;
|
||||||
|
|
||||||
QMenu * helpMenu = nullptr;
|
QMenu * helpMenu = nullptr;
|
||||||
TranslatedToolButton helpMenuButton;
|
TranslatedToolButton helpMenuButton;
|
||||||
TranslatedAction actionReportBug;
|
TranslatedAction actionReportBug;
|
||||||
@ -335,6 +338,14 @@ public:
|
|||||||
actionSettings->setShortcut(QKeySequence::Preferences);
|
actionSettings->setShortcut(QKeySequence::Preferences);
|
||||||
all_actions.append(&actionSettings);
|
all_actions.append(&actionSettings);
|
||||||
|
|
||||||
|
actionUndoTrashInstance = TranslatedAction(MainWindow);
|
||||||
|
connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), MainWindow, SLOT(undoTrashInstance()));
|
||||||
|
actionUndoTrashInstance->setObjectName(QStringLiteral("actionUndoTrashInstance"));
|
||||||
|
actionUndoTrashInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Undo Last Instance Deletion"));
|
||||||
|
actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
|
||||||
|
actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z"));
|
||||||
|
all_actions.append(&actionUndoTrashInstance);
|
||||||
|
|
||||||
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
|
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
|
||||||
actionReportBug = TranslatedAction(MainWindow);
|
actionReportBug = TranslatedAction(MainWindow);
|
||||||
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
|
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
|
||||||
@ -508,6 +519,9 @@ public:
|
|||||||
fileMenu->addSeparator();
|
fileMenu->addSeparator();
|
||||||
fileMenu->addAction(actionSettings);
|
fileMenu->addAction(actionSettings);
|
||||||
|
|
||||||
|
editMenu = menuBar->addMenu(tr("&Edit"));
|
||||||
|
editMenu->addAction(actionUndoTrashInstance);
|
||||||
|
|
||||||
viewMenu = menuBar->addMenu(tr("&View"));
|
viewMenu = menuBar->addMenu(tr("&View"));
|
||||||
viewMenu->setSeparatorsCollapsible(false);
|
viewMenu->setSeparatorsCollapsible(false);
|
||||||
viewMenu->addAction(actionCAT);
|
viewMenu->addAction(actionCAT);
|
||||||
@ -732,9 +746,10 @@ public:
|
|||||||
|
|
||||||
actionDeleteInstance = TranslatedAction(MainWindow);
|
actionDeleteInstance = TranslatedAction(MainWindow);
|
||||||
actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance"));
|
actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance"));
|
||||||
actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance..."));
|
actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance"));
|
||||||
actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));
|
actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));
|
||||||
actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete});
|
actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete});
|
||||||
|
actionDeleteInstance->setAutoRepeat(false);
|
||||||
all_actions.append(&actionDeleteInstance);
|
all_actions.append(&actionDeleteInstance);
|
||||||
|
|
||||||
actionCopyInstance = TranslatedAction(MainWindow);
|
actionCopyInstance = TranslatedAction(MainWindow);
|
||||||
@ -1150,6 +1165,11 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
|
|||||||
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
|
connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
|
||||||
actions.append(actionDeleteGroup);
|
actions.append(actionDeleteGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QAction *actionUndoTrashInstance = new QAction("Undo last trash instance", this);
|
||||||
|
connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), SLOT(undoTrashInstance()));
|
||||||
|
actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
|
||||||
|
actions.append(actionUndoTrashInstance);
|
||||||
}
|
}
|
||||||
QMenu myMenu;
|
QMenu myMenu;
|
||||||
myMenu.addActions(actions);
|
myMenu.addActions(actions);
|
||||||
@ -1832,6 +1852,11 @@ void MainWindow::deleteGroup()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::undoTrashInstance()
|
||||||
|
{
|
||||||
|
APPLICATION->instances()->undoTrashInstance();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionViewInstanceFolder_triggered()
|
void MainWindow::on_actionViewInstanceFolder_triggered()
|
||||||
{
|
{
|
||||||
QString str = APPLICATION->settings()->get("InstanceDir").toString();
|
QString str = APPLICATION->settings()->get("InstanceDir").toString();
|
||||||
@ -1957,7 +1982,12 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto id = m_selectedInstance->id();
|
auto id = m_selectedInstance->id();
|
||||||
|
if (APPLICATION->instances()->trashInstance(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto response = CustomMessageBox::selectable(
|
auto response = CustomMessageBox::selectable(
|
||||||
this,
|
this,
|
||||||
tr("CAREFUL!"),
|
tr("CAREFUL!"),
|
||||||
|
@ -145,6 +145,7 @@ private slots:
|
|||||||
void on_actionDeleteInstance_triggered();
|
void on_actionDeleteInstance_triggered();
|
||||||
|
|
||||||
void deleteGroup();
|
void deleteGroup();
|
||||||
|
void undoTrashInstance();
|
||||||
|
|
||||||
void on_actionExportInstance_triggered();
|
void on_actionExportInstance_triggered();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user