From e7ddc5187ad3c3fe8259197d809dd2866ffcbaa8 Mon Sep 17 00:00:00 2001 From: Silent Date: Fri, 3 Jan 2020 19:27:47 +0100 Subject: [PATCH] Add a dialog to Batch PKG Install Dialog allows users to preview the order in which PKG's will be installed and allows users to move items around if needed. Because clicking "Install" on this new dialog acts as a confirmation and user has a second chance to eyeball what is to be installed, "Install package X?" dialogs have been removed and instead user is only notified of success. In case of failure, batch installation aborts with a descriptive error. --- rpcs3/rpcs3.vcxproj | 41 +++++++- rpcs3/rpcs3.vcxproj.filters | 18 ++++ rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/main_window.cpp | 69 +++++------- rpcs3/rpcs3qt/main_window.h | 4 +- rpcs3/rpcs3qt/pkg_install_dialog.cpp | 151 +++++++++++++++++++++++++++ rpcs3/rpcs3qt/pkg_install_dialog.h | 18 ++++ 7 files changed, 258 insertions(+), 44 deletions(-) create mode 100644 rpcs3/rpcs3qt/pkg_install_dialog.cpp create mode 100644 rpcs3/rpcs3qt/pkg_install_dialog.h diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index ffb4b3d226..f95209a3e8 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -472,6 +472,11 @@ true true + + true + true + true + true true @@ -667,6 +672,11 @@ true true + + true + true + true + true true @@ -882,6 +892,11 @@ true true + + true + true + true + true true @@ -1077,6 +1092,11 @@ true true + + true + true + true + true true @@ -1151,6 +1171,7 @@ + @@ -1712,6 +1733,24 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB "-DBRANCH=$(BRANCH)" -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-IC:\Program Files (x86)\Visual Leak Detector\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-IC:\Program Files (x86)\Visual Leak Detector\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-IC:\Program Files (x86)\Visual Leak Detector\include" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" "-IC:\Program Files (x86)\Visual Leak Detector\include" + @@ -2191,4 +2230,4 @@ - + \ No newline at end of file diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index e8e7655799..bc7cddb099 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -788,6 +788,21 @@ Generated Files\Debug - LLVM + + Generated Files\Release - LLVM + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\Debug - LLVM + + + Gui\misc dialogs + @@ -1041,6 +1056,9 @@ Gui\cheat manager + + Gui\misc dialogs + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 46dfef6ca4..2cd5dfc95e 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -29,6 +29,7 @@ msg_dialog_frame.cpp osk_dialog_frame.cpp pad_settings_dialog.cpp + pkg_install_dialog.cpp progress_dialog.cpp qt_utils.cpp register_editor_dialog.cpp diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index fa3ac7f68b..d87013dae3 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -30,6 +30,7 @@ #include "progress_dialog.h" #include "skylander_dialog.h" #include "cheat_manager.h" +#include "pkg_install_dialog.h" #include @@ -369,59 +370,28 @@ void main_window::BootRsxCapture(std::string path) } } -void main_window::InstallPkg(const QString& dropPath, bool is_bulk) +bool main_window::InstallPkg(const QString& dropPath, bool show_confirm, bool show_success) { QString filePath = dropPath; - if (m_install_bulk == QMessageBox::NoToAll) - { - LOG_NOTICE(LOADER, "PKG: Skipped installation from drop. File: %s", sstr(filePath)); - return; - } - else if (m_install_bulk == QMessageBox::YesToAll) - { - LOG_NOTICE(LOADER, "PKG: Continuing bulk installation from drop. File: %s", sstr(filePath)); - } - else if (filePath.isEmpty()) + if (filePath.isEmpty()) { QString path_last_PKG = guiSettings->GetValue(gui::fd_install_pkg).toString(); filePath = QFileDialog::getOpenFileName(this, tr("Select PKG To Install"), path_last_PKG, tr("PKG files (*.pkg);;All files (*.*)")); } - else if (is_bulk) - { - QMessageBox::StandardButton ret = QMessageBox::question(this, tr("PKG Decrypter / Installer"), tr("Install package: %1?").arg(filePath), - QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::No, QMessageBox::No); - - if (ret == QMessageBox::No) - { - LOG_NOTICE(LOADER, "PKG: Cancelled installation from drop. File: %s", sstr(filePath)); - return; - } - else if (ret == QMessageBox::NoToAll) - { - LOG_NOTICE(LOADER, "PKG: Cancelled bulk installation from drop. File: %s", sstr(filePath)); - m_install_bulk = ret; - return; - } - else if (ret == QMessageBox::YesToAll) - { - LOG_NOTICE(LOADER, "PKG: Accepted bulk installation from drop. File: %s", sstr(filePath)); - m_install_bulk = ret; - } - } - else + else if (show_confirm) { if (QMessageBox::question(this, tr("PKG Decrypter / Installer"), tr("Install package: %1?").arg(filePath), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { LOG_NOTICE(LOADER, "PKG: Cancelled installation from drop. File: %s", sstr(filePath)); - return; + return false; } } if (filePath.isEmpty()) { - return; + return false; } Emu.SetForceBoot(true); @@ -479,13 +449,18 @@ void main_window::InstallPkg(const QString& dropPath, bool is_bulk) { m_gameListFrame->Refresh(true); LOG_SUCCESS(GENERAL, "Successfully installed %s.", fileName); - guiSettings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package!"), gui::ib_pkg_success, this); + if (show_success) + { + guiSettings->ShowInfoBox(tr("Success!"), tr("Successfully installed software from package(s)!"), gui::ib_pkg_success, this); + } + return true; } else if (!cancelled) { LOG_ERROR(GENERAL, "Failed to install %s.", fileName); QMessageBox::critical(this, tr("Failure!"), tr("Failed to install software from package %1!").arg(filePath)); } + return false; } void main_window::InstallPup(const QString& dropPath) @@ -1922,11 +1897,25 @@ void main_window::dropEvent(QDropEvent* event) case drop_type::drop_error: break; case drop_type::drop_pkg: // install the packages - for (const auto& path : dropPaths) + if (dropPaths.count() > 1) { - InstallPkg(path, dropPaths.count() > 1); + pkg_install_dialog dlg(dropPaths, this); + connect(&dlg, &QDialog::accepted, [this, &dlg]() + { + const QStringList paths = dlg.GetPathsToInstall(); + + for (int i = 0, count = paths.count(); i < count; i++) + { + if (!InstallPkg(paths.at(i), false, i == count - 1)) + break; + } + }); + dlg.exec(); + } + else + { + InstallPkg(dropPaths.front(), true); } - m_install_bulk = QMessageBox::NoButton; break; case drop_type::drop_pup: // install the firmware InstallPup(dropPaths.first()); diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 816cd68792..952db038e2 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -116,7 +116,7 @@ private: void CreateDockWindows(); void EnableMenus(bool enabled); void ShowTitleBars(bool show); - void InstallPkg(const QString& dropPath = "", bool is_bulk = false); + bool InstallPkg(const QString& dropPath = "", bool show_confirm = true, bool show_success = true); void InstallPup(const QString& dropPath = ""); int IsValidFile(const QMimeData& md, QStringList* dropPaths = nullptr); @@ -134,8 +134,6 @@ private: QActionGroup* m_listModeActGroup = nullptr; QActionGroup* m_categoryVisibleActGroup = nullptr; - QMessageBox::StandardButton m_install_bulk = QMessageBox::NoButton; - // Dockable widget frames QMainWindow *m_mw = nullptr; log_frame* m_logFrame = nullptr; diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.cpp b/rpcs3/rpcs3qt/pkg_install_dialog.cpp new file mode 100644 index 0000000000..76fe0b044f --- /dev/null +++ b/rpcs3/rpcs3qt/pkg_install_dialog.cpp @@ -0,0 +1,151 @@ +#include "pkg_install_dialog.h" + +#include +#include +#include +#include +#include +#include + +constexpr int FullPathRole = Qt::UserRole + 0; +constexpr int BaseDisplayRole = Qt::UserRole + 1; + +pkg_install_dialog::pkg_install_dialog(const QStringList& paths, QWidget* parent) + : QDialog(parent) +{ + m_dir_list = new QListWidget(this); + + class numbered_widget_item final : public QListWidgetItem + { + public: + explicit numbered_widget_item(const QString& text, QListWidget* listview = nullptr, int type = QListWidgetItem::Type) + : QListWidgetItem(text, listview, type) + { + } + + QVariant data(int role) const override + { + QVariant result; + switch (role) + { + case Qt::DisplayRole: + result = QStringLiteral("%1. %2").arg(listWidget()->row(this) + 1).arg(data(BaseDisplayRole).toString()); + break; + case BaseDisplayRole: + result = QListWidgetItem::data(Qt::DisplayRole); + break; + default: + result = QListWidgetItem::data(role); + break; + } + return result; + } + + bool operator<(const QListWidgetItem& other) const override + { + return data(BaseDisplayRole).toString() < other.data(BaseDisplayRole).toString(); + } + }; + + for (const QString& path : paths) + { + QListWidgetItem* item = new numbered_widget_item(QFileInfo(path).fileName(), m_dir_list); + // Save full path in a custom data role + item->setData(FullPathRole, path); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Checked); + } + + m_dir_list->sortItems(); + m_dir_list->setCurrentRow(0); + m_dir_list->setMinimumWidth((m_dir_list->sizeHintForColumn(0) * 125) / 100); + + // Create buttons + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + buttons->button(QDialogButtonBox::Ok)->setText(tr("Install")); + buttons->button(QDialogButtonBox::Ok)->setDefault(true); + + connect(buttons, &QDialogButtonBox::clicked, [this, buttons](QAbstractButton* button) + { + if (button == buttons->button(QDialogButtonBox::Ok)) + { + accept(); + } + else if (button == buttons->button(QDialogButtonBox::Cancel)) + { + reject(); + } + }); + + connect(m_dir_list, &QListWidget::itemChanged, [this, buttons](QListWidgetItem*) + { + bool any_checked = false; + for (int i = 0; i < m_dir_list->count(); i++) + { + if (m_dir_list->item(i)->checkState() == Qt::Checked) + { + any_checked = true; + break; + } + } + + buttons->button(QDialogButtonBox::Ok)->setEnabled(any_checked); + }); + + QToolButton* move_up = new QToolButton; + move_up->setArrowType(Qt::UpArrow); + move_up->setToolTip(tr("Move selected item up")); + connect(move_up, &QToolButton::clicked, [this]() { MoveItem(-1); }); + + QToolButton* move_down = new QToolButton; + move_down->setArrowType(Qt::DownArrow); + move_down->setToolTip(tr("Move selected item down")); + connect(move_down, &QToolButton::clicked, [this]() { MoveItem(1); }); + + QHBoxLayout* hbox = new QHBoxLayout; + hbox->addStretch(); + hbox->addWidget(move_up); + hbox->addWidget(move_down); + + QLabel* description = new QLabel(tr("You are about to install multiple packages.\nReorder and/or exclude them if needed, then click \"Install\" to proceed.")); + + QVBoxLayout* vbox = new QVBoxLayout; + vbox->addWidget(description); + vbox->addLayout(hbox); + vbox->addWidget(m_dir_list); + vbox->addWidget(buttons); + + setLayout(vbox); + setWindowTitle(tr("Batch PKG Installation")); + setObjectName("pkg_install_dialog"); +} + +void pkg_install_dialog::MoveItem(int offset) +{ + const int src_index = m_dir_list->currentRow(); + const int dest_index = src_index + offset; + + if (src_index >= 0 && src_index < m_dir_list->count() && + dest_index >= 0 && dest_index < m_dir_list->count()) + { + QListWidgetItem* item = m_dir_list->takeItem(src_index); + m_dir_list->insertItem(dest_index, item); + m_dir_list->setCurrentItem(item); + } +} + +QStringList pkg_install_dialog::GetPathsToInstall() const +{ + QStringList result; + + for (int i = 0; i < m_dir_list->count(); i++) + { + const QListWidgetItem* item = m_dir_list->item(i); + if (item->checkState() == Qt::Checked) + { + result.append(item->data(FullPathRole).toString()); + } + } + + return result; +} diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.h b/rpcs3/rpcs3qt/pkg_install_dialog.h new file mode 100644 index 0000000000..df9862d60e --- /dev/null +++ b/rpcs3/rpcs3qt/pkg_install_dialog.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +class pkg_install_dialog : public QDialog +{ + Q_OBJECT + +public: + explicit pkg_install_dialog(const QStringList& paths, QWidget* parent = nullptr); + QStringList GetPathsToInstall() const; + +private: + void MoveItem(int offset); + + QListWidget* m_dir_list; +};