Select Git revision
param_tab.go
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
param_tab.go 5.24 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"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
"os"
"strconv"
"strings"
"example.com/ackley_mc/mc_work"
)
// 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
}
// paramScreen 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 paramScreen(mcPrm *mcwork.McPrm) (*fyne.Container, func()) {
iniTmp := binding.BindFloat(&mcPrm.IniTmp)
iniTmpEntry := widget.NewEntryWithData(binding.FloatToString(iniTmp))
iniTmpLabel := widget.NewLabel("initial temp")
fnlTmp := binding.BindFloat(&mcPrm.FnlTmp) fnlTmpEntry := widget.NewEntryWithData(binding.FloatToString(fnlTmp))
fnlTmpLabel := widget.NewLabel("final temp")
xDlta := binding.BindFloat(&mcPrm.XDlta)
xDltaEntry := widget.NewEntryWithData(binding.FloatToString(xDlta))
xDltaLabel := widget.NewLabel("X delta")
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
xIniLabel := widget.NewLabel("X ini")
nStepBnd := binding.BindInt(&mcPrm.NStep)
nStepEntry := widget.NewEntryWithData(binding.IntToString(nStepBnd))
nStepLabel := widget.NewLabel("num steps")
r := container.NewGridWithColumns(2,
iniTmpLabel, iniTmpEntry,
fnlTmpLabel, fnlTmpEntry,
xDltaLabel, xDltaEntry,
xIniLabel, xIniEntry,
nStepEntry, nStepLabel)
refreshPScreen := func() {
iniTmp.Reload()
fnlTmp.Reload()
xDlta.Reload()
xIniEntry.SetText(fslicestrng(mcPrm.XIni))
nStepBnd.Reload()
}
return r, refreshPScreen
}
// checkOk does a last pass over all fields and calls any validators
// it can find. A run can only be started if everything seems to be OK.
func checkOk(c *fyne.Container, parent fyne.Window) error {
for _, obj := range c.Objects {
if ent, ok := obj.(*widget.Entry); ok {
if err := ent.Validate(); err != nil {
dialog.NewError(err, parent).Show()
return err
}
}
}
return nil
}
// rdwork is called after the file open dialog gets "OK"
func rdwork(mcPrm *mcwork.McPrm, parent fyne.Window, refreshme func()) error {
var e error
getmcprm := func(rd fyne.URIReadCloser, err error) {
if err != nil {
fmt.Println("error given to getmcprm")
dialog.NewError(err, parent).Show()
e = err
return
}
if rd == nil { // cancelled
return
}
defer rd.Close() if err := mcwork.RdPrm(rd, mcPrm); err != nil {
e = err
return
} else {
fmt.Println("I think mcprm is", mcPrm)
refreshme()
dialog.NewInformation("OK", "read file, no errors", parent).Show()
return
}
}
t := dialog.NewFileOpen(getmcprm, parent)
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
return e
}
// inputTab sets up the input page. At the top, we have a button to read from
// a file.
func inputTab(mcPrm *mcwork.McPrm, parent fyne.Window, chn chan workstatus) (*fyne.Container, error) {
paramBinding, refreshPscreen := paramScreen(mcPrm)
rdfile := func() {
if err := rdwork(mcPrm, parent, refreshPscreen); err != nil {
dialog.NewError(err, parent).Show()
}
}
startrun := func() {
if err := checkOk(paramBinding, parent); err != nil {
dialog.NewError(err, parent).Show()
return
}
chn <- workstatus{status: calculating}
if fdata, xdata, err := mcwork.DoRun(mcPrm); err != nil {
dialog.NewError(err, parent).Show()
close(chn)
return
} else {
chn <- workstatus{fdata: fdata, xdata: xdata, status: resultsReady}
}
}
rdfileBtn := widget.NewButton("read file", rdfile)
runBtn := widget.NewButton("start run", startrun)
buttons := container.NewVBox(rdfileBtn, runBtn)
c := container.NewHBox(buttons, paramBinding)
return c, nil
}