Qt: Add ISO Browser
This commit is contained in:
parent
58f5d7e1ba
commit
541985fb70
|
@ -106,6 +106,9 @@ set(SRCS
|
||||||
interfacesettingswidget.cpp
|
interfacesettingswidget.cpp
|
||||||
interfacesettingswidget.h
|
interfacesettingswidget.h
|
||||||
interfacesettingswidget.ui
|
interfacesettingswidget.ui
|
||||||
|
isobrowserwindow.cpp
|
||||||
|
isobrowserwindow.h
|
||||||
|
isobrowserwindow.ui
|
||||||
logwindow.cpp
|
logwindow.cpp
|
||||||
logwindow.h
|
logwindow.h
|
||||||
mainwindow.cpp
|
mainwindow.cpp
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
<ClCompile Include="hotkeysettingswidget.cpp" />
|
<ClCompile Include="hotkeysettingswidget.cpp" />
|
||||||
<ClCompile Include="inputbindingdialog.cpp" />
|
<ClCompile Include="inputbindingdialog.cpp" />
|
||||||
<ClCompile Include="inputbindingwidgets.cpp" />
|
<ClCompile Include="inputbindingwidgets.cpp" />
|
||||||
|
<ClCompile Include="isobrowserwindow.cpp" />
|
||||||
<ClCompile Include="logwindow.cpp" />
|
<ClCompile Include="logwindow.cpp" />
|
||||||
<ClCompile Include="memoryscannerwindow.cpp" />
|
<ClCompile Include="memoryscannerwindow.cpp" />
|
||||||
<ClCompile Include="memoryviewwidget.cpp" />
|
<ClCompile Include="memoryviewwidget.cpp" />
|
||||||
|
@ -85,6 +86,7 @@
|
||||||
<QtMoc Include="memoryscannerwindow.h" />
|
<QtMoc Include="memoryscannerwindow.h" />
|
||||||
<QtMoc Include="gamecheatsettingswidget.h" />
|
<QtMoc Include="gamecheatsettingswidget.h" />
|
||||||
<QtMoc Include="gamepatchsettingswidget.h" />
|
<QtMoc Include="gamepatchsettingswidget.h" />
|
||||||
|
<QtMoc Include="isobrowserwindow.h" />
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<QtMoc Include="selectdiscdialog.h" />
|
<QtMoc Include="selectdiscdialog.h" />
|
||||||
|
@ -242,6 +244,7 @@
|
||||||
<ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
|
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_interfacesettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_interfacesettingswidget.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_isobrowserwindow.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_logwindow.cpp" />
|
<ClCompile Include="$(IntDir)moc_logwindow.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
||||||
|
@ -354,6 +357,9 @@
|
||||||
<QtUi Include="memorycardrenamefiledialog.ui">
|
<QtUi Include="memorycardrenamefiledialog.ui">
|
||||||
<FileType>Document</FileType>
|
<FileType>Document</FileType>
|
||||||
</QtUi>
|
</QtUi>
|
||||||
|
<QtUi Include="isobrowserwindow.ui">
|
||||||
|
<FileType>Document</FileType>
|
||||||
|
</QtUi>
|
||||||
<None Include="translations\duckstation-qt_es-es.ts" />
|
<None Include="translations\duckstation-qt_es-es.ts" />
|
||||||
<None Include="translations\duckstation-qt_sv.ts" />
|
<None Include="translations\duckstation-qt_sv.ts" />
|
||||||
<None Include="translations\duckstation-qt_tr.ts" />
|
<None Include="translations\duckstation-qt_tr.ts" />
|
||||||
|
|
|
@ -175,6 +175,10 @@
|
||||||
<ClCompile Include="$(IntDir)moc_gamecheatsettingswidget.cpp">
|
<ClCompile Include="$(IntDir)moc_gamecheatsettingswidget.cpp">
|
||||||
<Filter>moc</Filter>
|
<Filter>moc</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="isobrowserwindow.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_isobrowserwindow.cpp">
|
||||||
|
<Filter>moc</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="qtutils.h" />
|
<ClInclude Include="qtutils.h" />
|
||||||
|
@ -237,6 +241,7 @@
|
||||||
<QtMoc Include="selectdiscdialog.h" />
|
<QtMoc Include="selectdiscdialog.h" />
|
||||||
<QtMoc Include="gamecheatsettingswidget.h" />
|
<QtMoc Include="gamecheatsettingswidget.h" />
|
||||||
<QtMoc Include="gamepatchsettingswidget.h" />
|
<QtMoc Include="gamepatchsettingswidget.h" />
|
||||||
|
<QtMoc Include="isobrowserwindow.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtUi Include="consolesettingswidget.ui" />
|
<QtUi Include="consolesettingswidget.ui" />
|
||||||
|
@ -289,6 +294,7 @@
|
||||||
<QtUi Include="gamepatchdetailswidget.ui" />
|
<QtUi Include="gamepatchdetailswidget.ui" />
|
||||||
<QtUi Include="gamecheatcodechoiceeditordialog.ui" />
|
<QtUi Include="gamecheatcodechoiceeditordialog.ui" />
|
||||||
<QtUi Include="memorycardrenamefiledialog.ui" />
|
<QtUi Include="memorycardrenamefiledialog.ui" />
|
||||||
|
<QtUi Include="isobrowserwindow.ui" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="duckstation-qt.rc" />
|
<ResourceCompile Include="duckstation-qt.rc" />
|
||||||
|
|
|
@ -0,0 +1,340 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
|
#include "isobrowserwindow.h"
|
||||||
|
#include "qtprogresscallback.h"
|
||||||
|
#include "qtutils.h"
|
||||||
|
|
||||||
|
#include "util/cd_image.h"
|
||||||
|
|
||||||
|
#include "common/align.h"
|
||||||
|
#include "common/error.h"
|
||||||
|
#include "common/file_system.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/path.h"
|
||||||
|
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
#include <QtGui/QIcon>
|
||||||
|
#include <QtWidgets/QFileDialog>
|
||||||
|
#include <QtWidgets/QMenu>
|
||||||
|
#include <QtWidgets/QMessageBox>
|
||||||
|
|
||||||
|
LOG_CHANNEL(Host);
|
||||||
|
|
||||||
|
ISOBrowserWindow::ISOBrowserWindow(QWidget* parent) : QWidget(parent)
|
||||||
|
{
|
||||||
|
m_ui.setupUi(this);
|
||||||
|
m_ui.splitter->setSizes({200, 600});
|
||||||
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
|
||||||
|
connect(m_ui.openFile, &QAbstractButton::clicked, this, &ISOBrowserWindow::onOpenFileClicked);
|
||||||
|
connect(m_ui.directoryView, &QTreeWidget::itemClicked, this, &ISOBrowserWindow::onDirectoryItemClicked);
|
||||||
|
connect(m_ui.fileView, &QTreeWidget::itemActivated, this, &ISOBrowserWindow::onFileItemActivated);
|
||||||
|
connect(m_ui.fileView, &QTreeWidget::itemSelectionChanged, this, &ISOBrowserWindow::onFileItemSelectionChanged);
|
||||||
|
connect(m_ui.fileView, &QTreeWidget::customContextMenuRequested, this, &ISOBrowserWindow::onFileContextMenuRequested);
|
||||||
|
connect(m_ui.close, &QAbstractButton::clicked, this, &ISOBrowserWindow::close);
|
||||||
|
}
|
||||||
|
|
||||||
|
ISOBrowserWindow::~ISOBrowserWindow() = default;
|
||||||
|
|
||||||
|
ISOBrowserWindow* ISOBrowserWindow::createAndOpenFile(QWidget* parent, const QString& path)
|
||||||
|
{
|
||||||
|
ISOBrowserWindow* ib = new ISOBrowserWindow(nullptr);
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
if (!ib->tryOpenFile(path, &error))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(parent, tr("Error"),
|
||||||
|
tr("Failed to open %1:\n%2").arg(path).arg(QString::fromStdString(error.GetDescription())));
|
||||||
|
delete ib;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ib;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ISOBrowserWindow::tryOpenFile(const QString& path, Error* error /*= nullptr*/)
|
||||||
|
{
|
||||||
|
const std::string native_path = QDir::toNativeSeparators(path).toStdString();
|
||||||
|
std::unique_ptr<CDImage> image = CDImage::Open(native_path.c_str(), false, error);
|
||||||
|
if (!image)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
IsoReader new_reader;
|
||||||
|
if (!new_reader.Open(image.get(), 1, error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_image = std::move(image);
|
||||||
|
m_iso = std::move(new_reader);
|
||||||
|
m_ui.openPath->setText(QString::fromStdString(native_path));
|
||||||
|
setWindowTitle(tr("ISO Browser - %1").arg(QtUtils::StringViewToQString(Path::GetFileName(native_path))));
|
||||||
|
populateDirectories();
|
||||||
|
populateFiles(QString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::resizeEvent(QResizeEvent* ev)
|
||||||
|
{
|
||||||
|
QWidget::resizeEvent(ev);
|
||||||
|
resizeFileListColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::showEvent(QShowEvent* ev)
|
||||||
|
{
|
||||||
|
QWidget::showEvent(ev);
|
||||||
|
resizeFileListColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::onOpenFileClicked()
|
||||||
|
{
|
||||||
|
const QString path = QFileDialog::getOpenFileName(
|
||||||
|
this, tr("Select File"),
|
||||||
|
m_image ? QtUtils::StringViewToQString(Path::GetDirectory(m_image->GetPath())) : QString());
|
||||||
|
if (path.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
if (!tryOpenFile(path, &error))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, tr("Error"),
|
||||||
|
tr("Failed to open %1:\n%2").arg(path).arg(QString::fromStdString(error.GetDescription())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::onDirectoryItemClicked(QTreeWidgetItem* item, int column)
|
||||||
|
{
|
||||||
|
populateFiles(item->data(0, Qt::UserRole).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::onFileItemActivated(QTreeWidgetItem* item, int column)
|
||||||
|
{
|
||||||
|
if (item->data(0, Qt::UserRole + 1).toBool())
|
||||||
|
{
|
||||||
|
// directory
|
||||||
|
const QString dir = item->data(0, Qt::UserRole).toString();
|
||||||
|
populateFiles(dir);
|
||||||
|
|
||||||
|
// select it in the directory list
|
||||||
|
QTreeWidgetItem* dir_item = findDirectoryItemForPath(dir, nullptr);
|
||||||
|
if (dir_item)
|
||||||
|
{
|
||||||
|
QSignalBlocker sb(m_ui.directoryView);
|
||||||
|
m_ui.directoryView->clearSelection();
|
||||||
|
dir_item->setSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// file, go to extract
|
||||||
|
extractFile(item->data(0, Qt::UserRole).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::onFileItemSelectionChanged()
|
||||||
|
{
|
||||||
|
const QList<QTreeWidgetItem*> items = m_ui.fileView->selectedItems();
|
||||||
|
if (items.isEmpty())
|
||||||
|
{
|
||||||
|
m_ui.extract->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// directory?
|
||||||
|
const bool is_directory = items.front()->data(0, Qt::UserRole + 1).toBool();
|
||||||
|
m_ui.extract->setEnabled(!is_directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::onFileContextMenuRequested(const QPoint& pos)
|
||||||
|
{
|
||||||
|
const QList<QTreeWidgetItem*> items = m_ui.fileView->selectedItems();
|
||||||
|
if (items.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QMenu menu;
|
||||||
|
|
||||||
|
const bool is_directory = items.front()->data(0, Qt::UserRole + 1).toBool();
|
||||||
|
const QString path = items.front()->data(0, Qt::UserRole).toString();
|
||||||
|
if (is_directory)
|
||||||
|
{
|
||||||
|
connect(menu.addAction(QIcon::fromTheme(QIcon::ThemeIcon::FolderOpen), tr("&Open")), &QAction::triggered, this,
|
||||||
|
[this, &path]() { populateFiles(path); });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connect(menu.addAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSaveAs), tr("&Extract")), &QAction::triggered,
|
||||||
|
this, [this, &path]() { extractFile(path); });
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.exec(m_ui.fileView->mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::resizeFileListColumns()
|
||||||
|
{
|
||||||
|
QtUtils::ResizeColumnsForTreeView(m_ui.fileView, {-1, 200, 100});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::extractFile(const QString& path)
|
||||||
|
{
|
||||||
|
const std::string spath = path.toStdString();
|
||||||
|
const QString filename = QtUtils::StringViewToQString(Path::GetFileName(spath));
|
||||||
|
std::string save_path =
|
||||||
|
QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Extract File"), filename)).toStdString();
|
||||||
|
if (save_path.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
std::optional<IsoReader::ISODirectoryEntry> de = m_iso.LocateFile(path.toStdString(), &error);
|
||||||
|
if (de.has_value())
|
||||||
|
{
|
||||||
|
auto fp = FileSystem::CreateAtomicRenamedFile(std::move(save_path), &error);
|
||||||
|
if (fp)
|
||||||
|
{
|
||||||
|
QtModalProgressCallback cb(this, 0.15f);
|
||||||
|
cb.SetCancellable(true);
|
||||||
|
cb.SetTitle("ISO Browser");
|
||||||
|
cb.SetStatusText(tr("Extracting %1...").arg(filename).toStdString());
|
||||||
|
if (m_iso.WriteFileToStream(de.value(), fp.get(), &error, &cb))
|
||||||
|
{
|
||||||
|
if (FileSystem::CommitAtomicRenamedFile(fp, &error))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// don't display error if cancelled
|
||||||
|
FileSystem::DiscardAtomicRenamedFile(fp);
|
||||||
|
if (cb.IsCancellable())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::critical(this, tr("Error"),
|
||||||
|
tr("Failed to save %1:\n%2").arg(path).arg(QString::fromStdString(error.GetDescription())));
|
||||||
|
}
|
||||||
|
|
||||||
|
QTreeWidgetItem* ISOBrowserWindow::findDirectoryItemForPath(const QString& path, QTreeWidgetItem* parent) const
|
||||||
|
{
|
||||||
|
if (!parent)
|
||||||
|
{
|
||||||
|
parent = m_ui.directoryView->topLevelItem(0);
|
||||||
|
if (path.isEmpty())
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int count = parent->childCount();
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem* item = parent->child(i);
|
||||||
|
if (item->data(0, Qt::UserRole) == path)
|
||||||
|
return item;
|
||||||
|
|
||||||
|
QTreeWidgetItem* child_item = findDirectoryItemForPath(path, item);
|
||||||
|
if (child_item)
|
||||||
|
return child_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::populateDirectories()
|
||||||
|
{
|
||||||
|
m_ui.directoryView->clear();
|
||||||
|
m_ui.extract->setEnabled(false);
|
||||||
|
|
||||||
|
QTreeWidgetItem* root = new QTreeWidgetItem;
|
||||||
|
root->setIcon(0, QIcon::fromTheme("disc-line"));
|
||||||
|
root->setText(0, QtUtils::StringViewToQString(Path::GetFileTitle(m_image->GetPath())));
|
||||||
|
root->setData(0, Qt::UserRole, QString());
|
||||||
|
m_ui.directoryView->addTopLevelItem(root);
|
||||||
|
|
||||||
|
populateSubdirectories(std::string_view(), root);
|
||||||
|
|
||||||
|
root->setExpanded(true);
|
||||||
|
|
||||||
|
QSignalBlocker sb(m_ui.directoryView);
|
||||||
|
root->setSelected(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::populateSubdirectories(std::string_view dir, QTreeWidgetItem* parent)
|
||||||
|
{
|
||||||
|
Error error;
|
||||||
|
std::vector<std::pair<std::string, IsoReader::ISODirectoryEntry>> entries = m_iso.GetEntriesInDirectory(dir, &error);
|
||||||
|
if (entries.empty() && error.IsValid())
|
||||||
|
{
|
||||||
|
ERROR_LOG("Failed to populate directory '{}': {}", dir, error.GetDescription());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [full_path, entry] : entries)
|
||||||
|
{
|
||||||
|
if (!entry.IsDirectory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QTreeWidgetItem* item = new QTreeWidgetItem(parent);
|
||||||
|
item->setIcon(0, QIcon::fromTheme(QStringLiteral("folder-open-line")));
|
||||||
|
item->setText(0, QtUtils::StringViewToQString(Path::GetFileName(full_path)));
|
||||||
|
item->setData(0, Qt::UserRole, QString::fromStdString(full_path));
|
||||||
|
populateSubdirectories(full_path, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ISOBrowserWindow::populateFiles(const QString& path)
|
||||||
|
{
|
||||||
|
const std::string spath = path.toStdString();
|
||||||
|
|
||||||
|
m_ui.fileView->clear();
|
||||||
|
|
||||||
|
Error error;
|
||||||
|
std::vector<std::pair<std::string, IsoReader::ISODirectoryEntry>> entries =
|
||||||
|
m_iso.GetEntriesInDirectory(spath, &error);
|
||||||
|
if (entries.empty() && error.IsValid())
|
||||||
|
{
|
||||||
|
ERROR_LOG("Failed to populate files '{}': {}", spath, error.GetDescription());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto add_entry = [this](const std::string& full_path, const IsoReader::ISODirectoryEntry& entry) {
|
||||||
|
const std::string_view filename = Path::GetFileName(full_path);
|
||||||
|
|
||||||
|
QTreeWidgetItem* item = new QTreeWidgetItem;
|
||||||
|
item->setIcon(
|
||||||
|
0, QIcon::fromTheme(entry.IsDirectory() ? QStringLiteral("folder-open-line") : QStringLiteral("file-line")));
|
||||||
|
item->setText(0, QtUtils::StringViewToQString(Path::GetFileName(full_path)));
|
||||||
|
item->setData(0, Qt::UserRole, QString::fromStdString(full_path));
|
||||||
|
item->setData(0, Qt::UserRole + 1, entry.IsDirectory());
|
||||||
|
item->setText(1, QString::fromStdString(entry.recoding_time.GetFormattedTime()));
|
||||||
|
item->setText(2, tr("%1 KB").arg(Common::AlignUpPow2(entry.length_le, 1024) / 1024));
|
||||||
|
m_ui.fileView->addTopLevelItem(item);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!path.isEmpty())
|
||||||
|
{
|
||||||
|
QTreeWidgetItem* item = new QTreeWidgetItem;
|
||||||
|
item->setIcon(0, QIcon::fromTheme(QStringLiteral("folder-open-line")));
|
||||||
|
item->setText(0, tr("<Parent Directory>"));
|
||||||
|
item->setData(0, Qt::UserRole, QtUtils::StringViewToQString(Path::GetDirectory(spath)));
|
||||||
|
item->setData(0, Qt::UserRole + 1, true);
|
||||||
|
m_ui.fileView->addTopLevelItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// list directories first
|
||||||
|
for (const auto& [full_path, entry] : entries)
|
||||||
|
{
|
||||||
|
if (!entry.IsDirectory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
add_entry(full_path, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [full_path, entry] : entries)
|
||||||
|
{
|
||||||
|
if (entry.IsDirectory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
add_entry(full_path, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is utter shit, the scrollbar visibility doesn't update in time, so we have to queue it.
|
||||||
|
QTimer::singleShot(20, Qt::TimerType::CoarseTimer, this, SLOT(resizeFileListColumns()));
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_isobrowserwindow.h"
|
||||||
|
|
||||||
|
#include "util/iso_reader.h"
|
||||||
|
|
||||||
|
class ISOBrowserWindow : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ISOBrowserWindow(QWidget* parent = nullptr);
|
||||||
|
~ISOBrowserWindow();
|
||||||
|
|
||||||
|
static ISOBrowserWindow* createAndOpenFile(QWidget* parent, const QString& path);
|
||||||
|
|
||||||
|
bool tryOpenFile(const QString& path, Error* error = nullptr);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent* ev);
|
||||||
|
void showEvent(QShowEvent* ev);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void onOpenFileClicked();
|
||||||
|
void onDirectoryItemClicked(QTreeWidgetItem* item, int column);
|
||||||
|
void onFileItemActivated(QTreeWidgetItem* item, int column);
|
||||||
|
void onFileItemSelectionChanged();
|
||||||
|
void onFileContextMenuRequested(const QPoint& pos);
|
||||||
|
void resizeFileListColumns();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void populateDirectories();
|
||||||
|
void populateSubdirectories(std::string_view dir, QTreeWidgetItem* parent);
|
||||||
|
void populateFiles(const QString& path);
|
||||||
|
void extractFile(const QString& path);
|
||||||
|
|
||||||
|
QTreeWidgetItem* findDirectoryItemForPath(const QString& path, QTreeWidgetItem* parent = nullptr) const;
|
||||||
|
|
||||||
|
Ui::ISOBrowserWindow m_ui;
|
||||||
|
std::unique_ptr<CDImage> m_image;
|
||||||
|
IsoReader m_iso;
|
||||||
|
};
|
|
@ -0,0 +1,132 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ISOBrowserWindow</class>
|
||||||
|
<widget class="QWidget" name="ISOBrowserWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>828</width>
|
||||||
|
<height>511</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon">
|
||||||
|
<iconset resource="resources/duckstation-qt.qrc">
|
||||||
|
<normaloff>:/icons/media-optical.png</normaloff>:/icons/media-optical.png</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>File:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="openPath">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="openFile">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="QIcon::ThemeIcon::DocumentOpen"/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="QTreeWidget" name="directoryView">
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="headerHidden">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Name</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
<widget class="QTreeWidget" name="fileView">
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
|
||||||
|
</property>
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="rootIsDecorated">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Name</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Date</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Size</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="extract">
|
||||||
|
<property name="text">
|
||||||
|
<string>Extract</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="close">
|
||||||
|
<property name="text">
|
||||||
|
<string>Close</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="resources/duckstation-qt.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -12,6 +12,7 @@
|
||||||
#include "gamelistsettingswidget.h"
|
#include "gamelistsettingswidget.h"
|
||||||
#include "gamelistwidget.h"
|
#include "gamelistwidget.h"
|
||||||
#include "interfacesettingswidget.h"
|
#include "interfacesettingswidget.h"
|
||||||
|
#include "isobrowserwindow.h"
|
||||||
#include "logwindow.h"
|
#include "logwindow.h"
|
||||||
#include "memorycardeditorwindow.h"
|
#include "memorycardeditorwindow.h"
|
||||||
#include "memoryscannerwindow.h"
|
#include "memoryscannerwindow.h"
|
||||||
|
@ -1422,6 +1423,18 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absolutePath()));
|
QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absolutePath()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (entry->IsDisc())
|
||||||
|
{
|
||||||
|
connect(menu.addAction(tr("Browse ISO...")), &QAction::triggered, [this, entry]() {
|
||||||
|
ISOBrowserWindow* ib = ISOBrowserWindow::createAndOpenFile(this, QString::fromStdString(entry->path));
|
||||||
|
if (ib)
|
||||||
|
{
|
||||||
|
ib->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
ib->show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
connect(menu.addAction(tr("Set Cover Image...")), &QAction::triggered,
|
connect(menu.addAction(tr("Set Cover Image...")), &QAction::triggered,
|
||||||
[this, entry]() { setGameListEntryCoverImage(entry); });
|
[this, entry]() { setGameListEntryCoverImage(entry); });
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue