Compare commits

..

No commits in common. "guimaster" and "v0.22.16" have entirely different histories.

36 changed files with 1865 additions and 4014 deletions

1
.gitignore vendored
View File

@ -5,4 +5,3 @@
go.mod go.mod
go.sum go.sum
gocui gocui
resources/*.so

View File

@ -1,15 +1,11 @@
VERSION = $(shell git describe --tags) VERSION = $(shell git describe --tags)
BUILDTIME = $(shell date +%Y.%m.%d) BUILDTIME = $(shell date +%Y.%m.%d)
all: clean goimports vet gocui all: gocui.so
@ldd gocui.so @#ldd gocui.so
vet: gocui.so: goimports
@GO111MODULE=off go vet GO111MODULE=off go build -v -work -buildmode=plugin -o gocui.so \
@echo this go plugin builds okay
gocui:
GO111MODULE=off go build -v -x -buildmode=plugin -o gocui.so \
-ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}" -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
install: install:
@ -23,9 +19,9 @@ custom:
GO111MODULE=off go build -v -work -buildmode=blah GO111MODULE=off go build -v -work -buildmode=blah
clean: clean:
rm -f gocui *.so go.* rm -f gocui gocui.so
rm -f *.pb.go *.patch rm -f *.pb.go *.patch
go-mod-clean purge go-mod-clean --purge
# Test the README.md & doc.go file # Test the README.md & doc.go file
# this runs pkgsite, the binary that does dev.go.dev # this runs pkgsite, the binary that does dev.go.dev
@ -49,5 +45,5 @@ redomod:
GO111MODULE= go mod tidy GO111MODULE= go mod tidy
proto: proto:
autogenpb --proto gocuiView.proto autogenpb --proto view.proto
make goimports make goimports

View File

@ -9,32 +9,48 @@ import (
"go.wit.com/widget" "go.wit.com/widget"
) )
// this comes from the application
func setChecked(n *tree.Node, b bool) { func setChecked(n *tree.Node, b bool) {
if n.WidgetType != widget.Checkbox { if n.WidgetType != widget.Checkbox {
} }
n.State.Checked = b
var tk *guiWidget var tk *guiWidget
tk = n.TK.(*guiWidget) tk = n.TK.(*guiWidget)
tk.setCheckbox() if tk.node.State.Label == "" {
tk.node.State.Label = "BLANK"
} }
if tk.node.State.Checked {
// redraw the checkbox log.Log(WARN, "setCheckbox() got true", tk.node.State.Checked)
func (tk *guiWidget) setCheckbox() { tk.labelN = "X " + tk.node.State.Label
if tk.WidgetType() != widget.Checkbox {
log.Log(WARN, "setCheckbox() being run on widget:", tk.WidgetType())
return
}
if tk.Checked() {
log.Log(WARN, "setCheckbox() got true", tk.Checked())
tk.labelN = "X " + tk.GetLabel()
} else { } else {
log.Log(WARN, "setCheckbox() got false", tk.Checked()) log.Log(WARN, "setCheckbox() got false", tk.node.State.Checked)
tk.labelN = "_ " + tk.GetLabel() tk.labelN = " " + tk.node.State.Label
} }
tk.Hide() tk.Hide()
tk.Show() tk.Show()
} }
// redraw the checkbox
func (w *guiWidget) setCheckbox() {
if w.node.WidgetType != widget.Checkbox {
log.Log(WARN, "setCheckbox() being run on widget:", w.node.WidgetType)
return
}
if w.node.State.Label == "" {
w.node.State.Label = "BLANK"
}
if w.node.State.Checked {
log.Log(WARN, "setCheckbox() got true", w.node.State.Checked)
w.labelN = "X " + w.node.State.Label
// w.changed = true
} else {
log.Log(WARN, "setCheckbox() got false", w.node.State.Checked)
w.labelN = " " + w.node.State.Label
// w.changed = true
}
// t := len(w.labelN) + 3
// w.gocuiSize.w1 = w.gocuiSize.w0 + t
w.Hide()
w.Show()
}

489
color.go
View File

@ -3,327 +3,202 @@
package main package main
// information about how terminfo works
// https://jvns.ca/blog/2024/10/01/terminal-colours/
import ( import (
"math/rand" "math/rand"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
"go.wit.com/log" "go.wit.com/log"
) )
// simple colors for light and dark //w.v.SelBgColor = gocui.ColorCyan
//color.go: w.v.SelFgColor = gocui.ColorBlack
//color.go: w.v.BgColor = gocui.ColorGreen
// information about how terminfo works type colorT struct {
// https://jvns.ca/blog/2024/10/01/terminal-colours/ frame gocui.Attribute
fg gocui.Attribute
bg gocui.Attribute
selFg gocui.Attribute
selBg gocui.Attribute
name string
}
// TODO: move all this to a protobuf var none gocui.Attribute = gocui.AttrNone
var lightPurple gocui.Attribute = gocui.GetColor("#DDDDDD") // light purple
var darkPurple gocui.Attribute = gocui.GetColor("#FFAA55") // Dark Purple
var heavyPurple gocui.Attribute = gocui.GetColor("#88AA55") // heavy purple
var powdererBlue gocui.Attribute = gocui.GetColor("#B0E0E6") // w3c 'powerder blue'
var superLightGrey gocui.Attribute = gocui.GetColor("#55AAFF") // super light grey
// Standard defined colors from gocui:
// ColorBlack ColorRed ColorGreen ColorYellow ColorBlue ColorMagenta ColorCyan ColorWhite
// v.BgColor = gocui.GetColor("#111111") // crazy red
// v.BgColor = gocui.GetColor("#FF9911") // heavy red
// v.SelBgColor = gocui.GetColor("#FFEE11") // blood red
// v.BgColor = gocui.GetColor("#55AAFF") // super light grey
// v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow
// Normal Text On mouseover
//
// Widget Frame Text background Text background
var colorWindow colorT = colorT{
frame: none,
fg: gocui.ColorBlue,
bg: none,
selFg: none,
selBg: powdererBlue,
name: "normal window",
}
var colorActiveW colorT = colorT{none, none, powdererBlue, none, powdererBlue, "active window"}
var colorTab colorT = colorT{gocui.ColorBlue, gocui.ColorBlue, none, none, powdererBlue, "normal tab"}
var colorActiveT colorT = colorT{gocui.ColorBlue, none, powdererBlue, none, powdererBlue, "active tab"}
// var colorLabel colorT = colorT{none, none, superLightGrey, none, superLightGrey, "normal label"}
// var colorGroup colorT = colorT{none, none, superLightGrey, none, superLightGrey, "normal group"}
var colorDisabled colorT = colorT{
frame: superLightGrey,
fg: superLightGrey,
bg: superLightGrey,
selFg: gocui.ColorBlack,
selBg: gocui.ColorBlack,
name: "disabled widget",
}
var colorLabel colorT = colorT{
frame: gocui.ColorWhite,
fg: none,
bg: none,
selFg: gocui.ColorWhite,
selBg: none,
name: "normal label",
}
var colorGroup colorT = colorT{
frame: none,
fg: none,
bg: none,
selFg: gocui.ColorWhite,
selBg: none,
name: "normal label",
}
var colorButton colorT = colorT{
frame: gocui.ColorGreen,
fg: none,
bg: none,
selFg: gocui.ColorGreen,
selBg: none,
name: "normal button",
}
var colorDropdown colorT = colorT{
frame: gocui.ColorYellow,
fg: none,
bg: none,
selFg: gocui.ColorYellow,
selBg: gocui.ColorBlack,
name: "normal dropdown",
}
var colorCombobox colorT = colorT{
frame: gocui.ColorBlue,
fg: none,
bg: none,
selFg: gocui.ColorBlue,
selBg: gocui.ColorBlack,
name: "normal combobox",
}
var colorCheckbox colorT = colorT{
frame: gocui.ColorRed,
fg: none,
bg: none,
selFg: gocui.ColorRed,
selBg: gocui.ColorBlack,
name: "normal checkbox",
}
// widget debugging colors. these widgets aren't displayed unless you are debugging
var colorRoot colorT = colorT{gocui.ColorRed, none, powdererBlue, none, gocui.ColorBlue, "debug root"}
var colorFlag colorT = colorT{gocui.ColorRed, none, powdererBlue, none, gocui.ColorGreen, "debug flag"}
var colorBox colorT = colorT{gocui.ColorRed, none, lightPurple, none, gocui.ColorCyan, "debug box"}
var colorGrid colorT = colorT{gocui.ColorRed, none, lightPurple, none, gocui.ColorRed, "debug grid"}
var colorNone colorT = colorT{none, none, none, none, none, "debug none"}
// actually sets the colors for the gocui element
// the user will see the colors change when this runs
// TODO: add black/white only flag for ttyS0 // TODO: add black/white only flag for ttyS0
// TODO: fix kvm/qemu serial console & SIGWINCH. // TODO: or fix kvm/qemu serial console & SIGWINCH.
// TODO: check minicom (doesn't work) // TODO: and minicom and uboot and 5 million other things.
// TODO: fix riscv boards // TODO: maybe enough of us could actually do that if we made it a goal.
// TODO: start with riscv boards and fix it universally there
// DONE ON ENABLE() WIDGET // TODO: so just a small little 'todo' item here
// restores the last saved color and makes it active func (tk *guiWidget) setColor(newColor *colorT) {
func (tk *guiWidget) restoreEnableColor() { if tk.color == newColor {
if tk.color == nil { // nothing to do since the colors have nto changed
tk.color = new(colorT) return
} }
tk.color.frame = tk.colorLast.frame tk.color = newColor
tk.color.fg = tk.colorLast.fg
tk.color.bg = tk.colorLast.bg
tk.color.selFg = tk.colorLast.selFg
tk.color.selBg = tk.colorLast.selBg
tk.activateColor()
}
// DONE ON DISABLE() WIDGET
// makes the button look disabled
func (tk *guiWidget) setColorDisable() {
if tk.color == nil {
tk.color = new(colorT)
}
// save the current color
tk.color.frame = superLightGrey
tk.color.fg = gocui.ColorBlack
tk.color.bg = superLightGrey
tk.color.selFg = superLightGrey
tk.color.selBg = superLightGrey
tk.activateColor()
}
// sets the current gocui highlight colors
func (tk *guiWidget) activateColor() {
if tk.v == nil { if tk.v == nil {
return return
} }
tk.v.FrameColor = tk.color.frame if tk.color == nil {
tk.v.FgColor = tk.color.fg // log.Log(NOW, "Set the node to color = nil")
tk.v.BgColor = tk.color.bg tk.color = &colorNone
tk.v.SelFgColor = tk.color.selFg }
tk.v.SelBgColor = tk.color.selBg // log.Log(NOW, "Set the node to color =", tk.color.name)
tk.Show()
} }
// saves the color and makes it active func (w *guiWidget) disableColor() {
func (tk *guiWidget) updateColor() { if w.color != &colorDisabled {
if tk.v == nil { w.defaultColor = w.color
}
w.setColor(&colorDisabled)
}
func (w *guiWidget) enableColor() {
w.setColor(w.defaultColor)
}
func (w *guiWidget) setDefaultHighlight() {
if w.v == nil {
log.Log(ERROR, "SetColor() failed on view == nil")
return return
} }
if tk.color != nil { w.v.SelBgColor = gocui.ColorGreen
tk.colorLast.frame = tk.color.frame w.v.SelFgColor = gocui.ColorBlack
tk.colorLast.fg = tk.color.fg
tk.colorLast.bg = tk.color.bg
tk.colorLast.selFg = tk.color.selFg
tk.colorLast.selBg = tk.color.selBg
}
tk.activateColor()
} }
// Below are all the colors. TODO: move to protobuf and save in a config file func randColor() gocui.Attribute {
colors := []string{"Green", "#FFAA55", "Yellow", "Blue", "Red", "Black", "White"}
func (tk *guiWidget) setColorWindowFrame() { i := rand.Intn(len(colors))
if tk.color == nil { log.Log(NOW, "randColor() i =", i)
tk.color = new(colorT) return gocui.GetColor(colors[i])
}
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
} }
tk.updateColor() func (w *guiWidget) redoColor(draw bool) {
if w == nil {
return
} }
// weird. lots of color problems for me on debian sid using the traditional Andy Herzfield 'gnome' log.Sleep(.05)
func (tk *guiWidget) setColorWindowTitleActive() { w.setDefaultHighlight()
if tk.color == nil { // w.setDefaultWidgetColor()
tk.color = new(colorT) w.Show()
}
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor() for _, child := range w.children {
child.redoColor(draw)
} }
func (tk *guiWidget) setColorWindowTitle() {
if tk.color == nil {
tk.color = new(colorT)
} }
if me.dark { // use a dark color palette
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor()
}
func (tk *guiWidget) setColorBG() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorWhite
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.AttrNone
}
tk.updateColor()
}
func (tk *guiWidget) setColorLabel() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
func (tk *guiWidget) setColorLabelTable() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlack
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorGreen
}
tk.updateColor()
}
func (tk *guiWidget) setColorButtonDense() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorBlue
tk.color.selBg = gocui.AttrNone
}
tk.updateColor()
}
func (tk *guiWidget) setColorNotifyIcon() {
if tk.color == nil {
tk.color = new(colorT)
}
tk.color.frame = gocui.AttrNone
tk.color.fg = gocui.ColorWhite
tk.color.bg = gocui.ColorBlue
tk.color.selFg = gocui.ColorBlue
tk.color.selBg = gocui.AttrNone
tk.updateColor()
}
func (tk *guiWidget) setColorButton() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorBlack
tk.color.fg = gocui.ColorBlue
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
} else {
tk.color.frame = gocui.ColorBlue
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorWhite
tk.color.selBg = gocui.ColorBlue
}
tk.updateColor()
}
func (tk *guiWidget) setColorInput() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorYellow
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorYellow
tk.color.selBg = gocui.ColorBlack
} else {
tk.color.frame = gocui.ColorYellow
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.ColorYellow
tk.color.selBg = gocui.ColorBlack
}
tk.updateColor()
}
func (tk *guiWidget) setColorModal() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.ColorRed
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorBlack
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
// what genius figured this out?
func (tk *guiWidget) setColorTextbox() {
if tk.color == nil {
tk.color = new(colorT)
}
if me.dark {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.ColorRed
tk.color.bg = gocui.ColorBlack
tk.color.selFg = gocui.ColorBlack
tk.color.selBg = gocui.AttrNone
} else {
tk.color.frame = gocui.ColorRed
tk.color.fg = gocui.AttrNone
tk.color.bg = gocui.AttrNone
tk.color.selFg = gocui.AttrNone
tk.color.selBg = gocui.ColorWhite
}
tk.updateColor()
}
// just notes down here
// what genius figured this out? // what genius figured this out?
// originally from github.com/dimasma0305/GoFetch // originally from github.com/dimasma0305/GoFetch
@ -344,29 +219,3 @@ func get_teminal_color_palette() string {
return color1 + " " + color2 + " " + color3 + " " + color4 + " " + color5 + " " + color6 + " " + color7 + " " + color8 return color1 + " " + color2 + " " + color3 + " " + color4 + " " + color5 + " " + color6 + " " + color7 + " " + color8
} }
func randColor() gocui.Attribute {
colors := []string{"Green", "#FFAA55", "Yellow", "Blue", "Red", "Black", "White"}
i := rand.Intn(len(colors))
log.Log(NOW, "randColor() i =", i)
return gocui.GetColor(colors[i])
}
var none gocui.Attribute = gocui.AttrNone
var colorNone colorT = colorT{none, none, none, none, none, "debug none"}
var lightPurple gocui.Attribute = gocui.GetColor("#DDDDDD") // light purple
var darkPurple gocui.Attribute = gocui.GetColor("#FFAA55") // Dark Purple
var heavyPurple gocui.Attribute = gocui.GetColor("#88AA55") // heavy purple
var powdererBlue gocui.Attribute = gocui.GetColor("#B0E0E6") // w3c 'powerder blue'
var superLightGrey gocui.Attribute = gocui.GetColor("#55AAFF") // super light grey
// Standard defined colors from gocui:
// ColorBlack ColorRed ColorGreen ColorYellow ColorBlue ColorMagenta ColorCyan ColorWhite
// v.BgColor = gocui.GetColor("#111111") // crazy red
// v.BgColor = gocui.GetColor("#FF9911") // heavy red
// v.SelBgColor = gocui.GetColor("#FFEE11") // blood red
// v.BgColor = gocui.GetColor("#55AAFF") // super light grey
// v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow

View File

@ -6,101 +6,55 @@ package main
import ( import (
"fmt" "fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log" "go.wit.com/log"
"go.wit.com/widget" "go.wit.com/widget"
) )
func (w *guiWidget) dumpTree(s string) { func (w *guiWidget) dumpTree(s string) {
// log.Log(ERROR, "dump w", w.WidgetId(), w.WidgetType, w.String()) // log.Log(ERROR, "dump w", w.node.WidgetId, w.WidgetType, w.String())
if w == nil { if w == nil {
log.Log(ERROR, "dump w.TK == nil", w.WidgetId(), w.WidgetType(), w.String()) log.Log(ERROR, "dump w.TK == nil", w.node.WidgetId, w.WidgetType, w.String())
return return
} }
w.dumpWidget("dumpTree() " + s) w.showWidgetPlacement("dumpTree() " + s)
for _, child := range w.children { for _, child := range w.children {
child.dumpTree(s) child.dumpTree(s)
} }
} }
func (w *guiWidget) dumpWindows(s string) { func (w *guiWidget) showWidgetPlacement(s string) {
// log.Log(ERROR, "dump w", w.WidgetId(), w.WidgetType, w.String())
if w == nil {
log.Log(ERROR, "dump w.TK == nil", w.WidgetId(), w.WidgetType(), w.String())
return
}
if w.WidgetType() == widget.Window {
s += fmt.Sprintf(" F(%d,%d)", w.force.w0, w.force.h0)
// can't set this here. doesn't work
// w.full.w0 = w.force.w0
// w.full.h0 = w.force.h0
w.dumpWidget("dumpWindow() " + s)
w.windowFrame.dumpWidget("dumpFrame() " + s)
}
for _, child := range w.children {
child.dumpWindows(s)
}
}
// a standard function to print out information about a widget
func (tk *guiWidget) dumpWidget(s string) {
var s1 string var s1 string
var pId int var pId int
// tk.verifyRect() if w.node.Parent == nil {
if tk.parent == nil { log.Log(INFO, "showWidgetPlacement() parent == nil", w.node.WidgetId, w.cuiName)
log.Logf(WARN, "showWidgetPlacement() parent == nil wId=%d cuiName=%s", tk.WidgetId(), tk.cuiName)
pId = 0 pId = 0
} else { } else {
pId = tk.parent.WidgetId() pId = w.node.Parent.WidgetId
} }
s1 = fmt.Sprintf("(wId,pId)=(%4d,%4d) ", tk.WidgetId(), pId) s1 = fmt.Sprintf("(wId,pId)=(%4d,%4d) ", w.node.WidgetId, pId)
sizeW, sizeH := tk.Size() sizeW, sizeH := w.Size()
hide := "S" s1 += fmt.Sprintf("size=(%3d,%3d)", sizeW, sizeH)
if tk.Hidden() { if w.Visible() {
hide = "H"
}
s1 += fmt.Sprintf("size=(%3d,%3d)%s)", sizeW, sizeH, hide)
if tk.Visible() {
s1 += fmt.Sprintf("gocui=(%3d,%3d,%3d,%3d)", s1 += fmt.Sprintf("gocui=(%3d,%3d,%3d,%3d)",
tk.gocuiSize.w0, tk.gocuiSize.h0, tk.gocuiSize.w1, tk.gocuiSize.h1) w.gocuiSize.w0, w.gocuiSize.h0, w.gocuiSize.w1, w.gocuiSize.h1)
} else { } else {
s1 += fmt.Sprintf(" %3s %3s %3s %3s ", "", "", "", "") s1 += fmt.Sprintf(" %3s %3s %3s %3s ", "", "", "", "")
} }
s1 += fmt.Sprintf(" full=(%3d,%3d,%3d,%3d)", tk.full.w0, tk.full.h0, tk.full.w1, tk.full.h1) if w.node.Parent != nil {
if tk.parent != nil { if w.node.Parent.WidgetType == widget.Grid {
if tk.parent.WidgetType() == widget.Grid { s1 += fmt.Sprintf("At(%3d,%3d) ", w.node.State.AtW, w.node.State.AtH)
s1 += fmt.Sprintf("At(%3d,%3d)", tk.GridW(), tk.GridH())
s1 += fmt.Sprintf("(%3d,%3d) ", tk.parent.widths[tk.GridW()], tk.parent.heights[tk.GridH()])
} else { } else {
s1 += fmt.Sprintf(" %3s %3s ", "", "") s1 += fmt.Sprintf(" %3s %3s ", "", "")
s1 += fmt.Sprintf(" %3s %3s ", "", "")
} }
} else { } else {
s1 += fmt.Sprintf(" %3s %3s ", "", "") s1 += fmt.Sprintf(" %3s %3s ", "", "")
} }
var end string var end string
if tk.WidgetType() == widget.Box { if w.node.WidgetType == widget.Box {
end = fmt.Sprintf("%-8s %-8s %s %s", tk.WidgetType(), tk.cuiName, tk.Direction().String(), tk.String()) end = fmt.Sprintf("%5s %-8s %s", w.cuiName, w.node.WidgetType, w.node.State.Direction.String())
} else { } else {
end = fmt.Sprintf("%-8s %-8s %s", tk.WidgetType(), tk.cuiName, tk.String()) end = fmt.Sprintf("%5s %-8s %s", w.cuiName, w.node.WidgetType, w.String())
} }
if tk.node.InTable() { log.Log(NOW, s1, s, end)
// log.Log(GOCUI, "findParentTAble()", tk.labelN, tk.cuiName, tk.node.WidgetId)
end += " (table)"
}
log.Log(GOCUI, s1, s, end)
}
func printWidgetTree(g *gocui.Gui, v *gocui.View) error {
me.treeRoot.ListWidgets()
return nil
}
func printWidgetPlacements(g *gocui.Gui, v *gocui.View) error {
w := me.treeRoot.TK.(*guiWidget)
w.dumpTree("MM")
w.dumpWindows("WW")
return nil
} }

View File

@ -3,113 +3,198 @@
package main package main
// simulates a dropdown menu in gocui
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log" log "go.wit.com/log"
"go.wit.com/toolkits/tree" "go.wit.com/toolkits/tree"
"go.wit.com/widget" "go.wit.com/widget"
) )
// create a new widget in the binary tree // dropdowns don't exist so this is an attempt to pretend they exist
func makeNewFlagWidget(wId int) *guiWidget { // by having a fake window view in gocui that appears with the dropdown
// items. by detecting where the user clicks, we should be
// able to set the text of the button to that value (and
// rezise it.
// the thing is this is so damned complicated because it
// is very hard to debug this. thanks to the floating on
// screen debugging output I might be able to figure it
// it out now. maybe. notsure.
func makeDropdownView(ddItems string) *guiWidget {
newNode := addDropdown()
tk := newNode.TK.(*guiWidget)
tk.labelN = ddItems
tk.SetText(ddItems)
tk.gocuiSize.w0 = 100
tk.gocuiSize.w1 = 120
tk.gocuiSize.h0 = 15
tk.gocuiSize.h1 = 18
tk.v, _ = me.baseGui.SetView("ddview",
tk.gocuiSize.w0,
tk.gocuiSize.h0,
tk.gocuiSize.w1,
tk.gocuiSize.h1, 0)
if tk.v == nil {
return tk
}
tk.v.Wrap = true
tk.v.Frame = true
tk.v.Clear()
fmt.Fprint(tk.v, ddItems)
tk.Show()
return tk
}
func addDropdown() *tree.Node {
n := new(tree.Node) n := new(tree.Node)
n.WidgetType = widget.Flag n.WidgetType = widget.Flag
n.WidgetId = wId n.WidgetId = -222
n.ParentId = 0 n.ParentId = 0
// store the internal toolkit information // store the internal toolkit information
tk := new(guiWidget) tk := new(guiWidget)
tk.frame = true tk.frame = true
tk.labelN = "DropBox text"
tk.node = n tk.node = n
if tk.node.Parent == nil { // copy the data from the action message
tk.node.Parent = me.treeRoot tk.node.State.Label = "DropBox"
}
// set the name used by gocui to the id // set the name used by gocui to the id
tk.cuiName = fmt.Sprintf("%d DR", wId) tk.cuiName = "-1 DR"
tk.setColorInput() tk.color = &colorFlag
// add this new widget on the binary tree // add this new widget on the binary tree
tk.parent = me.treeRoot.TK.(*guiWidget) tk.parent = me.treeRoot.TK.(*guiWidget)
if tk.parent == nil { if tk.parent == nil {
panic("makeNewFlagWidget() didn't get treeRoot guiWidget") panic("addDropdown() didn't get treeRoot guiWidget")
} else { } else {
tk.parent.children = append(tk.parent.children, tk) tk.parent.children = append(tk.parent.children, tk)
} }
n.TK = tk n.TK = tk
return tk return n
} }
func (tk *guiWidget) showDropdown() { func (tk *guiWidget) showDropdown() {
if me.dropdown.tk == nil { var ddItems string
// should only happen once
me.dropdown.tk = makeNewFlagWidget(me.dropdown.wId)
me.dropdown.tk.dumpWidget("init() dropdown")
}
if me.dropdown.tk == nil {
log.Log(GOCUI, "showDropdown() Is Broken!")
return
}
// todo: fix this after switching to protobuf // todo: fix this after switching to protobuf
me.dropdown.items = []string{} // zero out whatever was there before // var items []string
// items = tk.node.State.Strings
//for i, s := range items {
/*
for i, s := range tk.node.Strings() { for i, s := range tk.node.Strings() {
log.Log(GOCUI, "showDropdown()", tk.String(), i, s) // log.Log(GOCUI, "showDropdown()", tk.String(), i, s)
me.dropdown.items = append(me.dropdown.items, s) ddItems += s + "\n"
} }
log.Log(GOCUI, "new dropdown items should be set to:", me.dropdown.items) */
// log.Log(GOCUI, "new dropdown items should be set to:", ddItems)
// sizeW, sizeH := tk.Size()
// log.Log(GOCUI, "showDropdown() size W,H=", sizeW, sizeH)
startW, startH := tk.Position() startW, startH := tk.Position()
log.Log(GOCUI, "showDropdown() SHOWING AT W,H=", startW, startH) // log.Log(GOCUI, "showDropdown() location W,H=", startW, startH)
me.dropdown.tk.Hide() me.dropdownV.MoveToOffset(startW+3, startH+2)
me.dropdown.tk.MoveToOffset(startW+3, startH+2) me.dropdownV.labelN = ddItems
me.dropdown.tk.labelN = strings.Join(me.dropdown.items, "\n") me.dropdownV.Show()
me.dropdown.tk.Show() }
me.dropdown.active = true
me.dropdown.callerTK = tk
r := me.dropdown.tk.gocuiSize // set the 'full' size so that mouse clicks are sent here func hideDDview() error {
me.dropdown.tk.full.w0 = r.w0 w, h := me.baseGui.MousePosition()
me.dropdown.tk.full.w1 = r.w1 log.Log(GOCUI, "hide dropdown menu() view msgMouseDown (w,h) =", w, h)
me.dropdown.tk.full.h0 = r.h0 if me.dropdownV == nil {
me.dropdown.tk.full.h1 = r.h1 return gocui.ErrUnknownView
}
if me.dropdownV.v == nil {
return gocui.ErrUnknownView
}
me.dropdownV.SetVisible(false)
return nil
}
me.dropdown.tk.dumpWidget("showDropdown()") func showDDview() error {
w, h := me.baseGui.MousePosition()
log.Log(GOCUI, "show dropdown menu() view msgMouseDown (w,h) =", w, h)
if me.dropdownV == nil {
return gocui.ErrUnknownView
}
if me.dropdownV.v == nil {
return gocui.ErrUnknownView
}
me.dropdownV.SetVisible(true)
return nil
} }
// if there is a drop down view active, treat it like a dialog box and close it // if there is a drop down view active, treat it like a dialog box and close it
func (w *guiWidget) dropdownClicked(mouseW, mouseH int) string { func (w *guiWidget) dropdownClicked(mouseW, mouseH int) string {
w.Hide() w.Hide()
me.dropdown.active = false
// only need height to figure out what line in the dropdown menu the user clicked startW, startH := w.Position()
_, startH := w.Position() log.Log(GOCUI, "dropdownClicked() start (w,h) =", startW, startH)
log.Log(GOCUI, "dropdownClicked() at (w,h) =", mouseW, mouseH)
itemNumber := mouseH - startH itemNumber := mouseH - startH
items := me.dropdown.items items := strings.Split(w.labelN, "\n")
// log.Log(GOCUI, "dropdownClicked() look for item", itemNumber, "len(items) =", len(items)) log.Log(GOCUI, "dropdownClicked() look for item", itemNumber, "len(items) =", len(items))
if itemNumber < 1 { if itemNumber < 1 {
return "" return ""
} }
if len(items) >= itemNumber { if len(items) >= itemNumber {
// log.Log(GOCUI, "dropdownClicked() found", items[itemNumber-1]) log.Log(GOCUI, "dropdownClicked() found", items[itemNumber-1])
if items[itemNumber-1] != "" { if items[itemNumber-1] != "" {
if me.dropdown.tk != nil { if me.dropdownW != nil {
// log.Log(GOCUI, "dropdownClicked() send event for", me.dropdownW.cuiName, me.dropdownW.node.WidgetType) log.Log(GOCUI, "dropdownClicked() send event for", me.dropdownW.cuiName, me.dropdownW.WidgetType)
me.dropdown.callerTK.SetText(items[itemNumber-1]) me.dropdownW.SetText(items[itemNumber-1])
me.dropdown.callerTK.node.SetCurrentS(items[itemNumber-1]) me.dropdownW.node.SetCurrentS(items[itemNumber-1])
me.myTree.SendUserEvent(me.dropdown.callerTK.node) me.myTree.SendUserEvent(me.dropdownW.node)
} }
} }
return items[itemNumber-1] return items[itemNumber-1]
} }
return "" return ""
} }
var dtoggle bool = false // temporarily tracking show & hide
var doffset int = 5 // how many spaces over the dropdown menu should be from the mouse
func dropdownUnclicked(w, h int) {
var d *guiWidget
if me.dropdownV == nil {
log.Log(GOCUI, "mouseUp() dropdownV = nil")
return
}
// examine everything under X & Y on the screen)
for _, tk := range findByXY(w, h) {
// log.Log(GOCUI, fmt.Sprintf("findByXY() (dropdown) %-8s wId=%4d at (%3d,%3d) %s", tk.WidgetType, tk.node.WidgetId, w, h, tk.node.String()))
if tk.WidgetType == widget.Dropdown {
d = tk
}
}
if d == nil {
// log.Log(GOCUI, fmt.Sprintf("dropdownUnclicked() there was no dropdown widget at (w=%d h=%d)", mouseW, mouseH))
return
}
// log.Log(GOCUI, "dropdownUnclicked()", d.node.Strings(), "end. now try to enable me.dropdownV")
dtext := strings.Join(d.node.Strings(), "\n")
tk := me.dropdownV
if dtoggle {
log.Log(GOCUI, "dropdownUnclicked() set visible=false")
tk.Hide()
dtoggle = false
tk.SetText("goodbye")
} else {
log.Log(GOCUI, "dropdownUnclicked() set visible=true")
tk.MoveToOffset(w+doffset, h)
tk.SetText(dtext)
tk.Show()
dtoggle = true
}
}

