summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/archive.cpp7
-rw-r--r--src/config.cpp390
-rw-r--r--src/config.hpp103
-rw-r--r--src/getsuyomi.cpp2
-rw-r--r--src/luacxx.cpp33
-rw-r--r--src/main_window.cpp162
-rw-r--r--src/main_window.hpp15
-rw-r--r--src/settings.cpp167
-rw-r--r--src/settings.hpp80
9 files changed, 578 insertions, 381 deletions
diff --git a/src/archive.cpp b/src/archive.cpp
index d773f73..b4bee29 100644
--- a/src/archive.cpp
+++ b/src/archive.cpp
@@ -1,8 +1,5 @@
#include "archive.hpp"
-
-#include "main_window.hpp"
-
-#include <algorithm>
+#include "config.hpp"
namespace getsuyomi {
@@ -46,6 +43,8 @@ ZipArchive::~ZipArchive()
ZipArchive& ZipArchive::operator=(ZipArchive&& rhs)
{
+ cleanup();
+
m_archive = rhs.m_archive;
m_sortedIndices = std::move(rhs.m_sortedIndices);
m_filenames = std::move(rhs.m_filenames);
diff --git a/src/config.cpp b/src/config.cpp
index 5ffb76e..ab7d3ca 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -3,13 +3,145 @@
#include "luacxx.hpp"
#include <algorithm>
-#include <fcntl.h>
+#include <filesystem>
+#include <fud_c_file.hpp>
+#include <fud_status.hpp>
+#include <fud_string.hpp>
#include <vector>
namespace getsuyomi {
using fud::FudStatus;
+constexpr auto PermNone = fud::PermissionType::None;
+constexpr auto defaultPermissions = fud::Permissions(fud::PermReadWrite, PermNone, PermNone);
+
+std::optional<std::string> getEnvVar(const char* envVar)
+{
+ const QByteArray defaultArray{};
+ if (envVar == nullptr) {
+ return std::nullopt;
+ }
+ QByteArray varArray = qgetenv(envVar);
+ if (varArray == defaultArray) {
+ return std::nullopt;
+ }
+ return varArray.toStdString();
+}
+
+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) {
+ envValuePath.append(backup);
+ } else {
+ envValuePath = *envValueOpt;
+ }
+ envValuePath.append(AppName);
+ envValue = envValuePath;
+}
+
+GetEnvResult getEnvironment()
+{
+ GetsuyomiConfig config{};
+
+ auto homeOpt = getEnvVar(HOME);
+ if (homeOpt == std::nullopt || homeOpt->length() == 0) {
+ qCritical("Error getting home");
+ return GetEnvResult::error(FudStatus::Failure);
+ }
+ config.home = *homeOpt;
+
+ /* 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(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(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(config.home, XdgStateHome, config.stateHome, ".local/state");
+
+ return GetEnvResult::okay(config);
+}
+
+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;
+}
+
+void setUserConfig(GetsuyomiConfig& config, const ShortcutMap& shortcutMap)
+{
+ auto binder = [&](ShortcutList& shortcuts, ActionType action) {
+ shortcuts = shortcutListFromSet(shortcutMap.at(action));
+ };
+
+ binder(config.openFileShortcuts, ActionType::OpenFile);
+ binder(config.openDirectoryShortcuts, ActionType::OpenDirectory);
+ binder(config.quitShortcuts, ActionType::Quit);
+
+ binder(config.settingsShortcuts, ActionType::Configure);
+
+ binder(config.nextShortcuts, ActionType::Next);
+ binder(config.backShortcuts, ActionType::Back);
+
+ binder(config.singlePageShortcuts, ActionType::SinglePage);
+ binder(config.dualPageShortcuts, ActionType::DualPage);
+ binder(config.mangaPageShortcuts, ActionType::MangaPage);
+}
+
+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);
+ }
+
+ config.configFilename = std::filesystem::path(config.configHome).append("config.lua");
+ auto shortcutMap = Shortcuts::fromUserConfig(config.configFilename);
+
+ setUserConfig(config, shortcutMap);
+
+ return GetConfigResult::okay(config);
+}
+
ShortcutSet shortcutSetFromList(const ShortcutList& shortcutList)
{
ShortcutSet shortcutSet{};
@@ -214,14 +346,14 @@ Shortcuts Shortcuts::fromLuaConfig(const std::filesystem::path& configFileName)
ShortcutMap Shortcuts::fromUserConfig(const std::filesystem::path& configFileName)
{
- constexpr mode_t configFileMode = 0600;
- auto fdResult = open(configFileName.c_str(), O_CREAT, configFileMode);
- if (fdResult == -1) {
+ fud::String filepath(configFileName.c_str());
+ fud::CTextFile file{filepath, fud::CFileMode::ReadWriteAppend};
+ auto status = file.create(defaultPermissions);
+ if (status != FudStatus::Success) {
if (errno != EEXIST) {
qCritical("Could not load or create user config file %s.", configFileName.c_str());
}
}
- close(fdResult);
auto shortcuts = Shortcuts::fromLuaConfig(configFileName);
@@ -254,181 +386,135 @@ ShortcutMap Shortcuts::fromUserConfig(const std::filesystem::path& configFileNam
return shortcuts.shortcutMap();
}
-ShortcutMap GetsuyomiConfig::shortcuts() const
+FudStatus backupFile(const fud::String& filepath)
{
- ShortcutMap shortcuts{};
+ fud::CTextFile file{filepath, fud::CFileMode::ReadWriteAppend};
+ auto status = file.create(defaultPermissions);
+ if (status != FudStatus::Success) {
+ if (errno != EEXIST) {
+ qCritical("Could not load or create user config file %s.", filepath.c_str());
+ }
+ }
- shortcuts.insert({ActionType::OpenFile, shortcutSetFromList(openFileShortcuts)});
- shortcuts.insert({ActionType::OpenDirectory, shortcutSetFromList(openDirectoryShortcuts)});
- shortcuts.insert({ActionType::Quit, shortcutSetFromList(quitShortcuts)});
+ size_t fileSize;
+ auto fileSizeStatus = file.size();
+ if (fileSizeStatus.isOkay()) {
+ fileSize = fileSizeStatus.getOkay();
+ } else {
+ qWarning("Could not get file size");
+ return fileSizeStatus.getError();
+ }
- shortcuts.insert({ActionType::Configure, shortcutSetFromList(settingsShortcuts)});
+ if (fileSize == 0) {
+ return FudStatus::Empty;
+ }
- shortcuts.insert({ActionType::Next, shortcutSetFromList(nextShortcuts)});
- shortcuts.insert({ActionType::Back, shortcutSetFromList(backShortcuts)});
+ std::vector<char> backupFileData(fileSize);
+ auto readStatus = file.read(backupFileData.data(), backupFileData.size(), fileSize);
+ if (readStatus.status != FudStatus::Success) {
+ qWarning("Could not read existing config");
+ return readStatus.status;
+ }
- shortcuts.insert({ActionType::SinglePage, shortcutSetFromList(singlePageShortcuts)});
- shortcuts.insert({ActionType::DualPage, shortcutSetFromList(dualPageShortcuts)});
- shortcuts.insert({ActionType::MangaPage, shortcutSetFromList(mangaPageShortcuts)});
+ auto backupFileName{filepath.catenate(".bak")};
+ fud::CTextFile backupFile{backupFileName, fud::CFileMode::ReadWriteTruncate};
+ status = backupFile.create(defaultPermissions);
+ if (status != FudStatus::Success) {
+ qWarning("Could not create or open backup file %s", backupFileName.c_str());
+ return status;
+ }
- return shortcuts;
-}
+ auto writeResult = backupFile.write(backupFileData.data(), backupFileData.size());
+ if (writeResult.status != FudStatus::Success) {
+ qWarning("Could not write backupfile");
+ }
-ShortcutDisplay::ShortcutDisplay(QWidget* parent, QKeySequence shortcut) : QWidget{parent}, m_binding{shortcut}
-{
- auto layout = new QHBoxLayout();
- layout->addWidget(new QLabel(shortcut.toString()));
- auto* deleteButton = new QPushButton("Delete", this);
- connect(deleteButton, &QPushButton::clicked, this, &ShortcutDisplay::removeOnClicked);
- layout->addWidget(deleteButton);
- setLayout(layout);
+ return writeResult.status;
}
-void ShortcutDisplay::removeOnClicked()
+FudStatus Shortcuts::save(const std::filesystem::path& configFileName) const
{
- emit removeClicked(m_binding);
-}
+ fud::String filepath(configFileName.c_str());
+ static_cast<void>(backupFile(filepath));
-ShortcutCollector::ShortcutCollector(QWidget* parent, ActionType action, Shortcuts& shortcuts) :
- QWidget(parent), m_action{action}, m_shortcuts{shortcuts}, m_bindings{}
-{
- m_layout = new QVBoxLayout();
- auto headerLayout = new QHBoxLayout();
- auto* name = new QLabel(actionTypeToString(m_action));
- headerLayout->addWidget(name);
- m_shortcutEditor = new QKeySequenceEdit(this);
- m_shortcutEditor->setMaximumSequenceLength(1);
- connect(m_shortcutEditor, &QKeySequenceEdit::editingFinished, this, &ShortcutCollector::checkBinding);
- headerLayout->addWidget(m_shortcutEditor);
-
- m_acceptButton = new QPushButton("Accept", this);
- m_acceptButton->setEnabled(false);
- connect(m_acceptButton, &QPushButton::clicked, this, &ShortcutCollector::addBinding);
- headerLayout->addWidget(m_acceptButton);
- headerLayout->addStretch();
-
- m_layout->addLayout(headerLayout);
-
- auto shortcutOptions = m_shortcuts.shortcuts(m_action);
- if (shortcutOptions != std::nullopt) {
- for (const auto& binding : *shortcutOptions) {
- createBinding(binding);
+ fud::CTextFile file{filepath, fud::CFileMode::ReadWriteTruncate};
+ auto status = file.create(defaultPermissions);
+ if (status != FudStatus::Success) {
+ if (errno != EEXIST) {
+ qCritical("Could not load or create user config file %s.", configFileName.c_str());
}
- } else {
- qWarning("No shortcuts found for %s", actionTypeToString(action));
}
- setLayout(m_layout);
-}
-
-void ShortcutCollector::createBinding(QKeySequence binding)
-{
- auto displayItem = new ShortcutDisplay(this, binding);
- m_bindings[binding] = displayItem;
- m_layout->addWidget(displayItem);
- connect(displayItem, &ShortcutDisplay::removeClicked, this, &ShortcutCollector::removeBinding);
-}
-
-void ShortcutCollector::checkBinding()
-{
- auto keySequence = m_shortcutEditor->keySequence();
- if (keySequence == QKeySequence::UnknownKey) {
- m_acceptButton->setEnabled(false);
- return;
+ fud::String output{};
+ constexpr size_t defaultFileSize = 4096;
+ auto reserveStatus = output.reserve(defaultFileSize);
+ if (reserveStatus != FudStatus::Success) {
+ qCritical("Failed to reserve space for new file data.");
+ return reserveStatus;
}
- m_acceptButton->setEnabled(not m_shortcuts.contains(keySequence));
-}
+ for (auto [action, bindings] : m_actionToShortcuts) {
+ auto appendStatus = output.append(actionTypeToString(action));
+ if (appendStatus == FudStatus::Success) {
+ appendStatus = output.append(" = {");
+ }
-void ShortcutCollector::addBinding()
-{
- auto keySequence = m_shortcutEditor->keySequence();
- if (keySequence == QKeySequence::UnknownKey) {
- qWarning("Invalid state - can't accept unknown key");
- return;
- } else if (m_shortcuts.contains(keySequence)) {
- qWarning("Shortcut %s already bound", qPrintable(keySequence.toString()));
- return;
- }
- auto result = m_shortcuts.bind(m_action, keySequence);
- if (result != FudStatus::Success) {
- qCritical("Error binding %s to action %s", qPrintable(keySequence.toString()), actionTypeToString(m_action));
- return;
- }
- if (m_bindings.contains(keySequence)) {
- qWarning("binding %s to action %s already exists", qPrintable(keySequence.toString()), actionTypeToString(m_action));
- m_layout->removeWidget(m_bindings[keySequence]);
- delete m_bindings[keySequence];
- }
-
- createBinding(keySequence);
- m_shortcutEditor->clear();
-}
+ for (auto binding : bindings) {
+ if (appendStatus == FudStatus::Success) {
+ appendStatus = output.append("\"");
+ } else {
+ break;
+ }
+ if (appendStatus == FudStatus::Success) {
+ appendStatus = output.append(binding.toString().toUtf8().data());
+ } else {
+ break;
+ }
+ if (appendStatus == FudStatus::Success) {
+ appendStatus = output.append("\", ");
+ } else {
+ break;
+ }
+ }
+ if (appendStatus == FudStatus::Success) {
+ appendStatus = output.append("}\n");
+ } else {
+ qWarning("Failed to append to file data %s.", fud::FudStatusToString(appendStatus));
+ return appendStatus;
+ }
-void ShortcutCollector::removeBinding(QKeySequence binding)
-{
- auto result = m_shortcuts.remove(binding);
- if (result == FudStatus::NotFound) {
- qWarning("binding %s not found", qPrintable(binding.toString()));
- } else if (result != FudStatus::Success) {
- qWarning("error removing binding %s: %s", qPrintable(binding.toString()), FudStatusToString(result));
- }
- auto bindingHandle = m_bindings.extract(binding);
- if (bindingHandle) {
- m_layout->removeWidget(bindingHandle.mapped());
- delete bindingHandle.mapped();
- } else {
- qWarning("Could not remove widget!");
+ if (!output.utf8Valid()) {
+ qCritical("Output string is invalid.");
+ return FudStatus::Failure;
+ }
}
- checkBinding();
-}
-
-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;
+ auto writeStatus = file.write(output.data(), output.length());
+ if (writeStatus.status != FudStatus::Success) {
+ qCritical("Could not save user config file %s.", configFileName.c_str());
}
- auto containerLayout = new QHBoxLayout();
- auto* columnLayout = new QVBoxLayout();
- size_t counter{0};
- constexpr size_t maxEntriesPerColumn{4};
-
- for (const auto& action : m_shortcuts.actions()) {
- auto* collector = new ShortcutCollector(this, action, m_shortcuts);
- columnLayout->addWidget(collector);
- counter++;
- if (counter % (maxEntriesPerColumn + 1) == 0) {
- containerLayout->addLayout(columnLayout);
- columnLayout = new QVBoxLayout();
- }
- }
+ return writeStatus.status;
+}
- if (columnLayout->count() > 0) {
- containerLayout->addLayout(columnLayout);
- }
+ShortcutMap GetsuyomiConfig::shortcuts() const
+{
+ ShortcutMap shortcuts{};
- layout->addLayout(containerLayout);
+ shortcuts.insert({ActionType::OpenFile, shortcutSetFromList(openFileShortcuts)});
+ shortcuts.insert({ActionType::OpenDirectory, shortcutSetFromList(openDirectoryShortcuts)});
+ shortcuts.insert({ActionType::Quit, shortcutSetFromList(quitShortcuts)});
- auto* dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
- connect(dialogButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
- connect(dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
- layout->addWidget(dialogButtonBox);
+ shortcuts.insert({ActionType::Configure, shortcutSetFromList(settingsShortcuts)});
- setLayout(layout);
-}
+ shortcuts.insert({ActionType::Next, shortcutSetFromList(nextShortcuts)});
+ shortcuts.insert({ActionType::Back, shortcutSetFromList(backShortcuts)});
-bool Settings::valid() const
-{
- return m_shortcuts.valid();
-}
+ shortcuts.insert({ActionType::SinglePage, shortcutSetFromList(singlePageShortcuts)});
+ shortcuts.insert({ActionType::DualPage, shortcutSetFromList(dualPageShortcuts)});
+ shortcuts.insert({ActionType::MangaPage, shortcutSetFromList(mangaPageShortcuts)});
-const Shortcuts& Settings::shortcuts() const
-{
- return m_shortcuts;
+ return shortcuts;
}
} // namespace getsuyomi
diff --git a/src/config.hpp b/src/config.hpp
index 5937a9b..bc5698e 100644
--- a/src/config.hpp
+++ b/src/config.hpp
@@ -1,18 +1,26 @@
#ifndef GETSUYOMI_CONFIG_HPP
#define GETSUYOMI_CONFIG_HPP
-#include <QtWidgets>
-#include <fud_status.hpp>
#include <fud_result.hpp>
+#include <fud_status.hpp>
#include <map>
#include <optional>
#include <qkeysequence.h>
#include <qlist.h>
#include <set>
#include <string>
+#include <filesystem>
namespace getsuyomi {
+constexpr const char* AppVendor = "libfud";
+
+constexpr const char* AppName = "getsuyomi";
+
+constexpr const char* AppVersionString = "1.0.0";
+
+constexpr const char* HOME{"HOME"};
+
enum class ActionType
{
OpenFile,
@@ -30,6 +38,27 @@ enum class ActionType
MangaPage
};
+struct GetsuyomiConfig;
+
+using GetEnvResult = fud::Result<GetsuyomiConfig, fud::FudStatus>;
+using GetConfigResult = fud::Result<GetsuyomiConfig, fud::FudStatus>;
+using ShortcutList = QList<QKeySequence>;
+using ShortcutSet = std::set<QKeySequence>;
+using ShortcutMap = std::map<ActionType, ShortcutSet>;
+using ShortcutRevMap = std::map<QKeySequence, ActionType>;
+
+std::optional<std::string> getEnvVar(const char* envVar);
+
+void getEnvVar(const std::string& homeVar, const std::string& envVar, std::string& envValue, const char* backup);
+
+GetEnvResult getEnvironment();
+
+fud::FudStatus createXdgDirectory(const std::string& directoryName);
+
+void setUserConfig(GetsuyomiConfig& config, const ShortcutMap& shortcutMap);
+
+GetConfigResult getUserConfig();
+
constexpr const char* actionTypeToString(ActionType action)
{
switch (action) {
@@ -64,11 +93,6 @@ constexpr const char* actionTypeToString(ActionType action)
}
}
-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);
ShortcutList shortcutListFromSet(const ShortcutSet& shortcutList);
@@ -95,11 +119,15 @@ class Shortcuts {
static ShortcutMap fromUserConfig(const std::filesystem::path& configFileName);
+ fud::FudStatus save(const std::filesystem::path& configFileName) const;
+
private:
Shortcuts() = default;
static Shortcuts fromLuaConfig(const std::filesystem::path& configFileName);
+ // fud::FudStatus saveAsLuaConfig(const std::f
+
bool m_valid{false};
ShortcutMap m_actionToShortcuts{};
@@ -112,6 +140,7 @@ struct GetsuyomiConfig {
std::string dataHome{};
std::string configHome{};
std::string stateHome{};
+ std::filesystem::path configFilename{};
ShortcutList openFileShortcuts{};
ShortcutList openDirectoryShortcuts{};
@@ -129,66 +158,6 @@ struct GetsuyomiConfig {
ShortcutMap shortcuts() const;
};
-class ShortcutDisplay : public QWidget {
- Q_OBJECT
-public:
- ShortcutDisplay(QWidget* parent, QKeySequence shortcut);
-
-signals:
- void removeClicked(QKeySequence shortcut);
-private:
- QKeySequence m_binding;
-private slots:
- void removeOnClicked();
-};
-
-constexpr auto foo = sizeof(QKeySequence);
-
-class ShortcutCollector : public QWidget {
- Q_OBJECT
- 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:
- void createBinding(QKeySequence binding);
-
- private slots:
- void checkBinding();
- void addBinding();
- void removeBinding(QKeySequence shortcut);
-
- private:
- ActionType m_action;
- Shortcuts& m_shortcuts;
- std::map<QKeySequence, ShortcutDisplay*> m_bindings;
- QKeySequenceEdit* m_shortcutEditor{nullptr};
- QPushButton* m_acceptButton{nullptr};
- QVBoxLayout* m_layout{nullptr};
-};
-
-class Settings : public QDialog {
- Q_OBJECT
- 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 4e0dad7..9f78ba0 100644
--- a/src/getsuyomi.cpp
+++ b/src/getsuyomi.cpp
@@ -1,7 +1,5 @@
#include "getsuyomi.hpp"
-#include "main_window.hpp"
-
#include <algorithm>
namespace getsuyomi {
diff --git a/src/luacxx.cpp b/src/luacxx.cpp
index c4bc063..0a6db82 100644
--- a/src/luacxx.cpp
+++ b/src/luacxx.cpp
@@ -32,6 +32,11 @@ LuaContext::LuaContext(LuaContext&& rhs) : m_state{rhs.m_state}
LuaContext& LuaContext::operator=(LuaContext&& rhs)
{
+ if (m_state != nullptr) {
+ lua_close(m_state);
+ m_state = nullptr;
+ }
+
m_state = rhs.m_state;
rhs.m_state = nullptr;
return *this;
@@ -125,20 +130,34 @@ LuaResult<std::vector<std::string>> LuaContext::getGlobalStringArray(const char*
std::vector<std::string> output{};
output.reserve(static_cast<size_t>(length));
+
+ lua_pushnil(m_state);
+
+ auto result = FudStatus::Success;
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);
+ const char* luaResult{nullptr};
+ auto luaNext = lua_next(m_state, -2);
+ if (luaNext == 0) {
+ lua_pop(m_state, 2);
+ result = FudStatus::Failure;
+ break;
+ }
+ luaResult = lua_tolstring(m_state, -1, nullptr);
- if (result == nullptr) {
+ if (luaResult == nullptr) {
lua_pop(m_state, 1);
- return RetType::error(FudStatus::Failure);
+ result = FudStatus::Failure;
+ break;
}
- output.emplace_back(result);
+ output.emplace_back(luaResult);
+ lua_pop(m_state, 1);
}
lua_pop(m_state, 1);
+ if (result != FudStatus::Success)
+ {
+ return RetType::error(result);
+ }
return RetType::okay(output);
}
diff --git a/src/main_window.cpp b/src/main_window.cpp
index 82e6ef2..52c14ef 100644
--- a/src/main_window.cpp
+++ b/src/main_window.cpp
@@ -1,7 +1,7 @@
#include "main_window.hpp"
#include "config.hpp"
-#include "luacxx.hpp"
+#include "settings.hpp"
#include <QString>
#include <cerrno>
@@ -10,94 +10,11 @@
#include <fud_result.hpp>
#include <fud_status.hpp>
#include <string>
-#include <sys/stat.h>
#include <vector>
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& homeVar, const std::string& envVar, std::string& envValue, const char* backup);
-GetEnvResult getEnvironment();
-
-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;
-}
-
-void setUserConfig(GetsuyomiConfig& config, const ShortcutMap& shortcutMap)
-{
- auto binder = [&](ShortcutList& shortcuts, ActionType action) {
- shortcuts = shortcutListFromSet(shortcutMap.at(action));
- };
-
- binder(config.openFileShortcuts, ActionType::OpenFile);
- binder(config.openDirectoryShortcuts, ActionType::OpenDirectory);
- binder(config.quitShortcuts, ActionType::Quit);
-
- binder(config.settingsShortcuts, ActionType::Configure);
-
- binder(config.nextShortcuts, ActionType::Next);
- binder(config.backShortcuts, ActionType::Back);
-
- binder(config.singlePageShortcuts, ActionType::SinglePage);
- binder(config.dualPageShortcuts, ActionType::DualPage);
- binder(config.mangaPageShortcuts, ActionType::MangaPage);
-}
-
-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);
- }
-
- auto configFileName = std::filesystem::path(config.configHome).append("config.lua");
- auto shortcutMap = Shortcuts::fromUserConfig(configFileName);
-
- setUserConfig(config, shortcutMap);
-
- return GetConfigResult::okay(config);
-}
GetsuyomiApp::GetsuyomiApp() : QMainWindow(nullptr), m_getsuyomi{new Getsuyomi(this)}
{
@@ -127,6 +44,9 @@ FudStatus GetsuyomiApp::setup()
constexpr int minimumHeight = 480;
setMinimumSize(minimumWidth, minimumHeight);
+ /* TODO: Wire up a proper status bar */
+ statusBar()->addWidget(new QLabel("Status bar"));
+
show();
return FudStatus::Success;
@@ -146,6 +66,9 @@ void GetsuyomiApp::createActions()
m_settingsAction = new QAction(QIcon("resources/gear.svg"), tr("&Settings"), this);
connect(m_settingsAction, &QAction::triggered, this, &GetsuyomiApp::configure);
+ m_aboutApp = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::HelpAbout), tr("&About"), this);
+ connect(m_aboutApp, &QAction::triggered, this, &GetsuyomiApp::aboutApp);
+
m_nextAction = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::GoNext), tr("Next"), this);
connect(m_nextAction, &QAction::triggered, this, &GetsuyomiApp::next);
@@ -207,8 +130,11 @@ void GetsuyomiApp::createMenus()
m_fileMenu->addAction(m_openDirectory);
m_fileMenu->addAction(m_quitAction);
- m_SettingsMenu = menuBar()->addMenu(tr("&Settings"));
- m_SettingsMenu->addAction(m_settingsAction);
+ m_settingsMenu = menuBar()->addMenu(tr("&Settings"));
+ m_settingsMenu->addAction(m_settingsAction);
+
+ m_helpMenu = menuBar()->addMenu(tr("&Help"));
+ m_helpMenu->addAction(m_aboutApp);
}
void GetsuyomiApp::createToolBar()
@@ -305,9 +231,19 @@ void GetsuyomiApp::configure()
const auto& shortcuts = settings.shortcuts();
setUserConfig(m_config, shortcuts.shortcutMap());
bindShortcuts();
+ auto saveStatus = shortcuts.save(m_config.configFilename);
+ if (saveStatus != FudStatus::Success) {
+ qWarning("Failed to save shortcuts.");
+ }
}
}
+void GetsuyomiApp::aboutApp()
+{
+ /* TODO: proper implementation */
+ QMessageBox::about(this, "About Getsuyomi", "A comic book reading app.");
+}
+
void GetsuyomiApp::next()
{
m_getsuyomi->next();
@@ -366,58 +302,4 @@ void GetsuyomiApp::writeSettings()
settings.sync();
}
-std::optional<std::string> getEnvVar(const char* envVar)
-{
- const QByteArray defaultArray{};
- if (envVar == nullptr) {
- return std::nullopt;
- }
- QByteArray varArray = qgetenv(envVar);
- if (varArray == defaultArray) {
- return std::nullopt;
- }
- return varArray.toStdString();
-}
-
-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) {
- envValuePath.append(backup);
- } else {
- envValuePath = *envValueOpt;
- }
- envValuePath.append(AppName);
- envValue = envValuePath;
- // qDebug("%s is %s", envVar.c_str(), envValue.c_str());
-}
-
-GetEnvResult getEnvironment()
-{
- GetsuyomiConfig config{};
-
- auto homeOpt = getEnvVar(HOME);
- if (homeOpt == std::nullopt || homeOpt->length() == 0) {
- qCritical("Error getting home");
- return GetEnvResult::error(FudStatus::Failure);
- }
- config.home = *homeOpt;
- // qDebug("Home is %s", config.home.c_str());
-
- /* 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(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(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(config.home, XdgStateHome, config.stateHome, ".local/state");
-
- return GetEnvResult::okay(config);
-}
-
} // namespace getsuyomi
diff --git a/src/main_window.hpp b/src/main_window.hpp
index e5363a2..9f72f0a 100644
--- a/src/main_window.hpp
+++ b/src/main_window.hpp
@@ -1,7 +1,6 @@
#ifndef MAIN_WINDOW_HPP
#define MAIN_WINDOW_HPP
-#include "archive.hpp"
#include "getsuyomi.hpp"
#include "config.hpp"
@@ -10,12 +9,6 @@
namespace getsuyomi {
-constexpr const char* AppVendor = "libfud";
-
-constexpr const char* AppName = "getsuyomi";
-
-constexpr const char* AppVersionString = "1.0.0";
-
class GetsuyomiApp : public QMainWindow {
Q_OBJECT
@@ -47,6 +40,8 @@ class GetsuyomiApp : public QMainWindow {
QAction* m_settingsAction{nullptr};
+ QAction* m_aboutApp{nullptr};
+
QAction* m_nextAction{nullptr};
QAction* m_backAction{nullptr};
@@ -56,8 +51,8 @@ class GetsuyomiApp : public QMainWindow {
QActionGroup* m_setPageLayoutGroup{nullptr};
QMenu* m_fileMenu{nullptr};
- QMenu* m_SettingsMenu{nullptr};
- QMenu* m_HelpMenu{nullptr};
+ QMenu* m_settingsMenu{nullptr};
+ QMenu* m_helpMenu{nullptr};
QToolBar* m_toolBar{nullptr};
QString m_lastOpenedDirectory{};
@@ -69,6 +64,8 @@ class GetsuyomiApp : public QMainWindow {
void configure();
+ void aboutApp();
+
void readSettings();
void writeSettings();
void closeEvent(QCloseEvent* event) override;
diff --git a/src/settings.cpp b/src/settings.cpp
new file mode 100644
index 0000000..abd53c1
--- /dev/null
+++ b/src/settings.cpp
@@ -0,0 +1,167 @@
+#include "settings.hpp"
+
+namespace getsuyomi {
+
+using fud::FudStatus;
+
+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;
+ }
+
+ auto containerLayout = new QHBoxLayout();
+ auto* columnLayout = new QVBoxLayout();
+ size_t counter{0};
+ constexpr size_t maxEntriesPerColumn{4};
+
+ for (const auto& action : m_shortcuts.actions()) {
+ auto* collector = new ShortcutCollector(this, action, m_shortcuts);
+ columnLayout->addWidget(collector);
+ counter++;
+ if (counter % (maxEntriesPerColumn + 1) == 0) {
+ containerLayout->addLayout(columnLayout);
+ columnLayout = new QVBoxLayout();
+ }
+ }
+
+ if (columnLayout->count() > 0) {
+ containerLayout->addLayout(columnLayout);
+ }
+
+ layout->addLayout(containerLayout);
+
+ auto* dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+ connect(dialogButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(dialogButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ layout->addWidget(dialogButtonBox);
+
+ setLayout(layout);
+}
+
+bool Settings::valid() const
+{
+ return m_shortcuts.valid();
+}
+
+const Shortcuts& Settings::shortcuts() const
+{
+ return m_shortcuts;
+}
+
+ShortcutCollector::ShortcutCollector(QWidget* parent, ActionType action, Shortcuts& shortcuts) :
+ QWidget(parent), m_action{action}, m_shortcuts{shortcuts}, m_bindings{}
+{
+ m_layout = new QVBoxLayout();
+ auto headerLayout = new QHBoxLayout();
+ auto* name = new QLabel(actionTypeToString(m_action));
+ headerLayout->addWidget(name);
+ m_shortcutEditor = new QKeySequenceEdit(this);
+ m_shortcutEditor->setMaximumSequenceLength(1);
+ connect(m_shortcutEditor, &QKeySequenceEdit::editingFinished, this, &ShortcutCollector::checkBinding);
+ headerLayout->addWidget(m_shortcutEditor);
+
+ m_acceptButton = new QPushButton("Accept", this);
+ m_acceptButton->setEnabled(false);
+ connect(m_acceptButton, &QPushButton::clicked, this, &ShortcutCollector::addBinding);
+ headerLayout->addWidget(m_acceptButton);
+ headerLayout->addStretch();
+
+ m_layout->addLayout(headerLayout);
+
+ auto shortcutOptions = m_shortcuts.shortcuts(m_action);
+ if (shortcutOptions != std::nullopt) {
+ for (const auto& binding : *shortcutOptions) {
+ createBinding(binding);
+ }
+ } else {
+ qWarning("No shortcuts found for %s", actionTypeToString(action));
+ }
+
+ setLayout(m_layout);
+}
+
+void ShortcutCollector::createBinding(QKeySequence binding)
+{
+ auto displayItem = new ShortcutDisplay(this, binding);
+ m_bindings[binding] = displayItem;
+ m_layout->addWidget(displayItem);
+ connect(displayItem, &ShortcutDisplay::removeClicked, this, &ShortcutCollector::removeBinding);
+}
+
+void ShortcutCollector::checkBinding()
+{
+ auto keySequence = m_shortcutEditor->keySequence();
+ if (keySequence == QKeySequence::UnknownKey) {
+ m_acceptButton->setEnabled(false);
+ return;
+ }
+
+ m_acceptButton->setEnabled(not m_shortcuts.contains(keySequence));
+}
+
+void ShortcutCollector::addBinding()
+{
+ auto keySequence = m_shortcutEditor->keySequence();
+ if (keySequence == QKeySequence::UnknownKey) {
+ qWarning("Invalid state - can't accept unknown key");
+ return;
+ } else if (m_shortcuts.contains(keySequence)) {
+ qWarning("Shortcut %s already bound", qPrintable(keySequence.toString()));
+ return;
+ }
+ auto result = m_shortcuts.bind(m_action, keySequence);
+ if (result != FudStatus::Success) {
+ qCritical("Error binding %s to action %s", qPrintable(keySequence.toString()), actionTypeToString(m_action));
+ return;
+ }
+ if (m_bindings.contains(keySequence)) {
+ qWarning(
+ "binding %s to action %s already exists",
+ qPrintable(keySequence.toString()),
+ actionTypeToString(m_action));
+ m_layout->removeWidget(m_bindings[keySequence]);
+ delete m_bindings[keySequence];
+ }
+
+ createBinding(keySequence);
+ m_shortcutEditor->clear();
+}
+
+void ShortcutCollector::removeBinding(QKeySequence binding)
+{
+ auto result = m_shortcuts.remove(binding);
+ if (result == FudStatus::NotFound) {
+ qWarning("binding %s not found", qPrintable(binding.toString()));
+ } else if (result != FudStatus::Success) {
+ qWarning("error removing binding %s: %s", qPrintable(binding.toString()), FudStatusToString(result));
+ }
+ auto bindingHandle = m_bindings.extract(binding);
+ if (bindingHandle) {
+ m_layout->removeWidget(bindingHandle.mapped());
+ delete bindingHandle.mapped();
+ } else {
+ qWarning("Could not remove widget!");
+ }
+ checkBinding();
+}
+
+ShortcutDisplay::ShortcutDisplay(QWidget* parent, QKeySequence shortcut) : QWidget{parent}, m_binding{shortcut}
+{
+ auto layout = new QHBoxLayout();
+ layout->addWidget(new QLabel(shortcut.toString()));
+ auto* deleteButton = new QPushButton("Delete", this);
+ connect(deleteButton, &QPushButton::clicked, this, &ShortcutDisplay::removeOnClicked);
+ layout->addWidget(deleteButton);
+ setLayout(layout);
+}
+
+void ShortcutDisplay::removeOnClicked()
+{
+ emit removeClicked(m_binding);
+}
+
+}
diff --git a/src/settings.hpp b/src/settings.hpp
new file mode 100644
index 0000000..54e3486
--- /dev/null
+++ b/src/settings.hpp
@@ -0,0 +1,80 @@
+#ifndef GETSUYOMI_SETTINGS_HPP
+#define GETSUYOMI_SETTINGS_HPP
+
+#include <QtWidgets>
+#include <map>
+#include <optional>
+#include <qkeysequence.h>
+#include <qlist.h>
+#include <set>
+#include <string>
+#include <filesystem>
+
+#include "config.hpp"
+
+namespace getsuyomi {
+
+class Settings : public QDialog {
+ Q_OBJECT
+ 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;
+};
+
+class ShortcutDisplay;
+
+class ShortcutCollector : public QWidget {
+ Q_OBJECT
+ 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:
+ void createBinding(QKeySequence binding);
+
+ private slots:
+ void checkBinding();
+ void addBinding();
+ void removeBinding(QKeySequence shortcut);
+
+ private:
+ ActionType m_action;
+ Shortcuts& m_shortcuts;
+ std::map<QKeySequence, ShortcutDisplay*> m_bindings;
+ QKeySequenceEdit* m_shortcutEditor{nullptr};
+ QPushButton* m_acceptButton{nullptr};
+ QVBoxLayout* m_layout{nullptr};
+};
+
+class ShortcutDisplay : public QWidget {
+ Q_OBJECT
+ public:
+ ShortcutDisplay(QWidget* parent, QKeySequence shortcut);
+
+ signals:
+ void removeClicked(QKeySequence shortcut);
+
+ private:
+ QKeySequence m_binding;
+ private slots:
+ void removeOnClicked();
+};
+
+} // namespace getsuyomi
+
+#endif