| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682 |
- // Copyright 2016 The G3N Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package gui
- import (
- "fmt"
- "math"
- "sort"
- "strconv"
- "github.com/g3n/engine/gui/assets/icon"
- "github.com/g3n/engine/math32"
- "github.com/g3n/engine/window"
- )
- const (
- // Name of the event generated when the table is right or left clicked
- // Parameter is TableClickEvent
- OnTableClick = "onTableClick"
- // Name of the event generated when the table row count changes (no parameters)
- OnTableRowCount = "onTableRowCount"
- )
- // TableSortType is the type used to specify the sort method for a table column
- type TableSortType int
- const (
- TableSortNone TableSortType = iota
- TableSortString
- TableSortNumber
- )
- // TableSelType is the type used to specify the table row selection
- type TableSelType int
- const (
- // Single row selection mode (default)
- TableSelSingleRow = iota
- // Multiple row selection mode
- TableSelMultiRow
- )
- const (
- tableSortedNoneIcon = icon.SwapVert
- tableSortedAscIcon = icon.ArrowDownward
- tableSortedDescIcon = icon.ArrowUpward
- tableSortedNone = 0
- tableSortedAsc = 1
- tableSortedDesc = 2
- tableResizerPix = 4
- tableColMinWidth = 16
- tableErrInvRow = "Invalid row index"
- tableErrInvCol = "Invalid column id"
- )
- //
- // Table implements a panel which can contains child panels
- // organized in rows and columns.
- //
- type Table struct {
- Panel // Embedded panel
- styles *TableStyles // pointer to current styles
- header tableHeader // table headers
- rows []*tableRow // array of table rows
- rowCursor int // index of row cursor
- firstRow int // index of the first visible row
- lastRow int // index of the last visible row
- vscroll *ScrollBar // vertical scroll bar
- statusPanel Panel // optional bottom status panel
- statusLabel *Label // status label
- scrollBarEvent bool // do not update the scrollbar value in recalc() if true
- resizerPanel Panel // resizer panel
- resizeCol int // column being resized
- resizerX float32 // initial resizer x coordinate
- resizing bool // dragging the column resizer
- selType TableSelType // table selection type
- }
- // TableColumn describes a table column
- type TableColumn struct {
- Id string // Column id used to reference the column. Must be unique
- Header string // Column name shown in the table header
- Width float32 // Initial column width in pixels
- Minwidth float32 // Minimum width in pixels for this column
- Hidden bool // Hidden flag
- Align Align // Cell content alignment: AlignLeft|AlignCenter|AlignRight
- Format string // Format string for formatting the columns' cells
- FormatFunc TableFormatFunc // Format function (overrides Format string)
- Expand float32 // Column width expansion factor (0 for no expansion)
- Sort TableSortType // Column sort type
- Resize bool // Allow column to be resized by user
- }
- // TableCell describes a table cell.
- // It is used as a parameter for formatting function
- type TableCell struct {
- Tab *Table // Pointer to table
- Row int // Row index
- Col string // Column id
- Value interface{} // Cell value
- }
- // TableFormatFunc is the type for formatting functions
- type TableFormatFunc func(cell TableCell) string
- // TableHeaderStyle describes the style of the table header
- type TableHeaderStyle BasicStyle
- // TableRowStyle describes the style of the table row
- type TableRowStyle BasicStyle
- // TableStatusStyle describes the style of the table status line panel
- type TableStatusStyle BasicStyle
- // TableResizerStyle describes the style of the table resizer panel
- type TableResizerStyle struct {
- Width float32
- Border RectBounds
- BorderColor math32.Color4
- BgColor math32.Color4
- }
- // TableStyles describes all styles of the table header and rows
- type TableStyles struct {
- Header TableHeaderStyle
- RowEven TableRowStyle
- RowOdd TableRowStyle
- RowCursor TableRowStyle
- RowSel TableRowStyle
- Status TableStatusStyle
- Resizer TableResizerStyle
- }
- // TableClickEvent describes a mouse click event over a table
- // It contains the original mouse event plus additional information
- type TableClickEvent struct {
- window.MouseEvent // Embedded window mouse event
- X float32 // Table content area X coordinate
- Y float32 // Table content area Y coordinate
- Header bool // True if header was clicked
- Row int // Index of table row (may be -1)
- Col string // Id of table column (may be empty)
- ColOrder int // Current column exhibition order
- }
- // tableHeader is panel which contains the individual header panels for each column
- type tableHeader struct {
- Panel // embedded panel
- cmap map[string]*tableColHeader // maps column id with its panel/descriptor
- cols []*tableColHeader // array of individual column headers/descriptors
- lastPan Panel // last header panel not associated with a user column
- }
- // tableColHeader is panel for a column header
- type tableColHeader struct {
- Panel // header panel
- label *Label // header label
- ricon *Label // header right icon (sort direction)
- id string // column id
- width float32 // initial column width
- minWidth float32 // minimum width
- format string // column format string
- formatFunc TableFormatFunc // column format function
- align Align // column alignment
- expand float32 // column expand factor
- sort TableSortType // column sort type
- resize bool // column can be resized by user
- order int // row columns order
- sorted int // current sorted status
- xl float32 // left border coordinate in pixels
- xr float32 // right border coordinate in pixels
- }
- // tableRow is panel which contains an entire table row of cells
- type tableRow struct {
- Panel // embedded panel
- selected bool // row selected flag
- cells []*tableCell // array of row cells
- }
- // tableCell is a panel which contains one cell (a label)
- type tableCell struct {
- Panel // embedded panel
- label Label // cell label
- value interface{} // cell current value
- }
- // NewTable creates and returns a pointer to a new Table with the
- // specified width, height and columns
- func NewTable(width, height float32, cols []TableColumn) (*Table, error) {
- t := new(Table)
- t.Panel.Initialize(width, height)
- t.styles = &StyleDefault().Table
- t.rowCursor = -1
- // Initialize table header
- t.header.Initialize(0, 0)
- t.header.cmap = make(map[string]*tableColHeader)
- t.header.cols = make([]*tableColHeader, 0)
- // Create column header panels
- for ci := 0; ci < len(cols); ci++ {
- cdesc := cols[ci]
- // Column id must not be empty
- if cdesc.Id == "" {
- return nil, fmt.Errorf("Column with empty id")
- }
- // Column id must be unique
- if t.header.cmap[cdesc.Id] != nil {
- return nil, fmt.Errorf("Column with duplicate id")
- }
- // Creates a column header
- c := new(tableColHeader)
- c.Initialize(0, 0)
- t.applyHeaderStyle(&c.Panel, false)
- c.label = NewLabel(cdesc.Header)
- c.Add(c.label)
- c.id = cdesc.Id
- c.minWidth = cdesc.Minwidth
- if c.minWidth < tableColMinWidth {
- c.minWidth = tableColMinWidth
- }
- c.width = cdesc.Width
- if c.width < c.minWidth {
- c.width = c.minWidth
- }
- c.align = cdesc.Align
- c.format = cdesc.Format
- c.formatFunc = cdesc.FormatFunc
- c.expand = cdesc.Expand
- c.sort = cdesc.Sort
- c.resize = cdesc.Resize
- // Adds optional sort icon
- if c.sort != TableSortNone {
- c.ricon = NewLabel(string(tableSortedNoneIcon), true)
- c.Add(c.ricon)
- c.ricon.Subscribe(OnMouseDown, func(evname string, ev interface{}) {
- t.onRicon(evname, c)
- })
- }
- // Sets default format and order
- if c.format == "" {
- c.format = "%v"
- }
- c.order = ci
- c.SetVisible(!cdesc.Hidden)
- t.header.cmap[c.id] = c
- // Sets column header width and height
- width := cdesc.Width
- if width < c.label.Width()+c.MinWidth() {
- width = c.label.Width() + c.MinWidth()
- }
- c.SetContentSize(width, c.label.Height())
- // Adds the column header to the header panel
- t.header.cols = append(t.header.cols, c)
- t.header.Panel.Add(c)
- }
- // Creates last header
- t.header.lastPan.Initialize(0, 0)
- t.applyHeaderStyle(&t.header.lastPan, true)
- t.header.Panel.Add(&t.header.lastPan)
- // Add header panel to the table panel
- t.Panel.Add(&t.header)
- // Creates resizer panel
- t.resizerPanel.Initialize(t.styles.Resizer.Width, 0)
- t.resizerPanel.SetVisible(false)
- t.applyResizerStyle()
- t.Panel.Add(&t.resizerPanel)
- // Creates status panel
- t.statusPanel.Initialize(0, 0)
- t.statusPanel.SetVisible(false)
- t.statusLabel = NewLabel("")
- t.applyStatusStyle()
- t.statusPanel.Add(t.statusLabel)
- t.Panel.Add(&t.statusPanel)
- // Subscribe to events
- t.Panel.Subscribe(OnCursorEnter, t.onCursor)
- t.Panel.Subscribe(OnCursorLeave, t.onCursor)
- t.Panel.Subscribe(OnCursor, t.onCursorPos)
- t.Panel.Subscribe(OnScroll, t.onScroll)
- t.Panel.Subscribe(OnMouseUp, t.onMouse)
- t.Panel.Subscribe(OnMouseDown, t.onMouse)
- t.Panel.Subscribe(OnKeyDown, t.onKey)
- t.Panel.Subscribe(OnKeyRepeat, t.onKey)
- t.Panel.Subscribe(OnResize, t.onResize)
- t.recalc()
- return t, nil
- }
- // SetStyles set this table styles overriding the default
- func (t *Table) SetStyles(ts *TableStyles) {
- t.styles = ts
- t.recalc()
- }
- // SetSelectionType sets this table selection type
- // Possible values are: TableSelSingleRow|TableSelMultiRow
- func (t *Table) SetSelectionType(sel TableSelType) {
- t.selType = sel
- }
- // ShowHeader shows or hides the table header
- func (t *Table) ShowHeader(show bool) {
- if t.header.Visible() == show {
- return
- }
- t.header.SetVisible(show)
- t.recalc()
- }
- // ShowColumn sets the visibility of the column with the specified id
- // If the column id does not exit the function panics.
- func (t *Table) ShowColumn(col string, show bool) {
- c := t.header.cmap[col]
- if c == nil {
- panic(tableErrInvCol)
- }
- if c.Visible() == show {
- return
- }
- c.SetVisible(show)
- t.recalc()
- }
- // ShowAllColumns shows all the table columns
- func (t *Table) ShowAllColumns() {
- recalc := false
- for ci := 0; ci < len(t.header.cols); ci++ {
- c := t.header.cols[ci]
- if !c.Visible() {
- c.SetVisible(true)
- recalc = true
- }
- }
- if !recalc {
- return
- }
- t.recalc()
- }
- // RowCount returns the current number of rows in the table
- func (t *Table) RowCount() int {
- return len(t.rows)
- }
- // SetRows clears all current rows of the table and
- // sets new rows from the specifying parameter.
- // Each row is a map keyed by the colum id.
- // The map value currently can be a string or any number type
- // If a row column is not found it is ignored
- func (t *Table) SetRows(values []map[string]interface{}) {
- // Add missing rows
- if len(values) > len(t.rows) {
- count := len(values) - len(t.rows)
- for row := 0; row < count; row++ {
- t.insertRow(len(t.rows), nil)
- }
- // Remove remaining rows
- } else if len(values) < len(t.rows) {
- for row := len(values); row < len(t.rows); row++ {
- t.removeRow(row)
- }
- }
- // Set rows values
- for row := 0; row < len(values); row++ {
- t.setRow(row, values[row])
- }
- t.firstRow = 0
- t.rowCursor = -1
- t.recalc()
- }
- // SetRow sets the value of all the cells of the specified row from
- // the specified map indexed by column id.
- func (t *Table) SetRow(row int, values map[string]interface{}) {
- if row < 0 || row >= len(t.rows) {
- panic(tableErrInvRow)
- }
- t.setRow(row, values)
- t.recalc()
- }
- // SetCell sets the value of the cell specified by its row and column id
- // The function panics if the passed row or column id is invalid
- func (t *Table) SetCell(row int, colid string, value interface{}) {
- if row < 0 || row >= len(t.rows) {
- panic(tableErrInvRow)
- }
- if t.header.cmap[colid] == nil {
- panic(tableErrInvCol)
- }
- t.setCell(row, colid, value)
- t.recalc()
- }
- // SetColFormat sets the formatting string (Printf) for the specified column
- // Update must be called to update the table.
- func (t *Table) SetColFormat(id, format string) {
- c := t.header.cmap[id]
- if c == nil {
- panic(tableErrInvCol)
- }
- c.format = format
- }
- // SetColOrder sets the exhibition order of the specified column.
- // The previous column which has the specified order will have
- // the original column order.
- func (t *Table) SetColOrder(colid string, order int) {
- // Checks column id
- c := t.header.cmap[colid]
- if c == nil {
- panic(tableErrInvCol)
- }
- // Checks exhibition order
- if order < 0 || order > len(t.header.cols) {
- panic(tableErrInvRow)
- }
- // Find the exhibition order for the specified column
- for ci := 0; ci < len(t.header.cols); ci++ {
- if t.header.cols[ci] == c {
- // If the order of the specified column is the same, nothing to do
- if ci == order {
- return
- }
- // Swap column orders
- prev := t.header.cols[order]
- t.header.cols[order] = c
- t.header.cols[ci] = prev
- break
- }
- }
- // Recalculates the header and all rows
- t.recalc()
- }
- // EnableColResize enable or disables if the specified column can be resized by the
- // user using the mouse.
- func (t *Table) EnableColResize(colid string, enable bool) {
- // Checks column id
- c := t.header.cmap[colid]
- if c == nil {
- panic(tableErrInvCol)
- }
- c.resize = enable
- }
- // SetColWidth sets the specified column width and may
- // change the widths of the columns to the right
- func (t *Table) SetColWidth(colid string, width float32) {
- // Checks column id
- c := t.header.cmap[colid]
- if c == nil {
- panic(tableErrInvCol)
- }
- t.setColWidth(c, width)
- }
- // SetColExpand sets the column expand factor.
- // When the table width is increased the columns widths are
- // increased proportionally to their expand factor.
- // A column with expand factor = 0 is not increased.
- func (t *Table) SetColExpand(colid string, expand float32) {
- // Checks column id
- c := t.header.cmap[colid]
- if c == nil {
- panic(tableErrInvCol)
- }
- if expand < 0 {
- c.expand = 0
- } else {
- c.expand = expand
- }
- t.recalc()
- }
- // AddRow adds a new row at the end of the table with the specified values
- func (t *Table) AddRow(values map[string]interface{}) {
- t.InsertRow(len(t.rows), values)
- }
- // InsertRow inserts the specified values in a new row at the specified index
- func (t *Table) InsertRow(row int, values map[string]interface{}) {
- // Checks row index
- if row < 0 || row > len(t.rows) {
- panic(tableErrInvRow)
- }
- t.insertRow(row, values)
- t.recalc()
- t.Dispatch(OnTableRowCount, nil)
- }
- // RemoveRow removes from the specified row from the table
- func (t *Table) RemoveRow(row int) {
- // Checks row index
- if row < 0 || row >= len(t.rows) {
- panic(tableErrInvRow)
- }
- t.removeRow(row)
- maxFirst := t.calcMaxFirst()
- if t.firstRow > maxFirst {
- t.firstRow = maxFirst
- }
- t.recalc()
- t.Dispatch(OnTableRowCount, nil)
- }
- // Clear removes all rows from the table
- func (t *Table) Clear() {
- for ri := 0; ri < len(t.rows); ri++ {
- trow := t.rows[ri]
- t.Panel.Remove(trow)
- trow.DisposeChildren(true)
- trow.Dispose()
- }
- t.rows = nil
- t.firstRow = 0
- t.rowCursor = -1
- t.recalc()
- t.Dispatch(OnTableRowCount, nil)
- }
- // SelectedRows returns a slice with the indexes of the currently selected rows
- // If no row are selected returns an empty slice
- func (t *Table) SelectedRows() []int {
- res := make([]int, 0)
- if t.rowCursor >= 0 {
- res = append(res, t.rowCursor)
- }
- for ri := 0; ri < len(t.rows); ri++ {
- if t.rows[ri].selected && ri != t.rowCursor {
- res = append(res, ri)
- }
- }
- return res
- }
- // ShowStatus sets the visibility of the status lines at the bottom of the table
- func (t *Table) ShowStatus(show bool) {
- if t.statusPanel.Visible() == show {
- return
- }
- t.statusPanel.SetVisible(show)
- t.recalcStatus()
- t.recalc()
- }
- // SetStatusText sets the text of status line at the bottom of the table
- // It does not change its current visibility
- func (t *Table) SetStatusText(text string) {
- t.statusLabel.SetText(text)
- }
- // Rows returns a slice of maps with the contents of the table rows
- // specified by the rows first and last index.
- // To get all the table rows, use Rows(0, -1)
- func (t *Table) Rows(fi, li int) []map[string]interface{} {
- if fi < 0 || fi >= len(t.header.cols) {
- panic(tableErrInvRow)
- }
- if li < 0 {
- li = len(t.rows) - 1
- } else if li < 0 || li >= len(t.rows) {
- panic(tableErrInvRow)
- }
- if li < fi {
- panic("Last index less than first index")
- }
- res := make([]map[string]interface{}, li-li+1)
- for ri := fi; ri <= li; ri++ {
- trow := t.rows[ri]
- rmap := make(map[string]interface{})
- for ci := 0; ci < len(t.header.cols); ci++ {
- c := t.header.cols[ci]
- rmap[c.id] = trow.cells[c.order].value
- }
- res = append(res, rmap)
- }
- return res
- }
- // Row returns a map with the current contents of the specified row index
- func (t *Table) Row(ri int) map[string]interface{} {
- if ri < 0 || ri > len(t.header.cols) {
- panic(tableErrInvRow)
- }
- res := make(map[string]interface{})
- trow := t.rows[ri]
- for ci := 0; ci < len(t.header.cols); ci++ {
- c := t.header.cols[ci]
- res[c.id] = trow.cells[c.order].value
- }
- return res
- }
- // Cell returns the current content of the specified cell
- func (t *Table) Cell(col string, ri int) interface{} {
- c := t.header.cmap[col]
- if c == nil {
- panic(tableErrInvCol)
- }
- if ri < 0 || ri >= len(t.rows) {
- panic(tableErrInvRow)
- }
- trow := t.rows[ri]
- return trow.cells[c.order].value
- }
- // SortColumn sorts the specified column interpreting its values as strings or numbers
- // and sorting in ascending or descending order.
- // This sorting is independent of the sort configuration of column set when the table was created
- func (t *Table) SortColumn(col string, asString bool, asc bool) {
- c := t.header.cmap[col]
- if c == nil {
- panic(tableErrInvCol)
- }
- if len(t.rows) < 2 {
- return
- }
- if asString {
- ts := tableSortString{rows: t.rows, col: c.order, asc: asc, format: c.format}
- sort.Sort(ts)
- } else {
- ts := tableSortNumber{rows: t.rows, col: c.order, asc: asc}
- sort.Sort(ts)
- }
- t.recalc()
- }
- // setRow sets the value of all the cells of the specified row from
- // the specified map indexed by column id.
- func (t *Table) setRow(row int, values map[string]interface{}) {
- for ci := 0; ci < len(t.header.cols); ci++ {
- c := t.header.cols[ci]
- cv, ok := values[c.id]
- if !ok {
- continue
- }
- t.setCell(row, c.id, cv)
- }
- }
- // setCell sets the value of the cell specified by its row and column id
- func (t *Table) setCell(row int, colid string, value interface{}) {
- c := t.header.cmap[colid]
- if c == nil {
- return
- }
- cell := t.rows[row].cells[c.order]
- cell.label.SetText(fmt.Sprintf(c.format, value))
- cell.value = value
- }
- // insertRow is the internal version of InsertRow which does not call recalc()
- func (t *Table) insertRow(row int, values map[string]interface{}) {
- // Creates tableRow panel
- trow := new(tableRow)
- trow.Initialize(0, 0)
- trow.cells = make([]*tableCell, 0)
- for ci := 0; ci < len(t.header.cols); ci++ {
- // Creates tableRow cell panel
- cell := new(tableCell)
- cell.Initialize(0, 0)
- cell.label.initialize("", StyleDefault().Font)
- cell.Add(&cell.label)
- trow.cells = append(trow.cells, cell)
- trow.Panel.Add(cell)
- }
- t.Panel.Add(trow)
- // Inserts tableRow in the table rows at the specified index
- t.rows = append(t.rows, nil)
- copy(t.rows[row+1:], t.rows[row:])
- t.rows[row] = trow
- t.updateRowStyle(row)
- // Sets the new row values from the specified map
- if values != nil {
- t.SetRow(row, values)
- }
- t.recalcRow(row)
- }
- // ScrollDown scrolls the table the specified number of rows down if possible
- func (t *Table) scrollDown(n int) {
- // Calculates number of rows to scroll down
- maxFirst := t.calcMaxFirst()
- maxScroll := maxFirst - t.firstRow
- if maxScroll <= 0 {
- return
- }
- if n > maxScroll {
- n = maxScroll
- }
- t.firstRow += n
- if t.rowCursor < t.firstRow {
- t.rowCursor = t.firstRow
- t.Dispatch(OnChange, nil)
- }
- t.recalc()
- return
- }
- // ScrollUp scrolls the table the specified number of rows up if possible
- func (t *Table) scrollUp(n int) {
- // Calculates number of rows to scroll up
- if t.firstRow == 0 {
- return
- }
- if n > t.firstRow {
- n = t.firstRow
- }
- t.firstRow -= n
- lastRow := t.lastRow - n
- if t.rowCursor > lastRow {
- t.rowCursor = lastRow
- t.Dispatch(OnChange, nil)
- }
- t.recalc()
- }
- // removeRow removes from the table the row specified its index
- func (t *Table) removeRow(row int) {
- // Get row to be removed
- trow := t.rows[row]
- // Remove row from table children
- t.Panel.Remove(trow)
- // Remove row from rows array
- copy(t.rows[row:], t.rows[row+1:])
- t.rows[len(t.rows)-1] = nil
- t.rows = t.rows[:len(t.rows)-1]
- // Dispose row resources
- trow.DisposeChildren(true)
- trow.Dispose()
- }
- // onCursor process subscribed cursor events
- func (t *Table) onCursor(evname string, ev interface{}) {
- switch evname {
- case OnCursorEnter:
- t.root.SetScrollFocus(t)
- case OnCursorLeave:
- t.root.SetScrollFocus(nil)
- }
- t.root.StopPropagation(Stop3D)
- }
- // onCursorPos process subscribed cursor position events
- func (t *Table) onCursorPos(evname string, ev interface{}) {
- // Convert mouse window coordinates to table content coordinates
- kev := ev.(*window.CursorEvent)
- cx, _ := t.ContentCoords(kev.Xpos, kev.Ypos)
- // If user is dragging the resizer, updates its position
- if t.resizing {
- t.resizerPanel.SetPosition(cx, 0)
- return
- }
- // Checks if the mouse cursor is near the border of a resizable column
- found := false
- for ci := 0; ci < len(t.header.cols); ci++ {
- c := t.header.cols[ci]
- dx := math32.Abs(cx - c.xr)
- if dx < tableResizerPix {
- if c.resize {
- found = true
- t.resizeCol = ci
- t.resizerX = c.xr
- t.root.SetCursorHResize()
- }
- break
- }
- }
- // If column not found but previously was near a resizable column,
- // resets the the window cursor.
- if !found && t.resizeCol >= 0 {
- t.root.SetCursorNormal()
- t.resizeCol = -1
- }
- t.root.StopPropagation(Stop3D)
- }
- // onMouseEvent process subscribed mouse events
- func (t *Table) onMouse(evname string, ev interface{}) {
- e := ev.(*window.MouseEvent)
- t.root.SetKeyFocus(t)
- switch evname {
- case OnMouseDown:
- // If over a resizable column border, shows the resizer panel
- if t.resizeCol >= 0 && e.Button == window.MouseButtonLeft {
- t.resizing = true
- height := t.ContentHeight()
- if t.statusPanel.Visible() {
- height -= t.statusPanel.Height()
- }
- px := t.resizerX - t.resizerPanel.Width()/2
- t.resizerPanel.SetPositionX(px)
- t.resizerPanel.SetHeight(height)
- t.resizerPanel.SetVisible(true)
- t.SetTopChild(&t.resizerPanel)
- return
- }
- // Find click position
- var tce TableClickEvent
- tce.MouseEvent = *e
- t.findClick(&tce)
- // If row is clicked, selects it
- if tce.Row >= 0 && e.Button == window.MouseButtonLeft {
- t.rowCursor = tce.Row
- if t.selType == TableSelMultiRow && e.Mods == window.ModControl {
- t.toggleRowSel(t.rowCursor)
- }
- t.recalc()
- t.Dispatch(OnChange, nil)
- }
- // Creates and dispatch TableClickEvent for user's context menu
- t.Dispatch(OnTableClick, tce)
- case OnMouseUp:
- // If user was resizing a column, hides the resizer and
- // sets the new column width if possible
- if t.resizing {
- t.resizing = false
- t.resizerPanel.SetVisible(false)
- t.root.SetCursorNormal()
- // Calculates the new column width
- cx, _ := t.ContentCoords(e.Xpos, e.Ypos)
- c := t.header.cols[t.resizeCol]
- width := cx - c.xl
- t.setColWidth(c, width)
- }
- default:
- return
- }
- t.root.StopPropagation(StopAll)
- }
- // onKeyEvent receives subscribed key events for this table
- func (t *Table) onKey(evname string, ev interface{}) {
- kev := ev.(*window.KeyEvent)
- if kev.Keycode == window.KeyUp && kev.Mods == 0 {
- t.selPrev()
- } else if kev.Keycode == window.KeyDown && kev.Mods == 0 {
- t.selNext()
- } else if kev.Keycode == window.KeyPageUp && kev.Mods == 0 {
- t.prevPage()
- } else if kev.Keycode == window.KeyPageDown && kev.Mods == 0 {
- t.nextPage()
- } else if kev.Keycode == window.KeyPageUp && kev.Mods == window.ModControl {
- t.firstPage()
- } else if kev.Keycode == window.KeyPageDown && kev.Mods == window.ModControl {
- t.lastPage()
- } else if kev.Keycode == window.KeyEnter && kev.Mods == window.ModControl {
- if t.selType == TableSelMultiRow {
- t.toggleRowSel(t.rowCursor)
- }
- }
- }
- // onResize receives subscribed resize events for this table
- func (t *Table) onResize(evname string, ev interface{}) {
- t.recalc()
- t.recalcStatus()
- }
- // onScroll receives subscribed scroll events for this table
- func (t *Table) onScroll(evname string, ev interface{}) {
- sev := ev.(*window.ScrollEvent)
- if sev.Yoffset > 0 {
- t.scrollUp(1)
- } else if sev.Yoffset < 0 {
- t.scrollDown(1)
- }
- t.root.StopPropagation(Stop3D)
- }
- // onRicon receives subscribed events for column header right icon
- func (t *Table) onRicon(evname string, c *tableColHeader) {
- icon := tableSortedNoneIcon
- var asc bool
- if c.sorted == tableSortedNone || c.sorted == tableSortedDesc {
- c.sorted = tableSortedAsc
- icon = tableSortedAscIcon
- asc = false
- } else {
- c.sorted = tableSortedDesc
- icon = tableSortedDescIcon
- asc = true
- }
- var asString bool
- if c.sort == TableSortString {
- asString = true
- } else {
- asString = false
- }
- t.SortColumn(c.id, asString, asc)
- c.ricon.SetText(string(icon))
- }
- // findClick finds where in the table the specified mouse click event
- // occurred updating the specified TableClickEvent with the click coordinates.
- func (t *Table) findClick(ev *TableClickEvent) {
- x, y := t.ContentCoords(ev.Xpos, ev.Ypos)
- ev.X = x
- ev.Y = y
- ev.Row = -1
- // Find column id
- colx := float32(0)
- for ci := 0; ci < len(t.header.cols); ci++ {
- c := t.header.cols[ci]
- if !c.Visible() {
- continue
- }
- colx += t.header.cols[ci].Width()
- if x < colx {
- ev.Col = c.id
- ev.ColOrder = ci
- break
- }
- }
- // If column not found the user clicked at the right of rows
- if ev.Col == "" {
- return
- }
- // Checks if is in header
- if t.header.Visible() && y < t.header.Height() {
- ev.Header = true
- }
- // Find row clicked
- rowy := float32(0)
- if t.header.Visible() {
- rowy = t.header.Height()
- }
- theight := t.ContentHeight()
- for ri := t.firstRow; ri < len(t.rows); ri++ {
- trow := t.rows[ri]
- rowy += trow.height
- if rowy > theight {
- break
- }
- if y < rowy {
- ev.Row = ri
- break
- }
- }
- }
- // selNext selects the next row if possible
- func (t *Table) selNext() {
- // If selected row is last, nothing to do
- if t.rowCursor == len(t.rows)-1 {
- return
- }
- // If no selected row, selects first visible row
- if t.rowCursor < 0 {
- t.rowCursor = t.firstRow
- t.recalc()
- t.Dispatch(OnChange, nil)
- return
- }
- // Selects next row
- t.rowCursor++
- t.Dispatch(OnChange, nil)
- // Scroll down if necessary
- if t.rowCursor > t.lastRow {
- t.scrollDown(1)
- } else {
- t.recalc()
- }
- }
- // selPrev selects the previous row if possible
- func (t *Table) selPrev() {
- // If selected row is first, nothing to do
- sel := t.rowCursor
- if sel == 0 {
- return
- }
- // If no selected row, selects last visible row
- if sel < 0 {
- t.rowCursor = t.lastRow
- t.recalc()
- t.Dispatch(OnChange, nil)
- return
- }
- // Selects previous row and selects previous
- prev := sel - 1
- t.rowCursor = prev
- // Scroll up if necessary
- if prev < t.firstRow && t.firstRow > 0 {
- t.scrollUp(1)
- } else {
- t.recalc()
- }
- t.Dispatch(OnChange, nil)
- }
- // nextPage shows the next page of rows and selects its first row
- func (t *Table) nextPage() {
- if len(t.rows) == 0 {
- return
- }
- if t.lastRow == len(t.rows)-1 {
- t.rowCursor = t.lastRow
- t.recalc()
- t.Dispatch(OnChange, nil)
- return
- }
- plen := t.lastRow - t.firstRow
- if plen <= 0 {
- return
- }
- t.scrollDown(plen)
- }
- // prevPage shows the previous page of rows and selects its last row
- func (t *Table) prevPage() {
- if t.firstRow == 0 {
- t.rowCursor = 0
- t.recalc()
- t.Dispatch(OnChange, nil)
- return
- }
- plen := t.lastRow - t.firstRow
- if plen <= 0 {
- return
- }
- t.scrollUp(plen)
- }
- // firstPage shows the first page of rows and selects the first row
- func (t *Table) firstPage() {
- if len(t.rows) == 0 {
- return
- }
- t.firstRow = 0
- t.rowCursor = 0
- t.recalc()
- t.Dispatch(OnChange, nil)
- }
- // lastPage shows the last page of rows and selects the last row
- func (t *Table) lastPage() {
- if len(t.rows) == 0 {
- return
- }
- maxFirst := t.calcMaxFirst()
- t.firstRow = maxFirst
- t.rowCursor = len(t.rows) - 1
- t.recalc()
- t.Dispatch(OnChange, nil)
- }
- // selectRow selects the specified row.
- // Should be used only when multi row selection is enabled
- func (t *Table) selectRow(ri int) {
- trow := t.rows[ri]
- trow.selected = true
- t.Dispatch(OnChange, nil)
- }
- // toggleRowSel toogles the specified row selection state
- // Should be used only when multi row selection is enabled
- func (t *Table) toggleRowSel(ri int) {
- trow := t.rows[ri]
- trow.selected = !trow.selected
- t.Dispatch(OnChange, nil)
- }
- // setColWidth sets the width of the specified column
- func (t *Table) setColWidth(c *tableColHeader, width float32) {
- // Sets the column width
- if width < c.minWidth {
- width = c.minWidth
- }
- if c.Width() == width {
- return
- }
- dw := width - c.Width()
- c.SetWidth(width)
- // Find the column index and if any column has expand != 0
- hasExpand := false
- ci := -1
- for i := 0; i < len(t.header.cols); i++ {
- current := t.header.cols[i]
- if current == c {
- ci = i
- }
- if current.expand > 0 && current.Visible() {
- hasExpand = true
- }
- }
- if ci >= len(t.header.cols) {
- panic("Internal: column not found")
- }
- // If no column is expandable, nothing more todo
- if !hasExpand {
- t.recalc()
- return
- }
- // Calculates the width of the columns at the right
- rwidth := float32(0)
- for i := ci + 1; i < len(t.header.cols); i++ {
- c := t.header.cols[i]
- if !c.Visible() {
- continue
- }
- rwidth += c.Width()
- }
- // Distributes the delta to the columns at the right
- for i := ci + 1; i < len(t.header.cols); i++ {
- c := t.header.cols[i]
- if !c.Visible() {
- continue
- }
- cdelta := -dw * (c.Width() / rwidth)
- newWidth := c.Width() + cdelta
- if newWidth < c.minWidth {
- newWidth = c.minWidth
- }
- c.SetWidth(newWidth)
- }
- t.recalc()
- }
- // recalcHeader recalculates and sets the position and size of the header panels
- func (t *Table) recalcHeader() {
- // Calculates total width, height, expansion and available width space
- hwidth := float32(0)
- height := float32(0)
- wspace := float32(0)
- totalExpand := float32(0)
- for ci := 0; ci < len(t.header.cols); ci++ {
- // If column is invisible, ignore
- c := t.header.cols[ci]
- if !c.Visible() {
- continue
- }
- if c.Height() > height {
- height = c.Height()
- }
- if c.expand > 0 {
- totalExpand += c.expand
- }
- hwidth += c.Width()
- }
- // Total table width
- twidth := t.ContentWidth()
- if t.vscroll != nil && t.vscroll.Visible() {
- twidth -= t.vscroll.Width()
- }
- // Available space for columns: may be negative
- wspace = twidth - hwidth
- // If no expandable column, keeps the columns widths
- if totalExpand == 0 {
- } else if wspace >= 0 {
- for ci := 0; ci < len(t.header.cols); ci++ {
- // If column is invisible, ignore
- c := t.header.cols[ci]
- if !c.Visible() {
- continue
- }
- // There is space available and if column is expandable,
- // expands it proportionally to the other expandable columns
- factor := c.expand / totalExpand
- w := factor * wspace
- c.SetWidth(c.Width() + w)
- }
- } else {
- acols := make([]*tableColHeader, 0)
- awidth := float32(0)
- widthAvail := twidth
- // Sets the widths of the columns
- for ci := 0; ci < len(t.header.cols); ci++ {
- // If column is invisible, ignore
- c := t.header.cols[ci]
- if !c.Visible() {
- continue
- }
- // The table was reduced so shrinks this column proportionally to its current width
- factor := c.Width() / hwidth
- newWidth := factor * twidth
- if newWidth < c.minWidth {
- newWidth = c.minWidth
- c.SetWidth(newWidth)
- widthAvail -= c.minWidth
- } else {
- acols = append(acols, c)
- awidth += c.Width()
- }
- }
- for ci := 0; ci < len(acols); ci++ {
- c := acols[ci]
- factor := c.Width() / awidth
- newWidth := factor * widthAvail
- c.SetWidth(newWidth)
- }
- }
- // Sets the header panel and its internal panels positions
- posx := float32(0)
- for ci := 0; ci < len(t.header.cols); ci++ {
- // If column is invisible, ignore
- c := t.header.cols[ci]
- if !c.Visible() {
- continue
- }
- // Sets the right icon position inside the column header panel
- if c.ricon != nil {
- ix := c.ContentWidth() - c.ricon.Width()
- if ix < 0 {
- ix = 0
- }
- c.ricon.SetPosition(ix, 0)
- }
- // Sets the column header panel position
- c.SetPosition(posx, 0)
- c.SetVisible(true)
- c.xl = posx
- posx += c.Width()
- c.xr = posx
- }
- // Last header
- w := t.ContentWidth() - posx
- if w > 0 {
- t.header.lastPan.SetVisible(true)
- t.header.lastPan.SetSize(w, height)
- t.header.lastPan.SetPosition(posx, 0)
- } else {
- t.header.lastPan.SetVisible(false)
- }
- // Header container
- t.header.SetWidth(t.ContentWidth())
- t.header.SetContentHeight(height)
- }
- // recalcStatus recalculates and sets the position and size of the status panel and its label
- func (t *Table) recalcStatus() {
- if !t.statusPanel.Visible() {
- return
- }
- t.statusPanel.SetContentHeight(t.statusLabel.Height())
- py := t.ContentHeight() - t.statusPanel.Height()
- t.statusPanel.SetPosition(0, py)
- t.statusPanel.SetWidth(t.ContentWidth())
- }
- // recalc calculates the visibility, positions and sizes of all row cells.
- // should be called in the following situations:
- // - the table is resized
- // - row is added, inserted or removed
- // - column alignment and expansion changed
- // - column visibility is changed
- // - horizontal or vertical scroll position changed
- func (t *Table) recalc() {
- // Get available row height for rows
- starty, theight := t.rowsHeight()
- // Determines if it is necessary to show the scrollbar or not.
- scroll := false
- py := starty
- for ri := 0; ri < len(t.rows); ri++ {
- trow := t.rows[ri]
- py += trow.height
- if py > starty+theight {
- scroll = true
- break
- }
- }
- t.setVScrollBar(scroll)
- // Recalculates the header
- t.recalcHeader()
- // Sets the position and sizes of all cells of the visible rows
- py = starty
- for ri := 0; ri < len(t.rows); ri++ {
- trow := t.rows[ri]
- // If row is before first row or its y coordinate is greater the table height,
- // sets it invisible
- if ri < t.firstRow || py > starty+theight {
- trow.SetVisible(false)
- continue
- }
- t.recalcRow(ri)
- // Set row y position and visible
- trow.SetPosition(0, py)
- trow.SetVisible(true)
- t.updateRowStyle(ri)
- // Set the last completely visible row index
- if py+trow.Height() <= starty+theight {
- t.lastRow = ri
- }
- //log.Error("ri:%v py:%v theight:%v", ri, py, theight)
- py += trow.height
- }
- // Status panel must be on top of all the row panels
- t.SetTopChild(&t.statusPanel)
- }
- // recalcRow recalculates the positions and sizes of all cells of the specified row
- // Should be called when the row is created and column visibility or order is changed.
- func (t *Table) recalcRow(ri int) {
- trow := t.rows[ri]
- // Calculates and sets row height
- maxheight := float32(0)
- for ci := 0; ci < len(t.header.cols); ci++ {
- // If column is hidden, ignore
- c := t.header.cols[ci]
- if !c.Visible() {
- continue
- }
- cell := trow.cells[c.order]
- cellHeight := cell.MinHeight() + cell.label.Height()
- if cellHeight > maxheight {
- maxheight = cellHeight
- }
- }
- trow.SetContentHeight(maxheight)
- // Sets row cells sizes and positions and sets row width
- px := float32(0)
- for ci := 0; ci < len(t.header.cols); ci++ {
- // If column is hidden, ignore
- c := t.header.cols[ci]
- cell := trow.cells[c.order]
- if !c.Visible() {
- cell.SetVisible(false)
- continue
- }
- // Sets cell position and size
- cell.SetPosition(px, 0)
- cell.SetVisible(true)
- cell.SetSize(c.Width(), trow.ContentHeight())
- // Checks for format function
- if c.formatFunc != nil {
- text := c.formatFunc(TableCell{t, ri, c.id, cell.value})
- cell.label.SetText(text)
- }
- // Sets the cell label alignment inside the cell
- ccw := cell.ContentWidth()
- lw := cell.label.Width()
- space := ccw - lw
- lx := float32(0)
- switch c.align {
- case AlignLeft:
- case AlignRight:
- if space > 0 {
- lx = ccw - lw
- }
- case AlignCenter:
- if space > 0 {
- lx = space / 2
- }
- }
- cell.label.SetPosition(lx, 0)
- px += c.Width()
- }
- trow.SetContentWidth(px)
- }
- // rowsHeight returns the available start y coordinate and height in the table for rows,
- // considering the visibility of the header and status panels.
- func (t *Table) rowsHeight() (float32, float32) {
- start := float32(0)
- height := t.ContentHeight()
- if t.header.Visible() {
- height -= t.header.Height()
- start += t.header.Height()
- }
- if t.statusPanel.Visible() {
- height -= t.statusPanel.Height()
- }
- if height < 0 {
- return 0, 0
- }
- return start, height
- }
- // setVScrollBar sets the visibility state of the vertical scrollbar
- func (t *Table) setVScrollBar(state bool) {
- // Visible
- if state {
- var scrollWidth float32 = 20
- // Creates scroll bar if necessary
- if t.vscroll == nil {
- t.vscroll = NewVScrollBar(0, 0)
- t.vscroll.SetBorders(0, 0, 0, 1)
- t.vscroll.Subscribe(OnChange, t.onVScrollBar)
- t.Panel.Add(t.vscroll)
- }
- // Sets the scroll bar size and positions
- py, height := t.rowsHeight()
- t.vscroll.SetSize(scrollWidth, height)
- t.vscroll.SetPositionX(t.ContentWidth() - scrollWidth)
- t.vscroll.SetPositionY(py)
- t.vscroll.recalc()
- t.vscroll.SetVisible(true)
- if !t.scrollBarEvent {
- maxFirst := t.calcMaxFirst()
- t.vscroll.SetValue(float32(t.firstRow) / float32(maxFirst))
- } else {
- t.scrollBarEvent = false
- }
- // scroll bar must be on top of all table rows
- t.SetTopChild(t.vscroll)
- // Not visible
- } else {
- if t.vscroll != nil {
- t.vscroll.SetVisible(false)
- }
- }
- }
- // onVScrollBar is called when a vertical scroll bar event is received
- func (t *Table) onVScrollBar(evname string, ev interface{}) {
- // Calculates the new first visible line
- pos := t.vscroll.Value()
- maxFirst := t.calcMaxFirst()
- first := int(math.Floor((float64(maxFirst) * pos) + 0.5))
- // Sets the new selected row
- sel := t.rowCursor
- selChange := false
- if sel < first {
- t.rowCursor = first
- selChange = true
- } else {
- lines := first - t.firstRow
- lastRow := t.lastRow + lines
- if sel > lastRow {
- t.rowCursor = lastRow
- selChange = true
- }
- }
- t.scrollBarEvent = true
- t.firstRow = first
- t.recalc()
- if selChange {
- t.Dispatch(OnChange, nil)
- }
- }
- // calcMaxFirst calculates the maximum index of the first visible row
- // such as the remaining rows fits completely inside the table
- // It is used when scrolling the table vertically
- func (t *Table) calcMaxFirst() int {
- _, total := t.rowsHeight()
- ri := len(t.rows) - 1
- if ri < 0 {
- return 0
- }
- height := float32(0)
- for {
- trow := t.rows[ri]
- height += trow.height
- if height > total {
- break
- }
- ri--
- if ri < 0 {
- break
- }
- }
- return ri + 1
- }
- // updateRowStyle applies the correct style for the specified row
- func (t *Table) updateRowStyle(ri int) {
- row := t.rows[ri]
- var trs TableRowStyle
- if ri == t.rowCursor {
- trs = t.styles.RowCursor
- } else if row.selected {
- trs = t.styles.RowSel
- } else {
- if ri%2 == 0 {
- trs = t.styles.RowEven
- } else {
- trs = t.styles.RowOdd
- }
- }
- t.applyRowStyle(row, &trs)
- }
- // applyHeaderStyle applies style to the specified table header
- // the last header panel does not the right border.
- func (t *Table) applyHeaderStyle(h *Panel, last bool) {
- styleCopy := t.styles.Header.PanelStyle
- if last {
- styleCopy.Border.Right = 0
- }
- h.ApplyStyle(&styleCopy)
- }
- // applyRowStyle applies the specified style to all cells for the specified table row
- func (t *Table) applyRowStyle(trow *tableRow, trs *TableRowStyle) {
- for i := 0; i < len(trow.cells); i++ {
- cell := trow.cells[i]
- cell.ApplyStyle(&trs.PanelStyle)
- }
- }
- // applyStatusStyle applies the status style
- func (t *Table) applyStatusStyle() {
- s := t.styles.Status
- t.statusPanel.ApplyStyle(&s.PanelStyle)
- }
- // applyResizerStyle applies the status style
- func (t *Table) applyResizerStyle() {
- s := t.styles.Resizer
- t.resizerPanel.SetBordersFrom(&s.Border)
- t.resizerPanel.SetBordersColor4(&s.BorderColor)
- t.resizerPanel.SetColor4(&s.BgColor)
- }
- // tableSortString is an internal type implementing the sort.Interface
- // and is used to sort a table column interpreting its values as strings
- type tableSortString struct {
- rows []*tableRow
- col int
- asc bool
- format string
- }
- func (ts tableSortString) Len() int { return len(ts.rows) }
- func (ts tableSortString) Swap(i, j int) { ts.rows[i], ts.rows[j] = ts.rows[j], ts.rows[i] }
- func (ts tableSortString) Less(i, j int) bool {
- vi := ts.rows[i].cells[ts.col].value
- vj := ts.rows[j].cells[ts.col].value
- si := fmt.Sprintf(ts.format, vi)
- sj := fmt.Sprintf(ts.format, vj)
- if ts.asc {
- return si < sj
- } else {
- return sj < si
- }
- }
- // tableSortNumber is an internal type implementing the sort.Interface
- // and is used to sort a table column interpreting its values as numbers
- type tableSortNumber struct {
- rows []*tableRow
- col int
- asc bool
- }
- func (ts tableSortNumber) Len() int { return len(ts.rows) }
- func (ts tableSortNumber) Swap(i, j int) { ts.rows[i], ts.rows[j] = ts.rows[j], ts.rows[i] }
- func (ts tableSortNumber) Less(i, j int) bool {
- vi := ts.rows[i].cells[ts.col].value
- vj := ts.rows[j].cells[ts.col].value
- ni := cv2f64(vi)
- nj := cv2f64(vj)
- if ts.asc {
- return ni < nj
- } else {
- return nj < ni
- }
- }
- // Try to convert an interface value to a float64 number
- func cv2f64(v interface{}) float64 {
- if v == nil {
- return 0
- }
- switch n := v.(type) {
- case uint8:
- return float64(n)
- case uint16:
- return float64(n)
- case uint32:
- return float64(n)
- case uint64:
- return float64(n)
- case uint:
- return float64(n)
- case int8:
- return float64(n)
- case int16:
- return float64(n)
- case int32:
- return float64(n)
- case int64:
- return float64(n)
- case int:
- return float64(n)
- case string:
- sv, err := strconv.ParseFloat(n, 64)
- if err == nil {
- return sv
- }
- return 0
- default:
- return 0
- }
- }
|