Compare commits

..

No commits in common. "69fe7cf9caa27c4cd4ace5de40bd3e91cf0be27d" and "89414dd5ecec5936fcf0afb3d926ca0519b322f7" have entirely different histories.

10 changed files with 135 additions and 387 deletions

View File

@ -46,10 +46,7 @@ add_executable(
source/quam/main_window.h source/quam/main_window.h
source/quam/main_window.ui source/quam/main_window.ui
source/quam/pak.cc source/quam/pak.cc
source/quam/pak.h source/quam/pak.h)
source/quam/project.cc
source/quam/project.h
source/quam/project.ui)
make_qt_project(quam) make_qt_project(quam)

View File

@ -13,7 +13,7 @@
#include <ios> #include <ios>
#include <istream> #include <istream>
#include <iterator> #include <iterator>
#include <optional> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <utility> #include <utility>

View File

@ -1,23 +1,18 @@
#include "common.h" #include "common.h"
#include "quam/main_window.h" #include "quam/main_window.h"
#include "quam/project.h" #include "quam/pak.h"
#include <QErrorMessage>
#include <QFileDialog>
MainWindow::MainWindow(QWidget *parent) : MainWindow::MainWindow(QWidget *parent) :
QMainWindow{parent}, QMainWindow{parent},
Ui::MainWindow{}, Ui::MainWindow{},
m_errors{new QErrorMessage{this}} m_errors{}
{ {
setupUi(this); setupUi(this);
actionClose->setShortcut(QKeySequence{QKeySequence::Close});
actionOpen->setShortcut(QKeySequence{QKeySequence::Open}); actionOpen->setShortcut(QKeySequence{QKeySequence::Open});
actionQuit->setShortcut(QKeySequence{QKeySequence::Quit}); actionQuit->setShortcut(QKeySequence{QKeySequence::Quit});
}
MainWindow::~MainWindow() { tableWidget->sortByColumn(Pak::ColumnId, Qt::AscendingOrder);
} }
void MainWindow::fileOpen() { void MainWindow::fileOpen() {
@ -34,17 +29,11 @@ void MainWindow::fileOpen() {
try { try {
auto st = openReadBin(fileName.toStdString()); auto st = openReadBin(fileName.toStdString());
auto pak = readPak(st); auto pak = readPak(st);
new Project{std::move(pak), m_errors, mdiArea}; setTableToPakDir(*tableWidget, pak);
} catch(std::exception const &exc) { } catch(std::exception const &exc) {
m_errors->showMessage(tr(exc.what())); m_errors.showMessage(tr(exc.what()));
} }
} }
} }
void MainWindow::fileClose() {
if(auto win = mdiArea->activeSubWindow()) {
mdiArea->removeSubWindow(win);
}
}
// EOF // EOF

View File

@ -1,26 +1,23 @@
#pragma once #pragma once
#include "quam/pak.h"
#include "quam/ui_main_window.h" #include "quam/ui_main_window.h"
#include <QErrorMessage>
#include <QFileDialog>
#include <QMainWindow> #include <QMainWindow>
#include <QWidget> #include <QWidget>
class QErrorMessage;
class MainWindow : public QMainWindow, private Ui::MainWindow { class MainWindow : public QMainWindow, private Ui::MainWindow {
Q_OBJECT Q_OBJECT
public: public:
explicit MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
virtual ~MainWindow();
public slots: public slots:
void fileOpen(); void fileOpen();
void fileClose();
private: private:
QErrorMessage *m_errors; QErrorMessage m_errors;
}; };
// EOF // EOF

View File

