summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2024-09-30 00:36:19 -0500
committerDominick Allen <djallen@librehumanitas.org>2024-09-30 00:36:19 -0500
commit6f2b61b676a16482fdac70a58a8e875c4d68e713 (patch)
treee2f8b2376847b5b13b278572c0fae8a6bc4d0e82
parentdacd752bbf46f2afb08b4b8d730ba3619528dda4 (diff)
Add configuration handling.
-rw-r--r--CMakeLists.txt16
-rw-r--r--resources/CMakeLists.txt4
-rw-r--r--resources/config.lua2
-rw-r--r--resources/gear.svg66
-rw-r--r--src/archive.cpp1
-rw-r--r--src/config.cpp211
-rw-r--r--src/config.hpp144
-rw-r--r--src/getsuyomi.cpp16
-rw-r--r--src/getsuyomi.hpp2
-rw-r--r--src/luacxx.cpp142
-rw-r--r--src/luacxx.hpp47
-rw-r--r--src/main.cpp4
-rw-r--r--src/main_window.cpp147
-rw-r--r--src/main_window.hpp12
14 files changed, 782 insertions, 32 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c609098..175aa56 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,24 +17,30 @@ qt_add_executable(getsuyomi
src/archive.cpp
src/getsuyomi.cpp
src/fud_mem.cpp
+ src/config.cpp
+ src/luacxx.cpp
)
include(warnings.cmake)
target_compile_options(getsuyomi PRIVATE ${GETSUYOMI_WARNINGS})
find_package(Fud REQUIRED)
-# find_dependency(minizip-ng REQUIRED)
+find_package(Lua REQUIRED)
+find_package(libzip REQUIRED)
target_include_directories(getsuyomi PRIVATE
- # libzip
+ ${libzip_INCLUDE_DIR}
${FUD_INCLUDE_DIR}
- ${SDL2_INCLUDE_DIRS}
+ ${LUA_INCLUDE_DIR}
)
+# message(FATAL_ERROR "WHAT ${LUA_LIBRARIES}")
+
target_link_libraries(getsuyomi PRIVATE
Qt6::Widgets
- zip
+ libzip::zip
fud)
+target_link_libraries(getsuyomi PRIVATE "${LUA_LIBRARIES}")
set_target_properties(
getsuyomi PROPERTIES
@@ -48,3 +54,5 @@ set_target_properties(
# WIN32_EXECUTABLE ON
# MACOSX_BUNDLE ON
#)
+
+add_subdirectory(resources)
diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt
index e69de29..06dbb1f 100644
--- a/resources/CMakeLists.txt
+++ b/resources/CMakeLists.txt
@@ -0,0 +1,4 @@
+configure_file(pageSingle.png ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
+configure_file(pageDual.png ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
+configure_file(pageManga.png ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
+configure_file(gear.svg ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
diff --git a/resources/config.lua b/resources/config.lua
new file mode 100644
index 0000000..39b7c0b
--- /dev/null
+++ b/resources/config.lua
@@ -0,0 +1,2 @@
+-- Commentary
+OpenFile = "Ctrl+O"
diff --git a/resources/gear.svg b/resources/gear.svg
new file mode 100644
index 0000000..5e64c98
--- /dev/null
+++ b/resources/gear.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="128"
+ height="128"
+ viewBox="0 0 33.866666 33.866666"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
+ sodipodi:docname="gear.svg"
+ inkscape:export-filename="gear.png"
+ inkscape:export-xdpi="96"
+ inkscape:export-ydpi="96"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+ <sodipodi:namedview
+ id="namedview1"
+ pagecolor="#ffffff"
+ bordercolor="#000000"
+ borderopacity="0.25"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="true"
+ inkscape:deskcolor="#d1d1d1"
+ inkscape:document-units="mm"
+ showgrid="true"
+ inkscape:zoom="8"
+ inkscape:cx="63.5625"
+ inkscape:cy="63"
+ inkscape:window-width="2138"
+ inkscape:window-height="1418"
+ inkscape:window-x="1015"
+ inkscape:window-y="336"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg1">
+ <inkscape:grid
+ id="grid1"
+ units="mm"
+ originx="0"
+ originy="0"
+ spacingx="0.99999998"
+ spacingy="0.99999998"
+ empcolor="#0099e5"
+ empopacity="0.30196078"
+ color="#0099e5"
+ opacity="0.14901961"
+ empspacing="5"
+ dotted="false"
+ gridanglex="30"
+ gridanglez="30"
+ visible="true" />
+ </sodipodi:namedview>
+ <defs
+ id="defs1" />
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1" />
+ <path
+ id="circle9"
+ style="fill:#000000;stroke-width:2.08315"
+ d="M 16.5863,1.4670297 A 15.746587,15.746587 0 0 0 14.07602,1.71456 L 13.293966,4.788959 A 12.901794,12.901794 0 0 0 8.5173471,7.3117961 L 5.5469834,6.223021 A 15.746587,15.746587 0 0 0 3.9227891,8.1700803 L 5.5532608,10.912647 A 12.901794,12.901794 0 0 0 3.9640439,16.044418 L 1.051077,17.395072 a 15.746587,15.746587 0 0 0 0.2466332,2.511176 l 3.0744005,0.782053 a 12.901794,12.901794 0 0 0 2.5228374,4.777515 l -1.0878778,2.970366 a 15.746587,15.746587 0 0 0 1.9452645,1.623295 l 2.7434622,-1.63047 a 12.901794,12.901794 0 0 0 5.131771,1.590113 l 1.350655,2.912965 a 15.746587,15.746587 0 0 0 2.511178,-0.24753 l 0.782053,-3.074399 a 12.901794,12.901794 0 0 0 4.776617,-2.522837 l 2.970366,1.088776 a 15.746587,15.746587 0 0 0 1.625091,-1.946164 l -1.63137,-2.743462 a 12.901794,12.901794 0 0 0 1.590115,-5.131768 l 2.922832,-1.355139 A 15.746587,15.746587 0 0 0 32.291923,14.499146 L 29.200484,13.712609 A 12.901794,12.901794 0 0 0 26.675854,8.9207435 L 27.772699,5.9270607 A 15.746587,15.746587 0 0 0 25.839094,4.3234943 l -2.740771,1.629575 A 12.901794,12.901794 0 0 0 17.924399,4.3512966 Z m 0.196409,8.9631103 a 6.7693807,6.7693807 0 0 1 6.769419,6.76942 6.7693807,6.7693807 0 0 1 -6.769419,6.769416 6.7693807,6.7693807 0 0 1 -6.769418,-6.769416 6.7693807,6.7693807 0 0 1 6.769418,-6.76942 z" />
+</svg>
diff --git a/src/archive.cpp b/src/archive.cpp
index 468efed..d773f73 100644
--- a/src/archive.cpp
+++ b/src/archive.cpp
@@ -11,7 +11,6 @@ using fud::FudStatus;
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);
diff --git a/src/config.cpp b/src/config.cpp
new file mode 100644
index 0000000..6405e2b
--- /dev/null
+++ b/src/config.cpp
@@ -0,0 +1,211 @@
+#include "config.hpp"
+
+#include <algorithm>
+#include <vector>
+
+namespace getsuyomi {
+
+using fud::FudStatus;
+
+ShortcutSet shortcutSetFromList(const ShortcutList& shortcutList)
+{
+ ShortcutSet shortcutSet{};
+ for (const auto& entry : shortcutList) {
+ shortcutSet.insert(entry);
+ }
+ return shortcutSet;
+}
+
+Shortcuts::Shortcuts(ShortcutMap&& shortcutMap) :
+ m_actionToShortcuts{std::move(shortcutMap)}, m_shortcuts{}, m_shortcutToAction{}
+{
+ for (const auto& [action, shortcuts] : m_actionToShortcuts) {
+ for (const auto& shortcut : shortcuts) {
+ qDebug("Working on action %s", actionTypeToString(action));
+ if (bind(action, shortcut) != FudStatus::Success) {
+ return;
+ }
+ }
+ }
+ m_valid = true;
+}
+
+bool Shortcuts::contains(QKeySequence keySequence) const
+{
+ return m_shortcuts.contains(keySequence);
+}
+
+bool Shortcuts::contains(ActionType actionType) const
+{
+ return m_actionToShortcuts.contains(actionType);
+}
+
+std::vector<ActionType> Shortcuts::actions() const {
+ std::vector<ActionType> actionList{};
+ actionList.reserve(m_actionToShortcuts.size());
+ for (const auto& [action, discarded] : m_actionToShortcuts) {
+ actionList.push_back(action);
+ }
+ return actionList;
+}
+
+std::optional<ActionType> Shortcuts::action(QKeySequence keySequence) const
+{
+ if (m_shortcutToAction.contains(keySequence)) {
+ return m_shortcutToAction.at(keySequence);
+ } else {
+ return std::nullopt;
+ }
+}
+
+std::optional<ShortcutList> Shortcuts::shortcuts(ActionType action) const
+{
+ if (contains(action)) {
+ const auto& shortcuts = m_actionToShortcuts.at(action);
+ ShortcutList shortcutList{};
+ shortcutList.reserve(static_cast<qsizetype>(shortcuts.size()));
+ for (const auto& shortcut : shortcuts) {
+ shortcutList.push_back(shortcut);
+ }
+ return shortcutList;
+ } else {
+ return std::nullopt;
+ }
+}
+
+FudStatus Shortcuts::bind(ActionType action, QKeySequence keySequence)
+{
+ if (contains(keySequence)) {
+ return FudStatus::Full;
+ }
+
+ m_shortcuts.insert(keySequence);
+
+ if (!contains(action)) {
+ m_actionToShortcuts[action] = ShortcutSet{};
+ }
+ m_actionToShortcuts[action].insert(keySequence);
+ m_shortcutToAction[keySequence] = action;
+ return FudStatus::Success;
+}
+
+fud::FudStatus Shortcuts::remove(QKeySequence keySequence)
+{
+ if (!contains(keySequence)) {
+ return FudStatus::NotFound;
+ }
+
+ auto action = m_shortcutToAction[keySequence];
+ auto& actionShortcuts = m_actionToShortcuts[action];
+
+ actionShortcuts.erase(keySequence);
+ m_shortcutToAction.erase(keySequence);
+ m_shortcuts.erase(keySequence);
+
+ return FudStatus::Success;
+}
+
+fud::FudStatus Shortcuts::clear(ActionType action)
+{
+ if (!contains(action)) {
+ return FudStatus::NotFound;
+ }
+
+ auto& actionShortcuts = m_actionToShortcuts[action];
+
+ for (const auto& shortcut : actionShortcuts) {
+ m_shortcuts.erase(shortcut);
+ m_shortcutToAction.erase(shortcut);
+ }
+ m_actionToShortcuts[action] = {};
+
+ return FudStatus::Success;
+}
+
+bool Shortcuts::valid() const {
+ return m_valid;
+}
+
+ShortcutMap GetsuyomiConfig::shortcuts() const
+{
+ ShortcutMap shortcuts{};
+
+ shortcuts.insert({ActionType::OpenFile, shortcutSetFromList(openFileShortcuts)});
+ shortcuts.insert({ActionType::OpenDirectory, shortcutSetFromList(openDirectoryShortcuts)});
+ shortcuts.insert({ActionType::Quit, shortcutSetFromList(quitShortcuts)});
+ shortcuts.insert({ActionType::Configure, shortcutSetFromList(settingsShortcuts)});
+ shortcuts.insert({ActionType::Next, shortcutSetFromList(nextShortcuts)});
+ shortcuts.insert({ActionType::Back, shortcutSetFromList(backShortcuts)});
+
+ return shortcuts;
+}
+
+ShortcutCollector::ShortcutCollector(QWidget* parent, ActionType action, Shortcuts& shortcuts) :
+ QWidget(parent), m_action{action}, m_shortcuts{shortcuts}
+{
+ m_layout = new QGridLayout();
+ auto* name = new QLabel(actionTypeToString(m_action));
+ m_layout->addWidget(name, 0, 0);
+ m_shortcutEditor = new QKeySequenceEdit(this);
+ m_layout->addWidget(m_shortcutEditor, 0, 1);
+ m_shortcutTable = new QTableWidget(this);
+ m_layout->addWidget(m_shortcutTable);
+
+ auto shortcutOptions = m_shortcuts.shortcuts(m_action);
+ if (shortcutOptions != std::nullopt) {
+ m_shortcutTable->setColumnCount(2);
+ m_actionList = *shortcutOptions;
+
+ m_shortcutTable->setRowCount(static_cast<int>(m_actionList.size()));
+
+ for (auto index = 0; index < m_actionList.size(); ++index) {
+ const auto& shortcut = m_actionList[index];
+ m_shortcutTable->setCellWidget(index, 0, new QLabel(shortcut.toString()));
+ auto* deleteButton = new QPushButton("Delete", this);
+ m_shortcutTable->setCellWidget(index, 1, deleteButton);
+ connect(m_shortcutTable, &QTableWidget::cellClicked, this, &ShortcutCollector::removeShortcut);
+ }
+ } else {
+ qWarning("No shortcuts found for %s", actionTypeToString(action));
+ }
+
+ setLayout(m_layout);
+}
+
+void ShortcutCollector::removeShortcut(int row, int) {
+ auto keySequence = m_actionList[row];
+ auto result = m_shortcuts.remove(keySequence);
+ if (result == FudStatus::Success) {
+ m_actionList.removeAt(row);
+ }
+ else {
+ qCritical("What");
+ }
+}
+
+Settings::Settings(QWidget* parent, Shortcuts&& shortcuts) : QDialog{parent}, m_shortcuts{std::move(shortcuts)}
+{
+ auto* layout = new QVBoxLayout();
+ setWindowTitle("getsuyomi settings");
+
+ if (!m_shortcuts.valid()) {
+ return;
+ }
+
+ for (const auto& action : m_shortcuts.actions()) {
+ auto* collector = new ShortcutCollector(this, action, shortcuts);
+ layout->addWidget(collector);
+ }
+
+ setLayout(layout);
+}
+
+bool Settings::valid() const {
+ return m_shortcuts.valid();
+}
+
+const Shortcuts& Settings::shortcuts() const {
+ return m_shortcuts;
+}
+
+} // namespace getsuyomi
diff --git a/src/config.hpp b/src/config.hpp
index cb6a74a..81da2dc 100644
--- a/src/config.hpp
+++ b/src/config.hpp
@@ -1,18 +1,160 @@
#ifndef GETSUYOMI_CONFIG_HPP
#define GETSUYOMI_CONFIG_HPP
+#include <QtWidgets>
+#include <fud_status.hpp>
+#include <map>
+#include <optional>
+#include <qkeysequence.h>
+#include <qlist.h>
+#include <set>
#include <string>
namespace getsuyomi {
+enum class ActionType
+{
+ OpenFile,
+ OpenDirectory,
+ Quit,
+ Configure,
+ // Help,
+ // About
+ // GotoFirst,
+ Next,
+ Back,
+ // GotoLast,
+ SinglePage,
+ DualPage,
+ MangaPage
+};
+
+constexpr const char* actionTypeToString(ActionType action)
+{
+ switch (action) {
+ case ActionType::OpenFile:
+ return "OpenFile";
+ case ActionType::OpenDirectory:
+ return "OpenDirectory";
+ case ActionType::Quit:
+ return "Quit";
+ case ActionType::Configure:
+ return "Configure";
+ // case ActionType::Help:
+ return "Help";
+ // case ActionType::About:
+ // return "About";
+ // case ActionType::GotoFirst:
+ // return "GotoFirst";
+ case ActionType::Next:
+ return "Next";
+ case ActionType::Back:
+ return "Back";
+ // case ActionType::GotoLast:
+ // return "GotoLast";
+ case ActionType::SinglePage:
+ return "SinglePage";
+ case ActionType::DualPage:
+ return "DualPage";
+ case ActionType::MangaPage:
+ return "MangaPage";
+ default:
+ return "Unknown";
+ }
+}
+
+using ShortcutList = QList<QKeySequence>;
+using ShortcutSet = std::set<QKeySequence>;
+using ShortcutMap = std::map<ActionType, ShortcutSet>;
+using ShortcutRevMap = std::map<QKeySequence, ActionType>;
+
+ShortcutSet shortcutSetFromList(const ShortcutList& shortcutList);
+
+class Shortcuts {
+ public:
+ Shortcuts(ShortcutMap&& shortcutMap);
+
+ [[nodiscard]] bool contains(QKeySequence keySequence) const;
+ [[nodiscard]] bool contains(ActionType action) const;
+
+ std::vector<ActionType> actions() const;
+
+ std::optional<ActionType> action(QKeySequence keySequence) const;
+
+ std::optional<ShortcutList> shortcuts(ActionType action) const;
+
+ fud::FudStatus bind(ActionType action, QKeySequence keySequence);
+ fud::FudStatus remove(QKeySequence keySequence);
+ fud::FudStatus clear(ActionType action);
+
+ [[nodiscard]] bool valid() const;
+
+ const ShortcutMap& shortcutMap() const;
+
+ private:
+ bool m_valid{false};
+
+ ShortcutMap m_actionToShortcuts;
+ ShortcutSet m_shortcuts;
+ ShortcutRevMap m_shortcutToAction;
+};
+
struct GetsuyomiConfig {
std::string home{};
std::string dataHome{};
std::string configHome{};
std::string stateHome{};
+
+ ShortcutList openFileShortcuts{};
+ ShortcutList openDirectoryShortcuts{};
+ ShortcutList quitShortcuts{};
+
+ ShortcutList settingsShortcuts{};
+
+ ShortcutList nextShortcuts{};
+ ShortcutList backShortcuts{};
+
+ ShortcutMap shortcuts() const;
};
-} // namespace getsuyomi
+class ShortcutCollector : public QWidget {
+ public:
+ ShortcutCollector(QWidget* parent, ActionType action, Shortcuts& shortcuts);
+ ~ShortcutCollector() = default;
+ ShortcutCollector(const ShortcutCollector&) = delete;
+ ShortcutCollector(ShortcutCollector&&) = delete;
+ ShortcutCollector& operator=(const ShortcutCollector&) = delete;
+ ShortcutCollector& operator=(ShortcutCollector&&) = delete;
+ private slots:
+ void removeShortcut(int row, int);
+
+ private:
+ ActionType m_action;
+ Shortcuts& m_shortcuts;
+ QKeySequenceEdit* m_shortcutEditor{nullptr};
+ QGridLayout* m_layout{nullptr};
+ ShortcutList m_actionList{};
+ QTableWidget* m_shortcutTable{nullptr};
+};
+
+class Settings : public QDialog {
+ public:
+ Settings(QWidget* parent, Shortcuts&& shortcuts);
+ Settings(const Settings&) = delete;
+ Settings(Settings&& rhs) = delete;
+ ~Settings() = default;
+ Settings& operator=(const Settings&) = delete;
+ Settings& operator=(Settings&& rhs) = delete;
+
+ [[nodiscard]] bool valid() const;
+
+ const Shortcuts& shortcuts() const;
+
+ private:
+ Shortcuts m_shortcuts;
+};
+
+} // namespace getsuyomi
#endif
diff --git a/src/getsuyomi.cpp b/src/getsuyomi.cpp
index 0472496..4e0dad7 100644
--- a/src/getsuyomi.cpp
+++ b/src/getsuyomi.cpp
@@ -6,7 +6,7 @@
namespace getsuyomi {
-Getsuyomi::Getsuyomi()
+Getsuyomi::Getsuyomi(QWidget* parent) : QWidget(parent)
{
// m_pageLeft.setContentsMargins(0, 1, 0, 1);
// m_pageRight.setContentsMargins(0, 1, 0, 1);
@@ -104,7 +104,19 @@ void Getsuyomi::setPagesNormal()
auto& label1 = m_pageLeft;
if (page1.isOkay()) {
- label1.setPixmap(QPixmap::fromImage(page1.getOkay()));
+ auto pixmap = QPixmap::fromImage(page1.getOkay());
+ auto pixmapHeight = pixmap.height();
+ auto widgetHeight = height();
+ if (pixmapHeight < widgetHeight)
+ {
+ // do nothing
+ label1.setPixmap(pixmap);
+ }
+ else
+ {
+ label1.setPixmap(pixmap.scaledToHeight(widgetHeight));
+ }
+
label1.resize(label1.pixmap().size());
}
}
diff --git a/src/getsuyomi.hpp b/src/getsuyomi.hpp
index abf8a72..e36e49d 100644
--- a/src/getsuyomi.hpp
+++ b/src/getsuyomi.hpp
@@ -20,7 +20,7 @@ class Getsuyomi : public QWidget {
public:
friend class GetsuyomiApp;
- Getsuyomi();
+ Getsuyomi(QWidget* parent);
Getsuyomi(const Getsuyomi&) = delete;
Getsuyomi(Getsuyomi&&) = delete;
~Getsuyomi();
diff --git a/src/luacxx.cpp b/src/luacxx.cpp
new file mode 100644
index 0000000..926e30f
--- /dev/null
+++ b/src/luacxx.cpp
@@ -0,0 +1,142 @@
+#include "luacxx.hpp"
+
+extern "C" {
+#include <lauxlib.h>
+#include <lualib.h>
+}
+
+namespace getsuyomi {
+
+using fud::FudStatus;
+
+LuaContext::LuaContext()
+{
+ m_state = luaL_newstate();
+ if (m_state != nullptr) {
+ luaL_openlibs(m_state);
+ }
+}
+
+LuaContext::~LuaContext()
+{
+ if (m_state != nullptr) {
+ lua_close(m_state);
+ m_state = nullptr;
+ }
+}
+
+LuaContext::LuaContext(LuaContext&& rhs) : m_state{rhs.m_state}
+{
+ rhs.m_state = nullptr;
+}
+
+LuaContext& LuaContext::operator=(LuaContext&& rhs)
+{
+ m_state = rhs.m_state;
+ rhs.m_state = nullptr;
+ return *this;
+}
+
+FudStatus LuaContext::loadFile(const char* filename)
+{
+ if (!valid())
+ {
+ return FudStatus::ObjectInvalid;
+ }
+ if (luaL_loadfile(m_state, filename) || lua_pcall(m_state, 0, 0, 0)) {
+ return FudStatus::Failure;
+ }
+ return FudStatus::Success;
+}
+
+LuaResult<int64_t> LuaContext::getGlobalInteger(const char* name)
+{
+ if (m_state == nullptr) {
+ return LuaResult<int64_t>::error(FudStatus::ObjectInvalid);
+ }
+
+ int isNumber{};
+ int64_t result{};
+
+ auto luaType = lua_getglobal(m_state, name);
+ // discard luaType since isNumber will check if variable is integral
+ static_cast<void>(luaType);
+
+ result = lua_tointegerx(m_state, -1, &isNumber);
+
+ lua_pop(m_state, 1);
+
+ if (!static_cast<bool>(isNumber)) {
+ return LuaResult<int64_t>::error(FudStatus::Failure);
+ }
+ return LuaResult<int64_t>::okay(result);
+}
+
+LuaResult<const char*> LuaContext::getGlobalString(const char* name)
+{
+ if (m_state == nullptr) {
+ return LuaResult<const char*>::error(FudStatus::ObjectInvalid);
+ }
+
+ size_t length;
+ const char* result{nullptr};
+
+ auto luaType = lua_getglobal(m_state, name);
+ static_cast<void>(luaType);
+
+ result = lua_tolstring(m_state, -1, &length);
+
+ lua_pop(m_state, 1);
+
+ if (result == nullptr) {
+ return LuaResult<const char*>::error(FudStatus::Failure);
+ }
+ return LuaResult<const char*>::okay(result);
+}
+
+LuaResult<std::vector<std::string>> LuaContext::getGlobalStringArray(const char* name)
+{
+ using RetType = LuaResult<std::vector<std::string>>;
+ if (m_state == nullptr) {
+ return RetType::error(FudStatus::ObjectInvalid);
+ }
+
+ auto luaType = lua_getglobal(m_state, name);
+ static_cast<void>(luaType);
+
+ if (!lua_istable(m_state, -1)) {
+ lua_pop(m_state, 1);
+ return RetType::error(FudStatus::Failure);
+ }
+
+ int64_t length;
+ int isNumber{};
+ lua_len(m_state, -1);
+ length = lua_tointegerx(m_state, -1, &isNumber);
+ lua_pop(m_state, 1);
+
+ if (!static_cast<bool>(isNumber) || length < 0) {
+ lua_pop(m_state, 1);
+ return RetType::error(FudStatus::Failure);
+ }
+
+ std::vector<std::string> output{};
+ output.reserve(static_cast<size_t>(length));
+ for (int64_t index = 1; index <= length; ++index) {
+ const char* result{nullptr};
+ lua_pushinteger(m_state, index);
+ static_cast<void>(lua_gettable(m_state, -2));
+ result = lua_tolstring(m_state, -1, nullptr);
+
+ if (result == nullptr) {
+ lua_pop(m_state, 1);
+ return RetType::error(FudStatus::Failure);
+ }
+ output.emplace_back(result);
+ }
+
+ lua_pop(m_state, 1);
+ return RetType::okay(output);
+}
+
+} // namespace getsuyomi
diff --git a/src/luacxx.hpp b/src/luacxx.hpp
new file mode 100644
index 0000000..388f640
--- /dev/null
+++ b/src/luacxx.hpp
@@ -0,0 +1,47 @@
+#ifndef LUA_CXX_HPP
+#define LUA_CXX_HPP
+
+extern "C" {
+#include <lua.h>
+}
+#include <fud_status.hpp>
+#include <fud_result.hpp>
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace getsuyomi {
+
+template<typename T>
+using LuaResult = fud::Result<T, fud::FudStatus>;
+
+static_assert(std::is_convertible_v<lua_Integer, int64_t>);
+
+class LuaContext {
+public:
+ LuaContext();
+ ~LuaContext();
+ LuaContext(const LuaContext&) = delete;
+ LuaContext(LuaContext&& rhs);
+ LuaContext& operator=(const LuaContext&) = delete;
+ LuaContext& operator=(LuaContext&& rhs);
+
+ fud::FudStatus loadFile(const char* filename);
+
+ LuaResult<int64_t> getGlobalInteger(const char* name);
+ LuaResult<const char*> getGlobalString(const char* name);
+ LuaResult<std::vector<std::string>> getGlobalStringArray(const char* name);
+
+ [[nodiscard]] constexpr bool valid() const {
+ return m_state != nullptr;
+ }
+
+private:
+ lua_State *m_state{nullptr};
+};
+
+} // namespace getsuyomi
+
+
+#endif
diff --git a/src/main.cpp b/src/main.cpp
index 56c4563..641372e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -3,8 +3,8 @@
int main(int argc, char* argv[])
{
QApplication app{argc, argv};
- getsuyomi::GetsuyomiApp getsuyomi{};
- auto status = getsuyomi.setup();
+ auto getsuyomi = std::make_unique<getsuyomi::GetsuyomiApp>();
+ auto status = getsuyomi->setup();
if (status != fud::FudStatus::Success) {
return static_cast<int>(status);
}
diff --git a/src/main_window.cpp b/src/main_window.cpp
index a554358..007705f 100644
--- a/src/main_window.cpp
+++ b/src/main_window.cpp
@@ -1,27 +1,112 @@
#include "main_window.hpp"
#include "config.hpp"
+#include "luacxx.hpp"
#include <QString>
+#include <cerrno>
#include <filesystem>
#include <fud_result.hpp>
#include <fud_status.hpp>
#include <string>
+#include <sys/stat.h>
+#include <fcntl.h>
#include <vector>
-#include <zip.h>
namespace getsuyomi {
using fud::FudStatus;
using GetEnvResult = fud::Result<GetsuyomiConfig, FudStatus>;
+using GetConfigResult = fud::Result<GetsuyomiConfig, FudStatus>;
constexpr const char* HOME{"HOME"};
std::optional<std::string> getEnvVar(const char* envVar);
-void getEnvVar(const std::string envVar, std::string& envValue, const char* backup);
+void getEnvVar(const std::string& homeVar, const std::string& envVar, std::string& envValue, const char* backup);
GetEnvResult getEnvironment();
-GetsuyomiApp::GetsuyomiApp() : m_getsuyomi{new Getsuyomi()}
+FudStatus createXdgDirectory(const std::string& directoryName)
+{
+ constexpr mode_t xdgMode = 0700;
+ auto dirStatus = mkdir(directoryName.c_str(), xdgMode);
+ if (dirStatus == 0)
+ {
+ return FudStatus::Success;
+ }
+
+ if (errno != EEXIST) {
+ return FudStatus::Failure;
+ }
+ struct stat statBuffer{};
+ dirStatus = stat(directoryName.c_str(), &statBuffer);
+
+ if (dirStatus != 0) {
+ return FudStatus::Failure;
+ }
+
+ if ((statBuffer.st_mode & S_IFMT) != S_IFDIR) {
+ return FudStatus::Failure;
+ }
+
+ return FudStatus::Success;
+}
+
+GetConfigResult getUserConfig()
+{
+ auto configResult = getEnvironment();
+ if (configResult.isError()) {
+ return GetConfigResult::error(configResult.getError());
+ }
+ auto config = configResult.getOkay();
+
+ auto dirStatus = createXdgDirectory(config.dataHome);
+ if (dirStatus != FudStatus::Success) {
+ return GetConfigResult::error(dirStatus);
+ }
+ dirStatus = createXdgDirectory(config.configHome);
+ if (dirStatus != FudStatus::Success) {
+ return GetConfigResult::error(dirStatus);
+ }
+ dirStatus = createXdgDirectory(config.stateHome);
+ if (dirStatus != FudStatus::Success) {
+ return GetConfigResult::error(dirStatus);
+ }
+
+ LuaContext luaContext{};
+ if (!luaContext.valid()) {
+ return GetConfigResult::error(FudStatus::Failure);
+ }
+
+ auto configFileName = std::filesystem::path(config.configHome).append("config.lua");
+ qDebug("configFileName is %s", configFileName.c_str());
+
+ constexpr mode_t configFileMode = 0600;
+ auto fdResult = open(configFileName.c_str(), O_CREAT, configFileMode);
+ if (fdResult == -1) {
+ if (errno != EEXIST) {
+ return GetConfigResult::error(FudStatus::Failure);
+ }
+ }
+ close(fdResult);
+
+ auto luaStatus = luaContext.loadFile(configFileName.c_str());
+ if (luaStatus != FudStatus::Success)
+ {
+ qCritical("Failed to load file in lua %s", fud::FudStatusToString(luaStatus));
+ return GetConfigResult::error(luaStatus);
+ }
+
+ auto result = luaContext.getGlobalString(actionTypeToString(ActionType::OpenFile));
+ if (result.isError())
+ {
+ qCritical("Failed to get variable from lua %s", fud::FudStatusToString(result.getError()));
+ return GetConfigResult::error(result.getError());
+ }
+
+ return GetConfigResult::okay(config);
+}
+
+GetsuyomiApp::GetsuyomiApp() : QMainWindow(nullptr), m_getsuyomi{new Getsuyomi(this)}
{
readSettings();
}
@@ -34,10 +119,12 @@ FudStatus GetsuyomiApp::setup()
QCoreApplication::setApplicationVersion(AppVersionString);
setWindowTitle(AppName);
- auto envResult = getEnvironment();
+ auto envResult = getUserConfig();
if (envResult.isError()) {
+ qCritical("Failure %s", fud::FudStatusToString(envResult.getError()));
return envResult.getError();
}
+ m_config = envResult.getOkay();
createActions();
createMenus();
@@ -59,7 +146,7 @@ void GetsuyomiApp::createActions()
m_openFile->setStatusTip(tr("Open a file"));
connect(m_openFile, &QAction::triggered, this, &GetsuyomiApp::openFile);
- m_openDirectory = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::FolderOpen), tr("Open Directory"), this);
+ m_openDirectory = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::FolderOpen), tr("Open &Directory"), this);
m_openDirectory->setShortcut(Qt::CTRL | Qt::ALT | Qt::Key_O);
m_openDirectory->setStatusTip(tr("Open a directory"));
connect(m_openDirectory, &QAction::triggered, this, &GetsuyomiApp::openDirectory);
@@ -69,6 +156,11 @@ void GetsuyomiApp::createActions()
m_quitAction->setStatusTip(tr("Quit"));
connect(m_quitAction, &QAction::triggered, this, &GetsuyomiApp::quit);
+ m_settingsAction = new QAction(QIcon("resources/gear.svg"), tr("&Settings"), this);
+ m_settingsAction->setShortcut(Qt::CTRL | Qt::Key_P);
+ m_settingsAction->setStatusTip(tr("Configure getsuyomi"));
+ connect(m_settingsAction, &QAction::triggered, this, &GetsuyomiApp::configure);
+
auto nextShortcuts = QList<QKeySequence>{};
nextShortcuts.append(QKeySequence{Qt::Key_L});
nextShortcuts.append(QKeySequence{Qt::Key_Right});
@@ -111,17 +203,24 @@ void GetsuyomiApp::createActions()
void GetsuyomiApp::createMenus()
{
- m_menuBar = menuBar()->addMenu(tr("&File"));
- m_menuBar->addAction(m_openFile);
- m_menuBar->addAction(m_openDirectory);
- m_menuBar->addAction(m_quitAction);
+ m_fileMenu = menuBar()->addMenu(tr("&File"));
+ m_fileMenu->addAction(m_openFile);
+ m_fileMenu->addAction(m_openDirectory);
+ m_fileMenu->addAction(m_quitAction);
+
+ m_SettingsMenu = menuBar()->addMenu(tr("&Settings"));
+ m_SettingsMenu->addAction(m_settingsAction);
}
void GetsuyomiApp::createToolBar()
{
m_toolBar = addToolBar(tr("&Navigation"));
+ m_toolBar->setObjectName("Navigation");
+
+ // goto first
m_toolBar->addAction(m_backAction);
m_toolBar->addAction(m_nextAction);
+ // goto last
m_toolBar->addSeparator();
@@ -170,10 +269,7 @@ void GetsuyomiApp::openFile()
void GetsuyomiApp::openDirectory()
{
- auto dialog = QFileDialog(
- this,
- tr("Open Directory"),
- m_lastOpenedDirectory);
+ auto dialog = QFileDialog(this, tr("Open Directory"), m_lastOpenedDirectory);
dialog.setFileMode(QFileDialog::Directory);
QString directoryName;
if (dialog.exec()) {
@@ -199,6 +295,16 @@ void GetsuyomiApp::quit()
QCoreApplication::quit();
}
+void GetsuyomiApp::configure()
+{
+ Settings settings(this, m_config.shortcuts());
+ if (!settings.valid()) {
+ qCritical("Invalid settings");
+ return;
+ }
+ settings.exec();
+}
+
void GetsuyomiApp::next()
{
m_getsuyomi->next();
@@ -270,16 +376,17 @@ std::optional<std::string> getEnvVar(const char* envVar)
return varArray.toStdString();
}
-void getEnvVar(const std::string envVar, std::string& envValue, const char* backup)
+void getEnvVar(const std::string& homeVar, const std::string& envVar, std::string& envValue, const char* backup)
{
auto envValueOpt = getEnvVar(envVar.c_str());
+ std::filesystem::path envValuePath{homeVar};
if (envValueOpt == std::nullopt || envValueOpt->length() == 0) {
- std::filesystem::path envValuePath{HOME};
envValuePath.append(backup);
- envValue = envValuePath;
} else {
- envValue = *envValueOpt;
+ envValuePath = *envValueOpt;
}
+ envValuePath.append(AppName);
+ envValue = envValuePath;
// qDebug("%s is %s", envVar.c_str(), envValue.c_str());
}
@@ -297,15 +404,15 @@ GetEnvResult getEnvironment()
/* If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used. */
const std::string XdgDataHome{"XDG_DATA_HOME"};
- getEnvVar(XdgDataHome, config.dataHome, ".local/share");
+ getEnvVar(config.home, XdgDataHome, config.dataHome, ".local/share");
/* If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used. */
const std::string XdgConfigHome{"XDG_CONFIG_HOME"};
- getEnvVar(XdgConfigHome, config.configHome, ".config");
+ getEnvVar(config.home, XdgConfigHome, config.configHome, ".config");
/* If $XDG_STATE_HOME is either not set or empty, a default equal to $HOME/.local/state should be used. */
const std::string XdgStateHome{"XDG_STATE_HOME"};
- getEnvVar(XdgStateHome, config.stateHome, ".local/state");
+ getEnvVar(config.home, XdgStateHome, config.stateHome, ".local/state");
return GetEnvResult::okay(config);
}
diff --git a/src/main_window.hpp b/src/main_window.hpp
index 55b200d..2dabf77 100644
--- a/src/main_window.hpp
+++ b/src/main_window.hpp
@@ -3,6 +3,7 @@
#include "archive.hpp"
#include "getsuyomi.hpp"
+#include "config.hpp"
#include <QtWidgets>
#include <fud_status.hpp>
@@ -35,11 +36,16 @@ class GetsuyomiApp : public QMainWindow {
void createToolBar();
/* Private data */
+ GetsuyomiConfig m_config{};
+
Getsuyomi* m_getsuyomi{nullptr};
QAction* m_openFile{nullptr};
QAction* m_openDirectory{nullptr};
QAction* m_quitAction{nullptr};
+
+ QAction* m_settingsAction{nullptr};
+
QAction* m_nextAction{nullptr};
QAction* m_backAction{nullptr};
@@ -48,7 +54,9 @@ class GetsuyomiApp : public QMainWindow {
QAction* m_setMangaLayout{nullptr};
QActionGroup* m_setPageLayoutGroup{nullptr};
- QMenu* m_menuBar{nullptr};
+ QMenu* m_fileMenu{nullptr};
+ QMenu* m_SettingsMenu{nullptr};
+ QMenu* m_HelpMenu{nullptr};
QToolBar* m_toolBar{nullptr};
QString m_lastOpenedDirectory{};
@@ -58,6 +66,8 @@ class GetsuyomiApp : public QMainWindow {
void openDirectory();
void quit();
+ void configure();
+
void readSettings();
void writeSettings();
void closeEvent(QCloseEvent* event) override;