612 lines
12 KiB
Go
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()
|
|
}
|