erm/vendor/github.com/lxn/walk/databinding.go
2021-07-30 23:29:20 +01:00

612 lines
12 KiB
Go

// Copyright 2012 The Walk Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package walk
import (
"errors"
"fmt"
"reflect"
"strings"
"time"
)
var (
errValidationFailed = errors.New("validation failed")
)
type ErrorPresenter interface {
PresentError(err error, widget Widget)
}
type DataBinder struct {
dataSource interface{}
boundWidgets []Widget
properties []Property
property2Widget map[Property]Widget
property2ChangedHandle map[Property]int
rootExpression Expression
path2Expression map[string]Expression
errorPresenter ErrorPresenter
dataSourceChangedPublisher EventPublisher
canSubmitChangedPublisher EventPublisher
submittedPublisher EventPublisher
resetPublisher EventPublisher
autoSubmitDelay time.Duration
autoSubmitTimer *time.Timer
autoSubmit bool
autoSubmitSuspended bool
canSubmit bool
inReset bool
dirty bool
}
func NewDataBinder() *DataBinder {
db := new(DataBinder)
db.rootExpression = &dataBinderRootExpression{db}
return db
}
func (db *DataBinder) AutoSubmit() bool {
return db.autoSubmit
}
func (db *DataBinder) SetAutoSubmit(autoSubmit bool) {
db.autoSubmit = autoSubmit
if autoSubmit {
db.canSubmit = true
}
}
func (db *DataBinder) AutoSubmitDelay() time.Duration {
return db.autoSubmitDelay
}
func (db *DataBinder) SetAutoSubmitDelay(delay time.Duration) {
db.autoSubmitDelay = delay
}
func (db *DataBinder) AutoSubmitSuspended() bool {
return db.autoSubmitSuspended
}
func (db *DataBinder) SetAutoSubmitSuspended(suspended bool) {
if suspended == db.autoSubmitSuspended {
return
}
db.autoSubmitSuspended = suspended
if suspended {
if db.autoSubmitTimer != nil {
db.autoSubmitTimer.Stop()
}
} else {
db.Submit()
}
}
func (db *DataBinder) Submitted() *Event {
return db.submittedPublisher.Event()
}
func (db *DataBinder) DataSource() interface{} {
return db.dataSource
}
func (db *DataBinder) SetDataSource(dataSource interface{}) error {
if kind := reflect.ValueOf(dataSource).Kind(); kind != reflect.Func && kind != reflect.Map && kind != reflect.Slice &&
kind == reflect.ValueOf(db.dataSource).Kind() && dataSource == db.dataSource {
return nil
}
if dataSource != nil {
if t := reflect.TypeOf(dataSource); t.Kind() != reflect.Map && (t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct) {
return newError("dataSource must be pointer to struct or map[string]interface{}")
}
}
db.dataSource = dataSource
db.dataSourceChangedPublisher.Publish()
return nil
}
type dataBinderRootExpression struct {
db *DataBinder
}
func (dbre *dataBinderRootExpression) Value() interface{} {
return dbre.db.dataSource
}
func (dbre *dataBinderRootExpression) Changed() *Event {
return dbre.db.resetPublisher.Event()
}
func (db *DataBinder) DataSourceChanged() *Event {
return db.dataSourceChangedPublisher.Event()
}
func (db *DataBinder) BoundWidgets() []Widget {
return db.boundWidgets
}
func (db *DataBinder) SetBoundWidgets(boundWidgets []Widget) {
for prop, handle := range db.property2ChangedHandle {
prop.Changed().Detach(handle)
}
db.boundWidgets = boundWidgets
db.property2Widget = make(map[Property]Widget)
db.property2ChangedHandle = make(map[Property]int)
for _, widget := range boundWidgets {
widget := widget
for _, prop := range widget.AsWindowBase().name2Property {
prop := prop
if _, ok := prop.Source().(string); !ok {
continue
}
db.properties = append(db.properties, prop)
db.property2Widget[prop] = widget
db.property2ChangedHandle[prop] = prop.Changed().Attach(func() {
db.dirty = true
if db.autoSubmit && !db.autoSubmitSuspended {
if db.autoSubmitDelay > 0 {
if db.autoSubmitTimer == nil {
db.autoSubmitTimer = time.AfterFunc(db.autoSubmitDelay, func() {
widget.Synchronize(func() {
db.Submit()
})
})
} else {
db.autoSubmitTimer.Reset(db.autoSubmitDelay)
}
} else {
v := reflect.ValueOf(db.dataSource)
field := db.fieldBoundToProperty(v, prop)
if field == nil {
return
}
if err := db.submitProperty(prop, field); err != nil {
return
}
db.submittedPublisher.Publish()
}
} else {
if !db.inReset {
db.validateProperties()
}
}
})
}
}
}
func (db *DataBinder) Expression(path string) Expression {
if db.path2Expression == nil {
db.path2Expression = make(map[string]Expression)
}
if prop, ok := db.path2Expression[path]; ok {
return prop
}
expr := NewReflectExpression(db.rootExpression, path)
db.path2Expression[path] = expr
return expr
}
func (db *DataBinder) validateProperties() {
var hasError bool
for _, prop := range db.properties {
validator := prop.Validator()
if validator == nil {
continue
}
err := validator.Validate(prop.Get())
if err != nil {
hasError = true
}
if db.errorPresenter != nil {
widget := db.property2Widget[prop]
db.errorPresenter.PresentError(err, widget)
}
}
if hasError == db.canSubmit {
db.canSubmit = !hasError
db.canSubmitChangedPublisher.Publish()
}
}
func (db *DataBinder) ErrorPresenter() ErrorPresenter {
return db.errorPresenter
}
func (db *DataBinder) SetErrorPresenter(ep ErrorPresenter) {
db.errorPresenter = ep
}
func (db *DataBinder) CanSubmit() bool {
return db.canSubmit
}
func (db *DataBinder) CanSubmitChanged() *Event {
return db.canSubmitChangedPublisher.Event()
}
func (db *DataBinder) Reset() error {
db.inReset = true
defer func() {
db.inReset = false
}()
if err := db.forEach(func(prop Property, field DataField) error {
if f64, ok := prop.Get().(float64); ok {
switch v := field.Get().(type) {
case float32:
f64 = float64(v)
case float64:
f64 = v
case int:
f64 = float64(v)
case int8:
f64 = float64(v)
case int16:
f64 = float64(v)
case int32:
f64 = float64(v)
case int64:
f64 = float64(v)
case uint:
f64 = float64(v)
case uint8:
f64 = float64(v)
case uint16:
f64 = float64(v)
case uint32:
f64 = float64(v)
case uint64:
f64 = float64(v)
case uintptr:
f64 = float64(v)
default:
return newError(fmt.Sprintf("Field '%s': Can't convert %T to float64.", prop.Source().(string), field.Get()))
}
if err := prop.Set(f64); err != nil {
return err
}
} else {
if err := prop.Set(field.Get()); err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
db.validateProperties()
db.dirty = false
db.resetPublisher.Publish()
return nil
}
func (db *DataBinder) ResetFinished() *Event {
return db.resetPublisher.Event()
}
func (db *DataBinder) Submit() error {
if !db.CanSubmit() {
return errValidationFailed
}
if err := db.forEach(func(prop Property, field DataField) error {
return db.submitProperty(prop, field)
}); err != nil {
return err
}
db.dirty = false
db.submittedPublisher.Publish()
return nil
}
func (db *DataBinder) Dirty() bool {
return db.dirty
}
func (db *DataBinder) submitProperty(prop Property, field DataField) error {
if !field.CanSet() {
// FIXME: handle properly
return nil
}
value := prop.Get()
if value == nil {
if _, ok := db.property2Widget[prop].(*RadioButton); ok {
return nil
}
return field.Set(field.Zero())
}
if err, ok := value.(error); ok {
return err
}
return field.Set(value)
}
func (db *DataBinder) forEach(f func(prop Property, field DataField) error) error {
dsv := reflect.ValueOf(db.dataSource)
if dsv.Kind() == reflect.Ptr && dsv.IsNil() {
return nil
}
for _, prop := range db.properties {
// if widget := db.property2Widget[prop]; !widget.Visible() {
// continue
// }
field := db.fieldBoundToProperty(dsv, prop)
if field == nil {
continue
}
if err := f(prop, field); err != nil {
return err
}
}
return nil
}
func (db *DataBinder) fieldBoundToProperty(v reflect.Value, prop Property) DataField {
if db.dataSource == nil {
return nilField{prop: prop}
}
source, ok := prop.Source().(string)
if !ok || source == "" {
return nil
}
f, err := dataFieldFromPath(v, source)
if err != nil {
panic(fmt.Sprintf("invalid source '%s'", source))
}
return f
}
func validateBindingMemberSyntax(member string) error {
// FIXME
return nil
}
type DataField interface {
CanSet() bool
Get() interface{}
Set(interface{}) error
Zero() interface{}
}
func dataFieldFromPath(root reflect.Value, path string) (DataField, error) {
parent, value, err := reflectValueFromPath(root, path)
if err != nil {
return nil, err
}
// convert to DataField
if i, ok := value.Interface().(DataField); ok {
return i, nil
}
return &reflectField{parent: parent, value: value, key: path[strings.LastIndexByte(path, '.')+1:]}, nil
}
func reflectValueFromPath(root reflect.Value, path string) (parent, value reflect.Value, err error) {
fullPath := path
value = root
for path != "" {
var name string
name, path = nextPathPart(path)
var p reflect.Value
for value.Kind() == reflect.Interface || value.Kind() == reflect.Ptr {
p = value
value = value.Elem()
}
switch value.Kind() {
case reflect.Map:
parent = value
value = value.MapIndex(reflect.ValueOf(name))
case reflect.Struct:
parent = value
var fun reflect.Value
// Try as field first.
if f := value.FieldByName(name); f.IsValid() {
switch f.Kind() {
case reflect.Func:
fun = f
case reflect.Interface:
if fn := f.Elem(); fn.Kind() == reflect.Func {
fun = fn
} else {
value = f
}
default:
value = f
}
} else {
// No field, so let's see if we got a method.
if p.IsValid() {
// Try pointer receiver first.
fun = p.MethodByName(name)
}
if !fun.IsValid() {
// No pointer, try directly.
fun = value.MethodByName(name)
}
if !fun.IsValid() {
return parent, value, fmt.Errorf("bad member: '%s', path: '%s'", path, fullPath)
}
}
if fun.IsValid() {
// We assume it takes no args and returns one mandatory value plus
// maybe an error.
rvs := fun.Call(nil)
switch len(rvs) {
case 1:
value = rvs[0]
case 2:
rv2 := rvs[1].Interface()
if err, ok := rv2.(error); ok {
return parent, value, err
} else if rv2 != nil {
return parent, value, fmt.Errorf("Second method return value must implement error.")
}
value = rvs[0]
default:
return parent, value, fmt.Errorf("Method must return a value plus optionally an error: %s", name)
}
}
}
}
return parent, value, nil
}
func nextPathPart(p string) (next, remaining string) {
for i, r := range p {
if r == '.' {
return p[:i], p[i+1:]
}
}
return p, ""
}
type nilField struct {
prop Property
}
func (nilField) CanSet() bool {
return false
}
func (f nilField) Get() interface{} {
return f.Zero()
}
func (nilField) Set(interface{}) error {
return nil
}
func (f nilField) Zero() interface{} {
return reflect.Zero(reflect.TypeOf(f.prop.Get())).Interface()
}
type reflectField struct {
parent reflect.Value
value reflect.Value
key string
}
func (f *reflectField) CanSet() bool {
if f.parent.IsValid() && f.parent.Kind() == reflect.Map {
return true
}
return f.value.CanSet()
}
func (f *reflectField) Get() interface{} {
return f.value.Interface()
}
func (f *reflectField) Set(value interface{}) error {
if f.parent.IsValid() && f.parent.Kind() == reflect.Map {
f.parent.SetMapIndex(reflect.ValueOf(f.key), reflect.ValueOf(value))
return nil
}
if f64, ok := value.(float64); ok {
switch f.value.Kind() {
case reflect.Float32, reflect.Float64:
f.value.SetFloat(f64)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
f.value.SetInt(int64(f64))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
f.value.SetUint(uint64(f64))
default:
return newError(fmt.Sprintf("Can't convert float64 to %s.", f.value.Type().Name()))
}
return nil
}
f.value.Set(reflect.ValueOf(value))
return nil
}
func (f *reflectField) Zero() interface{} {
return reflect.Zero(f.value.Type()).Interface()
}