Compare commits

...

51 Commits

Author SHA1 Message Date
Jeff Carr 1127b96570 hack to identify protobuf tables 2025-03-25 13:17:00 -05:00
Jeff Carr 308543c02c identify widgets in tables 2025-03-25 13:17:00 -05:00
Jeff Carr d141b4d308 save the toolkit config options 2025-03-25 13:17:00 -05:00
Jeff Carr 3420aee291 minor 2025-03-22 05:22:18 -05:00
Jeff Carr 5ef3364bf1 now using the awesome golang 1.24 'iter' 2025-03-19 06:40:40 -05:00
Jeff Carr 0288d05f2d need to work on standard date formatting 2025-03-12 13:18:30 -05:00
Jeff Carr e640db7eb6 quiet table pb output 2025-03-10 09:44:31 -05:00
Jeff Carr 531a31e4b3 quiet normal init() conditions 2025-03-10 04:37:42 -05:00
Jeff Carr 154c750e02 pass buttons to the plugins 2025-03-05 12:42:50 -06:00
Jeff Carr 15100aad3e func shouldn't have been global 2025-03-05 03:00:21 -06:00
Jeff Carr 975c2d3102 try the lock but don't insist on it 2025-03-04 21:05:25 -06:00
Jeff Carr 860908c82f need proper locking 2025-03-04 20:29:27 -06:00
Jeff Carr 5e1ec700fd rm old code 2025-03-04 14:33:53 -06:00
Jeff Carr 5c033ce431 lock for gocui 2025-03-04 04:06:49 -06:00
Jeff Carr be696b98aa test to make a frozen channel 2025-03-04 01:58:24 -06:00
Jeff Carr 547e67042d start thinking about table Update() 2025-03-03 12:00:04 -06:00
Jeff Carr b61cf9902e die if this isn't already init'd 2025-03-03 03:46:24 -06:00
Jeff Carr 24475c4173 more on toolkit init() 2025-03-03 01:00:26 -06:00
Jeff Carr 7a1124c6f5 notsure 2025-03-03 00:51:20 -06:00
Jeff Carr 1e97138a1c finally add a ToolkitInit() 2025-03-03 00:12:58 -06:00
Jeff Carr 3fb1d3ef70 fix enable/disable buttons 2025-03-02 12:05:15 -06:00
Jeff Carr c502e7c5b6 new toolkit tree functions 2025-02-24 11:00:35 -06:00
Jeff Carr 4046e33a63 times kinda update 2025-02-23 13:13:30 -06:00
Jeff Carr 344b3ab1d1 update worked to gocui 2025-02-23 13:13:30 -06:00
Jeff Carr 281899055d check if the table is already here 2025-02-23 13:13:30 -06:00
Jeff Carr 55d4441668 better 'make' rules 2025-02-23 13:13:30 -06:00
Jeff Carr 5670c722c5 quiet more debugging output 2025-02-21 18:49:52 -06:00
Jeff Carr 5f2b7daae3 rm printf's 2025-02-21 16:55:43 -06:00
Jeff Carr 878f55cfcb this actually worked? 2025-02-21 15:50:46 -06:00
Jeff Carr 34b0c787c3 this needs to be redone 2025-02-21 13:57:29 -06:00
Jeff Carr 886f96c64f remove debugging printf's 2025-02-21 05:42:18 -06:00
Jeff Carr 1322fbead0 preliminary time column 2025-02-20 03:23:14 -06:00
Jeff Carr a52e81354a table v1 2025-02-19 17:39:45 -06:00
Jeff Carr e0775877c8 starting the table window 2025-02-19 17:39:45 -06:00
Jeff Carr f025713892 before gocui refactor 2025-02-19 17:39:45 -06:00
Jeff Carr 2a277c147a string rows 2025-02-19 17:39:45 -06:00
Jeff Carr 27c9417a32 testing on tables 2025-02-19 17:39:45 -06:00
Jeff Carr fd24d2ee0d actually delete the node from the tree 2025-02-19 17:39:45 -06:00
Jeff Carr f470cbc5e3 hide/destroy window 2025-02-19 17:39:45 -06:00
Jeff Carr 29f8f406ef take out delay 2025-02-14 19:12:18 -06:00
Jeff Carr 74de0ac89d debugging init() 2025-02-13 22:28:46 -06:00
Jeff Carr b97dace40e widget proto moved to guipb 2025-02-13 20:35:17 -06:00
Jeff Carr ede7c71fc7 subbed in Tables() 2025-02-13 20:11:06 -06:00
Jeff Carr a75f0be460 a simpler time 2025-02-13 17:52:43 -06:00
Jeff Carr dcfa5d03ee using WaitOK() to debug gocui & andlabs init() 2025-02-13 14:14:39 -06:00
Jeff Carr 827a258a86 initOnce() 2025-02-12 17:00:44 -06:00
Jeff Carr db0986fa06 updates for a standard plugin code file 2025-02-12 15:43:58 -06:00
Jeff Carr 6f5944d45f working on fixing button disable 2025-02-12 00:19:31 -06:00
Your Name 27c0a4afb9 quash old debugging messages 2024-01-01 12:00:00 -06:00
Jeff Carr bc55a8b33b add IsEnabled() 2025-02-09 05:58:53 -06:00
Jeff Carr cbaa1c3713 make a config file 2025-02-08 06:35:38 -06:00
13 changed files with 883 additions and 153 deletions

