2025-02-01 11:42:31 -06:00
|
|
|
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
|
|
|
|
// Use of this source code is governed by the GPL 3.0
|
|
|
|
|
2024-01-18 00:08:37 -06:00
|
|
|
// all structures and variables are local (aka lowercase)
|
|
|
|
// since the plugin should be isolated to access only
|
|
|
|
// by functions() to insure everything here is run
|
|
|
|
// inside a dedicated goroutine
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"sync"
|
2025-02-08 08:07:03 -06:00
|
|
|
"time"
|
2024-01-18 00:08:37 -06:00
|
|
|
|
2024-01-28 02:20:31 -06:00
|
|
|
"github.com/awesome-gocui/gocui"
|
|
|
|
|
2025-02-01 15:19:35 -06:00
|
|
|
log "go.wit.com/log"
|
2024-01-28 02:20:31 -06:00
|
|
|
"go.wit.com/toolkits/tree"
|
2024-01-18 00:08:37 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// It's probably a terrible idea to call this 'me'
|
2025-02-01 15:19:35 -06:00
|
|
|
// 2025 note: doesn't seem terrible to call this 'me' anymore. notsure.
|
2024-01-18 00:08:37 -06:00
|
|
|
var me config
|
|
|
|
|
2025-02-06 02:54:50 -06:00
|
|
|
// todo: move all this to a protobuf. then, redo all this mess.
|
|
|
|
// it got me here, but now it's time to clean it up for good
|
|
|
|
// I can't get a GO plugins that use protobuf to load yet (versioning mismatch)
|
2024-01-18 00:08:37 -06:00
|
|
|
type config struct {
|
2025-02-06 23:49:18 -06:00
|
|
|
baseGui *gocui.Gui // the main gocui handle
|
|
|
|
treeRoot *tree.Node // the base of the binary tree. it should have id == 0
|
|
|
|
myTree *tree.TreeInfo // ?
|
|
|
|
currentWindow *guiWidget // this is the current tab or window to show
|
2025-02-07 12:42:36 -06:00
|
|
|
ok bool // if the user doesn't hit a key or move the mouse, gocui doesn't really start
|
|
|
|
ctrlDown *tree.Node // shown if you click the mouse when the ctrl key is pressed
|
2025-02-06 23:49:18 -06:00
|
|
|
helpLabel *gocui.View // ?
|
|
|
|
showHelp bool // toggle boolean for the help menu (deprecate?)
|
2025-02-06 14:35:01 -06:00
|
|
|
FramePadW int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side
|
|
|
|
FramePadH int `default:"1" dense:"0"` // When the widget has a frame, like a button, it adds 2 lines runes on each side
|
|
|
|
PadW int `default:"1" dense:"0"` // pad spacing
|
|
|
|
PadH int `default:"1" dense:"0"` // pad spacing
|
|
|
|
WindowW int `default:"8" dense:"0"` // how far down to start Window or Tab headings
|
|
|
|
WindowH int `default:"-1"` // how far down to start Window or Tab headings
|
|
|
|
TabW int `default:"5" dense:"0"` // how far down to start Window or Tab headings
|
|
|
|
TabH int `default:"1" dense:"0"` // how far down to start Window or Tab headings
|
|
|
|
WindowPadW int `default:"8" dense:"0"` // additional amount of space to put between window & tab widgets
|
|
|
|
TabPadW int `default:"4" dense:"0"` // additional amount of space to put between window & tab widgets
|
|
|
|
GroupPadW int `default:"2" dense:"1"` // additional amount of space to indent on a group
|
|
|
|
BoxPadW int `default:"2" dense:"1"` // additional amount of space to indent on a box
|
|
|
|
GridPadW int `default:"2" dense:"1"` // additional amount of space to indent on a grid
|
|
|
|
RawW int `default:"1"` // the raw beginning of each window (or tab)
|
|
|
|
RawH int `default:"5"` // the raw beginning of each window (or tab)
|
|
|
|
FakeW int `default:"20"` // offset for the hidden widgets
|
|
|
|
padded bool // add space between things like buttons
|
|
|
|
bookshelf bool // do you want things arranged in the box like a bookshelf or a stack?
|
|
|
|
canvas bool // if set to true, the windows are a raw canvas
|
|
|
|
menubar bool // for windows
|
|
|
|
stretchy bool // expand things like buttons to the maximum size
|
|
|
|
margin bool // add space around the frames of windows
|
|
|
|
writeMutex sync.Mutex // writeMutex protects writes to *guiWidget (it's global right now maybe)
|
|
|
|
ecount int // counts how many mouse and keyboard events have occurred
|
|
|
|
supermouse bool // prints out every widget found while you move the mouse around
|
|
|
|
depth int // used for listWidgets() debugging
|
|
|
|
newWindowTrigger chan *guiWidget // work around hack to redraw windows a bit after NewWindow()
|
|
|
|
stdout stdout // information for the STDOUT window
|
|
|
|
dropdown dropdown // the dropdown menu
|
2025-02-06 23:49:18 -06:00
|
|
|
textbox dropdown // the textbox popup window
|
2025-02-08 12:50:05 -06:00
|
|
|
clock dropdown // the textbox popup window
|
2025-02-06 14:35:01 -06:00
|
|
|
allwin []*guiWidget // for tracking which window is next
|
2025-02-07 02:34:40 -06:00
|
|
|
dark bool // use a 'dark' color palette
|
2025-02-08 08:07:03 -06:00
|
|
|
mouse mouse // mouse settings
|
2025-02-08 12:50:05 -06:00
|
|
|
showDebug bool // todo: move this into config struct
|
2025-02-08 08:07:03 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// stuff controlling how the mouse works
|
|
|
|
type mouse struct {
|
2025-02-08 08:47:55 -06:00
|
|
|
down time.Time // when the mouse was pressed down
|
|
|
|
up time.Time // when the mouse was released. used to detect click vs drag
|
|
|
|
clicktime time.Duration // how long is too long for a mouse click vs drag
|
|
|
|
mouseUp bool // is the mouse up?
|
|
|
|
double bool // user is double clicking
|
|
|
|
doubletime time.Duration // how long is too long for double click
|
|
|
|
resize bool // mouse is resizing something
|
|
|
|
downW int // where the mouse was pressed down
|
|
|
|
downH int // where the mouse was pressed down
|
|
|
|
currentDrag *guiWidget // what widget is currently being moved around
|
2025-02-06 02:40:44 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// settings for the stdout window
|
|
|
|
type stdout struct {
|
2025-02-06 03:09:13 -06:00
|
|
|
tk *guiWidget // where to show STDOUT
|
2025-02-07 12:42:36 -06:00
|
|
|
wId int // the widget id
|
2025-02-06 03:09:13 -06:00
|
|
|
w int // the width
|
2025-02-06 05:41:51 -06:00
|
|
|
h int // the height
|
2025-02-06 03:09:13 -06:00
|
|
|
outputOnTop bool // is the STDOUT window on top?
|
|
|
|
outputOffscreen bool // is the STDOUT window offscreen?
|
2025-02-08 13:28:19 -06:00
|
|
|
startOnscreen bool // start the output window onscreen?
|
2025-02-06 05:41:51 -06:00
|
|
|
lastW int // the last 'w' location (used to move from offscreen to onscreen)
|
|
|
|
lastH int // the last 'h' location (used to move from offscreen to onscreen)
|
2025-02-07 02:44:52 -06:00
|
|
|
// mouseOffsetW int // the current 'w' offset
|
|
|
|
// mouseOffsetH int // the current 'h' offset
|
|
|
|
init bool // moves the window offscreen on startup
|
|
|
|
outputS []string // the buffer of all the output
|
|
|
|
pager int // allows the user to page through the buffer
|
2025-02-06 05:41:51 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// settings for the dropdown window
|
|
|
|
type dropdown struct {
|
|
|
|
tk *guiWidget // where to show STDOUT
|
|
|
|
callerTK *guiWidget // which widget called the dropdown menu
|
|
|
|
items []string // what is currently in the menu
|
|
|
|
w int // the width
|
|
|
|
h int // the height
|
|
|
|
active bool // is the dropdown menu currently in use?
|
|
|
|
init bool // moves the window offscreen on startup
|
2025-02-06 23:49:18 -06:00
|
|
|
wId int // the widget id to use
|
2025-02-06 05:41:51 -06:00
|
|
|
// dtoggle bool // is a dropdown or combobox currently active?
|
2024-01-18 00:08:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// this is the gocui way
|
|
|
|
// corner starts at in the upper left corner
|
|
|
|
type rectType struct {
|
|
|
|
w0, h0, w1, h1 int // left top right bottom
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *rectType) Width() int {
|
|
|
|
return r.w1 - r.w0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *rectType) Height() int {
|
2025-02-08 14:04:11 -06:00
|
|
|
if r.h0 == 0 && r.h1 == 0 {
|
|
|
|
// edge case. only return 0 for these
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if r.h1 == r.h0 {
|
|
|
|
// if they are equal, it's actually height = 1
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
if r.h1-r.h0 < 1 {
|
|
|
|
// can't have negatives. something is wrong. return 1 for now
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2024-01-18 00:08:37 -06:00
|
|
|
return r.h1 - r.h0
|
|
|
|
}
|
|
|
|
|
2025-02-06 13:47:19 -06:00
|
|
|
// settings that are window specific
|
|
|
|
type window struct {
|
|
|
|
windowFrame *guiWidget // this is the frame for a window widget
|
2025-02-06 14:46:32 -06:00
|
|
|
wasDragged bool // indicates the window was dragged. This keeps it from being rearranged
|
2025-02-06 13:47:19 -06:00
|
|
|
hasTabs bool // does the window have tabs?
|
|
|
|
currentTab bool // the visible tab
|
|
|
|
selectedTab *tree.Node // for a window, this is currently selected tab
|
|
|
|
active bool // means this window is the active one
|
|
|
|
isBG bool // means this is the background widget. There is only one of these
|
|
|
|
order int // what level the window is on
|
2025-02-06 15:19:39 -06:00
|
|
|
resize bool // only set the title once
|
2025-02-06 17:29:17 -06:00
|
|
|
collapsed bool // only show the window title bar
|
2025-02-06 20:34:29 -06:00
|
|
|
dense bool // true if the window is huge
|
2025-02-06 13:47:19 -06:00
|
|
|
}
|
|
|
|
|
2025-02-07 01:55:27 -06:00
|
|
|
type colorT struct {
|
|
|
|
frame gocui.Attribute
|
|
|
|
fg gocui.Attribute
|
|
|
|
bg gocui.Attribute
|
|
|
|
selFg gocui.Attribute
|
|
|
|
selBg gocui.Attribute
|
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
2024-01-18 00:08:37 -06:00
|
|
|
type guiWidget struct {
|
2025-02-01 15:15:05 -06:00
|
|
|
v *gocui.View // this is nil if the widget is not displayed
|
|
|
|
cuiName string // what gocui uses to reference the widget (usually "TK <widgetId>"
|
|
|
|
parent *guiWidget // mirrors the binary node tree
|
|
|
|
children []*guiWidget // mirrors the binary node tree
|
|
|
|
node *tree.Node // the pointer back to the tree
|
2025-02-05 14:11:37 -06:00
|
|
|
windowFrame *guiWidget // this is the frame for a window widget
|
2025-02-05 14:29:38 -06:00
|
|
|
internal bool // indicates the widget is internal to gocui and should be treated differently
|
2025-02-01 15:15:05 -06:00
|
|
|
hasTabs bool // does the window have tabs?
|
|
|
|
currentTab bool // the visible tab
|
2025-02-06 13:47:19 -06:00
|
|
|
window window // holds information specific only to Window widgets
|
2025-02-01 15:15:05 -06:00
|
|
|
value string // ?
|
|
|
|
checked bool // ?
|
|
|
|
labelN string // the actual text to display in the console
|
|
|
|
vals []string // dropdown menu items
|
|
|
|
enable bool // ?
|
|
|
|
gocuiSize rectType // should mirror the real display size. todo: verify these are accurate. they are not yet
|
2025-02-03 10:55:50 -06:00
|
|
|
full rectType // full size of children (used by widget.Window, etc)
|
2025-02-05 12:32:41 -06:00
|
|
|
force rectType // force widget within these boundries (using this to debug window dragging)
|
2025-02-01 15:15:05 -06:00
|
|
|
startW int // ?
|
|
|
|
startH int // ?
|
2025-02-08 09:12:35 -06:00
|
|
|
lastW int // used during mouse dragging
|
|
|
|
lastH int // used during mouse dragging
|
2025-02-01 15:15:05 -06:00
|
|
|
isFake bool // widget types like 'box' are 'false'
|
|
|
|
widths map[int]int // how tall each row in the grid is
|
|
|
|
heights map[int]int // how wide each column in the grid is
|
|
|
|
tainted bool // ?
|
|
|
|
frame bool // ?
|
|
|
|
selectedTab *tree.Node // for a window, this is currently selected tab
|
|
|
|
color *colorT // what color to use
|
2025-02-07 01:55:27 -06:00
|
|
|
defaultColor *colorT // the default colors // TODO: make a function for this instead
|
|
|
|
isBG bool // means this is the background widget. There is only one of these
|
2024-01-18 00:08:37 -06:00
|
|
|
}
|
|
|
|
|
2025-02-01 15:48:28 -06:00
|
|
|
// THIS IS GO COMPILER MAGIC
|
|
|
|
// this sets the `default` in the structs above on init()
|
|
|
|
// this is cool code. thank the GO devs for this code and Alex Flint for explaining it to me
|
2024-01-18 00:08:37 -06:00
|
|
|
func Set(ptr interface{}, tag string) error {
|
|
|
|
if reflect.TypeOf(ptr).Kind() != reflect.Ptr {
|
|
|
|
log.Log(ERROR, "Set() Not a pointer", ptr, "with tag =", tag)
|
|
|
|
return fmt.Errorf("Not a pointer")
|
|
|
|
}
|
|
|
|
|
|
|
|
v := reflect.ValueOf(ptr).Elem()
|
|
|
|
t := v.Type()
|
|
|
|
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
|
|
defaultVal := t.Field(i).Tag.Get(tag)
|
|
|
|
name := t.Field(i).Name
|
|
|
|
// log("Set() try name =", name, "defaultVal =", defaultVal)
|
|
|
|
setField(v.Field(i), defaultVal, name)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func setField(field reflect.Value, defaultVal string, name string) error {
|
|
|
|
|
|
|
|
if !field.CanSet() {
|
|
|
|
// log("setField() Can't set value", field, defaultVal)
|
|
|
|
return fmt.Errorf("Can't set value\n")
|
|
|
|
} else {
|
|
|
|
log.Log(NOW, "setField() Can set value", name, defaultVal)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch field.Kind() {
|
|
|
|
case reflect.Int:
|
|
|
|
val, _ := strconv.Atoi(defaultVal)
|
|
|
|
field.Set(reflect.ValueOf(int(val)).Convert(field.Type()))
|
|
|
|
case reflect.String:
|
|
|
|
field.Set(reflect.ValueOf(defaultVal).Convert(field.Type()))
|
|
|
|
case reflect.Bool:
|
|
|
|
if defaultVal == "true" {
|
|
|
|
field.Set(reflect.ValueOf(true))
|
|
|
|
} else {
|
|
|
|
field.Set(reflect.ValueOf(false))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|