// 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() }