Config view: Test connections below the service settings; hide a stale cursor
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -175,6 +175,10 @@ func New(cfg *config.Config) *App {
|
||||
AddItem(a.status, 1, 0, false)
|
||||
|
||||
a.app.SetBeforeDrawFunc(func(s tcell.Screen) bool {
|
||||
// A focused text field shows the cursor again while it is drawn. Hiding it
|
||||
// first covers focus moves that bypass tview (arrow keys in forms), which
|
||||
// would leave the cursor behind in the field that was left.
|
||||
s.HideCursor()
|
||||
a.screen = s
|
||||
a.cellAspect = cellAspectOf(s)
|
||||
return false
|
||||
|
||||
@@ -262,3 +262,56 @@ func (h *formHeading) Draw(screen tcell.Screen) {
|
||||
x, y, w, _ := h.GetInnerRect()
|
||||
tview.Print(screen, "[::b]"+tview.Escape(h.title)+"[::-]", x, y+1, w, tview.AlignLeft, tcell.ColorYellow)
|
||||
}
|
||||
|
||||
// formAction is a button inside a form's list of fields, so it can sit between
|
||||
// sections instead of at the end. Enter or Space presses it.
|
||||
type formAction struct {
|
||||
*tview.Button
|
||||
labelWidth int
|
||||
finished func(tcell.Key)
|
||||
}
|
||||
|
||||
func newFormAction(label string, pressed func()) *formAction {
|
||||
a := &formAction{Button: tview.NewButton(label)}
|
||||
a.SetSelectedFunc(pressed)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *formAction) GetLabel() string { return "" } // the button text is its own label
|
||||
|
||||
func (a *formAction) SetFormAttributes(labelWidth int, _, _, _, _ tcell.Color) tview.FormItem {
|
||||
a.labelWidth = labelWidth
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *formAction) GetFieldWidth() int { return tview.TaggedStringWidth(a.GetLabel()) + 4 }
|
||||
|
||||
// GetFieldHeight is 2: an empty line above the button.
|
||||
func (a *formAction) GetFieldHeight() int { return 2 }
|
||||
|
||||
func (a *formAction) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
|
||||
a.SetExitFunc(handler) // Tab, Shift+Tab and Esc
|
||||
a.finished = handler
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *formAction) SetDisabled(disabled bool) tview.FormItem {
|
||||
a.Button.SetDisabled(disabled)
|
||||
return a
|
||||
}
|
||||
|
||||
// SetRect places the button under the field column, one line down, as wide as its text.
|
||||
func (a *formAction) SetRect(x, y, width, height int) {
|
||||
w := min(max(width-a.labelWidth, 0), tview.TaggedStringWidth(a.Button.GetLabel())+4)
|
||||
a.Button.SetRect(x+a.labelWidth, y+1, w, 1)
|
||||
}
|
||||
|
||||
func (a *formAction) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
inner := a.Button.InputHandler()
|
||||
return func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
|
||||
if event.Key() == tcell.KeyRune && event.Rune() == ' ' {
|
||||
event = tcell.NewEventKey(tcell.KeyEnter, 0, 0)
|
||||
}
|
||||
inner(event, setFocus)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -67,10 +67,11 @@ var helpSections = []helpSection{
|
||||
{"r", "refresh now"},
|
||||
}},
|
||||
{title: "Config (F6)", note: "Edit the settings of the config file: service addresses and API keys, add-form defaults, refresh interval, posters, notifications.", items: []helpItem{
|
||||
{"↑↓", "move through all fields, one below the other, in the sections Radarr, Sonarr, SABnzbd and Interface; ↓ after the last field reaches the buttons"},
|
||||
{"Tab", "jump between the fields and the buttons"},
|
||||
{"Space", "toggle a checkbox"},
|
||||
{"Enter", "press a button: Test connections checks the three services with the values typed in; Save writes the file and applies everything at once; Revert discards edits"},
|
||||
{"↑↓", "move through all fields, one below the other, in the sections Radarr, Sonarr, SABnzbd and Interface; ↓ after the last field reaches Save and Revert"},
|
||||
{"Tab", "jump between the fields and Save / Revert"},
|
||||
{"Space", "toggle a checkbox; press a button"},
|
||||
{"Test connections", "(below the SABnzbd settings) checks the three services with the values typed in"},
|
||||
{"Save, Revert", "(at the bottom) Save writes the file and applies everything at once; Revert discards edits"},
|
||||
{"q", "is typed here as text (it does not quit); F9 opens this help"},
|
||||
}},
|
||||
{title: "Log (F7)", note: "Popups fade away by themselves; everything they said is kept here.", items: []helpItem{
|
||||
|
||||
@@ -102,10 +102,11 @@ type settingsView struct {
|
||||
app *App
|
||||
root *tview.Flex
|
||||
|
||||
form *tview.Form // every field, one below the other, with section headings
|
||||
actions *tview.Form // the buttons below it
|
||||
groups []*tview.Form // keyboard order: the fields, then the buttons
|
||||
status *tview.TextView
|
||||
form *tview.Form // every field, one below the other, with section headings
|
||||
testItem *formAction // the Test connections button, below the service settings
|
||||
actions *tview.Form // the buttons below it
|
||||
groups []*tview.Form // keyboard order: the fields, then the buttons
|
||||
status *tview.TextView
|
||||
|
||||
inputs map[string]*tview.InputField // by setting key, e.g. "radarr.url"
|
||||
refresh *tview.InputField
|
||||
@@ -120,7 +121,7 @@ type settingsView struct {
|
||||
}
|
||||
|
||||
func settingsHints() string {
|
||||
return "↑↓ move through the fields · Space toggles · Enter presses a button · Tab jumps between fields and buttons"
|
||||
return "↑↓ move through the fields · Space toggles or presses a button · Tab jumps between fields and Save/Revert"
|
||||
}
|
||||
|
||||
func newSettingsView(app *App) *settingsView {
|
||||
@@ -158,6 +159,9 @@ func newSettingsView(app *App) *settingsView {
|
||||
service("Radarr", "radarr", true)
|
||||
service("Sonarr", "sonarr", true)
|
||||
service("SABnzbd", "sabnzbd", false)
|
||||
// Right below the connection settings it checks.
|
||||
v.testItem = newFormAction("Test connections", v.test)
|
||||
f.AddFormItem(v.testItem)
|
||||
|
||||
f.AddFormItem(newFormHeading("Interface"))
|
||||
v.refresh = tview.NewInputField().SetLabel("Downloads refresh (seconds)").SetFieldWidth(6)
|
||||
@@ -177,7 +181,6 @@ func newSettingsView(app *App) *settingsView {
|
||||
v.actions = tview.NewForm().SetItemPadding(0)
|
||||
v.actions.SetBorder(true).SetTitle(" Actions ")
|
||||
v.actions.SetBorderPadding(0, 0, 1, 1)
|
||||
v.actions.AddButton("Test connections", v.test)
|
||||
v.actions.AddButton("Save", v.save)
|
||||
v.actions.AddButton("Revert", v.revert)
|
||||
|
||||
|
||||
@@ -272,3 +272,43 @@ func TestFormHeadingNeverTakesFocus(t *testing.T) {
|
||||
t.Error("a heading got the focus")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestConnectionsButtonSitsBelowTheServices(t *testing.T) {
|
||||
a := newTestApp(t)
|
||||
v := a.settings
|
||||
send := func(g *tview.Form, k tcell.Key) { g.GetInputCapture()(tcell.NewEventKey(k, 0, 0)) }
|
||||
focusedItem := func() tview.FormItem { i, _ := v.form.GetFocusedItemIndex(); return v.form.GetFormItem(i) }
|
||||
|
||||
// It comes right after the SABnzbd API key and before the Interface section.
|
||||
v.focusGroup(0, false)
|
||||
for i := 0; i < 10; i++ { // Radarr 4, Sonarr 4, SABnzbd 2 fields
|
||||
send(v.form, tcell.KeyDown)
|
||||
}
|
||||
if focusedItem() != tview.FormItem(v.testItem) {
|
||||
t.Fatalf("↓ after the SABnzbd API key reached %T %q, want the Test connections button", focusedItem(), focusedItem().GetLabel())
|
||||
}
|
||||
send(v.form, tcell.KeyDown)
|
||||
if got := focusedItem().GetLabel(); got != "Downloads refresh (seconds)" {
|
||||
t.Errorf("after the button comes %q", got)
|
||||
}
|
||||
send(v.form, tcell.KeyUp)
|
||||
if focusedItem() != tview.FormItem(v.testItem) {
|
||||
t.Error("↑ must come back to the button")
|
||||
}
|
||||
|
||||
// Save and Revert stay below everything; Test connections is not among them.
|
||||
if v.actions.GetButtonCount() != 2 || v.actions.GetButton(0).GetLabel() != "Save" || v.actions.GetButton(1).GetLabel() != "Revert" {
|
||||
t.Errorf("actions bar has %d buttons", v.actions.GetButtonCount())
|
||||
}
|
||||
|
||||
// Space and Enter press it.
|
||||
var focus func(p tview.Primitive)
|
||||
focus = func(p tview.Primitive) { p.Focus(focus) }
|
||||
for _, ev := range []*tcell.EventKey{tcell.NewEventKey(tcell.KeyRune, ' ', 0), tcell.NewEventKey(tcell.KeyEnter, 0, 0)} {
|
||||
v.describe()
|
||||
v.testItem.InputHandler()(ev, focus)
|
||||
if !strings.Contains(v.status.GetText(false), "Testing the connections") {
|
||||
t.Errorf("key %v did not start the test: %q", ev.Name(), v.status.GetText(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user