master
Raw Download raw file
  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}