diff --git a/source/common.h b/source/common.h index 1b66136..3c8a4e9 100644 --- a/source/common.h +++ b/source/common.h @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/source/quam/main_window.cc b/source/quam/main_window.cc index 9597f38..23580c0 100644 --- a/source/quam/main_window.cc +++ b/source/quam/main_window.cc @@ -2,17 +2,30 @@ #include "quam/main_window.h" #include "quam/pak.h" +#include +#include +#include +#include + MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}, Ui::MainWindow{}, - m_errors{} + m_directory{}, + m_errors{new QErrorMessage{this}}, + m_model{nullptr}, + m_sorter{nullptr} { setupUi(this); + actionClose->setShortcut(QKeySequence{QKeySequence::Close}); actionOpen->setShortcut(QKeySequence{QKeySequence::Open}); actionQuit->setShortcut(QKeySequence{QKeySequence::Quit}); - tableWidget->sortByColumn(Pak::ColumnId, Qt::AscendingOrder); + tableView->sortByColumn(Pak::ColumnSize, Qt::AscendingOrder); + tableView->resizeColumnsToContents(); +} + +MainWindow::~MainWindow() { } void MainWindow::fileOpen() { @@ -27,13 +40,23 @@ void MainWindow::fileOpen() { if(!fileName.isEmpty()) { try { - auto st = openReadBin(fileName.toStdString()); - auto pak = readPak(st); - setTableToPakDir(*tableWidget, pak); + auto st = openReadBin(fileName.toStdString()); + m_directory = readPak(st); + m_model = new PakDirModel{&m_directory, this}; + m_sorter = new PakDirModelSorter{m_model}; + m_sorter->setSourceModel(m_model); + tableView->setModel(m_sorter); } catch(std::exception const &exc) { - m_errors.showMessage(tr(exc.what())); + m_errors->showMessage(tr(exc.what())); } } } +void MainWindow::fileClose() { + tableView->setModel(nullptr); + delete m_sorter; + delete m_model; + m_directory = PakDir{}; +} + // EOF diff --git a/source/quam/main_window.h b/source/quam/main_window.h index 3f12e5a..7c435e2 100644 --- a/source/quam/main_window.h +++ b/source/quam/main_window.h @@ -1,23 +1,31 @@ #pragma once +#include "quam/pak.h" #include "quam/ui_main_window.h" -#include -#include #include #include +class QAbstractItemModel; +class QErrorMessage; +class QSortFilterProxyModel; + class MainWindow : public QMainWindow, private Ui::MainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); + virtual ~MainWindow(); public slots: void fileOpen(); + void fileClose(); private: - QErrorMessage m_errors; + PakDir m_directory; + QErrorMessage *m_errors; + QAbstractItemModel *m_model; + QSortFilterProxyModel *m_sorter; }; // EOF diff --git a/source/quam/main_window.ui b/source/quam/main_window.ui index f93548f..aeac4c7 100644 --- a/source/quam/main_window.ui +++ b/source/quam/main_window.ui @@ -11,80 +11,53 @@ - Quam! + QuAM! - + - - - Qt::ScrollBarAlwaysOn + + + Qt::Horizontal - - Qt::ScrollBarAlwaysOff - - - false - - - true - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - true - - - true - - - true - - - true - - - false - - - - ID + + + Qt::ScrollBarAlwaysOn - - The ID of the file. This is the N-th file in the archive. + + Qt::ScrollBarAlwaysOff - - - - Size + + false - - The size of the file in bytes. + + true - - - - Name + + true - - The name of the file. + + QAbstractItemView::SingleSelection - + + QAbstractItemView::SelectRows + + + false + + + true + + + true + + + false + + + - - - @@ -101,6 +74,7 @@ &File + @@ -109,7 +83,8 @@ - + + .. &Open @@ -117,12 +92,21 @@ - + + .. &Quit + + + + + + &Close + + @@ -158,8 +142,26 @@ + + actionClose + triggered() + MainWindow + fileClose() + + + -1 + -1 + + + 319 + 239 + + + fileOpen() + selectCell(int,int) + fileClose() diff --git a/source/quam/pak.cc b/source/quam/pak.cc index 33c2c4a..7fca3db 100644 --- a/source/quam/pak.cc +++ b/source/quam/pak.cc @@ -1,6 +1,8 @@ #include "common.h" #include "quam/pak.h" +#include + struct PakHeader { quint32 dirOffset; quint32 dirNum; @@ -33,7 +35,7 @@ static PakHeader readPakHeader(std::istream &st) { return hdr; } -static PakEntry readPakEntry(std::istream &st, quint32 id) { +static PakEntry readPakEntry(std::istream &st) { auto entName = readBytes<56>(st); auto entOffset = readLE(st); auto entSize = readLE(st); @@ -43,7 +45,6 @@ static PakEntry readPakEntry(std::istream &st, quint32 id) { st.seekg(entOffset); PakFile file; - file.id = id; file.resize(entSize); st.read(file.data(), entSize); @@ -65,15 +66,29 @@ static PakEntry readPakEntry(std::istream &st, quint32 id) { } void insertFile(PakDir &dir, std::string name, PakFile file) { + std::optional next; + if(auto slash = name.find('/'); slash != std::string::npos) { - auto folder = name.substr(0, slash); - auto next = name.substr(slash + 1); - dir[folder] = PakNode{PakDir{}}; - insertFile(std::get(dir[folder]), - std::move(next), - std::move(file)); + next = name.substr(slash + 1); + name = name.substr(0, slash); + } + + auto existingNode = std::find_if(dir.begin(), dir.end(), + [&name](PakNode const &node) { + return node.name == name; + }); + + if(next) { + auto ref = + existingNode != dir.end() + ? *existingNode + : dir.emplace_back(PakDir{}, std::move(name)); + insertFile(std::get(ref), *std::move(next), std::move(file)); } else { - dir[name] = PakNode{std::move(file)}; + if(existingNode != dir.end()) { + throw std::runtime_error("duplicate file"); + } + dir.emplace_back(std::move(file), std::move(name)); } } @@ -83,57 +98,103 @@ PakDir readPak(std::istream &st) { PakDir root; - for(quint32 id = 0; id < hdr.dirNum; id++) { - auto ent = readPakEntry(st, id); + for(quint32 i = 0; i < hdr.dirNum; i++) { + auto ent = readPakEntry(st); insertFile(root, std::move(ent.name), std::move(ent.file)); } return root; } -void setTableToPakDir(QTableWidget &table, PakDir const &dir) { - constexpr auto Flags = Qt::ItemIsSelectable | - Qt::ItemIsDragEnabled | - Qt::ItemIsEnabled | - Qt::ItemNeverHasChildren; +PakDirModel::PakDirModel(PakDir const *root, QObject *parent) : + QAbstractItemModel{parent}, + m_root{root} +{ +} - auto sorted = table.isSortingEnabled(); - table.clearContents(); - table.setSortingEnabled(false); - quint32 row{0}; - for(auto const &kv : dir) { - auto const &name = kv.first; - auto const &node = kv.second; - table.setRowCount(row + 1); - { - auto item = new QTableWidgetItem; - item->setFlags(Flags); - if(auto file = std::get_if(&node)) { - item->setText(QString::number(file->id)); - } - table.setItem(row, Pak::ColumnId, item); - } - { - auto item = new QTableWidgetItem; - item->setFlags(Flags); - if(auto file = std::get_if(&node)) { - item->setText(QString::number(file->size())); - } - table.setItem(row, Pak::ColumnSize, item); - } - { - auto item = new QTableWidgetItem; - auto icon = std::holds_alternative(node) ? "folder" : - "text-x-generic"; - item->setFlags(Flags); - item->setText(QString::fromStdString(name)); - item->setIcon(QIcon::fromTheme(icon)); - table.setItem(row, Pak::ColumnName, item); - } - ++row; +PakDirModel::~PakDirModel() { +} + +QVariant PakDirModel::data(QModelIndex const &index, int role) const { + if(!index.isValid()) { + return QVariant{}; } - table.setSortingEnabled(sorted); - table.resizeColumnsToContents(); + + auto node = static_cast(index.internalPointer()); + + switch(role) { + case Qt::DecorationRole: + if(index.column() == Pak::ColumnName) { + auto icon = + std::holds_alternative(*node) ? "folder" + : "text-x-generic"; + return QVariant{QIcon::fromTheme(icon)}; + } + break; + case Qt::DisplayRole: + switch(index.column()) { + case Pak::ColumnSize: + if(auto file = std::get_if(node)) { + return QVariant{QString::number(file->size())}; + } + break; + case Pak::ColumnName: + return QVariant{tr(node->name.data())}; + } + default: + break; + } + return QVariant{}; +} + +Qt::ItemFlags PakDirModel::flags(QModelIndex const &index) const { + if(!index.isValid()) { + return Qt::NoItemFlags; + } else { + return Qt::ItemIsSelectable | + Qt::ItemIsDragEnabled | + Qt::ItemIsEnabled | + Qt::ItemNeverHasChildren; + } +} + +QVariant PakDirModel::headerData(int section, + Qt::Orientation orientation, + int role) const { + if(orientation == Qt::Horizontal && role == Qt::DisplayRole) { + switch(section) { + case Pak::ColumnSize: return QVariant{tr("Size")}; + case Pak::ColumnName: return QVariant{tr("Name")}; + } + } + return QVariant{}; +} + +QModelIndex PakDirModel::index(int row, + int col, + QModelIndex const &parent) const { + if(!hasIndex(row, col, parent) || row > m_root->size()) { + return QModelIndex{}; + } else { + // despite index data being const, this function does not take a const + // pointer, which is very annoying! + return createIndex(row, col, const_cast(&m_root->at(row))); + } +} + +QModelIndex PakDirModel::parent(QModelIndex const &) const { + return QModelIndex{}; +} + +int PakDirModel::rowCount(QModelIndex const &) const { + return m_root->size(); +} + +int PakDirModel::columnCount(QModelIndex const &) const { + return Pak::ColumnMax; +} + +PakDirModelSorter::~PakDirModelSorter() { } // EOF diff --git a/source/quam/pak.h b/source/quam/pak.h index 093b45b..2ec9693 100644 --- a/source/quam/pak.h +++ b/source/quam/pak.h @@ -1,35 +1,73 @@ #pragma once -#include +#include +#include namespace Pak { enum PakColumn { - ColumnId, ColumnSize, ColumnName, + ColumnMax, }; } struct PakNode; -struct PakDir : public std::map { - using std::map::map; +struct PakDir : public std::vector { + using std::vector::vector; }; Q_DECLARE_METATYPE(PakDir) struct PakFile : public QByteArray { using QByteArray::QByteArray; - - quint32 id{0}; }; Q_DECLARE_METATYPE(PakFile) struct PakNode : public std::variant { - using std::variant::variant; + PakNode() = default; + + template + constexpr PakNode(T &&t, std::string &&n) : + variant(std::move(t)), + name(std::move(n)) { + } + + PakNode(PakNode const &) = default; + PakNode(PakNode &&) = default; + + std::string name; }; Q_DECLARE_METATYPE(PakNode) +class PakDirModel : public QAbstractItemModel { + Q_OBJECT + +public: + explicit PakDirModel(PakDir const *root, QObject *parent); + virtual ~PakDirModel(); + + QVariant data(QModelIndex const &index, int role) const override; + Qt::ItemFlags flags(QModelIndex const &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) + const override; + QModelIndex index(int row, int col, QModelIndex const &parent) + const override; + QModelIndex parent(QModelIndex const &index) const override; + int rowCount(QModelIndex const &parent) const override; + int columnCount(QModelIndex const &parent) const override; + +private: + PakDir const *const m_root; +}; + +class PakDirModelSorter : public QSortFilterProxyModel { + Q_OBJECT + +public: + using QSortFilterProxyModel::QSortFilterProxyModel; + virtual ~PakDirModelSorter(); +}; + PakDir readPak(std::istream &st); -void setTableToPakDir(QTableWidget &table, PakDir const &dir); // EOF