From e92be8003f0afbd0a8ad3f4f10fe0a35b3806346 Mon Sep 17 00:00:00 2001 From: Vladimir Markelov Date: Tue, 22 Dec 2015 16:39:32 -0800 Subject: [PATCH] #36 - table view control - event support --- consts.go | 26 +++++++++++ tableview.go | 126 +++++++++++++++++++++++++++++++++++++++++++-------- theme.go | 2 +- 3 files changed, 135 insertions(+), 19 deletions(-) diff --git a/consts.go b/consts.go index 59eb81f..2c44d44 100644 --- a/consts.go +++ b/consts.go @@ -42,6 +42,10 @@ type ( // SelectDialogType sets the way of choosing an item from a list for // SelectionDialog control: a list-based selections, or radio group one SelectDialogType uint + // TableAction is a type of user-generated event for TableView + TableAction int + // SortOrder is a way of sorting rows in TableView + SortOrder int ) // Event is structure used by Views and controls to communicate with Composer @@ -292,3 +296,25 @@ const ( // SelectDialogList - all items are displayed in a RadioGroup SelectDialogRadio ) + +// TableAction constants +const ( + // A user pressed F2 or Enter key in TableView + TableActionEdit TableAction = iota + // A user pressed Insert key in TableView + TableActionNew + // A user pressed Delete key in TableView + TableActionDelete + // A user clicked on a column header in TableView + TableActionSort +) + +// SortOrder constants +const ( + // Do not sort + SortNone SortOrder = iota + // Sort ascending + SortAsc + // Sort descending + SortDesc +) diff --git a/tableview.go b/tableview.go index 8a6d641..5333e7c 100644 --- a/tableview.go +++ b/tableview.go @@ -10,6 +10,7 @@ type Column struct { Width int Alignment Align Fg, Bg term.Attribute + Sort SortOrder } type ColumnDrawInfo struct { @@ -24,6 +25,13 @@ type ColumnDrawInfo struct { Bg term.Attribute } +type TableEvent struct { + Action TableAction + Col int + Row int + Sort SortOrder +} + /* TableView is control to display a list of items and allow to user to select any of them. Content is scrollable with arrow keys or by clicking up and bottom buttons @@ -49,9 +57,12 @@ type TableView struct { showVLines bool onDrawCell func(*ColumnDrawInfo) - onAction func() + onAction func(TableEvent) onKeyPress func(term.Key) bool onSelectCell func(int, int) + + lastEventCol int + lastEventRow int } /* @@ -86,6 +97,8 @@ func NewTableView(view View, parent Control, width, height int, scale int) *Tabl l.onAction = nil l.onKeyPress = nil l.onSelectCell = nil + l.lastEventCol = -1 + l.lastEventRow = -1 if parent != nil { parent.AddChild(l, scale) @@ -128,14 +141,24 @@ func (l *TableView) redrawHeader(canvas Canvas, tm Theme) { idx := l.topCol for pos < w && idx < len(l.columns) { w := l.columns[idx].Width - if l.width-pos < w { - w = l.width - pos + if l.width-1-pos < w { + w = l.width - 1 - pos } if w <= 0 { break } - shift, str := AlignText(l.columns[idx].Title, w, l.columns[idx].Alignment) + dw := 0 + if l.columns[idx].Sort != SortNone { + dw = -1 + ch := parts[3] + if l.columns[idx].Sort == SortDesc { + ch = parts[4] + } + canvas.PutSymbol(x+pos+w-1, y, term.Cell{Ch: ch, Fg: fg, Bg: bg}) + } + + shift, str := AlignText(l.columns[idx].Title, w+dw, l.columns[idx].Alignment) canvas.PutText(x+pos+shift, y, str, fg, bg) pos += w @@ -267,12 +290,25 @@ func (l *TableView) Repaint() { l.redrawCells(canvas, tm) } +func (l *TableView) emitSelectionChange() { + if l.lastEventRow == l.selectedRow && l.lastEventCol == l.selectedCol { + return + } + + if l.selectedCol != -1 && l.selectedRow != -1 && l.onSelectCell != nil { + l.onSelectCell(l.selectedCol, l.selectedRow) + l.lastEventRow = l.selectedRow + l.lastEventCol = l.selectedCol + } +} + func (l *TableView) home() { if len(l.columns) > 0 { l.selectedCol = 0 } l.topCol = 0 l.EnsureColVisible() + l.emitSelectionChange() } func (l *TableView) end() { @@ -284,6 +320,7 @@ func (l *TableView) end() { l.selectedCol = length - 1 l.EnsureColVisible() + l.emitSelectionChange() } func (l *TableView) firstRow() { @@ -292,6 +329,7 @@ func (l *TableView) firstRow() { } l.topRow = 0 l.EnsureRowVisible() + l.emitSelectionChange() } func (l *TableView) lastRow() { @@ -301,6 +339,7 @@ func (l *TableView) lastRow() { l.selectedRow = l.rowCount - 1 l.EnsureColVisible() + l.emitSelectionChange() } func (l *TableView) moveUp(dy int) { @@ -311,6 +350,7 @@ func (l *TableView) moveUp(dy int) { if l.selectedRow == -1 { if l.rowCount != 0 { l.selectedRow = 0 + l.emitSelectionChange() } return } @@ -322,6 +362,7 @@ func (l *TableView) moveUp(dy int) { } l.EnsureRowVisible() + l.emitSelectionChange() } func (l *TableView) moveDown(dy int) { @@ -338,6 +379,7 @@ func (l *TableView) moveDown(dy int) { } l.EnsureRowVisible() + l.emitSelectionChange() } func (l *TableView) moveRight(dx int) { @@ -357,6 +399,7 @@ func (l *TableView) moveRight(dx int) { } l.EnsureColVisible() + l.emitSelectionChange() } func (l *TableView) moveLeft(dx int) { @@ -376,6 +419,7 @@ func (l *TableView) moveLeft(dx int) { } l.EnsureColVisible() + l.emitSelectionChange() } func (l *TableView) isColVisible(idx int) bool { @@ -483,7 +527,7 @@ func (l *TableView) mouseToCol(dx int) int { } if dx < shift { - return l.selectedCol + return -1 } idx := l.topCol @@ -533,7 +577,6 @@ func (l *TableView) verticalScrollClick(dy int) { l.moveDown(1) } else if dy > 0 && dy < l.height-2 { pos := ThumbPosition(l.selectedRow, l.rowCount, l.height-1) - l.Logger().Printf("POS: %v, DY: %v", pos, dy) if pos > dy { l.moveUp(l.height - 3) } else if pos < dy { @@ -567,23 +610,53 @@ func (l *TableView) processMouseClick(ev Event) bool { } if dy < 2 { - // Header - no action now + l.headerClicked(dx) return true } dy -= 2 l.selectedRow = l.topRow + dy - oldCol := l.selectedCol - l.selectedCol = l.mouseToCol(dx) - - if oldCol != l.selectedCol { + newCol := l.mouseToCol(dx) + if newCol != l.selectedCol { + l.selectedCol = newCol l.EnsureColVisible() + l.emitSelectionChange() } return true } +func (l *TableView) headerClicked(dx int) { + colID := l.mouseToCol(dx) + if colID == -1 { + if l.onAction != nil { + ev := TableEvent{Action: TableActionSort, Col: -1, Row: -1} + l.onAction(ev) + } + } else { + sort := l.columns[colID].Sort + + for idx, _ := range l.columns { + l.columns[idx].Sort = SortNone + } + + if sort == SortAsc { + sort = SortDesc + } else if sort == SortNone { + sort = SortAsc + } else { + sort = SortNone + } + l.columns[colID].Sort = sort + + if l.onAction != nil { + ev := TableEvent{Action: TableActionSort, Col: -1, Row: -1, Sort: sort} + l.onAction(ev) + } + } +} + /* ProcessEvent processes all events come from the control parent. If a control processes an event it should return true. If the method returns false it means @@ -609,6 +682,7 @@ func (l *TableView) ProcessEvent(event Event) bool { if event.Mod == term.ModAlt { l.selectedRow = 0 l.EnsureRowVisible() + l.emitSelectionChange() } else { l.home() } @@ -617,6 +691,7 @@ func (l *TableView) ProcessEvent(event Event) bool { if event.Mod == term.ModAlt { l.selectedRow = l.rowCount - 1 l.EnsureRowVisible() + l.emitSelectionChange() } else { l.end() } @@ -639,13 +714,24 @@ func (l *TableView) ProcessEvent(event Event) bool { case term.KeyPgup: l.moveUp(l.height - 3) return true - // case term.KeyCtrlM: - // if l.currSelection != -1 && l.onSelectItem != nil { - // ev := Event{Y: l.currSelection, Msg: l.SelectedItemText()} - // go l.onSelectItem(ev) - // } - // default: - // return false + case term.KeyCtrlM: + case term.KeyF2: + if l.selectedRow != -1 && l.selectedCol != -1 && l.onAction != nil { + ev := TableEvent{Action: TableActionEdit, Col: l.selectedCol, Row: l.selectedRow} + go l.onAction(ev) + } + case term.KeyDelete: + if l.selectedRow != 1 && l.onAction != nil { + ev := TableEvent{Action: TableActionDelete, Col: l.selectedCol, Row: l.selectedRow} + go l.onAction(ev) + } + case term.KeyInsert: + if l.onAction != nil { + ev := TableEvent{Action: TableActionDelete, Col: l.selectedCol, Row: l.selectedRow} + go l.onAction(ev) + } + default: + return false } case EventMouse: return l.processMouseClick(event) @@ -721,3 +807,7 @@ func (l *TableView) OnKeyPress(fn func(term.Key) bool) { func (l *TableView) OnDrawCell(fn func(*ColumnDrawInfo)) { l.onDrawCell = fn } + +func (l *TableView) OnAction(fn func(TableEvent)) { + l.onAction = fn +} diff --git a/theme.go b/theme.go index 7fe76c2..9c26f20 100644 --- a/theme.go +++ b/theme.go @@ -138,7 +138,7 @@ func (s *ThemeManager) Reset() { defTheme.objects[ObjProgressBar] = "░▒" defTheme.objects[ObjBarChart] = "█─│┌┐└┘┬┴├┤┼" defTheme.objects[ObjSparkChart] = "█" - defTheme.objects[ObjTableView] = "─│┼" + defTheme.objects[ObjTableView] = "─│┼▼▲" defTheme.colors[ColorDisabledText] = ColorBlackBold defTheme.colors[ColorDisabledBack] = ColorWhite