diff options
Diffstat (limited to 'launcher/ui')
47 files changed, 1915 insertions, 228 deletions
diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index 09ce0d67..c62b370f 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -132,6 +132,12 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) { connect(m_instance.get(), &BaseInstance::statusChanged, this, &InstanceWindow::on_instanceStatusChanged); } + + // add ourself as the modpack page's instance window + { + static_cast<ManagedPackPage*>(m_container->getPage("managed_pack"))->setInstanceWindow(this); + } + show(); } diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 85b00b67..cc81d8e3 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -39,6 +39,7 @@ #include "Application.h" #include "BuildConfig.h" +#include "FileSystem.h" #include "MainWindow.h" @@ -49,7 +50,7 @@ #include <QKeyEvent> #include <QAction> - +#include <QActionGroup> #include <QApplication> #include <QButtonGroup> #include <QHBoxLayout> @@ -61,6 +62,7 @@ #include <QMenu> #include <QMenuBar> #include <QMessageBox> +#include <QFileDialog> #include <QInputDialog> #include <QLabel> #include <QToolButton> @@ -70,6 +72,7 @@ #include <BaseInstance.h> #include <InstanceList.h> +#include <minecraft/MinecraftInstance.h> #include <MMCZip.h> #include <icons/IconList.h> #include <java/JavaUtils.h> @@ -105,6 +108,13 @@ #include "ui/dialogs/UpdateDialog.h" #include "ui/dialogs/EditAccountDialog.h" #include "ui/dialogs/ExportInstanceDialog.h" +#include "ui/dialogs/ImportResourcePackDialog.h" +#include "ui/themes/ITheme.h" + +#include <minecraft/mod/ResourcePackFolderModel.h> +#include <minecraft/mod/tasks/LocalResourcePackParseTask.h> +#include <minecraft/mod/TexturePackFolderModel.h> +#include <minecraft/mod/tasks/LocalTexturePackParseTask.h> #include "UpdateController.h" #include "KonamiCode.h" @@ -237,6 +247,7 @@ public: TranslatedAction actionLaunchInstanceOffline; TranslatedAction actionLaunchInstanceDemo; TranslatedAction actionExportInstance; + TranslatedAction actionCreateInstanceShortcut; QVector<TranslatedAction *> all_actions; LabeledToolButton *renameButton = nullptr; @@ -253,6 +264,9 @@ public: QMenu * helpMenu = nullptr; TranslatedToolButton helpMenuButton; TranslatedAction actionClearMetadata; + #ifdef Q_OS_MAC + TranslatedAction actionAddToPATH; + #endif TranslatedAction actionReportBug; TranslatedAction actionDISCORD; TranslatedAction actionMATRIX; @@ -264,6 +278,8 @@ public: TranslatedAction actionLockToolbars; + TranslatedAction actionChangeTheme; + QVector<TranslatedToolButton *> all_toolbuttons; QWidget *centralWidget = nullptr; @@ -290,7 +306,6 @@ public: actionAddInstance = TranslatedAction(MainWindow); actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); actionAddInstance->setIcon(APPLICATION->getThemedIcon("new")); - actionAddInstance->setIconVisibleInMenu(false); actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instanc&e...")); actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance.")); actionAddInstance->setShortcut(QKeySequence::New); @@ -350,6 +365,14 @@ public: actionClearMetadata.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Clear cached metadata")); all_actions.append(&actionClearMetadata); + #ifdef Q_OS_MAC + actionAddToPATH = TranslatedAction(MainWindow); + actionAddToPATH->setObjectName(QStringLiteral("actionAddToPATH")); + actionAddToPATH.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Install to &PATH")); + actionAddToPATH.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Install a prismlauncher symlink to /usr/local/bin")); + all_actions.append(&actionAddToPATH); + #endif + if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { actionReportBug = TranslatedAction(MainWindow); actionReportBug->setObjectName(QStringLiteral("actionReportBug")); @@ -428,6 +451,11 @@ public: actionLockToolbars.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Lock Toolbars")); actionLockToolbars->setCheckable(true); all_actions.append(&actionLockToolbars); + + actionChangeTheme = TranslatedAction(MainWindow); + actionChangeTheme->setObjectName(QStringLiteral("actionChangeTheme")); + actionChangeTheme.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Themes")); + all_actions.append(&actionChangeTheme); } void createMainToolbar(QMainWindow *MainWindow) @@ -455,6 +483,10 @@ public: helpMenu->addAction(actionClearMetadata); + #ifdef Q_OS_MAC + helpMenu->addAction(actionAddToPATH); + #endif + if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { helpMenu->addAction(actionReportBug); } @@ -509,8 +541,6 @@ public: fileMenu->setSeparatorsCollapsible(false); fileMenu->addAction(actionAddInstance); fileMenu->addAction(actionLaunchInstance); - fileMenu->addAction(actionLaunchInstanceOffline); - fileMenu->addAction(actionLaunchInstanceDemo); fileMenu->addAction(actionKillInstance); fileMenu->addAction(actionCloseWindow); fileMenu->addSeparator(); @@ -518,8 +548,9 @@ public: fileMenu->addAction(actionChangeInstGroup); fileMenu->addAction(actionViewSelectedInstFolder); fileMenu->addAction(actionExportInstance); - fileMenu->addAction(actionDeleteInstance); fileMenu->addAction(actionCopyInstance); + fileMenu->addAction(actionDeleteInstance); + fileMenu->addAction(actionCreateInstanceShortcut); fileMenu->addSeparator(); fileMenu->addAction(actionSettings); @@ -528,6 +559,8 @@ public: viewMenu = menuBar->addMenu(tr("&View")); viewMenu->setSeparatorsCollapsible(false); + viewMenu->addAction(actionChangeTheme); + viewMenu->addSeparator(); viewMenu->addAction(actionCAT); viewMenu->addSeparator(); @@ -542,6 +575,9 @@ public: helpMenu = menuBar->addMenu(tr("&Help")); helpMenu->setSeparatorsCollapsible(false); helpMenu->addAction(actionClearMetadata); + #ifdef Q_OS_MAC + helpMenu->addAction(actionAddToPATH); + #endif helpMenu->addSeparator(); helpMenu->addAction(actionAbout); helpMenu->addAction(actionOpenWiki); @@ -555,10 +591,11 @@ public: helpMenu->addAction(actionDISCORD); if (!BuildConfig.SUBREDDIT_URL.isEmpty()) helpMenu->addAction(actionREDDIT); - helpMenu->addSeparator(); if(BuildConfig.UPDATER_ENABLED) + { + helpMenu->addSeparator(); helpMenu->addAction(actionCheckUpdate); - + } MainWindow->setMenuBar(menuBar); } @@ -576,6 +613,7 @@ public: actionOpenWiki->setObjectName(QStringLiteral("actionOpenWiki")); actionOpenWiki.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &Help")); actionOpenWiki.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki")); + actionOpenWiki->setIcon(APPLICATION->getThemedIcon("help")); connect(actionOpenWiki, &QAction::triggered, MainWindow, &MainWindow::on_actionOpenWiki_triggered); all_actions.append(&actionOpenWiki); @@ -583,6 +621,7 @@ public: actionNewsMenuBar->setObjectName(QStringLiteral("actionNewsMenuBar")); actionNewsMenuBar.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &News")); actionNewsMenuBar.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki")); + actionNewsMenuBar->setIcon(APPLICATION->getThemedIcon("news")); connect(actionNewsMenuBar, &QAction::triggered, MainWindow, &MainWindow::on_actionMoreNews_triggered); all_actions.append(&actionNewsMenuBar); } @@ -597,6 +636,7 @@ public: actionExportInstance->setEnabled(enabled); actionDeleteInstance->setEnabled(enabled); actionCopyInstance->setEnabled(enabled); + actionCreateInstanceShortcut->setEnabled(enabled); } void createStatusBar(QMainWindow *MainWindow) @@ -735,6 +775,15 @@ public: actionCopyInstance->setIcon(APPLICATION->getThemedIcon("copy")); all_actions.append(&actionCopyInstance); + actionCreateInstanceShortcut = TranslatedAction(MainWindow); + actionCreateInstanceShortcut->setObjectName(QStringLiteral("actionCreateInstanceShortcut")); + actionCreateInstanceShortcut.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Create Shortcut")); + actionCreateInstanceShortcut.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Creates a shortcut on your desktop to launch the selected instance.")); + //actionCreateInstanceShortcut->setShortcut(QKeySequence(tr("Ctrl+D"))); // TODO + // FIXME missing on Legacy, Flat and Flat (White) + actionCreateInstanceShortcut->setIcon(APPLICATION->getThemedIcon("shortcut")); + all_actions.append(&actionCreateInstanceShortcut); + setInstanceActionsEnabled(false); } @@ -773,6 +822,8 @@ public: instanceToolBar->addAction(actionCopyInstance); instanceToolBar->addAction(actionDeleteInstance); + instanceToolBar->addAction(actionCreateInstanceShortcut); // TODO find better position for this + QLayout * lay = instanceToolBar->layout(); for(int i = 0; i < lay->count(); i++) { @@ -822,6 +873,7 @@ public: createInstanceToolbar(MainWindow); MainWindow->updateToolsMenu(); + MainWindow->updateThemeMenu(); retranslateUi(MainWindow); @@ -1271,6 +1323,38 @@ void MainWindow::updateToolsMenu() ui->actionLaunchInstance->setMenu(launchMenu); } +void MainWindow::updateThemeMenu() +{ + QMenu *themeMenu = ui->actionChangeTheme->menu(); + + if (themeMenu) { + themeMenu->clear(); + } else { + themeMenu = new QMenu(this); + } + + auto themes = APPLICATION->getValidApplicationThemes(); + + QActionGroup* themesGroup = new QActionGroup( this ); + + for (auto* theme : themes) { + QAction * themeAction = themeMenu->addAction(theme->name()); + + themeAction->setCheckable(true); + if (APPLICATION->settings()->get("ApplicationTheme").toString() == theme->id()) { + themeAction->setChecked(true); + } + themeAction->setActionGroup(themesGroup); + + connect(themeAction, &QAction::triggered, [theme]() { + APPLICATION->setApplicationTheme(theme->id(),false); + APPLICATION->settings()->set("ApplicationTheme", theme->id()); + }); + } + + ui->actionChangeTheme->setMenu(themeMenu); +} + void MainWindow::repopulateAccountsMenu() { accountMenu->clear(); @@ -1732,17 +1816,41 @@ void MainWindow::on_actionAddInstance_triggered() void MainWindow::droppedURLs(QList<QUrl> urls) { - for(auto & url:urls) - { - if(url.isLocalFile()) - { - addInstance(url.toLocalFile()); - } - else - { + // NOTE: This loop only processes one dropped file! + for (auto& url : urls) { + // The isLocalFile() check below doesn't work as intended without an explicit scheme. + if (url.scheme().isEmpty()) + url.setScheme("file"); + + if (!url.isLocalFile()) { // probably instance/modpack addInstance(url.toString()); + break; } - // Only process one dropped file... + + auto localFileName = url.toLocalFile(); + QFileInfo localFileInfo(localFileName); + + bool isResourcePack = ResourcePackUtils::validate(localFileInfo); + bool isTexturePack = TexturePackUtils::validate(localFileInfo); + + if (!isResourcePack && !isTexturePack) { // probably instance/modpack + addInstance(localFileName); + break; + } + + ImportResourcePackDialog dlg(this); + + if (dlg.exec() != QDialog::Accepted) + break; + + qDebug() << "Adding resource/texture pack" << localFileName << "to" << dlg.selectedInstanceKey; + + auto inst = APPLICATION->instances()->getInstanceById(dlg.selectedInstanceKey); + auto minecraftInst = std::dynamic_pointer_cast<MinecraftInstance>(inst); + if (isResourcePack) + minecraftInst->resourcePackList()->installResource(localFileName); + else if (isTexturePack) + minecraftInst->texturePackList()->installResource(localFileName); break; } } @@ -1901,6 +2009,7 @@ void MainWindow::globalSettingsClosed() proxymodel->sort(0); updateMainToolBar(); updateToolsMenu(); + updateThemeMenu(); updateStatusCenter(); // This needs to be done to prevent UI elements disappearing in the event the config is changed // but Prism Launcher exits abnormally, causing the window state to never be saved: @@ -1929,6 +2038,29 @@ void MainWindow::on_actionClearMetadata_triggered() APPLICATION->metacache()->SaveNow(); } +#ifdef Q_OS_MAC +void MainWindow::on_actionAddToPATH_triggered() +{ + auto binaryPath = APPLICATION->applicationFilePath(); + auto targetPath = QString("/usr/local/bin/%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); + qDebug() << "Symlinking" << binaryPath << "to" << targetPath; + + QStringList args; + args << "-e"; + args << QString("do shell script \"mkdir -p /usr/local/bin && ln -sf '%1' '%2'\" with administrator privileges") + .arg(binaryPath, targetPath); + auto outcome = QProcess::execute("/usr/bin/osascript", args); + if (!outcome) { + QMessageBox::information(this, tr("Successfully added %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME), + tr("%1 was successfully added to your PATH. You can now start it by running `%2`.") + .arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.LAUNCHER_APP_BINARY_NAME)); + } else { + QMessageBox::critical(this, tr("Failed to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME), + tr("An error occurred while trying to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME)); + } +} +#endif + void MainWindow::on_actionOpenWiki_triggered() { DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(""))); @@ -1957,27 +2089,25 @@ void MainWindow::on_actionAbout_triggered() void MainWindow::on_actionDeleteInstance_triggered() { - if (!m_selectedInstance) - { + if (!m_selectedInstance) { return; } auto id = m_selectedInstance->id(); - if (APPLICATION->instances()->trashInstance(id)) { - ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); - return; - } - - auto response = CustomMessageBox::selectable( - this, - tr("CAREFUL!"), - tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()), - QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No, - QMessageBox::No - )->exec(); - if (response == QMessageBox::Yes) - { + + auto response = + CustomMessageBox::selectable(this, tr("CAREFUL!"), + tr("About to delete: %1\nThis may be permanent and will completely delete the instance.\n\nAre you sure?") + .arg(m_selectedInstance->name()), + QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + ->exec(); + + if (response == QMessageBox::Yes) { + if (APPLICATION->instances()->trashInstance(id)) { + ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); + return; + } + APPLICATION->instances()->deleteInstance(id); } } @@ -2075,6 +2205,145 @@ void MainWindow::on_actionKillInstance_triggered() } } +void MainWindow::on_actionCreateInstanceShortcut_triggered() +{ + if (m_selectedInstance) + { + auto desktopPath = FS::getDesktopDir(); + if (desktopPath.isEmpty()) { + // TODO come up with an alternative solution (open "save file" dialog) + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Couldn't find desktop?!")); + return; + } + +#if defined(Q_OS_MACOS) + QString appPath = QApplication::applicationFilePath(); + if (appPath.startsWith("/private/var/")) { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("The launcher is in the folder it was extracted from, therefore it cannot create shortcuts.")); + return; + } + + if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()), + appPath, { "--launch", m_selectedInstance->id() }, + m_selectedInstance->name(), "")) { + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); + } + else + { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); + } +#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) + QString appPath = QApplication::applicationFilePath(); + if (appPath.startsWith("/tmp/.mount_")) { + // AppImage! + appPath = QProcessEnvironment::systemEnvironment().value(QStringLiteral("APPIMAGE")); + if (appPath.isEmpty()) + { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Launcher is running as misconfigured AppImage? ($APPIMAGE environment variable is missing)")); + } + else if (appPath.endsWith("/")) + { + appPath.chop(1); + } + } + + auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (icon == nullptr) + { + icon = APPLICATION->icons()->icon("grass"); + } + + QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.png"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) + { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "PNG"); + iconFile.close(); + + if (!success) + { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + + QString desktopFilePath = FS::PathCombine(desktopPath, m_selectedInstance->name() + ".desktop"); + QStringList args; + if (DesktopServices::isFlatpak()) { + QFileDialog fileDialog; + // workaround to make sure the portal file dialog opens in the desktop directory + fileDialog.setDirectoryUrl(desktopPath); + desktopFilePath = fileDialog.getSaveFileName( + this, tr("Create Shortcut"), desktopFilePath, + tr("Desktop Entries (*.desktop)")); + if (desktopFilePath.isEmpty()) + return; // file dialog canceled by user + appPath = "flatpak"; + QString flatpakAppId = BuildConfig.LAUNCHER_DESKTOPFILENAME; + flatpakAppId.remove(".desktop"); + args.append({ "run", flatpakAppId }); + } + args.append({ "--launch", m_selectedInstance->id() }); + if (FS::createShortcut(desktopFilePath, appPath, args, m_selectedInstance->name(), iconPath)) { + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); + } + else + { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); + } +#elif defined(Q_OS_WIN) + auto icon = APPLICATION->icons()->icon(m_selectedInstance->iconKey()); + if (icon == nullptr) + { + icon = APPLICATION->icons()->icon("grass"); + } + + QString iconPath = FS::PathCombine(m_selectedInstance->instanceRoot(), "icon.ico"); + + // part of fix for weird bug involving the window icon being replaced + // dunno why it happens, but this 2-line fix seems to be enough, so w/e + auto appIcon = APPLICATION->getThemedIcon("logo"); + + QFile iconFile(iconPath); + if (!iconFile.open(QFile::WriteOnly)) + { + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + bool success = icon->icon().pixmap(64, 64).save(&iconFile, "ICO"); + iconFile.close(); + + // restore original window icon + QGuiApplication::setWindowIcon(appIcon); + + if (!success) + { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create icon for shortcut.")); + return; + } + + if (FS::createShortcut(FS::PathCombine(desktopPath, m_selectedInstance->name()), + QApplication::applicationFilePath(), { "--launch", m_selectedInstance->id() }, + m_selectedInstance->name(), iconPath)) { + QMessageBox::information(this, tr("Create instance shortcut"), tr("Created a shortcut to this instance on your desktop!")); + } + else + { + iconFile.remove(); + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Failed to create instance shortcut!")); + } +#else + QMessageBox::critical(this, tr("Create instance shortcut"), tr("Not supported on your platform!")); +#endif + } +} + void MainWindow::taskEnd() { QObject *sender = QObject::sender(); diff --git a/launcher/ui/M |
