func (tv *TableView) LayoutHeader() {
	nWidgPerRow, idxOff := tv.RowWidgetNs()
	nfld := tv.NCols + idxOff
	sgh := tv.SliceHeader()
	sgf := tv.SliceGrid()
	spc := sgh.Spacing.Dots
	gd := sgf.GridData[gi.Col]
	if gd == nil {
		return
	}
	sumwd := float32(0)
	for fli := 0; fli < nfld; fli++ {
		lbl := sgh.Child(fli).(gi.Node2D).AsWidget()
		wd := gd[fli].AllocSize - spc
		if fli == 0 {
			wd += spc
		}
		lbl.SetMinPrefWidth(units.NewValue(wd, units.Dot))
		lbl.SetProp("max-width", units.NewValue(wd, units.Dot))
		sumwd += wd
	}
	if !tv.IsInactive() {
		for fli := nfld; fli < nWidgPerRow; fli++ {
			lbl := sgh.Child(fli).(gi.Node2D).AsWidget()
			wd := gd[fli].AllocSize - spc
			lbl.SetMinPrefWidth(units.NewValue(wd, units.Dot))
			lbl.SetProp("max-width", units.NewValue(wd, units.Dot))
			sumwd += wd
		}
	}
	sgh.SetMinPrefWidth(units.NewValue(sumwd+spc, units.Dot))
}
LayoutHeader updates the header layout based on field widths
package etview
import (
	"bytes"
	"encoding/csv"
	"fmt"
	"image"
	"log"
	"reflect"
	"strings"
	"github.com/emer/etable/etable"
	"github.com/emer/etable/etensor"
	"github.com/goki/gi/gi"
	"github.com/goki/gi/girl"
	"github.com/goki/gi/gist"
	"github.com/goki/gi/giv"
	"github.com/goki/gi/oswin"
	"github.com/goki/gi/oswin/cursor"
	"github.com/goki/gi/oswin/mimedata"
	"github.com/goki/gi/oswin/mouse"
	"github.com/goki/gi/units"
	"github.com/goki/ki/ints"
	"github.com/goki/ki/ki"
	"github.com/goki/ki/kit"
	"github.com/goki/mat32"
	"github.com/goki/pi/filecat"
)
type TableView struct {
	giv.SliceViewBase
	Table      *etable.IdxView     `desc:"the idx view of the table that we're a view of"`
	TsrDisp    TensorDisp          `desc:"overall display options for tensor display"`
	ColTsrDisp map[int]*TensorDisp `desc:"per column tensor display"`
	NCols      int                 `inactive:"+" desc:"number of columns in table (as of last update)"`
	SortIdx    int                 `desc:"current sort index"`
	SortDesc   bool                `desc:"whether current sort order is descending"`
}
var KiT_TableView = kit.Types.AddType(&TableView{}, TableViewProps)
func AddNewTableView(parent ki.Ki, name string) *TableView {
	return parent.AddNewChild(KiT_TableView, name).(*TableView)
}
var _ giv.SliceViewer = (*TableView)(nil)
func (tv *TableView) SetTable(et *etable.Table, tmpSave giv.ValueView) {
	if et == nil {
		return
	}
	tv.Table = etable.NewIdxView(et)
	tv.TmpSave = tmpSave
	tv.TableConfig()
}
func (tv *TableView) SetTableView(ix *etable.IdxView, tmpSave giv.ValueView) {
	if ix == nil {
		return
	}
	tv.Table = ix.Clone()  
	tv.TmpSave = tmpSave
	tv.TableConfig()
}
func (tv *TableView) TableConfig() {
	if op, has := tv.Table.Table.MetaData["read-only"]; has {
		if op == "+" || op == "true" {
			tv.SetInactive()
		} else {
			tv.ClearInactive()
		}
	}
	tv.ColTsrDisp = make(map[int]*TensorDisp)
	tv.TsrDisp.Defaults()
	tv.TsrDisp.BotRtSpace.Set(4, units.Px)
	if !tv.IsInactive() {
		tv.SelectedIdx = -1
	}
	tv.StartIdx = 0
	tv.SortIdx = -1
	tv.SortDesc = false
	updt := tv.UpdateStart()
	tv.ResetSelectedIdxs()
	tv.SelectMode = false
	tv.SetFullReRender()
	tv.ShowIndex = true
	if sidxp, err := tv.PropTry("index"); err == nil {
		tv.ShowIndex, _ = kit.ToBool(sidxp)
	}
	tv.InactKeyNav = true
	if siknp, err := tv.PropTry("inact-key-nav"); err == nil {
		tv.InactKeyNav, _ = kit.ToBool(siknp)
	}
	tv.Config()
	tv.UpdateEnd(updt)
}
var TableViewProps = ki.Props{
	"EnumType:Flag":    gi.KiT_NodeFlags,
	"background-color": &gi.Prefs.Colors.Background,
	"color":            &gi.Prefs.Colors.Font,
	"max-width":        -1,
	"max-height":       -1,
}
func (tv *TableView) UpdateTable() {
	if tv.Table != nil {
		tv.Table.Sequential()
		tv.SortIfNeeded()
	}
	tv.Update()
}
func (tv *TableView) SortIfNeeded() {
	if tv.SortIdx >= 0 {
		tv.Table.SortCol(tv.SortIdx, !tv.SortDesc)
	}
}
func (tv *TableView) IsConfiged() bool {
	if len(tv.Kids) == 0 {
		return false
	}
	sf := tv.SliceFrame()
	if len(sf.Kids) == 0 {
		return false
	}
	return true
}
func (tv *TableView) Config() {
	tv.Lay = gi.LayoutVert
	tv.SetProp("spacing", gi.StdDialogVSpaceUnits)
	config := kit.TypeAndNameList{}
	config.Add(gi.KiT_ToolBar, "toolbar")
	config.Add(gi.KiT_Frame, "frame")
	mods, updt := tv.ConfigChildren(config)
	tv.ConfigSliceGrid()
	tv.ConfigToolbar()
	if mods {
		tv.SetFullReRender()
		tv.UpdateEnd(updt)
	}
}
func (tv *TableView) UpdtSliceSize() int {
	tv.Table.DeleteInvalid()  
	tv.SliceSize = tv.Table.Len()
	tv.NCols = tv.Table.Table.NumCols()
	return tv.SliceSize
}
func (tv *TableView) SliceFrame() *gi.Frame {
	return tv.ChildByName("frame", 0).(*gi.Frame)
}
func (tv *TableView) GridLayout() *gi.Layout {
	gli := tv.SliceFrame().ChildByName("grid-lay", 0)
	if gli == nil {
		return nil
	}
	return gli.(*gi.Layout)
}
func (tv *TableView) SliceGrid() *gi.Frame {
	gl := tv.GridLayout()
	if gl == nil {
		return nil
	}
	return gl.ChildByName("grid", 0).(*gi.Frame)
}
func (tv *TableView) ScrollBar() *gi.ScrollBar {
	return tv.GridLayout().ChildByName("scrollbar", 1).(*gi.ScrollBar)
}
func (tv *TableView) SliceHeader() *gi.ToolBar {
	return tv.SliceFrame().Child(0).(*gi.ToolBar)
}
func (tv *TableView) ToolBar() *gi.ToolBar {
	return tv.ChildByName("toolbar", 0).(*gi.ToolBar)
}
func (tv *TableView) RowWidgetNs() (nWidgPerRow, idxOff int) {
	nWidgPerRow = 1 + tv.NCols
	if !tv.IsInactive() {
		if !tv.NoAdd {
			nWidgPerRow++
		}
		if !tv.NoDelete {
			nWidgPerRow++
		}
	}
	idxOff = 1
	if !tv.ShowIndex {
		nWidgPerRow -= 1
		idxOff = 0
	}
	return
}
func (tv *TableView) ConfigSliceGrid() {
	sg := tv.SliceFrame()
	updt := sg.UpdateStart()
	defer sg.UpdateEnd(updt)
	sgf := tv.This().(giv.SliceViewer).SliceGrid()
	if sgf != nil {
		sgf.DeleteChildren(ki.DestroyKids)
	}
	if tv.Table.Table == nil {
		return
	}
	sz := tv.This().(giv.SliceViewer).UpdtSliceSize()
	if tv.NCols == 0 {
		return
	}
	if sz == 0 {
		tv.Table.Table.SetNumRows(1)  
	}
	nWidgPerRow, idxOff := tv.RowWidgetNs()
	sg.Lay = gi.LayoutVert
	sg.SetMinPrefWidth(units.NewCh(20))
	sg.SetProp("overflow", gist.OverflowScroll)  
	sg.SetStretchMax()                           
	sg.SetProp("border-width", 0)
	sg.SetProp("margin", 0)
	sg.SetProp("padding", 0)
	sgcfg := kit.TypeAndNameList{}
	sgcfg.Add(gi.KiT_ToolBar, "header")
	sgcfg.Add(gi.KiT_Layout, "grid-lay")
	sg.ConfigChildren(sgcfg)
	sgh := tv.SliceHeader()
	sgh.Lay = gi.LayoutHoriz
	sgh.SetProp("overflow", gist.OverflowHidden)  
	sgh.SetProp("spacing", 0)
	gl := tv.GridLayout()
	gl.Lay = gi.LayoutHoriz
	gl.SetStretchMax()  
	gconfig := kit.TypeAndNameList{}
	gconfig.Add(gi.KiT_Frame, "grid")
	gconfig.Add(gi.KiT_ScrollBar, "scrollbar")
	gl.ConfigChildren(gconfig)  
	sgf = tv.This().(giv.SliceViewer).SliceGrid()
	sgf.Lay = gi.LayoutGrid
	sgf.Stripes = gi.RowStripes
	sgf.SetMinPrefHeight(units.NewEm(10))
	sgf.SetStretchMax()  
	sgf.SetProp("columns", nWidgPerRow)
	sgf.SetProp("overflow", gist.OverflowScroll)  
	hcfg := kit.TypeAndNameList{}
	if tv.ShowIndex {
		hcfg.Add(gi.KiT_Action, "head-idx")
	}
	for fli := 0; fli < tv.NCols; fli++ {
		labnm := fmt.Sprintf("head-%v", tv.Table.Table.ColNames[fli])
		hcfg.Add(gi.KiT_Action, labnm)
	}
	if !tv.IsInactive() {
		hcfg.Add(gi.KiT_Label, "head-add")
		hcfg.Add(gi.KiT_Label, "head-del")
	}
	sgh.ConfigChildren(hcfg)
	sgf.Kids = make(ki.Slice, nWidgPerRow)
	itxt := fmt.Sprintf("%05d", 0)
	labnm := fmt.Sprintf("index-%v", itxt)
	if tv.ShowIndex {
		hdr := sgh.Child(0).(*gi.Action)
		hdr.SetText("Index")
		hdr.Tooltip = "Click to sort by original native table order"
		hdr.ActionSig.ConnectOnly(tv.This(), func(recv, send ki.Ki, sig int64, data interface{}) {
			tvv := recv.Embed(KiT_TableView).(*TableView)
			tvv.SortSliceAction(-1)
		})
		idxlab := &gi.Label{}
		sgf.SetChild(idxlab, 0, labnm)
		idxlab.Text = itxt
	}
	for fli := 0; fli < tv.NCols; fli++ {
		col := tv.Table.Table.Cols[fli]
		colnm := tv.Table.Table.ColNames[fli]
		hdr := sgh.Child(idxOff + fli).(*gi.Action)
		hdr.SetText(colnm)
		if fli == tv.SortIdx {
			if tv.SortDesc {
				hdr.SetIcon("wedge-down")
			} else {
				hdr.SetIcon("wedge-up")
			}
		}
		hdr.Data = fli
		hdr.Tooltip = colnm + " (click to sort by) Type: " + col.DataType().String()
		if dsc, has := tv.Table.Table.MetaData[colnm+":desc"]; has {
			hdr.Tooltip += ": " + dsc
		}
		var vv giv.ValueView
		if stsr, isstr := col.(*etensor.String); isstr {
			vv = giv.ToValueView(&stsr.Values[0], "")
			vv.SetSliceValue(reflect.ValueOf(&stsr.Values[0]), stsr.Values, 0, tv.TmpSave, tv.ViewPath)
			hdr.ActionSig.ConnectOnly(tv.This(), func(recv, send ki.Ki, sig int64, data interface{}) {
				tvv := recv.Embed(KiT_TableView).(*TableView)
				act := send.(*gi.Action)
				fldIdx := act.Data.(int)
				tvv.SortSliceAction(fldIdx)
			})
		} else {
			if col.NumDims() == 1 {
				fval := 1.0
				vv = giv.ToValueView(&fval, "")
				vv.SetSoloValue(reflect.ValueOf(&fval))
				hdr.ActionSig.ConnectOnly(tv.This(), func(recv, send ki.Ki, sig int64, data interface{}) {
					tvv := recv.Embed(KiT_TableView).(*TableView)
					act := send.(*gi.Action)
					fldIdx := act.Data.(int)
					tvv.SortSliceAction(fldIdx)
				})
			} else {
				cell := tv.Table.Table.CellTensorIdx(fli, 0)
				tvv := &TensorGridValueView{}
				ki.InitNode(tvv)
				vv = tvv
				vv.SetSoloValue(reflect.ValueOf(cell))
				hdr.Tooltip = "(click to edit display parameters for this column)"
				hdr.ActionSig.ConnectOnly(tv.This(), func(recv, send ki.Ki, sig int64, data interface{}) {
					tvv := recv.Embed(KiT_TableView).(*TableView)
					act := send.(*gi.Action)
					fldIdx := act.Data.(int)
					tvv.TensorDispAction(fldIdx)
				})
			}
		}
		if wd, has := tv.Table.Table.MetaData[colnm+":width"]; has {
			vv.SetTag("width", wd)
		}
		vtyp := vv.WidgetType()
		valnm := fmt.Sprintf("value-%v.%v", fli, itxt)
		cidx := idxOff + fli
		widg := ki.NewOfType(vtyp).(gi.Node2D)
		sgf.SetChild(widg, cidx, valnm)
		vv.ConfigWidget(widg)
	}
	if !tv.IsInactive() {
		cidx := tv.NCols + idxOff
		if !tv.NoAdd {
			lbl := sgh.Child(cidx).(*gi.Label)
			lbl.Text = "+"
			lbl.Tooltip = "insert row"
			addnm := fmt.Sprintf("add-%v", itxt)
			addact := gi.Action{}
			sgf.SetChild(&addact, cidx, addnm)
			addact.SetIcon("plus")
			cidx++
		}
		if !tv.NoDelete {
			lbl := sgh.Child(cidx).(*gi.Label)
			lbl.Text = "-"
			lbl.Tooltip = "delete row"
			delnm := fmt.Sprintf("del-%v", itxt)
			delact := gi.Action{}
			sgf.SetChild(&delact, cidx, delnm)
			delact.SetIcon("minus")
			cidx++
		}
	}
	if sz == 0 {
		tv.Table.Table.SetNumRows(0)  
	}
	tv.ConfigScroll()
}
func (tv *TableView) LayoutSliceGrid() bool {
	sg := tv.This().(giv.SliceViewer).SliceGrid()
	if sg == nil {
		return false
	}
	updt := sg.UpdateStart()
	defer sg.UpdateEnd(updt)
	if tv.Table.Table == nil {
		sg.DeleteChildren(ki.DestroyKids)
		return false
	}
	tv.ViewMuLock()
	defer tv.ViewMuUnlock()
	tv.This().(giv.SliceViewer).UpdtSliceSize()
	if tv.NCols == 0 {
		sg.DeleteChildren(ki.DestroyKids)
		return false
	}
	nWidgPerRow, _ := tv.RowWidgetNs()
	if len(sg.GridData) > 0 && len(sg.GridData[gi.Row]) > 0 {
		tv.RowHeight = sg.GridData[gi.Row][0].AllocSize + sg.Spacing.Dots
	}
	if tv.Sty.Font.Face == nil {
		girl.OpenFont(&tv.Sty.Font, &tv.Sty.UnContext)
	}
	tv.RowHeight = mat32.Max(tv.RowHeight, tv.Sty.Font.Face.Metrics.Height)
	mvp := tv.ViewportSafe()
	if mvp != nil && mvp.HasFlag(int(gi.VpFlagPrefSizing)) {
		tv.VisRows = ints.MinInt(gi.LayoutPrefMaxRows, tv.SliceSize)
		tv.LayoutHeight = float32(tv.VisRows) * tv.RowHeight
	} else {
		sgHt := tv.AvailHeight()
		tv.LayoutHeight = sgHt
		if sgHt == 0 {
			return false
		}
		tv.VisRows = int(mat32.Floor(sgHt / tv.RowHeight))
	}
	tv.DispRows = ints.MinInt(tv.SliceSize, tv.VisRows)
	nWidg := nWidgPerRow * tv.DispRows
	if tv.Values == nil || sg.NumChildren() != nWidg {
		sg.DeleteChildren(ki.DestroyKids)
		tv.Values = make([]giv.ValueView, tv.NCols*tv.DispRows)
		sg.Kids = make(ki.Slice, nWidg)
	}
	tv.ConfigScroll()
	tv.LayoutHeader()
	return true
}