diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2024-09-18 21:59:54 -0500 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2024-09-18 21:59:54 -0500 |
commit | fa4b4097d3283e1d6e6376c70910e245f0b1f6ec (patch) | |
tree | 74dff4ded82d3f4854b3f10d5dd2e5be1f69b95e | |
parent | 04dbfdc97e94e6f477675b9d3135164752a7cfef (diff) |
Save progress of qt6 implementation.
-rw-r--r-- | CMakeLists.txt | 7 | ||||
-rw-r--r-- | src/archive.cpp | 136 | ||||
-rw-r--r-- | src/archive.hpp | 52 | ||||
-rw-r--r-- | src/getsuyomi.cpp | 106 | ||||
-rw-r--r-- | src/getsuyomi.hpp | 51 | ||||
-rw-r--r-- | src/main.cpp | 2 | ||||
-rw-r--r-- | src/main_window.cpp | 91 | ||||
-rw-r--r-- | src/main_window.hpp | 26 | ||||
-rw-r--r-- | src/result.hpp | 82 |
9 files changed, 504 insertions, 49 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index dfb9afb..bbcbf38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,15 +12,16 @@ set(CXX_CPPCHECK "project=build/compile_commands.json;enable=information;force") set(CMAKE_EXPORT_COMPILE_COMMANDS true) qt_add_executable(getsuyomi - # main_window.ui src/main_window.cpp src/main.cpp + src/archive.cpp + src/getsuyomi.cpp ) # find_dependency(minizip-ng REQUIRED) -target_include_directories(getsuyomi PRIVATE minizip-ng) -target_link_libraries(getsuyomi PRIVATE Qt6::Widgets minizip-ng) +target_include_directories(getsuyomi PRIVATE libzip) +target_link_libraries(getsuyomi PRIVATE Qt6::Widgets zip) # set_target_properties(getsuyomi PROPERTIES # WIN32_EXECUTABLE ON diff --git a/src/archive.cpp b/src/archive.cpp new file mode 100644 index 0000000..0339d85 --- /dev/null +++ b/src/archive.cpp @@ -0,0 +1,136 @@ +#include "archive.hpp" + +#include "main_window.hpp" + +#include <algorithm> + +namespace getsuyomi { + +ZipArchive::ZipArchive(const QString& filename) +{ + auto filenameUtf8 = filename.toUtf8(); + qDebug("Open file %s", filenameUtf8.data()); + int err; + m_archive = zip_open(filenameUtf8.data(), 0, &err); + if (m_archive == nullptr) { + zip_error_t error; + zip_error_init_with_code(&error, err); + qCritical("%s: cannot open zip archive '%s': %s", AppName, filenameUtf8.data(), zip_error_strerror(&error)); + zip_error_fini(&error); + throw std::runtime_error("Bad zip file"); + } + + populate(); +} + +void ZipArchive::populate() +{ + auto numEntries = zip_get_num_entries(m_archive, ZIP_FL_UNCHANGED); + qDebug("%zu pages", numEntries); + + struct NameIndex { + std::string name; + size_t index; + size_t filesize; + }; + + std::vector<NameIndex> nameIndices{}; + nameIndices.reserve(numEntries); + m_pages.reserve(numEntries); + + for (size_t idx = 0; idx < numEntries; ++idx) { + struct zip_stat stats; + + auto status = zip_stat_index(m_archive, idx, 0, &stats); + static_assert(ZIP_STAT_NAME != 0); + static_assert(ZIP_STAT_SIZE != 0); + static_assert(ZIP_STAT_INDEX != 0); + // auto* nameCString = zip_get_name(m_archive, idx, ZIP_FL_ENC_RAW); + auto* nameCString = stats.name; + if (nameCString == nullptr) { + zip_error_t* error = zip_get_error(m_archive); + qWarning("cannot get name %s", zip_error_strerror(error)); + continue; + } + std::string name{nameCString}; + if (name.empty() || name.back() == '/') { + qDebug("Directory %s", name.empty() ? "N/A" : nameCString); + continue; + } + + nameIndices.emplace_back(NameIndex{name, idx, stats.size}); + m_pages.emplace_back(std::nullopt); + } + + std::sort(nameIndices.begin(), nameIndices.end(), [](const auto& lhs, const auto& rhs) { + return std::lexicographical_compare(lhs.name.begin(), lhs.name.end(), rhs.name.begin(), rhs.name.end()); + }); + + m_sortedIndices.reserve(nameIndices.size()); + m_filenames.reserve(nameIndices.size()); + m_fileSizes.reserve(nameIndices.size()); + + for (const auto& nameIndex : nameIndices) { + m_sortedIndices.push_back(nameIndex.index); + m_filenames.push_back(nameIndex.name); + m_fileSizes.push_back(nameIndex.filesize); + } +} + +ZipArchive::~ZipArchive() +{ + if (m_archive != nullptr) { + zip_discard(m_archive); + m_archive = nullptr; + } +} + +ArchiveResult ZipArchive::getPage(size_t page) +{ + qDebug("Getting page %zu", page); + if (page > m_sortedIndices.size()) { + return ArchiveResult::error(ArchiveError::BadIndex); + } + + if (m_pages[page] != std::nullopt) { + qDebug("Page found %zu", page); + return ArchiveResult::okay(std::cref(*m_pages[page])); + } + + auto* file = zip_fopen_index(m_archive, m_sortedIndices[page], 0); + if (file == nullptr) { + zip_error_t* error = zip_get_error(m_archive); + qWarning("cannot get file name %s: %s", m_filenames[page].data(), zip_error_strerror(error)); + return ArchiveResult::error(ArchiveError::ZipError); + } + + QByteArray data; + data.resize(m_fileSizes[page]); + auto index = m_sortedIndices[page]; + + qDebug("Reading in page data"); + auto bytesRead = zip_fread(file, data.data(), data.size()); + zip_fclose(file); + file = nullptr; + + if (bytesRead != data.size()) { + return ArchiveResult::error(ArchiveError::ZipError); + } + + m_pages[page] = QImage(); + qDebug("Loading QImage from page data"); + auto loaded = m_pages[page]->loadFromData(data); + if (!loaded) { + qWarning("Failed to load QImage"); + m_pages[page] = std::nullopt; + return ArchiveResult::error(ArchiveError::BadData); + } + + return ArchiveResult::okay(std::cref(*m_pages[page])); +} + +size_t ZipArchive::numPages() const { + return m_pages.size(); +} + +} // namespace getsuyomi diff --git a/src/archive.hpp b/src/archive.hpp new file mode 100644 index 0000000..d8464e5 --- /dev/null +++ b/src/archive.hpp @@ -0,0 +1,52 @@ +#ifndef ARCHIVE_HPP +#define ARCHIVE_HPP + +#include "result.hpp" + +#include <QtWidgets> +#include <cstdlib> +#include <string> +#include <vector> +#include <zip.h> + +namespace getsuyomi { + +enum class ArchiveError +{ + BadIndex, + ZipError, + BadData, + Unimplemented +}; + +using ArchiveResult = Result<std::reference_wrapper<const QImage>, ArchiveError>; + +class Archive { + public: + virtual ~Archive() = default; + virtual ArchiveResult getPage(size_t page) = 0; + virtual size_t numPages() const = 0; +}; + +class ZipArchive : public Archive { + public: + explicit ZipArchive(const QString& filename); + virtual ~ZipArchive() override; + virtual ArchiveResult getPage(size_t page) override; + virtual size_t numPages() const override; + + private: + void populate(); + + zip_t* m_archive{nullptr}; + + std::vector<size_t> m_sortedIndices{}; + std::vector<std::string> m_filenames{}; + std::vector<size_t> m_fileSizes{}; + + std::vector<std::optional<QImage>> m_pages{}; +}; + +} // namespace getsuyomi + +#endif diff --git a/src/getsuyomi.cpp b/src/getsuyomi.cpp new file mode 100644 index 0000000..11f273b --- /dev/null +++ b/src/getsuyomi.cpp @@ -0,0 +1,106 @@ +#include "getsuyomi.hpp" +#include "main_window.hpp" + +namespace getsuyomi { + +Getsuyomi::Getsuyomi() +{ + m_layout = new QVBoxLayout(); + + m_layout->addWidget(&m_pageLeft); + m_layout->addWidget(&m_pageRight); + + setLayout(m_layout); +} + +void Getsuyomi::setArchive(Archive* archive) { + if (archive == m_archive) { + return; + } + delete(m_archive); + m_archive = archive; + m_pageNumber = 0; + setPages(); +} + +void Getsuyomi::next() { + auto numPages = m_archive->numPages(); + size_t increment = 1; + if (m_pageLayout != PageLayout::Single) { + increment = 2; + } + if ((m_pageNumber + increment) >= numPages) + { + return; + } + m_pageNumber += increment; + setPages(); +} + +void Getsuyomi::back() { + auto numPages = m_archive->numPages(); + size_t decrement = 1; + if (m_pageLayout != PageLayout::Single) { + decrement = 2; + } + if (m_pageNumber < decrement) + { + m_pageNumber = 0; + decrement = 0; + } + m_pageNumber -= decrement; + setPages(); +} + +void Getsuyomi::setPages() { + m_pageLeft.setPixmap(QPixmap()); + m_pageRight.setPixmap(QPixmap()); + + switch (m_pageLayout) + { + case PageLayout::Dual: + return setPagesDual(); + case PageLayout::Manga: + return setPagesManga(); + case PageLayout::Single: + default: + return setPagesNormal(); + } +} + +void Getsuyomi::setPagesNormal() { + auto page1 = m_archive->getPage(m_pageNumber); + auto& label1 = m_pageLeft; + + if (page1.isOkay()) { + label1.setPixmap(QPixmap::fromImage(page1.getOkay())); + } +} + +void Getsuyomi::setPagesDual() { + auto& label1 = m_pageLeft; + auto& label2 = m_pageRight; + setPages(label1, label2); +} + +void Getsuyomi::setPagesManga() { + auto& label1 = m_pageRight; + auto& label2 = m_pageLeft; + setPages(label1, label2); +} + +void Getsuyomi::setPages(QLabel& label1, QLabel& label2) { + auto page1 = m_archive->getPage(m_pageNumber); + auto page2 = m_archive->getPage(m_pageNumber + 1); + + if (page1.isOkay()) { + label1.setPixmap(QPixmap::fromImage(page1.getOkay())); + } + + + if (page2.isOkay()) { + label2.setPixmap(QPixmap::fromImage(page1.getOkay())); + } +} + +} // namespace getsuyomi diff --git a/src/getsuyomi.hpp b/src/getsuyomi.hpp new file mode 100644 index 0000000..3d3c092 --- /dev/null +++ b/src/getsuyomi.hpp @@ -0,0 +1,51 @@ +#ifndef GETSUYOMI_HPP +#define GETSUYOMI_HPP + +#include "archive.hpp" + +#include <QtWidgets> +#include <cstdint> +#include <vector> + +namespace getsuyomi { + +enum class PageLayout : uint8_t { + Single, + Dual, + Manga +}; + +class Getsuyomi : public QWidget { + Q_OBJECT + + public: + friend class GetsuyomiApp; + Getsuyomi(); + + public slots: + void setArchive(Archive* archive); + void next(); + void back(); + + private: + void setPages(); + void setPagesNormal(); + void setPagesDual(); + void setPagesManga(); + void setPages(QLabel& label1, QLabel& label2); + + QLayout* m_layout{nullptr}; + + Archive* m_archive{nullptr}; + + PageLayout m_pageLayout{PageLayout::Single}; + + QLabel m_pageLeft{}; + QLabel m_pageRight{}; + + size_t m_pageNumber{0}; +}; + +} // namespace getsuyomi + +#endif diff --git a/src/main.cpp b/src/main.cpp index 9a57bfc..bac3ff2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,7 +3,7 @@ int main(int argc, char* argv[]) { QApplication app{argc, argv}; - getsuyomi::GetsuyomiApp getsuyobi{}; + getsuyomi::GetsuyomiApp getsuyomi{}; QCommandLineParser parser; parser.setApplicationDescription(QApplication::translate("main", "A comic book and manga reader")); diff --git a/src/main_window.cpp b/src/main_window.cpp index 61b3b65..b05507c 100644 --- a/src/main_window.cpp +++ b/src/main_window.cpp @@ -1,9 +1,7 @@ #include "main_window.hpp" #include <QString> -#include <minizip-ng/mz.h> -#include <minizip-ng/mz_strm_buf.h> -#include <minizip-ng/mz_strm_os.h> +#include <zip.h> namespace getsuyomi { @@ -15,13 +13,18 @@ GetsuyomiApp::GetsuyomiApp() : m_getsuyomi{new Getsuyomi()} void GetsuyomiApp::setup() { - createActions(); - createMenus(); - QCoreApplication::setApplicationName(AppName); QCoreApplication::setApplicationVersion(AppVersionString); setWindowTitle(AppName); + createActions(); + createMenus(); + createToolBar(); + + constexpr int minimumWidth = 640; + constexpr int minimumHeight = 480; + setMinimumSize(minimumWidth, minimumHeight); + show(); } @@ -41,14 +44,39 @@ void GetsuyomiApp::createActions() m_quitAction->setShortcuts(QKeySequence::Quit); m_quitAction->setStatusTip(tr("Quit")); connect(m_quitAction, &QAction::triggered, this, &GetsuyomiApp::quit); + + auto nextShortcuts = QList<QKeySequence>{}; + nextShortcuts.append(QKeySequence{Qt::Key_L}); + nextShortcuts.append(QKeySequence{Qt::Key_Right}); + nextShortcuts.append(QKeySequence{Qt::Key_Down}); + m_nextAction = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::GoNext), tr("Next"), this); + m_nextAction->setShortcuts(nextShortcuts); + m_nextAction->setStatusTip(tr("Next")); + connect(m_nextAction, &QAction::triggered, this, &GetsuyomiApp::next); + + auto backShortcuts = QList<QKeySequence>{}; + backShortcuts.append(QKeySequence{Qt::Key_H}); + backShortcuts.append(QKeySequence{Qt::Key_Left}); + backShortcuts.append(QKeySequence{Qt::Key_Up}); + m_backAction = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::GoPrevious), tr("Back"), this); + m_backAction->setShortcuts(backShortcuts); + m_backAction->setStatusTip(tr("Back")); + connect(m_backAction, &QAction::triggered, this, &GetsuyomiApp::back); } void GetsuyomiApp::createMenus() { - m_fileMenu = menuBar()->addMenu(tr("&File")); - m_fileMenu->addAction(m_openFile); - m_fileMenu->addAction(m_openDirectory); - m_fileMenu->addAction(m_quitAction); + m_menuBar = menuBar()->addMenu(tr("&File")); + m_menuBar->addAction(m_openFile); + m_menuBar->addAction(m_openDirectory); + m_menuBar->addAction(m_quitAction); +} + +void GetsuyomiApp::createToolBar() +{ + m_toolBar = addToolBar(tr("&Navigation")); + m_toolBar->addAction(m_backAction); + m_toolBar->addAction(m_nextAction); } void GetsuyomiApp::openFile() @@ -58,7 +86,17 @@ void GetsuyomiApp::openFile() tr("Open Archive"), QDir::homePath(), tr("Archive types (*.zip *.cbz *.cbr *.gz)")); - qDebug("Open file %s\n", qPrintable(filename)); + + if (filename.endsWith(".zip")) { + try { + auto* archive = new ZipArchive(filename); + m_getsuyomi->setArchive(archive); + } catch (std::runtime_error& exc) { + qCritical("Failed to change archive"); + } + } else { + qCritical("Unsupported file extension"); + } } void GetsuyomiApp::openDirectory() @@ -69,38 +107,23 @@ void GetsuyomiApp::openDirectory() QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); qDebug("Open directory %s", qPrintable(directory)); - - auto* stream = mz_stream_os_create(); - if (stream == nullptr) { - qCritical("Nullpointer - stream"); - return; - } - - auto* bufStream = mz_stream_buffered_create(); - if (stream == nullptr) { - qCritical("Nullpointer - bufStream"); - return; - } - - mz_stream_buffered_open(bufStream, nullptr, MZ_OPEN_MODE_READ); - mz_stream_buffered_set_base(bufStream, stream); - } void GetsuyomiApp::quit() { - printf("Quit!\n"); QCoreApplication::quit(); } -Getsuyomi::Getsuyomi() +void GetsuyomiApp::next() { - m_layout = new QVBoxLayout(); - - auto* label = new QLabel(AppName); - m_layout->addWidget(label); + qDebug("Next"); + m_getsuyomi->next(); +} - setLayout(m_layout); +void GetsuyomiApp::back() +{ + qDebug("Back"); + m_getsuyomi->back(); } } // namespace getsuyomi diff --git a/src/main_window.hpp b/src/main_window.hpp index 886ba42..bc23b01 100644 --- a/src/main_window.hpp +++ b/src/main_window.hpp @@ -1,3 +1,9 @@ +#ifndef MAIN_WINDOW_HPP +#define MAIN_WINDOW_HPP + +#include "archive.hpp" +#include "getsuyomi.hpp" + #include <QtWidgets> namespace getsuyomi { @@ -5,8 +11,6 @@ namespace getsuyomi { constexpr const char* AppName = "GetsuYomi"; constexpr const char* AppVersionString = "1.0.0"; -class Getsuyomi; - class GetsuyomiApp : public QMainWindow { Q_OBJECT @@ -17,28 +21,28 @@ class GetsuyomiApp : public QMainWindow { void setup(); void createActions(); void createMenus(); + void createToolBar(); Getsuyomi* m_getsuyomi; QAction* m_openFile; QAction* m_openDirectory; QAction* m_quitAction; + QAction* m_nextAction; + QAction* m_backAction; - QMenu* m_fileMenu; + QMenu* m_menuBar; + QToolBar* m_toolBar; private slots: void openFile(); void openDirectory(); void quit(); -}; -class Getsuyomi : public QWidget { - Q_OBJECT - public: - Getsuyomi(); - - private: - QLayout* m_layout{nullptr}; + void next(); + void back(); }; } // namespace getsuyomi + +#endif diff --git a/src/result.hpp b/src/result.hpp new file mode 100644 index 0000000..6bb8971 --- /dev/null +++ b/src/result.hpp @@ -0,0 +1,82 @@ +/* + * ExtLib + * Copyright 2024 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GETSUYOMI_RESULT_HPP +#define GETSUYOMI_RESULT_HPP + +#include <variant> + +namespace getsuyomi { + +template<typename T, typename E> +class Result { +public: + using ResultType = Result<T, E>; + static ResultType okay(const T& okay) + { + return ResultType{okay}; + } + + static ResultType okay(T&& okay) + { + return ResultType{std::move(okay)}; + } + + static ResultType error(const E& error) + { + return ResultType{error}; + } + + static ResultType error(E&& error) + { + return ResultType{std::move(error)}; + } + + [[nodiscard]] constexpr bool isOkay() const + { + return(m_value.index() == 0); + } + + [[nodiscard]] constexpr bool isError() const + { + return(m_value.index() == 1); + } + + T getOkay() + { + return std::get<T>(m_value); + } + + E getError() + { + return std::get<E>(m_value); + } + +private: + explicit Result() : m_value() {} + explicit Result(const T& value) : m_value(value) {} + explicit Result(const E& value) : m_value(value) {} + + explicit Result(T&& value) : m_value(std::move(value)) {} + explicit Result(E&& value) : m_value(std::move(value)) {} + + std::variant<T, E> m_value; +}; + +} // namespace getsuyomi + +#endif |