master
1package main
2
3import (
4 "fmt"
5 "github.com/jroimartin/gocui"
6 "log"
7 "os"
8 "strings"
9)
10
11type ScreenView struct {
12 MainGui *gocui.Gui
13 MAIN_WINDOW_NAME string
14 USER_ENTRY_NAME string
15 USER_ENTRY_HEIGHT int
16 COMMAND_PALLET_WIDTH int
17 COMMAND_PALLET_NAME string
18 STATUS_BAR_HEIGHT int
19 STATUS_BAR_NAME string
20}
21
22var ApplicationView ScreenView
23var readUserInputSemaphore *CountingSemaphore
24
25// displayStringToMainScreen prints the given string
26// to the primary string used by the application
27func displayStringToMainScreen(str string) {
28 if ApplicationView.MainGui == nil {
29 return
30 }
31 view, _ := ApplicationView.MainGui.View(ApplicationView.MAIN_WINDOW_NAME)
32 view.Write([]byte(str))
33 return
34}
35
36// updateMainView causes the main view to update
37// the display
38func updateMainView() {
39 ApplicationView.MainGui.Execute(func(arg1 *gocui.Gui) error {
40 if ApplicationView.MainGui == nil {
41 return nil
42 }
43 for _, view := range ApplicationView.MainGui.Views() {
44 view.Write([]byte(""))
45 }
46 return nil
47 })
48}
49
50// updateMainView causes the main view to update
51// the display
52func setStatusBar(status string) {
53 ApplicationView.MainGui.Execute(func(arg1 *gocui.Gui) error {
54 if ApplicationView.MainGui == nil {
55 return nil
56 }
57 if view, err := ApplicationView.MainGui.View(ApplicationView.STATUS_BAR_NAME); err == nil {
58 view.Clear()
59 view.Write([]byte(status))
60 }
61 return nil
62 })
63}
64
65// reads input from the user in the main screen
66func readInputFromMainScreen() (string, error) {
67 if ApplicationView.MainGui == nil {
68 return "", nil
69 }
70 v, err := ApplicationView.MainGui.View(ApplicationView.USER_ENTRY_NAME)
71 if err != nil {
72 return "", err
73 }
74 buffer := string(v.ViewBuffer())
75 v.Clear()
76 v.MoveCursor(-len(buffer), 0, true)
77 return buffer, err
78}
79
80// initView initializes the view for the application
81// see https://github.com/jroimartin/gocui/blob/master/_examples/active.go
82// for inspiration
83func initView() error {
84 var err error
85 ApplicationView.MainGui, err = gocui.NewGui(gocui.OutputNormal)
86 if err != nil {
87 return err
88 }
89 ApplicationView.MainGui.Cursor = true
90 ApplicationView.MainGui.Highlight = true
91 ApplicationView.MainGui.SetManagerFunc(manageLayout)
92 ApplicationView.MainGui.SelFgColor = gocui.ColorWhite
93 if err = ApplicationView.MainGui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
94 log.Printf("%+v", err)
95 }
96 return err
97}
98
99// quit breaks out of the main program loop to allow the application to exit
100func quit(g *gocui.Gui, v *gocui.View) error {
101 return gocui.ErrQuit
102}
103
104func initMainView(g *gocui.Gui, maxX int, maxY int) error {
105 // Main View
106 v, err := g.SetView(ApplicationView.MAIN_WINDOW_NAME,
107 ApplicationView.COMMAND_PALLET_WIDTH+1, 0+1, //x0, y0
108 maxX-5, maxY-(ApplicationView.STATUS_BAR_HEIGHT+ApplicationView.USER_ENTRY_HEIGHT)-3) // x1, y1
109 if err != nil {
110 if err != gocui.ErrUnknownView {
111 return err
112 }
113 v.Title = "Shadow Quiz"
114 v.Autoscroll = true
115 v.Wrap = true
116 }
117 return nil
118}
119
120func initCommandView(g *gocui.Gui, maxX int, maxY int) error {
121 // Command Palette
122 v, err := g.SetView(ApplicationView.COMMAND_PALLET_NAME,
123 0, 0+1,
124 ApplicationView.COMMAND_PALLET_WIDTH+1, maxY-1-ApplicationView.STATUS_BAR_HEIGHT)
125 if err != nil {
126 if err != gocui.ErrUnknownView {
127 return err
128 }
129 v.Title = "Commands"
130 v.Editable = false
131 for _, command := range commandArray {
132 v.Write([]byte(command.Command + "\n"))
133 }
134 v.Write([]byte("↑/↓"))
135 }
136 return nil
137}
138
139func initProgressView(g *gocui.Gui, maxX int, maxY int) error {
140 //Progress Bar
141 v, err := g.SetView(ApplicationView.STATUS_BAR_NAME,
142 0, maxY-ApplicationView.STATUS_BAR_HEIGHT-3,
143 maxX-5, maxY-2)
144 if err != nil {
145 if err != gocui.ErrUnknownView {
146 return err
147 }
148 v.Title = "Status"
149 v.Editable = false
150 }
151 return nil
152}
153
154func initUserEntryView(g *gocui.Gui, maxX int, maxY int) error {
155 // User Input View
156 v, err := g.SetView(ApplicationView.USER_ENTRY_NAME,
157 ApplicationView.COMMAND_PALLET_WIDTH+1, maxY-
158 (ApplicationView.STATUS_BAR_HEIGHT+ApplicationView.USER_ENTRY_HEIGHT)-3, //x0, y0
159 maxX-5, maxY-ApplicationView.STATUS_BAR_HEIGHT-3) // x1, y1
160 if err != nil {
161 if err != gocui.ErrUnknownView {
162 return err
163 }
164 v.Editable = true
165 v.Wrap = true
166 v.Autoscroll = true
167 _, err = ApplicationView.MainGui.SetCurrentView(ApplicationView.USER_ENTRY_NAME)
168 if err != nil {
169 log.Printf("Could not set current view: %+v", err)
170 }
171 _, err = ApplicationView.MainGui.SetViewOnTop(ApplicationView.USER_ENTRY_NAME)
172 if err != nil {
173 log.Printf("Could not set top view: %+v", err)
174 }
175 v.Clear()
176 }
177 return nil
178}
179
180// manageLayout handles drawing the layout for the application
181// the magic numbers come from somewhere, and they make things
182// do stuff
183func manageLayout(g *gocui.Gui) error {
184 maxX, maxY := g.Size()
185 if err := initMainView(g, maxX, maxY); err != nil {
186 return err
187 }
188 if err := initCommandView(g, maxX, maxY); err != nil {
189 return err
190 }
191 if err := initProgressView(g, maxX, maxY); err != nil {
192 return err
193 }
194 if err := initUserEntryView(g, maxX, maxY); err != nil {
195 return err
196 }
197 return nil
198}
199
200// initUserSession starts the interactive prompt for the user
201func initUserSession() {
202 var err error
203 if clientConfig.USER == "" {
204 displayStringToMainScreen("\nUsername is blank. Setting to 'anonymous.' Use 'user' to change user name.")
205 clientConfig.USER = "anonymous"
206 }
207 err = initView()
208 if err != nil {
209 log.Printf("\nFailed to initiate user screen: %+v", err)
210 os.Exit(EXIT_CODE.BAD_CONFIG)
211 }
212 defer ApplicationView.MainGui.Close()
213 if err = ApplicationView.MainGui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
214 log.Printf("\nFailed to bind key: %+v", err)
215 }
216 if err = ApplicationView.MainGui.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, handleEnterKeyPress); err != nil {
217 log.Printf("\nFailed to bind key: %+v", err)
218 }
219 if err = ApplicationView.MainGui.SetKeybinding("", gocui.KeyArrowUp, gocui.ModNone,
220 func(g *gocui.Gui, v *gocui.View) error {
221 view, _ := ApplicationView.MainGui.View(ApplicationView.MAIN_WINDOW_NAME)
222 scrollView(view, -1)
223 return nil
224 }); err != nil {
225 log.Printf("\nFailed to bind key: %+v", err)
226 }
227 if err = ApplicationView.MainGui.SetKeybinding("", gocui.KeyArrowDown, gocui.ModNone,
228 func(g *gocui.Gui, v *gocui.View) error {
229 view, _ := ApplicationView.MainGui.View(ApplicationView.MAIN_WINDOW_NAME)
230 scrollView(view, 1)
231 return nil
232 }); err != nil {
233 log.Printf("\nFailed to bind key: %+v", err)
234 }
235 if err = ApplicationView.MainGui.MainLoop(); err != nil && err != gocui.ErrQuit {
236 log.Printf("Error from CUI: %+v", err)
237 exitApplication(Command{})
238 }
239}
240
241// scrollView scrolls the view buffer for the specified view
242// see https://github.com/jroimartin/gocui/blob/master/_examples/stdin.go
243func scrollView(v *gocui.View, dy int) error {
244 if v != nil {
245 v.Autoscroll = false
246 ox, oy := v.Origin()
247 if err := v.SetOrigin(ox, oy+dy); err != nil {
248 return err
249 }
250 }
251 return nil
252}
253
254// clearScreen clears the screen of all content in the main
255// window
256func clearScreen(command Command) error {
257 if ApplicationView.MainGui != nil {
258 if view, err := ApplicationView.MainGui.View(ApplicationView.MAIN_WINDOW_NAME); err != nil {
259 return fmt.Errorf("Error clearing screen: %+v", err)
260 } else {
261 view.Clear()
262 view.SetOrigin(0, 0)
263 return nil
264 }
265 }
266 return fmt.Errorf("No application window.")
267}
268
269// handleEnterKeyPress parses the input supplied by the user after
270// pressing 'Enter'
271func handleEnterKeyPress(g *gocui.Gui, v *gocui.View) error {
272 view, err := ApplicationView.MainGui.View(ApplicationView.MAIN_WINDOW_NAME)
273 view.Autoscroll = true
274 if readUserInputSemaphore.Queued() > 0 {
275 readUserInputSemaphore.V() //taken whenever user input is required
276 return nil
277 }
278 var input string
279 var command Command
280 setStatusBar("")
281 input, err = readInputFromMainScreen()
282 if err != nil {
283 log.Printf("Error reading input: %+v", err)
284 setStatusBar(fmt.Sprintf(COLOR_RED+"Error reading input: %+v"+COLOR_RESET, err))
285 return nil
286 }
287 args := strings.Fields(input)
288 if len(args) < 1 { // skip the empty strings
289 return nil
290 }
291 // select a command from the list of available commands
292 if command, err = SelectCommand(strings.TrimSpace(args[0]), commandArray); err != nil {
293 log.Printf("Error while parsing command: %+v", err)
294 setStatusBar(fmt.Sprintf(COLOR_RED+"Error while parsing command: %+v"+COLOR_RESET, err))
295 return nil
296 }
297 // parse the flags for the command
298 err = ParseUserCommands(args[1:], &command)
299 if err != nil {
300 log.Printf("Error while parsing parameters: %+v", err)
301 setStatusBar(fmt.Sprintf(COLOR_RED+"Error while parsing parameters: %+v"+COLOR_RESET, err))
302 return nil
303 }
304 // execute the command and check for any errors that may have occurred
305 go func() { // must be separate function for the calls that block
306 // on user input
307 if err = command.Run(command); err != nil {
308 log.Printf("Error while executing command: %+v", err)
309 setStatusBar(fmt.Sprintf(COLOR_RED+"Error while executing command: %+v"+COLOR_RESET, err))
310 }
311 updateMainView()
312 }()
313 return nil
314}
315
316// initializes the values for the ApplicationView
317func init() {
318 ApplicationView = ScreenView{}
319 ApplicationView.MAIN_WINDOW_NAME = "mainwindow"
320 ApplicationView.USER_ENTRY_NAME = "userentry"
321 ApplicationView.USER_ENTRY_HEIGHT = 2
322 ApplicationView.COMMAND_PALLET_WIDTH = 20
323 ApplicationView.COMMAND_PALLET_NAME = "commandpallet"
324 ApplicationView.STATUS_BAR_HEIGHT = 1
325 ApplicationView.STATUS_BAR_NAME = "statusbar"
326 readUserInputSemaphore = &CountingSemaphore{}
327 readUserInputSemaphore.SetCapacity(0)
328}