mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 08:25:32 +00:00
This commit update the state object to have a deep copy for the action_struct member. The tests have been modified but have a few issues however this branch is out of date with the refacted master branch for unit tests. This commit gets the main change in a future commit will update the tests.
216 lines
6.5 KiB
C++
216 lines
6.5 KiB
C++
/*
|
|
Copyright: © 2023 SIL International.
|
|
Description: Implementation of the action API functions using internal
|
|
data structures and functions.
|
|
Create Date: 23 Oct 2023
|
|
Authors: Marc Durdin (MCD)
|
|
History: 23 Oct 2023 - MCD - Initial implementation from #9720
|
|
*/
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <memory>
|
|
|
|
|
|
#include "action.hpp"
|
|
#include "state.hpp"
|
|
#include "option.hpp"
|
|
|
|
bool km::core::action_item_list_to_actions_object(
|
|
km_core_action_item const *action_items,
|
|
km_core_actions *actions
|
|
) {
|
|
assert(action_items != nullptr);
|
|
assert(actions != nullptr);
|
|
if(action_items == nullptr || actions == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
|
|
// Set actions default values
|
|
std::vector<km_core_context_item> output;
|
|
std::vector<km_core_option_item> options;
|
|
actions->code_points_to_delete = 0;
|
|
actions->do_alert = KM_CORE_FALSE;
|
|
actions->emit_keystroke = KM_CORE_FALSE;
|
|
actions->new_caps_lock_state = KM_CORE_CAPS_UNCHANGED;
|
|
|
|
// deleted_context data will be set in state::apply_actions_and_merge_app_context
|
|
// because it needs access to the state's app context
|
|
actions->deleted_context = nullptr;
|
|
|
|
// Clear output pointers, will be set later once we have sizes
|
|
actions->output = nullptr;
|
|
actions->persist_options = nullptr;
|
|
|
|
for (; action_items->type != KM_CORE_IT_END; ++action_items) {
|
|
assert(action_items->type < KM_CORE_IT_MAX_TYPE_ID);
|
|
|
|
switch(action_items->type) {
|
|
case KM_CORE_IT_ALERT:
|
|
actions->do_alert = KM_CORE_TRUE;
|
|
break;
|
|
case KM_CORE_IT_BACK:
|
|
switch(action_items->backspace.expected_type) {
|
|
case KM_CORE_BT_UNKNOWN:
|
|
// this is equivalent to emit_keystroke, because the only time we
|
|
// are allowed to do an unknown bksp is when a bksp is passed in
|
|
actions->emit_keystroke = KM_CORE_TRUE;
|
|
break;
|
|
case KM_CORE_BT_CHAR:
|
|
if(output.empty()) {
|
|
actions->code_points_to_delete++;
|
|
} else {
|
|
#ifndef NDEBUG
|
|
auto last_context_item = output.back();
|
|
#endif
|
|
output.pop_back();
|
|
assert(last_context_item.type == KM_CORE_CT_CHAR);
|
|
assert(last_context_item.character == action_items->backspace.expected_value);
|
|
}
|
|
break;
|
|
case KM_CORE_BT_MARKER:
|
|
if(output.empty()) {
|
|
// deleting a marker has no effect on the application
|
|
} else {
|
|
#ifndef NDEBUG
|
|
auto last_context_item = output.back();
|
|
#endif
|
|
output.pop_back();
|
|
assert(last_context_item.type == KM_CORE_CT_MARKER);
|
|
assert(last_context_item.marker == action_items->backspace.expected_value);
|
|
}
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
break;
|
|
case KM_CORE_IT_CAPSLOCK:
|
|
actions->new_caps_lock_state = action_items->capsLock ? KM_CORE_CAPS_ON : KM_CORE_CAPS_OFF;
|
|
break;
|
|
case KM_CORE_IT_CHAR:
|
|
output.push_back({KM_CORE_CT_CHAR,{0},{action_items->character}});
|
|
break;
|
|
case KM_CORE_IT_EMIT_KEYSTROKE:
|
|
actions->emit_keystroke = KM_CORE_TRUE;
|
|
break;
|
|
case KM_CORE_IT_INVALIDATE_CONTEXT:
|
|
// no-op
|
|
break;
|
|
case KM_CORE_IT_MARKER:
|
|
output.push_back({KM_CORE_CT_MARKER,{0},{action_items->marker}});
|
|
break;
|
|
case KM_CORE_IT_PERSIST_OPT:
|
|
{
|
|
// TODO: lowpri: replace existing item if already present in options vector?
|
|
km::core::option opt(static_cast<km_core_option_scope>(action_items->option->scope),
|
|
action_items->option->key,
|
|
action_items->option->value
|
|
);
|
|
options.push_back(opt.release()); // hand over memory management of the option item to the action struct
|
|
break;
|
|
}
|
|
default:
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
|
|
// Strip the markers from the output, and convert to an string of UTF-32
|
|
|
|
output.push_back(KM_CORE_CONTEXT_ITEM_END);
|
|
|
|
size_t buf_size;
|
|
|
|
if(context_items_to_utf32(output.data(), nullptr, &buf_size) != KM_CORE_STATUS_OK) {
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<km_core_usv[]> output_usv(new km_core_usv[buf_size]);
|
|
|
|
if(context_items_to_utf32(output.data(), output_usv.get(), &buf_size) != KM_CORE_STATUS_OK) {
|
|
return false;
|
|
}
|
|
|
|
actions->output = output_usv.release();
|
|
|
|
// Create an array of the persisted options
|
|
|
|
options.push_back(KM_CORE_OPTIONS_END);
|
|
actions->persist_options = new km_core_option_item[options.size()];
|
|
std::copy(options.begin(), options.end(), actions->persist_options);
|
|
|
|
// We now have a complete set of actions
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// TODO: this is effectively the inverse of action_item_list_to_actions_object,
|
|
// and perhaps we should consider changing that function to be a member
|
|
// of state also, so that we can move memory management into state?
|
|
bool km::core::state::set_actions(
|
|
km_core_actions const &actions
|
|
) {
|
|
_actions.clear();
|
|
|
|
// number of codepoints (not codeunits!) to delete from app context.
|
|
|
|
for(unsigned int i = 0; i < actions.code_points_to_delete; i++) {
|
|
_actions.push_backspace(KM_CORE_BT_CHAR, 0); // expected value is not known
|
|
}
|
|
|
|
for(auto output = actions.output; *output; output++) {
|
|
_actions.push_character(*output);
|
|
}
|
|
|
|
for(auto opt = actions.persist_options; opt->scope; opt++) {
|
|
km::core::option opt0(static_cast<km_core_option_scope>(opt->scope), opt->key, opt->value);
|
|
_actions.push_persist(opt0);
|
|
}
|
|
|
|
if(actions.do_alert) {
|
|
_actions.push_alert();
|
|
}
|
|
|
|
if(actions.emit_keystroke) {
|
|
_actions.push_emit_keystroke();
|
|
}
|
|
|
|
if(actions.new_caps_lock_state != KM_CORE_CAPS_UNCHANGED) {
|
|
_actions.push_capslock(actions.new_caps_lock_state == KM_CORE_CAPS_ON);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
using namespace km::core;
|
|
|
|
namespace {
|
|
|
|
km_core_usv * duplicate_km_core_usv(const km_core_usv *src) {
|
|
if (!src) {
|
|
return nullptr;
|
|
}
|
|
size_t len = 0;
|
|
while (src[len]) {
|
|
++len;
|
|
}
|
|
km_core_usv *result = new km_core_usv[len + 1];
|
|
std::copy(src, src + len + 1, result);
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
km_core_actions km::core::clone_actions_object(km_core_actions const &src) {
|
|
km_core_actions result;
|
|
memset(&result, 0, sizeof(result));
|
|
result.code_points_to_delete = src.code_points_to_delete;
|
|
result.do_alert = src.do_alert;
|
|
result.emit_keystroke = src.emit_keystroke;
|
|
result.new_caps_lock_state = src.new_caps_lock_state;
|
|
result.deleted_context = duplicate_km_core_usv(src.deleted_context);
|
|
result.output = duplicate_km_core_usv(src.output);
|
|
result.persist_options = clone_options(src.persist_options);
|
|
return result;
|
|
}
|