View File

@ -4,67 +4,54 @@
package main package main
import ( import (
"fmt"
"syscall" "syscall"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
"go.wit.com/log" "go.wit.com/log"
"go.wit.com/widget"
) )
// THIS IS A STANDARD.
// register how the 'gocui' will work as a GO toolkit plugin // register how the 'gocui' will work as a GO toolkit plugin
// all applications will use these keys. they are universal. // all applications will use these keys. they are universal.
// tells 'gocui' where to send events // tells 'gocui' where to send events
func registerHandlers(g *gocui.Gui) { func registerHandlers(g *gocui.Gui) {
defer func() { keyForced, modForced := gocui.MustParse("ctrl+z") // setup ctrl+z
if r := recover(); r != nil {
log.Info("EVENT BINDINGS recovered in r", r)
return
}
}()
// mouse handlers // mouse handlers
g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown) // normal left mouse down g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown) // normal left mouse down
g.SetKeybinding("", gocui.MouseLeft, gocui.ModMouseCtrl, ctrlDown) // mouse with the ctrl key held down g.SetKeybinding("", gocui.MouseLeft, gocui.ModMouseCtrl, ctrlDown) // mouse with the ctrl key held down
g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp) // mouse button release g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp) // mouse button release
g.SetKeybinding("", gocui.MouseWheelUp, gocui.ModNone, wheelsUp) // mouse button release
g.SetKeybinding("", gocui.MouseWheelDown, gocui.ModNone, wheelsDown) // mouse button release
// Ctrl key handlers // Ctrl key handlers
g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, doExit) // CTRL-C : exits the application g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, doExit) // CTRL-C : exits the application
g.SetKeybinding("", gocui.KeyCtrlV, gocui.ModNone, doPanic) // CTRL-V : force a panic() g.SetKeybinding("", gocui.KeyCtrlV, gocui.ModNone, doPanic) // CTRL-V : force a panic()
g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, openDebuggger) // CTRL-D : open the (D)ebugger g.SetKeybinding("", gocui.KeyCtrlD, gocui.ModNone, openDebuggger) // CTRL-D : open the (D)ebugger
keyForced, modForced := gocui.MustParse("ctrl+z") // setup ctrl+z
g.SetKeybinding("", keyForced, modForced, handle_ctrl_z) // CTRL-Z :cleverly let's you background gocui (breaks cursor mouse on return) g.SetKeybinding("", keyForced, modForced, handle_ctrl_z) // CTRL-Z :cleverly let's you background gocui (breaks cursor mouse on return)
g.SetKeybinding("", gocui.KeyEsc, gocui.ModNone, doEsc) // escape key
// regular keys // regular keys
g.SetKeybinding("", 'H', gocui.ModNone, theHelp) // 'H' toggles on and off the help menu g.SetKeybinding("", '?', gocui.ModNone, theHelp) // '?' toggles on and off the help menu
g.SetKeybinding("", 'O', gocui.ModNone, theStdout) // 'O' toggle the STDOUT window g.SetKeybinding("", 'w', gocui.ModNone, doWindow) // 'w' close all windows
g.SetKeybinding("", 'D', gocui.ModNone, theDarkness) // 'D' toggles light/dark mode g.SetKeybinding("", 'r', gocui.ModNone, widgetRefresh) // 'r' screen refresh
g.SetKeybinding("", 'q', gocui.ModNone, doExit) // 'q' exit g.SetKeybinding("", 'q', gocui.ModNone, doExit) // 'q' exit
g.SetKeybinding("", gocui.KeyTab, gocui.ModNone, tabCycleWindows) // '2' use this to test new ideas
// stdout keys
g.SetKeybinding("", gocui.KeyPgup, gocui.ModNone, stdoutPgup) // Pgup scroll up the Stdout buffer
g.SetKeybinding("", gocui.KeyPgdn, gocui.ModNone, stdoutPgdn) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyHome, gocui.ModNone, stdoutHome) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone, stdoutArrowUp) // Pgdn scroll down the Stdout buffer
g.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone, stdoutArrowDown) // Pgdn scroll down the Stdout buffer
// debugging // debugging
g.SetKeybinding("", '2', gocui.ModNone, theNotsure) // '2' use this to test new ideas
g.SetKeybinding("", 'S', gocui.ModNone, theSuperMouse) // 'S' Super Mouse mode!
g.SetKeybinding("", 'M', gocui.ModNone, printWidgetPlacements) // 'M' list all widgets with positions
g.SetKeybinding("", 'L', gocui.ModNone, printWidgetTree) // 'L' list all widgets in tree view
g.SetKeybinding("", 'f', gocui.ModNone, theFind) // 'f' shows what is under your mouse g.SetKeybinding("", 'f', gocui.ModNone, theFind) // 'f' shows what is under your mouse
g.SetKeybinding("", 'S', gocui.ModNone, setSuperMouse) // 'S' Super Mouse mode!
g.SetKeybinding("", 'h', gocui.ModNone, theHide) // 'h' hide all widgets
g.SetKeybinding("", 'M', gocui.ModNone, dumpWidgetPlacement) // 'M' list all widgets with positions
g.SetKeybinding("", 'L', gocui.ModNone, dumpWidgets) // 'L' list all widgets in tree view
g.SetKeybinding("", 'd', gocui.ModNone, theLetterD) // 'd' toggles on and off debugging buttons g.SetKeybinding("", 'd', gocui.ModNone, theLetterD) // 'd' toggles on and off debugging buttons
g.SetKeybinding("", 'r', gocui.ModNone, reverseStdout) // 'r' turns scrolling of STDOUT upside down g.SetKeybinding("", '2', gocui.ModNone, theNotsure) // '2' for testing new ideas
g.SetKeybinding("", 'q', gocui.ModNone, quit) // 'q' only exits gocui. plugin stays alive (?) g.SetKeybinding("", 'q', gocui.ModNone, quit) // 'q' only exits gocui. plugin stays alive (?)
} }
// flips on 'super mouse' mode // this was awesome for debugging gocui. never remove this code. // flips on 'super mouse' mode
// while this is turned on, it will print out every widget found under the mouse // while this is turned on, it will print out every widget found under the mouse
func theSuperMouse(g *gocui.Gui, v *gocui.View) error { func setSuperMouse(g *gocui.Gui, v *gocui.View) error {
if me.supermouse { if me.supermouse {
log.Log(GOCUI, "supermouse off") log.Log(GOCUI, "supermouse off")
me.supermouse = false me.supermouse = false
@ -75,80 +62,26 @@ func theSuperMouse(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
// use this to test code ideas // put whatever you want here and hit '2' to activate it // use this to test code ideas
func theNotsure(g *gocui.Gui, v *gocui.View) error { func theNotsure(g *gocui.Gui, v *gocui.View) error {
log.Info("got to theNotsure(). now what? dark =", me.dark) log.Info("got keypress 2. now what?")
me.refresh = true w, h := g.MousePosition()
log.Info("running VerifyParentId()") for _, tk := range findByXY(w, h) {
me.treeRoot.VerifyParentId() if tk.WidgetType == widget.Stdout {
/* log.Log(GOCUI, fmt.Sprintf("findByXY() '2' key %-8s wId=%4d at (%3d,%3d) %s", tk.WidgetType, tk.node.WidgetId, w, h, tk.node.String()))
if me.debug { log.Info("skipping stdout")
log.Info("debugging off") continue
me.debug = false
} else {
log.Info("debugging on")
me.debug = true
} }
win := findWindowUnderMouse() log.Log(GOCUI, fmt.Sprintf("findByXY() HIDDING %-8s wId=%4d at (%3d,%3d) %s", tk.WidgetType, tk.node.WidgetId, w, h, tk.node.String()))
if win != nil { tk.Hide()
win.dumpWidget("found() win. redrawing window:")
win.makeWindowActive()
}
*/
return nil
}
func theDarkness(g *gocui.Gui, v *gocui.View) error {
if me.dark {
me.dark = false
log.Info("you have seen the light")
} else {
me.dark = true
log.Info("you have entered into darkness (you may need to trigger SIGWINCH)")
log.Info("or maybe open a new window. notsure. This obviously isn't finished.")
log.Info("submit patches to this and you will definitely get free cloud credits at WIT")
} }
return nil return nil
} }
func wheelsUp(g *gocui.Gui, v *gocui.View) error { func theHide(g *gocui.Gui, v *gocui.View) error {
stdoutWheelsUp() var w *guiWidget
return nil w = me.treeRoot.TK.(*guiWidget)
} w.hideWidgets()
func wheelsDown(g *gocui.Gui, v *gocui.View) error {
stdoutWheelsDown()
return nil
}
func tabCycleWindows(g *gocui.Gui, v *gocui.View) error {
// log.Info("try to switch windows here")
if len(me.allwin) != len(findWindows()) {
me.allwin = findWindows()
}
tk := findNextWindow()
if tk == nil {
log.Info("findNextWindow() err. returned nil")
return nil
}
tk.makeWindowActive()
return nil
}
func doEsc(g *gocui.Gui, v *gocui.View) error {
log.Info("got escape key")
if me.dropdown.active {
me.dropdown.tk.Hide()
me.dropdown.active = false
log.Info("escaped from dropdown")
me.baseGui.SetCurrentView(me.notify.clock.tk.cuiName)
}
if me.textbox.active {
me.textbox.tk.Hide()
me.textbox.active = false
log.Info("escaped from textbox")
me.baseGui.SetCurrentView(me.notify.clock.tk.cuiName)
}
return nil return nil
} }
@ -168,6 +101,32 @@ func doPanic(g *gocui.Gui, v *gocui.View) error {
log.Log(GOCUI, "do panic() here") log.Log(GOCUI, "do panic() here")
standardClose() standardClose()
panic("forced panic in gocui") panic("forced panic in gocui")
return nil
}
func dumpWidgets(g *gocui.Gui, v *gocui.View) error {
me.treeRoot.ListWidgets()
tk := me.logStdout.TK.(*guiWidget)
// msg := fmt.Sprintf("test out kb %d\n", ecount)
// tk.Write([]byte(msg))
if tk == nil {
log.Log(ERROR, "tk = nil")
}
if tk.v == nil {
log.Log(ERROR, "tk.v = nil")
} else {
log.Log(ERROR, "setting log.CaptureMode(tk.v)")
log.Log(ERROR, "setting log.CaptureMode(tk.v)")
log.CaptureMode(tk)
}
return nil
}
func dumpWidgetPlacement(g *gocui.Gui, v *gocui.View) error {
w := me.treeRoot.TK.(*guiWidget)
w.dumpTree("MM")
return nil
} }
func openDebuggger(g *gocui.Gui, v *gocui.View) error { func openDebuggger(g *gocui.Gui, v *gocui.View) error {
@ -178,22 +137,7 @@ func openDebuggger(g *gocui.Gui, v *gocui.View) error {
func theFind(g *gocui.Gui, v *gocui.View) error { func theFind(g *gocui.Gui, v *gocui.View) error {
w, h := g.MousePosition() w, h := g.MousePosition()
for _, tk := range findByXY(w, h) { for _, tk := range findByXY(w, h) {
// tk.v.BgColor = gocui.ColorGreen log.Log(GOCUI, fmt.Sprintf("findByXY() 'f' key %-8s wId=%4d at (%3d,%3d) %s", tk.WidgetType, tk.node.WidgetId, w, h, tk.node.String()))
tk.dumpWidget("theFind()")
// tk.verifyRect()
}
return nil
}
func reverseStdout(g *gocui.Gui, v *gocui.View) error {
if me.stdout.reverse {
me.stdout.reverse = false
log.Info("stdout scrolling normal")
} else {
me.stdout.reverse = true
log.Info("stdout scrolling is reversed. this is sometimes useful when you")
log.Info("only need to see a few most recent lines and have the STDOUT window")
log.Info("take up minimal realestate at the bottom of your window")
} }
return nil return nil
} }
@ -209,24 +153,39 @@ func theLetterD(g *gocui.Gui, v *gocui.View) error {
fakeStartWidth = me.FakeW fakeStartWidth = me.FakeW
fakeStartHeight = me.TabH + me.FramePadH fakeStartHeight = me.TabH + me.FramePadH
if me.showDebug { if showDebug {
showFake() showFake()
me.showDebug = false showDebug = false
} else { } else {
hideFake() hideFake()
me.showDebug = true showDebug = true
} }
return nil return nil
} }
func theHelp(g *gocui.Gui, v *gocui.View) error { func theHelp(g *gocui.Gui, v *gocui.View) error {
if me.showHelp { if showHelp {
log.Info("Show the help!") helplayout()
showHelp() showHelp = false
} else { if me.dropdownV == nil {
log.Info("Hide the help!") me.dropdownV = makeDropdownView("addWidget() ddview")
hideHelp()
} }
me.dropdownV.Show()
} else {
me.baseGui.DeleteView("help")
showHelp = true
me.dropdownV.Hide()
}
return nil
}
func widgetRefresh(g *gocui.Gui, v *gocui.View) error {
log.Log(GOCUI, "todo: refresh windows here")
return nil
}
func doWindow(g *gocui.Gui, v *gocui.View) error {
log.Log(GOCUI, "todo: close all windows here")
return nil return nil
} }

View File

@ -1,131 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
)
func theStdout(g *gocui.Gui, v *gocui.View) error {
// me.stdout.pager = 0
infos := fmt.Sprintf("pager=%d len(%d) ", me.stdout.pager, len(me.stdout.outputS))
infos += fmt.Sprintf("last(%d,%d)", me.stdout.lastW, me.stdout.lastH)
me.stdout.changed = true
if me.stdout.outputOnTop {
if me.stdout.outputOffscreen {
me.stdout.outputOffscreen = false
log.Info("stdout moved off screen", infos)
me.stdout.lastW = me.stdout.tk.gocuiSize.w0
me.stdout.lastH = me.stdout.tk.gocuiSize.h0
relocateStdoutOffscreen()
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutoffscreen"
new1.Value = "true"
me.myTree.ConfigSave(new1)
return nil
} else {
me.stdout.outputOffscreen = true
log.Info("stdout moved on screen", infos)
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutoffscreen"
new1.Value = "false"
me.myTree.ConfigSave(new1)
}
// move the stdout window back onscreen
me.stdout.tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
me.stdout.outputOnTop = false
setThingsOnTop()
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutlevel"
new1.Value = "bottom"
me.myTree.ConfigSave(new1)
} else {
me.stdout.outputOnTop = true
setThingsOnTop()
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutlevel"
new1.Value = "top"
me.myTree.ConfigSave(new1)
}
return nil
}
func stdoutPgup(g *gocui.Gui, v *gocui.View) error {
me.stdout.pager -= me.stdout.Height() - 2
if me.stdout.pager < 0 {
me.stdout.pager = 0
}
tk := me.stdout.tk
tk.refreshStdout()
return nil
}
func stdoutHome(g *gocui.Gui, v *gocui.View) error {
me.stdout.pager = 0
me.stdout.tk.refreshStdout()
return nil
}
func stdoutArrowUp(g *gocui.Gui, v *gocui.View) error {
if me.stdout.reverse {
stdoutWheelsDown()
} else {
stdoutWheelsUp()
}
return nil
}
func stdoutArrowDown(g *gocui.Gui, v *gocui.View) error {
if me.stdout.reverse {
stdoutWheelsUp()
} else {
stdoutWheelsDown()
}
return nil
}
func stdoutPgdn(g *gocui.Gui, v *gocui.View) error {
win := findWindowUnderMouse()
if win != nil {
if win.full.Height() > 50 {
log.Info("paging through really large window pager =", win.window.pager)
win.window.pager += 10
return nil
}
}
me.stdout.pager += 10
tk := me.stdout.tk
tk.refreshStdout()
return nil
}
// scrolling up with the mouse wheel (or trackpad)
func stdoutWheelsUp() {
// log.Info("private wheels up")
me.stdout.pager -= 2
if me.stdout.pager < 0 {
me.stdout.pager = 0
}
me.stdout.tk.refreshStdout()
}
// scrolling down with the mouse wheel (or trackpad)
func stdoutWheelsDown() {
// log.Info("you've landed")
me.stdout.pager += 2
if me.stdout.pager > len(me.stdout.outputS) {
me.stdout.pager = len(me.stdout.outputS)
}
me.stdout.tk.refreshStdout()
}

View File

@ -1,6 +1,10 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0 // Use of this source code is governed by the GPL 3.0
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main package main
import ( import (
@ -8,6 +12,7 @@ import (
"fmt" "fmt"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
"go.wit.com/log" "go.wit.com/log"
) )
@ -19,10 +24,36 @@ import (
// This is equivalent to the linux command xev (apt install x11-utils) // This is equivalent to the linux command xev (apt install x11-utils)
func gocuiEvent(g *gocui.Gui) error { func gocuiEvent(g *gocui.Gui) error {
me.ecount += 1 me.ecount += 1
maxX, maxY := g.Size()
mx, my := g.MousePosition()
log.Verbose("handleEvent() START", maxX, maxY, mx, my, msgMouseDown)
if _, err := g.View("msg"); msgMouseDown && err == nil {
moveMsg(g)
}
if widgetView, _ := g.View("msg"); widgetView == nil {
log.Log(NOW, "handleEvent() create output widget now", maxX, maxY, mx, my)
makeOutputWidget(g, "this is a create before a mouse click")
if me.logStdout != nil {
// setOutput(me.logStdout)
// me.logStdout.Write("test out")
w := me.logStdout.TK.(*guiWidget)
msg := fmt.Sprintf("test out gocuiEvent() %d\n", me.ecount)
w.Write([]byte(msg))
// log.CaptureMode(w)
log.Log(NOW, "logStdout test out")
}
} else {
log.Verbose("output widget already exists", maxX, maxY, mx, my)
}
mouseMove(g) mouseMove(g)
log.Verbose("handleEvent() END ", maxX, maxY, mx, my, msgMouseDown)
return nil return nil
} }
func dragOutputWindow() {
log.Log(GOCUI, "todo: make dragOutputWindow")
}
// turns off the frame on the global window // turns off the frame on the global window
func setFrame(b bool) { func setFrame(b bool) {
// TODO: figure out what this might be useful for // TODO: figure out what this might be useful for
@ -40,36 +71,6 @@ func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit return gocui.ErrQuit
} }
func (tk *guiWidget) SetView() error {
if me.baseGui == nil {
return fmt.Errorf("me.baseGui == nil")
}
r := new(rectType)
r.w0 = tk.gocuiSize.w0
r.h0 = tk.gocuiSize.h0
r.w1 = tk.gocuiSize.w1
r.h1 = tk.gocuiSize.h1
return tk.SetViewRect(r)
}
func (tk *guiWidget) SetViewRect(r *rectType) error {
if me.baseGui == nil {
return fmt.Errorf("me.baseGui == nil")
}
var err error
tk.v, err = me.baseGui.SetView(tk.cuiName, r.w0, r.h0, r.w1, r.h1, 0)
if err != nil {
if !errors.Is(err, gocui.ErrUnknownView) {
log.Log(ERROR, "SetView() global failed on name =", tk.cuiName)
return err
}
}
return nil
}
func SetView(name string, x0, y0, x1, y1 int, overlaps byte) *gocui.View { func SetView(name string, x0, y0, x1, y1 int, overlaps byte) *gocui.View {
if me.baseGui == nil { if me.baseGui == nil {
log.Log(ERROR, "SetView() ERROR: me.baseGui == nil") log.Log(ERROR, "SetView() ERROR: me.baseGui == nil")

View File

@ -1,74 +1,151 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0 // Use of this source code is governed by the GPL 3.0
// NOTE: our code is under the GPL, not BSD
// note by jcarr@wit.com in 2025: this is one of the coolest
// things ever what this does. I've tried to improve
// it while I've been working on making a gocui
// GO plugin so it can be generalized as a useful
// console interface. Well done everyone that has
// contributed to this gocui project !!!
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main package main
import ( import (
"errors"
"fmt" "fmt"
"time"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
"go.wit.com/log" "go.wit.com/log"
) )
// this function uses the mouse position to highlight & unhighlight things
// this is run every time the user moves the mouse over the terminal window
func mouseMove(g *gocui.Gui) {
mx, my := g.MousePosition()
w := mx
h := my
if me.supermouse {
for _, tk := range findByXY(w, h) {
log.Log(GOCUI, fmt.Sprintf("findByXY() mouseMove() %s wId=%d cuiName=%s at (%d,%d)", tk.WidgetType, tk.node.WidgetId, tk.cuiName, w, h))
}
}
for _, view := range g.Views() {
view.Highlight = false
}
if v, err := g.ViewByPosition(mx, my); err == nil {
v.Highlight = true
}
}
// I think this lets me drag the debugging window
func msgDown(g *gocui.Gui, v *gocui.View) error {
initialMouseX, initialMouseY = g.MousePosition()
w := initialMouseX
h := initialMouseY
for _, tk := range findByXY(w, h) {
log.Log(GOCUI, fmt.Sprintf("findByXY() msgDown() %s wId=%d cuiName=%s at (%d,%d)", tk.WidgetType, tk.node.WidgetId, tk.cuiName, w, h))
}
// debugging output
// log.Log(GOCUI, "msgDown() X,Y", initialMouseX, initialMouseY)
//
vx, vy, _, _, err := g.ViewPosition("msg")
if err == nil {
xOffset = initialMouseX - vx
yOffset = initialMouseY - vy
msgMouseDown = true
}
return nil
}
func mouseUp(g *gocui.Gui, v *gocui.View) error { func mouseUp(g *gocui.Gui, v *gocui.View) error {
w, h := g.MousePosition()
// useful to debug everything that is being clicked on // useful to debug everything that is being clicked on
/* /*
for _, tk := range findByXY(w, h) { for _, tk := range findByXY(w, h) {
tk.dumpWidget("mouseUp()") log.Log(GOCUI, fmt.Sprintf("findByXY() mouseUp() %s wId=%d cuiName=%s at (%d,%d)", tk.WidgetType, tk.node.WidgetId, tk.cuiName, w, h))
} }
*/ */
me.mouse.mouseUp = true dropdownUnclicked(w, h)
me.mouse.currentDrag = nil
if me.mouse.double && (time.Since(me.mouse.down) < me.mouse.doubletime) { if msgMouseDown {
me.mouse.double = false msgMouseDown = false
doMouseDoubleClick(me.mouse.downW, me.mouse.downH) if movingMsg {
movingMsg = false
return nil return nil
} else {
g.DeleteView("msg")
} }
me.mouse.double = false } else if globalMouseDown {
globalMouseDown = false
if time.Since(me.mouse.down) < me.mouse.clicktime { g.DeleteView("globalDown")
doMouseClick(me.mouse.downW, me.mouse.downH)
} }
return nil return nil
} }
// func isMouseInMsg
// this is where you have to figure out what // this is where you have to figure out what
// widget was underneath so you can active // widget was underneath so you can active
// the right response for the toolkit user's app // the right response for the toolkit user's app
func mouseDown(g *gocui.Gui, v *gocui.View) error { func mouseDown(g *gocui.Gui, v *gocui.View) error {
if me.mouse.mouseUp { mx, my := g.MousePosition()
if time.Since(me.mouse.down) < me.mouse.doubletime {
me.mouse.double = true
}
me.mouse.mouseUp = false
me.mouse.down = time.Now()
w, h := g.MousePosition()
me.mouse.downW = w
me.mouse.downH = h
win := findWindowUnderMouse() var found bool = false
if win != nil { for _, tk := range findByXY(mx, my) {
w, h := g.MousePosition() log.Log(GOCUI, fmt.Sprintf("findByXY() mouseDown() %s wId=%d cuiName=%s at (%d,%d)", tk.WidgetType, tk.node.WidgetId, tk.cuiName, mx, my))
s := fmt.Sprintf("mouse(%d,%d) ", w, h) found = true
offW := win.full.w1 - w
offH := win.full.h1 - h
s += fmt.Sprintf("corner(%d,%d)", offW, offH)
if (offW < 3) && (offH < 3) {
log.Info("attempting resize on ", s, win.cuiName)
me.mouse.resize = true
// store the stdout corner for computing the drag size
me.stdout.lastW = me.stdout.tk.gocuiSize.w0
me.stdout.lastH = me.stdout.tk.gocuiSize.h0
} else {
// log.Info("mouse down resize off", s)
me.mouse.resize = false
} }
win.setAsDragging() if !found {
log.Log(GOCUI, fmt.Sprintf("findByXY() mouseDown() found nothing at (%d,%d)", mx, my))
} }
vx0, vy0, vx1, vy1, err := g.ViewPosition("msg")
if err == nil {
if mx >= vx0 && mx <= vx1 && my >= vy0 && my <= vy1 {
return msgDown(g, v)
}
}
globalMouseDown = true
maxX, _ := g.Size()
// why was this here?
// findUnderMouse()
// TODO: USE THIS TO MAKE TEMPORARY HELP / INSTRUCTION DIALOGS
// this message will pop up when you click on the magic thing
// figure out how this works and make it generically useful.
msg := fmt.Sprintf("This is -222 widget demo. %d,%d", mx, my)
// dropdownClicked(mx, my)
x := mx - len(msg)/2
if x < 0 {
x = 0
} else if x+len(msg)+1 > maxX-1 {
x = maxX - 1 - len(msg) - 1
}
log.Log(GOCUI, "mouseDown() about to write out message to 'globalDown' view. msg =", msg)
if v, err := g.SetView("globalDown", x, my-1, x+len(msg)+1, my+1, 0); err != nil {
if !errors.Is(err, gocui.ErrUnknownView) {
return err
}
v.WriteString(msg)
} }
return nil return nil
} }

View File

@ -4,158 +4,119 @@
package main package main
import ( import (
"fmt"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
"go.wit.com/log" "go.wit.com/log"
"go.wit.com/widget" "go.wit.com/widget"
) )
func (tk *guiWidget) doButtonClick() { // this whole things was impossible to make but it got me where I am now
if tk.IsEnabled() { // the debugging is way way better now with it being visible in the Stdout window
tk.dumpWidget("click()") // enable this to debug widget clicks // so now it's possible to redo all this and make it better
me.myTree.SendFromUser(tk.node) func (tk *guiWidget) doWidgetClick(w int, h int) {
} else { switch tk.WidgetType {
log.Info("button is currently disabled by the application") case widget.Window:
// tk.dumpWidget("disabled()") // enable this to debug widget clicks // if there is a current window, hide it
} if me.currentWindow != nil {
me.currentWindow.setColor(&colorWindow)
me.currentWindow.hideWidgets()
me.currentWindow.isCurrent = false
} }
// handles a mouse click // now set this window as the current window
func doMouseClick(w int, h int) { me.currentWindow = tk
// Flag widgets (dropdown menus, etc) are the highest priority. ALWAYS SEND MOUSE CLICKS THERE FIRST me.currentWindow.isCurrent = true
// handle an open dropdown menu or text entry window first
if me.dropdown.active || me.textbox.active {
// can't drag or do anything when dropdown or textbox are visible
for _, tk := range findByXY(w, h) {
if tk.WidgetId() == me.dropdown.wId {
log.Info("got dropdown click", w, h, tk.cuiName)
tk.dropdownClicked(w, h)
return
}
if tk.WidgetId() == me.textbox.wId {
log.Info("got textbox click", w, h, tk.cuiName)
textboxClosed()
return
}
}
log.Info("a dropdown or textbox is active. you can't click anywhere else (otherwise hit ESC)", w, h)
return
}
win := findWindowUnderMouse() // draw the current window
if win == nil { tk.setColor(&colorActiveW)
log.Log(INFO, "click() nothing was at:", w, h) tk.DrawAt(3, 2)
log.Log(INFO, "click() check if", w, h, "is the libnotify icon") tk.placeWidgets(3, 2) // compute the sizes & places for each widget
if me.notify.icon.tk != nil && me.notify.icon.tk.gocuiSize.inRect(w, h) { tk.active = false
log.Log(GOCUI, "click() is libnotify.icon!") tk.showWidgets()
if me.notify.icon.active { case widget.Group:
log.Info("show notify.icon here") if tk.active {
setNotifyIconText("[X]") tk.active = false
me.notify.icon.active = false tk.placeWidgets(tk.startW, tk.startH)
tk.showWidgets()
} else { } else {
log.Info("hide notify.icon here") tk.active = true
setNotifyIconText("[ ]") for _, child := range tk.children {
me.notify.icon.active = true child.hideWidgets()
}
return
}
if me.notify.clock.tk != nil && me.notify.clock.tk.gocuiSize.inRect(w, h) {
log.Log(GOCUI, "click() is the clock!")
if me.showHelp {
log.Info("show help")
showHelp()
} else {
log.Info("hide help")
hideHelp()
}
return
}
return
}
if !win.isWindowActive() {
win.makeWindowActive()
return
} else {
// potentally the user is closing the window
if win.checkWindowClose(w, h) {
return
} }
} }
// look in this window for widgets
// widgets have priority. send the click here first
for _, tk := range win.findByXYreal(w, h) {
switch tk.WidgetType() {
case widget.Checkbox: case widget.Checkbox:
if tk.Checked() { if tk.node.State.Checked {
log.Log(WARN, "checkbox is being set to false") log.Log(WARN, "checkbox is being set to false")
tk.SetChecked(false) tk.node.State.Checked = false
tk.setCheckbox() tk.setCheckbox()
} else { } else {
log.Log(WARN, "checkbox is being set to true") log.Log(WARN, "checkbox is being set to true")
tk.SetChecked(true) tk.node.State.Checked = true
tk.setCheckbox() tk.setCheckbox()
} }
me.myTree.SendUserEvent(tk.node) me.myTree.SendUserEvent(tk.node)
return case widget.Grid:
newR := tk.realGocuiSize()
// w,h := n.logicalSize()
// w := newR.w1 - newR.w0
// h := newR.h1 - newR.h0
tk.placeGrid(newR.w0, newR.h0)
tk.showWidgets()
case widget.Box:
if tk.node.State.Direction == widget.Horizontal {
log.Log(GOCUI, "BOX IS HORIZONTAL", tk.String())
} else {
log.Log(GOCUI, "BOX IS VERTICAL", tk.String())
}
tk.placeWidgets(tk.startW, tk.startH)
tk.toggleTree()
case widget.Button: case widget.Button:
tk.doButtonClick() // doUserEvent(n)
return me.myTree.SendFromUser(tk.node)
case widget.Combobox: case widget.Combobox:
log.Log(GOCUI, "do the combobox here")
tk.showDropdown() tk.showDropdown()
return me.dropdownW = tk
case widget.Dropdown: case widget.Dropdown:
log.Log(GOCUI, "do the dropdown here")
tk.showDropdown() tk.showDropdown()
return me.dropdownW = tk
case widget.Textbox:
log.Log(WARN, "TODO: textbox click")
tk.prepTextbox()
return
case widget.Label:
if tk.node.InTable() {
if tk.node.State.AtH == 0 {
log.Log(NOW, "todo: sort by column here")
tk.dumpWidget("sort")
}
}
return
default: default:
// TODO: enable the GUI debugger in gocui log.Log(GOCUI, fmt.Sprintf("findByXY() blank click() %s wId=%d cuiName=%s at (%d,%d)", tk.WidgetType, tk.node.WidgetId, tk.cuiName, w, h))
// tk.dumpWidget("undef click()") // enable this to debug widget clicks
}
} }
} }
// todo: use this? // sends the mouse click to a widget underneath
func ctrlDown(g *gocui.Gui, v *gocui.View) error { func click(g *gocui.Gui, v *gocui.View) error {
log.Info("todo: clicked with ctrlDown") mouseW, mouseH := me.baseGui.MousePosition()
w := mouseW
h := mouseH
for _, tk := range findByXY(w, h) {
// will show you everything found on a mouse click. great for debugging!
// log.Log(GOCUI, fmt.Sprintf("findByXY() click() %s wId=%d cuiName=%s at (%d,%d)", tk.WidgetType, tk.node.WidgetId, tk.cuiName, w, h))
if tk.WidgetType == widget.Stdout {
// don't send clicks to the stdout debugging window
continue
}
tk.doWidgetClick(w, h)
return nil return nil
} }
func doMouseDoubleClick(w int, h int) { log.Log(GOCUI, "click() nothing was at:", v.Name(), mouseW, mouseH)
me.mouse.double = false return nil
// log.Printf("actually a double click (%d,%d)", w, h) /*
// not sure what SetCurrentView() does right now. it was here before
if me.dropdown.active || me.textbox.active { // SetCurrentView dies if it's sent an non-existent view
// can't drag or do anything when dropdown or textbox are visible if _, err := g.SetCurrentView(v.Name()); err != nil {
log.Info("can't double click. dropdown or textbox is active") log.Log(GOCUI, "click() END v.Name =", v.Name(), "err =", err)
return // return err // return causes gocui.MainLoop() to exit. Do we ever want that to happen here?
} return nil
for _, tk := range findByXY(w, h) {
if tk.WidgetType() == widget.Window {
tk.makeWindowActive()
return
}
if tk.WidgetType() == widget.Stdout {
if me.stdout.outputOnTop {
me.stdout.outputOnTop = false
setThingsOnTop()
} else {
me.stdout.outputOnTop = true
setThingsOnTop()
}
return
}
} }
*/
return nil
} }

View File

@ -1,136 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// 2025 note by jcarr:
// this is one of the coolest things ever worked with.
// Personally, I've been working on making a gocui GO plugin
// so I can use it as a generalized console GUI toolkit.
//
// Well done everyone that has contributed to this gocui project !!!
// I am in your debt. Happy hacking & peace.
package main
import (
"fmt"
"time"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget"
)
// this function uses the mouse position to highlight & unhighlight things
// this is run every time the user moves the mouse over the terminal window
func mouseMove(g *gocui.Gui) {
// this runs while the user moves the mouse. this highlights text
// toggle off all highlight views except for whatever is under the mouse
// START HIGHLIGHTING
for _, view := range g.Views() {
view.Highlight = false
}
w, h := g.MousePosition()
// TODO: try to highlight entire grid rows
if v, err := g.ViewByPosition(w, h); err == nil {
// block anything from highlighting while a dialog box is open
if me.dropdown.active || me.textbox.active {
if me.dropdown.tk != nil && me.dropdown.tk.v == v {
v.Highlight = true
}
if me.textbox.tk != nil && me.textbox.tk.v == v {
v.Highlight = true
}
} else {
v.Highlight = true
}
}
// old hack. create the 'msg' view if it does not yet exist
// TODO: put this somewhere more correct
if widgetView, _ := g.View("msg"); widgetView == nil {
me.stdout.changed = true
if createStdout(g) {
return
}
return
}
// END HIGHLIGHTING
// Super Mouse Mode. very useful for debugging in the past. also, just fun
if me.supermouse {
w, h := g.MousePosition()
for _, tk := range findByXY(w, h) {
s := fmt.Sprintf("SM (%3d,%3d)", w, h)
tk.dumpWidget(s)
}
}
if me.mouse.mouseUp {
return
}
// EVERYTHING BELOW THIS IS RELATED TO MOUSE DRAGGING
// has the mouse been pressed down long enough to start dragging?
if time.Since(me.mouse.down) < me.mouse.clicktime {
// not dragging
return
}
if me.dropdown.active || me.textbox.active {
// can't drag
return
}
// drag whatever was set to drag
if me.mouse.currentDrag != nil {
// me.mouse.currentDrag.dumpWidget(fmt.Sprintf("MM (%3d,%3d)", w, h))
me.mouse.currentDrag.moveNew()
return
}
log.Info(fmt.Sprintf("gocui gui toolkit plugin error. nothing to drag at (%d,%d)", w, h))
return
}
func (tk *guiWidget) setAsDragging() {
me.mouse.currentDrag = tk
tk.lastW = tk.gocuiSize.w0
tk.lastH = tk.gocuiSize.h0
}
// this is how the window gets dragged around
func (tk *guiWidget) moveNew() {
w, h := me.baseGui.MousePosition()
if tk.WidgetType() == widget.Window {
tk.window.wasDragged = true
// compute the new location based off how far the mouse has moved
// since the mouse button was pressed down
tk.gocuiSize.w0 = tk.lastW + w - me.mouse.downW
tk.gocuiSize.h0 = tk.lastH + h - me.mouse.downH
tk.makeWindowActive()
return
}
if tk.WidgetType() == widget.Stdout {
if me.mouse.resize {
newW := w - me.stdout.lastW
newH := h - me.stdout.lastH
me.stdout.w = newW
me.stdout.h = newH
// log.Info("Resize true", w, h, newW, newH)
// me.stdout.lastW = w - me.stdout.mouseOffsetW
// me.stdout.lastH = h - me.stdout.mouseOffsetH
tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
} else {
// compute the new location based off how far the mouse has moved
// since the mouse button was pressed down
newW := tk.lastW + w - me.mouse.downW
newH := tk.lastH + h - me.mouse.downH
tk.relocateStdout(newW, newH)
// log.Info("Resize false", w, h, newW, newH)
}
setThingsOnTop() // sets help, Stdout, etc on the top after windows have been redrawn
me.stdout.changed = true
}
}

211
find.go
View File

@ -4,10 +4,7 @@
package main package main
import ( import (
"slices"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget" "go.wit.com/widget"
) )
@ -30,189 +27,97 @@ func findByXY(w int, h int) []*guiWidget {
rootW := me.treeRoot.TK.(*guiWidget) rootW := me.treeRoot.TK.(*guiWidget)
// this searches the binary tree recursively (function is right below) // this searches the binary tree recursively (function is right below)
return rootW.findByXYreal(w, h) return findByXYreal(rootW, w, h)
}
func (r rectType) inRect(w int, h int) bool {
if (r.w0 <= w) && (w <= r.w1) && (r.h0 <= h) && (h <= r.h1) {
return true
}
return false
} }
// this checks a widget to see if it is under (W,H), then checks the widget's children // this checks a widget to see if it is under (W,H), then checks the widget's children
// anything that matches is passed back as an array of widgets // anything that matches is passed back as an array of widgets
func (tk *guiWidget) findByXYreal(w int, h int) []*guiWidget { func findByXYreal(widget *guiWidget, w int, h int) []*guiWidget {
var widgets []*guiWidget var widgets []*guiWidget
// if !tk.Visible() { if !widget.Visible() {
// ignore widgets that are not visible // ignore widgets that are not visible
// } else { } else {
// check the location to see if this is under (W,H) // check the location to see if this is under (W,H)
// if it is, return this widget // if it is, return this widget
// if (tk.gocuiSize.w0 <= w) && (w <= tk.gocuiSize.w1) && if (widget.gocuiSize.w0 <= w) && (w <= widget.gocuiSize.w1) &&
// (tk.gocuiSize.h0 <= h) && (h <= tk.gocuiSize.h1) { (widget.gocuiSize.h0 <= h) && (h <= widget.gocuiSize.h1) {
// if tk.gocuiSize.inRect(w, h) { widgets = append(widgets, widget)
// widgets = append(widgets, tk) // log.Log(GOCUI, "findByXY() found", widget.WidgetType, w, h)
// } else { }
// if (tk.full.w0 <= w) && (w <= tk.full.w1) &&
// (tk.full.h0 <= h) && (h <= tk.full.h1) {
if tk.full.inRect(w, h) {
widgets = append(widgets, tk)
} }
// log.Log(GOCUI, "findByXY() found", widget.WidgetType(), w, h)
// }
// }
// tk.verifyRect()
// search through the children widgets in the binary tree // search through the children widgets in the binary tree
for _, child := range tk.children { for _, child := range widget.children {
widgets = append(widgets, child.findByXYreal(w, h)...) widgets = append(widgets, findByXYreal(child, w, h)...)
} }
return widgets return widgets
} }
// returns all the windows from the root of the binary tree // returns the "highest priority widget under the mouse
func findWindows() []*guiWidget { func findUnderMouse() *guiWidget {
rootW := me.treeRoot.TK.(*guiWidget)
return rootW.findWindows()
}
// walk the binary tree looking for WidgetType == Window
func (tk *guiWidget) findWindows() []*guiWidget {
var found []*guiWidget
if tk.WidgetType() == widget.Window {
found = append(found, tk)
}
for _, child := range tk.children {
found = append(found, child.findWindows()...)
}
return found
}
// used by gocui.TabKey to rotate through the windows
func findNextWindow() *guiWidget {
var found bool
if len(me.allwin) == 0 {
return nil
}
for _, tk := range me.allwin {
if tk.window.active {
found = true
continue
}
if found {
return tk
}
}
// at the end, loop to the beginning
return me.allwin[0]
}
// find the window under the mouse and only the window under the mouse
func findWindowUnderMouse() *guiWidget {
w, h := me.baseGui.MousePosition() w, h := me.baseGui.MousePosition()
if len(me.allwin) != len(findWindows()) { widgets := findByXY(w, h)
me.allwin = findWindows()
}
// if the stdout window is on top, check it first // search through all the widgets that were below the mouse click
if me.stdout.outputOnTop { var found *guiWidget
if me.stdout.tk.full.inRect(w, h) { for _, w := range widgets {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s stdout on top (%dx%d)", me.stdout.tk.cuiName, w, h)) // prioritize window buttons. This means if some code covers
return me.stdout.tk // up the window widgets, then it will ignore everything else
// and allow the user (hopefully) to redraw or switch windows
// TODO: display the window widgets on top
if w.WidgetType == widget.Window {
return w
} }
} }
// now check if the active window is below the mouse // return anything else that is interactive
for _, tk := range me.allwin { for _, w := range widgets {
if tk.window.active { if w.WidgetType == widget.Button {
if tk.full.inRect(w, h) { return w
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s active window (%dx%d)", tk.cuiName, w, h))
return tk
} }
if w.WidgetType == widget.Combobox {
return w
} }
if w.WidgetType == widget.Checkbox {
return w
} }
w.showWidgetPlacement("findUnderMouse() found something unknown")
// well, just find any window then found = w
// sorting by order might work?
slices.SortFunc(me.allwin, func(a, b *guiWidget) int {
return a.window.order - b.window.order
})
for _, win := range me.allwin {
if win.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s window (%dx%d)", win.cuiName, w, h))
return win
} }
} // maybe something else was found
// okay, no window. maybe the stdout is there?
if me.stdout.tk.full.inRect(w, h) {
// log.Info(fmt.Sprintf("findWindowUnderMouse() found %s stdout (%dx%d)", me.stdout.tk.cuiName, w, h))
return me.stdout.tk
}
// geez. nothing! maybe auto return stdout?
log.Info("findWindowUnderMouse() no window found at", w, h)
return nil
}
func (tk *guiWidget) findParentWindow() *guiWidget {
if tk.WidgetType() == widget.Window {
return tk
}
if tk.parent == nil {
return nil
}
return tk.parent.findParentWindow()
}
func (tk *guiWidget) findWidgetByName(name string) *guiWidget {
if tk.cuiName == name {
return tk
}
for _, child := range tk.children {
found := child.findWidgetByName(name)
if found != nil {
return found return found
} }
}
return nil
}
func (tk *guiWidget) findWidgetByView(v *gocui.View) *guiWidget { // panics. todo: fix ctrl-mouse click?
if tk.v == v { // find the widget under the mouse click
return tk func ctrlDown(g *gocui.Gui, v *gocui.View) error {
} var found *guiWidget
if tk.cuiName == v.Name() { // var widgets []*node
log.Log(NOW, "findWidget() error. names are mismatched or out of sync", tk.cuiName) // var f func (n *node)
log.Log(NOW, "findWidget() or maybe the view has been deleted") found = findUnderMouse()
// return tk if me.ctrlDown == nil {
} setupCtrlDownWidget()
for _, child := range tk.children {
found := child.findWidgetByView(v)
if found != nil {
return found
}
}
return nil
}
func (tk *guiWidget) findWidgetById(id int) *guiWidget { var tk *guiWidget
if tk.WidgetId() == id { tk = me.ctrlDown.TK.(*guiWidget)
return tk tk.labelN = found.String()
} tk.cuiName = "ctrlDown"
for _, child := range tk.children { // me.ctrlDown.parent = me.rootNode
found := child.findWidgetById(id)
if found != nil {
return found
} }
var tk *guiWidget
tk = me.ctrlDown.TK.(*guiWidget)
if found == nil {
found = me.treeRoot.TK.(*guiWidget)
} }
tk.labelN = found.String()
newR := found.realGocuiSize()
tk.gocuiSize.w0 = newR.w0
tk.gocuiSize.h0 = newR.h0
tk.gocuiSize.w1 = newR.w1
tk.gocuiSize.h1 = newR.h1
return nil return nil
} }

74
help.go
View File

@ -1,7 +1,7 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0 // Use of this source code is governed by the GPL 3.0
// Prior Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -13,7 +13,6 @@ import (
"strings" "strings"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
log "go.wit.com/log"
) )
/* /*
@ -23,41 +22,30 @@ import (
// possible with gocui. it doesn't seem to work for me // possible with gocui. it doesn't seem to work for me
*/ */
var helpText []string = []string{"Help Menu", var helpText []string = []string{"KEYBINDINGS",
"", "",
"Tab toggle through windows", "?: toggle zhelp",
"'O' toggle STDOUT", "S: super mouse",
"'H' toggle this gocui menu", "M: list all widgets positions",
"'D' toggle light/dark mode", "L: list all widgets in tree form",
"CTRL-z background to shell", "d: toggle debugging",
"CTRL-c quit()", "s/h: show/hide all widgets",
"q: quit()",
"p: panic()",
"o: show Stdout",
"l: log to /tmp/witgui.log",
"Ctrl-D: Toggle Debugging",
"Ctrl-V: Toggle Verbose Debugging",
"Ctrl-C: Exit",
"", "",
"Debugging:",
"'S' Supermouse mode",
"'M' list all widget positions",
"'L' list all widgets in tree",
"<Pgup> scroll up the STDOUT window",
"<Pgdn> scroll down the STDOUT window",
"'r' reverse STDOUT scrolling",
} }
func hideHelp() { func hidehelplayout() {
if me.showHelp {
log.Info("help is already down")
me.showHelp = true
return
}
me.showHelp = true
me.baseGui.DeleteView("help") me.baseGui.DeleteView("help")
} }
func showHelp() error { func helplayout() error {
if !me.showHelp {
log.Info("help is already up")
me.showHelp = false
return nil
}
me.showHelp = false
g := me.baseGui g := me.baseGui
var err error var err error
maxX, _ := g.Size() maxX, _ := g.Size()
@ -69,18 +57,17 @@ func showHelp() error {
} }
} }
a := maxX - (newW + me.FramePadW) help, err := g.SetView("help", maxX-(newW+me.FramePadW), 0, maxX-1, len(helpText)+me.FramePadH, 0)
b := me.notify.help.offsetH
c := maxX - 1
d := me.notify.help.offsetH + len(helpText) + me.FramePadH
help, err := g.SetView("help", a, b, c, d, 0)
if err != nil { if err != nil {
if !errors.Is(err, gocui.ErrUnknownView) { if !errors.Is(err, gocui.ErrUnknownView) {
return err return err
} }
help.SelBgColor = gocui.ColorGreen help.SelBgColor = gocui.ColorGreen
help.SelFgColor = gocui.ColorBlack help.SelFgColor = gocui.ColorBlack
// fmt.Fprintln(help, "Enter: Click Button")
// fmt.Fprintln(help, "Tab/Space: Switch Buttons")
// fmt.Fprintln(help, "Backspace: Delete Button")
// fmt.Fprintln(help, "Arrow keys: Move Button")
fmt.Fprintln(help, strings.Join(helpText, "\n")) fmt.Fprintln(help, strings.Join(helpText, "\n"))
@ -88,21 +75,6 @@ func showHelp() error {
return err return err
} }
} }
g.SetViewOnTop("help")
me.helpLabel = help me.helpLabel = help
/*
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return nil
} else {
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "made this in showHelp()")
msg := fmt.Sprintf("test to stdout from in showHelp() %d\n", me.ecount)
me.stdout.Write([]byte(msg))
log.Log(NOW, "log.log(NOW) test")
}
}
*/
return nil return nil
} }

516
init.go
View File

@ -1,3 +1,4 @@
//gjcarro:pjcarrlugin
// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0 // Use of this source code is governed by the GPL 3.0
@ -9,14 +10,8 @@ package main
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"runtime"
"runtime/debug" "runtime/debug"
"runtime/pprof"
"strconv"
"strings"
"time"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
"go.wit.com/log" "go.wit.com/log"
@ -27,231 +22,143 @@ import (
var VERSION string var VERSION string
var BUILDTIME string var BUILDTIME string
var PLUGIN string = "gocui" func queueToolkitClose() {
// this is called at the very initial connection
// between the app and this gocui plugin
// this is a good place to initialize gocui's default behavior
func toolkitInit() {
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
if me.baseGui == nil {
log.Info("gocui baseGui is still nil")
standardExit()
}
if me.treeRoot == nil {
log.Info("gocui treeRoot is still nil")
standardExit()
}
// w := me.treeRoot.TK.(*guiWidget)
// w.dumpTree("MM")
// w.dumpWindows("WW")
// SETUP HELP START
me.baseGui.Update(testRefresh)
log.Log(INFO, "gocui toolkitInit() trying showHelp() me.ok =", me.ok)
showHelp()
hideHelp()
// SETUP HELP END
// SETUP STDOUT START
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "from setThingsOnTop()")
}
// time.Sleep(300 * time.Millisecond)
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
me.baseGui.Update(testRefresh)
if !me.stdout.init {
log.Log(INFO, "gocui toolkitInit() stdout.Init me.ok =", me.ok)
me.stdout.init = true
relocateStdoutOffscreen()
}
// time.Sleep(1 * time.Second)
me.stdout.outputOnTop = false
setThingsOnTop()
// SETUP STDOUT END
// SETUP BG
if me.BG.tk == nil {
me.BG.tk = makeNewInternalWidget(me.BG.wId)
}
// SETUP libnotify clock and menu
me.notify.clock.once.Do(makeNotifyClock)
me.notify.icon.once.Do(makeNotifyIcon)
// TODO: for some reason, this makes the background doesn't display
// PUT INIT DEBUG COOE HERE
var toggle bool
for i := 0; i < 4; i++ {
// enable this to show early debugging
// w := me.treeRoot.TK.(*guiWidget)
// w.dumpTree("MM")
// w.dumpWindows("WW")
time.Sleep(100 * time.Millisecond)
if toggle {
toggle = false
// log.Info("gocui toolkitInit() put testing true stuff here")
} else {
toggle = true
// log.Info("gocui toolkitInit() put testing false stuff here")
}
setBottomBG()
}
// PUT INIT DEBUG COOE HERE END
// TEST TEXTBOX START
// time.Sleep(1 * time.Second)
log.Log(INFO, "gocui toolkitInit() me.ok =", me.ok)
me.baseGui.Update(testRefresh)
if me.textbox.tk == nil {
log.Log(INFO, "gocui toolkitInit() initTextbox me.ok =", me.ok)
initTextbox()
}
// TEST TEXTBOX END
}
func toolkitClose() {
me.baseGui.Close() me.baseGui.Close()
} }
// a GO GUI plugin should initTree in init() func queueSetChecked(n *tree.Node, b bool) {
// this should be done before the application setChecked(n, b)
// starts trying to open up a channel
func init() {
me.myTree = initTree()
} }
// sets defaults and establishes communication // sets defaults and establishes communication
// to this toolkit from the wit/gui golang package // to this toolkit from the wit/gui golang package
func initPlugin() { func init() {
log.Log(INFO, "Init() of awesome-gocui")
// init the config struct default values
Set(&me, "default")
// Set(&me, "dense")
me.myTree = tree.New()
me.myTree.PluginName = "gocui"
me.myTree.NodeAction = newaction
me.myTree.Add = newAdd
me.myTree.SetTitle = newSetTitle
me.myTree.SetLabel = newSetLabel
me.myTree.SetText = newSetText
me.myTree.AddText = newAddText
me.myTree.SetChecked = queueSetChecked
me.myTree.ToolkitClose = queueToolkitClose
log.Log(NOW, "Init() start pluginChan")
log.Sleep(.1) // probably not needed, but in here for now under development
go mainGogui()
log.Sleep(.1) // probably not needed, but in here for now under development
}
func standardExit() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
log.Log(NOW, "standardExit() doing outf.Close()")
outf.Close()
// log(true, "standardExit() setOutput(os.Stdout)")
// setOutput(os.Stdout)
log.Log(NOW, "standardExit() send back Quit()")
// go sendBackQuit() // don't stall here in case the
// induces a delay in case the callback channel is broken
log.Sleep(1)
log.Log(NOW, "standardExit() exit()")
os.Exit(0)
}
func standardClose() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
log.Log(NOW, "standardExit() doing outf.Close()")
outf.Close()
os.Stdin = os.Stdin
os.Stdout = os.Stdout
os.Stderr = os.Stderr
log.Log(NOW, "standardExit() send back Quit()")
}
var outf *os.File
func main() {
}
var origStdout *os.File
var origStderr *os.File
func mainGogui() {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
fmt.Fprintf(me.outf, "PANIC: initPlugin() recovered %v\n", r) log.Warn("YAHOOOO Recovered in guiMain application:", r)
log.Warn("Recovered from panic:", r)
me.baseGui.Close()
log.CaptureMode(nil)
log.Warn("YAHOOOO Recovered in guiMain application:", r)
log.Warn("Recovered from panic:", r)
me.myTree.SendToolkitPanic()
return return
} }
}() }()
var err error var err error
// read in defaults from config protobuf outf, err = os.OpenFile("/tmp/captureMode.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if val, err := me.myTree.ConfigFind("stdout"); err == nil {
if val == "true" {
me.stdout.startOnscreen = true
// me.stdout.Write([]byte("starting with stdout onscreen\n"))
}
if val == "disable" {
log.Log(INFO, "gocui: attempt to COMPLETELY DISABLE STDOUT LOG")
me.stdout.disable = true
}
}
if val, err := me.myTree.ConfigFind("stdoutoffscreen"); err == nil {
if val == "false" {
// log.Log(NOW, "gocui: START ON SCREEN TRUE")
me.stdout.startOnscreen = true
}
}
if val, err := me.myTree.ConfigFind("dark"); err == nil {
if val == "true" {
me.dark = true
}
} else {
// macos iterm2 really only works with dark mode right now
if runtime.GOOS == "macos" {
me.dark = true
}
}
// todo: make this a tmp file that goes away
if !me.stdout.disable {
tmpFile, err := os.CreateTemp("", "gocui-*.log")
if err != nil { if err != nil {
fmt.Println("Error creating temp file:", err) log.Error(err, "error opening file: %v")
standardExit() os.Exit(0)
} }
// defer os.Remove(tmpFile.Name()) origStdout = os.Stdout
os.Stdout = outf
defer outf.Close()
log.Log(INFO, "stdout.disable == true. writing to", tmpFile.Name()) log.CaptureMode(outf)
me.outf = tmpFile
// todo: some early output still goes to the /tmp/ file
//time.Sleep(200 * time.Millisecond)
log.CaptureMode(me.stdout)
}
me.starttime = time.Now()
log.Log(INFO, "Init() of awesome-gocui")
// init the config struct default values gocuiMain()
Set(&me, "default")
// initial app window settings
// initial stdout window settings
me.stdout.w = 180
me.stdout.h = 40
me.stdout.lastW = 4
me.stdout.lastH = 20
if val, err := me.myTree.ConfigFind("stdoutsize"); err == nil {
parts := strings.Fields(val)
if len(parts) == 4 {
log.Info("initial stdout settings:", parts, "setting startOnscreen = true")
me.stdout.w, _ = strconv.Atoi(parts[0])
me.stdout.h, _ = strconv.Atoi(parts[1])
me.stdout.lastW, _ = strconv.Atoi(parts[2])
me.stdout.lastH, _ = strconv.Atoi(parts[3])
me.stdout.startOnscreen = true
} else {
log.Info("initial stdout settings parse error:", parts)
}
} }
// just make up unique values for these // This initializes the gocui package
me.dropdown.wId = -77 // it runs SetManagerFunc which passes every input
me.textbox.wId = -55 // event (keyboard, mouse, etc) to the function "gocuiEvent()"
me.stdout.wId = -4 func gocuiMain() {
me.BG.wId = -22 defer func() {
if r := recover(); r != nil {
log.Warn("YAHOOOO Recovered in gocuiMain()", r)
log.Warn("Recovered from panic:", r)
me.baseGui.Close()
// the clock widget id and offset // allow gocui to close if possible, then print stack
me.notify.clock.wId = -5 log.Sleep(1)
me.notify.clock.offsetW = 13 os.Stdout = origStdout
me.notify.clock.offsetH = 1 os.Stderr = origStderr
me.myTree.SendToolkitPanic()
log.Warn("Stack trace:")
debug.PrintStack()
// panic("BUMMER 2")
me.notify.icon.wId = -6 // attempt to switch to the nocui toolkit
me.notify.icon.offsetW = 4 log.Sleep(1)
me.notify.icon.offsetH = 1 me.myTree.SendToolkitLoad("nocui")
log.Sleep(3)
me.notify.help.wId = -7 me.myTree.SendToolkitLoad("nocui")
me.notify.help.offsetH = 3 // panic("BUMMER")
Set(&me.dropdown, "default")
// s := fmt.Sprintln("fake default check =", me.FakeW, "dropdown.Id", me.dropdown.Id)
// me.stdout.Write([]byte(s))
me.mouse.mouseUp = true
me.mouse.clicktime = time.Millisecond * 200
me.mouse.doubletime = time.Millisecond * 400
me.newWindowTrigger = make(chan *guiWidget, 1)
go newWindowTrigger()
go refreshGocui()
log.Log(NOW, "Init() start pluginChan")
if me.stdout.disable {
log.Info("Using STDOUT")
} else {
log.Info("Using gocui STDOUT")
os.Stdout = me.outf
log.CaptureMode(me.outf)
}
// init gocui
g, err := gocui.NewGui(gocui.OutputNormal, true)
if err != nil {
os.Exit(-1)
return return
} }
}()
g, err := gocui.NewGui(gocui.OutputNormal, true)
if err != nil {
return
}
defer g.Close()
me.baseGui = g me.baseGui = g
g.Cursor = true g.Cursor = true
g.Mouse = true g.Mouse = true
@ -262,203 +169,18 @@ func initPlugin() {
// register how the 'gocui' will work as a GO toolkit plugin // register how the 'gocui' will work as a GO toolkit plugin
// all applications will use these keys. they are universal. // all applications will use these keys. they are universal.
// registered event handlers still have the events sent to gocuiEvent() above
registerHandlers(g) registerHandlers(g)
/*
time.Sleep(100 * time.Millisecond) if err := defaultKeybindings(g); err != nil {
// normally panic here
if me.outf != nil { log.Log(NOW, "defaultKeybindings(g) panic err =", err)
fmt.Fprintln(me.outf, "hello world", time.Since(me.starttime)) panic("gocuiTKdefaultkeybindings OOPS")
} }
*/
// coreStdout() if err := g.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
// createStdout(g)
// tell 'tree' that we are okay to start talking to
me.myTree.InitOK()
me.ok = true // this tells init() it's okay to work with gocui
go gocuiMain()
}
// This goroutine sits in gocui's MainLoop()
func gocuiMain() {
defer func() {
if r := recover(); r != nil {
log.Warn("PANIC ecovered in gocuiMain()", r)
if me.outf == nil {
debug.PrintStack()
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
panic(os.Stdout)
} else {
fmt.Fprintf(me.outf, "PANIC recovered in r = %v", r)
os.Stderr = me.outf
os.Stdout = me.outf
debug.PrintStack()
pprof.Lookup("goroutine").WriteTo(me.outf, 1)
panic(me.outf)
}
}
}()
// me.stdout.Write([]byte("begin gogui.MainLoop()\n"))
if err := me.baseGui.MainLoop(); err != nil && !errors.Is(err, gocui.ErrQuit) {
log.Log(NOW, "g.MainLoop() panic err =", err) log.Log(NOW, "g.MainLoop() panic err =", err)
// normally panic here // normally panic here
panic("gocuiTKmainloop OOPS") panic("gocuiTKmainloop OOPS")
} }
} }
func standardExit() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
if me.outf != nil {
log.Log(NOW, "standardExit() doing outf.Close()")
me.outf.Close()
os.Remove(me.outf.Name())
}
// log(true, "standardExit() setOutput(os.Stdout)")
// setOutput(os.Stdout)
log.Log(NOW, "standardExit() send back Quit()")
// go sendBackQuit() // don't stall here in case the
// induces a delay in case the callback channel is broken
time.Sleep(200 * time.Millisecond)
log.Log(NOW, "standardExit() exit()")
os.Exit(0)
}
func standardClose() {
log.Log(NOW, "standardExit() doing baseGui.Close()")
me.baseGui.Close()
log.Log(NOW, "standardExit() doing outf.Close()")
me.outf.Close()
os.Remove(me.outf.Name())
// os.Stdin = os.Stdin
// os.Stdout = os.Stdout
// os.Stderr = os.Stderr
log.Log(NOW, "standardExit() send back Quit()")
}
func main() {
}
// this hack is to wait for the application to send something
// before trying to do anything. todo: rethink this someday
func waitOK() {
for {
if me.ok {
return
}
time.Sleep(10 * time.Millisecond)
}
}
// this hack is to wait for the application to send something
// before trying to do anything. todo: rethink this someday
func waitFirstWindow() {
for {
if me.firstWindowOk {
return
}
time.Sleep(10 * time.Millisecond)
}
}
// empty function. this triggers gocui to refresh the screen
func testRefresh(*gocui.Gui) error {
// log.Info("in testRefresh")
return nil
}
// refresh the screen 10 times a second
func refreshGocui() {
defer func() {
if r := recover(); r != nil {
if me.outf == nil {
log.Info("INIT PLUGIN recovered in r", r)
} else {
fmt.Fprintln(me.outf, "INIT PLUGIN recovered in r", r)
}
return
}
}()
var lastRefresh time.Time
lastRefresh = time.Now()
me.refresh = false
for {
time.Sleep(100 * time.Millisecond)
// log.Info("refresh checking ok")
if !me.ok {
continue
}
// redraw the windows if something has changed
if time.Since(lastRefresh) > 1000*time.Millisecond {
if me.refresh {
log.Log(NOW, "newWindowTrigger() sending refresh to channel")
me.newWindowTrigger <- me.treeRoot.TK.(*guiWidget)
me.refresh = false
}
if me.stdout.changed {
me.stdout.changed = false
lastRefresh = time.Now()
new1 := new(tree.ToolkitConfig)
new1.Plugin = "gocui"
new1.Name = "stdoutsize"
width := me.stdout.tk.gocuiSize.w1 - me.stdout.tk.gocuiSize.w0
height := me.stdout.tk.gocuiSize.h1 - me.stdout.tk.gocuiSize.h0
new1.Value = fmt.Sprintf("%d %d %d %d", width, height, me.stdout.tk.gocuiSize.w0, me.stdout.tk.gocuiSize.h0)
me.myTree.ConfigSave(new1)
// log.Log(NOW, "newWindowTrigger() gocui setting stdout size =", new1.Value)
// me.stdout.tk.dumpWidget("save")
}
}
// this code updates the clock
if time.Since(lastRefresh) > 1000*time.Millisecond {
// artificially pause clock while dragging.
// this is a reminder to make this refresh code smarter
// after the switch to protocol buffers
me.myTree.Lock()
if me.mouse.mouseUp {
// log.Info("refresh now on mouseUp")
// todo: add logic here to see if the application has changed anything
libNotifyUpdate()
lastRefresh = time.Now()
} else {
if time.Since(lastRefresh) > 3*time.Second {
libNotifyUpdate()
lastRefresh = time.Now()
}
}
me.myTree.Unlock()
}
}
}
// set the widget start width & height
func newWindowTrigger() {
// log.Log(NOW, "newWindowTriggerl() START")
for {
// log.Log(NOW, "GO plugin toolkit made a new window")
select {
case tk := <-me.newWindowTrigger:
// log.Log(NOW, "newWindowTrigger() got new window", tk.cuiName)
// time.Sleep(200 * time.Millisecond)
waitOK()
me.myTree.Lock()
// time.Sleep(200 * time.Millisecond)
redoWindows(me.FirstWindowW, me.FirstWindowH)
me.firstWindowOk = true
if !me.stdout.init {
me.stdout.init = true
relocateStdoutOffscreen()
}
if me.textbox.tk == nil {
initTextbox()
}
tk.makeWindowActive()
me.myTree.Unlock()
}
}
}

