tabs, windows + gocui dropdown menu (almost)

dropdown menu figures out what text was clicked
    dropdown menu movement changes line colors
    dropdown menus force user to select a response
    accidentally committed a binary
    tab selection works
    tab and window views almost working
    tabs and windows almost working
    window widgets selection works
    better color handling
    using gocui view.Visable flag
    removal of old color setting code
    still need an artificial delay for andlabs SetText()
    catching more 'nil' errors
    fixed the stupid duplicate tab problem in andlabs
    figured out how andlabs had a tab/box mess
    works on more than one domain
    builds and runs again
    debugging double tabs in andlabs gui
    GO111MODULE compile notes
    code reorg
    further improvements
    example cloudflare app does first successful dns update
    add NewEntryLine() for single line entry boxes

Signed-off-by: Jeff Carr <jcarr@wit.com>
This commit is contained in:
Jeff Carr 2023-12-14 10:36:56 -06:00
parent eab47f738d
commit de771dbe98
39 changed files with 1156 additions and 410 deletions

2
.gitignore vendored
View File

@ -3,7 +3,7 @@
# ignore compiled plugins
*.so
examples/buttonplugin/buttonplugin
examples/buttons/buttons
examples/console-ui-helloworld/console-ui-helloworld
examples/debug/debug
examples/helloworld/helloworld

View File

@ -8,7 +8,19 @@ all: README.md
@echo
@echo This Requires working IPv6
@echo
@sleep 1
ifeq ($(GO111MODULE),)
@echo
@echo If you are compiling this here, you probably want to set GO111MODULE
@echo
@echo Setting GO111MODULE means that the version you are compiling has plugins
@echo that get compiled against this current running version of the code
@echo Otherwise, the GO language plugins can complain about being compiled against
@echo mis-matched versions
@echo
@echo export GO111MODULE=off
@echo
sleep 3
endif
ifeq (,$(wildcard go.mod))
go mod init gui
go mod tidy
@ -35,8 +47,11 @@ examples: \
examples-helloworld \
examples-buttons \
examples-console-ui-helloworld \
examples-textbox \
examples-debug
examples-cloudflare
# this is the most basic one. This syntax should always work
examples-helloworld:
make -C examples/helloworld
examples-buttons:
make -C examples/buttons
@ -44,18 +59,8 @@ examples-buttons:
examples-console-ui-helloworld:
make -C examples/console-ui-helloworld
# this is the most basic one. This syntax should always work
examples-helloworld:
make -C examples/helloworld
examples-debug:
-make -C examples/debug
examples-textbox:
make -C examples/textbox
examples-helloconsole:
make -C examples/plugin-consoleonly
examples-cloudflare:
-make -C examples/cloudflare
# sync repo to the github backup
# git remote add github git@github.com:witorg/gui.git
@ -102,3 +107,6 @@ objdump:
log:
reset
tail -f /tmp/witgui.* /tmp/guilogfile
submit-to-docs:
GOPROXY=https://proxy.golang.org GO111MODULE=on go get go.wit.com/gui@v1.0.0

View File

@ -11,22 +11,6 @@ func (parent *Node) NewButton(name string, custom func()) *Node {
return newNode
}
/*
// deprecate this once andlabs is refactored
func callback(i int) bool {
log(debugError, "callback() for widget id =", i)
n := me.rootNode.FindId(i)
log(debugError, "callback() found node =", n)
// running custom here means the button get's clicked twice
if (n.Custom == nil) {
log(debugError, "callback() = nil. SKIPPING")
return false
}
n.Custom()
return true
}
*/
// find widget by number
func (n *Node) FindId(i int) (*Node) {
if (n == nil) {
@ -45,4 +29,3 @@ func (n *Node) FindId(i int) (*Node) {
}
return nil
}

View File

@ -14,6 +14,8 @@ import (
"github.com/sourcegraph/conc/panics"
)
// this should never exit
// TODO: clean up all this poorly named code
func makeConc() {
var wg conc.WaitGroup
defer wg.Wait()

View File

@ -140,9 +140,8 @@ func (n *Node) dumpWidget(b bool) string {
for i := 0; i < listChildrenDepth; i++ {
tabs = tabs + defaultPadding
}
d = tabs + d
logindent(b, listChildrenDepth, defaultPadding, n.id, info)
return d
logindent(b, listChildrenDepth, defaultPadding, d)
return tabs + d
}
// func (n *Node) ListChildren(dump bool, dropdown *Node, mapNodes map[string]*Node) {

View File

@ -16,3 +16,6 @@ update:
log:
reset
tail -f /tmp/witgui.* /tmp/guilogfile
gocui: build
./cloudflare -gui gocui >/tmp/witgui.log.stderr 2>&1

187
examples/cloudflare/api.go Normal file
View File

@ -0,0 +1,187 @@
// This is a simple example
package main
import (
"os"
"log"
"encoding/json"
"io/ioutil"
"net/http"
"strconv"
"bytes"
"github.com/davecgh/go-spew/spew"
)
func doChange(dnsRow *RRT) {
log.Println("Look for changes in row", dnsRow.ID)
log.Println("Proxy", dnsRow.Proxied, "vs", dnsRow.proxyNode.S)
log.Println("Content", dnsRow.Content, "vs", dnsRow.valueNode.S)
if (dnsRow.Content != dnsRow.valueNode.S) {
log.Println("UPDATE VALUE", dnsRow.nameNode.Name, dnsRow.typeNode.Name, "to", dnsRow.valueNode.S)
httpPut(dnsRow)
}
dnsRow.saveNode.Disable()
}
func getZonefile(c *configT) *DNSRecords {
var url = cloudflareURL + c.zoneID + "/dns_records/"
log.Println("getZonefile()", c.domain, url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println("http.NewRequest error:", err)
return nil
}
// Set headers
req.Header.Set("X-Auth-Key", c.auth)
req.Header.Set("X-Auth-Email", c.email)
log.Println("getZonefile() auth, email", c.auth, c.email)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Println("http.Client error:", err)
return nil
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("ioutil.ReadAll() error", err)
return nil
}
var records DNSRecords
if err := json.Unmarshal(body, &records); err != nil {
log.Println("json.Unmarshal() error", err)
return nil
}
log.Println("getZonefile() worked", records)
return &records
}
/*
pass in a DNS Resource Records (the stuff in a zonefile)
This will talk to the cloudflare API and generate a resource record in the zonefile:
For example:
gitea.wit.com. 3600 IN CNAME git.wit.org.
go.wit.com. 3600 IN A 1.1.1.9
test.wit.com. 3600 IN NS ns1.wit.com.
*/
func httpPut(dnsRow *RRT) {
var url string = cloudflareURL + os.Getenv("CLOUDFLARE_ZONEID") + "/dns_records/" + dnsRow.ID
var authKey string = os.Getenv("CLOUDFLARE_AUTHKEY")
var email string = os.Getenv("CLOUDFLARE_EMAIL")
// make a json record to send on port 90 to cloudflare
var tmp string
tmp = `{"content": "` + dnsRow.valueNode.S + `", `
tmp += `"name": "` + dnsRow.Name + `", `
tmp += `"type": "` + dnsRow.Type + `", `
tmp+= `"ttl": "` + strconv.Itoa(dnsRow.TTL) + `", `
tmp += `"comment": "WIT DNS Control Panel"`
tmp += `}`
data := []byte(tmp)
log.Println("http PUT url =", url)
log.Println("http PUT data =", data)
spew.Dump(data)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(data))
// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Auth-Key", authKey)
req.Header.Set("X-Auth-Email", email)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Println(err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return
}
log.Println("http PUT body =", body)
spew.Dump(body)
return
}
// https://api.cloudflare.com/client/v4/zones
func getZones(auth, email string) *DNSRecords {
var url = "https://api.cloudflare.com/client/v4/zones"
log.Println("getZones()", url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Println("http.NewRequest error:", err)
return nil
}
// Set headers
req.Header.Set("X-Auth-Key", auth)
req.Header.Set("X-Auth-Email", email)
log.Println("getZones() auth, email", auth, email)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Println("getZones() http.Client error:", err)
return nil
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("getZones() ioutil.ReadAll() error", err)
return nil
}
var records DNSRecords
if err := json.Unmarshal(body, &records); err != nil {
log.Println("getZones() json.Unmarshal() error", err)
return nil
}
/* Cloudflare API returns struct[] of:
struct { ID string "json:\"id\""; Type string "json:\"type\""; Name string "json:\"name\"";
Content string "json:\"content\""; Proxied bool "json:\"proxied\"";
Proxiable bool "json:\"proxiable\""; TTL int "json:\"ttl\"" }
*/
// log.Println("getZones() worked", records)
// log.Println("spew dump:")
spew.Dump(records)
for _, record := range records.Result {
log.Println("spew record:", record)
log.Println("record:", record.Name, record.ID)
var newc *configT
newc = new(configT)
newc.domain = record.Name
newc.zoneID = record.ID
newc.auth = auth
newc.email = email
config[record.Name] = newc
zonedrop.AddText(record.Name)
log.Println("zonedrop.AddText:", record.Name, record.ID)
}
for d, _ := range config {
log.Println("config entry:", d)
}
return &records
}

View File

@ -0,0 +1,3 @@
curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer AAAPutYourTokenInHereSoYouCanTestItL5Cl3" \
-H "Content-Type:application/json"

View File

@ -1,132 +0,0 @@
// This is a simple example
package main
import (
"os"
"log"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
)
// Define a struct to match the JSON structure of the response.
// This structure should be adjusted based on the actual format of the response.
type DNSRecords struct {
Result []struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
Proxied bool `json:"proxied"`
Proxiable bool `json:"proxiable"`
TTL int `json:"ttl"`
} `json:"result"`
}
// var domain string = "wit.org"
// var os.Getenv("CLOUDFLARE_DOMAIN")
func loadDNS(hostname string) {
log.Println("adding DNS record")
newt := mainWindow.NewTab(hostname)
newg := newt.NewGroup("more")
grid := newg.NewGrid("gridnuts", 5, gridH)
// grid.NewButton("Type", func () {
// log.Println("sort by Type")
// })
typedrop := grid.NewDropdown("type")
typedrop.AddText("A")
typedrop.AddText("AAAA")
typedrop.AddText("CNAME")
typedrop.Custom = func () {
log.Println("custom dropdown() a =", typedrop.Name, typedrop.S)
}
grid.NewButton("Name", func () {
log.Println("sort by Name")
})
grid.NewButton("Protection", func () {
log.Println("sort proxied")
})
grid.NewButton("TTL", func () {
log.Println("sort by TTL")
})
grid.NewButton("Value", func () {
log.Println("sort by Value")
})
newt.NewButton("Save", func () {
log.Println("save stuff to cloudflare")
})
records := getRecords()
for _, record := range records.Result {
grid.NewLabel(record.Type)
textbox := grid.NewTextbox(record.Name)
textbox.SetText(record.Name)
if (record.Proxied) {
grid.NewLabel("Proxied")
} else {
grid.NewLabel("DNS")
}
var ttl, short string
if (record.TTL == 1) {
ttl = "Auto"
} else {
ttl = strconv.Itoa(record.TTL)
}
grid.NewLabel(ttl)
// short = fmt.Sprintf("%80s", record.Content)
short = record.Content
if len(short) > 40 {
short = short[:40] // Slice the first 20 characters
}
namebox := grid.NewTextbox(short)
namebox.SetText(short)
fmt.Printf("ID: %s, Type: %s, Name: %s, short Content: %s\n", record.ID, record.Type, record.Name, short)
fmt.Printf("\tproxied: %b, %b, string TTL: %i\n", record.Proxied, record.Proxiable, ttl)
}
}
func getRecords() *DNSRecords {
var url string = os.Getenv("CLOUDFLARE_URL")
req, err := http.NewRequest("GET", url, nil)
if err != nil {
fmt.Println(err)
return nil
}
var authKey string = os.Getenv("CLOUDFLARE_AUTHKEY")
var email string = os.Getenv("CLOUDFLARE_EMAIL")
// Set headers
req.Header.Set("X-Auth-Key", authKey)
req.Header.Set("X-Auth-Email", email)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return nil
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return nil
}
var records DNSRecords
if err := json.Unmarshal(body, &records); err != nil {
fmt.Println(err)
return nil
}
return &records
}

122
examples/cloudflare/gui.go Normal file
View File

@ -0,0 +1,122 @@
// This is a simple example
package main
import (
"log"
"strconv"
)
func loadDNS(c *configT) {
hostname := c.domain
log.Println("adding DNS record", hostname)
newt := mainWindow.NewTab(hostname)
newg := newt.NewGroup("more")
// make a grid 6 things wide
grid := newg.NewGrid("gridnuts", 6, gridH)
// grid.NewButton("Type", func () {
// log.Println("sort by Type")
// })
typedrop := grid.NewDropdown("type")
typedrop.AddText("A")
typedrop.AddText("AAAA")
typedrop.AddText("CNAME")
typedrop.Custom = func () {
log.Println("custom dropdown() a =", typedrop.Name, typedrop.S)
}
nb := grid.NewButton("Name", func () {
log.Println("sort by Name")
})
nb.Disable()
grid.NewButton("Protection", func () {
log.Println("sort proxied")
})
grid.NewButton("TTL", func () {
log.Println("sort by TTL")
})
nb = grid.NewButton("Value", func () {
log.Println("sort by Value")
})
nb.Disable()
nb = grid.NewButton("Save", func () {
log.Println("click below to save")
})
nb.Disable()
masterSave = newt.NewButton("Master Save", func () {
log.Println("save stuff to cloudflare")
})
masterSave.Disable()
records := getZonefile(c)
for _, record := range records.Result {
var rr RRT // dns zonefile resource record
// copy all the JSON values into the row record.
rr.ID = record.ID
rr.Type = record.Type
rr.Name = record.Name
rr.Content = record.Content
rr.Proxied = record.Proxied
rr.Proxiable = record.Proxiable
rr.TTL = record.TTL
rr.typeNode = grid.NewLabel(record.Type)
rr.nameNode = grid.NewEntryLine(record.Name)
rr.nameNode.SetText(record.Name)
rr.nameNode.Disable()
// set proxy or unproxied
rr.proxyNode = grid.NewDropdown("proxy")
if (record.Proxied) {
rr.proxyNode.AddText("Proxied")
rr.proxyNode.AddText("DNS")
} else {
rr.proxyNode.AddText("DNS")
rr.proxyNode.AddText("Proxied")
}
rr.proxyNode.Custom = func () {
log.Println("proxy dropdown() a =", rr.proxyNode.Name, rr.proxyNode.S, rr.ID)
rr.saveNode.Enable()
masterSave.Enable()
}
var ttl, short string
if (record.TTL == 1) {
ttl = "Auto"
} else {
ttl = strconv.Itoa(record.TTL)
}
rr.ttlNode = grid.NewLabel(ttl)
// short = fmt.Sprintf("%80s", record.Content)
short = record.Content
if len(short) > 40 {
short = short[:40] // Slice the first 20 characters
}
rr.valueNode = grid.NewEntryLine(short)
rr.valueNode.SetText(record.Content)
rr.valueNode.Custom = func () {
log.Println("value changed =", rr.valueNode.Name, rr.proxyNode.S, rr.ID)
rr.saveNode.Enable()
masterSave.Enable()
}
// fmt.Printf("ID: %s, Type: %s, Name: %s, short Content: %s\n", record.ID, record.Type, record.Name, short)
// fmt.Printf("\tproxied: %b, %b, string TTL: %i\n", record.Proxied, record.Proxiable, ttl)
rr.saveNode = grid.NewButton("Save", nil)
rr.saveNode.Disable()
rr.saveNode.Custom = func () {
name := "save stuff to cloudflare for " + rr.ID
log.Println(name)
doChange(&rr)
}
}
grid.Pad()
}

View File