@ -11,26 +11,80 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>QuAM!</string> <string>Quam!</string>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<widget class="QMdiArea" name="mdiArea"> <widget class="QTableWidget" name="tableWidget">
<property name="viewMode"> <property name="verticalScrollBarPolicy">
<enum>QMdiArea::TabbedView</enum> <enum>Qt::ScrollBarAlwaysOn</enum>
</property> </property>
<property name="documentMode"> <property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="autoScroll">
<bool>false</bool>
</property>
<property name="tabKeyNavigation">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="tabsClosable"> <property name="alternatingRowColors">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="tabsMovable"> <property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>ID</string>
</property>
<property name="toolTip">
<string>The ID of the file. This is the N-th file in the archive.</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
<property name="toolTip">
<string>The size of the file in bytes.</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>The name of the file.</string>
</property>
</column>
</widget> </widget>
</item> </item>
<item>
<widget class="QPlainTextEdit" name="textEdit"/>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QMenuBar" name="menubar"> <widget class="QMenuBar" name="menubar">
@ -47,7 +101,6 @@
<string>&amp;File</string> <string>&amp;File</string>
</property> </property>
<addaction name="actionOpen"/> <addaction name="actionOpen"/>
<addaction name="actionClose"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionQuit"/> <addaction name="actionQuit"/>
</widget> </widget>
@ -56,8 +109,7 @@
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
<action name="actionOpen"> <action name="actionOpen">
<property name="icon"> <property name="icon">
<iconset theme="document-open"> <iconset theme="document-open"/>
<normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Open</string> <string>&amp;Open</string>
@ -65,22 +117,12 @@
</action> </action>
<action name="actionQuit"> <action name="actionQuit">
<property name="icon"> <property name="icon">
<iconset theme="application-exit"> <iconset theme="application-exit"/>
<normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Quit</string> <string>&amp;Quit</string>
</property> </property>
</action> </action>
<action name="actionClose">
<property name="icon">
<iconset theme="document-close">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Close</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>
@ -116,26 +158,8 @@
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection>
<sender>actionClose</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>fileClose()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>319</x>
<y>239</y>
</hint>
</hints>
</connection>
</connections> </connections>
<slots> <slots>
<slot>fileOpen()</slot> <slot>fileOpen()</slot>
<slot>selectCell(int,int)</slot>
<slot>fileClose()</slot>
</slots> </slots>
</ui> </ui>

View File

