mirror of https://github.com/stenzek/duckstation
				
				
				
			Qt: Add ISO Browser
							parent
							
								
									58f5d7e1ba
								
							
						
					
					
						commit
						541985fb70
					
				@ -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>
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue