From 154892ac2b0947b95746b8da2d451cadf56dbfab Mon Sep 17 00:00:00 2001 From: craftablescience Date: Tue, 19 Sep 2023 00:03:39 -0400 Subject: [PATCH] feat: display error preview if reading a file fails --- CMakeLists.txt | 4 +- src/gui/EntryTree.cpp | 1 + src/gui/FileViewer.cpp | 83 ++++++++++++------------------ src/gui/FileViewer.h | 37 ++++++++----- src/gui/Window.cpp | 26 ++++++---- src/gui/Window.h | 4 +- src/gui/previews/ErrorPreview.cpp | 21 ++++++++ src/gui/previews/ErrorPreview.h | 14 +++++ src/gui/res/error.png | Bin 0 -> 7726 bytes src/gui/res/res.qrc | 1 + 10 files changed, 115 insertions(+), 76 deletions(-) create mode 100644 src/gui/previews/ErrorPreview.cpp create mode 100644 src/gui/previews/ErrorPreview.h create mode 100644 src/gui/res/error.png diff --git a/CMakeLists.txt b/CMakeLists.txt index 308cce77..fa3aa12c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.25 FATAL_ERROR) project(vpkedit DESCRIPTION "A tool to read, preview, and write to VPK files." - VERSION 3.1.2 + VERSION 3.2.0 HOMEPAGE_URL "https://github.com/craftablescience/VPKEdit") set(PROJECT_NAME_PRETTY "VPKEdit" CACHE STRING "" FORCE) set(CMAKE_CXX_STANDARD 17) @@ -110,6 +110,8 @@ if(VPKEDIT_BUILD_GUI) "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/popups/NewVPKDialog.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/previews/DirPreview.h" "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/previews/DirPreview.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/previews/ErrorPreview.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/previews/ErrorPreview.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/previews/ImagePreview.h" "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/previews/ImagePreview.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/gui/previews/TextPreview.h" diff --git a/src/gui/EntryTree.cpp b/src/gui/EntryTree.cpp index 6e80ac13..1521315b 100644 --- a/src/gui/EntryTree.cpp +++ b/src/gui/EntryTree.cpp @@ -262,6 +262,7 @@ void EntryTree::removeEntry(QTreeWidgetItem* item) { } } +// NOLINTNEXTLINE(*-no-recursion) void EntryTree::removeEntryRecurse(QTreeWidgetItem* item) { if (item->childCount() == 0) { this->window->removeFile(this->getItemPath(item)); diff --git a/src/gui/FileViewer.cpp b/src/gui/FileViewer.cpp index 0bf788da..d8311189 100644 --- a/src/gui/FileViewer.cpp +++ b/src/gui/FileViewer.cpp @@ -6,6 +6,7 @@ #include #include "previews/DirPreview.h" +#include "previews/ErrorPreview.h" #include "previews/ImagePreview.h" #include "previews/TextPreview.h" #include "previews/VTFPreview.h" @@ -19,20 +20,22 @@ FileViewer::FileViewer(Window* window_, QWidget* parent) auto* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - this->dirPreview = new DirPreview(this); - layout->addWidget(this->dirPreview); + auto* dirPreview = newPreview(this, this); + layout->addWidget(dirPreview); - this->imagePreview = new ImagePreview(this); - layout->addWidget(this->imagePreview); + auto* errorPreview = newPreview(this); + layout->addWidget(errorPreview); - this->textPreview = new TextPreview(this); - layout->addWidget(this->textPreview); + auto* imagePreview = newPreview(this); + layout->addWidget(imagePreview); - this->vtfPreview = new VTFPreview(this); - layout->addWidget(this->vtfPreview); + auto* textPreview = newPreview(this); + layout->addWidget(textPreview); + + auto* vtfPreview = newPreview(this); + layout->addWidget(vtfPreview); this->clearContents(); - this->setTextPreviewVisible(); } void FileViewer::displayEntry(const QString& path) { @@ -41,32 +44,38 @@ void FileViewer::displayEntry(const QString& path) { if (ImagePreview::EXTENSIONS.contains(extension)) { // Image auto binary = this->window->readBinaryEntry(path); - if (binary.empty()) { - QMessageBox::critical(this->window, tr("Error"), tr("Failed to open file! Please ensure that a game or another application is not using the VPK.")); + if (!binary) { + this->showPreview(); return; } - this->imagePreview->setImage(binary); - this->setImagePreviewVisible(); + this->getPreview()->setImage(*binary); + this->showPreview(); } else if (TextPreview::EXTENSIONS.contains(extension)) { // Text - this->textPreview->setText(this->window->readTextEntry(path)); - this->setTextPreviewVisible(); + auto text = this->window->readTextEntry(path); + if (!text) { + this->showPreview(); + return; + } + this->getPreview()->setText(*text); + this->showPreview(); } else if (VTFPreview::EXTENSIONS.contains(extension)) { // VTF (texture) auto binary = this->window->readBinaryEntry(path); - if (binary.empty()) { - QMessageBox::critical(this->window, tr("Error"), tr("Failed to open file! Please ensure that a game or another application is not using the VPK.")); + if (!binary) { + this->showPreview(); return; } - this->vtfPreview->setImage(binary); - this->setVTFPreviewVisible(); + this->getPreview()->setImage(*binary); + this->showPreview(); } } void FileViewer::displayDir(const QString& /*path*/, const QList& subfolders, const QList& entryPaths, const VPK& vpk) { this->clearContents(); - this->dirPreview->setPath(subfolders, entryPaths, vpk); - this->setDirPreviewVisible(); + this->getPreview()->setPath(subfolders, entryPaths, vpk); + this->showPreview(); + this->showPreview(); } void FileViewer::selectSubItemInDir(const QString& name) { @@ -74,34 +83,6 @@ void FileViewer::selectSubItemInDir(const QString& name) { } void FileViewer::clearContents() { - this->textPreview->setText(""); - this->setTextPreviewVisible(); -} - -void FileViewer::setDirPreviewVisible() { - this->dirPreview->show(); - this->imagePreview->hide(); - this->textPreview->hide(); - this->vtfPreview->hide(); -} - -void FileViewer::setImagePreviewVisible() { - this->dirPreview->hide(); - this->imagePreview->show(); - this->textPreview->hide(); - this->vtfPreview->hide(); -} - -void FileViewer::setTextPreviewVisible() { - this->dirPreview->hide(); - this->imagePreview->hide(); - this->textPreview->show(); - this->vtfPreview->hide(); -} - -void FileViewer::setVTFPreviewVisible() { - this->dirPreview->hide(); - this->imagePreview->hide(); - this->textPreview->hide(); - this->vtfPreview->show(); + this->getPreview()->setText(""); + this->showPreview(); } diff --git a/src/gui/FileViewer.h b/src/gui/FileViewer.h index c3468b94..aeab7eb9 100644 --- a/src/gui/FileViewer.h +++ b/src/gui/FileViewer.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include class QTextEdit; @@ -10,10 +13,6 @@ class VPK; } // namespace vpkedit -class DirPreview; -class ImagePreview; -class TextPreview; -class VTFPreview; class Window; class FileViewer : public QWidget { @@ -33,13 +32,25 @@ class FileViewer : public QWidget { private: Window* window; - DirPreview* dirPreview; - ImagePreview* imagePreview; - TextPreview* textPreview; - VTFPreview* vtfPreview; - - void setDirPreviewVisible(); - void setImagePreviewVisible(); - void setTextPreviewVisible(); - void setVTFPreviewVisible(); + std::unordered_map previews; + + template + T* newPreview(Args... args) { + auto* preview = new T(std::forward(args)...); + this->previews[std::type_index(typeid(T))] = preview; + return preview; + } + + template + inline T* getPreview() { + return dynamic_cast(this->previews.at(std::type_index(typeid(T)))); + } + + template + void showPreview() { + for (const auto [index, widget] : this->previews) { + widget->hide(); + } + this->previews.at(std::type_index(typeid(T)))->show(); + } }; diff --git a/src/gui/Window.cpp b/src/gui/Window.cpp index 41cf2613..5303e357 100644 --- a/src/gui/Window.cpp +++ b/src/gui/Window.cpp @@ -349,20 +349,24 @@ void Window::aboutQt() { QMessageBox::aboutQt(this); } -std::vector Window::readBinaryEntry(const QString& path) { +std::optional> Window::readBinaryEntry(const QString& path) { auto entry = (*this->vpk).findEntry(path.toStdString()); if (!entry) { - return {}; + return std::nullopt; } return (*this->vpk).readBinaryEntry(*entry); } -QString Window::readTextEntry(const QString& path) { +std::optional Window::readTextEntry(const QString& path) { auto entry = (*this->vpk).findEntry(path.toStdString()); if (!entry) { - return {}; + return std::nullopt; } - return {(*this->vpk).readTextEntry(*entry).c_str()}; + auto textData = (*this->vpk).readTextEntry(*entry); + if (!textData) { + return std::nullopt; + } + return QString(textData->c_str()); } void Window::selectEntry(const QString& path) { @@ -512,7 +516,8 @@ bool Window::loadVPK(const QString& path) { QMessageBox::critical(this, tr("Error"), "Unable to load given VPK. Please ensure you are loading a " "\"directory\" VPK (typically ending in _dir), not a VPK that " "ends with 3 numbers. Loading a directory VPK will allow you " - "to browse the contents of the numbered archives next to it."); + "to browse the contents of the numbered archives next to it.\n" + "Also, please ensure that a game or another application is not using the VPK."); return false; } @@ -536,16 +541,19 @@ bool Window::loadVPK(const QString& path) { } void Window::writeEntryToFile(const QString& path, const VPKEntry& entry) { + auto data = (*this->vpk).readBinaryEntry(entry); + if (!data) { + QMessageBox::critical(this, tr("Error"), QString("Failed to read data from the VPK for \"") + entry.filename.c_str() + "\". Please ensure that a game or another application is not using the VPK."); + return; + } QFile file(path); if (!file.open(QIODevice::WriteOnly)) { QMessageBox::critical(this, tr("Error"), QString("Failed to write to file at \"") + path + "\"."); return; } - auto data = (*this->vpk).readBinaryEntry(entry); - auto bytesWritten = file.write(reinterpret_cast(data.data()), entry.length); + auto bytesWritten = file.write(reinterpret_cast(data->data()), entry.length); if (bytesWritten != entry.length) { QMessageBox::critical(this, tr("Error"), QString("Failed to write to file at \"") + path + "\"."); - return; } file.close(); } diff --git a/src/gui/Window.h b/src/gui/Window.h index e3766ccc..a4218023 100644 --- a/src/gui/Window.h +++ b/src/gui/Window.h @@ -40,9 +40,9 @@ class Window : public QMainWindow { void aboutQt(); - [[nodiscard]] std::vector readBinaryEntry(const QString& path); + [[nodiscard]] std::optional> readBinaryEntry(const QString& path); - [[nodiscard]] QString readTextEntry(const QString& path); + [[nodiscard]] std::optional readTextEntry(const QString& path); void selectEntry(const QString& path); diff --git a/src/gui/previews/ErrorPreview.cpp b/src/gui/previews/ErrorPreview.cpp new file mode 100644 index 00000000..f9e79c8f --- /dev/null +++ b/src/gui/previews/ErrorPreview.cpp @@ -0,0 +1,21 @@ +#include "ErrorPreview.h" + +#include +#include +#include + +ErrorPreview::ErrorPreview(QWidget* parent) + : QWidget(parent) { + this->setSizePolicy(QSizePolicy::Policy::Maximum, QSizePolicy::Policy::Minimum); + + auto* layout = new QHBoxLayout(this); + layout->setSpacing(16); + + auto* warningImage = new QLabel(this); + warningImage->setPixmap(QPixmap(":/error.png")); + layout->addWidget(warningImage, Qt::AlignCenter); + + auto* warningLabel = new QLabel(this); + warningLabel->setText(tr("Failed to read file contents!\nPlease ensure that a game or another application is not using the VPK.")); + layout->addWidget(warningLabel, Qt::AlignLeft); +} diff --git a/src/gui/previews/ErrorPreview.h b/src/gui/previews/ErrorPreview.h new file mode 100644 index 00000000..3699946f --- /dev/null +++ b/src/gui/previews/ErrorPreview.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class ErrorPreview : public QWidget { + Q_OBJECT; + +public: + static inline const QStringList EXTENSIONS { + // None, this is displayed when an entry cannot be read, maybe due to another process using it + }; + + explicit ErrorPreview(QWidget* parent = nullptr); +}; diff --git a/src/gui/res/error.png b/src/gui/res/error.png new file mode 100644 index 0000000000000000000000000000000000000000..4bfa2464d68bdc7b566e019bdfe9fe18b60965a6 GIT binary patch literal 7726 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 literal 0 HcmV?d00001 diff --git a/src/gui/res/res.qrc b/src/gui/res/res.qrc index 619648d1..8827b736 100644 --- a/src/gui/res/res.qrc +++ b/src/gui/res/res.qrc @@ -1,5 +1,6 @@ icon.png + error.png