View File

@ -1,295 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
// this file implements a libnotify-like menu
// also there is SIGWINCH resizing
package main
import (
"fmt"
"time"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
// create a new widget in the binary tree
func makeNewInternalWidget(wId int) *guiWidget {
if me.treeRoot == nil {
log.Info("GOGUI Init ERROR. treeRoot == nil")
return nil
}
n := new(tree.Node)
n.WidgetType = widget.Flag
n.WidgetId = wId
n.ParentId = 0
// store the internal toolkit information
tk := new(guiWidget)
tk.frame = true
tk.node = n
tk.node.Parent = me.treeRoot
// set the name used by gocui to the id
tk.cuiName = fmt.Sprintf("%d DR", wId)
tk.setColorInput()
// add this new widget on the binary tree
tk.parent = me.treeRoot.TK.(*guiWidget)
if tk.parent == nil {
panic("makeNewFlagWidget() didn't get treeRoot guiWidget")
} else {
tk.parent.children = append(tk.parent.children, tk)
}
n.TK = tk
return tk
}
func makeNotifyClock() {
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return
}
me.notify.clock.tk = makeNewInternalWidget(me.notify.clock.wId)
// me.notify.clock.tk.dumpWidget("init() clock")
me.notify.clock.tk.MoveToOffset(0, 0)
me.notify.clock.tk.labelN = time.Now().Format("15:04:05")
me.notify.clock.tk.frame = false
me.notify.clock.tk.setColorLabel()
me.notify.clock.tk.Show()
me.notify.clock.active = true
// me.notify.clock.tk.dumpWidget("notifyClock()")
}
func makeNotifyIcon() {
if me.treeRoot == nil {
log.Info("gogui makeClock() error. treeRoot == nil")
return
}
me.notify.icon.tk = makeNewInternalWidget(me.notify.icon.wId)
// me.notify.icon.tk.dumpWidget("init() menu")
w, _ := me.baseGui.Size()
me.notify.icon.tk.MoveToOffset(w-5, me.notify.icon.offsetH)
me.notify.icon.tk.labelN = "[ ]"
me.notify.icon.tk.frame = false
me.notify.icon.tk.setColorNotifyIcon()
me.notify.icon.tk.Show()
me.notify.icon.active = true
// me.notify.icon.tk.dumpWidget("notifyIcon()")
}
func libNotifyUpdate() {
if me.baseGui == nil {
log.Info("libNotifyUpdate error baseGui == nil")
return
}
// refresh GOCUI
me.baseGui.Update(testRefresh)
// me.baseGui.UpdateAsync(testRefresh) // Async option. probably don't need this?
if me.notify.clock.tk == nil {
log.Info("libNotifyUpdate error clock.tk == nil")
return
}
// check for SIGWINCH. If so, move the libnotify clock
w, h := me.baseGui.Size()
if me.winchW != w || me.winchH != h {
if me.winchW == 0 && me.winchH == 0 {
// this isn't really SIGWINCH. This is the app starting
} else {
log.Printf("gocui: long live SIGWINCH! (w,h) is now (%d,%d)\n", w, h)
}
me.winchW = w
me.winchH = h
me.notify.clock.tk.MoveToOffset(w-me.notify.clock.offsetW, me.notify.clock.offsetH)
me.notify.clock.tk.Hide()
me.notify.clock.tk.Show()
sigWinchBG()
sigWinchIcon()
}
// update the time
me.notify.clock.tk.v.Clear()
me.notify.clock.tk.labelN = time.Now().Format("15:04:05")
me.notify.clock.tk.v.WriteString(me.notify.clock.tk.labelN)
hardDrawAtgocuiSize(me.notify.clock.tk)
// hardDrawUnderMouse(me.notify.clock.tk, "clock")
// log.Info("libNotifyUpdate updated clock", me.notify.clock.tk.labelN)
if me.notify.icon.tk == nil {
log.Info("libNotifyUpdate error menu.tk == nil")
return
}
if me.notify.icon.tk.v == nil {
log.Info("libNotifyUpdate error menu.tk.v == nil")
return
}
// update the menu
hardDrawAtgocuiSize(me.notify.icon.tk)
me.notify.icon.tk.setColorNotifyIcon()
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
me.baseGui.SetViewOnTop(me.notify.clock.tk.v.Name())
}
func setNotifyIconText(s string) {
me.notify.icon.tk.v.Clear()
me.notify.icon.tk.labelN = s
me.notify.icon.tk.v.WriteString(me.notify.icon.tk.labelN)
hardDrawAtgocuiSize(me.notify.icon.tk)
me.notify.icon.tk.setColorNotifyIcon()
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
log.Info("setNotifyIconText() updated menu to:", me.notify.icon.tk.labelN)
// print out the window list // TODO: put this in libnotify
for _, tk := range me.allwin {
log.Info("known window Window", tk.labelN, tk.window.active, tk.window.order)
}
if s == "[X]" {
log.Warn("should turn on help window here")
showHelp()
} else {
log.Warn("should turn off help window here")
hideHelp()
}
}
// in the very end of redrawing things, this will place the help and stdout on the top or botton
// depending on the state the user has chosen
func setThingsOnTop() {
if me.showHelp { // terrible variable name. FIXME
// log.Info("help does not exist")
} else {
me.baseGui.SetViewOnTop("help")
}
if me.notify.clock.tk != nil {
me.baseGui.SetViewOnTop(me.notify.clock.tk.v.Name())
}
if me.notify.icon.tk != nil {
if me.notify.icon.tk.v != nil {
me.baseGui.SetViewOnTop(me.notify.icon.tk.v.Name())
}
}
if me.stdout.tk == nil {
makeOutputWidget(me.baseGui, "from setThingsOnTop()")
}
if me.stdout.tk == nil {
return
}
if me.stdout.tk.v == nil {
return
}
if me.dark {
me.stdout.tk.v.FgColor = gocui.ColorWhite
me.stdout.tk.v.BgColor = gocui.ColorBlack
} else {
me.stdout.tk.v.FgColor = gocui.ColorBlack
me.stdout.tk.v.BgColor = gocui.AttrNone
}
if me.stdout.outputOnTop {
me.baseGui.SetViewOnTop("msg")
} else {
me.baseGui.SetViewOnBottom("msg")
}
if me.stdout.startOnscreen {
// log.Info("THIS TRIGGERS STDOUT") // todo: make a proper init() & move this there
me.stdout.tk.relocateStdout(me.stdout.lastW, me.stdout.lastH)
me.stdout.startOnscreen = false
}
setBottomBG()
}
// useful for debuggging
func hardDrawUnderMouse(tk *guiWidget, name string) {
if tk.v != nil {
tk.Hide()
}
w, h := me.baseGui.MousePosition()
r := new(rectType)
r.w0 = w
r.h0 = h
r.w1 = w + 8
r.h1 = h + 4
if err := tk.SetViewRect(r); err != nil {
log.Info("hardDrawUnderMouse() err", tk.cuiName, err)
tk.dumpWidget("hardDrawERR")
}
tk.v.Frame = false
tk.v.Clear()
tk.v.WriteString(tk.labelN + "\n" + name)
}
func hardDrawAtgocuiSize(tk *guiWidget) {
if tk.v != nil {
tk.Hide()
}
if err := tk.SetView(); err != nil {
log.Info("hardDrawAtgocuiSize() err ok widget", tk.cuiName)
tk.dumpWidget("hardDrawERR")
}
tk.v.Frame = false
tk.v.Clear()
tk.v.WriteString(tk.labelN)
// log.Verbose("hardDrawAtgocuiSize() err ok widget", tk.cuiName, a, b, c, d, tk.v.Name())
}
func sigWinchIcon() {
w, _ := me.baseGui.Size()
me.notify.icon.tk.MoveToOffset(w-me.notify.icon.offsetW, me.notify.icon.offsetH)
me.notify.icon.tk.Hide()
me.notify.icon.tk.Show()
}
func sigWinchBG() {
tk := me.BG.tk
w, h := me.baseGui.Size()
tk.gocuiSize.w0 = -1
tk.gocuiSize.h0 = -1
tk.gocuiSize.w1 = w + 1
tk.gocuiSize.h1 = h + 1
if err := tk.SetView(); err != nil {
tk.dumpWidget("sigWinchBGerr()")
log.Log(ERROR, "sigWinchBG()", err)
}
log.Log(INFO, "background resized to", tk.gocuiSize)
}
// find the "BG" widget and set it to the background on the very very bottom
func setBottomBG() {
if me.BG.tk == nil {
log.Info("background tk widget not initialized")
return
}
tk := me.BG.tk
// log.Info("found BG. setting to bottom", tk.cuiName)
if tk.v == nil {
sigWinchBG()
return
}
if me.dark {
tk.v.BgColor = gocui.ColorBlack
} else {
tk.v.BgColor = gocui.ColorWhite
}
tk.v.Clear()
me.baseGui.SetViewOnBottom(tk.cuiName)
w, h := me.baseGui.Size()
tk.gocuiSize.w0 = -1
tk.gocuiSize.h0 = -1
tk.gocuiSize.w1 = w + 1
tk.gocuiSize.h1 = h + 1
tk.SetView()
}

2
log.go
View File

@ -11,6 +11,8 @@ import (
"go.wit.com/log" "go.wit.com/log"
) )
var outputS []string
var NOW *log.LogFlag var NOW *log.LogFlag
var INFO *log.LogFlag var INFO *log.LogFlag
var GOCUI *log.LogFlag var GOCUI *log.LogFlag

63
node.go
View File

@ -1,63 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"go.wit.com/widget"
)
func (tk *guiWidget) WidgetType() widget.WidgetType {
if tk.node == nil {
return widget.Label
}
return tk.node.WidgetType
}
func (tk *guiWidget) WidgetId() int {
return tk.node.WidgetId
}
func (tk *guiWidget) GetLabel() string {
return tk.node.GetLabel()
}
func (tk *guiWidget) IsEnabled() bool {
return tk.node.IsEnabled()
}
func (tk *guiWidget) Checked() bool {
return tk.node.State.Checked
}
func (tk *guiWidget) Hidden() bool {
if tk.node == nil {
return false
}
if tk.parent == nil {
return tk.node.Hidden()
}
if tk.parent.WidgetId() == 0 {
return tk.node.Hidden()
}
if tk.parent.Hidden() {
return true
}
return tk.node.Hidden()
}
func (tk *guiWidget) Direction() widget.Orientation {
return tk.node.State.Direction
}
func (tk *guiWidget) GridW() int {
return tk.node.State.AtW
}
func (tk *guiWidget) GridH() int {
return tk.node.State.AtH
}
func (tk *guiWidget) SetChecked(b bool) {
tk.node.State.Checked = b
}

