Select Git revision
input_tab.go
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
input_tab.go 6.35 KiB
// Feb 2022
// Set up a tab for getting Monte Carlo Parameters
// This should return an object that can be embedded in a tabbed page.
//go:build !no_gfx
// +build !no_gfx
package ui
import (
"fmt"
"os"
"strconv"
"strings"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
"example.com/ackley_mc/mc_work"
)
// startrun is what happens when you click on the form's button to start
// a calculation.
func startrun(genParams genParams, form *widget.Form, mcPrm *mcwork.McPrm) {
form.Disable()
go mcWrap(genParams)
}
// fslicestrng makes a nice string from a slice of float32's
func fslicestrng(f []float32) string {
if len(f) == 0 {
return ""
}
s := fmt.Sprintf("%g", f[0])
for i := 1; i < len(f); i++ {
s += fmt.Sprintf(", %g", f[i])
}
return s
}
// str2f32 takes a string and returns a slice of float32's
func s2f32(s string) ([]float32, error) {
y := strings.Split(s, ",")
if len(y) < 1 {
return nil, fmt.Errorf("no fields found")
}
x := make([]float32, len(y))
for i, s := range y {
if r, err := strconv.ParseFloat(strings.TrimSpace(s), 32); err != nil {
return nil, fmt.Errorf("tried to parse %v %v", s, err)
} else {
x[i] = float32(r)
}
}
return x, nil
}
// validateXini checks if we can convert a string to a slice of float32's.
// Unfortunately, fyne calls this function every time the cursor is moved
// in the field. I think this is a little bug on their part.
func validateXini(s string) error {
_, err := s2f32(s)
return err
}
// floatItem gives us a form item, but also a function to refresh the item.
// This gets put on the list of things to do from the reload function.
func floatItem(xAddr *float64, label string) (*widget.FormItem, func()) {
bind := binding.BindFloat(xAddr)
entry := widget.NewEntryWithData(binding.FloatToStringWithFormat(bind, "%.3f"))
fitem := widget.NewFormItem(label, entry)
reloadfunc := func() { _ = bind.Reload() }
return fitem, reloadfunc
}
// intItem does the same wrapping as floatItem, but for ints
func intItem(xAddr *int, label string, htext string) (*widget.FormItem, func()) {
bind := binding.BindInt(xAddr)
entry := widget.NewEntryWithData(binding.IntToString(bind))
iItem := widget.NewFormItem(label, entry)
iItem.HintText = htext
reloadfunc := func() { _ = bind.Reload() }
return iItem, reloadfunc
}
var xCoordHint = "The starting point. A comma-separated list of floats. It is used to set the dimensionality"
// paramBox sets up a screen full of parameters we can adjust.
// It returns the screen and a function to be called to refresh it.
// This is necessary, since there is a button to read parameters from
// a file, which means all the values should be updated.
func paramForm(genParams genParams) *widget.Form {
mcPrm := genParams.mcPrm
iniTmpItem, iniTmpReload := floatItem(&mcPrm.IniTmp, "initial temperature")
fnlTmpItem, fnlTmpReload := floatItem(&mcPrm.FnlTmp, "final temperature")
xDltaItem, xDltaReload := floatItem(&mcPrm.XDlta, "X delta")
xDltaItem.HintText = "Maximum step size in one dimension"
xIniStr := binding.NewString()
_ = xIniStr.Set(fslicestrng(mcPrm.XIni))
xIniEntry := widget.NewEntryWithData(xIniStr)
xIniEntry.OnChanged = func(s string) {
x, err := s2f32(s)
if err != nil {
xIniEntry.SetValidationError(err)
} else {
mcPrm.XIni = append(mcPrm.XIni[:0], x...)
}
}
xIniEntry.Validator = validateXini
xIniItem := widget.NewFormItem("initial X", xIniEntry)
xIniItem.HintText = xCoordHint
nStepItem, nStepReload := intItem(&mcPrm.NStep, "N steps", "number of steps in calculation")
nEqulItem, nEqulReload := intItem(&mcPrm.NEquil, "N equilibration", "number of steps for equilibration")
seedItem, seedReload := intItem(&mcwork.Seed, "seed random numbers", "seed for random number generator")
reloadPTab := func() {
iniTmpReload() // The values linked to in the the various fields can
fnlTmpReload() // change outside of this function, so reloadPTab
xDltaReload() // is called to refresh all the individual elements.
xIniEntry.SetText(fslicestrng(mcPrm.XIni))
nStepReload()
nEqulReload()
seedReload()
}
rdfile := func() { rdwork(genParams, reloadPTab) }
rdfileBtn := widget.NewButton("get file ", rdfile)
rdFileItem := widget.NewFormItem("read from a file", rdfileBtn)
// puke-oh-rama, Can we tidy up the next three lines ?
r := widget.NewForm( // Lays out the items in the form
iniTmpItem, fnlTmpItem, xDltaItem, xIniItem,
nStepItem, nEqulItem, seedItem, rdFileItem)
r.SubmitText = "start calculation"
r.OnSubmit = func() { startrun(genParams, r, mcPrm) }
reloadPTab() // does this help ? sometimes the form pops up with entries not ready
return r
}
// getmcprm gets MC parameters from a file. It is called by the file open
// dialog and is given an already open file handle.
func getmcprm(rd fyne.URIReadCloser, err error, genParams genParams, reload func()) {
win := genParams.win
mcPrm := genParams.mcPrm
if err != nil {
dialog.NewError(err, win).Show()
return
}
if rd == nil { // cancelled
return
}
defer rd.Close()
if err := mcwork.RdPrm(rd, mcPrm); err != nil {
dialog.NewError(err, win).Show()
return
} else {
dialog.NewInformation("OK", "read file, no errors", win).Show()
reload()
return
}
}
// rdwork is called after the file open dialog gets "OK"
//func rdwork(mcPrm *mcwork.McPrm, parent fyne.Window, reload func()) {
func rdwork(genParams genParams, reload func()) {
getter := func(rd fyne.URIReadCloser, err error) {
getmcprm(rd, err, genParams, reload)
}
t := dialog.NewFileOpen(getter, genParams.win)
if cwd, err := os.Getwd(); err == nil {
if y, err := storage.ListerForURI(storage.NewFileURI(cwd)); err == nil {
t.SetLocation(y) // If there was an error, we just use default location
}
}
t.Show() // set an error and see if anybody notices
}
// mcWrap is run in the background so the interface does not block.
// It runs the Monte Carlo, then when it is finished, sends a message
// down the channel.
func mcWrap(genParams genParams) {
chn := genParams.chn
chn <- workstatus{status: calculating}
if result, err := mcwork.DoRun(genParams.mcPrm); err != nil {
dialog.NewError(err, genParams.win).Show()
chn <- workstatus{status: errorCalc, err: err}
} else {
chn <- workstatus{MCresult: result, status: resultsReady}
}
}
// inputTab sets up the input page.
func inputTab(genParams genParams) *widget.Form {
return paramForm(genParams)
}