...

Source file src/gitlab.com/tslocum/cview/table.go

Documentation: gitlab.com/tslocum/cview

     1  package cview
     2  
     3  import (
     4  	"bytes"
     5  	"sort"
     6  	"sync"
     7  
     8  	"github.com/gdamore/tcell/v2"
     9  	colorful "github.com/lucasb-eyer/go-colorful"
    10  )
    11  
    12  // TableCell represents one cell inside a Table. You can instantiate this type
    13  // directly but all colors (background and text) will be set to their default
    14  // which is black.
    15  type TableCell struct {
    16  	// The reference object.
    17  	Reference interface{}
    18  
    19  	// The text to be displayed in the table cell.
    20  	Text []byte
    21  
    22  	// The alignment of the cell text. One of AlignLeft (default), AlignCenter,
    23  	// or AlignRight.
    24  	Align int
    25  
    26  	// The maximum width of the cell in screen space. This is used to give a
    27  	// column a maximum width. Any cell text whose screen width exceeds this width
    28  	// is cut off. Set to 0 if there is no maximum width.
    29  	MaxWidth int
    30  
    31  	// If the total table width is less than the available width, this value is
    32  	// used to add extra width to a column. See SetExpansion() for details.
    33  	Expansion int
    34  
    35  	// The color of the cell text.
    36  	Color tcell.Color
    37  
    38  	// The background color of the cell.
    39  	BackgroundColor tcell.Color
    40  
    41  	// The style attributes of the cell.
    42  	Attributes tcell.AttrMask
    43  
    44  	// If set to true, this cell cannot be selected.
    45  	NotSelectable bool
    46  
    47  	// The position and width of the cell the last time table was drawn.
    48  	x, y, width int
    49  
    50  	sync.RWMutex
    51  }
    52  
    53  // NewTableCell returns a new table cell with sensible defaults. That is, left
    54  // aligned text with the primary text color (see Styles) and a transparent
    55  // background (using the background of the Table).
    56  func NewTableCell(text string) *TableCell {
    57  	return &TableCell{
    58  		Text:            []byte(text),
    59  		Align:           AlignLeft,
    60  		Color:           Styles.PrimaryTextColor,
    61  		BackgroundColor: tcell.ColorDefault,
    62  	}
    63  }
    64  
    65  // SetBytes sets the cell's text.
    66  func (c *TableCell) SetBytes(text []byte) {
    67  	c.Lock()
    68  	defer c.Unlock()
    69  
    70  	c.Text = text
    71  }
    72  
    73  // SetText sets the cell's text.
    74  func (c *TableCell) SetText(text string) {
    75  	c.SetBytes([]byte(text))
    76  }
    77  
    78  // GetBytes returns the cell's text.
    79  func (c *TableCell) GetBytes() []byte {
    80  	c.RLock()
    81  	defer c.RUnlock()
    82  
    83  	return c.Text
    84  }
    85  
    86  // GetText returns the cell's text.
    87  func (c *TableCell) GetText() string {
    88  	return string(c.GetBytes())
    89  }
    90  
    91  // SetAlign sets the cell's text alignment, one of AlignLeft, AlignCenter, or
    92  // AlignRight.
    93  func (c *TableCell) SetAlign(align int) {
    94  	c.Lock()
    95  	defer c.Unlock()
    96  
    97  	c.Align = align
    98  }
    99  
   100  // SetMaxWidth sets maximum width of the cell in screen space. This is used to
   101  // give a column a maximum width. Any cell text whose screen width exceeds this
   102  // width is cut off. Set to 0 if there is no maximum width.
   103  func (c *TableCell) SetMaxWidth(maxWidth int) {
   104  	c.Lock()
   105  	defer c.Unlock()
   106  
   107  	c.MaxWidth = maxWidth
   108  }
   109  
   110  // SetExpansion sets the value by which the column of this cell expands if the
   111  // available width for the table is more than the table width (prior to applying
   112  // this expansion value). This is a proportional value. The amount of unused
   113  // horizontal space is divided into widths to be added to each column. How much
   114  // extra width a column receives depends on the expansion value: A value of 0
   115  // (the default) will not cause the column to increase in width. Other values
   116  // are proportional, e.g. a value of 2 will cause a column to grow by twice
   117  // the amount of a column with a value of 1.
   118  //
   119  // Since this value affects an entire column, the maximum over all visible cells
   120  // in that column is used.
   121  //
   122  // This function panics if a negative value is provided.
   123  func (c *TableCell) SetExpansion(expansion int) {
   124  	c.Lock()
   125  	defer c.Unlock()
   126  
   127  	if expansion < 0 {
   128  		panic("Table cell expansion values may not be negative")
   129  	}
   130  	c.Expansion = expansion
   131  }
   132  
   133  // SetTextColor sets the cell's text color.
   134  func (c *TableCell) SetTextColor(color tcell.Color) {
   135  	c.Lock()
   136  	defer c.Unlock()
   137  
   138  	c.Color = color
   139  }
   140  
   141  // SetBackgroundColor sets the cell's background color. Set to
   142  // tcell.ColorDefault to use the table's background color.
   143  func (c *TableCell) SetBackgroundColor(color tcell.Color) {
   144  	c.Lock()
   145  	defer c.Unlock()
   146  
   147  	c.BackgroundColor = color
   148  }
   149  
   150  // SetAttributes sets the cell's text attributes. You can combine different
   151  // attributes using bitmask operations:
   152  //
   153  //   cell.SetAttributes(tcell.AttrUnderline | tcell.AttrBold)
   154  func (c *TableCell) SetAttributes(attr tcell.AttrMask) {
   155  	c.Lock()
   156  	defer c.Unlock()
   157  
   158  	c.Attributes = attr
   159  }
   160  
   161  // SetStyle sets the cell's style (foreground color, background color, and
   162  // attributes) all at once.
   163  func (c *TableCell) SetStyle(style tcell.Style) {
   164  	c.Lock()
   165  	defer c.Unlock()
   166  
   167  	c.Color, c.BackgroundColor, c.Attributes = style.Decompose()
   168  }
   169  
   170  // SetSelectable sets whether or not this cell can be selected by the user.
   171  func (c *TableCell) SetSelectable(selectable bool) {
   172  	c.Lock()
   173  	defer c.Unlock()
   174  
   175  	c.NotSelectable = !selectable
   176  }
   177  
   178  // SetReference allows you to store a reference of any type in this cell. This
   179  // will allow you to establish a mapping between the cell and your
   180  // actual data.
   181  func (c *TableCell) SetReference(reference interface{}) {
   182  	c.Lock()
   183  	defer c.Unlock()
   184  
   185  	c.Reference = reference
   186  }
   187  
   188  // GetReference returns this cell's reference object.
   189  func (c *TableCell) GetReference() interface{} {
   190  	c.RLock()
   191  	defer c.RUnlock()
   192  
   193  	return c.Reference
   194  }
   195  
   196  // GetLastPosition returns the position of the table cell the last time it was
   197  // drawn on screen. If the cell is not on screen, the return values are
   198  // undefined.
   199  //
   200  // Because the Table class will attempt to keep selected cells on screen, this
   201  // function is most useful in response to a "selected" event (see
   202  // SetSelectedFunc()) or a "selectionChanged" event (see
   203  // SetSelectionChangedFunc()).
   204  func (c *TableCell) GetLastPosition() (x, y, width int) {
   205  	c.RLock()
   206  	defer c.RUnlock()
   207  
   208  	return c.x, c.y, c.width
   209  }
   210  
   211  // Table visualizes two-dimensional data consisting of rows and columns. Each
   212  // Table cell is defined via SetCell() by the TableCell type. They can be added
   213  // dynamically to the table and changed any time.
   214  //
   215  // Each row of the table must have the same number of columns when it is drawn
   216  // or navigated. This isn't strictly enforced, however you may encounter issues
   217  // when navigating a table with rows of varied column sizes.
   218  //
   219  // The most compact display of a table is without borders. Each row will then
   220  // occupy one row on screen and columns are separated by the rune defined via
   221  // SetSeparator() (a space character by default).
   222  //
   223  // When borders are turned on (via SetBorders()), each table cell is surrounded
   224  // by lines. Therefore one table row will require two rows on screen.
   225  //
   226  // Columns will use as much horizontal space as they need. You can constrain
   227  // their size with the MaxWidth parameter of the TableCell type.
   228  //
   229  // Fixed Columns
   230  //
   231  // You can define fixed rows and rolumns via SetFixed(). They will always stay
   232  // in their place, even when the table is scrolled. Fixed rows are always the
   233  // top rows. Fixed columns are always the leftmost columns.
   234  //
   235  // Selections
   236  //
   237  // You can call SetSelectable() to set columns and/or rows to "selectable". If
   238  // the flag is set only for columns, entire columns can be selected by the user.
   239  // If it is set only for rows, entire rows can be selected. If both flags are
   240  // set, individual cells can be selected. The "selected" handler set via
   241  // SetSelectedFunc() is invoked when the user presses Enter on a selection.
   242  //
   243  // Navigation
   244  //
   245  // If the table extends beyond the available space, it can be navigated with
   246  // key bindings similar to Vim:
   247  //
   248  //   - h, left arrow: Move left by one column.
   249  //   - l, right arrow: Move right by one column.
   250  //   - j, down arrow: Move down by one row.
   251  //   - k, up arrow: Move up by one row.
   252  //   - g, home: Move to the top.
   253  //   - G, end: Move to the bottom.
   254  //   - Ctrl-F, page down: Move down by one page.
   255  //   - Ctrl-B, page up: Move up by one page.
   256  //
   257  // When there is no selection, this affects the entire table (except for fixed
   258  // rows and columns). When there is a selection, the user moves the selection.
   259  // The class will attempt to keep the selection from moving out of the screen.
   260  //
   261  // Use SetInputCapture() to override or modify keyboard input.
   262  type Table struct {
   263  	*Box
   264  
   265  	// Whether or not this table has borders around each cell.
   266  	borders bool
   267  
   268  	// The color of the borders or the separator.
   269  	bordersColor tcell.Color
   270  
   271  	// If there are no borders, the column separator.
   272  	separator rune
   273  
   274  	// The cells of the table. Rows first, then columns.
   275  	cells [][]*TableCell
   276  
   277  	// The rightmost column in the data set.
   278  	lastColumn int
   279  
   280  	// If true, when calculating the widths of the columns, all rows are evaluated
   281  	// instead of only the visible ones.
   282  	evaluateAllRows bool
   283  
   284  	// The number of fixed rows / columns.
   285  	fixedRows, fixedColumns int
   286  
   287  	// Whether or not rows or columns can be selected. If both are set to true,
   288  	// cells can be selected.
   289  	rowsSelectable, columnsSelectable bool
   290  
   291  	// The currently selected row and column.
   292  	selectedRow, selectedColumn int
   293  
   294  	// The number of rows/columns by which the table is scrolled down/to the
   295  	// right.
   296  	rowOffset, columnOffset int
   297  
   298  	// If set to true, the table's last row will always be visible.
   299  	trackEnd bool
   300  
   301  	// The sort function of the table. Defaults to a case-sensitive comparison.
   302  	sortFunc func(column, i, j int) bool
   303  
   304  	// Whether or not the table should be sorted when a fixed row is clicked.
   305  	sortClicked bool
   306  
   307  	// The last direction the table was sorted by when clicked.
   308  	sortClickedDescending bool
   309  
   310  	// The last column the table was sorted by when clicked.
   311  	sortClickedColumn int
   312  
   313  	// The number of visible rows the last time the table was drawn.
   314  	visibleRows int
   315  
   316  	// The indices of the visible columns as of the last time the table was drawn.
   317  	visibleColumnIndices []int
   318  
   319  	// The net widths of the visible columns as of the last time the table was
   320  	// drawn.
   321  	visibleColumnWidths []int
   322  
   323  	// Visibility of the scroll bar.
   324  	scrollBarVisibility ScrollBarVisibility
   325  
   326  	// The scroll bar color.
   327  	scrollBarColor tcell.Color
   328  
   329  	// The style of the selected rows. If this value is StyleDefault, selected rows
   330  	// are simply inverted.
   331  	selectedStyle tcell.Style
   332  
   333  	// An optional function which gets called when the user presses Enter on a
   334  	// selected cell. If entire rows selected, the column value is undefined.
   335  	// Likewise for entire columns.
   336  	selected func(row, column int)
   337  
   338  	// An optional function which gets called when the user changes the selection.
   339  	// If entire rows selected, the column value is undefined.
   340  	// Likewise for entire columns.
   341  	selectionChanged func(row, column int)
   342  
   343  	// An optional function which gets called when the user presses Escape, Tab,
   344  	// or Backtab. Also when the user presses Enter if nothing is selectable.
   345  	done func(key tcell.Key)
   346  
   347  	sync.RWMutex
   348  }
   349  
   350  // NewTable returns a new table.
   351  func NewTable() *Table {
   352  	return &Table{
   353  		Box:                 NewBox(),
   354  		scrollBarVisibility: ScrollBarAuto,
   355  		scrollBarColor:      Styles.ScrollBarColor,
   356  		bordersColor:        Styles.GraphicsColor,
   357  		separator:           ' ',
   358  		sortClicked:         true,
   359  		lastColumn:          -1,
   360  	}
   361  }
   362  
   363  // Clear removes all table data.
   364  func (t *Table) Clear() {
   365  	t.Lock()
   366  	defer t.Unlock()
   367  
   368  	t.cells = nil
   369  	t.lastColumn = -1
   370  }
   371  
   372  // SetBorders sets whether or not each cell in the table is surrounded by a
   373  // border.
   374  func (t *Table) SetBorders(show bool) {
   375  	t.Lock()
   376  	defer t.Unlock()
   377  
   378  	t.borders = show
   379  }
   380  
   381  // SetBordersColor sets the color of the cell borders.
   382  func (t *Table) SetBordersColor(color tcell.Color) {
   383  	t.Lock()
   384  	defer t.Unlock()
   385  
   386  	t.bordersColor = color
   387  }
   388  
   389  // SetScrollBarVisibility specifies the display of the scroll bar.
   390  func (t *Table) SetScrollBarVisibility(visibility ScrollBarVisibility) {
   391  	t.Lock()
   392  	defer t.Unlock()
   393  
   394  	t.scrollBarVisibility = visibility
   395  }
   396  
   397  // SetScrollBarColor sets the color of the scroll bar.
   398  func (t *Table) SetScrollBarColor(color tcell.Color) {
   399  	t.Lock()
   400  	defer t.Unlock()
   401  
   402  	t.scrollBarColor = color
   403  }
   404  
   405  // SetSelectedStyle sets a specific style for selected cells. If no such style
   406  // is set, per default, selected cells are inverted (i.e. their foreground and
   407  // background colors are swapped).
   408  //
   409  // To reset a previous setting to its default, make the following call:
   410  //
   411  //   table.SetSelectedStyle(tcell.ColorDefault, tcell.ColorDefault, 0)
   412  func (t *Table) SetSelectedStyle(foregroundColor, backgroundColor tcell.Color, attributes tcell.AttrMask) {
   413  	t.Lock()
   414  	defer t.Unlock()
   415  
   416  	t.selectedStyle = SetAttributes(tcell.StyleDefault.Foreground(foregroundColor).Background(backgroundColor), attributes)
   417  }
   418  
   419  // SetSeparator sets the character used to fill the space between two
   420  // neighboring cells. This is a space character ' ' per default but you may
   421  // want to set it to Borders.Vertical (or any other rune) if the column
   422  // separation should be more visible. If cell borders are activated, this is
   423  // ignored.
   424  //
   425  // Separators have the same color as borders.
   426  func (t *Table) SetSeparator(separator rune) {
   427  	t.Lock()
   428  	defer t.Unlock()
   429  
   430  	t.separator = separator
   431  }
   432  
   433  // SetFixed sets the number of fixed rows and columns which are always visible
   434  // even when the rest of the cells are scrolled out of view. Rows are always the
   435  // top-most ones. Columns are always the left-most ones.
   436  func (t *Table) SetFixed(rows, columns int) {
   437  	t.Lock()
   438  	defer t.Unlock()
   439  
   440  	t.fixedRows, t.fixedColumns = rows, columns
   441  }
   442  
   443  // SetSelectable sets the flags which determine what can be selected in a table.
   444  // There are three selection modi:
   445  //
   446  //   - rows = false, columns = false: Nothing can be selected.
   447  //   - rows = true, columns = false: Rows can be selected.
   448  //   - rows = false, columns = true: Columns can be selected.
   449  //   - rows = true, columns = true: Individual cells can be selected.
   450  func (t *Table) SetSelectable(rows, columns bool) {
   451  	t.Lock()
   452  	defer t.Unlock()
   453  
   454  	t.rowsSelectable, t.columnsSelectable = rows, columns
   455  }
   456  
   457  // GetSelectable returns what can be selected in a table. Refer to
   458  // SetSelectable() for details.
   459  func (t *Table) GetSelectable() (rows, columns bool) {
   460  	t.RLock()
   461  	defer t.RUnlock()
   462  
   463  	return t.rowsSelectable, t.columnsSelectable
   464  }
   465  
   466  // GetSelection returns the position of the current selection.
   467  // If entire rows are selected, the column index is undefined.
   468  // Likewise for entire columns.
   469  func (t *Table) GetSelection() (row, column int) {
   470  	t.RLock()
   471  	defer t.RUnlock()
   472  
   473  	return t.selectedRow, t.selectedColumn
   474  }
   475  
   476  // Select sets the selected cell. Depending on the selection settings
   477  // specified via SetSelectable(), this may be an entire row or column, or even
   478  // ignored completely. The "selection changed" event is fired if such a callback
   479  // is available (even if the selection ends up being the same as before and even
   480  // if cells are not selectable).
   481  func (t *Table) Select(row, column int) {
   482  	t.Lock()
   483  	defer t.Unlock()
   484  
   485  	t.selectedRow, t.selectedColumn = row, column
   486  	if t.selectionChanged != nil {
   487  		t.Unlock()
   488  		t.selectionChanged(row, column)
   489  		t.Lock()
   490  	}
   491  }
   492  
   493  // SetOffset sets how many rows and columns should be skipped when drawing the
   494  // table. This is useful for large tables that do not fit on the screen.
   495  // Navigating a selection can change these values.
   496  //
   497  // Fixed rows and columns are never skipped.
   498  func (t *Table) SetOffset(row, column int) {
   499  	t.Lock()
   500  	defer t.Unlock()
   501  
   502  	t.rowOffset, t.columnOffset = row, column
   503  	t.trackEnd = false
   504  }
   505  
   506  // GetOffset returns the current row and column offset. This indicates how many
   507  // rows and columns the table is scrolled down and to the right.
   508  func (t *Table) GetOffset() (row, column int) {
   509  	t.RLock()
   510  	defer t.RUnlock()
   511  
   512  	return t.rowOffset, t.columnOffset
   513  }
   514  
   515  // SetEvaluateAllRows sets a flag which determines the rows to be evaluated when
   516  // calculating the widths of the table's columns. When false, only visible rows
   517  // are evaluated. When true, all rows in the table are evaluated.
   518  //
   519  // Set this flag to true to avoid shifting column widths when the table is
   520  // scrolled. (May be slower for large tables.)
   521  func (t *Table) SetEvaluateAllRows(all bool) {
   522  	t.Lock()
   523  	defer t.Unlock()
   524  
   525  	t.evaluateAllRows = all
   526  }
   527  
   528  // SetSelectedFunc sets a handler which is called whenever the user presses the
   529  // Enter key on a selected cell/row/column. The handler receives the position of
   530  // the selection and its cell contents. If entire rows are selected, the column
   531  // index is undefined. Likewise for entire columns.
   532  func (t *Table) SetSelectedFunc(handler func(row, column int)) {
   533  	t.Lock()
   534  	defer t.Unlock()
   535  
   536  	t.selected = handler
   537  }
   538  
   539  // SetSelectionChangedFunc sets a handler which is called whenever the current
   540  // selection changes. The handler receives the position of the new selection.
   541  // If entire rows are selected, the column index is undefined. Likewise for
   542  // entire columns.
   543  func (t *Table) SetSelectionChangedFunc(handler func(row, column int)) {
   544  	t.Lock()
   545  	defer t.Unlock()
   546  
   547  	t.selectionChanged = handler
   548  }
   549  
   550  // SetDoneFunc sets a handler which is called whenever the user presses the
   551  // Escape, Tab, or Backtab key. If nothing is selected, it is also called when
   552  // user presses the Enter key (because pressing Enter on a selection triggers
   553  // the "selected" handler set via SetSelectedFunc()).
   554  func (t *Table) SetDoneFunc(handler func(key tcell.Key)) {
   555  	t.Lock()
   556  	defer t.Unlock()
   557  
   558  	t.done = handler
   559  }
   560  
   561  // SetCell sets the content of a cell the specified position. It is ok to
   562  // directly instantiate a TableCell object. If the cell has content, at least
   563  // the Text and Color fields should be set.
   564  //
   565  // Note that setting cells in previously unknown rows and columns will
   566  // automatically extend the internal table representation, e.g. starting with
   567  // a row of 100,000 will immediately create 100,000 empty rows.
   568  //
   569  // To avoid unnecessary garbage collection, fill columns from left to right.
   570  func (t *Table) SetCell(row, column int, cell *TableCell) {
   571  	t.Lock()
   572  	defer t.Unlock()
   573  
   574  	if row >= len(t.cells) {
   575  		t.cells = append(t.cells, make([][]*TableCell, row-len(t.cells)+1)...)
   576  	}
   577  	rowLen := len(t.cells[row])
   578  	if column >= rowLen {
   579  		t.cells[row] = append(t.cells[row], make([]*TableCell, column-rowLen+1)...)
   580  		for c := rowLen; c < column; c++ {
   581  			t.cells[row][c] = &TableCell{}
   582  		}
   583  	}
   584  	t.cells[row][column] = cell
   585  	if column > t.lastColumn {
   586  		t.lastColumn = column
   587  	}
   588  }
   589  
   590  // SetCellSimple calls SetCell() with the given text, left-aligned, in white.
   591  func (t *Table) SetCellSimple(row, column int, text string) {
   592  	t.SetCell(row, column, NewTableCell(text))
   593  }
   594  
   595  // GetCell returns the contents of the cell at the specified position. A valid
   596  // TableCell object is always returned but it will be uninitialized if the cell
   597  // was not previously set. Such an uninitialized object will not automatically
   598  // be inserted. Therefore, repeated calls to this function may return different
   599  // pointers for uninitialized cells.
   600  func (t *Table) GetCell(row, column int) *TableCell {
   601  	t.RLock()
   602  	defer t.RUnlock()
   603  
   604  	if row >= len(t.cells) || column >= len(t.cells[row]) {
   605  		return &TableCell{}
   606  	}
   607  	return t.cells[row][column]
   608  }
   609  
   610  // RemoveRow removes the row at the given position from the table. If there is
   611  // no such row, this has no effect.
   612  func (t *Table) RemoveRow(row int) {
   613  	t.Lock()
   614  	defer t.Unlock()
   615  
   616  	if row < 0 || row >= len(t.cells) {
   617  		return
   618  	}
   619  
   620  	t.cells = append(t.cells[:row], t.cells[row+1:]...)
   621  }
   622  
   623  // RemoveColumn removes the column at the given position from the table. If
   624  // there is no such column, this has no effect.
   625  func (t *Table) RemoveColumn(column int) {
   626  	t.Lock()
   627  	defer t.Unlock()
   628  
   629  	for row := range t.cells {
   630  		if column < 0 || column >= len(t.cells[row]) {
   631  			continue
   632  		}
   633  		t.cells[row] = append(t.cells[row][:column], t.cells[row][column+1:]...)
   634  	}
   635  }
   636  
   637  // InsertRow inserts a row before the row with the given index. Cells on the
   638  // given row and below will be shifted to the bottom by one row. If "row" is
   639  // equal or larger than the current number of rows, this function has no effect.
   640  func (t *Table) InsertRow(row int) {
   641  	t.Lock()
   642  	defer t.Unlock()
   643  
   644  	if row >= len(t.cells) {
   645  		return
   646  	}
   647  	t.cells = append(t.cells, nil)       // Extend by one.
   648  	copy(t.cells[row+1:], t.cells[row:]) // Shift down.
   649  	t.cells[row] = nil                   // New row is uninitialized.
   650  }
   651  
   652  // InsertColumn inserts a column before the column with the given index. Cells
   653  // in the given column and to its right will be shifted to the right by one
   654  // column. Rows that have fewer initialized cells than "column" will remain
   655  // unchanged.
   656  func (t *Table) InsertColumn(column int) {
   657  	t.Lock()
   658  	defer t.Unlock()
   659  
   660  	for row := range t.cells {
   661  		if column >= len(t.cells[row]) {
   662  			continue
   663  		}
   664  		t.cells[row] = append(t.cells[row], nil)             // Extend by one.
   665  		copy(t.cells[row][column+1:], t.cells[row][column:]) // Shift to the right.
   666  		t.cells[row][column] = &TableCell{}                  // New element is an uninitialized table cell.
   667  	}
   668  }
   669  
   670  // GetRowCount returns the number of rows in the table.
   671  func (t *Table) GetRowCount() int {
   672  	t.RLock()
   673  	defer t.RUnlock()
   674  
   675  	return len(t.cells)
   676  }
   677  
   678  // GetColumnCount returns the (maximum) number of columns in the table.
   679  func (t *Table) GetColumnCount() int {
   680  	t.RLock()
   681  	defer t.RUnlock()
   682  
   683  	if len(t.cells) == 0 {
   684  		return 0
   685  	}
   686  	return t.lastColumn + 1
   687  }
   688  
   689  // cellAt returns the row and column located at the given screen coordinates.
   690  // Each returned value may be negative if there is no row and/or cell. This
   691  // function will also process coordinates outside the table's inner rectangle so
   692  // callers will need to check for bounds themselves.
   693  func (t *Table) cellAt(x, y int) (row, column int) {
   694  	rectX, rectY, _, _ := t.GetInnerRect()
   695  
   696  	// Determine row as seen on screen.
   697  	if t.borders {
   698  		row = (y - rectY - 1) / 2
   699  	} else {
   700  		row = y - rectY
   701  	}
   702  
   703  	// Respect fixed rows and row offset.
   704  	if row >= 0 {
   705  		if row >= t.fixedRows {
   706  			row += t.rowOffset
   707  		}
   708  		if row >= len(t.cells) {
   709  			row = -1
   710  		}
   711  	}
   712  
   713  	// Search for the clicked column.
   714  	column = -1
   715  	if x >= rectX {
   716  		columnX := rectX
   717  		if t.borders {
   718  			columnX++
   719  		}
   720  		for index, width := range t.visibleColumnWidths {
   721  			columnX += width + 1
   722  			if x < columnX {
   723  				column = t.visibleColumnIndices[index]
   724  				break
   725  			}
   726  		}
   727  	}
   728  
   729  	return
   730  }
   731  
   732  // ScrollToBeginning scrolls the table to the beginning to that the top left
   733  // corner of the table is shown. Note that this position may be corrected if
   734  // there is a selection.
   735  func (t *Table) ScrollToBeginning() {
   736  	t.Lock()
   737  	defer t.Unlock()
   738  
   739  	t.trackEnd = false
   740  	t.columnOffset = 0
   741  	t.rowOffset = 0
   742  }
   743  
   744  // ScrollToEnd scrolls the table to the beginning to that the bottom left corner
   745  // of the table is shown. Adding more rows to the table will cause it to
   746  // automatically scroll with the new data. Note that this position may be
   747  // corrected if there is a selection.
   748  func (t *Table) ScrollToEnd() {
   749  	t.Lock()
   750  	defer t.Unlock()
   751  
   752  	t.trackEnd = true
   753  	t.columnOffset = 0
   754  	t.rowOffset = len(t.cells)
   755  }
   756  
   757  // SetSortClicked sets a flag which determines whether the table is sorted when
   758  // a fixed row is clicked. This flag is enabled by default.
   759  func (t *Table) SetSortClicked(sortClicked bool) {
   760  	t.Lock()
   761  	defer t.Unlock()
   762  
   763  	t.sortClicked = sortClicked
   764  }
   765  
   766  // SetSortFunc sets the sorting function used for the table. When unset, a
   767  // case-sensitive string comparison is used.
   768  func (t *Table) SetSortFunc(sortFunc func(column, i, j int) bool) {
   769  	t.Lock()
   770  	defer t.Unlock()
   771  
   772  	t.sortFunc = sortFunc
   773  }
   774  
   775  // Sort sorts the table by the column at the given index. You may set a custom
   776  // sorting function with SetSortFunc.
   777  func (t *Table) Sort(column int, descending bool) {
   778  	t.Lock()
   779  	defer t.Unlock()
   780  
   781  	if len(t.cells) == 0 || column < 0 || column >= len(t.cells[0]) {
   782  		return
   783  	}
   784  
   785  	if t.sortFunc == nil {
   786  		t.sortFunc = func(column, i, j int) bool {
   787  			return bytes.Compare(t.cells[i][column].Text, t.cells[j][column].Text) == -1
   788  		}
   789  	}
   790  
   791  	sort.SliceStable(t.cells, func(i, j int) bool {
   792  		if i < t.fixedRows {
   793  			return i < j
   794  		} else if j < t.fixedRows {
   795  			return j > i
   796  		}
   797  
   798  		if !descending {
   799  			return t.sortFunc(column, i, j)
   800  		}
   801  		return t.sortFunc(column, j, i)
   802  	})
   803  }
   804  
   805  // Draw draws this primitive onto the screen.
   806  func (t *Table) Draw(screen tcell.Screen) {
   807  	if !t.GetVisible() {
   808  		return
   809  	}
   810  
   811  	t.Box.Draw(screen)
   812  
   813  	t.Lock()
   814  	defer t.Unlock()
   815  
   816  	// What's our available screen space?
   817  	x, y, width, height := t.GetInnerRect()
   818  	if t.borders {
   819  		t.visibleRows = height / 2
   820  	} else {
   821  		t.visibleRows = height
   822  	}
   823  
   824  	showVerticalScrollBar := t.scrollBarVisibility == ScrollBarAlways || (t.scrollBarVisibility == ScrollBarAuto && len(t.cells) > t.visibleRows-t.fixedRows)
   825  	if showVerticalScrollBar {
   826  		width-- // Subtract space for scroll bar.
   827  	}
   828  
   829  	// Return the cell at the specified position (nil if it doesn't exist).
   830  	getCell := func(row, column int) *TableCell {
   831  		if row < 0 || column < 0 || row >= len(t.cells) || column >= len(t.cells[row]) {
   832  			return nil
   833  		}
   834  		return t.cells[row][column]
   835  	}
   836  
   837  	// If this cell is not selectable, find the next one.
   838  	if t.rowsSelectable || t.columnsSelectable {
   839  		if t.selectedColumn < 0 {
   840  			t.selectedColumn = 0
   841  		}
   842  		if t.selectedRow < 0 {
   843  			t.selectedRow = 0
   844  		}
   845  		for t.selectedRow < len(t.cells) {
   846  			cell := getCell(t.selectedRow, t.selectedColumn)
   847  			if cell == nil || !cell.NotSelectable {
   848  				break
   849  			}
   850  			t.selectedColumn++
   851  			if t.selectedColumn > t.lastColumn {
   852  				t.selectedColumn = 0
   853  				t.selectedRow++
   854  			}
   855  		}
   856  	}
   857  
   858  	// Clamp row offsets.
   859  	if t.rowsSelectable {
   860  		if t.selectedRow >= t.fixedRows && t.selectedRow < t.fixedRows+t.rowOffset {
   861  			t.rowOffset = t.selectedRow - t.fixedRows
   862  			t.trackEnd = false
   863  		}
   864  		if t.borders {
   865  			if 2*(t.selectedRow+1-t.rowOffset) >= height {
   866  				t.rowOffset = t.selectedRow + 1 - height/2
   867  				t.trackEnd = false
   868  			}
   869  		} else {
   870  			if t.selectedRow+1-t.rowOffset >= height {
   871  				t.rowOffset = t.selectedRow + 1 - height
   872  				t.trackEnd = false
   873  			}
   874  		}
   875  	}
   876  	if t.borders {
   877  		if 2*(len(t.cells)-t.rowOffset) < height {
   878  			t.trackEnd = true
   879  		}
   880  	} else {
   881  		if len(t.cells)-t.rowOffset < height {
   882  			t.trackEnd = true
   883  		}
   884  	}
   885  	if t.trackEnd {
   886  		if t.borders {
   887  			t.rowOffset = len(t.cells) - height/2
   888  		} else {
   889  			t.rowOffset = len(t.cells) - height
   890  		}
   891  	}
   892  	if t.rowOffset < 0 {
   893  		t.rowOffset = 0
   894  	}
   895  
   896  	// Clamp column offset. (Only left side here. The right side is more
   897  	// difficult and we'll do it below.)
   898  	if t.columnsSelectable && t.selectedColumn >= t.fixedColumns && t.selectedColumn < t.fixedColumns+t.columnOffset {
   899  		t.columnOffset = t.selectedColumn - t.fixedColumns
   900  	}
   901  	if t.columnOffset < 0 {
   902  		t.columnOffset = 0
   903  	}
   904  	if t.selectedColumn < 0 {
   905  		t.selectedColumn = 0
   906  	}
   907  
   908  	// Determine the indices and widths of the columns and rows which fit on the
   909  	// screen.
   910  	var (
   911  		columns, rows, allRows, widths []int
   912  		tableHeight, tableWidth        int
   913  	)
   914  	rowStep := 1
   915  	if t.borders {
   916  		rowStep = 2    // With borders, every table row takes two screen rows.
   917  		tableWidth = 1 // We start at the second character because of the left table border.
   918  	}
   919  	if t.evaluateAllRows {
   920  		allRows = make([]int, len(t.cells))
   921  		for row := range t.cells {
   922  			allRows[row] = row
   923  		}
   924  	}
   925  	indexRow := func(row int) bool { // Determine if this row is visible, store its index.
   926  		if tableHeight >= height {
   927  			return false
   928  		}
   929  		rows = append(rows, row)
   930  		tableHeight += rowStep
   931  		return true
   932  	}
   933  	for row := 0; row < t.fixedRows && row < len(t.cells); row++ { // Do the fixed rows first.
   934  		if !indexRow(row) {
   935  			break
   936  		}
   937  	}
   938  	for row := t.fixedRows + t.rowOffset; row < len(t.cells); row++ { // Then the remaining rows.
   939  		if !indexRow(row) {
   940  			break
   941  		}
   942  	}
   943  	var (
   944  		skipped, lastTableWidth, expansionTotal int
   945  		expansions                              []int
   946  	)
   947  ColumnLoop:
   948  	for column := 0; ; column++ {
   949  		// If we've moved beyond the right border, we stop or skip a column.
   950  		for tableWidth-1 >= width { // -1 because we include one extra column if the separator falls on the right end of the box.
   951  			// We've moved beyond the available space.
   952  			if column < t.fixedColumns {
   953  				break ColumnLoop // We're in the fixed area. We're done.
   954  			}
   955  			if !t.columnsSelectable && skipped >= t.columnOffset {
   956  				break ColumnLoop // There is no selection and we've already reached the offset.
   957  			}
   958  			if t.columnsSelectable && t.selectedColumn-skipped == t.fixedColumns {
   959  				break ColumnLoop // The selected column reached the leftmost point before disappearing.
   960  			}
   961  			if t.columnsSelectable && skipped >= t.columnOffset &&
   962  				(t.selectedColumn < column && lastTableWidth < width-1 && tableWidth < width-1 || t.selectedColumn < column-1) {
   963  				break ColumnLoop // We've skipped as many as requested and the selection is visible.
   964  			}
   965  			if len(columns) <= t.fixedColumns {
   966  				break // Nothing to skip.
   967  			}
   968  
   969  			// We need to skip a column.
   970  			skipped++
   971  			lastTableWidth -= widths[t.fixedColumns] + 1
   972  			tableWidth -= widths[t.fixedColumns] + 1
   973  			columns = append(columns[:t.fixedColumns], columns[t.fixedColumns+1:]...)
   974  			widths = append(widths[:t.fixedColumns], widths[t.fixedColumns+1:]...)
   975  			expansions = append(expansions[:t.fixedColumns], expansions[t.fixedColumns+1:]...)
   976  		}
   977  
   978  		// What's this column's width (without expansion)?
   979  		maxWidth := -1
   980  		expansion := 0
   981  		evaluationRows := rows
   982  		if t.evaluateAllRows {
   983  			evaluationRows = allRows
   984  		}
   985  		for _, row := range evaluationRows {
   986  			if cell := getCell(row, column); cell != nil {
   987  				_, _, _, _, _, _, cellWidth := decomposeText(cell.Text, true, false)
   988  				if cell.MaxWidth > 0 && cell.MaxWidth < cellWidth {
   989  					cellWidth = cell.MaxWidth
   990  				}
   991  				if cellWidth > maxWidth {
   992  					maxWidth = cellWidth
   993  				}
   994  				if cell.Expansion > expansion {
   995  					expansion = cell.Expansion
   996  				}
   997  			}
   998  		}
   999  		if maxWidth < 0 {
  1000  			break // No more cells found in this column.
  1001  		}
  1002  
  1003  		// Store new column info at the end.
  1004  		columns = append(columns, column)
  1005  		widths = append(widths, maxWidth)
  1006  		lastTableWidth = tableWidth
  1007  		tableWidth += maxWidth + 1
  1008  		expansions = append(expansions, expansion)
  1009  		expansionTotal += expansion
  1010  	}
  1011  	t.columnOffset = skipped
  1012  
  1013  	// If we have space left, distribute it.
  1014  	if tableWidth < width {
  1015  		toDistribute := width - tableWidth
  1016  		for index, expansion := range expansions {
  1017  			if expansionTotal <= 0 {
  1018  				break
  1019  			}
  1020  			expWidth := toDistribute * expansion / expansionTotal
  1021  			widths[index] += expWidth
  1022  			toDistribute -= expWidth
  1023  			expansionTotal -= expansion
  1024  		}
  1025  		tableWidth = width - toDistribute
  1026  	}
  1027  
  1028  	// Helper function which draws border runes.
  1029  	borderStyle := tcell.StyleDefault.Background(t.backgroundColor).Foreground(t.bordersColor)
  1030  	drawBorder := func(colX, rowY int, ch rune) {
  1031  		screen.SetContent(x+colX, y+rowY, ch, nil, borderStyle)
  1032  	}
  1033  
  1034  	// Draw the cells (and borders).
  1035  	var columnX int
  1036  	if !t.borders {
  1037  		columnX--
  1038  	}
  1039  	for columnIndex, column := range columns {
  1040  		columnWidth := widths[columnIndex]
  1041  		for rowY, row := range rows {
  1042  			if t.borders {
  1043  				// Draw borders.
  1044  				rowY *= 2
  1045  				for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
  1046  					drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
  1047  				}
  1048  				ch := Borders.Cross
  1049  				if columnIndex == 0 {
  1050  					if rowY == 0 {
  1051  						ch = Borders.TopLeft
  1052  					} else {
  1053  						ch = Borders.LeftT
  1054  					}
  1055  				} else if rowY == 0 {
  1056  					ch = Borders.TopT
  1057  				}
  1058  				drawBorder(columnX, rowY, ch)
  1059  				rowY++
  1060  				if rowY >= height {
  1061  					break // No space for the text anymore.
  1062  				}
  1063  				drawBorder(columnX, rowY, Borders.Vertical)
  1064  			} else if columnIndex > 0 {
  1065  				// Draw separator.
  1066  				drawBorder(columnX, rowY, t.separator)
  1067  			}
  1068  
  1069  			// Get the cell.
  1070  			cell := getCell(row, column)
  1071  			if cell == nil {
  1072  				continue
  1073  			}
  1074  
  1075  			// Draw text.
  1076  			finalWidth := columnWidth
  1077  			if columnX+1+columnWidth >= width {
  1078  				finalWidth = width - columnX - 1
  1079  			}
  1080  			cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth
  1081  			_, printed := PrintStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, SetAttributes(tcell.StyleDefault.Foreground(cell.Color), cell.Attributes))
  1082  			if TaggedTextWidth(cell.Text)-printed > 0 && printed > 0 {
  1083  				_, _, style, _ := screen.GetContent(x+columnX+finalWidth, y+rowY)
  1084  				PrintStyle(screen, []byte(string(SemigraphicsHorizontalEllipsis)), x+columnX+finalWidth, y+rowY, 1, AlignLeft, style)
  1085  			}
  1086  		}
  1087  
  1088  		// Draw bottom border.
  1089  		if rowY := 2 * len(rows); t.borders && rowY < height {
  1090  			for pos := 0; pos < columnWidth && columnX+1+pos < width; pos++ {
  1091  				drawBorder(columnX+pos+1, rowY, Borders.Horizontal)
  1092  			}
  1093  			ch := Borders.BottomT
  1094  			if columnIndex == 0 {
  1095  				ch = Borders.BottomLeft
  1096  			}
  1097  			drawBorder(columnX, rowY, ch)
  1098  		}
  1099  
  1100  		columnX += columnWidth + 1
  1101  	}
  1102  
  1103  	// Draw right border.
  1104  	if t.borders && len(t.cells) > 0 && columnX < width {
  1105  		for rowY := range rows {
  1106  			rowY *= 2
  1107  			if rowY+1 < height {
  1108  				drawBorder(columnX, rowY+1, Borders.Vertical)
  1109  			}
  1110  			ch := Borders.RightT
  1111  			if rowY == 0 {
  1112  				ch = Borders.TopRight
  1113  			}
  1114  			drawBorder(columnX, rowY, ch)
  1115  		}
  1116  		if rowY := 2 * len(rows); rowY < height {
  1117  			drawBorder(columnX, rowY, Borders.BottomRight)
  1118  		}
  1119  	}
  1120  
  1121  	if showVerticalScrollBar {
  1122  		// Calculate scroll bar position and dimensions.
  1123  		rows := len(t.cells)
  1124  
  1125  		scrollBarItems := rows - t.fixedRows
  1126  		scrollBarHeight := t.visibleRows - t.fixedRows
  1127  
  1128  		scrollBarX := x + width
  1129  		scrollBarY := y + t.fixedRows
  1130  		if scrollBarX > x+tableWidth {
  1131  			scrollBarX = x + tableWidth
  1132  		}
  1133  
  1134  		padTotalOffset := 1
  1135  		if t.borders {
  1136  			padTotalOffset = 2
  1137  
  1138  			scrollBarItems *= 2
  1139  			scrollBarHeight = (scrollBarHeight * 2) - 1
  1140  
  1141  			scrollBarY += t.fixedRows + 1
  1142  		}
  1143  
  1144  		// Draw scroll bar.
  1145  		cursor := int(float64(scrollBarItems) * (float64(t.rowOffset) / float64(((rows-t.fixedRows)-t.visibleRows)+padTotalOffset)))
  1146  		for printed := 0; printed < scrollBarHeight; printed++ {
  1147  			RenderScrollBar(screen, t.scrollBarVisibility, scrollBarX, scrollBarY+printed, scrollBarHeight, scrollBarItems, cursor, printed, t.hasFocus, t.scrollBarColor)
  1148  		}
  1149  	}
  1150  
  1151  	// TODO Draw horizontal scroll bar
  1152  
  1153  	// Helper function which colors the background of a box.
  1154  	// backgroundColor == tcell.ColorDefault => Don't color the background.
  1155  	// textColor == tcell.ColorDefault => Don't change the text color.
  1156  	// attr == 0 => Don't change attributes.
  1157  	// invert == true => Ignore attr, set text to backgroundColor or t.backgroundColor;
  1158  	//                   set background to textColor.
  1159  	colorBackground := func(fromX, fromY, w, h int, backgroundColor, textColor tcell.Color, attr tcell.AttrMask, invert bool) {
  1160  		for by := 0; by < h && fromY+by < y+height; by++ {
  1161  			for bx := 0; bx < w && fromX+bx < x+width; bx++ {
  1162  				m, c, style, _ := screen.GetContent(fromX+bx, fromY+by)
  1163  				fg, bg, a := style.Decompose()
  1164  				if invert {
  1165  					if fg == textColor || fg == t.bordersColor {
  1166  						fg = backgroundColor
  1167  					}
  1168  					if fg == tcell.ColorDefault {
  1169  						fg = t.backgroundColor
  1170  					}
  1171  					style = style.Background(textColor).Foreground(fg)
  1172  				} else {
  1173  					if backgroundColor != tcell.ColorDefault {
  1174  						bg = backgroundColor
  1175  					}
  1176  					if textColor != tcell.ColorDefault {
  1177  						fg = textColor
  1178  					}
  1179  					if attr != 0 {
  1180  						a = attr
  1181  					}
  1182  					style = SetAttributes(style.Background(bg).Foreground(fg), a)
  1183  				}
  1184  				screen.SetContent(fromX+bx, fromY+by, m, c, style)
  1185  			}
  1186  		}
  1187  	}
  1188  
  1189  	// Color the cell backgrounds. To avoid undesirable artefacts, we combine
  1190  	// the drawing of a cell by background color, selected cells last.
  1191  	type cellInfo struct {
  1192  		x, y, w, h int
  1193  		color      tcell.Color
  1194  		selected   bool
  1195  	}
  1196  	cellsByBackgroundColor := make(map[tcell.Color][]*cellInfo)
  1197  	var backgroundColors []tcell.Color
  1198  	for rowY, row := range rows {
  1199  		columnX := 0
  1200  		rowSelected := t.rowsSelectable && !t.columnsSelectable && row == t.selectedRow
  1201  		for columnIndex, column := range columns {
  1202  			columnWidth := widths[columnIndex]
  1203  			cell := getCell(row, column)
  1204  			if cell == nil {
  1205  				continue
  1206  			}
  1207  			bx, by, bw, bh := x+columnX, y+rowY, columnWidth+1, 1
  1208  			if t.borders {
  1209  				by = y + rowY*2
  1210  				bw++
  1211  				bh = 3
  1212  			}
  1213  			columnSelected := t.columnsSelectable && !t.rowsSelectable && column == t.selectedColumn
  1214  			cellSelected := !cell.NotSelectable && (columnSelected || rowSelected || t.rowsSelectable && t.columnsSelectable && column == t.selectedColumn && row == t.selectedRow)
  1215  			entries, ok := cellsByBackgroundColor[cell.BackgroundColor]
  1216  			cellsByBackgroundColor[cell.BackgroundColor] = append(entries, &cellInfo{
  1217  				x:        bx,
  1218  				y:        by,
  1219  				w:        bw,
  1220  				h:        bh,
  1221  				color:    cell.Color,
  1222  				selected: cellSelected,
  1223  			})
  1224  			if !ok {
  1225  				backgroundColors = append(backgroundColors, cell.BackgroundColor)
  1226  			}
  1227  			columnX += columnWidth + 1
  1228  		}
  1229  	}
  1230  	sort.Slice(backgroundColors, func(i int, j int) bool {
  1231  		// Draw brightest colors last (i.e. on top).
  1232  		r, g, b := backgroundColors[i].RGB()
  1233  		c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
  1234  		_, _, li := c.Hcl()
  1235  		r, g, b = backgroundColors[j].RGB()
  1236  		c = colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
  1237  		_, _, lj := c.Hcl()
  1238  		return li < lj
  1239  	})
  1240  	selFg, selBg, selAttr := t.selectedStyle.Decompose()
  1241  	for _, bgColor := range backgroundColors {
  1242  		entries := cellsByBackgroundColor[bgColor]
  1243  		for _, cell := range entries {
  1244  			if cell.selected {
  1245  				if t.selectedStyle != tcell.StyleDefault {
  1246  					defer colorBackground(cell.x, cell.y, cell.w, cell.h, selBg, selFg, selAttr, false)
  1247  				} else {
  1248  					defer colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, cell.color, 0, true)
  1249  				}
  1250  			} else {
  1251  				colorBackground(cell.x, cell.y, cell.w, cell.h, bgColor, tcell.ColorDefault, 0, false)
  1252  			}
  1253  		}
  1254  	}
  1255  
  1256  	// Remember column infos.
  1257  	t.visibleColumnIndices, t.visibleColumnWidths = columns, widths
  1258  }
  1259  
  1260  // InputHandler returns the handler for this primitive.
  1261  func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
  1262  	return t.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
  1263  		t.Lock()
  1264  		defer t.Unlock()
  1265  
  1266  		key := event.Key()
  1267  
  1268  		if (!t.rowsSelectable && !t.columnsSelectable && key == tcell.KeyEnter) ||
  1269  			key == tcell.KeyEscape ||
  1270  			key == tcell.KeyTab ||
  1271  			key == tcell.KeyBacktab {
  1272  			if t.done != nil {
  1273  				t.Unlock()
  1274  				t.done(key)
  1275  				t.Lock()
  1276  			}
  1277  			return
  1278  		}
  1279  
  1280  		// Movement functions.
  1281  		previouslySelectedRow, previouslySelectedColumn := t.selectedRow, t.selectedColumn
  1282  		var (
  1283  			validSelection = func(row, column int) bool {
  1284  				if row < t.fixedRows || row >= len(t.cells) || column < t.fixedColumns || column > t.lastColumn {
  1285  					return false
  1286  				}
  1287  				cell := t.cells[row][column]
  1288  				return cell == nil || !cell.NotSelectable
  1289  			}
  1290  
  1291  			home = func() {
  1292  				if t.rowsSelectable {
  1293  					t.selectedRow = 0
  1294  					t.selectedColumn = 0
  1295  				} else {
  1296  					t.trackEnd = false
  1297  					t.rowOffset = 0
  1298  					t.columnOffset = 0
  1299  				}
  1300  			}
  1301  
  1302  			end = func() {
  1303  				if t.rowsSelectable {
  1304  					t.selectedRow = len(t.cells) - 1
  1305  					t.selectedColumn = t.lastColumn
  1306  				} else {
  1307  					t.trackEnd = true
  1308  					t.columnOffset = 0
  1309  				}
  1310  			}
  1311  
  1312  			down = func() {
  1313  				if t.rowsSelectable {
  1314  					if validSelection(t.selectedRow+1, t.selectedColumn) {
  1315  						t.selectedRow++
  1316  					}
  1317  				} else {
  1318  					t.rowOffset++
  1319  				}
  1320  			}
  1321  
  1322  			up = func() {
  1323  				if t.rowsSelectable {
  1324  					if validSelection(t.selectedRow-1, t.selectedColumn) {
  1325  						t.selectedRow--
  1326  					}
  1327  				} else {
  1328  					t.trackEnd = false
  1329  					t.rowOffset--
  1330  				}
  1331  			}
  1332  
  1333  			left = func() {
  1334  				if t.columnsSelectable {
  1335  					if validSelection(t.selectedRow, t.selectedColumn-1) {
  1336  						t.selectedColumn--
  1337  					}
  1338  				} else {
  1339  					t.columnOffset--
  1340  				}
  1341  			}
  1342  
  1343  			right = func() {
  1344  				if t.columnsSelectable {
  1345  					if validSelection(t.selectedRow, t.selectedColumn+1) {
  1346  						t.selectedColumn++
  1347  					}
  1348  				} else {
  1349  					t.columnOffset++
  1350  				}
  1351  			}
  1352  
  1353  			pageDown = func() {
  1354  				offsetAmount := t.visibleRows - t.fixedRows
  1355  				if offsetAmount < 0 {
  1356  					offsetAmount = 0
  1357  				}
  1358  
  1359  				if t.rowsSelectable {
  1360  					t.selectedRow += offsetAmount
  1361  					if t.selectedRow >= len(t.cells) {
  1362  						t.selectedRow = len(t.cells) - 1
  1363  					}
  1364  				} else {
  1365  					t.rowOffset += offsetAmount
  1366  				}
  1367  			}
  1368  
  1369  			pageUp = func() {
  1370  				offsetAmount := t.visibleRows - t.fixedRows
  1371  				if offsetAmount < 0 {
  1372  					offsetAmount = 0
  1373  				}
  1374  
  1375  				if t.rowsSelectable {
  1376  					t.selectedRow -= offsetAmount
  1377  					if t.selectedRow < 0 {
  1378  						t.selectedRow = 0
  1379  					}
  1380  				} else {
  1381  					t.trackEnd = false
  1382  					t.rowOffset -= offsetAmount
  1383  				}
  1384  			}
  1385  		)
  1386  
  1387  		if HitShortcut(event, Keys.MoveFirst, Keys.MoveFirst2) {
  1388  			home()
  1389  		} else if HitShortcut(event, Keys.MoveLast, Keys.MoveLast2) {
  1390  			end()
  1391  		} else if HitShortcut(event, Keys.MoveUp, Keys.MoveUp2, Keys.MovePreviousField) {
  1392  			up()
  1393  		} else if HitShortcut(event, Keys.MoveDown, Keys.MoveDown2, Keys.MoveNextField) {
  1394  			down()
  1395  		} else if HitShortcut(event, Keys.MoveLeft, Keys.MoveLeft2) {
  1396  			left()
  1397  		} else if HitShortcut(event, Keys.MoveRight, Keys.MoveRight2) {
  1398  			right()
  1399  		} else if HitShortcut(event, Keys.MovePreviousPage) {
  1400  			pageUp()
  1401  		} else if HitShortcut(event, Keys.MoveNextPage) {
  1402  			pageDown()
  1403  		} else if HitShortcut(event, Keys.Select, Keys.Select2) {
  1404  			if (t.rowsSelectable || t.columnsSelectable) && t.selected != nil {
  1405  				t.Unlock()
  1406  				t.selected(t.selectedRow, t.selectedColumn)
  1407  				t.Lock()
  1408  			}
  1409  		}
  1410  
  1411  		// If the selection has changed, notify the handler.
  1412  		if t.selectionChanged != nil && ((t.rowsSelectable && previouslySelectedRow != t.selectedRow) || (t.columnsSelectable && previouslySelectedColumn != t.selectedColumn)) {
  1413  			t.Unlock()
  1414  			t.selectionChanged(t.selectedRow, t.selectedColumn)
  1415  			t.Lock()
  1416  		}
  1417  	})
  1418  }
  1419  
  1420  // MouseHandler returns the mouse handler for this primitive.
  1421  func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  1422  	return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  1423  		x, y := event.Position()
  1424  		if !t.InRect(x, y) {
  1425  			return false, nil
  1426  		}
  1427  
  1428  		switch action {
  1429  		case MouseLeftClick:
  1430  			_, tableY, _, _ := t.GetInnerRect()
  1431  			mul := 1
  1432  			maxY := tableY
  1433  			if t.borders {
  1434  				mul = 2
  1435  				maxY = tableY + 1
  1436  			}
  1437  
  1438  			if t.sortClicked && t.fixedRows > 0 && (y >= tableY && y < maxY+(t.fixedRows*mul)) {
  1439  				_, column := t.cellAt(x, y)
  1440  				if t.sortClickedColumn != column {
  1441  					t.sortClickedColumn = column
  1442  					t.sortClickedDescending = false
  1443  				} else {
  1444  					t.sortClickedDescending = !t.sortClickedDescending
  1445  				}
  1446  				t.Sort(column, t.sortClickedDescending)
  1447  
  1448  				if t.columnsSelectable {
  1449  					t.selectedColumn = column
  1450  				}
  1451  			} else if t.rowsSelectable || t.columnsSelectable {
  1452  				t.Select(t.cellAt(x, y))
  1453  			}
  1454  
  1455  			consumed = true
  1456  			setFocus(t)
  1457  		case MouseScrollUp:
  1458  			t.trackEnd = false
  1459  			t.rowOffset--
  1460  			consumed = true
  1461  		case MouseScrollDown:
  1462  			t.rowOffset++
  1463  			consumed = true
  1464  		}
  1465  
  1466  		return
  1467  	})
  1468  }
  1469  

View as plain text