@ -5,11 +5,14 @@ import (
"os"
"fmt"
"log"
"bufio"
"strings"
"git.wit.org/wit/gui"
)
var title string = "Cloudflare DNS Control Panel"
var outfile string = "/tmp/guilogfile"
var configfile string = ".config/wit/cloudflare"
var myGui *gui.Node
var buttonCounter int = 5
@ -19,8 +22,10 @@ var gridH int = 3
var mainWindow, more, more2 *gui.Node
func main() {
config = make(map[string]*configT)
readConfig()
myGui = gui.New().Default()
buttonWindow()
makeCloudflareWindow()
// This is just a optional goroutine to watch that things are alive
gui.Watchdog()
@ -28,30 +33,90 @@ func main() {
}
// This creates a window
func buttonWindow() {
var t, g *gui.Node
func makeCloudflareWindow() {
var t *gui.Node
log.Println("buttonWindow() START")
mainWindow = myGui.NewWindow(title).SetText(title)
t = mainWindow.NewTab("Cloudflare")
g = t.NewGroup("buttons")
g1 := t.NewGroup("buttonGroup 2")
more = g1.NewGroup("more")
showCloudflareCredentials(more)
// this tab has the master cloudflare API credentials
makeConfigTab(mainWindow)
// more2 = g1.NewGrid("gridnuts", gridW, gridH)
t = mainWindow.NewTab("Zones")
g1 := t.NewGroup("zones")
var domain string = os.Getenv("CLOUDFLARE_DOMAIN")
if (domain == "") {
domain = "example.org"
// make dropdown list of zones
zonedrop = g1.NewDropdown("zone")
zonedrop.AddText("example.org")
for d, _ := range config {
zonedrop.AddText(d)
}
g.NewButton("Load " + domain + " DNS", func () {
loadDNS(domain)
zonedrop.Custom = func () {
domain := zonedrop.S
log.Println("custom dropdown() zone (domain name) =", zonedrop.Name, domain)
if (config[domain] == nil) {
log.Println("custom dropdown() config[domain] = nil for domain =", domain)
domainWidget.SetText(domain)
zoneWidget.SetText("")
authWidget.SetText("")
emailWidget.SetText("")
} else {
log.Println("custom dropdown() a =", domain, config[domain].zoneID, config[domain].auth, config[domain].email)
domainWidget.SetText(config[domain].domain)
zoneWidget.SetText(config[domain].zoneID)
authWidget.SetText(config[domain].auth)
emailWidget.SetText(config[domain].email)
}
}
more = g1.NewGroup("data")
showCloudflareCredentials(more)
makeDebugTab(mainWindow)
}
func makeConfigTab(window *gui.Node) {
t := window.NewTab("Get Zones")
vb := t.NewBox("vBox", false)
g1 := vb.NewGroup("Cloudflare API Config")
g1.NewLabel("If you have an API key with access to list all of /n your zone files, enter it here. \n \n Alternatively, you can set the enviroment variables: \n env $CLOUDFLARE_AUTHKEY \n env $CLOUDFLARE_EMAIL \n env $CLOUDFLARE_URL \n")
// make grid to display credentials
grid := g1.NewGrid("credsGrid", 2, 4) // width = 2
grid.NewLabel("Auth Key")
aw := grid.NewEntryLine(os.Getenv("CLOUDFLARE_AUTHKEY"))
aw.SetText(os.Getenv("CLOUDFLARE_AUTHKEY"))
grid.NewLabel("Email")
ew := grid.NewEntryLine(os.Getenv("CLOUDFLARE_EMAIL"))
ew.SetText(os.Getenv("CLOUDFLARE_EMAIL"))
var url string = "https://api.cloudflare.com/client/v4/zones/"
grid.NewLabel("Cloudflare API")
grid.NewLabel(url)
grid.Pad()
vb.NewButton("getZones()", func () {
log.Println("getZones()")
getZones(aw.S, ew.S)
})
t.Pad()
t.Margin()
vb.Pad()
vb.Margin()
g1.Pad()
g1.Margin()
}
func makeDebugTab(window *gui.Node) {
t2 := window.NewTab("debug")
g := t2.NewGroup("debug")
g.NewButton("Load 'gocui'", func () {
// this set the xterm and mate-terminal window title. maybe works generally?
fmt.Println("\033]0;" + title + "blah \007")
@ -65,20 +130,103 @@ func buttonWindow() {
g.NewButton("gui.DebugWindow()", func () {
gui.DebugWindow()
})
g.NewButton("List all Widgets", func () {
myGui.ListChildren(true)
})
g.NewButton("Dump all Widgets", func () {
myGui.Dump()
})
}
func showCloudflareCredentials(box *gui.Node) {
// make grid to display credentials
grid := box.NewGrid("credsGrid", 2, 4) // width = 2
grid.NewLabel("Domain")
grid.NewLabel(os.Getenv("CLOUDFLARE_DOMAIN"))
domainWidget = grid.NewEntryLine(os.Getenv("CLOUDFLARE_DOMAIN"))
grid.NewLabel("Zone ID")
zoneWidget = grid.NewEntryLine(os.Getenv("CLOUDFLARE_ZONEID"))
grid.NewLabel("Auth Key")
grid.NewLabel(os.Getenv("CLOUDFLARE_AUTHKEY"))
authWidget = grid.NewEntryLine(os.Getenv("CLOUDFLARE_AUTHKEY"))
grid.NewLabel("Email")
grid.NewLabel(os.Getenv("CLOUDFLARE_EMAIL"))
emailWidget = grid.NewEntryLine(os.Getenv("CLOUDFLARE_EMAIL"))
grid.NewLabel("URL")
grid.NewLabel(os.Getenv("CLOUDFLARE_URL"))
var url string = "https://api.cloudflare.com/client/v4/zones/"
grid.NewLabel("Cloudflare API")
grid.NewLabel(url)
grid.Pad()
saveButton = box.NewButton("Save to config", func () {
})
saveButton.Disable()
loadButton = box.NewButton("Load Cloudflare DNS zonefile", func () {
var domain configT
domain.domain = domainWidget.S
domain.zoneID = zoneWidget.S
domain.auth = authWidget.S
domain.email = emailWidget.S
loadDNS(&domain)
})
}
func readConfig() {
homeDir, err := os.UserHomeDir()
if err != nil {
log.Println("searchPaths() error. exiting here?")
}
filename := homeDir + "/" + configfile
log.Println("filename =", filename)
readFileLineByLine(filename)
// os.Exit(0)
}
// readFileLineByLine opens a file and reads through each line.
func readFileLineByLine(filename string) error {
// Open the file.
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
log.Println("readFileLineByLine() =", filename)
// Create a new Scanner for the file.
scanner := bufio.NewScanner(file)
// Read through each line using scanner.
for scanner.Scan() {
var newc *configT
newc = new(configT)
line := scanner.Text()
parts := strings.Fields(line)
if (len(parts) < 4) {
log.Println("readFileLineByLine() SKIP =", parts)
continue
}
newc.domain = parts[0]
newc.zoneID = parts[1]
newc.auth = parts[2]
newc.email = parts[3]
config[parts[0]] = newc
log.Println("readFileLineByLine() =", newc.domain, newc.zoneID, newc.auth, newc.email)
}
// Check for errors during Scan.
if err := scanner.Err(); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,77 @@
// This is a simple example
package main
import (
"git.wit.org/wit/gui"
)
var cloudflareURL string = "https://api.cloudflare.com/client/v4/zones/"
// Define a struct to match the JSON structure of the response.
// This structure should be adjusted based on the actual format of the response.
type DNSRecords struct {
Result []struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Content string `json:"content"`
Proxied bool `json:"proxied"`
Proxiable bool `json:"proxiable"`
TTL int `json:"ttl"`
} `json:"result"`
}
var masterSave *gui.Node
var domainWidget *gui.Node
var zoneWidget *gui.Node
var authWidget *gui.Node
var emailWidget *gui.Node
var loadButton *gui.Node
var saveButton *gui.Node
var zonedrop *gui.Node
// Resource Record (used in a DNS zonefile)
type RRT struct {
typeNode *gui.Node
nameNode *gui.Node
proxyNode *gui.Node
ttlNode *gui.Node
valueNode *gui.Node
saveNode *gui.Node
ID string
Type string
Name string
Content string
Proxied bool
Proxiable bool
TTL int
}
/*
This is a structure of all the RR's (Resource Records)
in the DNS zonefiile for a hostname. For example:
For the host test.wit.com:
test.wit.com A 127.0.0.1
test.wit.com AAAA
test.wit.com TXT email test@wit.com
test.wit.com TXT phone 212-555-1212
test.wit.com CNAME real.wit.com
*/
type hostT struct {
hostname string
RRs []configT
}
type configT struct {
domain string
zoneID string
auth string
email string
}
var config map[string]*configT

View File

@ -61,7 +61,7 @@ func watchCallback() {
}
// this maybe a good idea?
// TODO: Throttle user events somehow
sleep(.01)
// sleep(.01) // hack that throttles user events
}
}
}

View File

@ -132,7 +132,6 @@ func searchPaths(name string) *aplug {
log(logError, filename, "was not embedded in the binary. Error:", err)
}
log(logError, "fuck off")
// attempt to write out the file from the internal resource
filename = "toolkit/" + name + ".so"
p := initToolkit(name, filename)
@ -254,7 +253,7 @@ func sendAction(a *toolkit.Action) {
log(logInfo, "Action() SEND to pluginChan", aplug.name)
aplug.pluginChan <- *a
// added during debugging. might be a good idea in general for a tactile experience
sleep(.02)
sleep(.02) // this delay makes it so SetText() works on initial widget creation
}
}
@ -303,7 +302,7 @@ func (n *Node) LoadToolkit(name string) *Node {
var a toolkit.Action
a.ActionType = toolkit.InitToolkit
plug.pluginChan <- a
sleep(.5) // temp hack until chan communication is setup
// sleep(.5) // temp hack until chan communication is setup
// TODO: find a new way to do this that is locking, safe and accurate
me.rootNode.redraw(plug)
@ -320,7 +319,7 @@ func (n *Node) CloseToolkit(name string) bool {
var a toolkit.Action
a.ActionType = toolkit.CloseToolkit
plug.pluginChan <- a
sleep(.5)
// sleep(.5) // is this needed? TODO: properly close channel
return true
}
}

3
tab.go
View File

@ -10,7 +10,6 @@ import (
func (n *Node) NewTab(text string) *Node {
// check to make sure n is actually a window
if (n.WidgetType != toolkit.Window) {
// figure out what the actual window is
log(logError, "NewTab() is being requested on something that isn't a Window. node =", n)
@ -35,7 +34,7 @@ func (n *Node) NewTab(text string) *Node {
// by default, create a box inside the tab
// TODO: allow this to be configurable
newBox := newNode.NewBox(text, true)
newBox := newNode.NewBox(text + " box", true)
return newBox
}

View File

@ -15,3 +15,17 @@ func (parent *Node) NewTextbox(name string) *Node {
sendAction(a)
return newNode
}
func (parent *Node) NewEntryLine(name string) *Node {
newNode := parent.newNode(name, toolkit.Textbox)
newNode.X = 1
newNode.Custom = func() {
log(debugGui, "NewTextbox changed =", name)
}
a := newAction(newNode, toolkit.Add)
sendAction(a)
return newNode
}

View File

@ -1,6 +1,7 @@
package main
import (
"strconv"
"github.com/andlabs/ui"
"git.wit.org/wit/gui/toolkit"
)
@ -20,6 +21,9 @@ func (n *node) show(b bool) {
}
func (n *node) enable(b bool) {
if n == nil {
panic("WHAT? enable was passed nil. How does this even happen?")
}
if n.tk == nil {
return
}
@ -193,12 +197,32 @@ func rawAction(a toolkit.Action) {
n := rootNode.findWidgetId(a.WidgetId)
switch a.ActionType {
case toolkit.Add:
if (a.ActionType == toolkit.Add) {
ui.QueueMain(func() {
add(a)
})
sleep(.05)
// TODO: remove this artificial delay
// sleep(.001)
return
}
if (a.ActionType == toolkit.Dump) {
log(debugNow, "rawAction() Dump =", a.ActionType, a.WidgetType, n.Name)
rootNode.listChildren(true)
return
}
if (n == nil) {
rootNode.listChildren(true)
log(true, "rawAction() ERROR findWidgetId found nil", a.ActionType, a.WidgetType)
log(true, "rawAction() ERROR findWidgetId found nil for id =", a.WidgetId)
log(true, "rawAction() ERROR findWidgetId found nil", a.ActionType, a.WidgetType)
log(true, "rawAction() ERROR findWidgetId found nil for id =", a.WidgetId)
return
panic("findWidgetId found nil for id = " + strconv.Itoa(a.WidgetId))
}
switch a.ActionType {
case toolkit.Show:
n.show(true)
case toolkit.Hide:

View File

@ -134,6 +134,9 @@ func (p *node) place(n *node) bool {
log(logError, "n.tk.uiControl == nil", n.tk)
panic("n.tk.uiControl == nil")
}
log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() node=", n.WidgetId, n.Name, n.Text, n.WidgetType)
log(logError, "THIS SHOULD NEVER HAPPEN ??????? trying to place() on parent=", p.WidgetId, p.Name, p.Text, p.WidgetType)
// panic("n.tk.uiControl == nil")
p.tk.uiTab.Append(n.Text, n.tk.uiControl)
p.tk.boxC += 1
return true

View File

@ -6,7 +6,7 @@ import (
)
func (p *node) newButton(n *node) {
log(debugToolkit, "newButton()", n.Name)
log(debugToolkit, "newButton() START", n.Name)
t := p.tk
if (t == nil) {
@ -27,4 +27,5 @@ func (p *node) newButton(n *node) {
n.tk = newt
p.place(n)
log(debugToolkit, "newButton() END", n.Name)
}

View File

@ -1,6 +1,9 @@
package main
import "git.wit.org/wit/gui/toolkit"
import (
"strconv"
"git.wit.org/wit/gui/toolkit"
)
var defaultBehavior bool = true
@ -126,3 +129,40 @@ func flag(a *toolkit.Action) {
log(debugError, "Can't set unknown flag", a.S)
}
}
func (n *node) dumpWidget(b bool) {
var info, d string
if (n == nil) {
log(debugError, "dumpWidget() node == nil")
return
}
info = n.WidgetType.String()
d = strconv.Itoa(n.WidgetId) + " " + info + " " + n.Name
var tabs string
for i := 0; i < listChildrenDepth; i++ {
tabs = tabs + defaultPadding
}
log(b, tabs + d)
}
var defaultPadding string = " "
var listChildrenDepth int = 0
func (n *node) listChildren(dump bool) {
if (n == nil) {
return
}
n.dumpWidget(dump)
if len(n.children) == 0 {
return
}
for _, child := range n.children {
listChildrenDepth += 1
child.listChildren(dump)
listChildrenDepth -= 1
}
}

View File

@ -30,7 +30,8 @@ func catchActionChannel() {
muAction.Lock()
// TODO ui.QueueMain(f)
// TODO ui.QueueMain( func() {rawAction(a)} )
rawAction(a)
ui.QueueMain( func() {rawAction(a)} )
// rawAction(a)
muAction.Unlock()
log(logInfo, "catchActionChannel() STUFF END", a.WidgetId, a.ActionType, a.WidgetType)
}
@ -57,7 +58,7 @@ func init() {
log(logNow, "Init() START")
log(debugToolkit, "Init()")
// Can you pass values to a plugin init() ? Otherwise, there is no way to safely print
// log(debugToolkit, "gui/toolkit init() Setting defaultBehavior = true")
// log(debugToolkit, "init() Setting defaultBehavior = true")
setDefaultBehavior(true)
// andlabs = make(map[int]*andlabsT)

View File

@ -5,6 +5,7 @@ import (
)
func (n *node) setText(a *toolkit.Action) {
log(debugChange, "setText() START with a.S =", a.S)
t := n.tk
if (t == nil) {
log(debugError, "setText error. tk == nil", n.Name, n.WidgetId)
@ -34,9 +35,19 @@ func (n *node) setText(a *toolkit.Action) {
case toolkit.Textbox:
switch a.ActionType {
case toolkit.Set:
t.uiMultilineEntry.SetText(a.S)
if (t.uiEntry != nil) {
t.uiEntry.SetText(a.S)
}
if (t.uiMultilineEntry != nil) {
t.uiMultilineEntry.SetText(a.S)
}
case toolkit.SetText:
t.uiMultilineEntry.SetText(a.S)
if (t.uiEntry != nil) {
t.uiEntry.SetText(a.S)
}
if (t.uiMultilineEntry != nil) {
t.uiMultilineEntry.SetText(a.S)
}
default:
log(debugError, "setText() unknown", a.ActionType, "on checkbox", n.Name)
}
@ -113,4 +124,5 @@ func (n *node) setText(a *toolkit.Action) {
default:
log(debugError, "plugin Send() Don't know how to setText on", n.WidgetType, "yet", a.ActionType)
}
log(debugChange, "setText() END with a.S =", a.S)
}

View File

@ -51,6 +51,9 @@ type andlabsT struct {
parent *andlabsT
children []*andlabsT
// used to track if a tab has a child widget yet
child bool
uiControl ui.Control
uiBox *ui.Box

View File

@ -23,7 +23,7 @@ func (p *node) newTab(n *node) {
var newt *andlabsT
if (p.WidgetType != toolkit.Window) {
log(debugToolkit, "newTab() uiWindow == nil. I can't add a toolbar without window", n.WidgetId, n.ParentId)
log(debugError, "newTab() uiWindow == nil. I can't add a toolbar without window", n.WidgetId, n.ParentId)
return
}
t := p.tk
@ -38,7 +38,15 @@ func (p *node) newTab(n *node) {
} else {
// this means you have to append a tab
log(debugToolkit, "newTab() GOOD. This should be an additional tab:", n.WidgetId, n.ParentId)
newt = t.appendTab(n.Text)
if (n.WidgetType == toolkit.Tab) {
// andlabs doesn't have multiple tab widgets so make a fake one?
// this makes a andlabsT internal structure with the parent values
newt = new(andlabsT)
newt.uiWindow = t.uiWindow
newt.uiTab = t.uiTab
} else {
newt = t.appendTab(n.Text)
}
}
n.tk = newt
@ -63,7 +71,7 @@ func rawTab(w *ui.Window, name string) *andlabsT {
log(debugError, "UiWindow == nil. I can't add a tab without a window")
log(debugError, "UiWindow == nil. I can't add a tab without a window")
log(debugError, "UiWindow == nil. I can't add a tab without a window")
sleep(1)
// sleep(1)
return nil
}
@ -77,13 +85,13 @@ func rawTab(w *ui.Window, name string) *andlabsT {
func (t *andlabsT) appendTab(name string) *andlabsT {
var newT andlabsT
log(debugToolkit, "gui.toolkit.NewTab() ADD", name)
log(debugToolkit, "appendTab() ADD", name)
if (t.uiTab == nil) {
log(debugToolkit, "gui.Toolkit.UiWindow == nil. I can't add a widget without a place to put it")
log(debugToolkit, "UiWindow == nil. I can't add a widget without a place to put it")
panic("should never have happened. wit/gui/toolkit has ui.Tab == nil")
}
log(debugToolkit, "gui.toolkit.AddTab() START name =", name)
log(debugToolkit, "appendTab() START name =", name)
var hbox *ui.Box
if (defaultBehavior) {
@ -103,12 +111,3 @@ func (t *andlabsT) appendTab(name string) *andlabsT {
newT.uiBox = hbox
return &newT
}
/*
func newTab(n *node) {
log(logInfo, "newTab() add to parent id:", n.ParentId)
p := n.parent
p.newTab(n)
}
*/

View File

@ -8,14 +8,25 @@ import (
func (p *node) newTextbox(n *node) {
newt := new(andlabsT)
e := ui.NewNonWrappingMultilineEntry()
newt.uiMultilineEntry = e
newt.uiControl = e
if (n.X == 1) {
e := ui.NewEntry()
newt.uiEntry = e
newt.uiControl = e
e.OnChanged(func(spin *ui.MultilineEntry) {
n.S = spin.Text()
n.doUserEvent()
})
e.OnChanged(func(spin *ui.Entry) {
n.S = spin.Text()
n.doUserEvent()
})
} else {
e := ui.NewNonWrappingMultilineEntry()
newt.uiMultilineEntry = e
newt.uiControl = e
e.OnChanged(func(spin *ui.MultilineEntry) {
n.S = spin.Text()
n.doUserEvent()
})
}
n.tk = newt
p.place(n)
}

View File

@ -2,10 +2,10 @@ all: plugin
ldd ../gocui.so
goget:
GO111MODULE="off" go get -v -t -u
go get -v -t -u
plugin:
GO111MODULE="off" go build -v -x -buildmode=plugin -o ../gocui.so
go build -v -x -buildmode=plugin -o ../gocui.so
objdump:
objdump -t ../gocui.so |less

View File

@ -31,29 +31,40 @@ func (n *node) addWidget() {
switch n.WidgetType {
case toolkit.Root:
log(logInfo, "setStartWH() rootNode w.id =", n.WidgetId, "w.name", n.Name)
nw.color = &colorRoot
n.setFake()
return
case toolkit.Flag:
nw.color = &colorFlag
n.setFake()
return
case toolkit.Window:
nw.frame = false
redoWindows(0,0)
nw.color = &colorWindow
// redoWindows(0,0)
return
case toolkit.Tab:
nw.color = &colorTab
// redoWindows(0,0)
return
case toolkit.Button:
nw.color = &colorButton
case toolkit.Box:
nw.color = &colorBox
nw.isFake = true
n.setFake()
return
case toolkit.Grid:
nw.color = &colorGrid
nw.isFake = true
n.setFake()
return
case toolkit.Group:
nw.color = &colorGroup
nw.frame = false
return
case toolkit.Label:
nw.color = &colorLabel
nw.frame = false
return
default:

View File

@ -1,17 +1,23 @@
package main
import (
"fmt"
"github.com/awesome-gocui/gocui"
"git.wit.org/wit/gui/toolkit"
)
// set isCurrent = false everywhere
func UnsetCurrent(n *node) {
func unsetCurrent(n *node) {
w := n.tk
w.isCurrent = false
if n.WidgetType == toolkit.Tab {
// n.tk.color = &colorTab
// n.setColor()
}
for _, child := range n.children {
UnsetCurrent(child)
unsetCurrent(child)
}
}
@ -22,7 +28,14 @@ func (n *node) updateCurrent() {
log("updateCurrent()", n.Name)
if n.WidgetType == toolkit.Tab {
if n.IsCurrent() {
// n.tk.color = &colorActiveT
n.setColor(&colorActiveT)
n.hideView()
n.showView()
setCurrentTab(n)
} else {
// n.tk.color = &colorTab
// n.setColor()
}
return
}
@ -47,7 +60,7 @@ func setCurrentWindow(n *node) {
if n.WidgetType != toolkit.Window {
return
}
UnsetCurrent(me.rootNode)
unsetCurrent(me.rootNode)
if n.hasTabs {
// set isCurrent = true on the first tab
@ -66,7 +79,7 @@ func setCurrentTab(n *node) {
if n.WidgetType != toolkit.Tab {
return
}
UnsetCurrent(me.rootNode)
unsetCurrent(me.rootNode)
w.isCurrent = true
p := n.parent.tk
p.isCurrent = true
@ -83,14 +96,51 @@ func (n *node) doWidgetClick() {
// me.rootNode.redoColor(true)
me.rootNode.dumpTree(true)
case toolkit.Window:
me.rootNode.hideWidgets()
n.redoTabs(me.TabW, me.TabH)
if ! n.hasTabs {
setCurrentWindow(n)
n.placeWidgets(me.RawW, me.RawH)
n.showWidgets()
if (me.currentWindow == n) {
return
}
if (me.currentWindow != nil) {
unsetCurrent(me.currentWindow)
me.currentWindow.setColor(&colorWindow)
me.currentWindow.hideWidgets()
}
n.hideWidgets()
me.currentWindow = n
// setCurrentWindow(n) // probably delete this
n.setColor(&colorActiveW)
n.redoTabs(me.TabW, me.TabH)
for _, child := range n.children {
if (child.currentTab == true) {
log(true, "FOUND CURRENT TAB", child.Name)
setCurrentTab(child)
child.placeWidgets(me.RawW, me.RawH)
child.showWidgets()
return
}
}
/* FIXME: redo this
if ! n.hasTabs {
}
*/
case toolkit.Tab:
if (n.IsCurrent()) {
return // do nothing if you reclick on the already selected tab
}
// find the window and disable the active tab
p := n.parent
if (p != nil) {
p.hideWidgets()
p.redoTabs(me.TabW, me.TabH)
unsetCurrent(p)
for _, child := range p.children {
if child.WidgetType == toolkit.Tab {
child.setColor(&colorTab)
n.currentTab = false
}
}
}
n.currentTab = true
n.setColor(&colorActiveT)
setCurrentTab(n)
n.placeWidgets(me.RawW, me.RawH)
n.showWidgets()
@ -118,6 +168,50 @@ func (n *node) doWidgetClick() {
n.toggleTree()
case toolkit.Button:
n.doUserEvent()
case toolkit.Dropdown:
log(true, "do the dropdown here")
if (me.ddview == nil) {
me.ddview = addDropdown()
tk := me.ddview.tk
tk.gocuiSize.w0 = 20
tk.gocuiSize.w1 = 40
tk.gocuiSize.h0 = 10
tk.gocuiSize.h1 = 25
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.v.Wrap = true
tk.v.Frame = true
tk.v.Clear()
fmt.Fprint(tk.v, "example.com\nwit.org\nwit.com")
me.ddview.SetVisible(true)
return
}
log(true, "doWidgetClick() visible =", me.ddview.Visible())
if (me.ddview.Visible()) {
me.ddview.SetVisible(false)
me.baseGui.DeleteView("ddview")
me.ddview.tk.v = nil
} else {
var dnsList string
for i, s := range n.vals {
log(logNow, "AddText()", n.Name, i, s)
dnsList += s + "\n"
}
me.ddNode = n
log(logNow, "new dns list should be set to:", dnsList)
me.ddview.Text = dnsList
me.ddview.SetText(dnsList)
me.ddview.SetVisible(true)
}
for i, s := range n.vals {
log(logNow, "AddText()", n.Name, i, s)
}
default:
}
}
@ -162,26 +256,15 @@ func click(g *gocui.Gui, v *gocui.View) error {
n := findUnderMouse()
if (n != nil) {
log(logNow, "click() Found widget =", n.WidgetId, n.Name, ",", n.Text)
if (n.Name == "DropBox") {
log(logNow, "click() this is the dropdown menu. set a flag here what did I click? where is the mouse?")
log(logNow, "click() set a global dropdown clicked flag=true here")
me.ddClicked = true
}
n.doWidgetClick()
} else {
log(logNow, "click() could not find node name =", v.Name())
}
/*
i, err := strconv.Atoi(v.Name())
if (err != nil) {
log(logError, "click() Can't find widget. error =", err)
} else {
log(logVerbose, "click() ok v.Name() =", v.Name())
n := me.rootNode.findWidgetId(i)
if (n == nil) {
log(logError, "click() CANT FIND VIEW in binary tree. v.Name =", v.Name())
return nil
}
log(logNow, "click() Found widget =", n.WidgetId, n.Name, ",", n.Text)
n.doWidgetClick()
return nil
}
*/
if _, err := g.SetCurrentView(v.Name()); err != nil {
return err
@ -207,6 +290,17 @@ func findUnderMouse() *node {
found = n
}
}
if (n == me.ddview) {
log(true, "findUnderMouse() found ddview")
if n.Visible() {
log(true, "findUnderMouse() and ddview is visable. hide it here. TODO: find highlighted row")
found = n
// find the actual value here and set the dropdown widget
me.baseGui.DeleteView("ddview")
} else {
log(true, "findUnderMouse() I was lying, actually it's not found")
}
}
for _, child := range n.children {
f(child)
@ -217,7 +311,7 @@ func findUnderMouse() *node {
// TODO: pop up menu with a list of them
for _, n := range widgets {
//log(logNow, "ctrlDown() FOUND widget", widget.id, widget.name)
n.showWidgetPlacement(logNow, "ctrlDown() FOUND")
n.showWidgetPlacement(logNow, "findUnderMouse() FOUND")
}
return found
}
@ -228,27 +322,6 @@ func ctrlDown(g *gocui.Gui, v *gocui.View) error {
// var widgets []*node
// var f func (n *node)
found = findUnderMouse()
/*
w, h := g.MousePosition()
// find buttons that are below where the mouse button click
f = func(n *node) {
widget := n.tk
// ignore widgets that are not visible
if n.Visible() {
if ((widget.gocuiSize.w0 <= w) && (w <= widget.gocuiSize.w1) &&
(widget.gocuiSize.h0 <= h) && (h <= widget.gocuiSize.h1)) {
widgets = append(widgets, n)
found = n
}
}
for _, child := range n.children {
f(child)
}
}
f(me.rootNode)
*/
if (me.ctrlDown == nil) {
setupCtrlDownWidget()
me.ctrlDown.Text = found.Name
@ -266,9 +339,9 @@ func ctrlDown(g *gocui.Gui, v *gocui.View) error {
cd.gocuiSize.w1 = newR.w1
cd.gocuiSize.h1 = newR.h1
if me.ctrlDown.Visible() {
me.ctrlDown.deleteView()
me.ctrlDown.hideView()
} else {
me.ctrlDown.updateView()
me.ctrlDown.showView()
}
me.ctrlDown.showWidgetPlacement(logNow, "ctrlDown:")
return nil

View File

@ -3,86 +3,85 @@ package main
import (
"math/rand"
"github.com/awesome-gocui/gocui"
"git.wit.org/wit/gui/toolkit"
)
// ColorBlack ColorRed ColorGreen ColorYellow ColorBlue ColorMagenta ColorCyan ColorWhite
// gocui.GetColor("#FFAA55") // Dark Purple
func (n *node) setDefaultWidgetColor() {
w := n.tk
log(logInfo, "setDefaultWidgetColor() on", n.WidgetType, n.Name)
v, _ := me.baseGui.View(w.cuiName)
if (v == nil) {
log(logError, "setDefaultWidgetColor() failed on view == nil")
return
}
sleep(.05)
// v.BgColor = gocui.GetColor("#FFAA55") // Dark Purple
// v.BgColor = gocui.GetColor("#88AA55") // heavy purple
// v.BgColor = gocui.GetColor("#111111") // crazy red
// v.BgColor = gocui.GetColor("#FF9911") // heavy red
// v.SelBgColor = gocui.GetColor("#FFEE11") // blood red
//w.v.SelBgColor = gocui.ColorCyan
//color.go: w.v.SelFgColor = gocui.ColorBlack
//color.go: w.v.BgColor = gocui.ColorGreen
// v.BgColor = gocui.GetColor("#55AAFF") // super light grey
// v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow
switch n.WidgetType {
case toolkit.Root:
v.FrameColor = gocui.ColorRed
v.BgColor = gocui.GetColor("#B0E0E6") // w3c 'powerder blue'
case toolkit.Flag:
v.FrameColor = gocui.ColorRed
v.BgColor = gocui.GetColor("#B0E0E6") // w3c 'powerder blue'
case toolkit.Window:
v.FgColor = gocui.ColorCyan
v.SelBgColor = gocui.ColorBlue
v.FrameColor = gocui.ColorBlue
case toolkit.Tab:
v.SelBgColor = gocui.ColorBlue
v.FrameColor = gocui.ColorBlue
case toolkit.Button:
v.BgColor = gocui.ColorWhite
v.FrameColor = gocui.ColorGreen
v.SelBgColor = gocui.ColorBlack
v.SelFgColor = gocui.ColorGreen
case toolkit.Label:
v.BgColor = gocui.GetColor("#55AAFF") // super light grey
v.SelBgColor = gocui.GetColor("#55AAFF") // super light grey
case toolkit.Box:
v.FrameColor = gocui.ColorRed
// v.BgColor = gocui.GetColor("#FFC0CB") // 'w3c pink' yellow
v.BgColor = gocui.GetColor("#DDDDDD") // light purple
case toolkit.Grid:
// v.FgColor = gocui.ColorCyan
// v.SelBgColor = gocui.ColorBlue
// v.FrameColor = gocui.ColorBlue
case toolkit.Group:
v.BgColor = gocui.GetColor("#55AAFF") // super light grey
default:
}
type colorT struct {
frame gocui.Attribute
fg gocui.Attribute
bg gocui.Attribute
selFg gocui.Attribute
selBg gocui.Attribute
name string
}
// SetColor("#FFAA55") // purple
func (w *cuiWidget) SetColor(c string) {
if (w.v == nil) {
log(logError, "SetColor() failed on view == nil")
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{ none , gocui.ColorBlue, none , none , powdererBlue , "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 colorButton colorT = colorT{gocui.ColorGreen, none , gocui.ColorWhite, gocui.ColorGreen, gocui.ColorBlack, "normal button"}
var colorLabel colorT = colorT{ none , none , superLightGrey , none , superLightGrey , "normal label"}
var colorGroup colorT = colorT{ none , none , superLightGrey , none , superLightGrey , "normal group"}
// 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: or fix kvm/qemu serial console & SIGWINCH.
// TODO: and minicom and uboot and 5 million other things.
// 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
// TODO: so just a small little 'todo' item here
func (n *node) setColor(newColor *colorT) {
tk := n.tk
if (tk.color == newColor) {
// nothing to do since the colors have nto changed
return
}
w.v.SelBgColor = gocui.ColorCyan
w.v.SelFgColor = gocui.ColorBlack
switch c {
case "Green":
w.v.BgColor = gocui.ColorGreen
case "Purple":
w.v.BgColor = gocui.GetColor("#FFAA55")
case "Yellow":
w.v.BgColor = gocui.ColorYellow
case "Blue":
w.v.BgColor = gocui.ColorBlue
case "Red":
w.v.BgColor = gocui.ColorRed
default:
w.v.BgColor = gocui.GetColor(c)
tk.color = newColor
if (tk.v == nil) {
return
}
if (tk.color == nil) {
log(true, "Set the node to color = nil")
tk.color = &colorNone
}
log(true, "Set the node to color =", tk.color.name)
n.recreateView()
}
func (n *node) setDefaultWidgetColor() {
n.showView()
}
func (n *node) setDefaultHighlight() {

View File

@ -98,6 +98,46 @@ func (n *node) findWidgetName(name string) *node {
return nil
}
func (n *node) IsCurrent() bool {
w := n.tk
if (n.WidgetType == toolkit.Tab) {
return w.isCurrent
}
if (n.WidgetType == toolkit.Window) {
return w.isCurrent
}
if (n.WidgetType == toolkit.Root) {
return false
}
return n.parent.IsCurrent()
}
func (n *node) Visible() bool {
if (n == nil) {
return false
}
if (n.tk == nil) {
return false
}
if (n.tk.v == nil) {
return false
}
return n.tk.v.Visible
}
func (n *node) SetVisible(b bool) {
if (n == nil) {
return
}
if (n.tk == nil) {
return
}
if (n.tk.v == nil) {
return
}
n.tk.v.Visible = b
}
func addNode(a *toolkit.Action) *node {
n := new(node)
n.WidgetType = a.WidgetType
@ -143,42 +183,29 @@ func addNode(a *toolkit.Action) *node {
return n
}
func (n *node) IsCurrent() bool {
w := n.tk
if (n.WidgetType == toolkit.Tab) {
return w.isCurrent
}
if (n.WidgetType == toolkit.Window) {
return w.isCurrent
}
if (n.WidgetType == toolkit.Root) {
return false
}
return n.parent.IsCurrent()
}
func addDropdown() *node {
n := new(node)
n.WidgetType = toolkit.Flag
n.WidgetId = -2
n.ParentId = 0
func (n *node) Visible() bool {
if (n == nil) {
return false
}
if (n.tk == nil) {
return false
}
if (n.tk.v == nil) {
return false
}
return n.tk.v.Visible
}
// copy the data from the action message
n.Name = "DropBox"
n.Text = "DropBox text"
func (n *node) SetVisible(b bool) {
if (n == nil) {
return
// store the internal toolkit information
n.tk = new(cuiWidget)
n.tk.frame = true
// set the name used by gocui to the id
n.tk.cuiName = "-1 dropbox"
n.tk.color = &colorFlag
// add this new widget on the binary tree
n.parent = me.rootNode
if n.parent != nil {
n.parent.children = append(n.parent.children, n)
}
if (n.tk == nil) {
return
}
if (n.tk.v == nil) {
return
}
n.tk.v.Visible = b
return n
}

View File

@ -72,7 +72,7 @@ func dragOutputWindow() {
func setFrame(b bool) {
// TODO: figure out what this might be useful for
// what is this do? I made it just 2 lines for now. Is this useful for something?
v := SetView("global", 15, 5, 80, 8, 10)
v := SetView("global", 5, 10, 5, 10, 0) // x0, x1, y1, y2, overlap
if (v == nil) {
log(logError, "setFrame() global failed")
}

View File

@ -14,7 +14,9 @@ import (
var helpText []string = []string{"KEYBINDINGS",
"",
"d: show/hide debugging",
"?: toggle help",
"d: toggle debugging",
"r: redraw widgets",
"s/h: show/hide all widgets",
"L: list all widgets",
"q: quit()",
@ -27,6 +29,12 @@ var helpText []string = []string{"KEYBINDINGS",
"",
}
func hidehelplayout() {
me.baseGui.DeleteView("help")
// n.deleteView()
// child.hideFake()
}
func helplayout() error {
g := me.baseGui
var err error

View File

@ -22,6 +22,7 @@ func defaultKeybindings(g *gocui.Gui) error {
if err := g.SetKeybinding("", gocui.MouseRelease, gocui.ModNone, mouseUp); err != nil {
return err
}
// mouseDown() runs whenever you click on an unknown view (?)
if err := g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, mouseDown); err != nil {
return err
}
@ -40,10 +41,8 @@ func defaultKeybindings(g *gocui.Gui) error {
return nil
}
var showDebug bool = true
func addDebugKeys(g *gocui.Gui) {
// dump all widget info to the log
// show debugging buttons
g.SetKeybinding("", 'd', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
log(logNow, "gocui.SetKeyBinding() dumpTree() START")
@ -63,9 +62,29 @@ func addDebugKeys(g *gocui.Gui) {
// display the help menu
g.SetKeybinding("", '?', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
helplayout()
if (showHelp) {
helplayout()
showHelp = false
} else {
me.baseGui.DeleteView("help")
showHelp = true
}
return nil
})
// redraw all the widgets
g.SetKeybinding("", 'r', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {
if (redoWidgets) {
redoWindows(0,0)
redoWidgets = false
} else {
me.rootNode.hideWidgets()
redoWidgets = true
}
return nil
})
// hide all widgets
g.SetKeybinding("", 'h', gocui.ModNone,
func(g *gocui.Gui, v *gocui.View) error {

View File

@ -24,6 +24,7 @@ func mouseMove(g *gocui.Gui) {
func msgDown(g *gocui.Gui, v *gocui.View) error {
initialMouseX, initialMouseY = g.MousePosition()
log(true, "msgDown() X,Y", initialMouseX, initialMouseY)
if vx, vy, _, _, err := g.ViewPosition("msg"); err == nil {
xOffset = initialMouseX - vx
yOffset = initialMouseY - vy
@ -32,7 +33,58 @@ func msgDown(g *gocui.Gui, v *gocui.View) error {
return nil
}
func hideDDview() error {
w, h := me.baseGui.MousePosition()
log(true, "hide dropdown menu() view msgMouseDown (w,h) =", w, h)
if (me.ddview == nil) {
return gocui.ErrUnknownView
}
if (me.ddview.tk.v == nil) {
return gocui.ErrUnknownView
}
me.ddview.SetVisible(false)
return nil
}
func showDDview() error {
w, h := me.baseGui.MousePosition()
log(true, "show dropdown menu() view msgMouseDown (w,h) =", w, h)
if (me.ddview == nil) {
return gocui.ErrUnknownView
}
if (me.ddview.tk.v == nil) {
return gocui.ErrUnknownView
}
me.ddview.SetVisible(true)
return nil
}
func mouseUp(g *gocui.Gui, v *gocui.View) error {
w, h := g.MousePosition()
log(true, "mouseUp() view msgMouseDown (check here for dropdown menu click) (w,h) =", w, h)
if (me.ddClicked) {
log(true, "mouseUp() ddview is the thing that was clicked", w, h)
log(true, "mouseUp() find out what the string is here", w, h, me.ddview.tk.gocuiSize.h1)
if (me.ddNode != nil) {
value := h - me.ddview.tk.gocuiSize.h0 - 1
log(true, "mouseUp() me.ddview.tk.gocuiSize.h1 =", me.ddview.tk.gocuiSize.h1)
log(true, "mouseUp() me.ddNode.vals =", me.ddNode.vals)
valsLen := len(me.ddNode.vals)
log(true, "mouseUp() value =", value, "valsLen =", valsLen)
log(true, "mouseUp() me.ddNode.vals =", me.ddNode.vals)
if ((value >= 0) && (value < valsLen)) {
str := me.ddNode.vals[value]
log(true, "mouseUp() value =", value, "str =", str)
}
}
}
/*
// if there is a drop down view active, treat it like a dialog box and close it
if (hideDDview() == nil) {
return nil
}
*/
if msgMouseDown {
msgMouseDown = false
if movingMsg {
@ -57,13 +109,25 @@ func mouseDown(g *gocui.Gui, v *gocui.View) error {
}
globalMouseDown = true
maxX, _ := g.Size()
msg := fmt.Sprintf("Mouse really down at: %d,%d", mx, my) + "foo\n" + "bar\n"
test := findUnderMouse()
msg := fmt.Sprintf("Mouse really down at: %d,%d", mx, my) + "foobar"
if (test == me.ddview) {
if (me.ddview.Visible()) {
log(true, "hide DDview() Mouse really down at:", mx, my)
hideDDview()
} else {
log(true, "show DDview() Mouse really down at:", mx, my)
showDDview()
}
return nil
}
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(true, "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

View File

@ -24,6 +24,7 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
log(true, "showMsg() v.name =", v.Name())
if _, err := g.SetCurrentView(v.Name()); err != nil {
return err
}

View File

@ -21,14 +21,24 @@ import (
// It's probably a terrible idea to call this 'me'
var me config
var showDebug bool = true
var showHelp bool = true
var redoWidgets bool = true
// This is the window that is currently active
var currentWindow *node
type config struct {
baseGui *gocui.Gui // the main gocui handle
rootNode *node // the base of the binary tree. it should have id == 0
ctrlDown *node // shown if you click the mouse when the ctrl key is pressed
// current *cuiWidget // this is the current tab or window to show
currentWindow *node // this is the current tab or window to show
logStdout *node // where to show STDOUT
helpLabel *gocui.View
ddview *node // the gocui view to select dropdrown lists
ddClicked bool // the dropdown menu view was clicked
ddNode *node // the dropdown menu is for this widget
// this is the channel we send user events like
// mouse clicks or keyboard events back to the program
@ -123,6 +133,7 @@ type node struct {
horizontal bool `default:false`
hasTabs bool // does the window have tabs?
currentTab bool // the visible tab
// the internal plugin toolkit structure
tk *cuiWidget
@ -152,7 +163,7 @@ type cuiWidget struct {
size rectType
// the actual gocui display view of this widget
// sometimes this isn't visable like with a Box or Grid
// sometimes this isn't visible like with a Box or Grid
gocuiSize rectType
isCurrent bool // is this the currently displayed Window or Tab?
@ -164,6 +175,12 @@ type cuiWidget struct {
tainted bool
frame bool
// for a window, this is currently selected tab
selectedTab *node
// what color to use
color *colorT
}
// from the gocui devs:

View File

@ -103,6 +103,10 @@ func (p *node) redoTabs(nextW int, nextH int) {
n.gocuiSetWH(nextW, nextH)
n.deleteView()
// setCurrentTab(n)
// if (len(w.cuiName) < 4) {
// w.cuiName = "abcd"
// }
n.showView()
sizeW := w.Width() + me.TabPadW

View File

@ -36,7 +36,12 @@ func (n *node) textResize() {
n.showWidgetPlacement(logNow, "textResize()")
}
func (n *node) hideView() {
n.SetVisible(false)
}
// display's the text of the widget in gocui
// will create a new gocui view if there isn't one or if it has been moved
func (n *node) showView() {
var err error
w := n.tk
@ -46,31 +51,34 @@ func (n *node) showView() {
w.cuiName = strconv.Itoa(n.WidgetId)
}
// if the gocui element doesn't exist, create it
if (w.v == nil) {
n.updateView()
n.recreateView()
}
x0, y0, x1, y1, err := me.baseGui.ViewPosition(w.cuiName)
log(logInfo, "showView() w.v already defined for widget", n.Name, err)
// if the gocui element has changed where it is supposed to be on the screen
// recreate it
if (x0 != w.gocuiSize.w0) || (y0 != w.gocuiSize.h0) {
log(logError, "showView() w.v.w0 != x0", n.Name, w.gocuiSize.w0, x0)
log(logError, "showView() w.v.h0 != y0", n.Name, w.gocuiSize.h0, y0)
n.updateView()
n.recreateView()
return
}
if (x1 != w.gocuiSize.w1) || (y1 != w.gocuiSize.h1) {
log(logError, "showView() w.v.w1 != x1", n.Name, w.gocuiSize.w1, x1)
log(logError, "showView() w.v.h1 != y1", n.Name, w.gocuiSize.h1, y1)
n.updateView()
n.recreateView()
return
}
if (w.v.Visible == false) {
log(logInfo, "showView() w.v.Visible set to true ", n.Name)
w.v.Visible = true
}
n.SetVisible(true)
}
func (n *node) updateView() {
// create or recreate the gocui widget visible
// deletes the old view if it exists and recreates it
func (n *node) recreateView() {
var err error
w := n.tk
if (me.baseGui == nil) {
@ -105,8 +113,14 @@ func (n *node) updateView() {
fmt.Fprint(w.v, n.Text)
n.showWidgetPlacement(logNow, "Window: " + n.Text)
n.setDefaultHighlight()
n.setDefaultWidgetColor()
// 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
}
}
func (n *node) hideWidgets() {
@ -119,7 +133,7 @@ func (n *node) hideWidgets() {
case toolkit.Box:
case toolkit.Grid:
default:
n.deleteView()
n.hideView()
}
for _, child := range n.children {
child.hideWidgets()
@ -129,7 +143,7 @@ func (n *node) hideWidgets() {
func (n *node) hideFake() {
w := n.tk
if (w.isFake) {
n.deleteView()
n.hideView()
}
for _, child := range n.children {
child.hideFake()
@ -153,13 +167,13 @@ func (n *node) showWidgets() {
if (w.isFake) {
// don't display by default
} else {
if n.IsCurrent() {
// if n.IsCurrent() {
n.showWidgetPlacement(logInfo, "current:")
n.showView()
} else {
n.showWidgetPlacement(logInfo, "not:")
// } else {
// n.showWidgetPlacement(logInfo, "not:")
// w.drawView()
}
// }
}
for _, child := range n.children {
child.showWidgets()

View File

@ -69,6 +69,7 @@ const (
Textbox // is this a Label with edit=true
Slider // like a progress bar
Spinner // like setting the oven temperature
Separator // TODO
Image // TODO
Area // TODO
Form // TODO
@ -138,6 +139,8 @@ func (s WidgetType) String() string {
return "Slider"
case Spinner:
return "Spinner"
case Separator:
return "Separator"
case Image:
return "Image"
case Area: