Skip to content

Commit

Permalink
feat(key_binder): single select a radio group option
Browse files Browse the repository at this point in the history
using toggle, set_option, unset_option commands.

`toggle` behaves like round robin in a radio group no matter which option is
given or whether it is the selected option.
`set_option` also unset other options in the same radio group.
`unset_option` resets radio selection to the default option if the given option
is selected, or does nothing if the given option is deselected.
  • Loading branch information
lotem committed Jun 23, 2022
1 parent 2393851 commit 5e8aca2
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 38 deletions.
20 changes: 10 additions & 10 deletions src/rime/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -387,17 +387,17 @@ void ConcreteEngine::InitializeOptions() {
// reset custom switches
Config* config = schema_->config();
Switches switches(config);
switches.ForEachOption([this](Switches::SwitchOption option) {
if (option.reset_value < 0) // unspecified
return;
if (option.type == Switches::kToggleOption) {
context_->set_option(option.option_name, (option.reset_value != 0));
}
else if (option.type == Switches::kRadioGroup) {
context_->set_option(
option.option_name,
static_cast<int>(option.option_index) == option.reset_value);
switches.FindOption([this](Switches::SwitchOption option) {
if (option.reset_value >= 0) {
if (option.type == Switches::kToggleOption) {
context_->set_option(option.option_name, (option.reset_value != 0));
} else if (option.type == Switches::kRadioGroup) {
context_->set_option(
option.option_name,
static_cast<int>(option.option_index) == option.reset_value);
}
}
return Switches::kContinue;
});
}

Expand Down
63 changes: 59 additions & 4 deletions src/rime/gear/key_binder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <rime/key_table.h>
#include <rime/schema.h>
#include <rime/switcher.h>
#include <rime/switches.h>
#include <rime/gear/key_binder.h>

using namespace std::placeholders;
Expand Down Expand Up @@ -57,29 +58,83 @@ struct KeyBinding {
};

class KeyBindings : public map<KeyEvent,
vector<KeyBinding>> {
vector<KeyBinding>> {
public:
void LoadBindings(const an<ConfigList>& bindings);
void Bind(const KeyEvent& key, const KeyBinding& binding);
};

static void radio_select_option(Context* ctx,
const Switches::SwitchOption& the_option) {
Switches::FindRadioGroupOption(
the_option.the_switch,
[ctx, &the_option](Switches::SwitchOption option) {
bool value = (option.option_index == the_option.option_index);
if (ctx->get_option(option.option_name) != value) {
ctx->set_option(option.option_name, value);
}
return Switches::kContinue;
});
}

static void toggle_option(Engine* engine, const string& option) {
if (!engine)
return;
Context* ctx = engine->context();
ctx->set_option(option, !ctx->get_option(option));
Switches switches(engine->schema()->config());
auto the_option = switches.OptionByName(option);
if (the_option.found() && the_option.type == Switches::kRadioGroup) {
auto selected_option = switches.FindRadioGroupOption(
the_option.the_switch,
[ctx](Switches::SwitchOption option) {
return ctx->get_option(option.option_name)
? Switches::kFound
: Switches::kContinue;
});
if (!selected_option.found()) {
// invalid state: none is selected. select the given option.
radio_select_option(ctx, the_option);
return;
}
// cycle through the ratio group and select the next option.
auto next_option = Switches::Cycle(selected_option);
if (next_option.found()) {
radio_select_option(ctx, next_option);
}
} else { // toggle
ctx->set_option(option, !ctx->get_option(option));
}
}

static void set_option(Engine* engine, const string& option) {
if (!engine)
return;
Context* ctx = engine->context();
ctx->set_option(option, 1);
Switches switches(engine->schema()->config());
auto the_option = switches.OptionByName(option);
if (the_option.found() && the_option.type == Switches::kRadioGroup) {
radio_select_option(ctx, the_option);
} else {
ctx->set_option(option, 1);
}
}

static void unset_option(Engine* engine, const string& option) {
if (!engine)
return;
Context* ctx = engine->context();
ctx->set_option(option, 0);
Switches switches(engine->schema()->config());
auto the_option = switches.OptionByName(option);
if (the_option.found() && the_option.type == Switches::kRadioGroup) {
if (ctx->get_option(option)) {
auto default_option = Switches::Reset(the_option);
if (default_option.found()) {
radio_select_option(ctx, default_option);
}
}
} else {
ctx->set_option(option, 0);
}
}

static void select_schema(Engine* engine, const string& schema) {
Expand Down
103 changes: 80 additions & 23 deletions src/rime/switches.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,54 @@

namespace rime {

void Switches::ForEachOption(function<void (SwitchOption option)> callback) {
Switches::SwitchOption Switches::FindOption(
function<FindResult (SwitchOption option)> callback) {
auto switches = (*config_)["switches"];
if (!switches.IsList())
return;
return {};
for (size_t i = 0; i < switches.size(); ++i) {
auto item = switches[i];
if (!item.IsMap())
continue;
auto the_switch = As<ConfigMap>(*item);
auto reset = item["reset"];
int reset_value = reset.IsValue() ? reset.ToInt() : -1;
auto name = item["name"];
if (name.IsValue()) {
callback(
SwitchOption{
As<ConfigMap>(*item),
kToggleOption,
name.ToString(),
reset_value,
i
});
SwitchOption option{
the_switch,
kToggleOption,
name.ToString(),
reset_value,
i,
};
if (callback(option) == kFound)
return option;
continue;
}
auto options = item["options"];
if (options.IsList()) {
for (size_t j = 0; j < options.size(); ++j) {
callback(
SwitchOption{
As<ConfigMap>(*item),
kRadioGroup,
options[j].ToString(),
reset_value,
i,
j
});
SwitchOption option{
the_switch,
kRadioGroup,
options[j].ToString(),
reset_value,
i,
j,
};
if (callback(option) == kFound)
return option;
}
}
}
return {};
}

Switches::SwitchOption Switches::OptionByName(const string& option_name) {
ForEachOption([&option_name](SwitchOption option) {
if (option.option_name == option_name)
return option;
return FindOption([&option_name](SwitchOption option) {
return option.option_name == option_name ? kFound : kContinue;
});
return {};
}

an<ConfigMap> Switches::ByIndex(size_t switch_index) {
Expand All @@ -60,6 +63,60 @@ an<ConfigMap> Switches::ByIndex(size_t switch_index) {
return As<ConfigMap>(*item);
}

Switches::SwitchOption Switches::Cycle(const SwitchOption& current) {
if (auto options = As<ConfigList>(current.the_switch->Get("options"))) {
size_t next_option_index = (current.option_index + 1) % options->size();
if (next_option_index != current.option_index) {
return {
current.the_switch,
current.type,
options->GetValueAt(next_option_index)->str(),
current.reset_value,
current.switch_index,
next_option_index,
};
}
}
return {};
}

Switches::SwitchOption Switches::Reset(const SwitchOption& current) {
size_t default_state = (current.reset_value >= 0) ? current.reset_value : 0;
if (auto options = As<ConfigList>(current.the_switch->Get("options"))) {
if (default_state >= options->size() ||
default_state == current.option_index)
return {};
return {
current.the_switch,
current.type,
options->GetValueAt(default_state)->str(),
current.reset_value,
current.switch_index,
default_state,
};
}
}

Switches::SwitchOption Switches::FindRadioGroupOption(
an<ConfigMap> the_switch,
function<FindResult (SwitchOption option)> callback) {
if (auto options = As<ConfigList>(the_switch->Get("options"))) {
for (size_t j = 0; j < options->size(); ++j) {
SwitchOption option{
the_switch,
kRadioGroup,
options->GetValueAt(j)->str(),
0, // unknown
0, // unknown
j,
};
if (callback(option) == kFound)
return option;
}
}
return {};
}

an<ConfigValue> Switches::GetStateLabel(an<ConfigMap> the_switch,
size_t state_index) {
if (!the_switch)
Expand Down
16 changes: 15 additions & 1 deletion src/rime/switches.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,28 @@ class Switches {
}
};

void ForEachOption(function<void (SwitchOption option)> callback);
enum FindResult {
kContinue,
kFound,
};

SwitchOption FindOption(function<FindResult (SwitchOption option)> callback);

SwitchOption OptionByName(const string& option_name);

an<ConfigMap> ByIndex(size_t switch_index);

static SwitchOption Cycle(const SwitchOption& option);

static SwitchOption Reset(const SwitchOption& option);

static SwitchOption FindRadioGroupOption(
an<ConfigMap> the_switch,
function<FindResult (SwitchOption option)> callback);

static an<ConfigValue> GetStateLabel(
an<ConfigMap> the_switch, size_t state_index);

an<ConfigValue> GetStateLabel(const string& option_name, int state);

private:
Expand Down

1 comment on commit 5e8aca2

@lotem
Copy link
Member Author

@lotem lotem commented on 5e8aca2 Jun 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closes #555

Please sign in to comment.