merge origin/develop
Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com>
This commit is contained in:
96
launcher/ui/dialogs/ChooseProviderDialog.cpp
Normal file
96
launcher/ui/dialogs/ChooseProviderDialog.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "ChooseProviderDialog.h"
|
||||
#include "ui_ChooseProviderDialog.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
|
||||
ChooseProviderDialog::ChooseProviderDialog(QWidget* parent, bool single_choice, bool allow_skipping)
|
||||
: QDialog(parent), ui(new Ui::ChooseProviderDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
addProviders();
|
||||
m_providers.button(0)->click();
|
||||
|
||||
connect(ui->skipOneButton, &QPushButton::clicked, this, &ChooseProviderDialog::skipOne);
|
||||
connect(ui->skipAllButton, &QPushButton::clicked, this, &ChooseProviderDialog::skipAll);
|
||||
|
||||
connect(ui->confirmOneButton, &QPushButton::clicked, this, &ChooseProviderDialog::confirmOne);
|
||||
connect(ui->confirmAllButton, &QPushButton::clicked, this, &ChooseProviderDialog::confirmAll);
|
||||
|
||||
if (single_choice) {
|
||||
ui->providersLayout->removeWidget(ui->skipAllButton);
|
||||
ui->providersLayout->removeWidget(ui->confirmAllButton);
|
||||
}
|
||||
|
||||
if (!allow_skipping) {
|
||||
ui->providersLayout->removeWidget(ui->skipOneButton);
|
||||
ui->providersLayout->removeWidget(ui->skipAllButton);
|
||||
}
|
||||
}
|
||||
|
||||
ChooseProviderDialog::~ChooseProviderDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void ChooseProviderDialog::setDescription(QString desc)
|
||||
{
|
||||
ui->explanationLabel->setText(desc);
|
||||
}
|
||||
|
||||
void ChooseProviderDialog::skipOne()
|
||||
{
|
||||
reject();
|
||||
}
|
||||
void ChooseProviderDialog::skipAll()
|
||||
{
|
||||
m_response.skip_all = true;
|
||||
reject();
|
||||
}
|
||||
|
||||
void ChooseProviderDialog::confirmOne()
|
||||
{
|
||||
m_response.chosen = getSelectedProvider();
|
||||
m_response.try_others = ui->tryOthersCheckbox->isChecked();
|
||||
accept();
|
||||
}
|
||||
void ChooseProviderDialog::confirmAll()
|
||||
{
|
||||
m_response.chosen = getSelectedProvider();
|
||||
m_response.confirm_all = true;
|
||||
m_response.try_others = ui->tryOthersCheckbox->isChecked();
|
||||
accept();
|
||||
}
|
||||
|
||||
auto ChooseProviderDialog::getSelectedProvider() const -> ModPlatform::Provider
|
||||
{
|
||||
return ModPlatform::Provider(m_providers.checkedId());
|
||||
}
|
||||
|
||||
void ChooseProviderDialog::addProviders()
|
||||
{
|
||||
int btn_index = 0;
|
||||
QRadioButton* btn;
|
||||
|
||||
for (auto& provider : { ModPlatform::Provider::MODRINTH, ModPlatform::Provider::FLAME }) {
|
||||
btn = new QRadioButton(ProviderCaps.readableName(provider), this);
|
||||
m_providers.addButton(btn, btn_index++);
|
||||
ui->providersLayout->addWidget(btn);
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseProviderDialog::disableInput()
|
||||
{
|
||||
for (auto& btn : m_providers.buttons())
|
||||
btn->setEnabled(false);
|
||||
|
||||
ui->skipOneButton->setEnabled(false);
|
||||
ui->skipAllButton->setEnabled(false);
|
||||
ui->confirmOneButton->setEnabled(false);
|
||||
ui->confirmAllButton->setEnabled(false);
|
||||
}
|
56
launcher/ui/dialogs/ChooseProviderDialog.h
Normal file
56
launcher/ui/dialogs/ChooseProviderDialog.h
Normal file
@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <QButtonGroup>
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class ChooseProviderDialog;
|
||||
}
|
||||
|
||||
namespace ModPlatform {
|
||||
enum class Provider;
|
||||
}
|
||||
|
||||
class Mod;
|
||||
class NetJob;
|
||||
class ModUpdateDialog;
|
||||
|
||||
class ChooseProviderDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
struct Response {
|
||||
bool skip_all = false;
|
||||
bool confirm_all = false;
|
||||
|
||||
bool try_others = false;
|
||||
|
||||
ModPlatform::Provider chosen;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit ChooseProviderDialog(QWidget* parent, bool single_choice = false, bool allow_skipping = true);
|
||||
~ChooseProviderDialog();
|
||||
|
||||
auto getResponse() const -> Response { return m_response; }
|
||||
|
||||
void setDescription(QString desc);
|
||||
|
||||
private slots:
|
||||
void skipOne();
|
||||
void skipAll();
|
||||
void confirmOne();
|
||||
void confirmAll();
|
||||
|
||||
private:
|
||||
void addProviders();
|
||||
void disableInput();
|
||||
|
||||
auto getSelectedProvider() const -> ModPlatform::Provider;
|
||||
|
||||
private:
|
||||
Ui::ChooseProviderDialog* ui;
|
||||
|
||||
QButtonGroup m_providers;
|
||||
|
||||
Response m_response;
|
||||
};
|
89
launcher/ui/dialogs/ChooseProviderDialog.ui
Normal file
89
launcher/ui/dialogs/ChooseProviderDialog.ui
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ChooseProviderDialog</class>
|
||||
<widget class="QDialog" name="ChooseProviderDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>453</width>
|
||||
<height>197</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Choose a mod provider</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="explanationLabel">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignJustify|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<layout class="QFormLayout" name="providersLayout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignHCenter|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignHCenter|Qt::AlignTop</set>
|
||||
</property>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="buttonsLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="skipOneButton">
|
||||
<property name="text">
|
||||
<string>Skip this mod</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="skipAllButton">
|
||||
<property name="text">
|
||||
<string>Skip all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="confirmAllButton">
|
||||
<property name="text">
|
||||
<string>Confirm for all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="confirmOneButton">
|
||||
<property name="text">
|
||||
<string>Confirm</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="tryOthersCheckbox">
|
||||
<property name="text">
|
||||
<string>Try to automatically use other providers if the chosen one fails</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
408
launcher/ui/dialogs/ModUpdateDialog.cpp
Normal file
408
launcher/ui/dialogs/ModUpdateDialog.cpp
Normal file
@ -0,0 +1,408 @@
|
||||
#include "ModUpdateDialog.h"
|
||||
#include "ChooseProviderDialog.h"
|
||||
#include "CustomMessageBox.h"
|
||||
#include "ProgressDialog.h"
|
||||
#include "ScrollMessageBox.h"
|
||||
#include "ui_ReviewMessageBox.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
|
||||
#include "modplatform/EnsureMetadataTask.h"
|
||||
#include "modplatform/flame/FlameCheckUpdate.h"
|
||||
#include "modplatform/modrinth/ModrinthCheckUpdate.h"
|
||||
|
||||
#include <HoeDown.h>
|
||||
#include <QTextBrowser>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
|
||||
static std::list<Version> mcVersions(BaseInstance* inst)
|
||||
{
|
||||
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
|
||||
}
|
||||
|
||||
static ModAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
|
||||
{
|
||||
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders() };
|
||||
}
|
||||
|
||||
ModUpdateDialog::ModUpdateDialog(QWidget* parent,
|
||||
BaseInstance* instance,
|
||||
const std::shared_ptr<ModFolderModel> mods,
|
||||
QList<Mod::Ptr>& search_for)
|
||||
: ReviewMessageBox(parent, tr("Confirm mods to update"), "")
|
||||
, m_parent(parent)
|
||||
, m_mod_model(mods)
|
||||
, m_candidates(search_for)
|
||||
, m_second_try_metadata(new ConcurrentTask())
|
||||
, m_instance(instance)
|
||||
{
|
||||
ReviewMessageBox::setGeometry(0, 0, 800, 600);
|
||||
|
||||
ui->explainLabel->setText(tr("You're about to update the following mods:"));
|
||||
ui->onlyCheckedLabel->setText(tr("Only mods with a check will be updated!"));
|
||||
}
|
||||
|
||||
void ModUpdateDialog::checkCandidates()
|
||||
{
|
||||
// Ensure mods have valid metadata
|
||||
auto went_well = ensureMetadata();
|
||||
if (!went_well) {
|
||||
m_aborted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Report failed metadata generation
|
||||
if (!m_failed_metadata.empty()) {
|
||||
QString text;
|
||||
for (const auto& failed : m_failed_metadata) {
|
||||
const auto& mod = std::get<0>(failed);
|
||||
const auto& reason = std::get<1>(failed);
|
||||
text += tr("Mod name: %1<br>File name: %2<br>Reason: %3<br><br>").arg(mod->name(), mod->fileinfo().fileName(), reason);
|
||||
}
|
||||
|
||||
ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"),
|
||||
tr("Could not generate metadata for the following mods:<br>"
|
||||
"Do you wish to proceed without those mods?"),
|
||||
text);
|
||||
message_dialog.setModal(true);
|
||||
if (message_dialog.exec() == QDialog::Rejected) {
|
||||
m_aborted = true;
|
||||
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto versions = mcVersions(m_instance);
|
||||
auto loaders = mcLoaders(m_instance);
|
||||
|
||||
SequentialTask check_task(m_parent, tr("Checking for updates"));
|
||||
|
||||
if (!m_modrinth_to_update.empty()) {
|
||||
m_modrinth_check_task = new ModrinthCheckUpdate(m_modrinth_to_update, versions, loaders, m_mod_model);
|
||||
connect(m_modrinth_check_task, &CheckUpdateTask::checkFailed, this,
|
||||
[this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({mod, reason, recover_url}); });
|
||||
check_task.addTask(m_modrinth_check_task);
|
||||
}
|
||||
|
||||
if (!m_flame_to_update.empty()) {
|
||||
m_flame_check_task = new FlameCheckUpdate(m_flame_to_update, versions, loaders, m_mod_model);
|
||||
connect(m_flame_check_task, &CheckUpdateTask::checkFailed, this,
|
||||
[this](Mod* mod, QString reason, QUrl recover_url) { m_failed_check_update.append({mod, reason, recover_url}); });
|
||||
check_task.addTask(m_flame_check_task);
|
||||
}
|
||||
|
||||
connect(&check_task, &Task::failed, this,
|
||||
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
|
||||
|
||||
connect(&check_task, &Task::succeeded, this, [&]() {
|
||||
QStringList warnings = check_task.warnings();
|
||||
if (warnings.count()) {
|
||||
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
|
||||
}
|
||||
});
|
||||
|
||||
// Check for updates
|
||||
ProgressDialog progress_dialog(m_parent);
|
||||
progress_dialog.setSkipButton(true, tr("Abort"));
|
||||
progress_dialog.setWindowTitle(tr("Checking for updates..."));
|
||||
auto ret = progress_dialog.execWithTask(&check_task);
|
||||
|
||||
// If the dialog was skipped / some download error happened
|
||||
if (ret == QDialog::DialogCode::Rejected) {
|
||||
m_aborted = true;
|
||||
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add found updates for Modrinth
|
||||
if (m_modrinth_check_task) {
|
||||
auto modrinth_updates = m_modrinth_check_task->getUpdatable();
|
||||
for (auto& updatable : modrinth_updates) {
|
||||
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
|
||||
|
||||
appendMod(updatable);
|
||||
m_tasks.insert(updatable.name, updatable.download);
|
||||
}
|
||||
}
|
||||
|
||||
// Add found updated for Flame
|
||||
if (m_flame_check_task) {
|
||||
auto flame_updates = m_flame_check_task->getUpdatable();
|
||||
for (auto& updatable : flame_updates) {
|
||||
qDebug() << QString("Mod %1 has an update available!").arg(updatable.name);
|
||||
|
||||
appendMod(updatable);
|
||||
m_tasks.insert(updatable.name, updatable.download);
|
||||
}
|
||||
}
|
||||
|
||||
// Report failed update checking
|
||||
if (!m_failed_check_update.empty()) {
|
||||
QString text;
|
||||
for (const auto& failed : m_failed_check_update) {
|
||||
const auto& mod = std::get<0>(failed);
|
||||
const auto& reason = std::get<1>(failed);
|
||||
const auto& recover_url = std::get<2>(failed);
|
||||
|
||||
qDebug() << mod->name() << " failed to check for updates!";
|
||||
|
||||
text += tr("Mod name: %1").arg(mod->name()) + "<br>";
|
||||
if (!reason.isEmpty())
|
||||
text += tr("Reason: %1").arg(reason) + "<br>";
|
||||
if (!recover_url.isEmpty())
|
||||
text += tr("Possible solution: ") + tr("Getting the latest version manually:") + "<br>" +
|
||||
QString("<a href='%1'>").arg(recover_url.toString()) + recover_url.toString() + "</a><br>";
|
||||
text += "<br>";
|
||||
}
|
||||
|
||||
ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"),
|
||||
tr("Could not check or get the following mods for updates:<br>"
|
||||
"Do you wish to proceed without those mods?"),
|
||||
text);
|
||||
message_dialog.setModal(true);
|
||||
if (message_dialog.exec() == QDialog::Rejected) {
|
||||
m_aborted = true;
|
||||
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no mod to be updated
|
||||
if (ui->modTreeWidget->topLevelItemCount() == 0) {
|
||||
m_no_updates = true;
|
||||
} else {
|
||||
// FIXME: Find a more efficient way of doing this!
|
||||
|
||||
// Sort major items in alphabetical order (also sorts the children unfortunately)
|
||||
ui->modTreeWidget->sortItems(0, Qt::SortOrder::AscendingOrder);
|
||||
|
||||
// Re-sort the children
|
||||
auto* item = ui->modTreeWidget->topLevelItem(0);
|
||||
for (int i = 1; item != nullptr; ++i) {
|
||||
item->sortChildren(0, Qt::SortOrder::DescendingOrder);
|
||||
item = ui->modTreeWidget->topLevelItem(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_aborted || m_no_updates)
|
||||
QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
// Part 1: Ensure we have a valid metadata
|
||||
auto ModUpdateDialog::ensureMetadata() -> bool
|
||||
{
|
||||
auto index_dir = indexDir();
|
||||
|
||||
SequentialTask seq(m_parent, tr("Looking for metadata"));
|
||||
|
||||
// A better use of data structures here could remove the need for this QHash
|
||||
QHash<QString, bool> should_try_others;
|
||||
QList<Mod*> modrinth_tmp;
|
||||
QList<Mod*> flame_tmp;
|
||||
|
||||
bool confirm_rest = false;
|
||||
bool try_others_rest = false;
|
||||
bool skip_rest = false;
|
||||
ModPlatform::Provider provider_rest = ModPlatform::Provider::MODRINTH;
|
||||
|
||||
auto addToTmp = [&](Mod* m, ModPlatform::Provider p) {
|
||||
switch (p) {
|
||||
case ModPlatform::Provider::MODRINTH:
|
||||
modrinth_tmp.push_back(m);
|
||||
break;
|
||||
case ModPlatform::Provider::FLAME:
|
||||
flame_tmp.push_back(m);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto candidate : m_candidates) {
|
||||
auto* candidate_ptr = candidate.get();
|
||||
if (candidate->status() != ModStatus::NoMetadata) {
|
||||
onMetadataEnsured(candidate_ptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (skip_rest)
|
||||
continue;
|
||||
|
||||
if (confirm_rest) {
|
||||
addToTmp(candidate_ptr, provider_rest);
|
||||
should_try_others.insert(candidate->internal_id(), try_others_rest);
|
||||
continue;
|
||||
}
|
||||
|
||||
ChooseProviderDialog chooser(this);
|
||||
chooser.setDescription(tr("This mod (%1) does not have a metadata yet. We need to create one in order to keep relevant "
|
||||
"information on how to update this "
|
||||
"mod. To do this, please select a mod provider from which we can search for updates for %1.")
|
||||
.arg(candidate->name()));
|
||||
auto confirmed = chooser.exec() == QDialog::DialogCode::Accepted;
|
||||
|
||||
auto response = chooser.getResponse();
|
||||
|
||||
if (response.skip_all)
|
||||
skip_rest = true;
|
||||
if (response.confirm_all) {
|
||||
confirm_rest = true;
|
||||
provider_rest = response.chosen;
|
||||
try_others_rest = response.try_others;
|
||||
}
|
||||
|
||||
should_try_others.insert(candidate->internal_id(), response.try_others);
|
||||
|
||||
if (confirmed)
|
||||
addToTmp(candidate_ptr, response.chosen);
|
||||
}
|
||||
|
||||
if (!modrinth_tmp.empty()) {
|
||||
auto* modrinth_task = new EnsureMetadataTask(modrinth_tmp, index_dir, ModPlatform::Provider::MODRINTH);
|
||||
connect(modrinth_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
|
||||
connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
|
||||
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::MODRINTH);
|
||||
});
|
||||
seq.addTask(modrinth_task);
|
||||
}
|
||||
|
||||
if (!flame_tmp.empty()) {
|
||||
auto* flame_task = new EnsureMetadataTask(flame_tmp, index_dir, ModPlatform::Provider::FLAME);
|
||||
connect(flame_task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
|
||||
connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) {
|
||||
onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::FLAME);
|
||||
});
|
||||
seq.addTask(flame_task);
|
||||
}
|
||||
|
||||
seq.addTask(m_second_try_metadata);
|
||||
|
||||
ProgressDialog checking_dialog(m_parent);
|
||||
checking_dialog.setSkipButton(true, tr("Abort"));
|
||||
checking_dialog.setWindowTitle(tr("Generating metadata..."));
|
||||
auto ret_metadata = checking_dialog.execWithTask(&seq);
|
||||
|
||||
return (ret_metadata != QDialog::DialogCode::Rejected);
|
||||
}
|
||||
|
||||
void ModUpdateDialog::onMetadataEnsured(Mod* mod)
|
||||
{
|
||||
// When the mod is a folder, for instance
|
||||
if (!mod->metadata())
|
||||
return;
|
||||
|
||||
switch (mod->metadata()->provider) {
|
||||
case ModPlatform::Provider::MODRINTH:
|
||||
m_modrinth_to_update.push_back(mod);
|
||||
break;
|
||||
case ModPlatform::Provider::FLAME:
|
||||
m_flame_to_update.push_back(mod);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ModPlatform::Provider next(ModPlatform::Provider p)
|
||||
{
|
||||
switch (p) {
|
||||
case ModPlatform::Provider::MODRINTH:
|
||||
return ModPlatform::Provider::FLAME;
|
||||
case ModPlatform::Provider::FLAME:
|
||||
return ModPlatform::Provider::MODRINTH;
|
||||
}
|
||||
|
||||
return ModPlatform::Provider::FLAME;
|
||||
}
|
||||
|
||||
void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::Provider first_choice)
|
||||
{
|
||||
if (try_others) {
|
||||
auto index_dir = indexDir();
|
||||
|
||||
auto* task = new EnsureMetadataTask(mod, index_dir, next(first_choice));
|
||||
connect(task, &EnsureMetadataTask::metadataReady, [this](Mod* candidate) { onMetadataEnsured(candidate); });
|
||||
connect(task, &EnsureMetadataTask::metadataFailed, [this](Mod* candidate) { onMetadataFailed(candidate, false); });
|
||||
|
||||
m_second_try_metadata->addTask(task);
|
||||
} else {
|
||||
QString reason{ tr("Didn't find a valid version on the selected mod provider(s)") };
|
||||
|
||||
m_failed_metadata.append({mod, reason});
|
||||
}
|
||||
}
|
||||
|
||||
void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info)
|
||||
{
|
||||
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
|
||||
item_top->setCheckState(0, Qt::CheckState::Checked);
|
||||
item_top->setText(0, info.name);
|
||||
item_top->setExpanded(true);
|
||||
|
||||
auto provider_item = new QTreeWidgetItem(item_top);
|
||||
provider_item->setText(0, tr("Provider: %1").arg(ProviderCaps.readableName(info.provider)));
|
||||
|
||||
auto old_version_item = new QTreeWidgetItem(item_top);
|
||||
old_version_item->setText(0, tr("Old version: %1").arg(info.old_version.isEmpty() ? tr("Not installed") : info.old_version));
|
||||
|
||||
auto new_version_item = new QTreeWidgetItem(item_top);
|
||||
new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
|
||||
|
||||
auto changelog_item = new QTreeWidgetItem(item_top);
|
||||
changelog_item->setText(0, tr("Changelog of the latest version"));
|
||||
|
||||
auto changelog = new QTreeWidgetItem(changelog_item);
|
||||
auto changelog_area = new QTextBrowser();
|
||||
|
||||
switch (info.provider) {
|
||||
case ModPlatform::Provider::MODRINTH: {
|
||||
HoeDown h;
|
||||
// HoeDown bug?: \n aren't converted to <br>
|
||||
auto text = h.process(info.changelog.toUtf8());
|
||||
|
||||
// Don't convert if there's an HTML tag right after (Qt rendering weirdness)
|
||||
text.remove(QRegularExpression("(\n+)(?=<)"));
|
||||
text.replace('\n', "<br>");
|
||||
|
||||
changelog_area->setHtml(text);
|
||||
break;
|
||||
}
|
||||
case ModPlatform::Provider::FLAME: {
|
||||
changelog_area->setHtml(info.changelog);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
changelog_area->setOpenExternalLinks(true);
|
||||
changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::NoWrap);
|
||||
changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
|
||||
|
||||
// HACK: Is there a better way of achieving this?
|
||||
auto font_height = QFontMetrics(changelog_area->font()).height();
|
||||
changelog_area->setMaximumHeight((changelog_area->toPlainText().count(QRegularExpression("\n|<br>")) + 2) * font_height);
|
||||
|
||||
ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area);
|
||||
|
||||
ui->modTreeWidget->addTopLevelItem(item_top);
|
||||
}
|
||||
|
||||
auto ModUpdateDialog::getTasks() -> const QList<ModDownloadTask*>
|
||||
{
|
||||
QList<ModDownloadTask*> list;
|
||||
|
||||
auto* item = ui->modTreeWidget->topLevelItem(0);
|
||||
|
||||
for (int i = 1; item != nullptr; ++i) {
|
||||
if (item->checkState(0) == Qt::CheckState::Checked) {
|
||||
list.push_back(m_tasks.find(item->text(0)).value());
|
||||
}
|
||||
|
||||
item = ui->modTreeWidget->topLevelItem(i);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
62
launcher/ui/dialogs/ModUpdateDialog.h
Normal file
62
launcher/ui/dialogs/ModUpdateDialog.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "ModDownloadTask.h"
|
||||
#include "ReviewMessageBox.h"
|
||||
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
|
||||
#include "modplatform/CheckUpdateTask.h"
|
||||
|
||||
class Mod;
|
||||
class ModrinthCheckUpdate;
|
||||
class FlameCheckUpdate;
|
||||
class ConcurrentTask;
|
||||
|
||||
class ModUpdateDialog final : public ReviewMessageBox {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ModUpdateDialog(QWidget* parent,
|
||||
BaseInstance* instance,
|
||||
const std::shared_ptr<ModFolderModel> mod_model,
|
||||
QList<Mod::Ptr>& search_for);
|
||||
|
||||
void checkCandidates();
|
||||
|
||||
void appendMod(const CheckUpdateTask::UpdatableMod& info);
|
||||
|
||||
const QList<ModDownloadTask*> getTasks();
|
||||
auto indexDir() const -> QDir { return m_mod_model->indexDir(); }
|
||||
|
||||
auto noUpdates() const -> bool { return m_no_updates; };
|
||||
auto aborted() const -> bool { return m_aborted; };
|
||||
|
||||
private:
|
||||
auto ensureMetadata() -> bool;
|
||||
|
||||
private slots:
|
||||
void onMetadataEnsured(Mod*);
|
||||
void onMetadataFailed(Mod*, bool try_others = false, ModPlatform::Provider first_choice = ModPlatform::Provider::MODRINTH);
|
||||
|
||||
private:
|
||||
QWidget* m_parent;
|
||||
|
||||
ModrinthCheckUpdate* m_modrinth_check_task = nullptr;
|
||||
FlameCheckUpdate* m_flame_check_task = nullptr;
|
||||
|
||||
const std::shared_ptr<ModFolderModel> m_mod_model;
|
||||
|
||||
QList<Mod::Ptr>& m_candidates;
|
||||
QList<Mod*> m_modrinth_to_update;
|
||||
QList<Mod*> m_flame_to_update;
|
||||
|
||||
ConcurrentTask* m_second_try_metadata;
|
||||
QList<std::tuple<Mod*, QString>> m_failed_metadata;
|
||||
QList<std::tuple<Mod*, QString, QUrl>> m_failed_check_update;
|
||||
|
||||
QHash<QString, ModDownloadTask*> m_tasks;
|
||||
BaseInstance* m_instance;
|
||||
|
||||
bool m_no_updates = false;
|
||||
bool m_aborted = false;
|
||||
};
|
@ -62,24 +62,24 @@ void ProgressDialog::updateSize()
|
||||
int ProgressDialog::execWithTask(Task* task)
|
||||
{
|
||||
this->task = task;
|
||||
QDialog::DialogCode result;
|
||||
|
||||
if (!task) {
|
||||
qDebug() << "Programmer error: progress dialog created with null task.";
|
||||
return Accepted;
|
||||
qDebug() << "Programmer error: Progress dialog created with null task.";
|
||||
return QDialog::DialogCode::Accepted;
|
||||
}
|
||||
|
||||
QDialog::DialogCode result;
|
||||
if (handleImmediateResult(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Connect signals.
|
||||
connect(task, SIGNAL(started()), SLOT(onTaskStarted()));
|
||||
connect(task, SIGNAL(failed(QString)), SLOT(onTaskFailed(QString)));
|
||||
connect(task, SIGNAL(succeeded()), SLOT(onTaskSucceeded()));
|
||||
connect(task, SIGNAL(status(QString)), SLOT(changeStatus(const QString&)));
|
||||
connect(task, SIGNAL(stepStatus(QString)), SLOT(changeStatus(const QString&)));
|
||||
connect(task, SIGNAL(progress(qint64, qint64)), SLOT(changeProgress(qint64, qint64)));
|
||||
connect(task, &Task::started, this, &ProgressDialog::onTaskStarted);
|
||||
connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed);
|
||||
connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded);
|
||||
connect(task, &Task::status, this, &ProgressDialog::changeStatus);
|
||||
connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus);
|
||||
connect(task, &Task::progress, this, &ProgressDialog::changeProgress);
|
||||
|
||||
connect(task, &Task::aborted, [this] { onTaskFailed(tr("Aborted by user")); });
|
||||
|
||||
@ -89,19 +89,15 @@ int ProgressDialog::execWithTask(Task* task)
|
||||
ui->globalProgressBar->setHidden(true);
|
||||
}
|
||||
|
||||
// if this didn't connect to an already running task, invoke start
|
||||
// It's a good idea to start the task after we entered the dialog's event loop :^)
|
||||
if (!task->isRunning()) {
|
||||
task->start();
|
||||
}
|
||||
if (task->isRunning()) {
|
||||
changeProgress(task->getProgress(), task->getTotalProgress());
|
||||
changeStatus(task->getStatus());
|
||||
return QDialog::exec();
|
||||
} else if (handleImmediateResult(result)) {
|
||||
return result;
|
||||
QMetaObject::invokeMethod(task, &Task::start, Qt::QueuedConnection);
|
||||
} else {
|
||||
return QDialog::Rejected;
|
||||
changeStatus(task->getStatus());
|
||||
changeProgress(task->getProgress(), task->getTotalProgress());
|
||||
}
|
||||
|
||||
return QDialog::exec();
|
||||
}
|
||||
|
||||
// TODO: only provide the unique_ptr overloads
|
||||
|
@ -40,7 +40,7 @@ auto ReviewMessageBox::deselectedMods() -> QStringList
|
||||
|
||||
auto* item = ui->modTreeWidget->topLevelItem(0);
|
||||
|
||||
for (int i = 0; item != nullptr; ++i) {
|
||||
for (int i = 1; item != nullptr; ++i) {
|
||||
if (item->checkState(0) == Qt::CheckState::Unchecked) {
|
||||
list.append(item->text(0));
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<width>500</width>
|
||||
<height>455</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -57,68 +57,72 @@ void SkinUploadDialog::on_buttonBox_accepted()
|
||||
{
|
||||
QString fileName;
|
||||
QString input = ui->skinPathTextBox->text();
|
||||
QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$"));
|
||||
bool isLocalFile = false;
|
||||
// it has an URL prefix -> it is an URL
|
||||
if(urlPrefixMatcher.match(input).hasMatch())
|
||||
{
|
||||
QUrl fileURL = input;
|
||||
if(fileURL.isValid())
|
||||
ProgressDialog prog(this);
|
||||
SequentialTask skinUpload;
|
||||
|
||||
if (!input.isEmpty()) {
|
||||
QRegularExpression urlPrefixMatcher(QRegularExpression::anchoredPattern("^([a-z]+)://.+$"));
|
||||
bool isLocalFile = false;
|
||||
// it has an URL prefix -> it is an URL
|
||||
if(urlPrefixMatcher.match(input).hasMatch())
|
||||
{
|
||||
// local?
|
||||
if(fileURL.isLocalFile())
|
||||
QUrl fileURL = input;
|
||||
if(fileURL.isValid())
|
||||
{
|
||||
isLocalFile = true;
|
||||
fileName = fileURL.toLocalFile();
|
||||
// local?
|
||||
if(fileURL.isLocalFile())
|
||||
{
|
||||
isLocalFile = true;
|
||||
fileName = fileURL.toLocalFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Skin Upload"),
|
||||
tr("Using remote URLs for setting skins is not implemented yet."),
|
||||
QMessageBox::Warning
|
||||
)->exec();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Skin Upload"),
|
||||
tr("Using remote URLs for setting skins is not implemented yet."),
|
||||
tr("You cannot use an invalid URL for uploading skins."),
|
||||
QMessageBox::Warning
|
||||
)->exec();
|
||||
)->exec();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CustomMessageBox::selectable(
|
||||
this,
|
||||
tr("Skin Upload"),
|
||||
tr("You cannot use an invalid URL for uploading skins."),
|
||||
QMessageBox::Warning
|
||||
)->exec();
|
||||
// just assume it's a path then
|
||||
isLocalFile = true;
|
||||
fileName = ui->skinPathTextBox->text();
|
||||
}
|
||||
if (isLocalFile && !QFile::exists(fileName))
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
SkinUpload::Model model = SkinUpload::STEVE;
|
||||
if (ui->steveBtn->isChecked())
|
||||
{
|
||||
model = SkinUpload::STEVE;
|
||||
}
|
||||
else if (ui->alexBtn->isChecked())
|
||||
{
|
||||
model = SkinUpload::ALEX;
|
||||
}
|
||||
skinUpload.addTask(shared_qobject_ptr<SkinUpload>(new SkinUpload(this, m_acct->accessToken(), FS::read(fileName), model)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// just assume it's a path then
|
||||
isLocalFile = true;
|
||||
fileName = ui->skinPathTextBox->text();
|
||||
}
|
||||
if (isLocalFile && !QFile::exists(fileName))
|
||||
{
|
||||
CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
SkinUpload::Model model = SkinUpload::STEVE;
|
||||
if (ui->steveBtn->isChecked())
|
||||
{
|
||||
model = SkinUpload::STEVE;
|
||||
}
|
||||
else if (ui->alexBtn->isChecked())
|
||||
{
|
||||
model = SkinUpload::ALEX;
|
||||
}
|
||||
ProgressDialog prog(this);
|
||||
SequentialTask skinUpload;
|
||||
skinUpload.addTask(shared_qobject_ptr<SkinUpload>(new SkinUpload(this, m_acct->accessToken(), FS::read(fileName), model)));
|
||||
|
||||
auto selectedCape = ui->capeCombo->currentData().toString();
|
||||
if(selectedCape != m_acct->accountData()->minecraftProfile.currentCape) {
|
||||
skinUpload.addTask(shared_qobject_ptr<CapeChange>(new CapeChange(this, m_acct->accessToken(), selectedCape)));
|
||||
|
@ -21,7 +21,11 @@
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="skinPathTextBox"/>
|
||||
<widget class="QLineEdit" name="skinPathTextBox">
|
||||
<property name="placeholderText">
|
||||
<string>Leave empty to keep current skin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="skinBrowseBtn">
|
||||
|
@ -147,6 +147,17 @@
|
||||
<string>Download a new resource</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUpdateItem">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Check for &Updates</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>"Tries to find / update all selected resources (all resources if none is selected)"</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
@ -49,6 +49,7 @@
|
||||
#include "ui/GuiUtil.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ModDownloadDialog.h"
|
||||
#include "ui/dialogs/ModUpdateDialog.h"
|
||||
|
||||
#include "DesktopServices.h"
|
||||
|
||||
@ -78,6 +79,23 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
|
||||
ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionDownloadItem);
|
||||
|
||||
connect(ui->actionDownloadItem, &QAction::triggered, this, &ModFolderPage::installMods);
|
||||
|
||||
ui->actionUpdateItem->setToolTip(tr("Tries to find / update all selected mods (all mods if none is selected)"));
|
||||
ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem);
|
||||
connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods);
|
||||
|
||||
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
[this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); });
|
||||
|
||||
connect(mods.get(), &ModFolderModel::rowsInserted, this,
|
||||
[this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); });
|
||||
|
||||
connect(mods.get(), &ModFolderModel::updateFinished, this, [this, mods] {
|
||||
ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty());
|
||||
|
||||
// Prevent a weird crash when trying to open the mods page twice in a session o.O
|
||||
disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +125,6 @@ bool CoreModFolderPage::shouldDisplay() const
|
||||
return false;
|
||||
if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -118,7 +135,7 @@ void ModFolderPage::installMods()
|
||||
return;
|
||||
if (m_instance->typeName() != "Minecraft")
|
||||
return; // this is a null instance or a legacy instance
|
||||
|
||||
|
||||
auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
|
||||
if (profile->getModLoaders() == ModAPI::Unspecified) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
|
||||
@ -140,7 +157,7 @@ void ModFolderPage::installMods()
|
||||
QStringList warnings = tasks->warnings();
|
||||
if (warnings.count())
|
||||
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
|
||||
|
||||
|
||||
tasks->deleteLater();
|
||||
});
|
||||
|
||||
@ -155,3 +172,58 @@ void ModFolderPage::installMods()
|
||||
m_model->update();
|
||||
}
|
||||
}
|
||||
|
||||
void ModFolderPage::updateMods()
|
||||
{
|
||||
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
|
||||
|
||||
auto mods_list = m_model->selectedMods(selection);
|
||||
bool use_all = mods_list.empty();
|
||||
if (use_all)
|
||||
mods_list = m_model->allMods();
|
||||
|
||||
ModUpdateDialog update_dialog(this, m_instance, m_model, mods_list);
|
||||
update_dialog.checkCandidates();
|
||||
|
||||
if (update_dialog.aborted()) {
|
||||
CustomMessageBox::selectable(this, tr("Aborted"), tr("The mod updater was aborted!"), QMessageBox::Warning)->show();
|
||||
return;
|
||||
}
|
||||
if (update_dialog.noUpdates()) {
|
||||
CustomMessageBox::selectable(this, tr("Update checker"),
|
||||
(mods_list.size() == 1)
|
||||
? tr("'%1' is up-to-date! :)").arg(mods_list.front()->name())
|
||||
: tr("All %1mods are up-to-date! :)").arg(use_all ? "" : (tr("selected") + " ")))
|
||||
->exec();
|
||||
return;
|
||||
}
|
||||
|
||||
if (update_dialog.exec()) {
|
||||
ConcurrentTask* tasks = new ConcurrentTask(this);
|
||||
connect(tasks, &Task::failed, [this, tasks](QString reason) {
|
||||
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
|
||||
tasks->deleteLater();
|
||||
});
|
||||
connect(tasks, &Task::aborted, [this, tasks]() {
|
||||
CustomMessageBox::selectable(this, tr("Aborted"), tr("Download stopped by user."), QMessageBox::Information)->show();
|
||||
tasks->deleteLater();
|
||||
});
|
||||
connect(tasks, &Task::succeeded, [this, tasks]() {
|
||||
QStringList warnings = tasks->warnings();
|
||||
if (warnings.count()) {
|
||||
CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
|
||||
}
|
||||
tasks->deleteLater();
|
||||
});
|
||||
|
||||
for (auto task : update_dialog.getTasks()) {
|
||||
tasks->addTask(task);
|
||||
}
|
||||
|
||||
ProgressDialog loadDialog(this);
|
||||
loadDialog.setSkipButton(true, tr("Abort"));
|
||||
loadDialog.execWithTask(tasks);
|
||||
|
||||
m_model->update();
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ class ModFolderPage : public ExternalResourcesPage {
|
||||
|
||||
private slots:
|
||||
void installMods();
|
||||
void updateMods();
|
||||
};
|
||||
|
||||
class CoreModFolderPage : public ModFolderPage {
|
||||
|
@ -64,7 +64,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
|
||||
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
|
||||
{
|
||||
Q_UNUSED(loaders);
|
||||
return ver.mcVersion.contains(mineVer);
|
||||
return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
|
||||
}
|
||||
|
||||
// I don't know why, but doing this on the parent class makes it so that
|
||||
|
@ -76,13 +76,20 @@ void WideBar::addSeparator()
|
||||
m_entries.push_back(entry);
|
||||
}
|
||||
|
||||
void WideBar::insertActionBefore(QAction* before, QAction* action){
|
||||
auto iter = std::find_if(m_entries.begin(), m_entries.end(), [before](BarEntry * entry) {
|
||||
return entry->wideAction == before;
|
||||
auto WideBar::getMatching(QAction* act) -> QList<BarEntry*>::iterator
|
||||
{
|
||||
auto iter = std::find_if(m_entries.begin(), m_entries.end(), [act](BarEntry * entry) {
|
||||
return entry->wideAction == act;
|
||||
});
|
||||
if(iter == m_entries.end()) {
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
void WideBar::insertActionBefore(QAction* before, QAction* action){
|
||||
auto iter = getMatching(before);
|
||||
if(iter == m_entries.end())
|
||||
return;
|
||||
}
|
||||
|
||||
auto entry = new BarEntry();
|
||||
entry->qAction = insertWidget((*iter)->qAction, new ActionButton(action, this));
|
||||
entry->wideAction = action;
|
||||
@ -90,14 +97,24 @@ void WideBar::insertActionBefore(QAction* before, QAction* action){
|
||||
m_entries.insert(iter, entry);
|
||||
}
|
||||
|
||||
void WideBar::insertActionAfter(QAction* after, QAction* action){
|
||||
auto iter = getMatching(after);
|
||||
if(iter == m_entries.end())
|
||||
return;
|
||||
|
||||
auto entry = new BarEntry();
|
||||
entry->qAction = insertWidget((*(iter+1))->qAction, new ActionButton(action, this));
|
||||
entry->wideAction = action;
|
||||
entry->type = BarEntry::Action;
|
||||
m_entries.insert(iter + 1, entry);
|
||||
}
|
||||
|
||||
void WideBar::insertSpacer(QAction* action)
|
||||
{
|
||||
auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) {
|
||||
return entry->wideAction == action;
|
||||
});
|
||||
if(iter == m_entries.end()) {
|
||||
auto iter = getMatching(action);
|
||||
if(iter == m_entries.end())
|
||||
return;
|
||||
}
|
||||
|
||||
QWidget* spacer = new QWidget();
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
@ -107,6 +124,18 @@ void WideBar::insertSpacer(QAction* action)
|
||||
m_entries.insert(iter, entry);
|
||||
}
|
||||
|
||||
void WideBar::insertSeparator(QAction* before)
|
||||
{
|
||||
auto iter = getMatching(before);
|
||||
if(iter == m_entries.end())
|
||||
return;
|
||||
|
||||
auto entry = new BarEntry();
|
||||
entry->qAction = QToolBar::insertSeparator(before);
|
||||
entry->type = BarEntry::Separator;
|
||||
m_entries.insert(iter, entry);
|
||||
}
|
||||
|
||||
QMenu * WideBar::createContextMenu(QWidget *parent, const QString & title)
|
||||
{
|
||||
QMenu *contextMenu = new QMenu(title, parent);
|
||||
|
@ -1,27 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <QToolBar>
|
||||
#include <QAction>
|
||||
#include <QMap>
|
||||
#include <QToolBar>
|
||||
|
||||
class QMenu;
|
||||
|
||||
class WideBar : public QToolBar
|
||||
{
|
||||
class WideBar : public QToolBar {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WideBar(const QString &title, QWidget * parent = nullptr);
|
||||
explicit WideBar(QWidget * parent = nullptr);
|
||||
public:
|
||||
explicit WideBar(const QString& title, QWidget* parent = nullptr);
|
||||
explicit WideBar(QWidget* parent = nullptr);
|
||||
virtual ~WideBar();
|
||||
|
||||
void addAction(QAction *action);
|
||||
void addAction(QAction* action);
|
||||
void addSeparator();
|
||||
void insertSpacer(QAction *action);
|
||||
void insertActionBefore(QAction *before, QAction *action);
|
||||
QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString());
|
||||
|
||||
private:
|
||||
void insertSpacer(QAction* action);
|
||||
void insertSeparator(QAction* before);
|
||||
void insertActionBefore(QAction* before, QAction* action);
|
||||
void insertActionAfter(QAction* after, QAction* action);
|
||||
|
||||
QMenu* createContextMenu(QWidget* parent = nullptr, const QString& title = QString());
|
||||
|
||||
private:
|
||||
struct BarEntry;
|
||||
QList<BarEntry *> m_entries;
|
||||
|
||||
auto getMatching(QAction* act) -> QList<BarEntry*>::iterator;
|
||||
|
||||
private:
|
||||
QList<BarEntry*> m_entries;
|
||||
};
|
||||
|
Reference in New Issue
Block a user