package main import ( "fmt" "strings" "time" todotxt "github.com/br0xen/go-todotxt" "github.com/br0xen/termbox-util" termbox "github.com/nsf/termbox-go" ) // MainScreen holds all that's going on type MainScreen struct { viewPort *ViewPort message string messageTimeout time.Duration messageTime time.Time mode int inputField *termboxUtil.InputField currentList string currentFilter string activeList todotxt.TaskList displayList []todotxt.Task undoQueue []string redoQueue []string backspaceDoes int } const ( MainBundleListKey = "mainscreen.list" MainBundleFilterKey = "mainscreen.filter" MainBundleListTodo = "mainscreen.list.todo" MainBundleListDone = "mainscreen.list.done" MainModalIdFilter = "mainscreen.filter" MainModalIdAddTask = "mainscreen.addtask" MainBackspaceNothing = iota MainBackspaceMain MainBackspaceFilter ) func (screen *MainScreen) initialize(bundle Bundle) error { if err := screen.reloadList(bundle); err != nil { return err } width, height := termbox.Size() screen.inputField = termboxUtil.CreateInputField(0, (height - 3), width, 1, DefaultFg, DefaultBg) screen.inputField.SetID("") screen.inputField.SetBordered(false) return nil } func (screen *MainScreen) reloadList(bundle Bundle) error { screen.displayList = []todotxt.Task{} screen.currentList = bundle.getString(MainBundleListKey, MainBundleListTodo) switch screen.currentList { case MainBundleListTodo: screen.setActiveList(*app.TaskList) if screen.currentFilter = bundle.getString(MainBundleFilterKey, ""); screen.currentFilter != "" { filteredList := app.filterList(&screen.activeList, screen.currentFilter) for _, av := range screen.activeList { for _, fv := range *filteredList { if av.String() == fv.String() { screen.displayList = append(screen.displayList, av) break } } } } else { for _, av := range screen.activeList { screen.displayList = append(screen.displayList, av) } } case MainBundleListDone: if err := app.LoadDoneList(); err != nil { return err } screen.setActiveList(*app.DoneList) if screen.currentFilter = bundle.getString(MainBundleFilterKey, ""); screen.currentFilter != "" { filteredList := app.filterList(&screen.activeList, screen.currentFilter) for _, av := range screen.activeList { for _, fv := range *filteredList { if av.String() == fv.String() { screen.displayList = append(screen.displayList, av) break } } } } else { for _, av := range screen.activeList { screen.displayList = append(screen.displayList, av) } } } return nil } func (screen *MainScreen) handleKeyEvent(event termbox.Event) int { if screen.inputField.GetID() != "" { return screen.handleInputKeyEvent(event) } if event.Ch == '?' { // Go to About Screen return ScreenAbout } else if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 { if screen.backspaceDoes == MainBackspaceNothing { if screen.currentList == MainBundleListDone { screen.reloadList(screen.buildBundle(MainBundleListTodo, screen.currentFilter)) } else if screen.currentFilter != "" { screen.reloadList(screen.buildBundle(screen.currentList, "")) } } else if screen.backspaceDoes == MainBackspaceMain { screen.reloadList(screen.buildBundle(MainBundleListTodo, screen.currentFilter)) } else if screen.backspaceDoes == MainBackspaceFilter { screen.reloadList(screen.buildBundle(screen.currentList, "")) } return ScreenMain } else if event.Key == termbox.KeySpace { return screen.toggleTaskComplete() } else if event.Ch == 'L' { return screen.toggleViewList() } else if event.Ch == 'a' { return screen.startAddNewTask() } else if event.Ch == 'e' || event.Ch == 'l' || event.Key == termbox.KeyEnter { return screen.startEditTaskScreen() } else if event.Ch == 'j' || event.Key == termbox.KeyArrowDown { screen.moveCursorDown() } else if event.Ch == 'k' || event.Key == termbox.KeyArrowUp { screen.moveCursorUp() } else if event.Ch == '/' { screen.startFilter() } else if event.Ch == 'D' { screen.archiveCurrentItem() } else if event.Ch == 'q' { return ScreenExit } return ScreenMain } func (screen *MainScreen) handleConfirmKeyEvent(event termbox.Event) int { return ScreenMain } func (screen *MainScreen) handleInputKeyEvent(event termbox.Event) int { id := screen.inputField.GetID() if id == "filter" { if event.Key == termbox.KeyEnter { // Apply the filter filter := screen.inputField.GetValue() screen.inputField.SetID("") screen.inputField.SetValue("") screen.backspaceDoes = MainBackspaceFilter screen.reloadList(screen.buildBundle(screen.currentList, filter)) return ScreenMain } } else if id == "add task" { if event.Key == termbox.KeyEnter { // Create the new item err := app.addTask(screen.inputField.GetValue()) if err != nil { screen.setMessage(err.Error()) } screen.inputField.SetID("") screen.inputField.SetValue("") if err = app.WriteList(); err != nil { screen.setMessage(err.Error()) return ScreenMain } screen.reloadList(screen.buildBundle(screen.currentList, screen.currentFilter)) return ScreenMain } } if event.Key == termbox.KeyBackspace || event.Key == termbox.KeyBackspace2 { if screen.inputField.GetValue() == "" { screen.reloadList(screen.buildBundle(screen.currentList, screen.inputField.GetValue())) screen.inputField.SetID("") screen.inputField.SetValue("") return ScreenMain } } screen.inputField.HandleEvent(event) return ScreenMain } func (screen *MainScreen) setActiveList(list todotxt.TaskList) { screen.activeList = todotxt.NewTaskList() for _, v := range list { screen.activeList.AddTask(&v) } } func (screen *MainScreen) drawScreen() { if screen.message == "" { screen.setMessageWithTimeout("Press '?' for help", -1) } screen.drawHeader() topId := 0 for _, v := range screen.displayList { if v.Id > topId { topId = v.Id } } padCnt := fmt.Sprintf("%d", topId) for k, v := range screen.displayList { pad := strings.Repeat(" ", len(padCnt)-len(fmt.Sprintf("%d", v.Id))) useFg, useBg := DefaultFg, DefaultBg if k == screen.viewPort.cursor { useFg, useBg = CursorFg, CursorBg } termboxUtil.DrawStringAtPoint(pad+app.getTaskString(v), 0, k+1, useFg, useBg) } screen.drawFooter() } func (screen *MainScreen) drawHeader() { width, _ := termbox.Size() headerString := screen.currentFilter if headerString == "" { if screen.currentList == MainBundleListTodo { headerString = "Todo List" } else if screen.currentList == MainBundleListDone { headerString = "Done List" } } spaces := strings.Repeat(" ", ((width-len(headerString))/2)+1) termboxUtil.DrawStringAtPoint(fmt.Sprintf("%s%s%s", spaces, headerString, spaces), 0, 0, TitleFg, TitleBg) } func (screen *MainScreen) drawFooter() { if screen.messageTimeout > 0 && time.Since(screen.messageTime) > screen.messageTimeout { screen.clearMessage() } _, height := termbox.Size() if screen.inputField.GetID() != "" { termboxUtil.DrawStringAtPoint(screen.inputField.GetID()+": ", 0, height-2, DefaultFg, DefaultBg) screen.inputField.Draw() } // And the 'message' termboxUtil.DrawStringAtPoint(screen.message, 0, height-1, DefaultFg, DefaultBg) } func (screen *MainScreen) archiveCurrentItem() int { if screen.currentList != MainBundleListTodo { screen.setMessage("Task is already archived") return ScreenMain } // Find the task under the cursor if len(screen.displayList) > screen.viewPort.cursor { t := screen.displayList[screen.viewPort.cursor] if err := app.archiveTask(t.Id); err != nil { screen.setMessage(err.Error()) return ScreenMain } if err := app.WriteList(); err != nil { screen.setMessage(err.Error()) return ScreenMain } // Reload the list b := screen.buildBundle(screen.currentList, screen.currentFilter) if err := screen.reloadList(b); err != nil { screen.setMessage(err.Error()) } } return ScreenMain } func (screen *MainScreen) startEditTaskScreen() int { // Find the task under the cursor if len(screen.displayList) > screen.viewPort.cursor { t := screen.displayList[screen.viewPort.cursor] // Load the task screen with this task b := Bundle{} b.setValue(TaskBundleTaskIdKey, t.Id) if err := app.screens[ScreenTask].initialize(b); err != nil { screen.setMessage(err.Error()) } return ScreenTask } return ScreenMain } func (screen *MainScreen) reloadCurrentView() { bundle := Bundle{} bundle.setValue(MainBundleListKey, screen.currentList) bundle.setValue(MainBundleFilterKey, screen.currentFilter) screen.initialize(bundle) } func (screen *MainScreen) toggleViewList() int { bundle := Bundle{} if screen.currentList == MainBundleListTodo { bundle.setValue(MainBundleListKey, MainBundleListDone) screen.backspaceDoes = MainBackspaceMain } else { bundle.setValue(MainBundleListKey, MainBundleListTodo) } bundle.setValue(MainBundleFilterKey, screen.currentFilter) screen.initialize(bundle) return ScreenMain } func (screen *MainScreen) startAddNewTask() int { screen.inputField.SetID("add task") screen.inputField.SetX(len(screen.inputField.GetID()) + 2) return ScreenMain } func (screen *MainScreen) toggleTaskComplete() int { // Find the task under the cursor if len(screen.displayList) > screen.viewPort.cursor { t := screen.displayList[screen.viewPort.cursor] err := app.toggleTaskComplete(t.Id) if err != nil { screen.setMessage(err.Error()) return ScreenMain } if err = app.WriteList(); err != nil { screen.setMessage(err.Error()) } } screen.reloadCurrentView() return ScreenMain } func (screen *MainScreen) moveCursorDown() bool { screen.viewPort.cursor++ if screen.viewPort.cursor >= len(screen.displayList) { screen.viewPort.cursor = len(screen.displayList) - 1 return false } return true } func (screen *MainScreen) moveCursorUp() bool { screen.viewPort.cursor-- if screen.viewPort.cursor < 0 { screen.viewPort.cursor = 0 return false } return true } func (screen *MainScreen) startFilter() { screen.inputField.SetID("filter") screen.inputField.SetX(len(screen.inputField.GetID()) + 2) } func (screen *MainScreen) setMessage(msg string) { screen.message = msg screen.messageTime = time.Now() screen.messageTimeout = time.Second * 2 } /* setMessageWithTimeout lets you specify the timeout for the message * setting it to -1 means it won't timeout */ func (screen *MainScreen) setMessageWithTimeout(msg string, timeout time.Duration) { screen.message = msg screen.messageTime = time.Now() screen.messageTimeout = timeout } func (screen *MainScreen) clearMessage() { screen.message = "" screen.messageTimeout = -1 } func (screen *MainScreen) buildBundle(list, filter string) Bundle { bundle := Bundle{} bundle.setValue(MainBundleListKey, list) bundle.setValue(MainBundleFilterKey, filter) return bundle }