summaryrefslogtreecommitdiff
path: root/structs.go
blob: 97c9dae71998b3b74f6d9ffa7acf96ca10e100f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// Copyright 2017-2025 WIT.COM Inc. All rights reserved.
// Use of this source code is governed by the GPL 3.0

// 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"
	"os"
	"reflect"
	"strconv"
	"sync"
	"time"

	"github.com/awesome-gocui/gocui"

	"go.wit.com/lib/protobuf/guipb"
	log "go.wit.com/log"
	"go.wit.com/toolkits/tree"
	"go.wit.com/widget"
)

var initOnce sync.Once // run initPlugin() only once

// It's probably a terrible idea to call this 'me'
// 2025 note: doesn't seem terrible to call this 'me' anymore. notsure.
var me config

// 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)
type config struct {
	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
	ok               bool            // if the user doesn't hit a key or move the mouse, gocui doesn't really start
	firstWindowOk    bool            // allows the init to wait for the first window from the application
	refresh          bool            // redraw everything?
	ctrlDown         *tree.Node      // shown if you click the mouse when the ctrl key is pressed
	helpLabel        *gocui.View     // ?
	showHelp         bool            // toggle boolean for the help menu (deprecate?)
	FirstWindowW     int             `default:"2"`           // how far over to start window #1
	FirstWindowH     int             `default:"0"`           // how far down to start window #1
	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
	DropdownId       int             `default:"-78"`         // the widget id to use
	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
	textbox          dropdown        // the textbox popup window
	BG               dropdown        // the background widget
	notify           libnotify       // emulates the desktop libnotify menu
	allwin           []*guiWidget    // for tracking which window is next
	dark             bool            // use a 'dark' color palette
	mouse            mouse           // mouse settings
	showDebug        bool            // todo: move this into config struct
	debug            bool            // todo: move this into config struct
	starttime        time.Time       // checks how long it takes on startup
	winchW           int             // used to detect SIGWINCH
	winchH           int             // used to detect SIGWINCH
	outf             *os.File        // hacks for capturing stdout
}

// stuff controlling how the mouse works
type mouse struct {
	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
}

// settings for the stdout window
type stdout struct {
	tk              *guiWidget // where to show STDOUT
	wId             int        // the widget id
	w               int        // the width
	h               int        // the height
	outputOnTop     bool       // is the STDOUT window on top?
	outputOffscreen bool       // is the STDOUT window offscreen?
	startOnscreen   bool       // start the output window onscreen?
	disable         bool       // disable the stdout window. do not change os.Stdout & os.Stderr
	lastW           int        // the last 'w' location (used to move from offscreen to onscreen)
	lastH           int        // the last 'h' location (used to move from offscreen to onscreen)
	init            bool       // moves the window offscreen on startup
	outputS         []string   // the buffer of all the output
	pager           int        // allows the user to page through the buffer
	changed         bool       // indicates the user has changed stdout. gocui should remember the state here
	reverse         bool       // flip the STDOUT upside down so new STDOUT lines are at the top
}

// settings for the dropdown window
type dropdown struct {
	tk       *guiWidget // where to show STDOUT
	callerTK *guiWidget // which widget called the dropdown menu
	items    []string   // what is currently in the menu
	w        int        // the width
	h        int        // the height
	active   bool       // is the dropdown menu currently in use?
	init     bool       // moves the window offscreen on startup
	// Id       int        `default:"-78"` // the widget id to use
	wId int `default:"-78"` // the widget id to use
}

// settings for the dropdown window
type internalTK struct {
	once     sync.Once  // for init
	tk       *guiWidget // where to show STDOUT
	callerTK *guiWidget // which widget called the dropdown menu
	wId      int        // the widget id to use
	active   bool       // is the internal widget currently in use?
	offsetW  int        // width offset
	offsetH  int        // height offset
}

// the desktop libnotify menu
type libnotify struct {
	clock  internalTK // widget for the clock
	icon   internalTK // libnotify menu icon
	window internalTK // the libnotify menu
	help   internalTK // the help menu
}

// this is the gocui way
// 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 {
	if r.h0 == 0 && r.h1 == 0 {
		// edge case. only return 0 for these
		return 0
	}
	if r.h1 == r.h0 {
		// if they are equal, it's actually height = 1
		return 1
	}
	if r.h1-r.h0 < 1 {
		// can't have negatives. something is wrong. return 1 for now
		return 1
	}

	return r.h1 - r.h0
}

// settings that are window specific
type window struct {
	windowFrame *guiWidget // this is the frame for a window widget
	wasDragged  bool       // indicates the window was dragged. This keeps it from being rearranged
	hasTabs     bool       // does the window have tabs?
	currentTab  bool       // the visible tab
	selectedTab *tree.Node // for a window, this is currently selected tab
	active      bool       // means this window is the active one
	order       int        // what level the window is on
	// resize      bool       // only set the title once
	collapsed bool // only show the window title bar
	dense     bool // true if the window is dense
	large     bool // true if the window is huge
	pager     int  // allows the user to page through the window
}

type colorT struct {
	frame gocui.Attribute
	fg    gocui.Attribute
	bg    gocui.Attribute
	selFg gocui.Attribute
	selBg gocui.Attribute
	name  string
}

type guiWidget struct {
	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
	pb           *guipb.Widget     // the guipb Widget
	wtype        widget.WidgetType // used for Tables for now. todo: fix this correctly
	windowFrame  *guiWidget        // this is the frame for a window widget
	internal     bool              // indicates the widget is internal to gocui and should be treated differently
	hasTabs      bool              // does the window have tabs?
	currentTab   bool              // the visible tab
	window       window            // holds information specific only to Window widgets
	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
	full         rectType          // full size of children (used by widget.Window, etc)
	force        rectType          // force widget within these boundries (using this to debug window dragging)
	startW       int               // ?
	startH       int               // ?
	lastW        int               // used during mouse dragging
	lastH        int               // used during mouse dragging
	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
	colorLast    colorT            // the last color the widget had
	defaultColor *colorT           // the default colors // TODO: make a function for this instead
	isTable      bool              // is this a table?
}

// THIS IS GO COMPILER MAGIC
// this sets the `default` in the structs above on init()
// this is cool code. thank the GO devs for this code and Alex Flint for explaining it to me
func Set(ptr interface{}, tag string) error {
	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
}