View File

@ -1,6 +1,4 @@
all: goimports vet
redo: clean proto goimports vet
redo: proto goimports vet
vet:
@GO111MODULE=off go vet
@ -14,8 +12,11 @@ redomod: goimports
goimports:
goimports -w *.go
proto:
autogenpb --proto widget.proto
proto: toolkitConfig.pb.go
toolkitConfig.pb.go: toolkitConfig.proto
autogenpb --proto toolkitConfig.proto
clean:
rm -f go.* *.pb.go
go-mod-clean --purge

166
action.go Normal file
View File

@ -0,0 +1,166 @@
// Although most code from WIT.COM Inc is under the GPL
// This code is more generic because it must be able
// to be used in any GUI plugin
package tree
import (
"go.wit.com/lib/protobuf/guipb"
"go.wit.com/log"
"go.wit.com/widget"
)
// everything from the application goes through here
func (me *TreeInfo) doAction(a widget.Action) {
if a.ActionType == widget.ToolkitInit {
log.Log(TREE, "tree.doAction() trapped ToolkitInit finally!")
a.WidgetType = widget.Root
n := addNode(&a)
me.Add(n)
log.Log(TREE, "tree.doAction() init() me.treeRoot")
if me.ToolkitInit == nil {
log.Log(TREE, "tree.doAction() ToolkitInit() was called before plugin had a chance to initialize")
log.Log(TREE, "tree.doAction() TODO: fix channel to pause")
return
}
log.Log(TREE, "tree.doAction() doing ToolkitInit()")
me.ToolkitInit()
return
}
if a.TablePB != nil {
log.Log(TREE, "tree: got a TablePB")
me.doTable(a)
return
}
if a.WidgetId == 0 {
if treeRoot == nil {
log.Log(TREE, "tree.doAction() yes, treeRoot is nil. add here")
}
}
n := treeRoot.FindWidgetId(a.WidgetId)
switch a.ActionType {
case widget.Add:
if n == nil {
n := me.AddNode(&a)
me.Add(n)
return
}
if a.WidgetId == 0 {
// this is ok. This is the binary tree base and it's already initialized. This happens on startup
return
}
// this shouldn't really happen. It's good to print a warning so the plugin code can be debugged
log.Log(TREEWARN, "attempting to re-add widget", a.WidgetId, a.WidgetType, a.ActionType)
return
}
if n == nil {
// log.Log(TREEWARN, "tree.FindWidgetId() n == nil", a.WidgetId, a.WidgetType, a.ActionType)
// log.Log(TREEWARN, "tree.FindWidgetId() n == nil", a.State.CurrentS)
// log.Log(TREEWARN, "tree.FindWidgetId() n == nil. A bug in your application?")
log.Log(TREEWARN, "tree.doAction() bug in gui. trying to do action", a.ActionType, "before widget init() wId =", a.WidgetId)
if a.WidgetId == 0 {
log.Log(TREEWARN, "tree.doAction() bug in gui. on wId zero. is treeRoot nil?")
if treeRoot == nil {
log.Log(TREEWARN, "tree.doAction() yes, treeRoot is nil")
}
}
return
}
switch a.ActionType {
case widget.SetText:
log.Log(TREE, "tree.SetText() a.State.CurrentS =", a.State.CurrentS)
log.Log(TREE, "tree.SetText() a.State.DefaultS =", a.State.DefaultS)
log.Log(TREE, "tree.SetText() a.State.NewString =", a.State.NewString)
switch n.WidgetType {
case widget.Dropdown:
me.SetText(n, a.State.NewString)
case widget.Combobox:
me.SetText(n, a.State.NewString)
case widget.Textbox:
me.SetText(n, a.State.NewString)
case widget.Window:
me.SetTitle(n, a.State.Label)
default:
// buttons, checkboxes, groups, etc
me.SetLabel(n, a.State.Label)
}
case widget.AddText:
switch n.WidgetType {
case widget.Dropdown:
n.ddStrings = append(n.ddStrings, a.State.NewString)
me.AddText(n, a.State.NewString)
case widget.Combobox:
n.ddStrings = append(n.ddStrings, a.State.NewString)
me.AddText(n, a.State.NewString)
default:
log.Log(TREEWARN, "AddText() not supported on widget", n.WidgetType, n.String())
}
case widget.Checked:
switch n.WidgetType {
case widget.Checkbox:
if me.SetChecked == nil {
log.Log(TREEWARN, "SetChecked() == nil in toolkit", me.PluginName)
} else {
me.SetChecked(n, a.State.Checked)
}
default:
log.Log(TREEWARN, "SetChecked() not supported on widget", n.WidgetType, n.String())
}
case widget.Show:
if n.WidgetType == widget.Table {
t, err := loadTable(&a)
if err != nil {
log.Info("unmarshal data failed", err)
return
}
if t == nil {
log.Info("unmarshal data failed table == nil")
} else {
me.ShowTable(nil)
}
} else {
n.State.Hidden = false
me.Show(n)
}
case widget.Hide:
n.State.Hidden = true
me.Hide(n)
log.Info("tree: doing hide here on", a.WidgetId, n.WidgetType)
case widget.Enable:
n.State.Enable = true
me.Enable(n)
case widget.Disable:
n.State.Enable = false
me.Disable(n)
case widget.Delete:
if me.Hide == nil {
log.Info("toolkit doesn't know how to Hide() widgets")
} else {
me.Hide(n)
}
me.DeleteNode(n)
// now remove the child from the parent
default:
log.Log(TREEWARN, "tree.Action() unknown action", a.ActionType, "on wId", a.WidgetId)
// me.NodeAction(n, a.ActionType)
}
}
func loadTable(a *widget.Action) (*guipb.Tables, error) {
var t *guipb.Tables
err := t.Unmarshal(a.TablePB)
/*
test := NewRepos()
if test.Uuid != all.Uuid {
log.Log(WARN, "uuids do not match", test.Uuid, all.Uuid)
deleteProtobufFile(cfgname)
}
if test.Version != all.Version {
log.Log(WARN, "versions do not match", test.Version, all.Version)
deleteProtobufFile(cfgname)
}
*/
// log.Log(INFO, cfgname, "protobuf versions and uuid match", all.Uuid, all.Version)
return t, err
}

