diff options
Diffstat (limited to 'launcher/ui')
32 files changed, 452 insertions, 60 deletions
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 603eaa97..97152a48 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -643,6 +643,7 @@ public: actionRenameInstance->setObjectName(QStringLiteral("actionRenameInstance")); actionRenameInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Rename")); actionRenameInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Rename the selected instance.")); + actionRenameInstance->setIcon(APPLICATION->getThemedIcon("rename")); all_actions.append(&actionRenameInstance); // the rename label is inside the rename tool button @@ -675,6 +676,7 @@ public: actionKillInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Kill")); actionKillInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance")); actionKillInstance->setShortcut(QKeySequence(tr("Ctrl+K"))); + actionKillInstance->setIcon(APPLICATION->getThemedIcon("status-bad")); all_actions.append(&actionKillInstance); actionEditInstance = TranslatedAction(MainWindow); @@ -682,6 +684,7 @@ public: actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Edit...")); actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions.")); actionEditInstance->setShortcut(QKeySequence(tr("Ctrl+I"))); + actionEditInstance->setIcon(APPLICATION->getThemedIcon("settings-configure")); all_actions.append(&actionEditInstance); actionChangeInstGroup = TranslatedAction(MainWindow); @@ -689,12 +692,14 @@ public: actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Change Group...")); actionChangeInstGroup.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's group.")); actionChangeInstGroup->setShortcut(QKeySequence(tr("Ctrl+G"))); + actionChangeInstGroup->setIcon(APPLICATION->getThemedIcon("tag")); all_actions.append(&actionChangeInstGroup); actionViewSelectedInstFolder = TranslatedAction(MainWindow); actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder")); actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Folder")); actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser.")); + actionViewSelectedInstFolder->setIcon(APPLICATION->getThemedIcon("viewfolder")); all_actions.append(&actionViewSelectedInstFolder); actionExportInstance = TranslatedAction(MainWindow); @@ -702,6 +707,7 @@ public: actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "E&xport...")); actionExportInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Export the selected instance as a zip file.")); actionExportInstance->setShortcut(QKeySequence(tr("Ctrl+E"))); + actionExportInstance->setIcon(APPLICATION->getThemedIcon("export")); all_actions.append(&actionExportInstance); actionDeleteInstance = TranslatedAction(MainWindow); @@ -710,14 +716,15 @@ public: actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance.")); actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete}); actionDeleteInstance->setAutoRepeat(false); + actionDeleteInstance->setIcon(APPLICATION->getThemedIcon("delete")); all_actions.append(&actionDeleteInstance); actionCopyInstance = TranslatedAction(MainWindow); actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance")); - actionCopyInstance->setIcon(APPLICATION->getThemedIcon("copy")); actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Cop&y...")); actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance.")); actionCopyInstance->setShortcut(QKeySequence(tr("Ctrl+D"))); + actionCopyInstance->setIcon(APPLICATION->getThemedIcon("copy")); all_actions.append(&actionCopyInstance); setInstanceActionsEnabled(false); @@ -1860,7 +1867,7 @@ void MainWindow::globalSettingsClosed() updateToolsMenu(); updateStatusCenter(); // This needs to be done to prevent UI elements disappearing in the event the config is changed - // but PolyMC exits abnormally, causing the window state to never be saved: + // but Prism Launcher exits abnormally, causing the window state to never be saved: APPLICATION->settings()->set("MainWindowState", saveState().toBase64()); update(); } diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index 47f204b3..cecda1df 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -69,7 +69,7 @@ QString getCreditsHtml() #endif stream << "<center>\n"; - //: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Developers" + //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers" stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n"; stream << QString("<p>Sefa Eyeoglu (Scrumplex) %1</p>\n") .arg(getWebsite("https://scrumplex.net")); stream << QString("<p>dada513 %1</p>\n") .arg(getGitHub("dada513")); @@ -79,15 +79,15 @@ QString getCreditsHtml() stream << QString("<p>cozyGalvinism %1</p>\n") .arg(getGitHub("cozyGalvinism")); stream << "<br />\n"; - //: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Contributors" + //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Contributors" stream << "<h3>" << QObject::tr("%1 Contributors", "About Credits").arg(BuildConfig.LAUNCHER_DISPLAYNAME) << "</h3>\n"; - stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio")); - stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln")); - stream << QString("<p>swirl %1</p>\n") .arg(getWebsite("https://swurl.xyz/")); + stream << QString("<p>DioEgizio %1</p>\n") .arg(getGitHub("DioEgizio")); + stream << QString("<p>flowln %1</p>\n") .arg(getGitHub("flowln")); + stream << QString("<p>swirl %1</p>\n") .arg(getWebsite("https://swurl.xyz/")); stream << "<br />\n"; // TODO: possibly retrieve from git history at build time? - //: %1 is the name of the launcher, determined at build time, e.g. "PolyMC Developers" + //: %1 is the name of the launcher, determined at build time, e.g. "Prism Launcher Developers" stream << "<h3>" << QObject::tr("%1 Developers", "About Credits").arg("MultiMC") << "</h3>\n"; stream << "<p>Andrew Okin <<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>></p>\n"; stream << QString("<p>Petr Mrázek <<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>></p>\n"); @@ -97,12 +97,20 @@ QString getCreditsHtml() stream << "<br />\n"; stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n"; + stream << QString("<p>Boba %1</p>\n") .arg(getWebsite("https://cmdplusv.neocities.org/")); + stream << QString("<p>Davi Rafael %1</p>\n") .arg(getWebsite("https://auti.one/")); + stream << QString("<p>Fulmine %1</p>\n") .arg(getWebsite("https://www.fulmine.xyz/")); + stream << QString("<p>ely %1</p>\n") .arg(getGitHub("elyrodso")); + stream << QString("<p>gon sawa %1</p>\n") .arg(getGitHub("gonsawa")); + stream << QString("<p>Pankakes</p>\n"); + stream << QString("<p>tobimori %1</p>\n") .arg(getGitHub("tobimori")); stream << "<p>Orochimarufan <<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>></p>\n"; stream << "<p>TakSuyu <<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>></p>\n"; stream << "<p>Kilobyte <<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>></p>\n"; stream << "<p>Rootbear75 <<a href='https://twitter.com/rootbear75'>@rootbear75</a>></p>\n"; stream << "<p>Zeker Zhayard <<a href='https://twitter.com/zeker_zhayard'>@Zeker_Zhayard</a>></p>\n"; - stream << "<p>Everyone else who <a href='https://github.com/PolyMC/PolyMC/graphs/contributors'>contributed</a>!</p>\n"; + stream << "<p>Everyone who helped establish our branding!</p>\n"; + stream << "<p>And everyone else who <a href='https://github.com/PrismLauncher/PrismLauncher/graphs/contributors'>contributed</a>!</p>\n"; stream << "<br />\n"; stream << "</center>\n"; @@ -164,7 +172,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>"); ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT)); - QString copyText("© 2021-2022 %1"); + QString copyText("© 2022 %1"); ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT)); connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index d203795a..df182f09 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -139,6 +139,10 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString void NewInstanceDialog::reject() { APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); + + // This is just so that the pages get the close() call and can react to it, if needed. + m_container->prepareToClose(); + QDialog::reject(); } @@ -146,6 +150,10 @@ void NewInstanceDialog::accept() { APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); importIconNow(); + + // This is just so that the pages get the close() call and can react to it, if needed. + m_container->prepareToClose(); + QDialog::accept(); } diff --git a/launcher/ui/dialogs/NewsDialog.cpp b/launcher/ui/dialogs/NewsDialog.cpp index d3b21627..e1b5dd74 100644 --- a/launcher/ui/dialogs/NewsDialog.cpp +++ b/launcher/ui/dialogs/NewsDialog.cpp @@ -20,7 +20,9 @@ NewsDialog::NewsDialog(QList<NewsEntryPtr> entries, QWidget* parent) : QDialog(p auto article_entry = m_entries.constFind(first_item->text()).value(); ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, first_item->text())); + ui->currentArticleContentBrowser->setText(article_entry->content); + ui->currentArticleContentBrowser->flush(); } NewsDialog::~NewsDialog() @@ -33,7 +35,9 @@ void NewsDialog::selectedArticleChanged(const QString& new_title) auto const& article_entry = m_entries.constFind(new_title).value(); ui->articleTitleLabel->setText(QString("<a href='%1'>%2</a>").arg(article_entry->link, new_title)); + ui->currentArticleContentBrowser->setText(article_entry->content); + ui->currentArticleContentBrowser->flush(); } void NewsDialog::toggleArticleList() diff --git a/launcher/ui/dialogs/NewsDialog.ui b/launcher/ui/dialogs/NewsDialog.ui index 2aaa08f1..08f35a0b 100644 --- a/launcher/ui/dialogs/NewsDialog.ui +++ b/launcher/ui/dialogs/NewsDialog.ui @@ -49,7 +49,7 @@ </widget> </item> <item> - <widget class="QTextBrowser" name="currentArticleContentBrowser"> + <widget class="ProjectDescriptionPage" name="currentArticleContentBrowser"> <property name="textInteractionFlags"> <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> </property> @@ -91,6 +91,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>ProjectDescriptionPage</class> + <extends>QTextBrowser</extends> + <header>ui/widgets/ProjectDescriptionPage.h</header> + </customwidget> + </customwidgets> <resources/> <connections> <connection> diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 258a32e4..05269f62 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -25,6 +25,7 @@ ProgressDialog::ProgressDialog(QWidget* parent) : QDialog(parent), ui(new Ui::Pr { ui->setupUi(this); this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); + setAttribute(Qt::WidgetAttribute::WA_QuitOnClose, true); setSkipButton(false); changeProgress(0, 100); } @@ -67,7 +68,7 @@ int ProgressDialog::execWithTask(Task* task) return QDialog::DialogCode::Accepted; } - QDialog::DialogCode result; + QDialog::DialogCode result {}; if (handleImmediateResult(result)) { return result; } @@ -80,7 +81,7 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); connect(task, &Task::progress, this, &ProgressDialog::changeProgress); - connect(task, &Task::aborted, [this] { QDialog::reject(); }); + connect(task, &Task::aborted, this, &ProgressDialog::hide); connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); m_is_multi_step = task->isMultiStep(); @@ -135,11 +136,13 @@ void ProgressDialog::onTaskStarted() {} void ProgressDialog::onTaskFailed(QString failure) { reject(); + hide(); } void ProgressDialog::onTaskSucceeded() { accept(); + hide(); } void ProgressDialog::changeStatus(const QString& status) diff --git a/launcher/ui/dialogs/UpdateDialog.cpp b/launcher/ui/dialogs/UpdateDialog.cpp index e0c5a495..9e82531a 100644 --- a/launcher/ui/dialogs/UpdateDialog.cpp +++ b/launcher/ui/dialogs/UpdateDialog.cpp @@ -73,12 +73,12 @@ void UpdateDialog::loadChangelog() QString url; if(channel == "stable") { - url = QString("https://raw.githubusercontent.com/PolyMC/PolyMC/%1/changelog.md").arg(channel); + url = QString("https://raw.githubusercontent.com/PrismLauncher/PrismLauncher/%1/changelog.md").arg(channel); m_changelogType = CHANGELOG_MARKDOWN; } else { - url = QString("https://api.github.com/repos/PolyMC/PolyMC/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); + url = QString("https://api.github.com/repos/PrismLauncher/PrismLauncher/compare/%1...%2").arg(BuildConfig.GIT_COMMIT, channel); m_changelogType = CHANGELOG_COMMITS; } dljob->addNetAction(Net::Download::makeByteArray(QUrl(url), &changelogData)); @@ -93,7 +93,7 @@ QString reprocessMarkdown(QByteArray markdown) QString output = hoedown.process(markdown); // HACK: easier than customizing hoedown - output.replace(QRegularExpression("GH-([0-9]+)"), "<a href=\"https://github.com/PolyMC/PolyMC/issues/\\1\">GH-\\1</a>"); + output.replace(QRegularExpression("GH-([0-9]+)"), "<a href=\"https://github.com/PrismLauncher/PrismLauncher/issues/\\1\">GH-\\1</a>"); qDebug() << output; return output; } @@ -135,7 +135,7 @@ QString reprocessCommits(QByteArray json) result += "<tr><td>"; if(issuenr.length()) { - result += QString("<a href=\"https://github.com/PolyMC/PolyMC/issues/%1\">GH-%2</a>").arg(issuenr, issuenr); + result += QString("<a href=\"https://github.com/PrismLauncher/PrismLauncher/issues/%1\">GH-%2</a>").arg(issuenr, issuenr); } else if(prefix.length()) { diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 1e5df5b2..b8431e8c 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -147,7 +147,7 @@ void LauncherPage::on_instDirBrowseBtn_clicked() { QMessageBox warning; warning.setText(tr("You're trying to specify an instance folder " - "which was granted temporaily via Flatpak.\n" + "which was granted temporarily via Flatpak.\n" "This is known to cause problems. " "After a restart the launcher might break, " "because it will no longer have access to that directory.\n\n" diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index f31e8325..b6c873cc 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -103,10 +103,6 @@ void ExternalResourcesPage::runningStateChanged(bool running) return; m_controlsEnabled = !running; - ui->actionAddItem->setEnabled(m_controlsEnabled); - ui->actionDisableItem->setEnabled(m_controlsEnabled); - ui->actionEnableItem->setEnabled(m_controlsEnabled); - ui->actionRemoveItem->setEnabled(m_controlsEnabled); } bool ExternalResourcesPage::shouldDisplay() const diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 28a874c2..f0106066 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -117,6 +117,10 @@ void ModFolderPage::runningStateChanged(bool running) ExternalResourcesPage::runningStateChanged(running); ui->actionDownloadItem->setEnabled(!running); ui->actionUpdateItem->setEnabled(!running); + ui->actionAddItem->setEnabled(!running); + ui->actionEnableItem->setEnabled(!running); + ui->actionDisableItem->setEnabled(!running); + ui->actionRemoveItem->setEnabled(!running); } bool ModFolderPage::shouldDisplay() const diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 2af9a10a..f2c1746f 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -352,4 +352,5 @@ void ModPage::updateUi() HoeDown h; ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8()))); + ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui index afcd9bb7..943f02aa 100644 --- a/launcher/ui/pages/modplatform/ModPage.ui +++ b/launcher/ui/pages/modplatform/ModPage.ui @@ -14,7 +14,7 @@ <item row="1" column="0" colspan="4"> <layout class="QGridLayout" name="gridLayout_3"> <item row="1" column="2"> - <widget class="QTextBrowser" name="packDescription"> + <widget class="ProjectDescriptionPage" name="packDescription"> <property name="openExternalLinks"> <bool>true</bool> </property> @@ -98,6 +98,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>ProjectDescriptionPage</class> + <extends>QTextBrowser</extends> + <header>ui/widgets/ProjectDescriptionPage.h</header> + </customwidget> + </customwidgets> <tabstops> <tabstop>searchEdit</tabstop> <tabstop>searchButton</tabstop> diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index 03196685..c68e40ba 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -43,10 +43,13 @@ AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget *parent) : { } -QVector<QString> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) +std::optional<QVector<QString>> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) { AtlOptionalModDialog optionalModDialog(m_parent, version, mods); - optionalModDialog.exec(); + auto result = optionalModDialog.exec(); + if (result == QDialog::Rejected) { + return {}; + } return optionalModDialog.getResult(); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h index aa22fc73..3b37c9be 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -47,7 +47,7 @@ public: private: QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; - QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override; + std::optional<QVector<QString>> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override; void displayMessage(QString message) override; private: diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 54a7be04..fd6e32ff 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -59,6 +59,8 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance) connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameModPage::onVersionSelectionChanged); connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected); + + ui->packDescription->setMetaEntry(metaEntryBase()); } auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index b9804681..9f8605eb 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -1,6 +1,7 @@ #include "FlameModel.h" #include <Json.h> #include "Application.h" +#include "ui/widgets/ProjectItem.h" #include <MMCStrings.h> #include <Version.h> @@ -31,29 +32,38 @@ QVariant ListModel::data(const QModelIndex& index, int role) const } IndexedPack pack = modpacks.at(pos); - if (role == Qt::DisplayRole) { - return pack.name; - } else if (role == Qt::ToolTipRole) { - if (pack.description.length() > 100) { - // some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); - edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); - return edit; + switch (role) { + case Qt::ToolTipRole: { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + } + return pack.description; + } case Qt::DecorationRole: { + if (m_logoMap.contains(pack.logoName)) { + return (m_logoMap.value(pack.logoName)); + } + QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); + return icon; + } case Qt::UserRole: { + QVariant v; + v.setValue(pack); + return v; } - return pack.description; - } else if (role == Qt::DecorationRole) { - if (m_logoMap.contains(pack.logoName)) { - return (m_logoMap.value(pack.logoName)); - } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); - return icon; - } else if (role == Qt::UserRole) { - QVariant v; - v.setValue(pack); - return v; + case Qt::SizeHintRole: + return QSize(0, 58); + case UserDataTypes::TITLE: + return pack.name; + case UserDataTypes::DESCRIPTION: + return pack.description; + case UserDataTypes::SELECTED: + return false; + default: + break; } - return QVariant(); } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp index 7d2ba2e2..a65b6585 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp @@ -43,6 +43,10 @@ #include "InstanceImportTask.h" #include "Json.h" #include "ui/dialogs/NewInstanceDialog.h" +#include "ui/widgets/ProjectItem.h" +#include "modplatform/flame/FlameAPI.h" + +static FlameAPI api; FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) { @@ -66,6 +70,9 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlamePage::onVersionSelectionChanged); + + ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + ui->packDescription->setMetaEntry("FlamePacks"); } FlamePage::~FlamePage() @@ -250,7 +257,10 @@ void FlamePage::updateUi() text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>"; } + text += "<hr>"; + text += api.getModDescription(current.addonId).toUtf8(); ui->packDescription->setHtml(text + current.description); + ui->packDescription->flush(); } diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui index 1a3d0225..71d19513 100644 --- a/launcher/ui/pages/modplatform/flame/FlamePage.ui +++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui @@ -66,7 +66,7 @@ </widget> </item> <item> - <widget class="QTextBrowser" name="packDescription"> + <widget class="ProjectDescriptionPage" name="packDescription"> <property name="openExternalLinks"> <bool>true</bool> </property> @@ -99,6 +99,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>ProjectDescriptionPage</class> + <extends>QTextBrowser</extends> + <header>ui/widgets/ProjectDescriptionPage.h</header> + </customwidget> + </customwidgets> <tabstops> <tabstop>packView</tabstop> <tabstop>packDescription</tabstop> diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp index ad15b6e6..3a149944 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp @@ -103,6 +103,8 @@ void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallbac void ListModel::request() { + m_aborted = false; + beginResetModel(); modpacks.clear(); endResetModel(); @@ -117,6 +119,12 @@ void ListModel::request() QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed); } +void ListModel::abortRequest() +{ + m_aborted = jobPtr->abort(); + jobPtr.reset(); +} + void ListModel::requestFinished() { jobPtr.reset(); @@ -162,6 +170,9 @@ void ListModel::requestPack() void ListModel::packRequestFinished() { + if (!jobPtr || m_aborted) + return; + jobPtr.reset(); remainingPacks.removeOne(currentPack); diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.h b/launcher/ui/pages/modplatform/ftb/FtbListModel.h index 314cb789..d7a120f0 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbListModel.h +++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.h @@ -47,9 +47,13 @@ public: QVariant data(const QModelIndex &index, int role) const override; void request(); + void abortRequest(); void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback); + [[nodiscard]] bool isMakingRequest() const { return jobPtr.get(); } + [[nodiscard]] bool wasAborted() const { return m_aborted; } + private slots: void requestFinished(); void requestFailed(QString reason); @@ -65,6 +69,8 @@ private: void requestLogo(QString file, QString url); private: + bool m_aborted = false; + QList<ModpacksCH::Modpack> modpacks; LogoMap m_logoMap; diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp index 8975d74e..b08f3bc4 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp @@ -73,6 +73,8 @@ FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent) connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged); + + ui->packDescription->setMetaEntry("FTBPacks"); } FtbPage::~FtbPage() @@ -105,7 +107,7 @@ void FtbPage::retranslate() void FtbPage::openedImpl() { - if(!initialised) + if(!initialised || listModel->wasAborted()) { listModel->request(); initialised = true; @@ -114,6 +116,12 @@ void FtbPage::openedImpl() suggestCurrent(); } +void FtbPage::closedImpl() +{ + if (listModel->isMakingRequest()) + listModel->abortRequest(); +} + void FtbPage::suggestCurrent() { if(!isOpened) diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.h b/launcher/ui/pages/modplatform/ftb/FtbPage.h index 90c8e7fd..631ae7f5 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.h +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.h @@ -78,6 +78,7 @@ public: void retranslate() override; void openedImpl() override; + void closedImpl() override; bool eventFilter(QObject * watched, QEvent * event) override; diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.ui b/launcher/ui/pages/modplatform/ftb/FtbPage.ui index 850bf091..8de0f4e6 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.ui +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.ui @@ -57,7 +57,7 @@ </widget> </item> <item row="0" column="1"> - <widget class="QTextBrowser" name="packDescription"> + <widget class="ProjectDescriptionPage" name="packDescription"> <property name="openExternalLinks"> <bool>true</bool> </property> @@ -70,6 +70,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>ProjectDescriptionPage</class> + <extends>QTextBrowser</extends> + <header>ui/widgets/ProjectDescriptionPage.h</header> + </customwidget> + </customwidgets> <tabstops> <tabstop>searchEdit</tabstop> <tabstop>versionSelectionBox</tabstop> diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp index 5fa00b9b..62e417c8 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp @@ -59,6 +59,8 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instan connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected); + + ui->packDescription->setMetaEntry(metaEntryBase()); } auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 43fda9de..e6704eef 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -218,7 +218,7 @@ void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, Logo { if (m_logoMap.contains(logo)) { callback(APPLICATION->metacache() - ->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))) + ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))) ->getFullPath()); } else { requestLogo(logo, logoUrl); @@ -232,7 +232,7 @@ void ModpackListModel::requestLogo(QString logo, QString url) } MetaEntryPtr entry = - APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); + APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0))); auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index cea6cdee..4482774c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -74,6 +74,7 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged); ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + ui->packDescription->setMetaEntry(metaEntryBase()); } ModrinthPage::~ModrinthPage() @@ -283,6 +284,7 @@ void ModrinthPage::updateUI() text += h.process(current.extra.body.toUtf8()); ui->packDescription->setHtml(text + current.description); + ui->packDescription->flush(); } void ModrinthPage::suggestCurrent() diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui index 6a34701d..6d8b2b67 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -66,7 +66,7 @@ </widget> </item> <item> - <widget class="QTextBrowser" name="packDescription"> + <widget class="ProjectDescriptionPage" name="packDescription"> <property name="openExternalLinks"> <bool>true</bool> </property> @@ -99,6 +99,13 @@ </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>ProjectDescriptionPage</class> + <extends>QTextBrowser</extends> + <header>ui/widgets/ProjectDescriptionPage.h</header> + </customwidget> + </customwidgets> <tabstops> <tabstop>searchEdit</tabstop> <tabstop>searchButton</tabstop> diff --git a/launcher/ui/widgets/ProjectDescriptionPage.cpp b/launcher/ui/widgets/ProjectDescriptionPage.cpp new file mode 100644 index 00000000..c7e79a17 --- /dev/null +++ b/launcher/ui/widgets/ProjectDescriptionPage.cpp @@ -0,0 +1,23 @@ +#include "ProjectDescriptionPage.h" + +#include "VariableSizedImageObject.h" + +#include <QDebug> + +ProjectDescriptionPage::ProjectDescriptionPage(QWidget* parent) : QTextBrowser(parent), m_image_text_object(new VariableSizedImageObject) +{ + m_image_text_object->setParent(this); + document()->documentLayout()->registerHandler(QTextFormat::ImageObject, m_image_text_object.get()); +} + +void ProjectDescriptionPage::setMetaEntry(QString entry) +{ + if (m_image_text_object) + m_image_text_object->setMetaEntry(entry); +} + +void ProjectDescriptionPage::flush() +{ + if (m_image_text_object) + m_image_text_object->flush(); +} diff --git a/launcher/ui/widgets/ProjectDescriptionPage.h b/launcher/ui/widgets/ProjectDescriptionPage.h new file mode 100644 index 00000000..3dd85302 --- /dev/null +++ b/launcher/ui/widgets/ProjectDescriptionPage.h @@ -0,0 +1,32 @@ +#pragma once + +#include <QTextBrowser> + +#include "QObjectPtr.h" + +QT_BEGIN_NAMESPACE +class VariableSizedImageObject; +QT_END_NAMESPACE + +/** This subclasses QTextBrowser to provide additional capabilities + * to it, like allowing for images to be shown. + */ +class ProjectDescriptionPage final : public QTextBrowser { + Q_OBJECT + + public: + ProjectDescriptionPage(QWidget* parent = nullptr); + + void setMetaEntry(QString entry); + + public slots: + /** Flushes the current processing happening in the page. + * + * Should be called when changing the page's content entirely, to + * prevent old tasks from changing the new content. + */ + void flush(); + + private: + shared_qobject_ptr<VariableSizedImageObject> m_image_text_object; +}; diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 01be88d9..d1ff9dbc 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -51,6 +51,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o auto remaining_width = rect.width() - icon_width - 2 * icon_x_margin; rect.setRect(rect.x() + icon_width + 2 * icon_x_margin, rect.y(), remaining_width, rect.height()); + int title_height = 0; + { // Title painting auto title = index.data(UserDataTypes::TITLE).toString(); @@ -66,8 +68,10 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o font.setPointSize(font.pointSize() + 2); painter->setFont(font); + title_height = QFontMetrics(font).height(); + // On the top, aligned to the left after the icon - painter->drawText(rect.x(), rect.y() + QFontMetrics(font).height(), title); + painter->drawText(rect.x(), rect.y() + title_height, title); painter->restore(); } @@ -82,17 +86,38 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o // Get first line unconditionally description = cut_text.first().second; + auto num_lines = 1; + // Get second line, elided if needed if (cut_text.size() > 1) { - if (cut_text.size() > 2) - description += opt.fontMetrics.elidedText(cut_text.at(1).second, opt.textElideMode, cut_text.at(1).first); - else - description += cut_text.at(1).second; + // 2.5x so because there should be some margin left from the 2x so things don't get too squishy. + if (rect.height() - title_height <= 2.5 * opt.fontMetrics.height()) { + // If there's not enough space, show only a single line, elided. + description = opt.fontMetrics.elidedText(description, opt.textElideMode, cut_text.at(0).first); + } else { + if (cut_text.size() > 2) { + description += opt.fontMetrics.elidedText(cut_text.at(1).second, opt.textElideMode, cut_text.at(1).first); + } else { + description += cut_text.at(1).second; + } + num_lines += 1; + } } + int description_x = rect.x(); + + + // Have the y-value be set based on the number of lines in the description, to centralize the + // description text with the space between the base and the title. + int description_y = rect.y() + title_height + (rect.height() - title_height) / 2; + if (num_lines == 1) + description_y -= opt.fontMetrics.height() / 2; + else + description_y -= opt.fontMetrics.height(); + // On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare) - painter->drawText(rect.x(), rect.y() + rect.height() - 2.2 * opt.fontMetrics.height(), remaining_width, - 2 * opt.fontMetrics.height(), Qt::TextWordWrap, description); + painter->drawText(description_x, description_y, remaining_width, + cut_text.size() * opt.fontMetrics.height(), Qt::TextWordWrap, description); } painter->restore(); diff --git a/launcher/ui/widgets/VariableSizedImageObject.cpp b/launcher/ui/widgets/VariableSizedImageObject.cpp new file mode 100644 index 00000000..e57f7e95 --- /dev/null +++ b/launcher/ui/widgets/VariableSizedImageObject.cpp @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln <flowlnlnln@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "VariableSizedImageObject.h" + +#include <QAbstractTextDocumentLayout> +#include <QDebug> +#include <QPainter> +#include <QTextObject> + +#include "Application.h" + +#include "net/NetJob.h" + +enum FormatProperties { ImageData = QTextFormat::UserProperty + 1 }; + +QSizeF VariableSizedImageObject::intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format) +{ + Q_UNUSED(posInDocument); + + auto image = qvariant_cast<QImage>(format.property(ImageData)); + auto size = image.size(); + + // Get the width of the text content to make the image similar sized. + // doc->textWidth() includes the margin, so we need to remove it. + auto doc_width = doc->textWidth() - 2 * doc->documentMargin(); + + if (size.width() > doc_width) + size *= doc_width / (double)size.width(); + + return { size }; +} +void VariableSizedImageObject::drawObject(QPainter* painter, + const QRectF& rect, + QTextDocument* doc, + int posInDocument, + const QTextFormat& format) +{ + if (!format.hasProperty(ImageData)) { + QUrl image_url{ qvariant_cast<QString>(format.property(QTextFormat::ImageName)) }; + if (m_fetching_images.contains(image_url)) + return; + + loadImage(doc, image_url, posInDocument); + return; + } + + auto image = qvariant_cast<QImage>(format.property(ImageData)); + + painter->setRenderHint(QPainter::RenderHint::SmoothPixmapTransform); + painter->drawImage(rect, image); +} + +void VariableSizedImageObject::flush() +{ + m_fetching_images.clear(); +} + +void VariableSizedImageObject::parseImage(QTextDocument* doc, QImage image, int posInDocument) +{ + QTextCursor cursor(doc); + cursor.setPosition(posInDocument); + cursor.setKeepPositionOnInsert(true); + + auto image_char_format = cursor.charFormat(); + + image_char_format.setObjectType(QTextFormat::ImageObject); + image_char_format.setProperty(ImageData, image); + + // Qt doesn't allow us to modify the properties of an existing object in the document. + // So we remove the old one and add the new one with the ImageData property set. + cursor.deleteChar(); + cursor.insertText(QString(QChar::ObjectReplacementCharacter), image_char_format); +} + +void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source, int posInDocument) +{ + m_fetching_images.insert(source); + + MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry( + m_meta_entry, + QString("images/%1").arg(QString(QCryptographicHash::hash(source.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); + + auto job = new NetJob(QString("Load Image: %1").arg(source.fileName()), APPLICATION->network()); + job->addNetAction(Net::Download::makeCached(source, entry)); + + auto full_entry_path = entry->getFullPath(); + auto source_url = source; + connect(job, &NetJob::succeeded, [this, doc, full_entry_path, source_url, posInDocument] { + qDebug() << "Loaded resource at" << full_entry_path; + + // If we flushed, don't proceed. + if (!m_fetching_images.contains(source_url)) + return; + + QImage image(full_entry_path); + doc->addResource(QTextDocument::ImageResource, source_url, image); + + parseImage(doc, image, posInDocument); + + // This size hack is needed to prevent the content from being laid out in an area smaller + // than the total width available (weird). + auto size = doc->pageSize(); + doc->adjustSize(); + doc->setPageSize(size); + + m_fetching_images.remove(source_url); + }); + connect(job, &NetJob::finished, job, &NetJob::deleteLater); + + job->start(); +} diff --git a/launcher/ui/widgets/VariableSizedImageObject.h b/launcher/ui/widgets/VariableSizedImageObject.h new file mode 100644 index 00000000..137487ee --- /dev/null +++ b/launcher/ui/widgets/VariableSizedImageObject.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln <flowlnlnln@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <QObject> +#include <QString> +#include <QTextObjectInterface> +#include <QUrl> + +/** Custom image text object to be used instead of the normal one in ProjectDescriptionPage. + * + * Why? Because we want to re-scale images dynamically based on the document's size, in order to + * not have images being weirdly cropped out in different resolutions. + */ +class VariableSizedImageObject final : public QObject, public QTextObjectInterface { + Q_OBJECT + Q_INTERFACES(QTextObjectInterface) + + public: + QSizeF intrinsicSize(QTextDocument* doc, int posInDocument, const QTextFormat& format) override; + void drawObject(QPainter* painter, const QRectF& rect, QTextDocument* doc, int posInDocument, const QTextFormat& format) override; + + void setMetaEntry(QString meta_entry) { m_meta_entry = meta_entry; } + + public slots: + /** Stops all currently loading images from modifying the document. + * + * This does not stop the ongoing network tasks, it only prevents their result + * from impacting the document any further. + */ + void flush(); + + private: + /** Adds the image to the document, in the given position. + */ + void parseImage(QTextDocument* doc, QImage image, int posInDocument); + + /** Loads an image from an external source, and adds it to the document. + * + * This uses m_meta_entry to cache the image. + */ + void loadImage(QTextDocument* doc, const QUrl& source, int posInDocument); + + private: + QString m_meta_entry; + + QSet<QUrl> m_fetching_images; +}; |