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; +};