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