@ -1,9 +1,6 @@
#include "common.h" #include "common.h"
#include "quam/pak.h" #include "quam/pak.h"
#include <QIcon>
#include <QMetaEnum>
struct PakHeader { struct PakHeader {
quint32 dirOffset; quint32 dirOffset;
quint32 dirNum; quint32 dirNum;
@ -36,7 +33,7 @@ static PakHeader readPakHeader(std::istream &st) {
return hdr; return hdr;
} }
static PakEntry readPakEntry(std::istream &st) { static PakEntry readPakEntry(std::istream &st, quint32 id) {
auto entName = readBytes<56>(st); auto entName = readBytes<56>(st);
auto entOffset = readLE<quint32>(st); auto entOffset = readLE<quint32>(st);
auto entSize = readLE<quint32>(st); auto entSize = readLE<quint32>(st);
@ -46,6 +43,7 @@ static PakEntry readPakEntry(std::istream &st) {
st.seekg(entOffset); st.seekg(entOffset);
PakFile file; PakFile file;
file.id = id;
file.resize(entSize); file.resize(entSize);
st.read(file.data(), entSize); st.read(file.data(), entSize);
@ -67,29 +65,15 @@ static PakEntry readPakEntry(std::istream &st) {
} }
void insertFile(PakDir &dir, std::string name, PakFile file) { void insertFile(PakDir &dir, std::string name, PakFile file) {
std::optional<std::string> next;
if(auto slash = name.find('/'); slash != std::string::npos) { if(auto slash = name.find('/'); slash != std::string::npos) {
next = name.substr(slash + 1); auto folder = name.substr(0, slash);
name = name.substr(0, slash); auto next = name.substr(slash + 1);
} dir[folder] = PakNode{PakDir{}};
insertFile(std::get<PakDir>(dir[folder]),
auto existingNode = std::find_if(dir.begin(), dir.end(), std::move(next),
[&name](PakNode const &node) { std::move(file));
return node.name == name;
});
if(next) {
auto ref =
existingNode != dir.end()
? *existingNode
: dir.emplace_back(PakDir{}, std::move(name));
insertFile(std::get<PakDir>(ref), *std::move(next), std::move(file));
} else { } else {
if(existingNode != dir.end()) { dir[name] = PakNode{std::move(file)};
throw std::runtime_error("duplicate file");
}
dir.emplace_back(std::move(file), std::move(name));
} }
} }
@ -99,113 +83,57 @@ PakDir readPak(std::istream &st) {
PakDir root; PakDir root;
for(quint32 i = 0; i < hdr.dirNum; i++) { for(quint32 id = 0; id < hdr.dirNum; id++) {
auto ent = readPakEntry(st); auto ent = readPakEntry(st, id);
insertFile(root, std::move(ent.name), std::move(ent.file)); insertFile(root, std::move(ent.name), std::move(ent.file));
} }
return root; return root;
} }
PakDirRoot::PakDirRoot(PakDir &&root, QObject *parent) : void setTableToPakDir(QTableWidget &table, PakDir const &dir) {
QObject{parent}, constexpr auto Flags = Qt::ItemIsSelectable |
PakDir{std::move(root)}
{
}
PakDirRoot::~PakDirRoot() {
}
PakDirModel::PakDirModel(PakDir const *root, QObject *parent) :
QAbstractItemModel{parent},
m_root{root}
{
}
PakDirModel::~PakDirModel() {
}
QVariant PakDirModel::data(QModelIndex const &index, int role) const {
if(!index.isValid()) {
return QVariant{};
}
auto node = static_cast<PakNode *>(index.internalPointer());
auto col = Pak::Column(index.column());
switch(role) {
case Qt::DecorationRole:
if(col == Pak::Column::Name) {
auto icon =
std::holds_alternative<PakDir>(*node) ? "folder"
: "text-x-generic";
return QVariant{QIcon::fromTheme(icon)};
}
break;
case Qt::DisplayRole:
switch(col) {
case Pak::Column::Size:
if(auto file = std::get_if<PakFile>(node)) {
return QVariant{QString::number(file->size())};
}
break;
case Pak::Column::Name:
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::ItemIsDragEnabled |
Qt::ItemIsEnabled | Qt::ItemIsEnabled |
Qt::ItemNeverHasChildren; Qt::ItemNeverHasChildren;
}
}
QVariant PakDirModel::headerData(int section, auto sorted = table.isSortingEnabled();
Qt::Orientation orientation, table.clearContents();
int role) const { table.setSortingEnabled(false);
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) { quint32 row{0};
switch(Pak::Column(section)) { for(auto const &kv : dir) {
case Pak::Column::Size: return QVariant{tr("Size")}; auto const &name = kv.first;
case Pak::Column::Name: return QVariant{tr("Name")}; auto const &node = kv.second;
table.setRowCount(row + 1);
{
auto item = new QTableWidgetItem;
item->setFlags(Flags);
if(auto file = std::get_if<PakFile>(&node)) {
item->setText(QString::number(file->id));
} }
table.setItem(row, Pak::ColumnId, item);
} }
return QVariant{}; {
auto item = new QTableWidgetItem;
item->setFlags(Flags);
if(auto file = std::get_if<PakFile>(&node)) {
item->setText(QString::number(file->size()));
} }
table.setItem(row, Pak::ColumnSize, item);
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<PakNode *>(&m_root->at(row)));
} }
{
auto item = new QTableWidgetItem;
auto icon = std::holds_alternative<PakDir>(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;
QModelIndex PakDirModel::parent(QModelIndex const &) const {
return QModelIndex{};
} }
table.setSortingEnabled(sorted);
int PakDirModel::rowCount(QModelIndex const &) const { table.resizeColumnsToContents();
return m_root->size();
}
int PakDirModel::columnCount(QModelIndex const &) const {
return QMetaEnum::fromType<Pak::Column>().keyCount();
}
PakDirModelSorter::~PakDirModelSorter() {
} }
// EOF // EOF

View File

@ -1,83 +1,35 @@
#pragma once #pragma once
#include <QAbstractItemModel> #include <QTableWidget>
#include <QSortFilterProxyModel>
namespace Pak { namespace Pak {
Q_NAMESPACE enum PakColumn {
ColumnId,
enum class Column { ColumnSize,
Size, ColumnName,
Name,
}; };
Q_ENUM_NS(Column)
} }
struct PakNode; struct PakNode;
struct PakDir : public std::vector<PakNode> { struct PakDir : public std::map<std::string, PakNode> {
using std::vector<PakNode>::vector; using std::map<std::string, PakNode>::map;
}; };
Q_DECLARE_METATYPE(PakDir) Q_DECLARE_METATYPE(PakDir)
struct PakFile : public QByteArray { struct PakFile : public QByteArray {
using QByteArray::QByteArray; using QByteArray::QByteArray;
quint32 id{0};
}; };
Q_DECLARE_METATYPE(PakFile) Q_DECLARE_METATYPE(PakFile)
struct PakNode : public std::variant<PakDir, PakFile> { struct PakNode : public std::variant<PakDir, PakFile> {
PakNode() = default; using std::variant<PakDir, PakFile>::variant;
template<typename T>
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) Q_DECLARE_METATYPE(PakNode)
class PakDirRoot : public QObject, public PakDir {
Q_OBJECT
public:
explicit PakDirRoot(PakDir &&root, QObject *parent);
virtual ~PakDirRoot();
};
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); PakDir readPak(std::istream &st);
void setTableToPakDir(QTableWidget &table, PakDir const &dir);
// EOF // EOF

View File

@ -1,41 +0,0 @@
#include "common.h"
#include "quam/project.h"
#include <QAbstractItemModel>
#include <QErrorMessage>
#include <QMdiArea>
#include <QSortFilterProxyModel>
Project::Project(QErrorMessage *errors, QMdiArea *parent) :
QMdiSubWindow{parent},
Ui::Project{},
m_errors{errors}
{
auto widget = new QWidget(this);
setupUi(widget);
setWidget(widget);
setAttribute(Qt::WA_DeleteOnClose);
showMaximized();
}
Project::Project(PakDir &&dir, QErrorMessage *errors, QMdiArea *parent) :
Project{errors, parent}
{
auto root = new PakDirRoot{std::move(dir), this};
auto model = new PakDirModel{root, this};
setupModel(model);
}
Project::~Project() {
}
void Project::setupModel(QAbstractItemModel *model) {
m_model = model;
m_sorter = new QSortFilterProxyModel{this};
m_sorter->setSourceModel(m_model);
tableView->setModel(m_sorter);
tableView->sortByColumn(int(Pak::Column::Name), Qt::AscendingOrder);
tableView->resizeColumnsToContents();
}
// EOF

View File

@ -1,31 +0,0 @@
#pragma once
#include "quam/pak.h"
#include "quam/ui_project.h"
#include <QMainWindow>
#include <QMdiSubWindow>
#include <QWidget>
class QAbstractItemModel;
class QErrorMessage;
class QSortFilterProxyModel;
class Project : public QMdiSubWindow, private Ui::Project {
Q_OBJECT
public:
explicit Project(PakDir &&dir, QErrorMessage *errors, QMdiArea *parent);
virtual ~Project();
private:
explicit Project(QErrorMessage *errors, QMdiArea *parent);
void setupModel(QAbstractItemModel *model);
QErrorMessage *m_errors;
QAbstractItemModel *m_model;
QSortFilterProxyModel *m_sorter;
};
// EOF

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Project</class>
<widget class="QWidget" name="Project">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>640</width>
<height>480</height>
</rect>
</property>
<property name="windowTitle">
<string>Project View</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTableView" name="tableView">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="autoScroll">
<bool>false</bool>
</property>
<property name="tabKeyNavigation">
<bool>true</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
<widget class="QPlainTextEdit" name="textEdit"/>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>