/**
* File: binding_context_awtk.c
* Author: AWTK Develop Team
* Brief: binding context awtk
*
* Copyright (c) 2019 - 2025 Guangzhou ZHIYUAN Electronics Co.,Ltd.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* License file for more details.
*
*/
/**
* History:
* ================================================================
* 2019-01-30 Li XianJing <
[email protected]> created
*
*/
#include "tkc/str.h"
#include "tkc/mem.h"
#include "tkc/utils.h"
#include "tkc/int_str.h"
#include "tkc/darray.h"
#include "tkc/tokenizer.h"
#include "awtk_global.h"
#include "base/idle.h"
#include "base/enums.h"
#include "base/widget.h"
#include "base/window.h"
#include "base/window_manager.h"
#include "mvvm/base/data_binding.h"
#include "mvvm/base/command_binding.h"
#include "mvvm/base/items_binding.h"
#include "mvvm/base/condition_binding.h"
#include "mvvm/base/view_model_dummy.h"
#include "mvvm/base/view_model_array.h"
#include "mvvm/base/view_model_factory.h"
#include "mvvm/base/view_model_compositor.h"
#include "mvvm/base/custom_binder.h"
#include "mvvm/awtk/ui_loader_mvvm.h"
#include "mvvm/awtk/mvvm_awtk.h"
#include "mvvm/awtk/binding_context_awtk.h"
#define STR_SUB_VIEW_MODEL "sub_view_model:"
#define STR_SUB_VIEW_MODEL_ARRAY "sub_view_model_array:"
#define IS_COMPOSITOR_VIEW_MODEL(type) strchr(type, '+') != NULL
static view_model_t* binding_context_awtk_create_one_view_model(view_model_t* parent,
const char* type,
navigator_request_t* req) {
view_model_t* view_model = NULL;
if (tk_str_start_with(type, STR_SUB_VIEW_MODEL)) {
const char* name = strchr(type, ':');
return_value_if_fail(name != NULL, NULL);
view_model = view_model_create_sub_view_model(parent, name + 1);
} else if (tk_str_start_with(type, STR_SUB_VIEW_MODEL_ARRAY)) {
const char* name = strchr(type, ':');
return_value_if_fail(name != NULL, NULL);
view_model = view_model_create_sub_view_model_array(parent, name + 1);
} else {
if (req != NULL) {
req->parent_view_model = TK_OBJECT(parent);
}
view_model = view_model_factory_create_model(type, req);
}
return view_model;
}
static view_model_t* binding_context_awtk_create_view_model(view_model_t* parent, const char* type,
navigator_request_t* req) {
view_model_t* view_model = NULL;
if (type != NULL) {
if (IS_COMPOSITOR_VIEW_MODEL(type)) {
tokenizer_t t;
view_model_t* compositor = view_model_compositor_create(req);
return_value_if_fail(compositor != NULL, NULL);
tokenizer_init(&t, type, strlen(type), "+");
while (tokenizer_has_more(&t)) {
const char* type1 = tokenizer_next(&t);
view_model_t* vm = binding_context_awtk_create_one_view_model(parent, type1, req);
if (vm != NULL) {
if (view_model_compositor_add(compositor, vm) != RET_OK) {
log_warn("view_model_compositor_add failed\n");
TK_OBJECT_UNREF(vm);
}
} else {
log_warn("create \"%s\" view_model failed\n", type1);
}
}
tokenizer_deinit(&t);
view_model = compositor;
} else {
view_model = binding_context_awtk_create_one_view_model(parent, type, req);
}
} else {
if (req != NULL) {
req->parent_view_model = TK_OBJECT(parent);
}
view_model = view_model_factory_create_model_generic(type, req);
}
if (view_model == NULL) {
if (type != NULL) {
log_warn("%s not found view_model \"%s\"\n", __FUNCTION__, type);
}
view_model = view_model_dummy_create(req);
}
return view_model;
}
static ret_t visit_data_binding_update_error_of(void* ctx, const void* data) {
data_binding_t* rule = DATA_BINDING((void*)data);
data_binding_t* trigger_rule = DATA_BINDING(ctx);
view_model_t* view_model = BINDING_RULE_VIEW_MODEL(trigger_rule);
return_value_if_fail(rule != NULL, RET_BAD_PARAMS);
return_value_if_fail(trigger_rule != NULL, RET_BAD_PARAMS);
return_value_if_fail(view_model != NULL, RET_BAD_PARAMS);
if (tk_str_start_with(rule->path, DATA_BINDING_ERROR_OF)) {
const char* path = rule->path + sizeof(DATA_BINDING_ERROR_OF) - 1;
if (tk_str_eq(path, trigger_rule->path)) {
widget_t* widget = WIDGET(BINDING_RULE_WIDGET(rule));
widget_set_tr_text(widget, view_model->last_error.str);
}
}
return RET_OK;
}
static ret_t widget_visit_data_binding_update_error_of(void* ctx, const void* data) {
darray_t* node = (darray_t*)data;
return_value_if_fail(node != NULL, RET_BAD_PARAMS);
return darray_foreach(node, visit_data_binding_update_error_of, ctx);
}
static ret_t binding_context_update_error_of(data_binding_t* rule) {
binding_context_t* ctx = BINDING_RULE_CONTEXT(rule);
view_model_t* view_model = BINDING_RULE_VIEW_MODEL(rule);
return_value_if_fail(ctx != NULL && view_model != NULL, RET_BAD_PARAMS);
slist_foreach(&(ctx->data_bindings), widget_visit_data_binding_update_error_of, rule);
return RET_OK;
}
static ret_t on_widget_prop_change(void* ctx, event_t* e) {
data_binding_t* rule = DATA_BINDING(ctx);
prop_change_event_t* evt = prop_change_event_cast(e);
return_value_if_fail(evt != NULL && rule != NULL, RET_BAD_PARAMS);
if (tk_str_eq(evt->name, rule->prop)) {
data_binding_set_prop(rule, evt->value);
binding_context_update_error_of(rule);
}
return RET_OK;
}
static ret_t on_widget_value_change(void* ctx, event_t* e) {
value_t v;
widget_t* widget = WIDGET(e->target);
data_binding_t* rule = DATA_BINDING(ctx);
binding_context_t* bctx = BINDING_RULE_CONTEXT(rule);
return_value_if_fail(widget != NULL && rule != NULL && bctx != NULL, RET_BAD_PARAMS);
return_value_if_fail(widget_get_prop(widget, rule->prop, &v) == RET_OK, RET_OK);
if (!bctx->updating_view) {
bctx->updating_view_by_ui = TRUE;
}
data_binding_set_prop(rule, &v);
binding_context_update_error_of(rule);
return RET_OK;
}
static ret_t widget_set_prop_if_diff(widget_t* widget, const char* name, const value_t* v,
bool_t set_force) {
value_t old;
return_value_if_fail(widget != NULL && widget->vt != NULL, RET_BAD_PARAMS);
if (!set_force) {
if (widget->vt->inputable) {
if (tk_str_eq(name, WIDGET_PROP_TEXT) || tk_str_eq(name, WIDGET_PROP_VALUE)) {
value_t inputing;
if (widget_get_prop(widget, WIDGET_PROP_INPUTING, &inputing) == RET_OK) {
if (value_bool(&inputing)) {
log_debug("%s is inputing, skip.\n", widget_get_type(widget));
return RET_OK;
}
}
}
}
value_set_int(&old, 0);
if (widget_get_prop(widget, name, &old) == RET_OK) {
if (value_equal(&old, v)) {
return RET_OK;
}
}
}
return widget_set_prop(widget, name, v);
}
static ret_t binding_context_awtk_update_data(data_binding_t* rule, bool_t force) {
binding_context_t* ctx = BINDING_RULE_CONTEXT(rule);
view_model_t* view_model = BINDING_RULE_VIEW_MODEL(rule);
return_value_if_fail(ctx != NULL && view_model != NULL, RET_BAD_PARAMS);
if (tk_object_is_collection(TK_OBJECT(view_model))) {
uint32_t size = view_model_get_items_size(TK_OBJECT(view_model));
binding_context_t* context = BINDING_RULE_CONTEXT(rule);
uint32_t cursor = binding_context_get_items_cursor_of_rule(context, BINDING_RULE(rule));
/*用于去掉TableView绑定时大量无用警告*/
if (cursor >= size && cursor > 0) {
return RET_OK;
}
}
if (tk_str_start_with(rule->path, DATA_BINDING_ERROR_OF)) {
return RET_OK;
}
if (rule->mode == BINDING_ONE_WAY || rule->mode == BINDING_TWO_WAY ||
(rule->mode == BINDING_ONCE && !(ctx->bound