ADD: shortcuts representation.

main
bog 2023-10-03 19:28:47 +02:00
parent 8b0d9d9cdc
commit 83cf9af94e
11 changed files with 470 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.cache
*~*
*\#*
build

11
Makefile Normal file
View File

@ -0,0 +1,11 @@
.PHONY: build tests
build:
meson setup build
meson compile -C build
tests: build
build/twq-tests
install: tests
meson install -C build

41
meson.build Normal file
View File

@ -0,0 +1,41 @@
project('tiwiq',
'cpp',
version: '0.0.0',
default_options: [
'prefix=/usr',
'warning_level=3',
'cpp_std=c++17',
])
twq_lib = static_library('tiwiq',
sources: [
'src/core/KeyMod.cpp',
'src/core/Shortcut.cpp',
],
dependencies: [
dependency('ncursesw')
])
twq_dep = declare_dependency(
link_with: twq_lib,
include_directories: ['src/']
)
executable('twq',
sources: [
'src/main.cpp'
],
dependencies: [
twq_dep
],
install: true)
executable('twq-tests',
sources: [
'tests/main.cpp',
'tests/Shortcut.cpp',
],
dependencies: [
twq_dep,
dependency('catch2')
])

33
src/commons.hpp Normal file
View File

@ -0,0 +1,33 @@
#ifndef kwq_COMMONS_HPP
#define kwq_COMMONS_HPP
#include <cstring>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <fstream>
#include <optional>
#include <memory>
#include <vector>
#include <unordered_map>
#include <stdexcept>
#define GEN_ENUM(X) X
#define GEN_STRING(X) #X
#define TWQ_ENUM(PREFIX, TYPES) \
enum PREFIX { TYPES(GEN_ENUM) }; \
constexpr char const* PREFIX ## Str [] = { TYPES(GEN_STRING) }
#define TWQ_ERROR(NAME) \
struct NAME : public std::runtime_error { \
NAME (std::string const& what): std::runtime_error {what} {} \
}
#define TWQ_ASSERT(COND, MSG) \
if (! COND) { \
std::cerr << MSG << std::endl; \
abort(); \
}
#endif

67
src/core/KeyMod.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "KeyMod.hpp"
namespace twq
{
namespace core
{
/*static*/ KeyMod KeyMod::key(KeyType key_type,
std::vector<ModType> const& mods)
{
return KeyMod { key_type, mods, std::nullopt };
}
/*explicit*/ KeyMod::KeyMod(char text,
std::vector<ModType> const& mods)
: m_key_type { KEY_TEXT }
, m_mods { mods }
, m_text { text }
{
}
/*explicit*/ KeyMod::KeyMod(KeyType key_type,
std::vector<ModType> const& mods,
std::optional<char> text)
: m_key_type { key_type }
, m_mods { mods }
, m_text { text }
{
}
/*virtual*/ KeyMod::~KeyMod()
{
}
bool KeyMod::has_mod(ModType mod_type) const
{
return
std::end(m_mods) !=
std::find(std::begin(m_mods), std::end(m_mods), mod_type);
}
std::string KeyMod::string() const
{
std::stringstream ss;
if (has_mod(MOD_LCTRL))
{
ss << "C-";
}
if (has_mod(MOD_ALT))
{
ss << "A-";
}
if (m_key_type == KEY_TEXT)
{
ss << *m_text;
}
else
{
ss << (KeyTypeStr[m_key_type] + strlen("KEY_"));
}
return ss.str();
}
}
}

56
src/core/KeyMod.hpp Normal file
View File

@ -0,0 +1,56 @@
#ifndef twq_core_KEYMOD_HPP
#define twq_core_KEYMOD_HPP
#include "../commons.hpp"
#define KEY_TYPES(G) \
G(KEY_TEXT), \
G(KEY_UP), \
G(KEY_DOWN), \
G(KEY_LEFT), \
G(KEY_RIGHT), \
G(KEY_COUNT),
#define MOD_TYPES(G)\
G(MOD_LCTRL), \
G(MOD_ALT), \
G(MOD_COUNT),
namespace twq
{
namespace core
{
TWQ_ENUM(KeyType, KEY_TYPES);
TWQ_ENUM(ModType, MOD_TYPES);
class KeyMod
{
public:
static KeyMod key(KeyType key_type,
std::vector<ModType> const& mods={});
explicit KeyMod(char text, std::vector<ModType> const& mods={});
explicit KeyMod(KeyType key_type,
std::vector<ModType> const& mods,
std::optional<char> text);
KeyType key_type() const { return m_key_type; }
std::vector<ModType> mods() const { return m_mods; }
std::optional<char> text() const { return m_text; }
bool has_mod(ModType mod_type) const;
std::string string() const;
virtual ~KeyMod();
private:
KeyType m_key_type;
std::vector<ModType> m_mods;
std::optional<char> m_text;
};
}
}
#endif

116
src/core/Shortcut.cpp Normal file
View File