161
place.go
View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"go.wit.com/log" "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget" "go.wit.com/widget"
) )
@ -34,12 +35,10 @@ func (tk *guiWidget) Position() (int, int) {
} }
func (w *guiWidget) placeBox(startW int, startH int) { func (w *guiWidget) placeBox(startW int, startH int) {
if w.WidgetType() != widget.Box { if w.WidgetType != widget.Box {
return return
} }
w.full.w0 = startW
w.full.h0 = startH
newW := startW newW := startW
newH := startH newH := startH
@ -50,7 +49,7 @@ func (w *guiWidget) placeBox(startW int, startH int) {
// re-get the Size (they should not have changed, but maybe they can?) // re-get the Size (they should not have changed, but maybe they can?)
// TODO: figure this out or report that they did // TODO: figure this out or report that they did
sizeW, sizeH = child.Size() sizeW, sizeH = child.Size()
if w.Direction() == widget.Vertical { if w.node.State.Direction == widget.Vertical {
log.Log(INFO, "BOX IS VERTICAL ", w.String(), "newWH()", newW, newH, "child()", sizeW, sizeH, child.String()) log.Log(INFO, "BOX IS VERTICAL ", w.String(), "newWH()", newW, newH, "child()", sizeW, sizeH, child.String())
// expand based on the child height // expand based on the child height
newH += sizeH newH += sizeH
@ -70,57 +69,37 @@ func (tk *guiWidget) placeWidgets(startW int, startH int) (int, int) {
if me.treeRoot == nil { if me.treeRoot == nil {
return 0, 0 return 0, 0
} }
if tk.Hidden() {
return 0, 0
}
tk.startW = startW tk.startW = startW
tk.startH = startH tk.startH = startH
switch tk.WidgetType() { switch tk.WidgetType {
case widget.Window: case widget.Window:
tk.full.w0 = startW newW := startW
tk.full.h0 = startH newH := startH
startW += -2 var maxH int = 0
startH += 3
for _, child := range tk.children { for _, child := range tk.children {
child.placeWidgets(startW, startH) child.placeWidgets(newW, newH)
sizeW, _ := child.Size() sizeW, sizeH := child.Size()
startW += sizeW + 4 // add the width to move the next widget over if sizeW < 20 {
sizeW = 20
}
newW += sizeW
if sizeH > maxH {
maxH = sizeH
}
} }
return startW, startH return newW - startW, maxH
case widget.Tab: case widget.Tab:
case widget.Grid: case widget.Grid:
// tk.dumpWidget(fmt.Sprintf("PlaceGridS(%d,%d)", startW, startH)) return tk.placeGrid(startW, startH)
// if you reset the values here, grid horizontal stacking doesn't work anymore
// tk.widths = make(map[int]int) // how tall each row in the grid is
// tk.heights = make(map[int]int) // how wide each column in the grid is
newW, newH := tk.placeGrid(startW, startH)
tk.full.w0 = newW
tk.full.h0 = newH
tk.full.w1 = newW
tk.full.h1 = newH
// tk.dumpWidget(fmt.Sprintf("PlaceGridE(%d,%d)", newW, newH))
return newW, newH
case widget.Box: case widget.Box:
tk.placeBox(startW, startH) tk.placeBox(startW, startH)
tk.full.w0 = startW
tk.full.h0 = startH
tk.full.w1 = startW
tk.full.h1 = startH
// tk.dumpWidget(fmt.Sprintf("PlaceBox(%d,%d)", startW, startH))
return 0, 0 return 0, 0
case widget.Stdout:
tk.setStdoutWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height()
case widget.Group: case widget.Group:
// move the group to the parent's next location // move the group to the parent's next location
tk.gocuiSetWH(startW, startH) tk.gocuiSetWH(startW, startH)
tk.full.w0 = startW
tk.full.h0 = startH
tk.full.w1 = startW
tk.full.h1 = startH
newW := startW + me.GroupPadW newW := startW + me.GroupPadW
newH := startH + 1 // normal hight of the group label newH := startH + 1 // normal hight of the group label
@ -139,27 +118,7 @@ func (tk *guiWidget) placeWidgets(startW int, startH int) (int, int) {
} }
log.Log(INFO, "REAL HEIGHT sizeW:", sizeW, "sizeH:", sizeH) log.Log(INFO, "REAL HEIGHT sizeW:", sizeW, "sizeH:", sizeH)
} }
newH = newH - startH return maxW, newH - startH
// tk.dumpWidget(fmt.Sprintf("PlaceGroup(%d,%d)", maxW, newH))
return maxW, newH
case widget.Button:
if tk.isDense() && tk.isInGrid() {
tk.frame = false
// tk.color = nil
// tk.defaultColor = nil
/*
if tk.IsEnabled() {
tk.setColorButtonDense()
} else {
tk.setColorDisable()
}
*/
// if tk.full.Height() > 0 {
tk.full.h1 = tk.full.h0
// }
}
tk.gocuiSetWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height()
default: default:
tk.gocuiSetWH(startW, startH) tk.gocuiSetWH(startW, startH)
return tk.gocuiSize.Width(), tk.gocuiSize.Height() return tk.gocuiSize.Width(), tk.gocuiSize.Height()
@ -167,64 +126,25 @@ func (tk *guiWidget) placeWidgets(startW int, startH int) (int, int) {
return 0, 0 return 0, 0
} }
func (tk *guiWidget) isDense() bool {
if tk.node.InTable() {
return true
}
return tk.isWindowDense()
}
func (tk *guiWidget) isWindowDense() bool {
if tk.WidgetType() == widget.Window {
return tk.window.dense
}
if tk.parent == nil {
return true
}
return tk.parent.isWindowDense()
}
func (tk *guiWidget) isInGrid() bool {
if tk.WidgetType() == widget.Grid {
return true
}
if tk.parent == nil {
return true
}
return tk.parent.isInGrid()
}
func (w *guiWidget) placeGrid(startW int, startH int) (int, int) { func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
// w.showWidgetPlacement("grid0:") // w.showWidgetPlacement("grid0:")
if w.WidgetType() != widget.Grid { if w.WidgetType != widget.Grid {
return 0, 0 return 0, 0
} }
w.full.w0 = startW
w.full.h0 = startH
// first compute the max sizes of the rows and columns // first compute the max sizes of the rows and columns
for _, child := range w.children { for _, child := range w.children {
childW, childH := child.placeWidgets(child.startW, child.startH) childW, childH := child.placeWidgets(child.startW, child.startH)
// set the child's realWidth, and grid offset // set the child's realWidth, and grid offset
if w.widths[child.GridW()] < childW { if w.widths[child.node.State.AtW] < childW {
w.widths[child.GridW()] = childW w.widths[child.node.State.AtW] = childW
} }
if w.heights[child.GridH()] < childH { if w.heights[child.node.State.AtH] < childH {
w.heights[child.GridH()] = childH w.heights[child.node.State.AtH] = childH
} }
if child.isDense() {
if w.heights[child.GridH()] > 0 {
w.heights[child.GridH()] = 1
} else {
w.heights[child.GridH()] = 0
}
}
// child.showWidgetPlacement("grid: ") // child.showWidgetPlacement("grid: ")
log.Log(INFO, "placeGrid:", child.String(), "child()", childW, childH, "At()", child.GridW(), child.GridH()) log.Log(INFO, "placeGrid:", child.String(), "child()", childW, childH, "At()", child.node.State.AtW, child.node.State.AtH)
} }
var maxW int = 0 var maxW int = 0
@ -236,12 +156,12 @@ func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
var totalW, totalH int var totalW, totalH int
for i, w := range w.widths { for i, w := range w.widths {
if i < child.GridW() { if i < child.node.State.AtW {
totalW += w totalW += w
} }
} }
for i, h := range w.heights { for i, h := range w.heights {
if i < child.GridH() { if i < child.node.State.AtH {
totalH += h totalH += h
} }
} }
@ -257,13 +177,11 @@ func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
maxH = totalH maxH = totalH
} }
log.Log(INFO, "placeGrid:", child.String(), "new()", newW, newH, "At()", child.GridW(), child.GridH()) log.Log(INFO, "placeGrid:", child.String(), "new()", newW, newH, "At()", child.node.State.AtW, child.node.State.AtH)
child.placeWidgets(newW, newH) child.placeWidgets(newW, newH)
// child.showWidgetPlacement("grid2:") // child.showWidgetPlacement("grid2:")
} }
// w.showWidgetPlacement("grid3:") // w.showWidgetPlacement("grid3:")
w.full.w1 = startW + maxW
w.full.h1 = startH + maxH
return maxW, maxH return maxW, maxH
} }
@ -271,8 +189,6 @@ func (w *guiWidget) placeGrid(startW int, startH int) (int, int) {
func (w *guiWidget) realGocuiSize() *rectType { func (w *guiWidget) realGocuiSize() *rectType {
var f func(tk *guiWidget, r *rectType) var f func(tk *guiWidget, r *rectType)
newR := new(rectType) newR := new(rectType)
outputW, outputH := w.Size()
// initialize the values to opposite // initialize the values to opposite
newR.w0 = outputW newR.w0 = outputW
newR.h0 = outputH newR.h0 = outputH
@ -309,7 +225,6 @@ func (w *guiWidget) realGocuiSize() *rectType {
return newR return newR
} }
/*
func textSize(n *tree.Node) (int, int) { func textSize(n *tree.Node) (int, int) {
var tk *guiWidget var tk *guiWidget
tk = n.TK.(*guiWidget) tk = n.TK.(*guiWidget)
@ -323,21 +238,12 @@ func textSize(n *tree.Node) (int, int) {
} }
return width, height return width, height
} }
*/
func (tk *guiWidget) gocuiSetWH(sizeW, sizeH int) { func (tk *guiWidget) gocuiSetWH(sizeW, sizeH int) {
w := len(tk.GetLabel()) w := len(widget.GetString(tk.value))
lines := strings.Split(tk.GetLabel(), "\n") lines := strings.Split(widget.GetString(tk.value), "\n")
h := len(lines) h := len(lines)
if tk.Hidden() {
tk.gocuiSize.w0 = 0
tk.gocuiSize.h0 = 0
tk.gocuiSize.w1 = 0
tk.gocuiSize.h1 = 0
return
}
// tk := n.tk // tk := n.tk
if tk.isFake { if tk.isFake {
tk.gocuiSize.w0 = sizeW tk.gocuiSize.w0 = sizeW
@ -359,10 +265,3 @@ func (tk *guiWidget) gocuiSetWH(sizeW, sizeH int) {
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + 1 tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + 1
} }
} }
func (tk *guiWidget) setStdoutWH(sizeW, sizeH int) {
tk.gocuiSize.w0 = sizeW
tk.gocuiSize.h0 = sizeH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + me.stdout.w
tk.gocuiSize.h1 = tk.gocuiSize.h0 + me.stdout.h
}

199
plugin.go
View File

@ -4,7 +4,8 @@
package main package main
import ( import (
"github.com/awesome-gocui/gocui" // if you include more than just this import
// then your plugin might be doing something un-ideal (just a guess from 2023/02/27)
"go.wit.com/log" "go.wit.com/log"
"go.wit.com/toolkits/tree" "go.wit.com/toolkits/tree"
"go.wit.com/widget" "go.wit.com/widget"
@ -16,7 +17,7 @@ func newAdd(n *tree.Node) {
return return
} }
if n.TK != nil { if n.TK != nil {
log.Log(INFO, "Tree Add() sent a widget we aleady seem to have") log.Warn("Tree Add() sent a widget we aleady seem to have")
// this is done to protect the plugin being 'refreshed' with the // this is done to protect the plugin being 'refreshed' with the
// widget binary tree. TODO: find a way to keep them in sync // widget binary tree. TODO: find a way to keep them in sync
return return
@ -34,26 +35,20 @@ func newAdd(n *tree.Node) {
w = n.TK.(*guiWidget) w = n.TK.(*guiWidget)
} }
*/ */
// w.setColor(&colorDisabled)
w := n.TK.(*guiWidget) w := n.TK.(*guiWidget)
w.Show() w.Show()
me.refresh = true // testing code to see if refresh can work
} }
// for gocui as a GUI plugin, SetTitle & SetLabel are identical to SetText func newSetTitle(n *tree.Node, s string) {
func setTitle(n *tree.Node, s string) { newSetText(n, s)
setText(n, s)
me.refresh = true // testing code to see if refresh can work
} }
func setLabel(n *tree.Node, s string) { func newSetLabel(n *tree.Node, s string) {
setText(n, s) newSetText(n, s)
me.refresh = true // testing code to see if refresh can work
} }
// setText() and addText() are simple. They take the event sent func newSetText(n *tree.Node, s string) {
// to the GO plugin from the application and lookup the plugin structure
// then pass that event to gocui. This is the transfer point
func setText(n *tree.Node, s string) {
if n == nil { if n == nil {
log.Warn("Tree Error: Add() sent n == nil") log.Warn("Tree Error: Add() sent n == nil")
return return
@ -64,10 +59,9 @@ func setText(n *tree.Node, s string) {
} }
w := n.TK.(*guiWidget) w := n.TK.(*guiWidget)
w.SetText(s) w.SetText(s)
me.refresh = true // testing code to see if refresh can work
} }
func addText(n *tree.Node, s string) { func newAddText(n *tree.Node, s string) {
if n == nil { if n == nil {
log.Warn("Tree Error: Add() sent n == nil") log.Warn("Tree Error: Add() sent n == nil")
return return
@ -78,7 +72,51 @@ func addText(n *tree.Node, s string) {
} }
w := n.TK.(*guiWidget) w := n.TK.(*guiWidget)
w.AddText(s) w.AddText(s)
me.refresh = true // testing code to see if refresh can work }
func newaction(n *tree.Node, atype widget.ActionType) {
log.Log(INFO, "newaction() START", atype)
if n == nil {
log.Warn("Tree Error: Add() sent n == nil")
return
}
if n.TK == nil {
log.Warn("Tree sent an action on a widget we didn't seem to have.")
// do this init here again? Probably something
// went wrong and we should reset the our while gocui.View tree
n.TK = initWidget(n)
}
w := n.TK.(*guiWidget)
switch atype {
case widget.Show:
w.Show()
case widget.Hide:
w.Hide()
case widget.Move:
log.Log(NOW, "attempt to move() =", atype, n.WidgetType, n.ProgName())
case widget.ToolkitClose:
log.Log(NOW, "attempting to close the plugin and release stdout and stderr")
standardClose()
case widget.Enable:
w.enable = true
w.enableColor()
case widget.Disable:
w.enable = false
w.disableColor()
case widget.Delete:
if w == nil {
return
} else {
w.hideWidgets()
w.deleteNode()
}
n.DeleteNode()
wRoot := me.treeRoot.TK.(*guiWidget)
wRoot.redoWindows(0, 0)
default:
log.Log(ERROR, "newaction() UNHANDLED Action Type =", atype, "WidgetType =", n.WidgetType, "Name =", n.ProgName())
}
log.Log(INFO, "newaction() END", atype, n.String())
} }
func (w *guiWidget) deleteGocuiViews() { func (w *guiWidget) deleteGocuiViews() {
@ -116,132 +154,45 @@ func (w *guiWidget) AddText(text string) {
} }
w.vals = append(w.vals, text) w.vals = append(w.vals, text)
for i, s := range w.vals { for i, s := range w.vals {
log.Log(INFO, "AddText()", w.String(), i, s) log.Log(NOW, "AddText()", w.String(), i, s)
} }
w.SetText(text) w.SetText(text)
} }
func (tk *guiWidget) SetText(text string) { func (w *guiWidget) SetText(text string) {
var changed bool = false var changed bool = false
if tk == nil { if w == nil {
log.Log(NOW, "widget is nil") log.Log(NOW, "widget is nil")
return return
} }
if tk.labelN != text { if w.labelN != text {
tk.labelN = text w.labelN = text
changed = true changed = true
} }
tk.node.State.Label = text
if !changed { if !changed {
return return
} }
if tk.Visible() { if w.Visible() {
tk.textResize() w.textResize()
tk.Hide() w.Hide()
tk.Show() w.Show()
} }
} }
func (tk *guiWidget) GetText() string { func (w *guiWidget) Set(val any) {
if tk == nil { if w == nil {
log.Log(NOW, "widget is nil") log.Log(WARN, "Set() w == nil. val =", val)
return ""
}
// deprecate this
if tk.labelN != "" {
return tk.labelN
}
if tk.node == nil {
// return gocui.view name?
return tk.cuiName
}
if tk.GetLabel() != "" {
return tk.GetLabel()
}
return ""
}
// hack. use "textbox widget" to "disable" user events
func hideDisable() {
if me.textbox.tk == nil {
initTextbox()
}
me.textbox.tk.Hide()
me.textbox.tk.enable = false
me.textbox.tk.node.State.Enable = false
me.textbox.active = false
me.baseGui.SetCurrentView("help")
// me.baseGui.DeleteView(me.textbox.tk.cuiName)
// me.baseGui.DeleteView(me.textbox.tk.v.Name())
}
// hack. use "textbox widget" to "disable" user events
func showDisable() {
if me.textbox.tk == nil {
initTextbox()
me.textbox.tk.prepTextbox()
}
r := new(rectType)
r.w0 = 2
r.h0 = 1
r.w1 = r.w0 + 24
r.h1 = r.h0 + 2
me.textbox.tk.forceSizes(r)
me.textbox.tk.Show() // actually makes the gocui view. TODO: redo this
// log.Info("textbox should be shown")
// showTextbox("Running...")
// me.textbox.tk.dumpWidget("shown?")
me.textbox.tk.setColorModal()
me.textbox.tk.v.Clear()
me.textbox.tk.v.WriteString("Running...")
me.textbox.tk.v.Editable = true
me.textbox.tk.v.Wrap = true
me.textbox.tk.SetViewRect(r)
me.baseGui.SetCurrentView(me.textbox.tk.v.Name())
// bind the enter key to a function so we can close the textbox
me.baseGui.SetKeybinding(me.textbox.tk.v.Name(), gocui.KeyEnter, gocui.ModNone, theCloseTheTextbox)
me.textbox.active = true
}
func (tk *guiWidget) Disable() {
if tk == nil {
log.Log(NOW, "widget is nil")
return return
} }
log.Log(INFO, "Set() value =", val)
switch tk.WidgetType() { w.value = val.(string)
case widget.Box: if w.node.WidgetType == widget.Checkbox {
showDisable() w.node.State.Checked = widget.GetBool(val)
return w.setCheckbox()
case widget.Button: }
tk.setColorDisable() if w.node.WidgetType == widget.Label {
return w.labelN = widget.GetString(val)
default:
tk.dumpWidget("fixme: disable")
}
}
func (tk *guiWidget) Enable() {
if tk == nil {
log.Log(NOW, "widget is nil")
return
}
switch tk.WidgetType() {
case widget.Box:
hideDisable()
return
case widget.Button:
tk.restoreEnableColor()
return
default:
tk.dumpWidget("fixme: enable")
} }
} }

307
size.go
View File

@ -4,8 +4,6 @@
package main package main
import ( import (
"fmt"
"go.wit.com/widget" "go.wit.com/widget"
) )
@ -18,16 +16,16 @@ func (tk *guiWidget) Size() (int, int) {
} }
// don't count hidden widgets in size calculations // don't count hidden widgets in size calculations
if tk.Hidden() { if tk.node.Hidden() {
return 0, 0 return 0, 0
} }
switch tk.WidgetType() { switch tk.WidgetType {
case widget.Window: case widget.Window:
var maxH int = 0 var maxH int = 0
var maxW int = 0 var maxW int = 0
for _, child := range tk.children { for _, child := range tk.children {
if tk.Hidden() { if tk.node.Hidden() {
continue continue
} }
sizeW, sizeH := child.Size() sizeW, sizeH := child.Size()
@ -48,7 +46,7 @@ func (tk *guiWidget) Size() (int, int) {
maxH := tk.gocuiSize.Height() maxH := tk.gocuiSize.Height()
for _, child := range tk.children { for _, child := range tk.children {
if tk.Hidden() { if tk.node.Hidden() {
continue continue
} }
sizeW, sizeH := child.Size() sizeW, sizeH := child.Size()
@ -62,15 +60,8 @@ func (tk *guiWidget) Size() (int, int) {
return maxW + me.GroupPadW + 3, maxH return maxW + me.GroupPadW + 3, maxH
case widget.Label: case widget.Label:
return len(tk.String()) + 2, 1 return len(tk.String()) + 2, 1
case widget.Textbox:
return len(tk.String()) + 10, 3 // TODO: compute this based on 'window dense'
case widget.Checkbox: case widget.Checkbox:
return len(tk.String()) + 2, 3 // TODO: compute this based on 'window dense' return len(tk.String()) + 2, 3
case widget.Button:
if tk.isDense() {
return len(tk.String()) + 2, 0
}
return len(tk.String()) + 2, 3 // TODO: compute this based on 'window dense'
} }
if tk.isFake { if tk.isFake {
return 0, 0 return 0, 0
@ -79,23 +70,25 @@ func (tk *guiWidget) Size() (int, int) {
} }
func (w *guiWidget) sizeGrid() (int, int) { func (w *guiWidget) sizeGrid() (int, int) {
if w.Hidden() { if w.node.Hidden() {
return 0, 0 return 0, 0
} }
// first compute the max sizes of the rows and columns // first compute the max sizes of the rows and columns
for _, child := range w.children { for _, child := range w.children {
if w.Hidden() { if w.node.Hidden() {
continue continue
} }
sizeW, sizeH := child.Size() sizeW, sizeH := child.Size()
sizeW += 2
// set the child's realWidth, and grid offset // set the child's realWidth, and grid offset
if w.widths[child.GridW()] < sizeW { if w.widths[child.node.State.AtW] < sizeW {
w.widths[child.GridW()] = sizeW w.widths[child.node.State.AtW] = sizeW
} }
if w.heights[child.GridH()] < sizeH { if w.heights[child.node.State.AtH] < sizeH {
w.heights[child.GridH()] = sizeH w.heights[child.node.State.AtH] = sizeH
} }
} }
@ -112,21 +105,21 @@ func (w *guiWidget) sizeGrid() (int, int) {
} }
func (w *guiWidget) sizeBox() (int, int) { func (w *guiWidget) sizeBox() (int, int) {
if w.WidgetType() != widget.Box { if w.WidgetType != widget.Box {
return 0, 0 return 0, 0
} }
if w.Hidden() { if w.node.Hidden() {
return 0, 0 return 0, 0
} }
var maxW int = 0 var maxW int = 0
var maxH int = 0 var maxH int = 0
for _, child := range w.children { for _, child := range w.children {
if w.Hidden() { if w.node.Hidden() {
continue continue
} }
sizeW, sizeH := child.Size() sizeW, sizeH := child.Size()
if child.Direction() == widget.Vertical { if child.node.State.Direction == widget.Vertical {
maxW += sizeW maxW += sizeW
if sizeH > maxH { if sizeH > maxH {
maxH = sizeH maxH = sizeH
@ -140,269 +133,3 @@ func (w *guiWidget) sizeBox() (int, int) {
} }
return maxW + me.BoxPadW, maxH return maxW + me.BoxPadW, maxH
} }
/*
var wtf bool
func (tk *guiWidget) verifyRect() bool {
if !tk.Visible() {
// log.Info("verifyRect() tk is not visible", tk.cuiName)
return false
}
vw0, vh0, vw1, vh1, err := me.baseGui.ViewPosition(tk.cuiName)
if err != nil {
// log.Printf("verifyRect() gocui err=%v cuiName=%s v.Name=%s", err, tk.cuiName, tk.v.Name())
vw0, vh0, vw1, vh1, err = me.baseGui.ViewPosition(tk.v.Name())
if err != nil {
log.Printf("verifyRect() ACTUAL FAIL gocui err=%v cuiName=%s v.Name=%s", err, tk.cuiName, tk.v.Name())
return false
}
// return false
}
var ok bool = true
if vw0 != tk.full.w0 {
// log.Info("verifyRect() FIXING w0", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.w0, "w0 =", vw0)
tk.full.w0 = vw0
ok = false
}
if vw1 != tk.full.w1 {
// log.Info("verifyRect() FIXING w1", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.w1, "w1 =", vw1)
tk.full.w1 = vw1
ok = false
}
if vh0 != tk.full.h0 {
// log.Info("verifyRect() FIXING h0", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.h0)
tk.full.h0 = vh0
ok = false
}
if vh1 != tk.full.h1 {
// log.Info("verifyRect() FIXING h1", tk.cuiName, vw0, vw1, vh0, vh1, tk.gocuiSize.h1)
tk.full.h1 = vh1
ok = false
}
if !ok {
// log.Info("verifyRect() NEED TO FIX RECT HERE", tk.cuiName)
// tk.dumpWidget("verifyRect() FIXME")
}
// log.Printf("verifyRect() OK cuiName=%s v.Name=%s", tk.cuiName, tk.v.Name())
return true
}
*/
func (tk *guiWidget) setFullSize() bool {
r := tk.getFullSize()
if tk.Hidden() {
p := tk.parent
if p != nil {
// tk.full.w0 = p.full.w0
// tk.full.w1 = p.full.w1
// tk.full.h0 = p.full.h0
// tk.full.h1 = p.full.h1
tk.full.w0 = 0
tk.full.w1 = 0
tk.full.h0 = 0
tk.full.h1 = 0
} else {
tk.full.w0 = 0
tk.full.w1 = 0
tk.full.h0 = 0
tk.full.h1 = 0
}
return false
}
var changed bool
if tk.full.w0 != r.w0 {
tk.full.w0 = r.w0
changed = true
}
// widget might be forced to a certain location
if tk.full.w0 < tk.force.w0 {
tk.gocuiSize.w0 = tk.force.w0
tk.full.w0 = tk.force.w0
changed = false
}
if tk.full.w1 != r.w1 {
tk.full.w1 = r.w1
changed = true
}
if tk.full.h0 != r.h0 {
tk.full.h0 = r.h0
changed = true
}
// widget might be forced to a certain location
if tk.full.h0 < tk.force.h0 {
tk.gocuiSize.h0 = tk.force.h0
tk.full.h0 = tk.force.h0
changed = false
}
if tk.full.h1 != r.h1 {
tk.full.h1 = r.h1
changed = true
}
if tk.WidgetType() == widget.Button {
tk.full.h1 = tk.full.h0 + 1
}
if tk.isDense() && tk.isInGrid() {
tk.full.h1 = tk.full.h0
}
if changed {
tk.dumpWidget(fmt.Sprintf("setFullSize(changed)"))
}
return changed
}
func (tk *guiWidget) gridFullSize() rectType {
var r rectType
var first bool = true
for _, child := range tk.children {
cr := child.getFullSize()
if cr.Width() == 0 && cr.Height() == 0 {
// ignore widgets of zero size?
continue
}
if first {
// use the lowest width and hight from children widgets
r.w0 = cr.w0
r.h0 = cr.h0
r.w1 = cr.w1
r.h1 = cr.h1
first = false
// child.dumpWidget(fmt.Sprintf("grid(f)"))
continue
}
// child.dumpWidget(fmt.Sprintf("grid()"))
// use the lowest width and hight from children widgets
if r.w0 > cr.w0 {
r.w0 = cr.w0
}
if r.h0 > cr.h0 {
r.h0 = cr.h0
}
// use the highest width and hight from children widgets
if r.w1 < cr.w1 {
r.w1 = cr.w1
}
if r.h1 < cr.h1 {
r.h1 = cr.h1
}
}
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
return r
}
func (tk *guiWidget) buttonFullSize() rectType {
var r rectType
r.w0 = tk.gocuiSize.w0
r.w1 = tk.gocuiSize.w1
r.h0 = tk.gocuiSize.h0
r.h1 = tk.gocuiSize.h1
// try setting the full values here ? is this right?
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
// total hack. fix this somewhere eventually correctly
if tk.isDense() { // total hack. fix this somewhere eventually correctly
tk.full.h0 += 1 // total hack. fix this somewhere eventually correctly
tk.full.h1 = tk.full.h0 // total hack. fix this somewhere eventually correctly
}
return r
}
// this checks a widget to see if it is under (W,H), then checks the widget's children
// anything that matches is passed back as an array of widgets
func (tk *guiWidget) getFullSize() rectType {
var r rectType
if tk.Hidden() {
/*
p := tk.parent
if p != nil {
return p.full
}
*/
var r rectType
r.w0 = 0
r.w1 = 0
r.h0 = 0
r.h1 = 0
return r
}
if tk.WidgetType() == widget.Grid {
return tk.gridFullSize()
}
// these are 'simple' widgets
// the full size is exactly what gocui uses
switch tk.WidgetType() {
case widget.Label:
r := tk.buttonFullSize()
r.w1 += 5
return r
case widget.Button:
r := tk.buttonFullSize()
r.w1 += 5
return r
case widget.Checkbox:
return tk.buttonFullSize()
case widget.Dropdown:
r := tk.buttonFullSize()
r.w1 += 7 // TODO: fix this to be real
return r
default:
}
if tk.v == nil {
r.w0 = tk.full.w0
r.w1 = tk.full.w1
r.h0 = tk.full.h0
r.h1 = tk.full.h1
} else {
r.w0 = tk.gocuiSize.w0
r.w1 = tk.gocuiSize.w1
r.h0 = tk.gocuiSize.h0
r.h1 = tk.gocuiSize.h1
}
// search through the children widgets in the binary tree
for _, child := range tk.children {
cr := child.getFullSize()
/* this didn't make things work either
if child.v == nil {
continue
}
*/
// use the lowest width and hight from children widgets
if r.w0 > cr.w0 {
r.w0 = cr.w0
}
if r.h0 > cr.h0 {
r.h0 = cr.h0
}
// use the highest width and hight from children widgets
if r.w1 < cr.w1 {
r.w1 = cr.w1
}
if r.h1 < cr.h1 {
r.h1 = cr.h1
}
}
// try setting the full values here ? is this right?
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
return r
}

73
stdoutFakefile.go Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"bytes"
"errors"
"io"
"github.com/awesome-gocui/gocui"
)
type FakeFile struct {
reader *bytes.Reader
buffer *bytes.Buffer
offset int64
view *gocui.View
}
func (f *FakeFile) Read(p []byte) (n int, err error) {
n, err = f.reader.ReadAt(p, f.offset)
f.offset += int64(n)
return n, err
}
var fakecount int = 0
func (f *FakeFile) Write(p []byte) (n int, err error) {
n, err = f.buffer.Write(p)
f.offset += int64(n)
f.reader.Reset(f.buffer.Bytes())
f.view.Write(p)
fakecount += 1
if fakecount > 20 {
fakecount = 0
f.view.Clear()
}
return n, err
}
func (f *FakeFile) Seek(offset int64, whence int) (int64, error) {
newOffset := f.offset
switch whence {
case io.SeekStart:
newOffset = offset
case io.SeekCurrent:
newOffset += offset
case io.SeekEnd:
newOffset = int64(f.buffer.Len()) + offset
default:
return 0, errors.New("Seek: whence not at start,current or end")
}
// never can get here right?
if newOffset < 0 {
return 0, errors.New("Seek: offset < 0")
}
f.offset = newOffset
return f.offset, nil
}
func NewFakeFile(v *gocui.View) *FakeFile {
buf := &bytes.Buffer{}
return &FakeFile{
reader: bytes.NewReader(buf.Bytes()),
buffer: buf,
offset: 0,
view: v,
}
}

View File

@ -4,9 +4,8 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"slices"
"strings"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
@ -14,206 +13,110 @@ import (
"go.wit.com/widget" "go.wit.com/widget"
) )
func createStdout(g *gocui.Gui) bool { var outputW int = 180
if me.stdout.tk == nil { var outputH int = 40
makeOutputWidget(g, "this is a create before a mouse click")
// me.logStdout.v.Write([]byte(msg))
// this will show very early debugging output func moveMsg(g *gocui.Gui) {
// keep this code commented out but do not remove it. when it doubt, this will be the Light of Elendil mx, my := g.MousePosition()
// NEVER REMOVE THIS CODE if !movingMsg && (mx != initialMouseX || my != initialMouseY) {
movingMsg = true
msg := fmt.Sprintf("test out gocuiEvent() %d\n", me.ecount)
me.stdout.tk.Write([]byte(msg))
log.Log(NOW, "logStdout test out")
} }
return true g.SetView("msg", mx-xOffset, my-yOffset, mx-xOffset+outputW, my-yOffset+outputH+me.FramePadH, 0)
me.startOutputW = mx - xOffset
me.startOutputH = my - yOffset
g.SetViewOnBottom("msg")
} }
func coreStdout() { func showMsg(g *gocui.Gui, v *gocui.View) error {
if me.stdout.tk != nil { var l string
return var err error
}
a := new(widget.Action)
a.ProgName = "2stdout2"
a.WidgetType = widget.Stdout
a.WidgetId = me.stdout.wId
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
me.stdout.tk = initWidget(n)
tk := me.stdout.tk log.Log(NOW, "showMsg() v.name =", v.Name())
tk.cuiName = "msg" if _, err := g.SetCurrentView(v.Name()); err != nil {
tk.gocuiSize.w0 = me.stdout.lastW return err
tk.gocuiSize.h0 = me.stdout.lastH }
tk.gocuiSize.w1 = tk.gocuiSize.w0 + me.stdout.w
tk.gocuiSize.h1 = tk.gocuiSize.h0 + me.stdout.h _, cy := v.Cursor()
if l, err = v.Line(cy); err != nil {
l = ""
}
outv := makeOutputWidget(g, l)
outv.Write([]byte("test out2"))
log.Info("test out2")
return nil
} }
func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View { func makeOutputWidget(g *gocui.Gui, stringFromMouseClick string) *gocui.View {
maxX, maxY := g.Size()
if me.treeRoot == nil { if me.treeRoot == nil {
// keep skipping this until the binary tree is initialized // keep skipping this until the binary tree is initialized
return nil return nil
} }
coreStdout() if me.logStdout == nil {
a := new(widget.Action)
a.ProgName = "stdout"
a.WidgetType = widget.Stdout
a.WidgetId = -3
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
n.TK = initWidget(n)
me.logStdout = n
if me.stdout.tk == nil { var tk *guiWidget
tk = me.logStdout.TK.(*guiWidget)
// tk.gocuiSize.w0 = maxX - 32
// tk.gocuiSize.h0 = maxY / 2
tk.gocuiSize.w0 = me.startOutputW
tk.gocuiSize.h0 = me.startOutputH
tk.gocuiSize.w1 = tk.gocuiSize.w0 + outputW
tk.gocuiSize.h1 = tk.gocuiSize.h0 + outputH
}
v, err := g.View("msg")
if v == nil {
log.Log(NOW, "makeoutputwindow() this is supposed to happen. v == nil", err)
} else {
log.Log(NOW, "makeoutputwindow() msg != nil. WTF now? err =", err)
}
// help, err := g.SetView("help", maxX-32, 0, maxX-1, 13, 0)
// v, err = g.SetView("msg", 3, 3, 30, 30, 0)
if me.startOutputW == 0 {
me.startOutputW = maxX - 32
}
if me.startOutputW == 0 {
me.startOutputH = maxY / 2
}
// v, err = g.SetView("msg", maxX-32, maxY/2, maxX/2+outputW, maxY/2+outputH, 0)
v, err = g.SetView("msg", me.startOutputW, me.startOutputH, maxX/2+outputW, maxY/2+outputH, 0)
if errors.Is(err, gocui.ErrUnknownView) {
log.Log(NOW, "makeoutputwindow() this is supposed to happen?", err)
}
if err != nil {
log.Log(NOW, "makeoutputwindow() create output window failed", err)
return nil return nil
} }
me.stdout.tk.cuiName = "msg"
me.stdout.tk.SetView()
v, err := g.View("msg")
if v == nil { if v == nil {
// log.Log(NOW, "makeoutputwindow() this is supposed to happen. v == nil", err) log.Log(NOW, "makeoutputwindow() msg == nil. WTF now? err =", err)
return nil
} else { } else {
log.Log(NOW, "makeoutputwindow() msg != nil. WTF now? err =", err) var tk *guiWidget
return v tk = me.logStdout.TK.(*guiWidget)
tk.v = v
} }
v = me.stdout.tk.v
v.Clear() v.Clear()
v.SelBgColor = gocui.ColorCyan v.SelBgColor = gocui.ColorCyan
v.SelFgColor = gocui.ColorBlack v.SelFgColor = gocui.ColorBlack
fmt.Fprintln(v, "figure out how to capture STDOUT to here\n"+stringFromMouseClick) fmt.Fprintln(v, "figure out how to capture STDOUT to here\n"+stringFromMouseClick)
// g.SetViewOnBottom("msg") g.SetViewOnBottom("msg")
// setBottomBG() // g.SetViewOnBottom(v.Name())
me.stdout.tk.DrawAt(me.stdout.lastW, me.stdout.lastH)
relocateStdoutOffscreen()
return v return v
} }
func relocateStdoutOffscreen() {
if me.stdout.tk == nil {
return
}
if !me.stdout.disable {
log.Info("Using gocui STDOUT")
log.CaptureMode(me.stdout.tk)
}
newW := 10
newH := 0 - me.stdout.h - 4
me.stdout.tk.relocateStdout(newW, newH)
}
func (tk *guiWidget) relocateStdout(w int, h int) {
if me.stdout.w < 8 {
me.stdout.w = 8
}
if me.stdout.h < 4 {
me.stdout.h = 4
}
if w+me.stdout.w < 2 {
w = 2
}
if h+me.stdout.h < 2 {
h = 2
}
w0 := w
h0 := h
w1 := w + me.stdout.w
h1 := h + me.stdout.h
tk.gocuiSize.w0 = w0
tk.gocuiSize.w1 = w1
tk.gocuiSize.h0 = h0
tk.gocuiSize.h1 = h1
tk.full.w0 = w0
tk.full.w1 = w1
tk.full.h0 = h0
tk.full.h1 = h1
tk.SetView()
}
// from the gocui devs:
// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (w stdout) Height() int {
if w.tk == nil {
return 40
}
return w.tk.gocuiSize.Height() - 2
}
func (w stdout) Write(p []byte) (n int, err error) {
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
lines := strings.Split(strings.TrimSpace(string(p)), "\n")
me.stdout.outputS = append(me.stdout.outputS, lines...)
if me.outf != nil {
fmt.Fprint(me.outf, string(p))
}
return len(p), nil
}
func (w *guiWidget) Write(p []byte) (n int, err error) {
lines := strings.Split(strings.TrimSpace(string(p)), "\n")
if me.outf != nil {
fmt.Fprint(me.outf, string(p))
}
if w == nil {
me.stdout.outputS = append(me.stdout.outputS, lines...)
return len(p), nil
}
w.tainted = true
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
me.stdout.outputS = append(me.stdout.outputS, lines...)
tk := me.stdout.tk
if tk == nil {
return len(p), nil
}
if tk.v == nil {
// redo this old code
v, _ := me.baseGui.View("msg")
if v != nil {
tk.v = v
}
return len(p), nil
}
tk.refreshStdout()
return len(p), nil
}
// lets the user page up and down through the stdout buffer
func (tk *guiWidget) refreshStdout() {
if len(me.stdout.outputS) < me.stdout.h+me.stdout.pager {
// log.Info(fmt.Sprintf("buffer too small=%d len(%d)", me.stdout.pager, len(me.stdout.outputS)))
var cur []string
cur = append(cur, me.stdout.outputS...)
slices.Reverse(cur)
tk.v.Clear()
fmt.Fprintln(tk.v, strings.Join(cur, "\n"))
return
}
var cur []string
// chop off the last lines in the buffer
chop := len(me.stdout.outputS) - (me.stdout.pager + me.stdout.h)
cur = append(cur, me.stdout.outputS[chop:chop+me.stdout.h]...)
if me.stdout.reverse {
slices.Reverse(cur)
}
tk.v.Clear()
fmt.Fprintln(tk.v, strings.Join(cur, "\n"))
}

View File

@ -1,6 +1,9 @@
// LICENSE: same as the go language itself
// Copyright 2017-2025 WIT.COM Inc. All rights reserved. // Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0 // Use of this source code is governed by the GPL 3.0
// Copyright 2023 WIT.COM
// all structures and variables are local (aka lowercase) // all structures and variables are local (aka lowercase)
// since the plugin should be isolated to access only // since the plugin should be isolated to access only
// by functions() to insure everything here is run // by functions() to insure everything here is run
@ -10,150 +13,105 @@ package main
import ( import (
"fmt" "fmt"
"os"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"sync" "sync"
"time"
"github.com/awesome-gocui/gocui" "github.com/awesome-gocui/gocui"
"go.wit.com/lib/protobuf/guipb" "go.wit.com/log"
log "go.wit.com/log"
"go.wit.com/toolkits/tree" "go.wit.com/toolkits/tree"
"go.wit.com/widget" "go.wit.com/widget"
) )
var initOnce sync.Once // run initPlugin() only once
// It's probably a terrible idea to call this 'me' // It's probably a terrible idea to call this 'me'
// 2025 note: doesn't seem terrible to call this 'me' anymore. notsure.
var me config var me config
// todo: move all this to a protobuf. then, redo all this mess. var showDebug bool = true
// it got me here, but now it's time to clean it up for good var showHelp bool = true
// I can't get a GO plugins that use protobuf to load yet (versioning mismatch) var redoWidgets bool = true
// This is the window that is currently active
// var currentWindow *tree.Node
type config struct { type config struct {
baseGui *gocui.Gui // the main gocui handle baseGui *gocui.Gui // the main gocui handle
// rootNode *node // the base of the binary tree. it should have id == 0
treeRoot *tree.Node // the base of the binary tree. it should have id == 0 treeRoot *tree.Node // the base of the binary tree. it should have id == 0
myTree *tree.TreeInfo // ? myTree *tree.TreeInfo
currentWindow *guiWidget // this is the current tab or window to show
ok bool // if the user doesn't hit a key or move the mouse, gocui doesn't really start
firstWindowOk bool // allows the init to wait for the first window from the application
refresh bool // redraw everything?
ctrlDown *tree.Node // shown if you click the mouse when the ctrl key is pressed ctrlDown *tree.Node // shown if you click the mouse when the ctrl key is pressed
helpLabel *gocui.View // ? currentWindow *guiWidget // this is the current tab or window to show
showHelp bool // toggle boolean for the help menu (deprecate?) logStdout *tree.Node // where to show STDOUT
FirstWindowW int `default:"2"` // how far over to start window #1 startOutputW int
FirstWindowH int `default:"0"` // how far down to start window #1 startOutputH int
FramePadW int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side helpLabel *gocui.View
FramePadH int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side
PadW int `default:"1" dense:"0"` // pad spacing // this is a floating widget that we show whenever the user clicks on a
PadH int `default:"1" dense:"0"` // pad spacing // dropdown menu or combobox
WindowW int `default:"8" dense:"0"` // how far down to start Window or Tab headings // the dropdown widget to select dropdrown lists
WindowH int `default:"-1"` // how far down to start Window or Tab headings dropdownV *guiWidget
TabW int `default:"5" dense:"0"` // how far down to start Window or Tab headings dropdownW *guiWidget // grab the dropdown choices from this widget
TabH int `default:"1" dense:"0"` // how far down to start Window or Tab headings
WindowPadW int `default:"8" dense:"0"` // additional amount of space to put between window & tab widgets // When the widget has a frame, like a button, it adds 2 lines runes on each side
TabPadW int `default:"4" dense:"0"` // additional amount of space to put between window & tab widgets // so you need 3 char spacing in each direction to not have them overlap
GroupPadW int `default:"2" dense:"1"` // additional amount of space to indent on a group // the amount of padding when there is a frame
BoxPadW int `default:"2" dense:"1"` // additional amount of space to indent on a box FramePadW int `default:"1" dense:"0"`
GridPadW int `default:"2" dense:"1"` // additional amount of space to indent on a grid FramePadH int `default:"1" dense:"0"`
RawW int `default:"1"` // the raw beginning of each window (or tab)
RawH int `default:"5"` // the raw beginning of each window (or tab) PadW int `default:"1" dense:"0"`
FakeW int `default:"20"` // offset for the hidden widgets PadH int `default:"1" dense:"0"`
DropdownId int `default:"-78"` // the widget id to use
// how far down to start Window or Tab headings
WindowW int `default:"8" dense:"0"`
WindowH int `default:"-1"`
TabW int `default:"5" dense:"0"`
TabH int `default:"1" dense:"0"`
// additional amount of space to put between window & tab widgets
WindowPadW int `default:"8" dense:"0"`
TabPadW int `default:"4" dense:"0"`
// additional amount of space to indent on a group
GroupPadW int `default:"2" dense:"1"`
// additional amount of space to indent on a group
BoxPadW int `default:"2" dense:"1"`
// additional amount of space to indent on a group
GridPadW int `default:"2" dense:"1"`
// the raw beginning of each window (or tab)
RawW int `default:"1"`
RawH int `default:"5"`
// offset for the hidden widgets
FakeW int `default:"20"`
padded bool // add space between things like buttons padded bool // add space between things like buttons
bookshelf bool // do you want things arranged in the box like a bookshelf or a stack? bookshelf bool // do you want things arranged in the box like a bookshelf or a stack?
canvas bool // if set to true, the windows are a raw canvas canvas bool // if set to true, the windows are a raw canvas
menubar bool // for windows menubar bool // for windows
stretchy bool // expand things like buttons to the maximum size stretchy bool // expand things like buttons to the maximum size
margin bool // add space around the frames of windows margin bool // add space around the frames of windows
writeMutex sync.Mutex // writeMutex protects writes to *guiWidget (it's global right now maybe)
writeMutex sync.Mutex // TODO: writeMutex protects locks the write process
fakefile *FakeFile // JUNK? used to attempt to write to the stdout window
dtoggle bool // is a dropdown or combobox currently active?
// debugging things
ecount int // counts how many mouse and keyboard events have occurred ecount int // counts how many mouse and keyboard events have occurred
supermouse bool // prints out every widget found while you move the mouse around supermouse bool // prints out every widget found while you move the mouse around
depth int // used for listWidgets() debugging depth int // used for listWidgets() debugging
newWindowTrigger chan *guiWidget // work around hack to redraw windows a bit after NewWindow()
stdout stdout // information for the STDOUT window
dropdown dropdown // the dropdown menu
textbox dropdown // the textbox popup window
BG dropdown // the background widget
notify libnotify // emulates the desktop libnotify menu
allwin []*guiWidget // for tracking which window is next
dark bool // use a 'dark' color palette
mouse mouse // mouse settings
showDebug bool // todo: move this into config struct
debug bool // todo: move this into config struct
starttime time.Time // checks how long it takes on startup
winchW int // used to detect SIGWINCH
winchH int // used to detect SIGWINCH
outf *os.File // hacks for capturing stdout
} }
// stuff controlling how the mouse works // deprecate these
type mouse struct { var (
down time.Time // when the mouse was pressed down initialMouseX, initialMouseY, xOffset, yOffset int
up time.Time // when the mouse was released. used to detect click vs drag globalMouseDown, msgMouseDown, movingMsg bool
clicktime time.Duration // how long is too long for a mouse click vs drag )
mouseUp bool // is the mouse up?
double bool // user is double clicking
doubletime time.Duration // how long is too long for double click
resize bool // mouse is resizing something
downW int // where the mouse was pressed down
downH int // where the mouse was pressed down
currentDrag *guiWidget // what widget is currently being moved around
}
// settings for the stdout window
type stdout struct {
tk *guiWidget // where to show STDOUT
wId int // the widget id
w int // the width
h int // the height
outputOnTop bool // is the STDOUT window on top?
outputOffscreen bool // is the STDOUT window offscreen?
startOnscreen bool // start the output window onscreen?
disable bool // disable the stdout window. do not change os.Stdout & os.Stderr
lastW int // the last 'w' location (used to move from offscreen to onscreen)
lastH int // the last 'h' location (used to move from offscreen to onscreen)
init bool // moves the window offscreen on startup
outputS []string // the buffer of all the output
pager int // allows the user to page through the buffer
changed bool // indicates the user has changed stdout. gocui should remember the state here
reverse bool // flip the STDOUT upside down so new STDOUT lines are at the top
}
// settings for the dropdown window
type dropdown struct {
tk *guiWidget // where to show STDOUT
callerTK *guiWidget // which widget called the dropdown menu
items []string // what is currently in the menu
w int // the width
h int // the height
active bool // is the dropdown menu currently in use?
init bool // moves the window offscreen on startup
// Id int `default:"-78"` // the widget id to use
wId int `default:"-78"` // the widget id to use
}
// settings for the dropdown window
type internalTK struct {
once sync.Once // for init
tk *guiWidget // where to show STDOUT
callerTK *guiWidget // which widget called the dropdown menu
wId int // the widget id to use
active bool // is the internal widget currently in use?
offsetW int // width offset
offsetH int // height offset
}
// the desktop libnotify menu
type libnotify struct {
clock internalTK // widget for the clock
icon internalTK // libnotify menu icon
window internalTK // the libnotify menu
help internalTK // the help menu
}
// this is the gocui way // this is the gocui way
// corner starts at in the upper left corner // corner starts at in the upper left corner
@ -166,87 +124,115 @@ func (r *rectType) Width() int {
} }
func (r *rectType) Height() int { func (r *rectType) Height() int {
if r.h0 == 0 && r.h1 == 0 {
// edge case. only return 0 for these
return 0
}
if r.h1 == r.h0 {
// if they are equal, it's actually height = 1
return 1
}
if r.h1-r.h0 < 1 {
// can't have negatives. something is wrong. return 1 for now
return 1
}
return r.h1 - r.h0 return r.h1 - r.h0
} }
// settings that are window specific
type window struct {
windowFrame *guiWidget // this is the frame for a window widget
wasDragged bool // indicates the window was dragged. This keeps it from being rearranged
hasTabs bool // does the window have tabs?
currentTab bool // the visible tab
selectedTab *tree.Node // for a window, this is currently selected tab
active bool // means this window is the active one
order int // what level the window is on
// resize bool // only set the title once
collapsed bool // only show the window title bar
dense bool // true if the window is dense
large bool // true if the window is huge
pager int // allows the user to page through the window
}
type colorT struct {
frame gocui.Attribute
fg gocui.Attribute
bg gocui.Attribute
selFg gocui.Attribute
selBg gocui.Attribute
name string
}
type guiWidget struct { type guiWidget struct {
// the gocui package variables
v *gocui.View // this is nil if the widget is not displayed v *gocui.View // this is nil if the widget is not displayed
cuiName string // what gocui uses to reference the widget (usually "TK <widgetId>" cuiName string // what gocui uses to reference the widget
parent *guiWidget // mirrors the binary node tree
children []*guiWidget // mirrors the binary node tree WidgetType widget.WidgetType
node *tree.Node // the pointer back to the tree
pb *guipb.Widget // the guipb Widget // tw *toolkit.Widget
wtype widget.WidgetType // used for Tables for now. todo: fix this correctly parent *guiWidget
windowFrame *guiWidget // this is the frame for a window widget children []*guiWidget
internal bool // indicates the widget is internal to gocui and should be treated differently
node *tree.Node
hasTabs bool // does the window have tabs? hasTabs bool // does the window have tabs?
currentTab bool // the visible tab currentTab bool // the visible tab
window window // holds information specific only to Window widgets
value string // ? value string
checked bool // ? checked bool
labelN string // the actual text to display in the console
// the actual text to display in the console
labelN string
vals []string // dropdown menu items vals []string // dropdown menu items
enable bool // ?
gocuiSize rectType // should mirror the real display size. todo: verify these are accurate. they are not yet active bool
full rectType // full size of children (used by widget.Window, etc)
force rectType // force widget within these boundries (using this to debug window dragging) enable bool
startW int // ? defaultColor *colorT // store the color to go back to
startH int // ?
lastW int // used during mouse dragging // hidden bool
lastH int // used during mouse dragging
// AtW int
// AtH int
// direction widget.Orientation
// progname string
// the logical size of the widget
// For example, 40x12 would be the center of a normal terminal
// size rectType
// the actual gocui display view of this widget
// sometimes this isn't visible like with a Box or Grid
gocuiSize rectType
startW int
startH int
isCurrent bool // is this the currently displayed Window or Tab?
isFake bool // widget types like 'box' are 'false' isFake bool // widget types like 'box' are 'false'
// used to track the size of grids
widths map[int]int // how tall each row in the grid is widths map[int]int // how tall each row in the grid is
heights map[int]int // how wide each column in the grid is heights map[int]int // how wide each column in the grid is
tainted bool // ?
frame bool // ? tainted bool
selectedTab *tree.Node // for a window, this is currently selected tab frame bool
color *colorT // what color to use
colorLast colorT // the last color the widget had // for a window, this is currently selected tab
defaultColor *colorT // the default colors // TODO: make a function for this instead selectedTab *tree.Node
isTable bool // is this a table?
// what color to use
color *colorT
}
// from the gocui devs:
// Write appends a byte slice into the view's internal buffer. Because
// View implements the io.Writer interface, it can be passed as parameter
// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
// be called to clear the view's buffer.
func (w *guiWidget) Write(p []byte) (n int, err error) {
w.tainted = true
me.writeMutex.Lock()
defer me.writeMutex.Unlock()
var tk *guiWidget
tk = me.logStdout.TK.(*guiWidget)
if tk.v == nil {
// optionally write the output to /tmp
s := fmt.Sprint(string(p))
s = strings.TrimSuffix(s, "\n")
fmt.Fprintln(outf, s)
v, _ := me.baseGui.View("msg")
if v != nil {
// fmt.Fprintln(outf, "found msg")
tk.v = v
}
} else {
// display the output in the gocui window
tk.v.Clear()
s := fmt.Sprint(string(p))
s = strings.TrimSuffix(s, "\n")
tmp := strings.Split(s, "\n")
outputS = append(outputS, tmp...)
if len(outputS) > outputH {
l := len(outputS) - outputH
outputS = outputS[l:]
}
fmt.Fprintln(tk.v, strings.Join(outputS, "\n"))
}
return len(p), nil
} }
// THIS IS GO COMPILER MAGIC
// this sets the `default` in the structs above on init()
// this is cool code. thank the GO devs for this code and Alex Flint for explaining it to me
func Set(ptr interface{}, tag string) error { func Set(ptr interface{}, tag string) error {
if reflect.TypeOf(ptr).Kind() != reflect.Ptr { if reflect.TypeOf(ptr).Kind() != reflect.Ptr {
log.Log(ERROR, "Set() Not a pointer", ptr, "with tag =", tag) log.Log(ERROR, "Set() Not a pointer", ptr, "with tag =", tag)
@ -266,11 +252,12 @@ func Set(ptr interface{}, tag string) error {
} }
func setField(field reflect.Value, defaultVal string, name string) error { func setField(field reflect.Value, defaultVal string, name string) error {
if !field.CanSet() { if !field.CanSet() {
// log("setField() Can't set value", field, defaultVal) // log("setField() Can't set value", field, defaultVal)
return fmt.Errorf("Can't set value\n") return fmt.Errorf("Can't set value\n")
} else { } else {
// log.Log(NOW, "setField() Can set value", name, defaultVal) log.Log(NOW, "setField() Can set value", name, defaultVal)
} }
switch field.Kind() { switch field.Kind() {

View File

@ -1,93 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"fmt"
"slices"
"go.wit.com/lib/protobuf/guipb"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func initGridPB(pb *guipb.Widget) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.pb = pb
w.wtype = widget.Grid
w.cuiName = fmt.Sprintf("%d %s", pb.Id, "TK")
w.labelN = pb.Name
w.isTable = true
return w
}
func showTable(t *guipb.Table) {
log.Info("gocui: should show table here")
if t == nil {
return
}
log.Info("gocui: table.Title", t.Title)
// log.Info("gocui: need to add window here id =", t.Window.Id, t.Window.Name)
if t.Grid == nil {
log.Info("gocui: missing grid widget. tree plugin error")
return
}
root := me.treeRoot.TK.(*guiWidget)
parent := root.findWidgetById(int(t.Parent.Id))
if parent == nil {
log.Info("gocui: show table error. parent.Id not found", t.Parent.Id)
return
}
log.Info("gocui: need to add grid here id =", t.Grid.Id)
grid := initGridPB(t.Grid)
grid.parent = parent
}
func enableWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Enable()
}
func disableWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Disable()
}
func showWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
tk.Show()
}
func hideWidget(n *tree.Node) {
tk := n.TK.(*guiWidget)
if n.WidgetType == widget.Window {
tk.windowFrame.Hide()
tk.hideWidgets()
}
tk.Hide()
tk.deleteWidget()
}
func (tk *guiWidget) deleteWidget() {
log.Log(INFO, "gocui deleteWidget() looking for child to delete:", tk.cuiName)
p := tk.parent
for i, child := range p.children {
if tk == child {
log.Log(INFO, "deleteWidget() found parent with child to delete:", i, child.cuiName, child.WidgetId())
p.children = slices.Delete(p.children, i, i+1)
}
}
tk.deleteTree()
}
func (tk *guiWidget) deleteTree() {
for _, child := range tk.children {
child.deleteTree()
}
tk.Hide()
}

View File

@ -1,151 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
// simulates a dropdown menu in gocui
import (
"strings"
"time"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
)
func (tk *guiWidget) forceSizes(r *rectType) {
tk.gocuiSize.w0 = r.w0
tk.gocuiSize.w1 = r.w1
tk.gocuiSize.h0 = r.h0
tk.gocuiSize.h1 = r.h1
tk.full.w0 = r.w0
tk.full.w1 = r.w1
tk.full.h0 = r.h0
tk.full.h1 = r.h1
tk.force.w0 = r.w0
tk.force.w1 = r.w1
tk.force.h0 = r.h0
tk.force.h1 = r.h1
}
func initTextbox() {
if me.textbox.tk == nil {
// should only happen once
me.textbox.tk = makeNewFlagWidget(me.textbox.wId)
// me.textbox.tk.dumpWidget("init() textbox")
}
}
func (callertk *guiWidget) prepTextbox() {
initTextbox()
if me.textbox.tk == nil {
log.Log(WARN, "prepTextbox() Is Broken")
return
}
r := new(rectType)
// startW, startH := tk.Position()
r.w0 = callertk.gocuiSize.w0 + 4
r.h0 = callertk.gocuiSize.h0 + 3
r.w1 = r.w0 + 24
r.h1 = r.h0 + 2
me.textbox.tk.forceSizes(r)
me.textbox.tk.dumpWidget("after sizes")
me.textbox.callerTK = callertk
if me.textbox.tk.v != nil {
log.Log(WARN, "WARNING textbox DeleteView()")
log.Log(WARN, "WARNING textbox DeleteView()")
log.Log(WARN, "WARNING textbox DeleteView()")
me.baseGui.DeleteView(me.textbox.tk.cuiName)
time.Sleep(time.Second)
}
if err := me.textbox.tk.SetViewRect(r); err != nil {
log.Log(WARN, "textbox SetViewRect() failed", err, "view name =", me.textbox.tk.cuiName)
return
}
// me.textbox.tk.Show() // actually makes the gocui view. TODO: redo this?
showTextbox(callertk.String())
}
func showTextbox(callers string) {
// tk := me.textbox.tk
// me.textbox.tk.dumpWidget("after sizes")
log.Log(WARN, "showTextbox() caller string =", callers)
// me.textbox.tk.Show() // actually makes the gocui view. TODO: redo this
if me.textbox.tk.v == nil {
log.Log(WARN, "textbox.tk.v == nil showTextbox() is broken")
return
}
me.textbox.tk.setColorModal()
me.textbox.tk.v.Clear()
cur := strings.TrimSpace(callers)
// log.Info("setting textbox string to:", cur)
me.textbox.tk.v.WriteString(cur)
me.textbox.tk.v.Editable = true
me.textbox.tk.v.Wrap = true
me.textbox.tk.SetView()
me.baseGui.SetCurrentView(me.textbox.tk.v.Name())
// bind the enter key to a function so we can close the textbox
me.baseGui.SetKeybinding(me.textbox.tk.v.Name(), gocui.KeyEnter, gocui.ModNone, theCloseTheTextbox)
me.textbox.active = true
me.baseGui.SetViewOnTop(me.textbox.tk.v.Name())
me.textbox.tk.dumpWidget("showTextbox()")
}
func theCloseTheTextbox(g *gocui.Gui, v *gocui.View) error {
textboxClosed()
return nil
}
// updates the text and sends an event back to the application
func textboxClosed() {
// get the text the user entered
var newtext string
if me.textbox.tk.v == nil {
newtext = ""
} else {
newtext = me.textbox.tk.v.ViewBuffer()
}
newtext = strings.TrimSpace(newtext)
me.textbox.active = false
me.textbox.tk.Hide()
// log.Info("textbox closed with text:", newtext, me.textbox.callerTK.cuiName)
if me.notify.clock.tk.v != nil {
me.baseGui.SetCurrentView("help")
} else {
me.baseGui.SetCurrentView("msg")
}
// change the text of the caller widget
me.textbox.callerTK.SetText(newtext)
me.textbox.callerTK.node.SetCurrentS(newtext)
// send an event from the plugin with the new string
me.myTree.SendUserEvent(me.textbox.callerTK.node)
win := me.textbox.callerTK.findParentWindow()
if win != nil {
// win.dumpWidget("redraw this!!!")
tk := me.textbox.callerTK
// me.textbox.callerTK.dumpWidget("resize this!!!")
me.textbox.callerTK.Size()
me.textbox.callerTK.placeWidgets(tk.gocuiSize.w0-4, tk.gocuiSize.h0-4)
// tk.dumpWidget("resize:" + tk.String())
win.makeWindowActive()
}
}

32
tree.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
/*
This is reference code for toolkit developers
This is how information is passed in GO back to the application
via the GO 'plugin' concept
TODO: switch this to protocol buffers
*/
import (
"go.wit.com/widget"
)
// Other goroutines must use this to access the GUI
//
// You can not acess / process the GUI thread directly from
// other goroutines. This is due to the nature of how
// Linux, MacOS and Windows work (they all work differently. suprise. surprise.)
//
// this sets the channel to send user events back from the plugin
func Callback(guiCallback chan widget.Action) {
me.myTree.Callback(guiCallback)
}
func PluginChannel() chan widget.Action {
return me.myTree.PluginChannel()
}

View File

@ -28,85 +28,63 @@ func setFake(n *tree.Node) {
} }
} }
// mostly just sets the colors of things // set the widget start width & height
// func (n *node) addWidget(n *tree.Node) {
func addWidget(n *tree.Node) { func addWidget(n *tree.Node) {
if !me.ok { var nw *guiWidget
log.Log(INFO, "addWidget() START NOT OKAY") nw = n.TK.(*guiWidget)
log.Log(INFO, "addWidget() START NOT OKAY")
log.Log(INFO, "addWidget() START NOT OKAY")
waitOK()
}
tk := n.TK.(*guiWidget)
log.Log(INFO, "setStartWH() w.id =", n.WidgetId, "n.name", n.String()) log.Log(INFO, "setStartWH() w.id =", n.WidgetId, "n.name", n.String())
switch n.WidgetType { switch n.WidgetType {
case widget.Root: case widget.Root:
log.Log(INFO, "setStartWH() rootNode w.id =", n.WidgetId, "w.name", n.String()) log.Log(INFO, "setStartWH() rootNode w.id =", n.WidgetId, "w.name", n.String())
nw.color = &colorRoot
setFake(n) setFake(n)
return return
case widget.Flag: case widget.Flag:
nw.color = &colorFlag
setFake(n) setFake(n)
return return
case widget.Window: case widget.Window:
tk.frame = false nw.frame = false
tk.labelN = tk.GetText() + " X" // nw.color = &colorWindow
me.newWindowTrigger <- tk nw.setColor(&colorWindow)
redoWindows(0, 0) wRoot := me.treeRoot.TK.(*guiWidget)
return wRoot.redoWindows(0, 0)
case widget.Stdout: // TODO: record the first window here?
tk.labelN = "moreSTDOUT" // do initial setup of helper widgets here:
n.State.ProgName = "moreSTDOUT" if me.dropdownV == nil {
n.State.Label = "moreSTDOUT" me.dropdownV = makeDropdownView("addWidget() ddview")
tk.isFake = true
return
case widget.Tab:
return
case widget.Button:
tk.setColorButton()
if tk.IsEnabled() {
} else {
tk.setColorDisable()
} }
return return
case widget.Tab:
nw.color = &colorTab
// redoWindows(0,0)
return
case widget.Button:
nw.color = &colorButton
case widget.Checkbox: case widget.Checkbox:
tk.setColorInput() nw.color = &colorCheckbox
tk.labelN = "X " + n.State.Label
return
case widget.Dropdown: case widget.Dropdown:
tk.setColorInput() nw.color = &colorDropdown
return
case widget.Textbox:
n.State.Label = ""
tk.labelN = " "
tk.setColorInput()
return
case widget.Combobox: case widget.Combobox:
tk.setColorInput() nw.color = &colorCombobox
return
case widget.Box: case widget.Box:
tk.isFake = true nw.color = &colorBox
nw.isFake = true
setFake(n) setFake(n)
return return
case widget.Grid: case widget.Grid:
tk.isFake = true nw.color = &colorGrid
nw.isFake = true
setFake(n) setFake(n)
return return
case widget.Group: case widget.Group:
tk.setColorLabel() nw.color = &colorGroup
tk.frame = false nw.frame = false
return return
case widget.Label: case widget.Label:
if tk.node.InTable() { nw.color = &colorLabel
if tk.node.State.AtH == 0 { nw.frame = false
// this is the table header
tk.setColorLabelTable()
} else {
// todo: highlight the whole table row
tk.setColorLabel()
}
} else {
tk.setColorLabel()
}
tk.frame = false
return return
default: default:
/* /*
@ -115,5 +93,5 @@ func addWidget(n *tree.Node) {
} }
*/ */
} }
tk.dumpWidget("addWidget()unknown") nw.showWidgetPlacement("addWidget()")
} }

115
treeDraw.go Normal file
View File

@ -0,0 +1,115 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"errors"
"fmt"
"strconv"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
)
var toggle bool = true
func (w *guiWidget) DrawAt(offsetW, offsetH int) {
w.setColor(&colorActiveW)
w.placeWidgets(offsetW, offsetH) // compute the sizes & places for each widget
w.active = false
w.showWidgets()
}
func (w *guiWidget) toggleTree() {
if toggle {
w.drawTree(toggle)
toggle = false
} else {
w.hideWidgets()
toggle = true
}
}
// display the widgets in the binary tree
func (w *guiWidget) drawTree(draw bool) {
if w == nil {
return
}
w.showWidgetPlacement("drawTree()")
if draw {
// w.textResize()
w.Show()
} else {
w.Hide()
}
for _, child := range w.children {
child.drawTree(draw)
}
}
// display's the text of the widget in gocui
// deletes the old view if it exists and recreates it
func (w *guiWidget) drawView() {
var err error
log.Log(INFO, "drawView() START", w.WidgetType, w.String())
if me.baseGui == nil {
log.Log(ERROR, "drawView() ERROR: me.baseGui == nil", w)
return
}
if w.cuiName == "" {
log.Log(ERROR, "drawView() w.cuiName was not set for widget", w)
w.cuiName = strconv.Itoa(w.node.WidgetId) + " TK"
}
log.Log(INFO, "drawView() labelN =", w.labelN)
// this deletes the button from gocui
me.baseGui.DeleteView(w.cuiName)
w.v = nil
w.textResize()
a := w.gocuiSize.w0
b := w.gocuiSize.h0
c := w.gocuiSize.w1
d := w.gocuiSize.h1
w.v, err = me.baseGui.SetView(w.cuiName, a, b, c, d, 0)
if err == nil {
w.showWidgetPlacement("drawView()")
log.Log(ERROR, "drawView() internal plugin error err = nil")
return
}
if !errors.Is(err, gocui.ErrUnknownView) {
w.showWidgetPlacement("drawView()")
log.Log(ERROR, "drawView() internal plugin error error.IS()", err)
return
}
// this sets up the keybinding for the name of the window
// does this really need to be done? I think we probably already
// know everything about where all the widgets are so we could bypass
// the gocui package and just handle all the mouse events internally here (?)
// for now, the w.v.Name is a string "1", "2", "3", etc from the widgetId
// set the binding for this gocui view now that it has been created
// gocui handles overlaps of views so it will run on the view that is clicked on
me.baseGui.SetKeybinding(w.v.Name(), gocui.MouseLeft, gocui.ModNone, click)
// this actually sends the text to display to gocui
w.v.Wrap = true
w.v.Frame = w.frame
w.v.Clear()
fmt.Fprint(w.v, w.labelN)
// if you don't do this here, it will be black & white only
if w.color != nil {
w.v.FrameColor = w.color.frame
w.v.FgColor = w.color.fg
w.v.BgColor = w.color.bg
w.v.SelFgColor = w.color.selFg
w.v.SelBgColor = w.color.selBg
}
log.Log(INFO, "drawView() END")
}

View File

@ -1,90 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
/*
DO NOT EDIT THIS FILE
this file is the same for every GUI toolkit plugin
when you are making a new GUI toolkit plugin for
a specific toolkit, you just need to define these
functions.
for example, in the "gocui" toolkit, the functions
below are what triggers the "gocui" GO package
to draw labels, buttons, windows, etc
If you are starting out trying to make a new GUI toolkit,
all you have to do is copy this file over. Then
work on making these functions. addWidget(), setText(), etc.
That's it!
*/
package main
/*
This is reference code for toolkit developers
This is how information is passed in GO back to the application
via the GO 'plugin' concept
TODO: switch this to protocol buffers
*/
import (
"time"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
// Other goroutines must use this to access the GUI
//
// You can not acess / process the GUI thread directly from
// other goroutines. This is due to the nature of how
// Linux, MacOS and Windows work (they all work differently. suprise. surprise.)
//
// this sets the channel to send user events back from the plugin
func Callback(guiCallback chan widget.Action) {
me.myTree.Callback(guiCallback)
}
func PluginChannel() chan widget.Action {
initOnce.Do(initPlugin)
for {
if me.myTree != nil {
break
}
log.Info("me.myTree == nil")
time.Sleep(300 * time.Millisecond)
}
return me.myTree.PluginChannel()
}
func FrozenChannel() chan widget.Action {
return me.myTree.FrozenChannel()
}
func initTree() *tree.TreeInfo {
t := tree.New()
t.PluginName = PLUGIN
t.Add = newAdd
t.SetTitle = setTitle
t.SetLabel = setLabel
t.SetText = setText
t.AddText = addText
t.Enable = enableWidget
t.Disable = disableWidget
t.Show = showWidget
t.Hide = hideWidget
t.SetChecked = setChecked
t.ToolkitInit = toolkitInit
t.ToolkitClose = toolkitClose
t.ShowTable = showTable
return t
}

203
treeWidget.go Normal file
View File

@ -0,0 +1,203 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"strconv"
"github.com/awesome-gocui/gocui"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func initWidget(n *tree.Node) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.node = n
w.cuiName = strconv.Itoa(w.node.WidgetId) + " TK"
w.WidgetType = n.WidgetType
w.labelN = n.State.Label
if w.labelN == "" {
// remove this debugging hack once things are stable and fixed
w.labelN = n.GetProgName()
}
w.frame = true
w.enable = n.State.Enable
if n.WidgetType == widget.Root {
log.Log(INFO, "setupWidget() FOUND ROOT w.id =", n.WidgetId)
}
if n.WidgetType == widget.Grid {
w.widths = make(map[int]int) // how tall each row in the grid is
w.heights = make(map[int]int) // how wide each column in the grid is
}
p := n.Parent
if p == nil {
log.Log(ERROR, "parent == nil", w.String(), n.WidgetId, w.WidgetType)
return w
}
if p.TK == nil {
log.Log(ERROR, "parent.TK == nil", w.String(), n.WidgetId, w.WidgetType)
return w
}
// set the parent and append to parent children
var ptk *guiWidget
ptk = p.TK.(*guiWidget)
w.parent = ptk
ptk.children = append(ptk.children, w)
return w
}
func setupCtrlDownWidget() {
a := new(widget.Action)
a.ProgName = "ctrlDown"
a.WidgetType = widget.Dialog
a.WidgetId = -1
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
me.ctrlDown = n
}
func (w *guiWidget) deleteView() {
// make sure the view isn't really there
// log.Log(GOCUI, "deleteView()", w.cuiName, w.WidgetType, w.node.WidgetId)
me.baseGui.DeleteView(w.cuiName)
w.v = nil
}
func (w *guiWidget) IsCurrent() bool {
if w.node.WidgetType == widget.Tab {
return w.isCurrent
}
if w.node.WidgetType == widget.Window {
// log.Log(GOCUI, "IsCurrent() found current window", w.cuiName, w.String())
// log.Log(GOCUI, "IsCurrent() window w.isCurrent =", w.isCurrent)
return w.isCurrent
}
if w.node.WidgetType == widget.Root {
return false
}
return w.parent.IsCurrent()
}
func (tk *guiWidget) String() string {
return tk.node.String()
}
func (tk *guiWidget) Visible() bool {
if tk == nil {
return false
}
if tk.v == nil {
return false
}
tk.v.Visible = true
return true
}
func (w *guiWidget) Show() {
// always should the dropdown widget
if w == me.dropdownV {
me.dropdownV.drawView()
return
}
// don't display fake widgets
if w.isFake {
return
}
// if this isn't in the binary tree
// it's some internal widget so always display those
if w.node == nil {
w.drawView()
return
}
// always show window titles
if w.node.WidgetType == widget.Window {
w.drawView()
return
}
/*
if w.node.WidgetType == widget.Dropdown {
log.Log(NOW, "Show() dropdown", w.cuiName, w.String())
log.Log(NOW, "Show() dropdown n.Strings() =", w.node.Strings())
}
if w.node.WidgetType == widget.Combobox {
log.Log(NOW, "Show() dropdown", w.cuiName, w.String())
log.Log(NOW, "Show() dropdown n.Strings() =", w.node.Strings())
}
*/
// if the widget is not in the current displayed windo
// then ignore it
// log.Log(GOCUI, "Show() widget =", w.cuiName, w.String())
// log.Log(GOCUI, "Show() w.IsCurrent() returned", w.IsCurrent())
if !w.IsCurrent() {
// log.Log(GOCUI, "Show() w.IsCurrent == false. NOT drawing", w.cuiName, w.String())
return
}
// finally, check if the widget State is hidden or not
if w.node.Hidden() {
// log.Log(GOCUI, "Show() w.node.Hidden() = false. not drawing", w.cuiName, w.String())
// don't display hidden widgets
return
}
// log.Log(GOCUI, "Show() doing w.drawView()", w.cuiName, w.String())
// okay, if you made it this far, then display the widget
w.drawView()
}
func (tk *guiWidget) Hide() {
tk.deleteView()
}
func (tk *guiWidget) SetVisible(b bool) {
if b {
tk.Show()
} else {
tk.Hide()
}
}
func (tk *guiWidget) findWidgetByName(name string) *guiWidget {
if tk.cuiName == name {
return tk
}
for _, child := range tk.children {
found := child.findWidgetByName(name)
if found != nil {
return found
}
}
return nil
}
func (tk *guiWidget) findWidgetByView(v *gocui.View) *guiWidget {
if tk.v == v {
return tk
}
if tk.cuiName == v.Name() {
log.Log(NOW, "findWidget() error. names are mismatched or out of sync", tk.cuiName)
log.Log(NOW, "findWidget() or maybe the view has been deleted")
// return tk
}
for _, child := range tk.children {
found := child.findWidgetByView(v)
if found != nil {
return found
}
}
return nil
}

61
view.go
View File

@ -4,53 +4,54 @@
package main package main
import ( import (
"bufio"
"strings" "strings"
"go.wit.com/log"
"go.wit.com/widget" "go.wit.com/widget"
) )
// expands the gocuiSize rectangle to fit func splitLines(s string) []string {
// all the text in tk.labelN var lines []string
func (tk *guiWidget) textResize() { sc := bufio.NewScanner(strings.NewReader(s))
var w, h int = 0, 0 for sc.Scan() {
lines = append(lines, sc.Text())
for _, s := range strings.Split(tk.labelN, "\n") {
s = strings.TrimSpace(s)
// log.Log(INFO, "textResize() len =", len(s), i, s)
if w < len(s) {
w = len(s)
} }
h += 1 return lines
} }
// todo: fix all this old code func (w *guiWidget) textResize() bool {
if tk.WidgetType() == widget.Textbox { // w := n.tk
if w < 5 { var width, height int = 0, 0
w = 5 var changed bool = false
}
}
// this is old code. now move this somewhere smarter for i, s := range splitLines(w.labelN) {
tk.gocuiSize.w1 = tk.gocuiSize.w0 + w + me.FramePadW // TODO: move this FramePadW out of here log.Log(INFO, "textResize() len =", len(s), i, s)
tk.gocuiSize.h1 = tk.gocuiSize.h0 + h + me.FramePadH // TODO: fix this size computation if width < len(s) {
width = len(s)
} }
height += 1
// deletes every view
func (w *guiWidget) hideWindow() {
if w == nil {
return
} }
w.Hide() if w.gocuiSize.w1 != w.gocuiSize.w0+width+me.FramePadW {
for _, child := range w.children { w.gocuiSize.w1 = w.gocuiSize.w0 + width + me.FramePadW
child.hideWindow() changed = true
} }
if w.gocuiSize.h1 != w.gocuiSize.h0+height+me.FramePadH {
w.gocuiSize.h1 = w.gocuiSize.h0 + height + me.FramePadH
changed = true
}
if changed {
// w.showWidgetPlacement("textResize() changed")
}
return changed
} }
func (w *guiWidget) hideWidgets() { func (w *guiWidget) hideWidgets() {
if w == nil { if w == nil {
return return
} }
switch w.WidgetType() { w.isCurrent = false
switch w.node.WidgetType {
case widget.Root: case widget.Root:
case widget.Flag: case widget.Flag:
case widget.Window: case widget.Window:
@ -90,7 +91,7 @@ func (w *guiWidget) hideFake() {
func (w *guiWidget) showFake() { func (w *guiWidget) showFake() {
if w.isFake { if w.isFake {
w.drawView() w.drawView()
w.dumpWidget("in showFake()") w.showWidgetPlacement("showFake:")
} }
for _, child := range w.children { for _, child := range w.children {
child.showFake() child.showFake()

44
view.proto.new Normal file
View File

@ -0,0 +1,44 @@
syntax = "proto3";
// including this in a plugin seems to keep it from loading at this point
package main;
import "google/protobuf/timestamp.proto"; // Import the well-known type for Timestamp
// maybe put all the gocui specific stuff here.
message GocuiState { // `autogenpb:nomutex`
bool visible = 1;
bool internal = 2;
int64 w0 = 3;
int64 h0 = 4;
int64 w1 = 5;
int64 h1 = 6;
}
message ViewSettings { // `autogenpb:nomutex`
bool pack = 1;
int64 framesize = 5;
}
message Tree {
View parent = 1;
repeated View children = 2;
View node = 3;
}
// this is the gocui 'view' in binary tree form
message View {
int64 widgetId = 3; // `autogenpb:unique` `autogenpb:sort`
string name = 4; // `autogenpb:unique` `autogenpb:sort`
GocuiState state = 7;
}
message Views { // `autogenpb:marshal` `autogenpb:mutex`
string uuid = 1; // `autogenpb:uuid:d19c1fbb-32c2-4957-aee6-f8128a511dca`
string version = 2; // `autogenpb:version:v0.0.1`
repeated View Views = 3;
Tree tree = 4;
map<string, string> junk = 5;
ViewSettings settings = 6;
}

View File

@ -1,126 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"strconv"
"strings"
"go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget"
)
func initWidget(n *tree.Node) *guiWidget {
var w *guiWidget
w = new(guiWidget)
w.node = n
w.cuiName = strconv.Itoa(w.WidgetId()) + " TK"
// w.WidgetType() = n.WidgetType
w.labelN = n.State.Label
if w.labelN == "" {
// remove this debugging hack once things are stable and fixed
w.labelN = n.GetProgName()
}
w.frame = true
w.enable = n.State.Enable
if n.WidgetType == widget.Root {
log.Log(INFO, "setupWidget() FOUND ROOT w.id =", n.WidgetId)
}
if n.WidgetType == widget.Grid {
w.widths = make(map[int]int) // how tall each row in the grid is
w.heights = make(map[int]int) // how wide each column in the grid is
}
p := n.Parent
if p == nil {
log.Log(ERROR, "parent == nil", w.String(), n.WidgetId, w.WidgetType())
return w
}
if p.TK == nil {
if n.WidgetId == 0 {
// this is a normal init condition
} else {
log.Log(ERROR, "parent.TK == nil", w.String(), n.WidgetId, w.WidgetType())
}
return w
}
// set the parent and append to parent children
var ptk *guiWidget
ptk = p.TK.(*guiWidget)
w.parent = ptk
ptk.children = append(ptk.children, w)
return w
}
func setupCtrlDownWidget() {
a := new(widget.Action)
a.ProgName = "ctrlDown"
a.WidgetType = widget.Dialog
a.WidgetId = -1
a.ParentId = 0
// n := addNode(a)
n := me.myTree.AddNode(a)
me.ctrlDown = n
}
func (w *guiWidget) deleteView() {
// make sure the view isn't really there
// log.Log(GOCUI, "deleteView()", w.cuiName, w.WidgetType(), w.WidgetId())
me.baseGui.DeleteView(w.cuiName)
w.v = nil
}
func (tk *guiWidget) String() string {
// deprecate this?
curval := strings.TrimSpace(tk.labelN)
if curval != "" {
return curval
}
curval = strings.TrimSpace(tk.GetLabel())
if curval != "" {
return curval
}
curval = tk.GetText()
if curval != "" {
return curval
}
curval = tk.node.String()
if curval != "" {
return curval
}
curval = strings.TrimSpace(tk.node.ProgName())
if curval != "" {
return curval
}
return ""
}
func (tk *guiWidget) Visible() bool {
if tk == nil {
return false
}
if tk.v == nil {
return false
}
tk.v.Visible = true
return true
}
func (tk *guiWidget) Hide() {
tk.deleteView()
}
func (tk *guiWidget) SetVisible(b bool) {
if b {
tk.Show()
} else {
tk.Hide()
}
}

View File

@ -1,266 +0,0 @@
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0
package main
import (
"errors"
"fmt"
"strconv"
"github.com/awesome-gocui/gocui"
log "go.wit.com/log"
"go.wit.com/widget"
)
// don't draw widgets that are too far down the window
func (tk *guiWidget) doNotDraw() bool {
var check bool
switch tk.WidgetType() {
case widget.Button:
check = true
case widget.Label:
check = true
default:
}
if !check {
return false
}
win := tk.findParentWindow()
if win == nil {
// don't draw anything if you can't find the parent window
return true
}
h := tk.gocuiSize.h0 - win.gocuiSize.h0
if h > 20 {
return true
}
return false
}
// page widgets in the window
func (tk *guiWidget) pageWidget() *rectType {
r := new(rectType)
var check bool
switch tk.WidgetType() {
case widget.Button:
check = true
case widget.Label:
check = true
default:
}
if !check {
return nil
}
win := tk.findParentWindow()
if win == nil {
// don't draw anything if you can't find the parent window
return nil
}
r.w0 = tk.gocuiSize.w0
r.h0 = tk.gocuiSize.h0
r.w1 = tk.gocuiSize.w1
r.h1 = tk.gocuiSize.h1
// r.h0 = tk.gocuiSize.h0 - win.gocuiSize.h0
if r.h0 > 20 {
return r
}
return r
}
// display's the text of the widget in gocui
// deletes the old view if it exists and recreates it
func (tk *guiWidget) drawView() {
var err error
log.Log(INFO, "drawView() START", tk.WidgetType(), tk.String())
if me.baseGui == nil {
log.Log(ERROR, "drawView() ERROR: me.baseGui == nil", tk)
return
}
if tk.cuiName == "" {
log.Log(ERROR, "drawView() tk.cuiName was not set for widget", tk)
tk.cuiName = strconv.Itoa(tk.WidgetId()) + " TK"
}
log.Log(INFO, "drawView() labelN =", tk.labelN)
// this deletes the button from gocui
me.baseGui.DeleteView(tk.cuiName)
tk.v = nil
a := tk.gocuiSize.w0
b := tk.gocuiSize.h0
c := tk.gocuiSize.w1
d := tk.gocuiSize.h1
/*
// testing code for paging large windows
if tk.doNotDraw() {
return
}
if tk.window.pager != 0 {
if r := tk.pageWidget(); r == nil {
// if nil, draw whatever it is anyway
} else {
if r.Width() == 0 && r.Height() == 0 {
// don't draw empty stuff
return
}
a = r.w0
b = r.h0
c = r.w1
d = r.h1
}
}
if tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Flag {
if tk.window.pager != 0 {
if tk.gocuiSize.Height() > 40 {
tk.window.large = true
tk.gocuiSize.h1 = tk.gocuiSize.h0 + 40
d = tk.gocuiSize.h1
}
}
}
*/
// this is all terrible. This sets the title. kinda
if tk.WidgetType() == widget.Window {
tk.textResize()
tk.full.w0 = tk.force.w0
tk.full.h0 = tk.force.h0
// for windows, make it the full size
a = tk.full.w0
b = tk.full.h0
c = tk.full.w0 + tk.gocuiSize.Width()
d = tk.full.h0 + tk.gocuiSize.Height()
} else {
if tk.internal {
// do nothing
} else {
tk.textResize() // resize gocui frame to the widget text
}
a = tk.gocuiSize.w0
b = tk.gocuiSize.h0
c = tk.gocuiSize.w1
d = tk.gocuiSize.h1
}
tk.v, err = me.baseGui.SetView(tk.cuiName, a, b, c, d, 0)
if err == nil {
tk.dumpWidget("drawView() err")
log.Log(ERROR, "drawView() internal plugin error err = nil")
return
}
if !errors.Is(err, gocui.ErrUnknownView) {
tk.dumpWidget("drawView() err")
log.Log(ERROR, "drawView() internal plugin error error.IS()", err)
return
}
if tk.v == nil {
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL. tk.v == nil here in drawView()")
return
}
// this actually sends the text to display to gocui
tk.v.Wrap = true
tk.v.Frame = tk.frame
tk.v.Clear()
fmt.Fprint(tk.v, tk.labelN)
// tmp hack to disable buttons on window open
if tk.WidgetType() == widget.Button {
if tk.IsEnabled() {
} else {
tk.setColorDisable()
}
}
switch tk.WidgetType() {
case widget.Button:
if tk.IsEnabled() {
if tk.isDense() && tk.isInGrid() {
tk.setColorButtonDense()
} else {
tk.setColorButton()
}
} else {
tk.setColorDisable()
}
default:
}
if tk.v == nil {
log.Info("MUTEX FAIL 2. tk.v was deleted somehow tk.v == nil here in drawView()")
log.Info("MUTEX FAIL 2. tk.v == nil here in drawView()")
log.Info("MUTEX FAIL 2. tk.v == nil here in drawView()")
return
}
// if you don't do this here, it will be black & white only
if tk.color != nil {
tk.v.FrameColor = tk.color.frame
tk.v.FgColor = tk.color.fg
tk.v.BgColor = tk.color.bg
tk.v.SelFgColor = tk.color.selFg
tk.v.SelBgColor = tk.color.selBg
}
log.Log(INFO, "drawView() END")
}
// redraw the widget tree starting at this location
func (w *guiWidget) DrawAt(offsetW, offsetH int) {
w.placeWidgets(offsetW, offsetH) // compute the sizes & places for each widget
// w.dumpWidget(fmt.Sprintf("DrawAt(%d,%d)", offsetW, offsetH))
}
// display the widgets in the binary tree
func (w *guiWidget) drawTree(draw bool) {
if w == nil {
return
}
w.dumpWidget("in drawTree()")
if draw {
// w.textResize()
w.Show()
} else {
w.Hide()
}
for _, child := range w.children {
child.drawTree(draw)
}
}
func (w *guiWidget) Show() {
if w.isFake {
// don't display fake widgets
return
}
if w.Hidden() {
// recursively checks if the parent is hidden
// never show hidden widgets
return
}
if me.debug {
w.dumpWidget("drawView()")
}
w.drawView()
}

241
window.go
View File

@ -4,237 +4,28 @@
package main package main
import ( import (
"fmt"
"strings"
log "go.wit.com/log"
"go.wit.com/toolkits/tree"
"go.wit.com/widget" "go.wit.com/widget"
) )
func (tk *guiWidget) setTitle(s string) {
if tk.WidgetType() != widget.Window {
return
}
if tk.v == nil {
return
}
tk.setColorWindowTitleActive()
rect := tk.gocuiSize
rect.w1 = rect.w0 + tk.full.Width() + 1
// rect.h1 = rect.h0 + 1
me.baseGui.SetView(tk.v.Name(), rect.w0-1, rect.h0, rect.w1+1, rect.h1, 0)
tk.v.Clear()
f := " %-" + fmt.Sprintf("%d", tk.full.Width()-3) + "s %s"
tmp := tk.GetLabel()
labelN := fmt.Sprintf(f, tmp, "X")
tk.v.WriteString(labelN)
}
func (tk *guiWidget) redrawWindow(w int, h int) {
if tk.WidgetType() != widget.Window {
return
}
// tk.dumpWidget(fmt.Sprintf("redrawWindow(%d,%d)", w, h))
if tk.full.Height() > 40 {
tk.window.dense = true
}
// pin the window to (w,h)
tk.gocuiSize.w0 = w
tk.gocuiSize.h0 = h
tk.gocuiSize.w1 = w + len(tk.GetLabel())
tk.labelN = tk.GetLabel() // could set XX here also but don't have final size of window yet
tk.force.w0 = w
tk.force.w1 = w
tk.force.h0 = h
tk.force.h1 = h
tk.setFullSize() // might make the green box the right size
tk.frame = false
tk.hasTabs = false
tk.DrawAt(w, h)
// tk.setColor(&colorActiveW) // sets the window to Green BG
tk.setColorWindowTitleActive()
if tk.window.collapsed {
// don't show anything but the title bar
tk.hideWindow()
return
}
tk.placeWidgets(w, h) // compute the sizes & places for each widget
// this is a test. this should not be needed
tk.full.w0 = tk.force.w0
tk.full.h0 = tk.force.h0
tk.setFullSize()
tk.Show()
if tk.v == nil {
log.Info("redrawWindow on tk.v == nil")
standardExit()
}
tk.v.Clear()
fmt.Fprint(tk.v, "ZZZ"+tk.GetText())
tk.showWidgets()
if tk.windowFrame == nil {
tk.addWindowFrameTK(0 - tk.WidgetId())
tk.windowFrame.makeTK([]string{""})
}
// this seems to correctly create the window frame
r := tk.getFullSize()
tk.windowFrame.gocuiSize.w0 = tk.force.w0
tk.windowFrame.gocuiSize.w1 = r.w1 + 1
tk.windowFrame.gocuiSize.h0 = tk.force.h0 + 2
tk.windowFrame.gocuiSize.h1 = r.h1 + 1
tk.windowFrame.full.w0 = tk.force.w0
tk.windowFrame.full.w1 = r.w1 + 1
tk.windowFrame.full.h0 = tk.force.h0 + 2
tk.windowFrame.full.h1 = r.h1 + 1
tk.windowFrame.setColorWindowFrame()
tk.windowFrame.Hide()
tk.windowFrame.Show()
// set the window frame below the window widget, but this resizes the window widget it seems
me.baseGui.SetViewBeneath(tk.windowFrame.cuiName, tk.cuiName, 1)
// so now we have to resize the window frame, but this moves it to the top?
me.baseGui.SetView(tk.windowFrame.cuiName, tk.windowFrame.full.w0, tk.windowFrame.full.h0, tk.windowFrame.full.w1, tk.windowFrame.full.h1, 0)
// so we have to redraw the widgets in the window anyway and then they will appear above he frame
tk.hideWidgets()
tk.showWidgets()
// draw the window title
tk.setTitle(tk.GetLabel())
}
// re-draws the buttons for each of the windows // re-draws the buttons for each of the windows
func redoWindows(nextW int, nextH int) { func (w *guiWidget) redoWindows(nextW int, nextH int) {
for _, tk := range findWindows() { var startW int = nextW
// tk.dumpWidget(fmt.Sprintf("redoWindowsS (%d,%d)", nextW, nextH)) var startH int = nextH
if tk.window.wasDragged {
// don't move windows around the user has dragged to a certain location
tk.makeWindowActive()
} else {
w, _ := me.baseGui.Size()
if nextW > w-20 {
nextW = 0
nextH += 4
}
// probably a new window?
tk.redrawWindow(nextW, nextH)
}
// tk.dumpWidget(fmt.Sprintf("redoWindowsE (%d,%d)", nextW, nextH))
// increment the width for the next window for _, child := range w.children {
nextW += tk.gocuiSize.Width() + 10 if child.node.WidgetType != widget.Window {
// nextH += 10 continue
}
} }
func (tk *guiWidget) addWindowFrameTK(wId int) { child.frame = false
n := tk.addWindowFrame(wId) child.hasTabs = false
tk.windowFrame = n.TK.(*guiWidget)
child.gocuiSetWH(nextW, nextH)
child.Hide()
child.drawView()
sizeW := child.gocuiSize.Width()
nextW += sizeW + 4
child.redoWindows(startW+3, startH+2)
} }
func (win *guiWidget) addWindowFrame(wId int) *tree.Node {
n := new(tree.Node)
n.WidgetType = widget.Flag
n.WidgetId = wId
n.ParentId = 0
// store the internal toolkit information
tk := new(guiWidget)
tk.frame = true
tk.labelN = "windowFrame text"
tk.internal = true
tk.node = n
if tk.node.Parent == nil {
tk.node.Parent = me.treeRoot
}
// set the name used by gocui to the id
tk.cuiName = fmt.Sprintf("%d DR", wId)
// tk.color = &colorGroup
// add this new widget on the binary tree
tk.parent = win
if tk.parent == nil {
panic("addDropdown() didn't get treeRoot guiWidget")
} else {
tk.parent.children = append(tk.parent.children, tk)
}
n.TK = tk
return n
}
func (tk *guiWidget) isWindowActive() bool {
if !(tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Stdout) {
// only allow Window or the Stdout widgets to be made active
return false
}
return tk.window.active
}
// always redraws at the corner of the gocuiSize box
func (tk *guiWidget) makeWindowActive() {
if !(tk.WidgetType() == widget.Window || tk.WidgetType() == widget.Stdout) {
// only allow Window or the Stdout widgets to be made active
return
}
if tk.WidgetType() == widget.Stdout {
me.stdout.outputOnTop = true
} else {
// me.stdout.outputOnTop = false // ?
}
// disable and increment all the windows
for _, tk := range me.allwin {
tk.window.order += 1
tk.window.active = false
// tk.setColor(&colorWindow) // color for inactive windows
tk.setColorWindowTitle()
}
// set this window as the active one
tk.window.active = true
tk.window.order = 0
tk.redrawWindow(tk.gocuiSize.w0, tk.gocuiSize.h0)
setThingsOnTop() // sets help, Stdout, etc on the top after windows have been redrawn
}
func (tk *guiWidget) makeTK(ddItems []string) {
items := strings.Join(ddItems, "\n")
tk.labelN = items
tk.SetText(items)
tk.gocuiSize.w0 = 100
tk.gocuiSize.w1 = 120
tk.gocuiSize.h0 = 15
tk.gocuiSize.h1 = 18
tk.Show()
}
func (win *guiWidget) checkWindowClose(w int, h int) bool {
s := fmt.Sprintf("mouse(%d,%d) ", w, h)
offW := win.full.w1 - w
offH := h - win.full.h0
s += fmt.Sprintf("offset(%d,%d)", offW, offH)
if (offW < 2) && (offH < 2) {
log.Info("attempting close on ", s, win.cuiName)
me.myTree.SendWindowCloseEvent(win.node)
// store the stdout corner for computing the drag size
return true
}
// log.Info("not attempting close on ", s, win.cuiName)
return false
} }