From d904117b798dcf71d32728f90f4201c8dec817ae Mon Sep 17 00:00:00 2001 From: Alison Watson Date: Thu, 7 Nov 2019 01:42:59 -0500 Subject: [PATCH] implement up/forward/back/top buttons --- CMakeLists.txt | 9 ++- source/common.h | 5 +- source/quam/archive.cc | 119 +++++++++++++++++++++++++++++++++---- source/quam/archive.h | 45 ++++++++++---- source/quam/main.cc | 13 ++-- source/quam/main_window.cc | 42 ++++++++++++- source/quam/main_window.h | 5 ++ source/quam/main_window.ui | 101 +++++++++++++++++++++++++++---- source/quam/pack.cc | 25 ++++---- source/quam/project.cc | 84 ++++++++++++++++++++++---- source/quam/project.h | 29 +++++---- source/quam/project.ui | 115 +++++++++++++++++++++++++++++++++-- source/quam/wad2.cc | 10 ++-- 13 files changed, 510 insertions(+), 92 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b2572f7..12a8802 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,9 +27,12 @@ macro(make_qt_project TARGET_NAME) target_compile_definitions( ${TARGET_NAME} PUBLIC - -DQT_DEPRECATED_WARNINGS - -DQT_STRICT_ITERATORS - -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) + -DQT_DISABLE_DEPRECATED_BEFORE=0x051300 + -DQT_NO_CAST_FROM_ASCII + -DQT_NO_CAST_TO_ASCII + -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT + -DQT_NO_URL_CAST_FROM_STRING + -DQT_STRICT_ITERATORS) endmacro() find_package( diff --git a/source/common.h b/source/common.h index e50dcae..45fa816 100644 --- a/source/common.h +++ b/source/common.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -28,8 +29,6 @@ using Option = std::optional; template using Variant = std::variant; -using Path = std::filesystem::path; - template using UniquePtr = std::unique_ptr; @@ -104,7 +103,7 @@ static inline std::array readBytes(std::istream &st) { return std::move(b); } -static inline std::ifstream openReadBin(Path path) { +static inline std::ifstream openReadBin(std::filesystem::path path) { return std::ifstream{path, std::ios_base::in | std::ios_base::binary}; } diff --git a/source/quam/archive.cc b/source/quam/archive.cc index 648e4c7..6610302 100644 --- a/source/quam/archive.cc +++ b/source/quam/archive.cc @@ -18,42 +18,120 @@ namespace Arc { return None; } - Node::Node(std::string _name, Dir *_parent) : + Node::Node(std::string _name) : name(_name), - parent(_parent) + parent(nullptr) { } Node::~Node() { } - Dir::Dir(std::string _name, Dir *_parent) : - Node(_name, _parent), - baseType() + Dir *Node::getRoot() { + if(!parent) { + return getDir(); + } else { + Dir *rover = parent; + while(rover->parent) { + rover = rover->parent; + } + return rover; + } + } + + Node *Node::findPath(std::string path) { + if(path.empty()) { + return nullptr; + } + + Dir *rover; + + // get the initial dir, which will either be: + // - the root if the path starts with / + // - the node itself + // - the node's current Dir (when it's not a Dir) + if(path.front() == '/') { + path.erase(path.begin()); + rover = getRoot(); + } else { + rover = getDir(); + if(!rover) { + rover = parent; + } + } + + if(path.empty()) { + // no file, so just the directory itself + return rover; + } else if(auto slash = path.find('/'); slash != std::string::npos) { + std::string prev; + prev = path.substr(0, slash); + path = path.substr(slash + 1); + + if(prev == "..") { + // parent dir on ".." + rover = rover->parent ? rover->parent : rover; + } else if(prev != ".") { + if(auto node = rover->findNode(prev)) { + // we want a subdirectory since this path continues + rover = node->getDir(); + } else { + // took a wrong turn + rover = nullptr; + } + } + + // recurse + return rover ? rover->findPath(path) : nullptr; + } else { + // must be a node inside a directory + return rover->findNode(path); + } + } + + std::string Node::getPath(bool fromChild) const { + if(parent) { + return parent->getPath(true) + '/' + name; + } else if(fromChild) { + return std::string{}; + } else { + return "/"; + } + } + + Dir::Dir(std::string _name) : + Node(_name), + m_data() { } Dir::~Dir() { } + UniquePtr const &Dir::emplaceBack(Node *node) { + node->parent = this; + return m_data.emplace_back(node); + } + Dir Dir::readArchive(std::istream &st, ArcType type) { switch(type) { case ArcType::Pack: return readPack(st); case ArcType::Wad2: return readWad2(st); } + Q_UNREACHABLE(); } Node *Dir::findNode(std::string const &name) { - auto it = std::find_if(begin(), end(), + auto it = std::find_if(m_data.begin(), m_data.end(), [&name](auto const &node) { return node->name == name; }); - return it != end() ? &**it : nullptr; + return it != m_data.end() ? &**it : nullptr; } - File::File(std::string _name, Dir *_parent) : - Node(_name, _parent), - baseType() + File::File(std::string _name, QByteArray &&_data) : + Node(_name), + m_data(std::move(_data)) { } @@ -110,7 +188,7 @@ namespace Arc { case Qt::DecorationRole: if(col == Column::Name) { auto icon = node->getDir() ? "folder" : "text-x-generic"; - return QVariant{QIcon::fromTheme(icon)}; + return QVariant{QIcon::fromTheme(QString::fromUtf8(icon))}; } break; case Qt::DisplayRole: @@ -167,7 +245,7 @@ namespace Arc { if(!m_dir || !hasIndex(row, col, parent) || row > m_dir->size()) { return QModelIndex{}; } else { - return createIndex(row, col, &*m_dir->at(row)); + return createIndex(row, col, m_dir->at(row)); } } @@ -187,6 +265,10 @@ namespace Arc { return m_dir; } + Node *Model::findPath(std::string path) { + return m_dir->findPath(path); + } + void Model::setDir(Dir *to) { auto from = m_dir; if(to != from) { @@ -209,7 +291,7 @@ namespace Arc { } } - bool Model::dirUp() { + bool Model::goUp() { if(m_dir && m_dir->parent) { setDir(m_dir->parent); return true; @@ -217,6 +299,17 @@ namespace Arc { return false; } } + + bool Model::goPath(std::string path) { + Node *node; + Dir *dir; + if(m_dir && (node = m_dir->findPath(path)) && (dir = node->getDir())) { + setDir(dir); + return true; + } else { + return false; + } + } } // EOF diff --git a/source/quam/archive.h b/source/quam/archive.h index 6c1d184..a2c085d 100644 --- a/source/quam/archive.h +++ b/source/quam/archive.h @@ -50,18 +50,23 @@ namespace Arc { virtual File const *getFile() const = 0; virtual File *getFile() = 0; + Dir *getRoot(); + Node *findPath(std::string path); + + std::string getPath(bool fromChild = false) const; + Dir *parent{nullptr}; std::string name; protected: - Node(std::string name, Dir *parent); + Node(std::string name); }; - class Dir : public Node, public std::vector> { + class Dir : public Node { public: - using baseType = std::vector>; + using DataType = std::vector>; - Dir(std::string name, Dir *parent = nullptr); + Dir(std::string name); Dir(Dir const &) = delete; Dir(Dir &&) = default; virtual ~Dir(); @@ -71,16 +76,25 @@ namespace Arc { File const *getFile() const override {return nullptr;} File *getFile() override {return nullptr;} + DataType const &data() const {return m_data;} + Node *at(std::size_t i) const {return &*m_data.at(i);} + + std::size_t size() const {return m_data.size();} + + UniquePtr const &emplaceBack(Node *node); Node *findNode(std::string const &name); static Dir readArchive(std::istream &st, ArcType type); + + private: + DataType m_data; }; - class File : public Node, public QByteArray { + class File : public Node { public: - using baseType = QByteArray; + using DataType = QByteArray; - File(std::string name, Dir *parent = nullptr); + File(std::string name, QByteArray &&data); File(File const &) = delete; File(File &&) = default; virtual ~File(); @@ -90,7 +104,14 @@ namespace Arc { File const *getFile() const override {return this;} File *getFile() override {return this;} + DataType const &data() const {return m_data;} + + std::size_t size() const {return m_data.size();} + FileType type{FileType::Normal}; + + private: + DataType m_data; }; class Model : public QAbstractItemModel { @@ -115,10 +136,14 @@ namespace Arc { Dir *dir() const; + Node *findPath(std::string path); + public slots: void setDir(Dir *dir); void setDirToIndex(QModelIndex const &ind); - bool dirUp(); + + bool goUp(); + bool goPath(std::string path); signals: void dirChanged(Dir *from, Dir *to); @@ -152,12 +177,12 @@ namespace Arc { } static inline QDebug operator<<(QDebug debug, Arc::Dir const &t) { - debug << static_cast(t); + debug << t.data(); return debug; } static inline QDebug operator<<(QDebug debug, Arc::File const &t) { - debug << static_cast(t); + debug << t.data(); return debug; } diff --git a/source/quam/main.cc b/source/quam/main.cc index dca3194..01716bb 100644 --- a/source/quam/main.cc +++ b/source/quam/main.cc @@ -6,10 +6,10 @@ #include static void setupAppl(QCoreApplication &appl) { - appl.setApplicationName("QuAM!"); - appl.setApplicationVersion("1.0"); - appl.setOrganizationDomain("greyserv.net"); - appl.setOrganizationName("Project Golan"); + appl.setApplicationName(QString::fromUtf8("QuAM!")); + appl.setApplicationVersion(QString::fromUtf8("1.0")); + appl.setOrganizationDomain(QString::fromUtf8("greyserv.net")); + appl.setOrganizationName(QString::fromUtf8("Project Golan")); } static int modeGui(int argc, char *argv[]) { @@ -26,14 +26,15 @@ static int modeText(int argc, char *argv[]) { QCommandLineParser par; - par.setApplicationDescription("Quake Archive Manager"); + par.setApplicationDescription(QString::fromUtf8("Quake Archive Manager")); par.setSingleDashWordOptionMode( QCommandLineParser::ParseAsCompactedShortOptions); par.addHelpOption(); par.addVersionOption(); - QCommandLineOption fileNameOpt{QStringList{"f", "file"}, + QCommandLineOption fileNameOpt{QStringList{QString::fromUtf8("f"), + QString::fromUtf8("file")}, trMain("Open the archive ."), trMain("file")}; par.addOption(fileNameOpt); diff --git a/source/quam/main_window.cc b/source/quam/main_window.cc index 78076be..2c1d0dd 100644 --- a/source/quam/main_window.cc +++ b/source/quam/main_window.cc @@ -5,6 +5,16 @@ #include #include +#include + +static bool applyActiveWin(QMdiArea *area, std::function fn) { + if(auto subWin = area->activeSubWindow()) { + if(auto win = qobject_cast(subWin->widget())) { + return fn(win); + } + } + return false; +} MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}, @@ -16,6 +26,11 @@ MainWindow::MainWindow(QWidget *parent) : actionClose->setShortcut(QKeySequence{QKeySequence::Close}); actionOpen->setShortcut(QKeySequence{QKeySequence::Open}); actionQuit->setShortcut(QKeySequence{QKeySequence::Quit}); + + actionUp->setShortcut(Qt::ALT + Qt::Key_Up); + actionTop->setShortcut(Qt::ALT + Qt::Key_Home); + actionBack->setShortcut(QKeySequence{QKeySequence::Back}); + actionForward->setShortcut(QKeySequence{QKeySequence::Forward}); } MainWindow::~MainWindow() { @@ -34,8 +49,15 @@ void MainWindow::fileOpen() { if(!fileName.isEmpty()) { try { - auto st = openReadBin(fileName.toStdString()); - new Project{st, m_errors, mdiArea}; + auto path = std::filesystem::path(fileName.toStdString()); + auto st = openReadBin(path); + auto win = new QMdiSubWindow; + auto proj = new Project{st, m_errors, win}; + win->setWidget(proj); + win->setAttribute(Qt::WA_DeleteOnClose); + win->setWindowTitle(QString::fromStdString(path.filename())); + mdiArea->addSubWindow(win); + win->showMaximized(); } catch(std::exception const &exc) { m_errors->showMessage(tr(exc.what())); } @@ -48,4 +70,20 @@ void MainWindow::fileClose() { } } +bool MainWindow::goBack() { + return applyActiveWin(mdiArea, [](auto win) {return win->goBack();}); +} + +bool MainWindow::goForward() { + return applyActiveWin(mdiArea, [](auto win) {return win->goForward();}); +} + +bool MainWindow::goTop() { + return applyActiveWin(mdiArea, [](auto win) {return win->goTop();}); +} + +bool MainWindow::goUp() { + return applyActiveWin(mdiArea, [](auto win) {return win->goUp();}); +} + // EOF diff --git a/source/quam/main_window.h b/source/quam/main_window.h index 40acbc5..6d6bb7b 100644 --- a/source/quam/main_window.h +++ b/source/quam/main_window.h @@ -20,6 +20,11 @@ public slots: void fileOpen(); void fileClose(); + bool goBack(); + bool goForward(); + bool goTop(); + bool goUp(); + private: QErrorMessage *m_errors; }; diff --git a/source/quam/main_window.ui b/source/quam/main_window.ui index d340dc5..2d3acea 100644 --- a/source/quam/main_window.ui +++ b/source/quam/main_window.ui @@ -17,6 +17,15 @@ + + + + 0 + 0 + 0 + + + QMdiArea::TabbedView @@ -39,7 +48,7 @@ 0 0 640 - 29 + 24 @@ -53,7 +62,7 @@ - Go + &Go @@ -93,34 +102,38 @@ - + + .. - Up + &Up - + + .. - Back + &Back - + + .. - Forward + &Forward - + + .. - Top + &Top @@ -174,10 +187,78 @@ + + actionUp + triggered() + MainWindow + goUp() + + + -1 + -1 + + + 319 + 239 + + + + + actionTop + triggered() + MainWindow + goTop() + + + -1 + -1 + + + 319 + 239 + + + + + actionBack + triggered() + MainWindow + goBack() + + + -1 + -1 + + + 319 + 239 + + + + + actionForward + triggered() + MainWindow + goForward() + + + -1 + -1 + + + 319 + 239 + + + fileOpen() selectCell(int,int) fileClose() + goUp() + goForward() + goBack() + goTop() diff --git a/source/quam/pack.cc b/source/quam/pack.cc index b5426a3..d7bc8a4 100644 --- a/source/quam/pack.cc +++ b/source/quam/pack.cc @@ -24,21 +24,20 @@ static Arc::File readPackEntry(std::istream &st) { auto entOffset = readLE(st); auto entSize = readLE(st); + if(entName.front() == '/') { + throw FileFormatError("empty root directory name"); + } + auto pos = st.tellg(); st.seekg(entOffset); - Arc::File file{ntbsToString(entName)}; - - file.resize(entSize); - st.read(file.data(), entSize); + QByteArray data; + data.resize(entSize); + st.read(data.data(), entSize); st.seekg(pos); - if(file.name.front() == '/') { - throw FileFormatError("empty root directory name"); - } - - return file; + return Arc::File{ntbsToString(entName), std::move(data)}; } static void insertFile(Arc::Dir &dir, Arc::File &file, std::string name) { @@ -55,12 +54,14 @@ static void insertFile(Arc::Dir &dir, Arc::File &file, std::string name) { if(existing) { ref = existing; } else { - ref = &*dir.emplace_back(new Arc::Dir(name, &dir)); + ref = new Arc::Dir(name); + dir.emplaceBack(ref); } insertFile(*ref->getDir(), file, *next); } else if(!existing) { - file.name = name; - dir.emplace_back(new Arc::File(std::move(file))); + file.name = name; + file.parent = &dir; + dir.emplaceBack(new Arc::File(std::move(file))); } else { throw FileFormatError("duplicate file"); } diff --git a/source/quam/project.cc b/source/quam/project.cc index d916c0b..e9084c8 100644 --- a/source/quam/project.cc +++ b/source/quam/project.cc @@ -1,24 +1,20 @@ #include "quam/project.h" -#include #include -#include #include -Project::Project(std::istream &st, QErrorMessage *errors, QMdiArea *parent) : - QMdiSubWindow{parent}, +Project::Project(std::istream &st, QErrorMessage *errors, QWidget *parent) : + QWidget{parent}, Ui::Project{}, + m_seekingDir{false}, + m_histPos{0}, + m_hist(), m_arc{new Arc::Arc{st, this}}, - m_root{&m_arc->root}, m_errors{errors}, m_model{new Arc::Model{this}}, m_sorter{new QSortFilterProxyModel{this}} { - auto widget = new QWidget{this}; - setupUi(widget); - setWidget(widget); - setAttribute(Qt::WA_DeleteOnClose); - showMaximized(); + setupUi(this); connect(m_model, &Arc::Model::dirChanged, this, &Project::dirChanged); @@ -28,18 +24,80 @@ Project::Project(std::istream &st, QErrorMessage *errors, QMdiArea *parent) : dirName->setValidator(m_arc->validator); m_sorter->setSourceModel(m_model); tableView->setModel(m_sorter); - m_model->setDir(m_root); + seekTop(); } Project::~Project() { } void Project::dirChanged(Arc::Dir *from, Arc::Dir *to) { + if(!m_seekingDir) { + if(std::ptrdiff_t(m_histPos) < std::ptrdiff_t(m_hist.size()) - 1) { + auto beg = m_hist.begin(); + std::advance(beg, m_histPos + 1); + m_hist.erase(beg, m_hist.end()); + } + + m_hist.push_back(to->getPath()); + m_histPos = m_hist.size() - 1; + } else { + m_seekingDir = false; + } + + tableView->sortByColumn(int(Arc::Column::Type), Qt::AscendingOrder); tableView->resizeColumnsToContents(); } -bool Project::dirUp() { - return m_model->dirUp(); +void Project::seekTop() { + m_model->setDir(&m_arc->root); +} + +bool Project::seekHistory(std::ptrdiff_t newPos) { + if(newPos < 0 || newPos >= m_hist.size()) { + return false; + } + + m_histPos = newPos; + + qDebug() << "hist pos changed:" << m_histPos; + + auto newPath = m_hist.at(newPos); + if(auto node = m_model->findPath(newPath)) { + if(auto dir = node->getDir()) { + m_seekingDir = true; + m_model->setDir(dir); + return true; + } else { + m_errors->showMessage(QString::fromStdString("'"s + newPath + "' ") + + tr("is not a directory")); + return false; + } + } else { + m_errors->showMessage(tr("could not find directory") + + QString::fromStdString(" '"s + newPath + "'")); + return false; + } +} + +bool Project::goBack() { + return seekHistory(std::ptrdiff_t(m_histPos) - 1); +} + +bool Project::goForward() { + return seekHistory(std::ptrdiff_t(m_histPos) + 1); +} + +bool Project::goTop() { + seekTop(); + return true; +} + +bool Project::goUp() { + return m_model->goUp(); +} + +bool Project::goPath(std::string path) { + return m_model->goPath(path); } // EOF diff --git a/source/quam/project.h b/source/quam/project.h index b2fd4c1..2098b7d 100644 --- a/source/quam/project.h +++ b/source/quam/project.h @@ -5,33 +5,40 @@ #include "quam/archive.h" #include "quam/ui_project.h" -#include -#include #include -class QAbstractItemModel; class QErrorMessage; class QSortFilterProxyModel; -class Project : public QMdiSubWindow, private Ui::Project { +class Project : public QWidget, private Ui::Project { Q_OBJECT public: - explicit Project(std::istream &st, QErrorMessage *errors, QMdiArea *parent); + explicit Project(std::istream &st, QErrorMessage *errors, QWidget *parent); virtual ~Project(); + bool seekHistory(std::ptrdiff_t newPos); + void seekTop(); + public slots: - bool dirUp(); + bool goBack(); + bool goForward(); + bool goTop(); + bool goUp(); + + bool goPath(std::string path); private slots: void dirChanged(Arc::Dir *from, Arc::Dir *to); private: - Arc::Arc *m_arc; - Arc::Dir *m_root; - QErrorMessage *m_errors; - Arc::Model *m_model; - QSortFilterProxyModel *m_sorter; + bool m_seekingDir; + std::size_t m_histPos; + std::vector m_hist; + Arc::Arc *m_arc; + QErrorMessage *m_errors; + Arc::Model *m_model; + QSortFilterProxyModel *m_sorter; }; // EOF diff --git a/source/quam/project.ui b/source/quam/project.ui index 021d00e..02c6323 100644 --- a/source/quam/project.ui +++ b/source/quam/project.ui @@ -13,16 +13,68 @@ Project View - + Qt::Horizontal - + - + + + + + + 0 + 0 + + + + + + + true + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + @@ -72,5 +124,60 @@ - + + + buttonBack + clicked() + Project + goBack() + + + 18 + 17 + + + 319 + 239 + + + + + buttonForward + clicked() + Project + goForward() + + + 47 + 17 + + + 319 + 239 + + + + + buttonUp + clicked() + Project + goUp() + + + 76 + 17 + + + 319 + 239 + + + + + + goUp() + goForward() + goBack() + goTop() + diff --git a/source/quam/wad2.cc b/source/quam/wad2.cc index 009227f..c0d1067 100644 --- a/source/quam/wad2.cc +++ b/source/quam/wad2.cc @@ -49,14 +49,14 @@ static Arc::File readWad2Entry(std::istream &st) { st.seekg(entOffset); - Arc::File file{ntbsToString(entName)}; + QByteArray data; - file.resize(entSize); - st.read(file.data(), entSize); + data.resize(entSize); + st.read(data.data(), entSize); st.seekg(pos); + Arc::File file{ntbsToString(entName), std::move(data)}; file.type = getFileType(entType); - return file; } @@ -67,7 +67,7 @@ Arc::Dir readWad2(std::istream &st) { Arc::Dir root{std::string{}}; for(quint32 i = 0; i < hdr.second; i++) { - root.emplace_back(new Arc::File(readWad2Entry(st))); + root.emplaceBack(new Arc::File(readWad2Entry(st))); } return root;