#include "config.hpp" #include "luacxx.hpp" #include #include #include namespace getsuyomi { using fud::FudStatus; ShortcutSet shortcutSetFromList(const ShortcutList& shortcutList) { ShortcutSet shortcutSet{}; for (const auto& entry : shortcutList) { shortcutSet.insert(entry); } return shortcutSet; } ShortcutList shortcutListFromSet(const ShortcutSet& shortcutSet) { ShortcutList shortcutList{}; for (const auto& entry : shortcutSet) { shortcutList.push_back(entry); } return shortcutList; } 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)); auto bindResult = bind(action, shortcut); if (bindResult != FudStatus::Success) { qWarning("Error: %s", FudStatusToString(bindResult)); return; } else { qDebug("Bound %s to %s", qPrintable(shortcut.toString()), actionTypeToString(action)); } } } 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 { qWarning("Action %s not found", actionTypeToString(action)); 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; } const ShortcutMap& Shortcuts::shortcutMap() const { return m_actionToShortcuts; } Shortcuts Shortcuts::fromLuaConfig(const std::filesystem::path& configFileName) { Shortcuts shortcuts{}; shortcuts.m_valid = true; LuaContext luaContext{}; if (!luaContext.valid()) { qCritical("Failed to create lua context"); return shortcuts; } auto luaStatus = luaContext.loadFile(configFileName.c_str()); if (luaStatus != FudStatus::Success) { qCritical("Failed to load file in lua %s", fud::FudStatusToString(luaStatus)); return shortcuts; } std::vector actions{ ActionType::OpenFile, ActionType::OpenDirectory, ActionType::Quit, ActionType::Configure, // ActionType::Help, // ActionType::About // ActionType::GotoFirst, ActionType::Next, ActionType::Back, // ActionType::GotoLast, ActionType::SinglePage, ActionType::DualPage, ActionType::MangaPage}; for (auto action : actions) { auto result = luaContext.getGlobalStringArray(actionTypeToString(action)); if (result.isError()) { qWarning( "Failed to get variable %s from lua %s", actionTypeToString(action), fud::FudStatusToString(result.getError())); // return RetType::error(result.getError()); continue; } auto actionBindings = result.getOkay(); for (const auto& shortcut : actionBindings) { QKeySequence keySeq{shortcut.c_str()}; if (keySeq == Qt::Key_unknown) { qCritical("Error: shortcut %s unknown", shortcut.c_str()); continue; } auto bindStatus = shortcuts.bind(action, keySeq); if (bindStatus != FudStatus::Success) { qCritical( "Error: failure to bind to action %s keySeq %s", actionTypeToString(action), shortcut.c_str()); continue; } } } return shortcuts; } 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) { if (errno != EEXIST) { qCritical("Could not load or create user config file %s.", configFileName.c_str()); } } close(fdResult); auto shortcuts = Shortcuts::fromLuaConfig(configFileName); auto binder = [&](ActionType action, QKeySequence keySeq) { auto bindDefaultStatus = shortcuts.bind(action, keySeq); if (bindDefaultStatus != FudStatus::Success) { qDebug("%s already bound", actionTypeToString(action)); } }; binder(ActionType::OpenFile, QKeySequence(QKeySequence::Open)); binder(ActionType::OpenDirectory, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_O)); binder(ActionType::Quit, QKeySequence(QKeySequence::Quit)); binder(ActionType::Configure, QKeySequence(Qt::CTRL | Qt::Key_P)); ShortcutList nextDefaults{QKeySequence{Qt::Key_L}, QKeySequence{Qt::Key_Right}, QKeySequence{Qt::Key_Down}}; for (const auto& binding : nextDefaults) { binder(ActionType::Next, binding); } ShortcutList backDefaults{QKeySequence{Qt::Key_H}, QKeySequence{Qt::Key_Left}, QKeySequence{Qt::Key_Up}}; for (const auto& binding : backDefaults) { binder(ActionType::Back, binding); } binder(ActionType::SinglePage, QKeySequence{Qt::Key_S}); binder(ActionType::DualPage, QKeySequence{Qt::Key_D}); binder(ActionType::MangaPage, QKeySequence{Qt::Key_M}); return shortcuts.shortcutMap(); } 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)}); shortcuts.insert({ActionType::SinglePage, shortcutSetFromList(singlePageShortcuts)}); shortcuts.insert({ActionType::DualPage, shortcutSetFromList(dualPageShortcuts)}); shortcuts.insert({ActionType::MangaPage, shortcutSetFromList(mangaPageShortcuts)}); 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, m_shortcuts); layout->addWidget(collector); } setLayout(layout); } bool Settings::valid() const { return m_shortcuts.valid(); } const Shortcuts& Settings::shortcuts() const { return m_shortcuts; } } // namespace getsuyomi