From 6f2b61b676a16482fdac70a58a8e875c4d68e713 Mon Sep 17 00:00:00 2001 From: Dominick Allen Date: Mon, 30 Sep 2024 00:36:19 -0500 Subject: Add configuration handling. --- CMakeLists.txt | 16 +++- resources/CMakeLists.txt | 4 + resources/config.lua | 2 + resources/gear.svg | 66 +++++++++++++++ src/archive.cpp | 1 - src/config.cpp | 211 +++++++++++++++++++++++++++++++++++++++++++++++ src/config.hpp | 144 +++++++++++++++++++++++++++++++- src/getsuyomi.cpp | 16 +++- src/getsuyomi.hpp | 2 +- src/luacxx.cpp | 142 +++++++++++++++++++++++++++++++ src/luacxx.hpp | 47 +++++++++++ src/main.cpp | 4 +- src/main_window.cpp | 147 ++++++++++++++++++++++++++++----- src/main_window.hpp | 12 ++- 14 files changed, 782 insertions(+), 32 deletions(-) create mode 100644 resources/config.lua create mode 100644 resources/gear.svg create mode 100644 src/config.cpp create mode 100644 src/luacxx.cpp create mode 100644 src/luacxx.hpp 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 @@ + + + + + + + + + + + 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 +#include + +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 Shortcuts::actions() const { + std::vector actionList{}; + actionList.reserve(m_actionToShortcuts.size()); + for (const auto& [action, discarded] : m_actionToShortcuts) { + actionList.push_back(action); + } + return actionList; +} + +std::optional Shortcuts::action(QKeySequence keySequence) const +{ + if (m_shortcutToAction.contains(keySequence)) { + return m_shortcutToAction.at(keySequence); + } else { + return std::nullopt; + } +} + +std::optional Shortcuts::shortcuts(ActionType action) const +{ + if (contains(action)) { + const auto& shortcuts = m_actionToShortcuts.at(action); + ShortcutList shortcutList{}; + shortcutList.reserve(static_cast(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(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 +#include +#include +#include +#include +#include +#include #include 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; +using ShortcutSet = std::set; +using ShortcutMap = std::map; +using ShortcutRevMap = std::map; + +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 actions() const; + + std::optional action(QKeySequence keySequence) const; + + std::optional 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 +#include +} + +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 LuaContext::getGlobalInteger(const char* name) +{ + if (m_state == nullptr) { + return LuaResult::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(luaType); + + result = lua_tointegerx(m_state, -1, &isNumber); + + lua_pop(m_state, 1); + + if (!static_cast(isNumber)) { + return LuaResult::error(FudStatus::Failure); + } + return LuaResult::okay(result); +} + +LuaResult LuaContext::getGlobalString(const char* name) +{ + if (m_state == nullptr) { + return LuaResult::error(FudStatus::ObjectInvalid); + } + + size_t length; + const char* result{nullptr}; + + auto luaType = lua_getglobal(m_state, name); + static_cast(luaType); + + result = lua_tolstring(m_state, -1, &length); + + lua_pop(m_state, 1); + + if (result == nullptr) { + return LuaResult::error(FudStatus::Failure); + } + return LuaResult::okay(result); +} + +LuaResult> LuaContext::getGlobalStringArray(const char* name) +{ + using RetType = LuaResult>; + if (m_state == nullptr) { + return RetType::error(FudStatus::ObjectInvalid); + } + + auto luaType = lua_getglobal(m_state, name); + static_cast(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(isNumber) || length < 0) { + lua_pop(m_state, 1); + return RetType::error(FudStatus::Failure); + } + + std::vector output{}; + output.reserve(static_cast(length)); + for (int64_t index = 1; index <= length; ++index) { + const char* result{nullptr}; + lua_pushinteger(m_state, index); + static_cast(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 +} +#include +#include + +#include +#include +#include + +namespace getsuyomi { + +template +using LuaResult = fud::Result; + +static_assert(std::is_convertible_v); + +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 getGlobalInteger(const char* name); + LuaResult getGlobalString(const char* name); + LuaResult> 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(); + auto status = getsuyomi->setup(); if (status != fud::FudStatus::Success) { return static_cast(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 +#include #include #include #include #include +#include +#include #include -#include namespace getsuyomi { using fud::FudStatus; using GetEnvResult = fud::Result; +using GetConfigResult = fud::Result; constexpr const char* HOME{"HOME"}; std::optional 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{}; 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 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 #include @@ -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; -- cgit v1.2.3