From e0affc82d4b65393a13abf017aa8491a4f9eb36f Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Wed, 25 Jan 2023 16:51:41 -0600 Subject: [PATCH] Commiting for C&C --- ui/list_timers.go | 220 ++++++++++++++++++++++++++++++------ ui/screen_settings.go | 2 +- ui/widdle_addtagtotimers.go | 23 ++++ util/helpers.go | 6 + 4 files changed, 215 insertions(+), 36 deletions(-) diff --git a/ui/list_timers.go b/ui/list_timers.go index 04244f0..362ae49 100644 --- a/ui/list_timers.go +++ b/ui/list_timers.go @@ -1,6 +1,7 @@ package ui import ( + "errors" "fmt" "strings" "time" @@ -39,8 +40,8 @@ type listTimersScreen struct { selected map[int]bool - inputDialog *widdles.InputDialog - filter string + confirm *widdles.ConfirmDialog + filter string choiceMenu *widdles.MenuV tagEditor *PromptForTagWiddle @@ -53,13 +54,12 @@ type listTimersScreen struct { type ListTimersMsg ScreenMsg func NewListTimersScreen(u *Ui) *listTimersScreen { - w, h := termbox.Size() s := listTimersScreen{ - ui: u, - menu: widdles.NewTopMenu(0, 0, 0), - scrollbar: widdles.NewScrollbar(w-2, 2, 1, h-2), - selected: make(map[int]bool), - inputDialog: widdles.NewInputDialog("", ""), + ui: u, + menu: widdles.NewTopMenu(0, 0, 0), + scrollbar: widdles.NewScrollbar(0, 0, 0, 0), + selected: make(map[int]bool), + confirm: widdles.NewConfirmDialog("", ""), choiceMenu: widdles.NewMenuV(0, 0, 0, 0), tagEditor: NewPromptForTagWiddle(0, 0, widdles.AUTO_SIZE, widdles.AUTO_SIZE, "", ""), @@ -78,7 +78,7 @@ func (s *listTimersScreen) Init() wandle.Cmd { settingsOption.SetCommand(s.ui.GotoScreen(SettingsId)) fileMenu.AddOption(settingsOption) quitOption := widdles.NewMenuItem("Quit") - quitOption.Hotkey = termbox.KeyCtrlC + quitOption.SetHotkey(widdles.NewHotkey(termbox.KeyCtrlC)) quitOption.SetCommand(func() wandle.Msg { return wandle.Quit() }) fileMenu.AddOption(quitOption) s.menu.Measure() @@ -87,11 +87,10 @@ func (s *listTimersScreen) Init() wandle.Cmd { s.fullList = timertxt.NewTimerList() s.fullList.AddTimers(s.timerList.GetTimerSlice()) s.fullList.AddTimers(s.doneList.GetTimerSlice()) - s.fullFilterList = s.fullList s.timerFilterList, s.doneFilterList = s.timerList, s.doneList - s.fullFilterList.Sort(timertxt.SORT_START_DATE_DESC) s.timerFilterList.Sort(timertxt.SORT_START_DATE_DESC) s.doneFilterList.Sort(timertxt.SORT_START_DATE_DESC) + s.updateFullFilterList() w, h := termbox.Size() s.choiceMenu.SetBorder(wandle.BRD_CSIMPLE) s.choiceMenu.SetX((w / 2) - 7) @@ -103,6 +102,11 @@ func (s *listTimersScreen) Init() wandle.Cmd { s.tagEditor.SetY(h / 4) s.tagEditor.SetWidth(w / 2) s.tagEditor.SetHeight(h / 2) + s.confirm.EnableHotkeys() + s.confirm.SetX(w / 4) + s.confirm.SetY(h / 4) + s.confirm.SetWidth(w / 2) + s.confirm.SetHeight(h / 2) s.updateFullFilterList() return nil @@ -163,10 +167,13 @@ func (s *listTimersScreen) View(style wandle.Style) { y++ } } - wandle.Print(1, h-1, style, "[A]ctive toggle, [p]roject(+), [c]ontext(@), [t]ags(:)") + selectedStatus := fmt.Sprintf("%s", s.getSelectedTimerDuration()) if len(s.selected) > 0 { - wandle.Print(39, h-1, style, fmt.Sprintf("(%d selected)", len(s.selected))) + selectedStatus = fmt.Sprintf("%s (%d / %d selected)", selectedStatus, len(s.selected), s.fullFilterList.Size()) } + wandle.Print(1, h-2, style, selectedStatus) + help := "[T]oggle Display, [p]roject(+), [c]ontext(@), [t]ags(:), [A]rchive Selected, [Ctrl+A]: Select All/None, [Ctrl+I]: Invert Selection" + wandle.Print(1, h-1, style, help) s.scrollbar.View(style) if s.menu.IsActive() { @@ -178,6 +185,9 @@ func (s *listTimersScreen) View(style wandle.Style) { if s.tagEditor.IsActive() { s.tagEditor.View(style) } + if s.confirm.IsActive() { + s.confirm.View(style) + } wandle.Print(1, h-2, style, s.msg) } @@ -186,11 +196,13 @@ func (s *listTimersScreen) ViewTimer(x, y int, style wandle.Style, tmr *timertxt for _, k := range util.SortedTagKeyList(tmr.AdditionalTags) { tags = append(tags, fmt.Sprintf("%s:%s", k, tmr.AdditionalTags[k])) } - wandle.Print(x, y, style, fmt.Sprintf("%s %s %s %s %s", tmr.StartDate.Format(time.Stamp), tmr.Duration(), tmr.Contexts, tmr.Projects, strings.Join(tags, "; "))) } func (s *listTimersScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd { + if s.confirm.IsActive() { + return s.confirm.Update(msg) + } if s.choiceMenu.IsActive() { return s.choiceMenu.Update(msg) } @@ -219,9 +231,11 @@ func (s *listTimersScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd { if s.cursor < s.fullFilterList.Size()-1 { s.cursor++ } - } else if msg.Ch == 'A' { + } else if msg.Ch == 'T' { s.activeToggle = (s.activeToggle + 1) % activeToggleErr s.updateFullFilterList() + } else if msg.Ch == 'A' { + return s.showArchiveSelected() } else if msg.Key == termbox.KeyArrowUp || msg.Ch == 'k' { if s.cursor > 0 { s.cursor-- @@ -251,47 +265,131 @@ func (s *listTimersScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd { // TODO: Prompt for choice: Add/Edit/Remove ctxts := s.fullList.GetContexts() _ = ctxts + } else if msg.Key == termbox.KeyCtrlA { + if len(s.selected) != s.fullFilterList.Size() { + // Select None + for k := range s.selected { + delete(s.selected, k) + } + } else { + // Select All + for i := 0; i < s.fullFilterList.Size(); i++ { + s.selected[i] = true + } + } + for i := 0; i < s.fullFilterList.Size(); i++ { + if v := s.selected[i]; v { + delete(s.selected, i) + } else { + s.selected[i] = true + } + } + } else if msg.Key == termbox.KeyCtrlI { + for i := 0; i < s.fullFilterList.Size(); i++ { + if v := s.selected[i]; v { + delete(s.selected, i) + } else { + s.selected[i] = true + } + } } } return nil } +func (s *listTimersScreen) showArchiveSelected() wandle.Cmd { + return func() wandle.Msg { + if len(s.selected) > 0 { + s.confirm.SetTitle(fmt.Sprintf("Archive %d Timers?", len(s.selected))) + s.confirm.SetMessage("Are you sure you want to archive these timers?") + } else { + s.confirm.SetTitle("Archive Timer?") + s.confirm.SetMessage("Are you sure you want to archive this timer?") + } + s.confirm.SetOkCommand(func() wandle.Msg { + s.confirm.SetVisible(false) + return s.doArchiveSelected() + }) + s.confirm.SetCancelCommand(func() wandle.Msg { + s.confirm.SetVisible(false) + return wandle.EmptyCmd + }) + s.confirm.SetVisible(true) + return nil + } +} +func (s *listTimersScreen) doArchiveSelected() wandle.Cmd { + return func() wandle.Msg { + selected := len(s.selected) + if selected == 0 { + if s.cursor < s.fullFilterList.Size() { + var selTimer *timertxt.Timer + if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { + + } + } + } else { + for i := range s.selected { + if tmr, err := s.fullFilterList.GetTimer(i); err == nil { + + } + } + } + return nil + } + //return wandle.EmptyCmd +} + func (s *listTimersScreen) showEditTagsChoice() wandle.Cmd { tags := s.getSelectedTimerTags() - var showTagEditor = func(key, val string) wandle.Cmd { + var showTagEditor = func(key, val string, multival bool) wandle.Cmd { return func() wandle.Msg { s.tagEditor.SetTag(key, val) - // TODO: Set Done & Cancel Commands + s.tagEditor.SetDoneCommand(func() wandle.Msg { + s.updateTagOnSelectedTimers(s.tagEditor.GetTag()) + s.tagEditor.Done() + return nil + }) + s.tagEditor.SetCancelCommand(func() wandle.Msg { + s.tagEditor.SetActive(false) + return nil + }) s.choiceMenu.SetActive(false) s.tagEditor.SetActive(true) + s.tagEditor.SetMultiVal(multival) return wandle.EmptyCmd } } s.choiceMenu.SetTitle("") s.choiceMenu.ClearOptions() - addTag := widdles.NewMenuItem("Add New Tag") - addTag.SetCommand(showTagEditor("", "")) + addTag := widdles.NewMenuItem("[A]dd New Tag") + addTag.SetHotkey(widdles.NewHotkeyCh('a')) + addTag.SetCommand(showTagEditor("", "", false)) s.choiceMenu.AddOption(addTag) - editTag := widdles.NewMenuItem("Edit Tag") + editTag := widdles.NewMenuItem("[E]dit Tag") + editTag.SetHotkey(widdles.NewHotkeyCh('e')) editTag.SetEnabled(len(tags) > 0) editTag.SetCommand(func() wandle.Msg { s.choiceMenu.ClearOptions() s.choiceMenu.SetTitle("Choose Tag to Edit") for k, v := range tags { var vals string + var multival bool if len(v) == 1 { vals = v[0] } else { - vals = "..." + vals = "" + multival = true } opt := widdles.NewMenuItem(fmt.Sprintf("%s (%s)", k, vals)) - opt.SetCommand(showTagEditor(k, vals)) + opt.SetCommand(showTagEditor(k, vals, multival)) s.choiceMenu.AddOption(opt) } return wandle.EmptyCmd }) s.choiceMenu.AddOption(editTag) - removeTag := widdles.NewMenuItem("Remove Tag") + removeTag := widdles.NewMenuItem("[R]emove Tag") + removeTag.SetHotkey(widdles.NewHotkeyCh('r')) removeTag.SetCommand(func() wandle.Msg { s.choiceMenu.ClearOptions() s.choiceMenu.SetTitle("Choose Tag to Remove") @@ -330,12 +428,13 @@ func (s *listTimersScreen) gotoSettingsScreen() wandle.Msg { func (s *listTimersScreen) getSelectedTimerTags() map[string][]string { ret := make(map[string][]string) selected := len(s.selected) - var selTimer *timertxt.Timer if selected == 0 { if s.cursor < s.fullFilterList.Size() { - selTimer, s.err = s.fullFilterList.GetTimer(s.cursor) - for k, v := range selTimer.AdditionalTags { - ret[k] = []string{v} + var selTimer *timertxt.Timer + if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { + for k, v := range selTimer.AdditionalTags { + ret[k] = []string{v} + } } } } else { @@ -351,15 +450,47 @@ func (s *listTimersScreen) getSelectedTimerTags() map[string][]string { } return ret } +func (s *listTimersScreen) updateTagOnSelectedTimers(key, val string) { + selected := len(s.selected) + if selected == 0 { + if s.cursor < s.fullFilterList.Size() { + var selTimer *timertxt.Timer + if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { + selTimer.AdditionalTags[key] = val + } + } + } else { + for i := range s.selected { + if tmr, err := s.fullFilterList.GetTimer(i); err == nil { + tmr.AdditionalTags[key] = val + } + } + } + var errText string + if err := s.ui.program.WriteTimerList(); err != nil { + errText = fmt.Sprintf("Errors Writing Lists (%v)", err) + } + if err := s.ui.program.WriteDoneList(); err != nil { + if len(errText) == 0 { + errText = fmt.Sprintf("Errors Writing Lists (%v)", err) + } else { + errText = fmt.Sprintf("%s (%v)", errText, err) + } + } + if len(errText) > 0 { + s.err = errors.New(errText) + } +} func (s *listTimersScreen) getSelectedTimerProjects() []string { var ret []string selected := len(s.selected) - var selTimer *timertxt.Timer if selected == 0 { if s.cursor < s.fullFilterList.Size() { - selTimer, s.err = s.fullFilterList.GetTimer(s.cursor) - for _, v := range selTimer.Projects { - ret = append(ret, v) + var selTimer *timertxt.Timer + if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { + for _, v := range selTimer.Projects { + ret = append(ret, v) + } } } } else { @@ -378,12 +509,13 @@ func (s *listTimersScreen) getSelectedTimerProjects() []string { func (s *listTimersScreen) getSelectedTimerContexts() []string { var ret []string selected := len(s.selected) - var selTimer *timertxt.Timer if selected == 0 { if s.cursor < s.fullFilterList.Size() { - selTimer, s.err = s.fullFilterList.GetTimer(s.cursor) - for _, v := range selTimer.Contexts { - ret = append(ret, v) + var selTimer *timertxt.Timer + if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { + for _, v := range selTimer.Contexts { + ret = append(ret, v) + } } } } else { @@ -399,3 +531,21 @@ func (s *listTimersScreen) getSelectedTimerContexts() []string { } return ret } +func (s *listTimersScreen) getSelectedTimerDuration() time.Duration { + selected := len(s.selected) + if selected == 0 { + if s.cursor < s.fullFilterList.Size() { + var selTimer *timertxt.Timer + if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { + return util.Round(selTimer.Duration()) + } + } + } + var ret time.Duration + for i := range s.selected { + if tmr, err := s.fullFilterList.GetTimer(i); err == nil { + ret = util.AddDurations(ret, util.Round(tmr.Duration())) + } + } + return ret +} diff --git a/ui/screen_settings.go b/ui/screen_settings.go index 87eaab8..0ffeca2 100644 --- a/ui/screen_settings.go +++ b/ui/screen_settings.go @@ -29,7 +29,7 @@ func (s *settingsScreen) Init() wandle.Cmd { // Set up the top menu fileMenu := s.menu.NewSubMenu("File") quitOption := widdles.NewMenuItem("Quit") - quitOption.Hotkey = termbox.KeyCtrlC + quitOption.SetHotkey(widdles.NewHotkey(termbox.KeyCtrlC)) quitOption.SetCommand(func() wandle.Msg { return wandle.Quit() }) fileMenu.AddOption(quitOption) gotoMenu := s.menu.NewSubMenu("Goto") diff --git a/ui/widdle_addtagtotimers.go b/ui/widdle_addtagtotimers.go index 7ab2b3f..600f76b 100644 --- a/ui/widdle_addtagtotimers.go +++ b/ui/widdle_addtagtotimers.go @@ -21,6 +21,8 @@ type PromptForTagWiddle struct { cancelButton *widdles.Button doneButton *widdles.Button + multipleValues bool + msg string } @@ -72,6 +74,13 @@ func (w *PromptForTagWiddle) View(style wandle.Style) { wandle.TitledBorderFilled(title, w.x, w.y, w.x+w.w, w.y+w.h, style, wandle.BRD_CSIMPLE) w.keyInput.View(style) w.valInput.View(style) + if w.multipleValues { + red := wandle.NewStyle( + termbox.RGBToAttribute(uint8(200), uint8(0), uint8(0)), + termbox.RGBToAttribute(uint8(0), uint8(0), uint8(0)), + ) + wandle.Print(w.valInput.GetX(), w.valInput.GetY()+1, red, "Changing a tag on timers that have different values!") + } w.cancelButton.View(style) w.doneButton.View(style) wandle.Print(w.x+1, w.y+w.h-2, style, w.msg) @@ -188,8 +197,22 @@ func (w *PromptForTagWiddle) SetTag(key, val string) { } w.valInput.SetValue(val) } +func (w *PromptForTagWiddle) GetTag() (string, string) { + return w.keyInput.GetValue(), w.valInput.GetValue() +} +func (w *PromptForTagWiddle) SetMultiVal(v bool) { w.multipleValues = v } func (w *PromptForTagWiddle) ClearCancelCommand() { w.SetCancelCommand(wandle.EmptyCmd) } func (w *PromptForTagWiddle) SetCancelCommand(cmd func() wandle.Msg) { w.cancelButton.SetCommand(cmd) } func (w *PromptForTagWiddle) ClearDoneCommand() { w.SetDoneCommand(wandle.EmptyCmd) } func (w *PromptForTagWiddle) SetDoneCommand(cmd func() wandle.Msg) { w.doneButton.SetCommand(cmd) } + +func (w *PromptForTagWiddle) Done() { + w.origKey, w.origVal = "", "" + w.multipleValues = false + w.keyInput.SetValue("") + w.valInput.SetValue("") + w.ClearCancelCommand() + w.ClearDoneCommand() + w.SetActive(false) +} diff --git a/util/helpers.go b/util/helpers.go index e9fb802..a5ad83e 100644 --- a/util/helpers.go +++ b/util/helpers.go @@ -102,6 +102,12 @@ func DurationToDecimal(dur time.Duration) float64 { mins := dur.Minutes() - (dur.Hours() * 60) return dur.Hours() + (mins / 60) } +func AddDurations(dur1, dur2 time.Duration) time.Duration { + return time.Duration(int64(dur1) + int64(dur2)) +} +func SubDurations(dur1, dur2 time.Duration) time.Duration { + return time.Duration(int64(dur1) - int64(dur2)) +} // GetContextsFromSlice pulls all '@' (contexts) out of the // string slice and return those contexts and the remaining