Add support for read only form items

When a form item is readonly, any input events or mouse events that
would cause its value to change are ignored.

Read only items are rendered when text color that matches the label
color to distinguish them from items that accept input.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
pull/449/head
Daniel P. Berrangé 4 years ago
parent 823f280c54
commit 78031b278f

@ -30,6 +30,12 @@ type Checkbox struct {
// The text color of the input area.
fieldTextColor tcell.Color
// The background color of the input area when readonly.
fieldBackgroundReadOnlyColor tcell.Color
// The text color of the input area when readonly.
fieldTextReadOnlyColor tcell.Color
// An optional function which is called when the user changes the checked
// state of this checkbox.
changed func(checked bool)
@ -42,15 +48,21 @@ type Checkbox struct {
// A callback function set by the Form class and called when the user leaves
// this form item.
finished func(tcell.Key)
// If set the item does not respond to form input/mouse events
// that would change its contents
readonly bool
}
// NewCheckbox returns a new input field.
func NewCheckbox() *Checkbox {
return &Checkbox{
Box: NewBox(),
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
Box: NewBox(),
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
fieldTextReadOnlyColor: Styles.SecondaryTextColor,
fieldBackgroundReadOnlyColor: Styles.ContrastBackgroundColor,
}
}
@ -65,6 +77,13 @@ func (c *Checkbox) IsChecked() bool {
return c.checked
}
// SetReadOnly sets whether the item responds to input
// or mouse events that would change its contents
func (c *Checkbox) SetReadOnly(readonly bool) *Checkbox {
c.readonly = readonly
return c
}
// SetLabel sets the text to be displayed before the input area.
func (c *Checkbox) SetLabel(label string) *Checkbox {
c.label = label
@ -101,6 +120,18 @@ func (c *Checkbox) SetFieldTextColor(color tcell.Color) *Checkbox {
return c
}
// SetFieldBackgroundReadOnlyColor sets the background color of the input area when readonly.
func (c *Checkbox) SetFieldBackgroundReadOnlyColor(color tcell.Color) *Checkbox {
c.fieldBackgroundReadOnlyColor = color
return c
}
// SetFieldTextReadOnlyColor sets the text color of the input area when readonly.
func (c *Checkbox) SetFieldTextReadOnlyColor(color tcell.Color) *Checkbox {
c.fieldTextReadOnlyColor = color
return c
}
// SetFormAttributes sets attributes shared by all form items.
func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
c.labelWidth = labelWidth
@ -108,6 +139,8 @@ func (c *Checkbox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldT
c.backgroundColor = bgColor
c.fieldTextColor = fieldTextColor
c.fieldBackgroundColor = fieldBgColor
c.fieldTextReadOnlyColor = labelColor
c.fieldBackgroundReadOnlyColor = fieldBgColor
return c
}
@ -168,7 +201,9 @@ func (c *Checkbox) Draw(screen tcell.Screen) {
// Draw checkbox.
fieldStyle := tcell.StyleDefault.Background(c.fieldBackgroundColor).Foreground(c.fieldTextColor)
if c.focus.HasFocus() {
if c.readonly {
fieldStyle = tcell.StyleDefault.Background(c.fieldBackgroundReadOnlyColor).Foreground(c.fieldTextReadOnlyColor)
} else if c.focus.HasFocus() {
fieldStyle = fieldStyle.Background(c.fieldTextColor).Foreground(c.fieldBackgroundColor)
}
checkedRune := 'X'
@ -184,6 +219,9 @@ func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
// Process key event.
switch key := event.Key(); key {
case tcell.KeyRune, tcell.KeyEnter: // Check.
if c.readonly {
break
}
if key == tcell.KeyRune && event.Rune() != ' ' {
break
}
@ -214,9 +252,11 @@ func (c *Checkbox) MouseHandler() func(action MouseAction, event *tcell.EventMou
// Process mouse event.
if action == MouseLeftClick && y == rectY {
setFocus(c)
c.checked = !c.checked
if c.changed != nil {
c.changed(c.checked)
if !c.readonly {
c.checked = !c.checked
if c.changed != nil {
c.changed(c.checked)
}
}
consumed = true
}

@ -56,6 +56,12 @@ type DropDown struct {
// The text color of the input area.
fieldTextColor tcell.Color
// The background color of the input area when readonly.
fieldBackgroundReadOnlyColor tcell.Color
// The text color of the input area when readonly.
fieldTextReadOnlyColor tcell.Color
// The color for prefixes.
prefixTextColor tcell.Color
@ -81,6 +87,10 @@ type DropDown struct {
selected func(text string, index int)
dragging bool // Set to true when mouse dragging is in progress.
// If set the item does not respond to form input/mouse events
// that would change its contents
readonly bool
}
// NewDropDown returns a new drop-down.
@ -94,13 +104,15 @@ func NewDropDown() *DropDown {
SetBackgroundColor(Styles.MoreContrastBackgroundColor)
d := &DropDown{
Box: NewBox(),
currentOption: -1,
list: list,
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
prefixTextColor: Styles.ContrastSecondaryTextColor,
Box: NewBox(),
currentOption: -1,
list: list,
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
prefixTextColor: Styles.ContrastSecondaryTextColor,
fieldTextReadOnlyColor: Styles.SecondaryTextColor,
fieldBackgroundReadOnlyColor: Styles.ContrastBackgroundColor,
}
d.focus = d
@ -158,6 +170,13 @@ func (d *DropDown) SetTextOptions(prefix, suffix, currentPrefix, currentSuffix,
return d
}
// SetReadOnly sets whether the item responds to input
// or mouse events that would change its contents
func (d *DropDown) SetReadOnly(readonly bool) *DropDown {
d.readonly = readonly
return d
}
// SetLabel sets the text to be displayed before the input area.
func (d *DropDown) SetLabel(label string) *DropDown {
d.label = label
@ -202,6 +221,18 @@ func (d *DropDown) SetPrefixTextColor(color tcell.Color) *DropDown {
return d
}
// SetFieldBackgroundColor sets the background color of the options area when readonly.
func (d *DropDown) SetFieldBackgroundReadOnlyColor(color tcell.Color) *DropDown {
d.fieldBackgroundReadOnlyColor = color
return d
}
// SetFieldTextRedaOnlyColor sets the text color of the options area when readonly.
func (d *DropDown) SetFieldTextReadOnlyColor(color tcell.Color) *DropDown {
d.fieldTextReadOnlyColor = color
return d
}
// SetFormAttributes sets attributes shared by all form items.
func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
d.labelWidth = labelWidth
@ -209,6 +240,8 @@ func (d *DropDown) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldT
d.backgroundColor = bgColor
d.fieldTextColor = fieldTextColor
d.fieldBackgroundColor = fieldBgColor
d.fieldTextReadOnlyColor = labelColor
d.fieldBackgroundReadOnlyColor = fieldBgColor
return d
}
@ -340,7 +373,9 @@ func (d *DropDown) Draw(screen tcell.Screen) {
fieldWidth = rightLimit - x
}
fieldStyle := tcell.StyleDefault.Background(d.fieldBackgroundColor)
if d.GetFocusable().HasFocus() && !d.open {
if d.readonly {
fieldStyle = fieldStyle.Background(d.fieldBackgroundReadOnlyColor)
} else if d.GetFocusable().HasFocus() && !d.open {
fieldStyle = fieldStyle.Background(d.fieldTextColor)
}
for index := 0; index < fieldWidth; index++ {
@ -360,14 +395,16 @@ func (d *DropDown) Draw(screen tcell.Screen) {
}
} else {
color := d.fieldTextColor
if d.readonly {
color = d.fieldTextReadOnlyColor
} else if d.GetFocusable().HasFocus() && !d.open {
// Just show the current selection.
color = d.fieldBackgroundColor
}
text := d.noSelection
if d.currentOption >= 0 && d.currentOption < len(d.options) {
text = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix
}
// Just show the current selection.
if d.GetFocusable().HasFocus() && !d.open {
color = d.fieldBackgroundColor
}
Print(screen, text, x, y, fieldWidth, AlignLeft, color)
}
@ -511,7 +548,7 @@ func (d *DropDown) MouseHandler() func(action MouseAction, event *tcell.EventMou
x, y := event.Position()
_, rectY, _, _ := d.GetInnerRect()
inRect := y == rectY
if !d.open && !inRect {
if !d.open && !inRect || d.readonly {
return d.InRect(x, y), nil // No, and it's not expanded either. Ignore.
}

@ -51,6 +51,12 @@ type InputField struct {
// The text color of the input area.
fieldTextColor tcell.Color
// The background color of the input area when readonly.
fieldBackgroundReadOnlyColor tcell.Color
// The text color of the input area when readonly.
fieldTextReadOnlyColor tcell.Color
// The text color of the placeholder.
placeholderTextColor tcell.Color
@ -96,16 +102,22 @@ type InputField struct {
fieldX int // The x-coordinate of the input field as determined during the last call to Draw().
offset int // The number of bytes of the text string skipped ahead while drawing.
// If set the item does not respond to form input/mouse events
// that would change its contents
readonly bool
}
// NewInputField returns a new input field.
func NewInputField() *InputField {
return &InputField{
Box: NewBox(),
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
placeholderTextColor: Styles.ContrastSecondaryTextColor,
Box: NewBox(),
labelColor: Styles.SecondaryTextColor,
fieldBackgroundColor: Styles.ContrastBackgroundColor,
fieldTextColor: Styles.PrimaryTextColor,
placeholderTextColor: Styles.ContrastSecondaryTextColor,
fieldTextReadOnlyColor: Styles.SecondaryTextColor,
fieldBackgroundReadOnlyColor: Styles.ContrastBackgroundColor,
}
}
@ -124,6 +136,13 @@ func (i *InputField) GetText() string {
return i.text
}
// SetReadOnly sets whether the item responds to input
// or mouse events that would change its contents
func (i *InputField) SetReadOnly(readonly bool) *InputField {
i.readonly = readonly
return i
}
// SetLabel sets the text to be displayed before the input area.
func (i *InputField) SetLabel(label string) *InputField {
i.label = label
@ -172,6 +191,18 @@ func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {
return i
}
// SetFieldBackgroundColor sets the background color of the input area when readonly.
func (i *InputField) SetFieldBackgroundReadOnlyColor(color tcell.Color) *InputField {
i.fieldBackgroundReadOnlyColor = color
return i
}
// SetFieldTextReadOnlyColor sets the text color of the input area when readonly.
func (i *InputField) SetFieldTextReadOnlyColor(color tcell.Color) *InputField {
i.fieldTextReadOnlyColor = color
return i
}
// SetFormAttributes sets attributes shared by all form items.
func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
i.labelWidth = labelWidth
@ -179,6 +210,8 @@ func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fiel
i.backgroundColor = bgColor
i.fieldTextColor = fieldTextColor
i.fieldBackgroundColor = fieldBgColor
i.fieldTextReadOnlyColor = labelColor
i.fieldBackgroundReadOnlyColor = fieldBgColor
return i
}
@ -334,7 +367,11 @@ func (i *InputField) Draw(screen tcell.Screen) {
if rightLimit-x < fieldWidth {
fieldWidth = rightLimit - x
}
fieldStyle := tcell.StyleDefault.Background(i.fieldBackgroundColor)
bgcolor := i.fieldBackgroundColor
if i.readonly {
bgcolor = i.fieldBackgroundReadOnlyColor
}
fieldStyle := tcell.StyleDefault.Background(bgcolor)
for index := 0; index < fieldWidth; index++ {
screen.SetContent(x+index, y, ' ', nil, fieldStyle)
}
@ -344,16 +381,24 @@ func (i *InputField) Draw(screen tcell.Screen) {
text := i.text
if text == "" && i.placeholder != "" {
// Draw placeholder text.
Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, i.placeholderTextColor)
color := i.placeholderTextColor
if i.readonly {
color = i.fieldTextReadOnlyColor
}
Print(screen, Escape(i.placeholder), x, y, fieldWidth, AlignLeft, color)
i.offset = 0
} else {
// Draw entered text.
color := i.fieldTextColor
if i.readonly {
color = i.fieldTextReadOnlyColor
}
if i.maskCharacter > 0 {
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
}
if fieldWidth >= stringWidth(text) {
// We have enough space for the full text.
Print(screen, Escape(text), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
Print(screen, Escape(text), x, y, fieldWidth, AlignLeft, color)
i.offset = 0
iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
if textPos >= i.cursorPos {
@ -391,7 +436,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
}
return false
})
Print(screen, Escape(text[i.offset:]), x, y, fieldWidth, AlignLeft, i.fieldTextColor)
Print(screen, Escape(text[i.offset:]), x, y, fieldWidth, AlignLeft, color)
}
}
@ -508,27 +553,45 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
case 'f': // Move word right.
moveWordRight()
default:
if i.readonly {
break
}
if !add(event.Rune()) {
return
}
}
} else {
if i.readonly {
break
}
// Other keys are simply accepted as regular characters.
if !add(event.Rune()) {
return
}
}
case tcell.KeyCtrlU: // Delete all.
if i.readonly {
break
}
i.text = ""
i.cursorPos = 0
case tcell.KeyCtrlK: // Delete until the end of the line.
if i.readonly {
break
}
i.text = i.text[:i.cursorPos]
case tcell.KeyCtrlW: // Delete last word.
if i.readonly {
break
}
lastWord := regexp.MustCompile(`\S+\s*$`)
newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
i.cursorPos -= len(i.text) - len(newText)
i.text = newText
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
if i.readonly {
break
}
iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.text = i.text[:textPos] + i.text[textPos+textWidth:]
i.cursorPos -= textWidth
@ -538,6 +601,9 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
i.offset = 0
}
case tcell.KeyDelete: // Delete character after the cursor.
if i.readonly {
break
}
iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:]
return true

Loading…
Cancel
Save