Use sync.RWMutex in all widgets

Resolves #30.
This commit is contained in:
Trevor Slocum 2020-08-02 10:06:34 -07:00
parent cc7796c4ca
commit cdeff20296
15 changed files with 112 additions and 106 deletions

View File

@ -3,6 +3,7 @@ v1.4.8 (WIP)
- Add Modal.GetForm and Modal.GetFrame
- Fix Form.Clear deadlock
- Fill nil Flex space with default background color
- Use sync.RWMutex in all widgets
v1.4.7 (2020-06-09)
- Add Box.SetBackgroundTransparent

5
DESIGN.md Normal file
View File

@ -0,0 +1,5 @@
This document lists architectural details of cview.
# Widgets always use `sync.RWMutex`
See [#30](https://gitlab.com/tslocum/cview/-/issues/30).

View File

@ -29,7 +29,7 @@ type Button struct {
// key is provided indicating which key was pressed to leave (tab or backtab).
blur func(tcell.Key)
sync.Mutex
sync.RWMutex
}
// NewButton returns a new input field.
@ -56,8 +56,8 @@ func (b *Button) SetLabel(label string) *Button {
// GetLabel returns the button text.
func (b *Button) GetLabel() string {
b.Lock()
defer b.Unlock()
b.RLock()
defer b.RUnlock()
return b.label
}

View File

@ -46,7 +46,7 @@ type CheckBox struct {
// this form item.
finished func(tcell.Key)
sync.Mutex
sync.RWMutex
}
// NewCheckBox returns a new input field.
@ -70,8 +70,8 @@ func (c *CheckBox) SetChecked(checked bool) *CheckBox {
// IsChecked returns whether or not the box is checked.
func (c *CheckBox) IsChecked() bool {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
return c.checked
}
@ -87,8 +87,8 @@ func (c *CheckBox) SetLabel(label string) *CheckBox {
// GetLabel returns the text to be displayed before the input area.
func (c *CheckBox) GetLabel() string {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
return c.label
}
@ -104,8 +104,8 @@ func (c *CheckBox) SetMessage(message string) *CheckBox {
// GetMessage returns the text to be displayed after the checkbox
func (c *CheckBox) GetMessage() string {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
return c.message
}
@ -162,8 +162,8 @@ func (c *CheckBox) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldT
// GetFieldWidth returns this primitive's field width.
func (c *CheckBox) GetFieldWidth() int {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
if c.message == "" {
return 1

View File

@ -37,7 +37,7 @@ type Flex struct {
// instead its box dimensions.
fullScreen bool
sync.Mutex
sync.RWMutex
}
// NewFlex returns a new flexbox layout container with no primitives and its
@ -217,8 +217,8 @@ func (f *Flex) Focus(delegate func(p Primitive)) {
// HasFocus returns whether or not this primitive has focus.
func (f *Flex) HasFocus() bool {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
for _, item := range f.items {
if item.Item != nil && item.Item.GetFocusable().HasFocus() {

34
form.go
View File

@ -84,7 +84,7 @@ type Form struct {
// An optional function which is called when the user hits Escape.
cancel func()
sync.Mutex
sync.RWMutex
}
// NewForm returns a new form.
@ -285,8 +285,8 @@ func (f *Form) AddButton(label string, selected func()) *Form {
// buttons have been specially prepared for this form and modifying some of
// their attributes may have unintended side effects.
func (f *Form) GetButton(index int) *Button {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
return f.buttons[index]
}
@ -303,8 +303,8 @@ func (f *Form) RemoveButton(index int) *Form {
// GetButtonCount returns the number of buttons in this form.
func (f *Form) GetButtonCount() int {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
return len(f.buttons)
}
@ -313,8 +313,8 @@ func (f *Form) GetButtonCount() int {
// with 0 for the button that was added first. If no such label was found, -1
// is returned.
func (f *Form) GetButtonIndex(label string) int {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
for index, button := range f.buttons {
if button.GetLabel() == label {
@ -368,8 +368,8 @@ func (f *Form) AddFormItem(item FormItem) *Form {
// GetFormItemCount returns the number of items in the form (not including the
// buttons).
func (f *Form) GetFormItemCount() int {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
return len(f.items)
}
@ -378,8 +378,8 @@ func (f *Form) GetFormItemCount() int {
// 0. Elements are referenced in the order they were added. Buttons are not
// included.
func (f *Form) GetFormItem(index int) FormItem {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
return f.items[index]
}
@ -399,8 +399,8 @@ func (f *Form) RemoveFormItem(index int) *Form {
// no such element is found, nil is returned. Buttons are not searched and will
// therefore not be returned.
func (f *Form) GetFormItemByLabel(label string) FormItem {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
for _, item := range f.items {
if item.GetLabel() == label {
@ -414,8 +414,8 @@ func (f *Form) GetFormItemByLabel(label string) FormItem {
// label. If no such element is found, -1 is returned. Buttons are not searched
// and will therefore not be returned.
func (f *Form) GetFormItemIndex(label string) int {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
for index, item := range f.items {
if item.GetLabel() == label {
@ -428,8 +428,8 @@ func (f *Form) GetFormItemIndex(label string) int {
// GetFocusedItemIndex returns the indices of the form element or button which
// currently has focus. If they don't, -1 is returned resepectively.
func (f *Form) GetFocusedItemIndex() (formItem, button int) {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
index := f.focusIndex()
if index < 0 {

View File

@ -28,7 +28,7 @@ type Frame struct {
// Border spacing.
top, bottom, header, footer, left, right int
sync.Mutex
sync.RWMutex
}
// NewFrame returns a new frame around the given primitive. The primitive's
@ -167,8 +167,8 @@ func (f *Frame) Focus(delegate func(p Primitive)) {
// HasFocus returns whether or not this primitive has focus.
func (f *Frame) HasFocus() bool {
f.Lock()
defer f.Unlock()
f.RLock()
defer f.RUnlock()
focusable, ok := f.primitive.(Focusable)
if ok {

3
go.mod
View File

@ -8,5 +8,6 @@ require (
github.com/mattn/go-runewidth v0.0.9
github.com/rivo/uniseg v0.1.0
gitlab.com/tslocum/cbind v0.1.1
golang.org/x/sys v0.0.0-20200610111108-226ff32320da // indirect
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3 // indirect
golang.org/x/text v0.3.3 // indirect
)

6
go.sum
View File

@ -23,11 +23,13 @@ gitlab.com/tslocum/cbind v0.1.1/go.mod h1:rX7vkl0pUSg/yy427MmD1FZAf99S7WwpUlxF/q
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3 h1:qDJKu1y/1SjhWac4BQZjLljqvqiWUhjmDMnonmVGDAU=
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

10
grid.go
View File

@ -56,7 +56,7 @@ type Grid struct {
// The color of the borders around grid items.
bordersColor tcell.Color
sync.Mutex
sync.RWMutex
}
// NewGrid returns a new grid-based layout container with no initial primitives.
@ -272,8 +272,8 @@ func (g *Grid) SetOffset(rows, columns int) *Grid {
// GetOffset returns the current row and column offset (see SetOffset() for
// details).
func (g *Grid) GetOffset() (rows, columns int) {
g.Lock()
defer g.Unlock()
g.RLock()
defer g.RUnlock()
return g.rowOffset, g.columnOffset
}
@ -306,8 +306,8 @@ func (g *Grid) Blur() {
// HasFocus returns whether or not this primitive has focus.
func (g *Grid) HasFocus() bool {
g.Lock()
defer g.Unlock()
g.RLock()
defer g.RUnlock()
for _, item := range g.items {
if item.visible && item.Item.GetFocusable().HasFocus() {

View File

@ -31,7 +31,7 @@ type Modal struct {
// receives the index of the clicked button and the button's label.
done func(buttonIndex int, buttonLabel string)
sync.Mutex
sync.RWMutex
}
// NewModal returns a new centered message window.
@ -121,16 +121,16 @@ func (m *Modal) SetText(text string) *Modal {
// GetForm returns the Form embedded in the window. The returned Form may be
// modified to include additional elements (e.g. AddInputField, AddFormItem).
func (m *Modal) GetForm() *Form {
m.Lock()
defer m.Unlock()
m.RLock()
defer m.RUnlock()
return m.form
}
// GetFrame returns the Frame embedded in the window.
func (m *Modal) GetFrame() *Frame {
m.Lock()
defer m.Unlock()
m.RLock()
defer m.RUnlock()
return m.frame
}
@ -188,10 +188,7 @@ func (m *Modal) Focus(delegate func(p Primitive)) {
// HasFocus returns whether or not this primitive has focus.
func (m *Modal) HasFocus() bool {
m.Lock()
defer m.Unlock()
return m.form.HasFocus()
return m.GetForm().HasFocus()
}
// Draw draws this primitive onto the screen.

View File

@ -31,7 +31,7 @@ type Pages struct {
// pages changes.
changed func()
sync.Mutex
sync.RWMutex
}
// NewPages returns a new Pages object.
@ -55,8 +55,8 @@ func (p *Pages) SetChangedFunc(handler func()) *Pages {
// GetPageCount returns the number of pages currently stored in this object.
func (p *Pages) GetPageCount() int {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
return len(p.pages)
}
@ -146,8 +146,8 @@ func (p *Pages) RemovePage(name string) *Pages {
// HasPage returns true if a page with the given name exists in this object.
func (p *Pages) HasPage(name string) bool {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
for _, page := range p.pages {
if page.Name == name {
@ -301,8 +301,8 @@ func (p *Pages) SendToBack(name string) *Pages {
// GetFrontPage returns the front-most visible page. If there are no visible
// pages, ("", nil) is returned.
func (p *Pages) GetFrontPage() (name string, item Primitive) {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
for index := len(p.pages) - 1; index >= 0; index-- {
if p.pages[index].Visible {
@ -314,8 +314,8 @@ func (p *Pages) GetFrontPage() (name string, item Primitive) {
// HasFocus returns whether or not this primitive has focus.
func (p *Pages) HasFocus() bool {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
for _, page := range p.pages {
if page.Item.GetFocusable().HasFocus() {

View File

@ -33,7 +33,7 @@ type ProgressBar struct {
// Progress required to fill the bar.
max int
sync.Mutex
sync.RWMutex
}
// NewProgressBar returns a new progress bar.
@ -98,8 +98,8 @@ func (p *ProgressBar) SetMax(max int) {
// GetMax returns the progress required to fill the bar.
func (p *ProgressBar) GetMax() int {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
return p.max
}
@ -122,16 +122,16 @@ func (p *ProgressBar) SetProgress(progress int) {
// GetProgress gets the current progress.
func (p *ProgressBar) GetProgress() int {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
return p.progress
}
// Complete returns whether the progress bar has been filled.
func (p *ProgressBar) Complete() bool {
p.Lock()
defer p.Unlock()
p.RLock()
defer p.RUnlock()
return p.progress >= p.max
}

View File

@ -46,7 +46,7 @@ type TableCell struct {
// The position and width of the cell the last time table was drawn.
x, y, width int
sync.Mutex
sync.RWMutex
}
// NewTableCell returns a new table cell with sensible defaults. That is, left
@ -178,8 +178,8 @@ func (c *TableCell) SetReference(reference interface{}) *TableCell {
// GetReference returns this cell's reference object.
func (c *TableCell) GetReference() interface{} {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
return c.Reference
}
@ -193,8 +193,8 @@ func (c *TableCell) GetReference() interface{} {
// SetSelectedFunc()) or a "selectionChanged" event (see
// SetSelectionChangedFunc()).
func (c *TableCell) GetLastPosition() (x, y, width int) {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
return c.x, c.y, c.width
}
@ -319,7 +319,7 @@ type Table struct {
// or Backtab. Also when the user presses Enter if nothing is selectable.
done func(key tcell.Key)
sync.Mutex
sync.RWMutex
}
// NewTable returns a new table.
@ -440,8 +440,8 @@ func (t *Table) SetSelectable(rows, columns bool) *Table {
// GetSelectable returns what can be selected in a table. Refer to
// SetSelectable() for details.
func (t *Table) GetSelectable() (rows, columns bool) {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
return t.rowsSelectable, t.columnsSelectable
}
@ -450,8 +450,8 @@ func (t *Table) GetSelectable() (rows, columns bool) {
// If entire rows are selected, the column index is undefined.
// Likewise for entire columns.
func (t *Table) GetSelection() (row, column int) {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
return t.selectedRow, t.selectedColumn
}
@ -491,8 +491,8 @@ func (t *Table) SetOffset(row, column int) *Table {
// GetOffset returns the current row and column offset. This indicates how many
// rows and columns the table is scrolled down and to the right.
func (t *Table) GetOffset() (row, column int) {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
return t.rowOffset, t.columnOffset
}
@ -588,8 +588,8 @@ func (t *Table) SetCellSimple(row, column int, text string) *Table {
// be inserted. Therefore, repeated calls to this function may return different
// pointers for uninitialized cells.
func (t *Table) GetCell(row, column int) *TableCell {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
if row >= len(t.cells) || column >= len(t.cells[row]) {
return &TableCell{}
@ -665,16 +665,16 @@ func (t *Table) InsertColumn(column int) *Table {
// GetRowCount returns the number of rows in the table.
func (t *Table) GetRowCount() int {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
return len(t.cells)
}
// GetColumnCount returns the (maximum) number of columns in the table.
func (t *Table) GetColumnCount() int {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
if len(t.cells) == 0 {
return 0

View File

@ -52,7 +52,7 @@ type TreeNode struct {
graphicsX int // The x-coordinate of the left-most graphics rune.
textX int // The x-coordinate of the first rune of the text.
sync.Mutex
sync.RWMutex
}
// NewTreeNode returns a new tree node.
@ -113,8 +113,8 @@ func (n *TreeNode) SetReference(reference interface{}) *TreeNode {
// GetReference returns this node's reference object.
func (n *TreeNode) GetReference() interface{} {
n.Lock()
defer n.Unlock()
n.RLock()
defer n.RUnlock()
return n.reference
}
@ -130,16 +130,16 @@ func (n *TreeNode) SetChildren(childNodes []*TreeNode) *TreeNode {
// GetText returns this node's text.
func (n *TreeNode) GetText() string {
n.Lock()
defer n.Unlock()
n.RLock()
defer n.RUnlock()
return n.text
}
// GetChildren returns this node's children.
func (n *TreeNode) GetChildren() []*TreeNode {
n.Lock()
defer n.Unlock()
n.RLock()
defer n.RUnlock()
return n.children
}
@ -241,8 +241,8 @@ func (n *TreeNode) CollapseAll() *TreeNode {
// IsExpanded returns whether the child nodes of this node are visible.
func (n *TreeNode) IsExpanded() bool {
n.Lock()
defer n.Unlock()
n.RLock()
defer n.RUnlock()
return n.expanded
}
@ -258,8 +258,8 @@ func (n *TreeNode) SetText(text string) *TreeNode {
// GetColor returns the node's color.
func (n *TreeNode) GetColor() tcell.Color {
n.Lock()
defer n.Unlock()
n.RLock()
defer n.RUnlock()
return n.color
}
@ -363,7 +363,7 @@ type TreeView struct {
// The visible nodes, top-down, as set by process().
nodes []*TreeNode
sync.Mutex
sync.RWMutex
}
// NewTreeView returns a new tree view.
@ -389,8 +389,8 @@ func (t *TreeView) SetRoot(root *TreeNode) *TreeView {
// GetRoot returns the root node of the tree. If no such node was previously
// set, nil is returned.
func (t *TreeView) GetRoot() *TreeNode {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
return t.root
}
@ -416,8 +416,8 @@ func (t *TreeView) SetCurrentNode(node *TreeNode) *TreeView {
// GetCurrentNode returns the currently selected node or nil of no node is
// currently selected.
func (t *TreeView) GetCurrentNode() *TreeNode {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
return t.currentNode
}
@ -532,8 +532,8 @@ func (t *TreeView) SetDoneFunc(handler func(key tcell.Key)) *TreeView {
// of the tree view. Note that when the user navigates the tree view, this value
// is only updated after the tree view has been redrawn.
func (t *TreeView) GetScrollOffset() int {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
return t.offsetY
}
@ -543,8 +543,8 @@ func (t *TreeView) GetScrollOffset() int {
// of collapsed nodes. Note that this value is only up to date after the tree
// view has been drawn.
func (t *TreeView) GetRowCount() int {
t.Lock()
defer t.Unlock()
t.RLock()
defer t.RUnlock()
return len(t.nodes)
}