summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt7
-rw-r--r--src/archive.cpp136
-rw-r--r--src/archive.hpp52
-rw-r--r--src/getsuyomi.cpp106
-rw-r--r--src/getsuyomi.hpp51
-rw-r--r--src/main.cpp2
-rw-r--r--src/main_window.cpp91
-rw-r--r--src/main_window.hpp26
-rw-r--r--src/result.hpp82
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