@ -0,0 +1,116 @@
#include "Shortcut.hpp"
#include "src/core/KeyMod.hpp"
namespace twq
{
namespace core
{
/*explicit*/ Shortcut::Shortcut()
{
}
/*explicit*/ Shortcut::Shortcut(std::string const& repr)
{
std::string buffer;
std::vector<ModType> mods;
auto create_keymod = [&](){
if (buffer.find("C-") != std::string::npos)
{
mods.push_back(MOD_LCTRL);
}
if (buffer.find("A-") != std::string::npos)
{
mods.push_back(MOD_ALT);
}
ssize_t k = buffer.size() - 1;
while (k >= 0 && std::isspace(buffer[k]))
{
k--;
}
std::string value;
while (k >= 0
&& !std::isspace(buffer[k])
&& buffer[k] != '-')
{
value = buffer[k] + value;
k--;
}
if (value.size() == 1)
{
return KeyMod {KEY_TEXT, mods, value.front()};
}
else
{
for (size_t i=0; i<KEY_COUNT; i++)
{
std::string val = KeyTypeStr[i] + strlen("KEY_");
if (val == value)
{
return KeyMod {(KeyType) i, mods, std::nullopt};
}
}
throw invalid_shortcut_error {"cannot convert '"
+ repr
+ "' to shortcut."};
}
};
for (size_t i=0; i<repr.size(); i++)
{
char c = repr[i];
if (std::isspace(c))
{
if (!buffer.empty())
{
push(create_keymod());
}
mods.clear();
buffer.clear();
}
else
{
buffer += c;
}
}
if (!buffer.empty())
{
push(create_keymod());
}
}
/*virtual*/ Shortcut::~Shortcut()
{
}
void Shortcut::push(KeyMod const& keymod)
{
m_keymods.push_back(keymod);
}
std::string Shortcut::string() const
{
std::stringstream ss;
std::string sep;
for (auto const& km: m_keymods)
{
ss << sep << km.string();
sep = " ";
}
return ss.str();
}
}
}

28
src/core/Shortcut.hpp Normal file
View File

@ -0,0 +1,28 @@
#ifndef twq_core_SHORTCUT_HPP
#define twq_core_SHORTCUT_HPP
#include "KeyMod.hpp"
namespace twq
{
namespace core
{
TWQ_ERROR(invalid_shortcut_error);
class Shortcut
{
public:
explicit Shortcut();
explicit Shortcut(std::string const& repr);
virtual ~Shortcut();
void push(KeyMod const& keymod);
std::string string() const;
private:
std::vector<KeyMod> m_keymods;
};
}
}
#endif

6
src/main.cpp Normal file
View File

@ -0,0 +1,6 @@
#include <iostream>
int main(int, char**)
{
return 0;
}

106
tests/Shortcut.cpp Normal file
View File

@ -0,0 +1,106 @@
#include <catch2/catch.hpp>
#include "../src/core/Shortcut.hpp"
using namespace twq::core;
class ShortcutTest
{
public:
explicit ShortcutTest() {}
virtual ~ShortcutTest() {}
void test_to_string(std::string const& oracle,
std::vector<KeyMod> keymods)
{
Shortcut sc;
for (auto km: keymods)
{
sc.push(km);
}
INFO("Expected '" << oracle << "' got '" << sc.string() << "'");
REQUIRE(oracle == sc.string());
}
void test_from_string(std::string const& oracle,
std::string const& shortcut)
{
Shortcut sc { shortcut };
INFO("Expected '" << oracle << "' got '" << sc.string() << "'");
REQUIRE(oracle == sc.string());
}
protected:
};
TEST_CASE_METHOD(ShortcutTest, "Shortcut_string")
{
test_to_string("f", {KeyMod {'f'}});
test_to_string("a b c", {
KeyMod {'a'},
KeyMod {'b'},
KeyMod {'c'},
});
test_to_string("C-g", {KeyMod {'g', {MOD_LCTRL}}});
test_to_string("A-g", {KeyMod {'g', {MOD_ALT}}});
test_to_string("C-A-g", {KeyMod {'g', {MOD_LCTRL, MOD_ALT}}});
test_to_string("A-a C-A-b C-c d", {
KeyMod {'a', {MOD_ALT}},
KeyMod {'b', {MOD_LCTRL, MOD_ALT}},
KeyMod {'c', {MOD_LCTRL}},
KeyMod {'d'},
});
test_to_string("UP", {
KeyMod::key(KEY_UP)
});
test_to_string("DOWN", {
KeyMod::key(KEY_DOWN)
});
test_to_string("LEFT", {
KeyMod::key(KEY_LEFT)
});
test_to_string("RIGHT", {
KeyMod::key(KEY_RIGHT)
});
test_to_string("C-UP", {
KeyMod::key(KEY_UP, {MOD_LCTRL})
});
test_to_string("A-LEFT", {
KeyMod::key(KEY_LEFT, {MOD_ALT}),
});
test_to_string("C-A-RIGHT", {
KeyMod::key(KEY_RIGHT, {MOD_LCTRL, MOD_ALT}),
});
test_from_string("", "");
test_from_string("a", "a");
test_from_string("a", " a");
test_from_string("a", "a ");
test_from_string("C-a", "C-a");
test_from_string("C-a", " C-a ");
test_from_string("A-a", "A-a");
test_from_string("A-a", " A-a ");
test_from_string("C-A-a", " A-C-a ");
test_from_string("C-A-a", " C-A-a ");
test_from_string("C-A-a b C-c", " C-A-a b C-c");
test_from_string("C-A-LEFT RIGHT C-UP",
" C-A-LEFT RIGHT C-UP");
REQUIRE_THROWS_AS(Shortcut { "C-A-DUCK" }, invalid_shortcut_error);
}

2
tests/main.cpp Normal file
View File

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>