mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-07 16:59:07 +00:00
UI updates
- add import dialog this is more complex than I thought - added scope exit grammar sugar - main dialog is now linked to import dialog - importer citra file path is fixed - importer now reports maximum size - file util is improved with GetDirectoryTreeSize
This commit is contained in:
@@ -8,6 +8,7 @@ add_library(common STATIC
|
|||||||
file_util.h
|
file_util.h
|
||||||
logging/log.h
|
logging/log.h
|
||||||
misc.cpp
|
misc.cpp
|
||||||
|
scope_exit.h
|
||||||
string_util.cpp
|
string_util.cpp
|
||||||
string_util.h
|
string_util.h
|
||||||
swap.h
|
swap.h
|
||||||
|
|||||||
@@ -367,6 +367,37 @@ u64 GetSize(FILE* f) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 GetDirectoryTreeSize(const std::string& path, unsigned int recursion) {
|
||||||
|
if (!IsDirectory(path)) {
|
||||||
|
LOG_ERROR(Common_FileSystem, "failed {}: is a file", path);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string real_path = path;
|
||||||
|
if (real_path.back() != '/' && real_path.back() != '\\') {
|
||||||
|
real_path += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 total_size = 0;
|
||||||
|
const auto callback = [recursion, &total_size](u64* /*num_entries_out*/,
|
||||||
|
const std::string& directory,
|
||||||
|
const std::string& virtual_name) -> bool {
|
||||||
|
if (IsDirectory(directory + virtual_name)) {
|
||||||
|
if (recursion == 0) {
|
||||||
|
LOG_WARNING(Common_FileSystem, "directory tree too deep");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
total_size += GetDirectoryTreeSize(directory + virtual_name, recursion - 1);
|
||||||
|
} else { // is a file
|
||||||
|
total_size += GetSize(directory + virtual_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
return ForeachDirectoryEntry(nullptr, real_path, callback) ? total_size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool CreateEmptyFile(const std::string& filename) {
|
bool CreateEmptyFile(const std::string& filename) {
|
||||||
LOG_TRACE(Common_Filesystem, "{}", filename);
|
LOG_TRACE(Common_Filesystem, "{}", filename);
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ u64 GetSize(const int fd);
|
|||||||
// Overloaded GetSize, accepts FILE*
|
// Overloaded GetSize, accepts FILE*
|
||||||
u64 GetSize(FILE* f);
|
u64 GetSize(FILE* f);
|
||||||
|
|
||||||
|
// Returns the size of a directory tree
|
||||||
|
u64 GetDirectoryTreeSize(const std::string& path, unsigned int recursion = 256);
|
||||||
|
|
||||||
// Returns true if successful, or path already exists.
|
// Returns true if successful, or path already exists.
|
||||||
bool CreateDir(const std::string& filename);
|
bool CreateDir(const std::string& filename);
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ void PrintLog(std::FILE* f, const std::string& log_class, const std::string& lev
|
|||||||
us / 1000000.0, real_class, level, file, line, func, args...);
|
us / 1000000.0, real_class, level, file, line, func, args...);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cerr << "FMT failed with exception" << std::endl;
|
std::cerr << "(unexpected) fmt failed with exception" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2014 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename Func>
|
||||||
|
struct ScopeExitHelper {
|
||||||
|
explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {}
|
||||||
|
~ScopeExitHelper() {
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
|
||||||
|
Func func;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Func>
|
||||||
|
ScopeExitHelper<Func> ScopeExit(Func&& func) {
|
||||||
|
return ScopeExitHelper<Func>(std::forward<Func>(func));
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This macro allows you to conveniently specify a block of code that will run on scope exit. Handy
|
||||||
|
* for doing ad-hoc clean-up tasks in a function with multiple returns.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* \code
|
||||||
|
* const int saved_val = g_foo;
|
||||||
|
* g_foo = 55;
|
||||||
|
* SCOPE_EXIT({ g_foo = saved_val; });
|
||||||
|
*
|
||||||
|
* if (Bar()) {
|
||||||
|
* return 0;
|
||||||
|
* } else {
|
||||||
|
* return 20;
|
||||||
|
* }
|
||||||
|
* \endcode
|
||||||
|
*/
|
||||||
|
#define SCOPE_EXIT(body) auto CONCAT2(scope_exit_helper_, __LINE__) = detail::ScopeExit([&]() body)
|
||||||
+44
-24
@@ -80,7 +80,10 @@ bool SDMCImporter::ImportTitle(u64 id) {
|
|||||||
}
|
}
|
||||||
return decryptor->DecryptAndWriteFile(
|
return decryptor->DecryptAndWriteFile(
|
||||||
"/" + path + virtual_name,
|
"/" + path + virtual_name,
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + path + virtual_name);
|
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
|
||||||
|
"Nintendo "
|
||||||
|
"3DS/00000000000000000000000000000000/00000000000000000000000000000000/" +
|
||||||
|
path + virtual_name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +94,9 @@ bool SDMCImporter::ImportSavegame(u64 id) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return save.Extract(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + path);
|
return save.Extract(
|
||||||
|
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
|
||||||
|
"Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000/" + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDMCImporter::ImportExtdata(u64 id) {
|
bool SDMCImporter::ImportExtdata(u64 id) {
|
||||||
@@ -101,7 +106,9 @@ bool SDMCImporter::ImportExtdata(u64 id) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return extdata.Extract(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + path);
|
return extdata.Extract(
|
||||||
|
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
|
||||||
|
"Nintendo 3DS/00000000000000000000000000000000/00000000000000000000000000000000/" + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDMCImporter::ImportSysdata(u64 id) {
|
bool SDMCImporter::ImportSysdata(u64 id) {
|
||||||
@@ -130,7 +137,7 @@ bool SDMCImporter::ImportSysdata(u64 id) {
|
|||||||
}
|
}
|
||||||
return FileUtil::Copy(
|
return FileUtil::Copy(
|
||||||
directory + virtual_name,
|
directory + virtual_name,
|
||||||
fmt::format("{}title/00040138/{}/content/{}",
|
fmt::format("{}00000000000000000000000000000000/title/00040138/{}/content/{}",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
|
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
|
||||||
(is_new_3ds ? "20000003" : "00000003"), virtual_name));
|
(is_new_3ds ? "20000003" : "00000003"), virtual_name));
|
||||||
});
|
});
|
||||||
@@ -167,16 +174,20 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
|||||||
nullptr, fmt::format("{}title/{:08x}/", sdmc_path, high_id),
|
nullptr, fmt::format("{}title/{:08x}/", sdmc_path, high_id),
|
||||||
[type, high_id, &out](u64* /*num_entries_out*/, const std::string& directory,
|
[type, high_id, &out](u64* /*num_entries_out*/, const std::string& directory,
|
||||||
const std::string& virtual_name) {
|
const std::string& virtual_name) {
|
||||||
if (!FileUtil::IsDirectory(directory + virtual_name)) {
|
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u64 id = (high_id << 32) + std::stoull(virtual_name, nullptr, 16);
|
const u64 id = (high_id << 32) + std::stoull(virtual_name, nullptr, 16);
|
||||||
const auto citra_path = fmt::format(
|
const auto citra_path = fmt::format(
|
||||||
"{}title/{:08x}/{}/", FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
|
"{}Nintendo "
|
||||||
high_id, virtual_name);
|
"3DS/00000000000000000000000000000000/00000000000000000000000000000000/title/"
|
||||||
|
"{:08x}/{}/",
|
||||||
|
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), high_id, virtual_name);
|
||||||
if (FileUtil::Exists(directory + virtual_name + "/content/")) {
|
if (FileUtil::Exists(directory + virtual_name + "/content/")) {
|
||||||
out.push_back({type, id, FileUtil::Exists(citra_path + "content/")});
|
out.push_back(
|
||||||
|
{type, id, FileUtil::Exists(citra_path + "content/"),
|
||||||
|
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/content/")});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != ContentType::Application) {
|
if (type != ContentType::Application) {
|
||||||
@@ -184,7 +195,8 @@ void SDMCImporter::ListTitle(std::vector<ContentSpecifier>& out) const {
|
|||||||
}
|
}
|
||||||
if (FileUtil::Exists(directory + virtual_name + "/data/")) {
|
if (FileUtil::Exists(directory + virtual_name + "/data/")) {
|
||||||
out.push_back(
|
out.push_back(
|
||||||
{ContentType::Savegame, id, FileUtil::Exists(citra_path + "data/")});
|
{ContentType::Savegame, id, FileUtil::Exists(citra_path + "data/"),
|
||||||
|
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/data/")});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -200,32 +212,38 @@ void SDMCImporter::ListExtdata(std::vector<ContentSpecifier>& out) const {
|
|||||||
nullptr, fmt::format("{}extdata/00000000/", config.sdmc_path),
|
nullptr, fmt::format("{}extdata/00000000/", config.sdmc_path),
|
||||||
[&out](u64* /*num_entries_out*/, const std::string& directory,
|
[&out](u64* /*num_entries_out*/, const std::string& directory,
|
||||||
const std::string& virtual_name) {
|
const std::string& virtual_name) {
|
||||||
if (!FileUtil::IsDirectory(directory + virtual_name)) {
|
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u64 id = std::stoull(virtual_name, nullptr, 16);
|
const u64 id = std::stoull(virtual_name, nullptr, 16);
|
||||||
const auto citra_path =
|
const auto citra_path =
|
||||||
fmt::format("{}extdata/00000000/{}",
|
fmt::format("{}Nintendo "
|
||||||
|
"3DS/00000000000000000000000000000000/00000000000000000000000000000000/"
|
||||||
|
"extdata/00000000/{}",
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), virtual_name);
|
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), virtual_name);
|
||||||
out.push_back({ContentType::Extdata, id, FileUtil::Exists(citra_path)});
|
out.push_back({ContentType::Extdata, id, FileUtil::Exists(citra_path),
|
||||||
|
FileUtil::GetDirectoryTreeSize(directory + virtual_name + "/")});
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDMCImporter::ListSysdata(std::vector<ContentSpecifier>& out) const {
|
void SDMCImporter::ListSysdata(std::vector<ContentSpecifier>& out) const {
|
||||||
#define CHECK_CONTENT(id, var_path, citra_path) \
|
#define CHECK_CONTENT(id, var_path, citra_path, display_name) \
|
||||||
if (!var_path.empty()) { \
|
if (!var_path.empty()) { \
|
||||||
out.push_back({ContentType::Sysdata, id, FileUtil::Exists(citra_path)}); \
|
out.push_back({ContentType::Sysdata, id, FileUtil::Exists(citra_path), \
|
||||||
|
FileUtil::GetSize(var_path), display_name}); \
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto sysdata_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
|
const auto sysdata_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
|
||||||
CHECK_CONTENT(0, config.bootrom_path, sysdata_path + BOOTROM9);
|
CHECK_CONTENT(0, config.bootrom_path, sysdata_path + BOOTROM9, BOOTROM9);
|
||||||
CHECK_CONTENT(2, config.seed_db_path, sysdata_path + SEED_DB);
|
CHECK_CONTENT(2, config.seed_db_path, sysdata_path + SEED_DB, SEED_DB);
|
||||||
CHECK_CONTENT(3, config.secret_sector_path, sysdata_path + SECRET_SECTOR);
|
CHECK_CONTENT(3, config.secret_sector_path, sysdata_path + SECRET_SECTOR, SECRET_SECTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef CHECK_CONTENT
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (config.safe_mode_firm_path.empty()) {
|
if (config.safe_mode_firm_path.empty()) {
|
||||||
break;
|
break;
|
||||||
@@ -240,13 +258,15 @@ void SDMCImporter::ListSysdata(std::vector<ContentSpecifier>& out) const {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto citra_path = fmt::format("{}title/00040138/{}/content/",
|
const auto citra_path = fmt::format(
|
||||||
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
|
"{}00000000000000000000000000000000/title/00040138/{}/content/",
|
||||||
(is_new ? "20000003" : "00000003"));
|
FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), (is_new ? "20000003" : "00000003"));
|
||||||
CHECK_CONTENT(1, config.safe_mode_firm_path, citra_path);
|
if (!config.safe_mode_firm_path.empty()) {
|
||||||
|
out.push_back({ContentType::Sysdata, 1, FileUtil::Exists(citra_path),
|
||||||
|
FileUtil::GetDirectoryTreeSize(config.safe_mode_firm_path),
|
||||||
|
"Safe mode firm"});
|
||||||
|
}
|
||||||
} while (0);
|
} while (0);
|
||||||
|
|
||||||
#undef CHECK_CONTENT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
||||||
@@ -288,7 +308,7 @@ std::vector<Config> LoadPresetConfig(std::string mount_point) {
|
|||||||
[&id_regex, &config_template, &out](u64* /*num_entries_out*/,
|
[&id_regex, &config_template, &out](u64* /*num_entries_out*/,
|
||||||
const std::string& directory,
|
const std::string& directory,
|
||||||
const std::string& virtual_name) {
|
const std::string& virtual_name) {
|
||||||
if (!FileUtil::IsDirectory(directory + virtual_name)) {
|
if (!FileUtil::IsDirectory(directory + virtual_name + "/")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ struct ContentSpecifier {
|
|||||||
ContentType type;
|
ContentType type;
|
||||||
u64 id;
|
u64 id;
|
||||||
bool already_exists; ///< Tells whether a file already exists in target path.
|
bool already_exists; ///< Tells whether a file already exists in target path.
|
||||||
|
u64 maximum_size; ///< The maximum size of the content. May be slightly bigger than real size.
|
||||||
|
std::string name; ///< Optional. The content's preferred display name.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ if (POLICY CMP0071)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(threeSD
|
add_executable(threeSD
|
||||||
|
import_dialog.cpp
|
||||||
|
import_dialog.h
|
||||||
|
import_dialog.ui
|
||||||
main.cpp
|
main.cpp
|
||||||
main.h
|
main.h
|
||||||
main.ui
|
main.ui
|
||||||
@@ -47,6 +50,9 @@ target_compile_definitions(threeSD PRIVATE
|
|||||||
|
|
||||||
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
|
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
|
||||||
-DQT_NO_URL_CAST_FROM_STRING
|
-DQT_NO_URL_CAST_FROM_STRING
|
||||||
|
|
||||||
|
# Disable automatic conversions from 8-bit strings (char *) to unicode QStrings
|
||||||
|
-DQT_NO_CAST_FROM_ASCII
|
||||||
)
|
)
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
// Copyright 2019 threeSD Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QStorageInfo>
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
#include "frontend/import_dialog.h"
|
||||||
|
#include "ui_import_dialog.h"
|
||||||
|
|
||||||
|
QString ReadableByteSize(qulonglong size) {
|
||||||
|
static const std::array<const char*, 6> units = {QT_TR_NOOP("B"), QT_TR_NOOP("KiB"),
|
||||||
|
QT_TR_NOOP("MiB"), QT_TR_NOOP("GiB"),
|
||||||
|
QT_TR_NOOP("TiB"), QT_TR_NOOP("PiB")};
|
||||||
|
if (size == 0)
|
||||||
|
return QStringLiteral("0");
|
||||||
|
int digit_groups = std::min<int>(static_cast<int>(std::log10(size) / std::log10(1024)),
|
||||||
|
static_cast<int>(units.size()));
|
||||||
|
return QStringLiteral("%L1 %2")
|
||||||
|
.arg(size / std::pow(1024, digit_groups), 0, 'f', 1)
|
||||||
|
.arg(QObject::tr(units[digit_groups], "ImportDialog"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportDialog::ImportDialog(QWidget* parent, const Core::Config& config)
|
||||||
|
: QDialog(parent), ui(std::make_unique<Ui::ImportDialog>()), user_path(config.user_path),
|
||||||
|
importer(config) {
|
||||||
|
|
||||||
|
ui->setupUi(this);
|
||||||
|
if (!importer.IsGood()) {
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Importer Error"),
|
||||||
|
tr("Failed to initalize the importer.\nRefer to the log for details."));
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
PopulateContent();
|
||||||
|
UpdateSizeDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportDialog::~ImportDialog() = default;
|
||||||
|
|
||||||
|
void ImportDialog::PopulateContent() {
|
||||||
|
contents = importer.ListContent();
|
||||||
|
ui->main->clear();
|
||||||
|
ui->main->setSortingEnabled(false);
|
||||||
|
|
||||||
|
const std::map<Core::ContentType, QString> content_type_map{
|
||||||
|
{Core::ContentType::Application, QStringLiteral("Application")},
|
||||||
|
{Core::ContentType::Update, QStringLiteral("Update")},
|
||||||
|
{Core::ContentType::DLC, QStringLiteral("DLC (Add-on Content)")},
|
||||||
|
{Core::ContentType::Savegame, QStringLiteral("Save Data")},
|
||||||
|
{Core::ContentType::Extdata, QStringLiteral("Extra Data")},
|
||||||
|
{Core::ContentType::Sysdata, QStringLiteral("System Data")},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& [type, name] : content_type_map) {
|
||||||
|
auto* checkBox = new QCheckBox();
|
||||||
|
checkBox->setText(name);
|
||||||
|
checkBox->setStyleSheet(QStringLiteral("margin-left:7px"));
|
||||||
|
checkBox->setTristate(true);
|
||||||
|
checkBox->setProperty("previousState", static_cast<int>(Qt::Unchecked));
|
||||||
|
|
||||||
|
auto* item = new QTreeWidgetItem;
|
||||||
|
item->setFirstColumnSpanned(true);
|
||||||
|
ui->main->invisibleRootItem()->addChild(item);
|
||||||
|
|
||||||
|
connect(checkBox, &QCheckBox::stateChanged, [this, checkBox, item](int state) {
|
||||||
|
SCOPE_EXIT({ checkBox->setProperty("previousState", state); });
|
||||||
|
|
||||||
|
if (program_trigger) {
|
||||||
|
program_trigger = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == Qt::PartiallyChecked) {
|
||||||
|
if (checkBox->property("previousState").toInt() == Qt::Unchecked) {
|
||||||
|
checkBox->setCheckState(static_cast<Qt::CheckState>(state = Qt::Checked));
|
||||||
|
} else {
|
||||||
|
checkBox->setCheckState(static_cast<Qt::CheckState>(state = Qt::Unchecked));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
program_trigger = true;
|
||||||
|
for (int i = 0; i < item->childCount(); ++i) {
|
||||||
|
static_cast<QCheckBox*>(ui->main->itemWidget(item->child(i), 0))
|
||||||
|
->setCheckState(static_cast<Qt::CheckState>(state));
|
||||||
|
}
|
||||||
|
program_trigger = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->main->setItemWidget(item, 0, checkBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& content : contents) {
|
||||||
|
auto* checkBox = new QCheckBox();
|
||||||
|
checkBox->setStyleSheet(QStringLiteral("margin-left:7px"));
|
||||||
|
|
||||||
|
auto* item = new QTreeWidgetItem{
|
||||||
|
{QString{},
|
||||||
|
content.name.empty() ? QStringLiteral("0x%1").arg(content.id, 16, 16, QLatin1Char('0'))
|
||||||
|
: QString::fromStdString(content.name),
|
||||||
|
ReadableByteSize(content.maximum_size),
|
||||||
|
content.already_exists ? QStringLiteral("Yes") : QStringLiteral("No")}};
|
||||||
|
|
||||||
|
ui->main->invisibleRootItem()->child(static_cast<int>(content.type))->addChild(item);
|
||||||
|
ui->main->setItemWidget(item, 0, checkBox);
|
||||||
|
|
||||||
|
connect(checkBox, &QCheckBox::stateChanged,
|
||||||
|
[this, item, size = content.maximum_size](int state) {
|
||||||
|
if (state == Qt::Checked) {
|
||||||
|
total_size += size;
|
||||||
|
} else {
|
||||||
|
total_size -= size;
|
||||||
|
}
|
||||||
|
UpdateSizeDisplay();
|
||||||
|
|
||||||
|
if (!program_trigger) {
|
||||||
|
UpdateItemCheckState(item->parent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->main->setSortingEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImportDialog::UpdateSizeDisplay() {
|
||||||
|
QStorageInfo storage(QString::fromStdString(user_path));
|
||||||
|
if (!storage.isValid() || !storage.isReady()) {
|
||||||
|
LOG_ERROR(Frontend, "Storage {} is not good", user_path);
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Bad Storage"),
|
||||||
|
tr("An error occured while trying to get available space for the storage.\nPlease "
|
||||||
|
"ensure that your SD card is well connected and try again."));
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->availableSpace->setText(
|
||||||
|
tr("Available Space: %1").arg(ReadableByteSize(storage.bytesAvailable())));
|
||||||
|
ui->totalSize->setText(tr("Total Size: %1").arg(ReadableByteSize(total_size)));
|
||||||
|
|
||||||
|
ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)
|
||||||
|
->setEnabled(total_size <= static_cast<u64>(storage.bytesAvailable()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImportDialog::UpdateItemCheckState(QTreeWidgetItem* item) {
|
||||||
|
bool has_checked = false, has_unchecked = false;
|
||||||
|
auto* item_checkBox = static_cast<QCheckBox*>(ui->main->itemWidget(item, 0));
|
||||||
|
for (int i = 0; i < item->childCount(); ++i) {
|
||||||
|
auto* checkBox = static_cast<QCheckBox*>(ui->main->itemWidget(item->child(i), 0));
|
||||||
|
if (checkBox->isChecked()) {
|
||||||
|
has_checked = true;
|
||||||
|
} else {
|
||||||
|
has_unchecked = true;
|
||||||
|
}
|
||||||
|
if (has_checked && has_unchecked) {
|
||||||
|
program_trigger = true;
|
||||||
|
item_checkBox->setCheckState(Qt::PartiallyChecked);
|
||||||
|
program_trigger = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
program_trigger = true;
|
||||||
|
if (has_checked) {
|
||||||
|
item_checkBox->setCheckState(Qt::Checked);
|
||||||
|
} else {
|
||||||
|
item_checkBox->setCheckState(Qt::Unchecked);
|
||||||
|
}
|
||||||
|
program_trigger = false;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2019 threeSD Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <QDialog>
|
||||||
|
#include "core/importer.h"
|
||||||
|
|
||||||
|
class QTreeWidgetItem;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ImportDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImportDialog : public QDialog {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ImportDialog(QWidget* parent, const Core::Config& config);
|
||||||
|
~ImportDialog() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void PopulateContent();
|
||||||
|
void UpdateSizeDisplay();
|
||||||
|
void UpdateItemCheckState(QTreeWidgetItem* item);
|
||||||
|
|
||||||
|
std::unique_ptr<Ui::ImportDialog> ui;
|
||||||
|
|
||||||
|
std::string user_path;
|
||||||
|
Core::SDMCImporter importer;
|
||||||
|
std::vector<Core::ContentSpecifier> contents;
|
||||||
|
u64 total_size = 0;
|
||||||
|
|
||||||
|
// HACK: To tell whether the checkbox state change is a programmatic trigger
|
||||||
|
// TODO: Is there a more elegant way of doing the same?
|
||||||
|
bool program_trigger = false;
|
||||||
|
};
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ImportDialog</class>
|
||||||
|
<widget class="QDialog" name="ImportDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>600</width>
|
||||||
|
<height>400</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Select Contents</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeWidget" name="main">
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Name</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Size</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Imported</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="availableSpace">
|
||||||
|
<property name="text">
|
||||||
|
<string>Available Space:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="totalSize">
|
||||||
|
<property name="text">
|
||||||
|
<string>Total Size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Ok|QDialogButtonBox::Cancel</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
+23
-2
@@ -7,6 +7,7 @@
|
|||||||
#include <QStorageInfo>
|
#include <QStorageInfo>
|
||||||
#include <qdevicewatcher.h>
|
#include <qdevicewatcher.h>
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
|
#include "frontend/import_dialog.h"
|
||||||
#include "frontend/main.h"
|
#include "frontend/main.h"
|
||||||
#include "ui_main.h"
|
#include "ui_main.h"
|
||||||
|
|
||||||
@@ -34,6 +35,9 @@ MainDialog::MainDialog(QWidget* parent) : QDialog(parent), ui(std::make_unique<U
|
|||||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) {
|
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) {
|
||||||
if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)) {
|
if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Reset)) {
|
||||||
LoadPresetConfig();
|
LoadPresetConfig();
|
||||||
|
} else if (button == ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)) {
|
||||||
|
ImportDialog dialog(this, GetCurrentConfig());
|
||||||
|
dialog.exec();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,10 +128,27 @@ void MainDialog::HideAdvanced() {
|
|||||||
adjustSize();
|
adjustSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Core::Config MainDialog::GetCurrentConfig() {
|
||||||
|
if (ui->customGroupBox->isVisible()) {
|
||||||
|
Core::Config config{
|
||||||
|
/*sdmc_path*/ ui->sdmcPath->text().toStdString(),
|
||||||
|
/*user_path*/ ui->userPath->text().toStdString(),
|
||||||
|
/*movable_sed_path*/ ui->movableSedPath->text().toStdString(),
|
||||||
|
/*bootrom_path*/ ui->bootrom9Path->text().toStdString(),
|
||||||
|
/*safe_mode_firm_path*/ ui->safeModeFirmPath->text().toStdString(),
|
||||||
|
/*seed_db_path*/ ui->seeddbPath->text().toStdString(),
|
||||||
|
/*secret_sector_path*/ ui->secretSectorPath->text().toStdString(),
|
||||||
|
};
|
||||||
|
return config;
|
||||||
|
} else {
|
||||||
|
return preset_config_list[ui->configSelect->currentIndex()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
// Init settings params
|
// Init settings params
|
||||||
QCoreApplication::setOrganizationName("zhaowenlan1779");
|
QCoreApplication::setOrganizationName(QStringLiteral("zhaowenlan1779"));
|
||||||
QCoreApplication::setApplicationName("threeSD");
|
QCoreApplication::setApplicationName(QStringLiteral("threeSD"));
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ private:
|
|||||||
void LoadPresetConfig();
|
void LoadPresetConfig();
|
||||||
void ShowAdvanced();
|
void ShowAdvanced();
|
||||||
void HideAdvanced();
|
void HideAdvanced();
|
||||||
|
Core::Config GetCurrentConfig();
|
||||||
|
|
||||||
std::vector<Core::Config> preset_config_list;
|
std::vector<Core::Config> preset_config_list;
|
||||||
std::unique_ptr<Ui::MainDialog> ui;
|
std::unique_ptr<Ui::MainDialog> ui;
|
||||||
|
|||||||
Reference in New Issue
Block a user