View File

@ -5,14 +5,23 @@ import (
"go.wit.com/widget"
)
// this is in common.go, do not move it
func (me *TreeInfo) AddNode(a *widget.Action) *Node {
if me.TryLock() {
defer me.Unlock()
} else {
log.Log(TREE, "mutex lock was already held before AddNode()")
}
return addNode(a)
}
func addNode(a *widget.Action) *Node {
n := new(Node)
n.WidgetType = a.WidgetType
n.WidgetId = a.WidgetId
n.ParentId = a.ParentId
n.State = a.State
n.State.Enable = a.State.Enable
// n.Strings = make(map[string]int)
// slices.Reverse(lines)
// dropdown strings
@ -29,9 +38,9 @@ func (me *TreeInfo) AddNode(a *widget.Action) *Node {
}
if treeRoot.FindWidgetId(a.WidgetId) != nil {
log.Log(TREEWARN, "AddNode() WidgetId already exists", a.WidgetId)
log.Log(TREEWARN, "probably this is a Show() / Hide() issue")
log.Log(TREEWARN, "TODO: figure out what to do here")
// ignore these errors for now
log.Log(TREE, "AddNode() WidgetId already exists", a.WidgetId)
log.Log(TREE, "TODO: figure out what to do here probably this is a Show() / Hide() issue")
return treeRoot.FindWidgetId(a.WidgetId)
}
@ -50,8 +59,15 @@ func (me *TreeInfo) AddNode(a *widget.Action) *Node {
return n
}
func (n *Node) DeleteNode() {
func (me *TreeInfo) DeleteNode(n *Node) {
log.Log(TREE, "DeleteNode() before lock n =", n.WidgetId, n.GetProgName())
if me.TryLock() {
defer me.Unlock()
} else {
log.Info("TREE: mutex lock was already held before DeleteNode()")
}
p := n.Parent
log.Log(TREE, "DeleteNode() parent =", p.WidgetId, p.GetProgName())
for i, child := range p.children {
log.Log(TREE, "parent has child:", i, child.WidgetId, child.GetProgName())
if n == child {

View File

@ -67,6 +67,10 @@ func (n *Node) Hidden() bool {
return n.State.Hidden
}
func (n *Node) IsEnabled() bool {
return n.State.Enable
}
/* avoid this function name as confusing
func (n *Node) GetText() string { // BAD
return widget.GetString(n.State.Value)

39
date.go Normal file
View File

@ -0,0 +1,39 @@
package tree
import (
"time"
)
// TODO; let the user choose the date format
func MakeDatestamp(t time.Time) string {
/*
// Get system locale from the environment
locale := os.Getenv("LANG")
if locale == "" {
locale = "en_US" // Default to English (US) if not set
}
// Parse the language tag
tag, err := language.Parse(locale)
if err != nil {
log.Fatalf("Invalid locale: %v", err)
}
// Create a date formatter
formatter := date.NewFormatter(date.OrderDefault, catalog.NewBuilder())
// Get the current timestamp
now := time.Now()
// Format the date based on the locale
p := message.NewPrinter(tag)
formattedDate := formatter.Format(tag, now)
// Print the formatted date
fmt.Println("Formatted Date:", formattedDate)
// Alternative: Use predefined time formats
fmt.Println("Localized Date (fallback):", p.Sprintf("%v", now.Format(time.RFC1123)))
*/
return t.Format(time.RFC1123)
}

63
find.go Normal file
View File

@ -0,0 +1,63 @@
package tree
import "log"
// find things in the tree
// also verify parent <-> child mappings aren't a lie
// searches the binary tree for a WidgetId
func FindWidgetId(id int) *Node {
return treeRoot.FindWidgetId(id)
}
func FindWidgetById(id int) *Node {
return treeRoot.FindWidgetId(id)
}
// searches the binary tree for a WidgetId
func (n *Node) FindWidgetId(id int) *Node {
if n == nil {
return nil
}
if n.WidgetId == id {
return n
}
for _, child := range n.children {
newN := child.FindWidgetId(id)
if newN != nil {
return newN
}
}
return nil
}
// used for debugging the 'gui' package
// to make sure things are valid
// fixes any errors along the way
func (me *Node) VerifyParentId() bool {
return me.verifyParentId(0)
}
func (n *Node) verifyParentId(parentId int) bool {
if n == nil {
return false
}
var ok bool = true
if n.ParentId != parentId {
log.Printf("fixed widgetId %d from %d to %d", n.WidgetId, n.ParentId, parentId)
n.ParentId = parentId
ok = false
}
for _, child := range n.children {
if child.verifyParentId(n.WidgetId) {
// everything is ok
} else {
ok = false
}
}
return ok
}

112
init.go
View File

@ -5,6 +5,7 @@
package tree
import (
"fmt"
"os"
"runtime/debug"
"sync"
@ -15,71 +16,6 @@ import (
var muAction sync.Mutex
// TODO: add checks for nil function pointers
func (me *TreeInfo) newAction(a widget.Action) {
n := treeRoot.FindWidgetId(a.WidgetId)
switch a.ActionType {
case widget.Add:
if n == nil {
n := me.AddNode(&a)
me.Add(n)
return
}
log.Log(TREEWARN, "attempting to re-add widget", a.WidgetId, a.WidgetType, a.ActionType)
return
}
if n == nil {
log.Log(TREEWARN, "tree.FindWidgetId() n == nil", a.WidgetId, a.WidgetType, a.ActionType)
log.Log(TREEWARN, "tree.FindWidgetId() n == nil", a.State.CurrentS)
log.Log(TREEWARN, "tree.FindWidgetId() n == nil. This should not happen. Bug in gui or tree package?")
log.Log(TREEWARN, "tree.FindWidgetId() n == nil. A bug in your application?")
return
}
switch a.ActionType {
case widget.SetText:
log.Log(TREE, "tree.SetText() a.State.CurrentS =", a.State.CurrentS)
log.Log(TREE, "tree.SetText() a.State.DefaultS =", a.State.DefaultS)
log.Log(TREE, "tree.SetText() a.State.NewString =", a.State.NewString)
switch n.WidgetType {
case widget.Dropdown:
me.SetText(n, a.State.NewString)
case widget.Combobox:
me.SetText(n, a.State.NewString)
case widget.Textbox:
me.SetText(n, a.State.NewString)
case widget.Window:
me.SetTitle(n, a.State.Label)
default:
// buttons, checkboxes, groups, etc
me.SetLabel(n, a.State.Label)
}
case widget.AddText:
switch n.WidgetType {
case widget.Dropdown:
n.ddStrings = append(n.ddStrings, a.State.NewString)
me.AddText(n, a.State.NewString)
case widget.Combobox:
n.ddStrings = append(n.ddStrings, a.State.NewString)
me.AddText(n, a.State.NewString)
default:
log.Log(TREEWARN, "AddText() not supported on widget", n.WidgetType, n.String())
}
case widget.Checked:
switch n.WidgetType {
case widget.Checkbox:
if me.SetChecked == nil {
log.Log(TREEWARN, "SetChecked() == nil in toolkit", me.PluginName)
} else {
me.SetChecked(n, a.State.Checked)
}
default:
log.Log(TREEWARN, "SetChecked() not supported on widget", n.WidgetType, n.String())
}
default:
me.NodeAction(n, a.ActionType)
}
}
func (me *TreeInfo) catchActionChannel() {
defer func() {
if r := recover(); r != nil {
@ -100,7 +36,11 @@ func (me *TreeInfo) catchActionChannel() {
case a := <-me.pluginChan:
log.Verbose("catchActionChannel() on ", a.WidgetId, a.WidgetType, a.ProgName)
muAction.Lock()
me.newAction(a)
me.WaitOK()
// time.Sleep(10 * time.Millisecond)
me.Lock()
me.doAction(a)
me.Unlock()
muAction.Unlock()
}
}
@ -109,9 +49,49 @@ func (me *TreeInfo) catchActionChannel() {
func New() *TreeInfo {
me := new(TreeInfo)
me.pluginChan = make(chan widget.Action, 1)
me.frozenChan = make(chan widget.Action, 1)
me.config = configLoad()
log.Log(TREE, "Init() start channel reciever")
go me.catchActionChannel()
log.Log(TREE, "Init() END")
return me
}
func (t *TreeInfo) ConfigFind(n string) (string, error) {
all := t.config.All() // get the list of repos
for all.Scan() {
r := all.Next()
if t.PluginName != r.Plugin {
continue
}
if n == r.Name {
return r.Value, nil
}
// log.Info("toolkit config no-match on", r.Plugin, r.Name, r.Value, n)
}
return "", fmt.Errorf("toolkit config %s not found", n)
}
func (t *TreeInfo) ConfigSave(o *ToolkitConfig) {
t.configInsert(o)
t.config.configSave()
}
// update the config option value (or append if new record)
func (t *TreeInfo) configInsert(newr *ToolkitConfig) {
all := t.config.All() // get the list of repos
for all.Scan() {
r := all.Next()
if t.PluginName != r.Plugin {
// option isn't for this plugin
continue
}
if newr.Name == r.Name {
// found the record!
r.Value = newr.Value
return
}
}
t.config.Append(newr)
}

View File

@ -6,37 +6,35 @@ package tree
There are some helper functions that are probably going to be
the same everywhere. Mostly due to handling the binary tree structure
and the channel communication
For now, it's just a symlink to the 'master' version in
./toolkit/nocui/common.go
*/
import (
"os"
"time"
"go.wit.com/log"
"go.wit.com/widget"
)
// searches the binary tree for a WidgetId
func FindWidgetId(id int) *Node {
return treeRoot.FindWidgetId(id)
func (me *TreeInfo) InitOK() {
me.ok = true
}
// searches the binary tree for a WidgetId
func (n *Node) FindWidgetId(id int) *Node {
if n == nil {
return nil
}
if n.WidgetId == id {
return n
}
for _, child := range n.children {
newN := child.FindWidgetId(id)
if newN != nil {
return newN
// this hack is to wait for the application to send something
// before trying to do anything. todo: rethink this someday
func (me *TreeInfo) WaitOK() {
for {
if me == nil {
log.Info("WaitOK lied. me == nil but returned anyway")
log.Info("WaitOK lied. me == nil but returned anyway")
log.Info("WaitOK lied. me == nil but returned anyway")
os.Exit(-1)
}
if me.ok {
return
}
time.Sleep(10 * time.Millisecond)
}
return nil
}
// Other goroutines must use this to access the GUI
@ -50,6 +48,13 @@ func (me *TreeInfo) Callback(guiCallback chan widget.Action) {
me.callback = guiCallback
}
// this is the function that receives things from the application
func (me *TreeInfo) PluginChannel() chan widget.Action {
me.WaitOK()
return me.pluginChan
}
// this is the function that receives things from the application
func (me *TreeInfo) FrozenChannel() chan widget.Action {
return me.frozenChan
}

View File

@ -7,6 +7,9 @@ package tree
*/
import (
"sync"
"go.wit.com/lib/protobuf/guipb"
"go.wit.com/widget"
)
@ -17,26 +20,28 @@ import (
var treeRoot *Node
type TreeInfo struct {
PluginName string
// this is the channel we send user events like
// mouse clicks or keyboard events back to the program
callback chan widget.Action
// this is the channel we get requests to make widgets
pluginChan chan widget.Action
// NodeI interface{}
// ActionFromChannel func(widget.Action)
NodeAction func(*Node, widget.ActionType)
Add func(*Node)
AddText func(*Node, string)
SetText func(*Node, string)
SetTitle func(*Node, string)
SetLabel func(*Node, string)
SetChecked func(*Node, bool)
ToolkitClose func()
sync.Mutex // a lock around the tree to serialize access
ok bool // indicates the plugin actually initialized
PluginName string // used to identify the plugin
config *ToolkitConfigs // protobuf of plugin settings
callback chan widget.Action // mouse clicks or keyboard events back to the program
pluginChan chan widget.Action // this is the channel we get requests to make widgets
frozenChan chan widget.Action // expirement to get fyne to work
Add func(*Node) // add a new widget
AddText func(*Node, string) // add a string to a dropdown widget
SetText func(*Node, string) // set the text of a widget
SetTitle func(*Node, string) // update the title of a window or tab
SetLabel func(*Node, string) // update the "label" (aka "Name") for a widget
SetChecked func(*Node, bool) // set the state of a checkbox
ToolkitInit func() // init the plugin
ToolkitClose func() // shutdown and unload the plugin
Show func(*Node) // show a widget
Hide func(*Node) // hide a widget
Enable func(*Node) // enable a widget
Disable func(*Node) // disable a widget
ShowTable func(*guipb.Table) // attempt at sending a whole table
currentTables []*guipb.Table // track the list of tables?
Root *guipb.Tree // this is the future of this system
}
type Node struct {

352
table.go Normal file
View File

@ -0,0 +1,352 @@
// Although most code from WIT.COM Inc is under the GPL
// This code is more generic because it must be able
// to be used in any GUI plugin
package tree
import (
"fmt"
"time"
"go.wit.com/lib/gui/shell"
"go.wit.com/lib/protobuf/guipb"
"go.wit.com/log"
"go.wit.com/widget"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
func (me *TreeInfo) doTable(a widget.Action) {
if a.TablePB == nil {
log.Log(TREE, "tree: action didn't have a Table PB")
return
}
pb := guipb.NewTables()
if err := pb.Unmarshal(a.TablePB); err != nil {
log.Info("unmarshal error", err, "data len =", len(a.TablePB))
return
}
log.Info("tree.doTables() start. # of tables:", len(pb.Tables))
for t := range pb.IterAll() {
// for i, o := range t.Order {
// log.Info("got order:", t.Title, i, o)
// }
// dumpTable(t)
// me.ShowTable(t)
// log.Info("TREE FOUND TABLE UUID", t.Uuid)
for i, ot := range me.currentTables {
// log.Info("TREE already has UUID", i, ot.Uuid)
if t.Uuid == ot.Uuid {
log.Log(TREE, "TREE found UUID! update table here", i, ot.Uuid)
if ot.Grid == nil {
log.Log(TREE, "TREE found UUID! ot.grid.Id = nil. need to find grid id here")
return
} else {
log.Log(TREE, "TREE found UUID! grid.Id =", ot.Grid.Id)
t.Grid = ot.Grid
}
if t.Grid == nil {
log.Log(TREE, "TREE found UUID! grid.Id = nil. need to find grid id here")
return
}
log.Log(TREE, "TREE found UUID! update table here", i, ot.Uuid, "grid.Id =", t.Grid.Id)
me.updateTable(t)
return
}
}
me.currentTables = append(me.currentTables, t)
if t.Grid == nil {
log.Log(TREEWARN, "new table error: grid.Id = nil need to set grid id here")
} else {
// log.Info("NEW TREE: grid.Id =", t.Grid.Id)
}
me.makeTable(t)
}
}
func (grid *Node) makeGridLabel(pb *guipb.Widget, w int, h int) *Node {
a := new(widget.Action)
a.WidgetType = widget.Label
a.WidgetId = int(pb.Id)
a.ParentId = grid.WidgetId
a.State.Enable = true
a.State.Label = pb.Name
a.State.AtW = w
a.State.AtH = h
a.State.GridOffset.X = w
a.State.GridOffset.Y = h
// log.Info("makeGridLabel()", a.State)
return addNode(a)
}
func (grid *Node) makeGridButton(pb *guipb.Widget, w int, h int) *Node {
a := new(widget.Action)
a.WidgetType = widget.Button
a.WidgetId = int(pb.Id)
a.ParentId = grid.WidgetId
a.State.Enable = true
a.State.Label = pb.Name
a.State.AtW = w
a.State.AtH = h
a.State.GridOffset.X = w
a.State.GridOffset.Y = h
// log.Info("makeGridLabel()", a.State)
return addNode(a)
}
func (me *TreeInfo) updateTable(t *guipb.Table) {
grid := FindWidgetId(int(t.Grid.Id))
if grid == nil {
log.Info("tree: updateTable() failed to make grid")
return
}
// delete the existing table
me.DeleteNode(grid)
// remake the table
me.Add(grid)
var h int = 0
var w int = 0
for _, name := range t.Order {
// log.Info("got order:", t.Title, name)
if me.addTableRow(t, grid, name, w) {
// log.Info("tree:row() COLUMN GOOD", t.Title, name, w, h)
} else {
log.Info("tree:row() COLOMN FAIL", t.Title, name, w, h)
}
w += 1
}
}
/*
func (me *TreeInfo) updateRow(t *guipb.Table, name string) {
for _, r := range t.StringRows {
if name == r.Header.Name {
// debugging code
// id := r.Header.Id
// n := treeRoot.FindWidgetId(int(id))
// if n == nil {
// log.Info("could not find widget id", id)
// }
log.Info("tree.updateRow(string)", r.Header, "len =", len(r.Widgets))
// if r.Header.Name == "Hostname" {
// }
// log.Info("tree.updateRow() found Hostnames", r.Vals)
for i, v := range r.Vals {
// log.Info("tree: Hostname Widget", i, v, w.Name)
w := r.Widgets[i]
if v != w.Name {
log.Info("tree: need to update:", i, v, "vs", w.Name, w.Id)
n := treeRoot.FindWidgetId(int(w.Id))
if n == nil {
log.Info("tree.TableUpdate() err n == nil ", w.Id, w.Name)
continue
}
me.SetText(n, v)
}
if r.Header.Name == "CurrentBranchName" {
log.Info("tree: check:", i, v, "vs", w.Name, w.Id)
}
}
return
}
}
for _, r := range t.TimeRows {
if name != r.Header.Name {
continue
}
log.Info("tree.updateRow(time)", r.Header, "len =", len(r.Widgets))
for i, _ := range r.Vals {
// log.Info("tree: Hostname Widget", i, v, w.Name)
w := r.Widgets[i]
n := treeRoot.FindWidgetId(int(w.Id))
if n == nil {
log.Info("tree.TableUpdate() err n == nil ", w.Id, w.Name)
continue
}
msg, err := anypb.UnmarshalNew(w.Val, proto.UnmarshalOptions{})
if err != nil {
log.Fatalf("failed to unmarshal: %v", err)
}
switch v := msg.(type) {
case *timestamppb.Timestamp:
me.SetText(n, shell.FormatDuration(time.Since(v.AsTime())))
default:
me.SetText(n, fmt.Sprintf("%v", v))
}
}
return
}
}
*/
func (me *TreeInfo) makeTable(t *guipb.Table) {
var grid *Node
if t.Parent != nil {
a := new(widget.Action)
a.WidgetType = widget.Grid
a.WidgetId = int(t.Grid.Id)
a.ParentId = int(t.Parent.Id)
a.State.Enable = true
grid = addNode(a)
}
if grid == nil {
log.Log(TREEWARN, "tree: makeTable() failed to make grid")
return
}
me.Add(grid)
grid.State.ProgName = "TableGridPB"
// log.Info("tree: makeTable() finished add win & grid")
var h int = 0
var w int = 0
for _, name := range t.Order {
// log.Info("got order:", t.Title, name)
if me.addTableRow(t, grid, name, w) {
// log.Info("tree:row() COLUMN GOOD", t.Title, name, w, h)
} else {
log.Log(TREEWARN, "tree:row() COLOMN FAIL", t.Title, name, w, h)
}
w += 1
}
}
func (me *TreeInfo) addTableRow(t *guipb.Table, grid *Node, name string, w int) bool {
var h int = 0
for _, r := range t.StringRows {
if name != r.Header.Name {
// log.Info("skip string row:", r.Header.Name, "!=", name)
continue
}
// log.Info("tree: Add()ing to grid here", r.Header.Id, r.Header.Name, w, h)
head := grid.makeGridLabel(r.Header, w, h)
me.Add(head)
h += 1
for _, v := range r.Widgets {
// log.Info("tree: Add()ing to grid here", v.Id, v.Name, w, h)
lab := grid.makeGridLabel(v, w, h)
me.Add(lab)
h += 1
}
return true
}
for _, r := range t.ButtonRows {
if name != r.Header.Name {
// log.Info("skip string row:", r.Header.Name, "!=", name)
continue
}
// log.Info("tree: Add()ing to grid here", r.Header.Id, r.Header.Name, w, h)
head := grid.makeGridLabel(r.Header, w, h)
me.Add(head)
h += 1
for _, v := range r.Widgets {
// log.Info("tree: Add()ing to grid here", v.Id, v.Name, w, h)
lab := grid.makeGridButton(v, w, h)
me.Add(lab)
h += 1
}
return true
}
for _, r := range t.IntRows {
if name != r.Header.Name {
// log.Info("skip sint row:", r.Header.Name, "!=", name)
continue
}
// log.Info("tree: Add()ing to grid here", r.Header.Id, r.Header.Name, w, h)
head := grid.makeGridLabel(r.Header, w, h)
me.Add(head)
h += 1
for _, v := range r.Widgets {
v.Name = fmt.Sprintf("%d", v.Size)
// log.Info("tree: Add()ing to grid here", v.Id, v.Name, w, h)
lab := grid.makeGridLabel(v, w, h)
me.Add(lab)
h += 1
}
return true
}
for _, r := range t.TimeRows {
if name != r.Header.Name {
// log.Info("skip sint row:", r.Header.Name, "!=", name)
continue
}
// log.Info("tree: Add()ing to grid here", r.Header.Id, r.Header.Name, w, h)
head := grid.makeGridLabel(r.Header, w, h)
me.Add(head)
h += 1
for _, widg := range r.Widgets {
msg, err := anypb.UnmarshalNew(widg.Val, proto.UnmarshalOptions{})
if err != nil {
log.Fatalf("failed to unmarshal: %v", err)
}
switch v := msg.(type) {
case *timestamppb.Timestamp:
// fmt.Println("Unpacked Timestamp:", shell.FormatDuration(time.Since(v.AsTime())))
widg.Name = shell.FormatDuration(time.Since(v.AsTime()))
default:
fmt.Println("Unknown type:", v)
widg.Name = fmt.Sprintf("%v", v)
}
// log.Info("tree: Add()ing to grid here", widg.Id, widg.Name, w, h)
lab := grid.makeGridLabel(widg, w, h)
me.Add(lab)
h += 1
}
return true
}
return false
}
// returns true if widget is in a table
func (n *Node) InTable() bool {
// log.Info("InTable() parent id =", n.ParentId)
grid := FindWidgetById(int(n.ParentId))
if grid != nil {
if grid.WidgetType == widget.Grid {
if grid.State.ProgName == "TableGridPB" {
// this is a protobuf table
return true
}
}
}
return false
}
func dumpTable(t *guipb.Table) {
for i, o := range t.Order {
log.Info("got order:", t.Title, i, o)
}
for i, r := range t.StringRows {
log.Info("got string row:", t.Title, i, r.Header.Name, r.Vals)
for _, v := range r.Widgets {
log.Info("tree: got string widget:", v.Id, v.Name)
}
}
for i, r := range t.IntRows {
log.Info("got int row:", t.Title, i, r.Header.Name, r.Vals)
for _, v := range r.Widgets {
log.Info("tree: got int widget:", v.Id, v.Size)
}
}
}
type TreeTable struct {
PB *guipb.Table
/*
hostnames []string
columns []*gui.NodeColumn
order []*gui.NodeColumn
*/
}

19
toolkitConfig.proto Normal file
View File

@ -0,0 +1,19 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
syntax = "proto3";
package forgepb;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message ToolkitConfig { //
string plugin = 1; // 'gocui', 'andlabs', etc `autogenpb:unique`
string name = 2; // variable name 'fullscreen' `autogenpb:unique`
string value = 3; // value "true"
}
message ToolkitConfigs { // `autogenpb:marshal` `autogenpb:nomutex`
string uuid = 1; // `autogenpb:uuid:d7886d47-a3b9-43b9-b0f6-17074a9844e6`
string version = 2; // `autogenpb:version:v0.0.1`
repeated ToolkitConfig ToolkitConfigs = 3;
}

116
toolkitConfig.save.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2025 WIT.COM Inc Licensed GPL 3.0
package tree
// functions to import and export the protobuf
// data to and from config files
import (
"errors"
"fmt"
"os"
"path/filepath"
"go.wit.com/log"
)
// load the ~/.config/forge/ files
func configLoad() *ToolkitConfigs {
if os.Getenv("FORGE_CONFIG") == "" {
homeDir, _ := os.UserHomeDir()
fullpath := filepath.Join(homeDir, ".config/forge")
os.Setenv("FORGE_CONFIG", fullpath)
}
c, err := loadText()
if err != nil {
log.Info("gui toolkit configLoad() error", err)
}
if c != nil {
return c
}
// first time user. make a template config file
c = sampleConfig()
return c
}
// makes a sample config (and saves it)
func sampleConfig() *ToolkitConfigs {
all := NewToolkitConfigs()
new1 := new(ToolkitConfig)
new1.Plugin = "tree"
new1.Name = "test"
new1.Value = "apple"
all.Append(new1)
all.configSave()
fmt.Println("first time user. adding an example config file with", len(all.ToolkitConfigs), "repos")
return all
}
// write to ~/.config/forge/ unless ENV{FORGE_CONFIG} is set
func (c *ToolkitConfigs) configSave() error {
s := c.FormatTEXT()
configWrite("toolkit.text", []byte(s))
return nil
}
func loadText() (*ToolkitConfigs, error) {
// this lets the user hand edit the config
data, err := loadFile("toolkit.text")
if err != nil {
return nil, err
}
if data == nil {
return nil, fmt.Errorf("toolkit.text data was nil")
}
if len(data) == 0 {
return nil, fmt.Errorf("toolkit.text was empty")
}
c := new(ToolkitConfigs)
// attempt to marshal toolkit.text
if err := c.UnmarshalTEXT(data); err != nil {
return nil, err
}
log.Log(TREE, "ConfigLoad()", len(c.ToolkitConfigs), "entries in ~/.config/forge")
return c, nil
}
func loadFile(filename string) ([]byte, error) {
fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
data, err := os.ReadFile(fullname)
if errors.Is(err, os.ErrNotExist) {
// if file does not exist, just return nil. this
// will cause ConfigLoad() to try the next config file like "toolkit.text"
// because the user might want to edit the .config by hand
return nil, nil
}
if err != nil {
// log.Info("open config file :", err)
return nil, err
}
return data, nil
}
func configWrite(filename string, data []byte) error {
fullname := filepath.Join(os.Getenv("FORGE_CONFIG"), filename)
cfgfile, err := os.OpenFile(fullname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
defer cfgfile.Close()
if err != nil {
log.Warn("open config file :", err)
return err
}
if filename == "toolkit.text" {
// add header
cfgfile.Write([]byte("\n"))
cfgfile.Write([]byte("# you can customize your GUI toolkit user settings here\n"))
cfgfile.Write([]byte("\n"))
}
cfgfile.Write(data)
return nil
}

View File

@ -1,36 +0,0 @@
syntax = "proto3";
// playing around with ideas here
package main;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
message Size {
int64 width = 1;
int64 height = 2;
}
message Location {
int64 x = 1;
int64 y = 2;
}
message Tree {
Widget parent = 1;
repeated Widget children = 2;
}
message Widget {
int64 id = 1;
string name = 2;
Size size = 3;
Location location = 4;
string color = 5;
}
message Widgets {
string uuid = 1; // `autogenpb:uuid:0331fcd7-3c8c-43e4-be1b-77db6a6bc58c`
string version = 2; // `autogenpb:version:v1`
repeated Widget Widgets = 3;
}