diff --git a/.gitignore b/.gitignore index 135bf8c..6dafe23 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index d0e7cfc..d37e551 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/button.go b/button.go index ccd7670..d4f8464 100644 --- a/button.go +++ b/button.go @@ -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 } - diff --git a/chan.go b/chan.go index 54eb4e4..985ee63 100644 --- a/chan.go +++ b/chan.go @@ -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() diff --git a/debug.go b/debug.go index acc249d..1fe2fe8 100644 --- a/debug.go +++ b/debug.go @@ -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) { diff --git a/examples/cloudflare/Makefile b/examples/cloudflare/Makefile index bcd88c6..fd82fdc 100644 --- a/examples/cloudflare/Makefile +++ b/examples/cloudflare/Makefile @@ -16,3 +16,6 @@ update: log: reset tail -f /tmp/witgui.* /tmp/guilogfile + +gocui: build + ./cloudflare -gui gocui >/tmp/witgui.log.stderr 2>&1 diff --git a/examples/cloudflare/api.go b/examples/cloudflare/api.go new file mode 100644 index 0000000..8cf550e --- /dev/null +++ b/examples/cloudflare/api.go @@ -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 +} diff --git a/examples/cloudflare/curl.sh b/examples/cloudflare/curl.sh new file mode 100644 index 0000000..ec8c014 --- /dev/null +++ b/examples/cloudflare/curl.sh @@ -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" diff --git a/examples/cloudflare/dns.go b/examples/cloudflare/dns.go deleted file mode 100644 index eb8de23..0000000 --- a/examples/cloudflare/dns.go +++ /dev/null @@ -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 -} diff --git a/examples/cloudflare/gui.go b/examples/cloudflare/gui.go new file mode 100644 index 0000000..60fd5fe --- /dev/null +++ b/examples/cloudflare/gui.go @@ -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() +} diff --git a/examples/cloudflare/main.go b/examples/cloudflare/main.go index b83d276..badf97a 100644 --- a/examples/cloudflare/main.go +++ b/examples/cloudflare/main.go @@ -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 } diff --git a/examples/cloudflare/structs.go b/examples/cloudflare/structs.go new file mode 100644 index 0000000..af4d7f3 --- /dev/null +++ b/examples/cloudflare/structs.go @@ -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 diff --git a/main.go b/main.go index f121cec..03479bba 100644 --- a/main.go +++ b/main.go @@ -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 } } } diff --git a/plugin.go b/plugin.go index c43b4af..1ce900c 100644 --- a/plugin.go +++ b/plugin.go @@ -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 } } diff --git a/tab.go b/tab.go index 95d9fa4..abaf5cb 100644 --- a/tab.go +++ b/tab.go @@ -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 } diff --git a/textbox.go b/textbox.go index 5b416a7..3c9fd36 100644 --- a/textbox.go +++ b/textbox.go @@ -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 +} diff --git a/toolkit/andlabs/action.go b/toolkit/andlabs/action.go index c792a09..16a895b 100644 --- a/toolkit/andlabs/action.go +++ b/toolkit/andlabs/action.go @@ -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: diff --git a/toolkit/andlabs/add.go b/toolkit/andlabs/add.go index fcdc56b..b01dd20 100644 --- a/toolkit/andlabs/add.go +++ b/toolkit/andlabs/add.go @@ -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 diff --git a/toolkit/andlabs/button.go b/toolkit/andlabs/button.go index a6260b5..d61c0ea 100644 --- a/toolkit/andlabs/button.go +++ b/toolkit/andlabs/button.go @@ -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) } diff --git a/toolkit/andlabs/debug.go b/toolkit/andlabs/debug.go index 7abd2d1..87e875d 100644 --- a/toolkit/andlabs/debug.go +++ b/toolkit/andlabs/debug.go @@ -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 + } +} diff --git a/toolkit/andlabs/main.go b/toolkit/andlabs/main.go index f66849d..3ba3079 100644 --- a/toolkit/andlabs/main.go +++ b/toolkit/andlabs/main.go @@ -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) diff --git a/toolkit/andlabs/setText.go b/toolkit/andlabs/setText.go index 11de327..a3332f5 100644 --- a/toolkit/andlabs/setText.go +++ b/toolkit/andlabs/setText.go @@ -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) } diff --git a/toolkit/andlabs/structs.go b/toolkit/andlabs/structs.go index c71732d..507a50c 100644 --- a/toolkit/andlabs/structs.go +++ b/toolkit/andlabs/structs.go @@ -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 diff --git a/toolkit/andlabs/tab.go b/toolkit/andlabs/tab.go index eecebfa..2f44b03 100644 --- a/toolkit/andlabs/tab.go +++ b/toolkit/andlabs/tab.go @@ -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) -} -*/ diff --git a/toolkit/andlabs/textbox.go b/toolkit/andlabs/textbox.go index 56788d8..1745d11 100644 --- a/toolkit/andlabs/textbox.go +++ b/toolkit/andlabs/textbox.go @@ -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) } diff --git a/toolkit/gocui/Makefile b/toolkit/gocui/Makefile index 6c4f7d5..4f2a6ee 100644 --- a/toolkit/gocui/Makefile +++ b/toolkit/gocui/Makefile @@ -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 diff --git a/toolkit/gocui/add.go b/toolkit/gocui/add.go index 97d65d3..446d70c 100644 --- a/toolkit/gocui/add.go +++ b/toolkit/gocui/add.go @@ -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: diff --git a/toolkit/gocui/click.go b/toolkit/gocui/click.go index 380b0a3..cb58bc3 100644 --- a/toolkit/gocui/click.go +++ b/toolkit/gocui/click.go @@ -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 diff --git a/toolkit/gocui/color.go b/toolkit/gocui/color.go index 5dbed05..4c2ea76 100644 --- a/toolkit/gocui/color.go +++ b/toolkit/gocui/color.go @@ -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() { diff --git a/toolkit/gocui/common.go b/toolkit/gocui/common.go index 05de64b..d3de34c 100644 --- a/toolkit/gocui/common.go +++ b/toolkit/gocui/common.go @@ -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 } diff --git a/toolkit/gocui/gocui.go b/toolkit/gocui/gocui.go index 85e6ea5..d2877d7 100644 --- a/toolkit/gocui/gocui.go +++ b/toolkit/gocui/gocui.go @@ -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") } diff --git a/toolkit/gocui/help.go b/toolkit/gocui/help.go index 66719bb..6113257 100644 --- a/toolkit/gocui/help.go +++ b/toolkit/gocui/help.go @@ -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 diff --git a/toolkit/gocui/keybindings.go b/toolkit/gocui/keybindings.go index dbe1fe2..658d09a 100644 --- a/toolkit/gocui/keybindings.go +++ b/toolkit/gocui/keybindings.go @@ -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 { diff --git a/toolkit/gocui/mouse.go b/toolkit/gocui/mouse.go index dbe2c6d..64786ab 100644 --- a/toolkit/gocui/mouse.go +++ b/toolkit/gocui/mouse.go @@ -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 diff --git a/toolkit/gocui/showStdout.go b/toolkit/gocui/showStdout.go index 22e95d3..f03dfef 100644 --- a/toolkit/gocui/showStdout.go +++ b/toolkit/gocui/showStdout.go @@ -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 } diff --git a/toolkit/gocui/structs.go b/toolkit/gocui/structs.go index 06a2266..05df963 100644 --- a/toolkit/gocui/structs.go +++ b/toolkit/gocui/structs.go @@ -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: diff --git a/toolkit/gocui/tab.go b/toolkit/gocui/tab.go index 60ee6b3..d910552 100644 --- a/toolkit/gocui/tab.go +++ b/toolkit/gocui/tab.go @@ -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 diff --git a/toolkit/gocui/view.go b/toolkit/gocui/view.go index e2c76aa..3baeb66 100644 --- a/toolkit/gocui/view.go +++ b/toolkit/gocui/view.go @@ -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() diff --git a/toolkit/widget.go b/toolkit/widget.go index 655a0db..02dbe3f 100644 --- a/toolkit/widget.go +++ b/toolkit/widget.go @@ -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: