tally/sheet.go

291 lines
6.5 KiB
Go

package main
import (
"fmt"
"strings"
"tildegit.org/sloum/tally/qline"
)
type sheet struct {
name string
selection point
cells [][]cell
cols int
rows int
zoom int
rowOff int
colOff int
yankBuff cell
yankPoint point
}
func (s sheet) Draw(termCols, termRows int) {
var selected bool
rowDigits := DigitCount(len(s.cells))
width := (termCols - rowDigits) / s.zoom
if width * s.zoom + rowDigits + 1 > termCols {
width -= 1
}
// Print column header
fmt.Printf("%s ", strings.Repeat(" ", rowDigits))
for i := s.colOff+1; i <= s.colOff + s.zoom; i++ {
if i > s.cols {
break
}
pre := "\033[7m"
if i == s.selection.col {
pre = ""
}
fmt.Printf("%s%-*c\033[0m", pre, width, i+64)
}
fmt.Print("\n")
// Print Rows
for row, r := range s.cells[s.rowOff:] {
if row >= termRows-5 {
break
}
mod := "\033[7m"
if row + s.rowOff == s.selection.row-1 {
mod = ""
}
fmt.Printf("%s%-*d \033[0m", mod, rowDigits, row+1+s.rowOff)
// Print Cells
for col, cell := range r[s.colOff:] {
if col >= s.zoom {
break
}
selected = false
if s.selection.row-1 == row + s.rowOff && s.selection.col-1 == col + s.colOff {
selected = true
}
fmt.Printf(cell.String(width, selected))
}
fmt.Print("\n\033[2K")
}
}
func (s sheet) CurrentValue(raw bool) string {
if raw {
return s.cells[s.selection.row-1][s.selection.col-1].rawVal
}
return s.cells[s.selection.row-1][s.selection.col-1].mask
}
func (s *sheet) Recalculate() {
for row, _ := range s.cells {
for col, _ := range s.cells[row] {
// TODO do this concurrently
s.cells[row][col].Calculate()
}
}
}
func (s *sheet) AddRows(count int) {
for ;count > 0; count-- {
s.cells = append(s.cells, make([]cell, s.cols))
s.rows++
}
}
func (s *sheet) AddCols(count int) {
for ;count > 0; count-- {
for i, _ := range s.cells {
s.cells[i] = append(s.cells[i], cell{})
}
s.cols++
}
}
func (s *sheet) Yank() {
c := s.cells[s.selection.row-1][s.selection.col-1]
s.yankBuff = cell{}
s.yankBuff.kind = c.kind
s.yankBuff.rawVal = c.rawVal
s.yankBuff.num = c.num
s.yankBuff.mask = c.mask
s.yankBuff.expr = append(s.yankBuff.expr, c.expr...)
s.yankBuff.mods = append(s.yankBuff.mods, c.mods...)
s.yankPoint = point{row: s.selection.row-1, col: s.selection.col-1}
}
func (s *sheet) Paste() {
c := cell{}
c.kind = s.yankBuff.kind
c.rawVal = s.yankBuff.rawVal
c.num = s.yankBuff.num
c.mask = s.yankBuff.mask
c.expr = append(c.expr, s.yankBuff.expr...)
c.mods = append(c.mods, s.yankBuff.mods...)
s.cells[s.selection.row-1][s.selection.col-1] = c
}
func (s *sheet) PasteRelative() {
c := cell{}
c.kind = s.yankBuff.kind
c.rawVal = s.yankBuff.rawVal
c.num = s.yankBuff.num
c.mask = s.yankBuff.mask
c.expr = append(c.expr, s.yankBuff.expr...)
c.mods = append(c.mods, s.yankBuff.mods...)
if c.kind == Expr {
for i, v := range c.expr {
if IsAddr(v) {
refPoint, lockRow, lockCol := Addr2Point(v)
diff := point{row: s.yankPoint.row-s.selection.row+1, col: s.yankPoint.col-s.selection.col+1}
diffApply := point{row: refPoint.row, col: refPoint.col}
if !lockRow {
diffApply.row = refPoint.row-diff.row
}
if !lockCol {
diffApply.col = refPoint.col-diff.col
}
p2a := Point2Addr(diffApply, lockRow, lockCol)
c.expr[i] = p2a
} else if IsRange(v) {
op := v[len(v)-1]
points := strings.Split(v[:len(v)-1], ":")
startRefPoint, lockStartRow, lockStartCol := Addr2Point(points[0])
endRefPoint, lockEndRow, lockEndCol := Addr2Point(points[1])
diff := point{row: s.yankPoint.row-s.selection.row+1, col: s.yankPoint.col-s.selection.col+1}
startApply := point{row: startRefPoint.row, col: startRefPoint.col}
endApply := point{row: endRefPoint.row, col: endRefPoint.col}
if !lockStartRow {
startApply.row = startRefPoint.row-diff.row
}
if !lockStartCol {
startApply.col = startRefPoint.col-diff.col
}
if !lockEndRow {
endApply.row = endRefPoint.row-diff.row
}
if !lockEndCol {
endApply.col = endRefPoint.col-diff.col
}
c.expr[i] = fmt.Sprintf("%s:%s%c", Point2Addr(startApply, lockStartRow, lockStartCol), Point2Addr(endApply, lockEndRow, lockEndCol), op)
}
}
c.rawVal = strings.Join(c.expr, " ")
}
s.cells[s.selection.row-1][s.selection.col-1] = c
}
func (s *sheet) moveSelection(dir rune, termRows int) {
switch dir {
case Left, qline.LeftArrow:
if s.selection.col > 1 {
s.selection.col--
}
if s.selection.col <= s.colOff {
s.colOff--
}
case Right, qline.RightArrow:
s.selection.col++
if s.selection.col > s.cols && s.selection.col < 27 {
s.AddCols(1)
}
if s.selection.col > s.colOff + s.zoom && s.cols > s.zoom {
s.colOff++
}
if s.selection.col > 26 {
s.selection.col = 26
}
case Up, qline.UpArrow:
if s.selection.row > 1 {
s.selection.row--
}
if s.selection.row <= s.rowOff {
s.rowOff--
}
case Down, qline.DownArrow:
s.selection.row++
if s.selection.row > s.rows {
s.AddRows(1)
}
if s.selection.row > s.rowOff + termRows - 5 && s.rows > termRows - 5 {
s.rowOff++
}
case RowStart, qline.Home:
s.selection.col = 1
s.colOff = 0
case ToTop, qline.PageUp:
s.selection.row = 1
s.rowOff = 0
case RowEnd, qline.End:
s.selection.col = s.cols
s.colOff = s.cols - s.zoom
if s.colOff < 0 {
s.colOff = 0
}
case ToBottom, qline.PageDown:
s.selection.row = s.rows
s.rowOff = s.rows - termRows + 5
if s.rowOff < 0 {
s.rowOff = 0
}
}
}
func (s *sheet) ZoomIn() {
if s.zoom == 1 {
return
}
s.zoom-- // Zooming in reduces number of cols viewed
}
func (s *sheet) ZoomOut() {
s.zoom++ // Zooming out increases number of cols viewed
}
func (s *sheet) TrimSheet() {
trimmed := false
for rowIndex := s.rows-1; rowIndex > 0; rowIndex-- {
remove := true
for _, cell := range s.cells[rowIndex] {
if cell.kind != Empty {
remove = false
break
}
}
if !remove {
break
}
s.cells = s.cells[:rowIndex]
s.rows--
trimmed = true
}
for colIndex := s.cols-1; colIndex > 0; colIndex-- {
remove := true
for _, row := range s.cells {
if row[colIndex].kind != Empty {
remove = false
break
}
}
if !remove {
break
}
for i, _ := range s.cells {
s.cells[i] = s.cells[i][:len(s.cells[i])-1]
}
s.cols--
trimmed = true
}
if trimmed {
s.selection = point{1,1}
s.rowOff = 0
s.colOff = 0
}
}
func makeSheet(name string) sheet {
if name == "" {
name = "Sheet1"
}
row := make([][]cell,1, 26)
row[0] = make([]cell, 1, 26)
return sheet{name, point{1,1}, row, 1, 1, 6, 0, 0, cell{}, point{}}
}