Initial implementation of column editor

master
Michael Dvorkin 11 years ago
parent 3ca08c6522
commit 7668f80e41
  1. 14
      at.go
  2. 91
      lib/column_editor.go
  3. 112
      lib/format.go
  4. 11
      lib/profile.go
  5. 10
      lib/screen.go
  6. 2
      lib/yahoo_market.go
  7. 2
      lib/yahoo_quotes.go
  8. 14
      mop.go

14
at.go

@ -0,0 +1,14 @@
package main
import (
`fmt`
`github.com/nsf/termbox-go`
)
func main() {
fore := termbox.ColorGreen | termbox.AttrUnderline
fmt.Printf("f: %08b\n", fore)
fore = termbox.ColorGreen | termbox.AttrUnderline | termbox.AttrReverse
fmt.Printf("f: %08b\n", fore)
fore &= ^termbox.AttrReverse
fmt.Printf("f: %08b\n", fore)
}

@ -0,0 +1,91 @@
// Copyright (c) 2013 by Michael Dvorkin. All Rights Reserved.
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
package mop
import (
// `regexp`
// `strings`
`github.com/nsf/termbox-go`
)
type ColumnEditor struct {
screen *Screen
profile *Profile
formatter *Formatter
}
//-----------------------------------------------------------------------------
func (self *ColumnEditor) Initialize(screen *Screen, profile *Profile) *ColumnEditor {
self.screen = screen
self.profile = profile
self.formatter = new(Formatter).Initialize()
self.select_current_column()
return self
}
//-----------------------------------------------------------------------------
func (self *ColumnEditor) Handle(ev termbox.Event) bool {
defer self.redraw_header()
switch ev.Key {
case termbox.KeyEsc:
return self.done()
case termbox.KeyEnter:
return self.execute().done()
case termbox.KeyArrowLeft:
self.select_left_column()
case termbox.KeyArrowRight:
self.select_right_column()
}
return false
}
//-----------------------------------------------------------------------------
func (self *ColumnEditor) select_current_column() *ColumnEditor {
self.profile.selected_column = self.profile.SortColumn
self.redraw_header()
return self
}
//-----------------------------------------------------------------------------
func (self *ColumnEditor) select_left_column() *ColumnEditor {
self.profile.selected_column--
if self.profile.selected_column < 0 {
self.profile.selected_column = self.formatter.TotalColumns() - 1
}
return self
}
//-----------------------------------------------------------------------------
func (self *ColumnEditor) select_right_column() *ColumnEditor {
self.profile.selected_column++
if self.profile.selected_column > self.formatter.TotalColumns() - 1 {
self.profile.selected_column = 0
}
return self
}
//-----------------------------------------------------------------------------
func (self *ColumnEditor) execute() *ColumnEditor {
return self
}
//-----------------------------------------------------------------------------
func (self *ColumnEditor) done() bool {
self.profile.selected_column = -1
return true
}
//-----------------------------------------------------------------------------
func (self *ColumnEditor) redraw_header() {
self.screen.DrawLine(0, 4, self.formatter.DoHeader(self.profile))
termbox.Flush()
}

@ -11,21 +11,40 @@ import (
`time` `time`
) )
type Formatter struct {} type Column struct {
width int
title string
}
type Formatter struct {
columns []Column
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (self *Formatter) Format(entity interface{}) string { func (self *Formatter) Initialize() *Formatter {
switch entity.(type) { self.columns = make([]Column, 15)
case *Market:
return self.format_market(entity.(*Market)) self.columns[0] = Column{ -7, `Ticker`}
case *Quotes: self.columns[1] = Column{ 10, `Last`}
return self.format_quotes(entity.(*Quotes)) self.columns[2] = Column{ 10, `Change`}
} self.columns[3] = Column{ 10, `%Change`}
return `` self.columns[4] = Column{ 10, `Open`}
self.columns[5] = Column{ 10, `Low`}
self.columns[6] = Column{ 10, `High`}
self.columns[7] = Column{ 10, `52w Low`}
self.columns[8] = Column{ 10, `52w High`}
self.columns[9] = Column{ 11, `Volume`}
self.columns[10] = Column{ 11, `AvgVolume`}
self.columns[11] = Column{ 10, `P/E`}
self.columns[12] = Column{ 10, `Dividend`}
self.columns[13] = Column{ 10, `Yield`}
self.columns[14] = Column{ 11, `MktCap`}
return self
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (self *Formatter) format_market(m *Market) string { func (self *Formatter) DoMarket(m *Market) string {
markup := `{{.Dow.name}}: ` markup := `{{.Dow.name}}: `
if m.Dow[`change`][0:1] != `-` { if m.Dow[`change`][0:1] != `-` {
markup += `<green>{{.Dow.change}} ({{.Dow.percent}})</green> at {{.Dow.latest}}, ` markup += `<green>{{.Dow.change}} ({{.Dow.percent}})</green> at {{.Dow.latest}}, `
@ -68,15 +87,15 @@ func (self *Formatter) format_market(m *Market) string {
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (self *Formatter) format_quotes(quotes *Quotes) string { func (self *Formatter) DoQuotes(quotes *Quotes) string {
vars := struct { vars := struct {
Now string Now string
Header string Header string
Stocks []Stock Stocks []Stock
}{ }{
time.Now().Format(`3:04:05pm PST`), time.Now().Format(`3:04:05pm PST`),
header(), self.DoHeader(quotes.profile),
prettify(quotes), self.prettify(quotes),
} }
markup := `<right><white>{{.Now}}</white></right> markup := `<right><white>{{.Now}}</white></right>
@ -84,7 +103,7 @@ func (self *Formatter) format_quotes(quotes *Quotes) string {
{{.Header}} {{.Header}}
{{range .Stocks}}{{.Color}}{{.Ticker}} {{.LastTrade}} {{.Change}} {{.ChangePercent}} {{.Open}} {{.Low}} {{.High}} {{.Low52}} {{.High52}} {{.Volume}} {{.AvgVolume}} {{.PeRatio}} {{.Dividend}} {{.Yield}} {{.MarketCap}} {{range.Stocks}}{{.Color}}{{.Ticker}}{{.LastTrade}}{{.Change}}{{.ChangePercent}}{{.Open}}{{.Low}}{{.High}}{{.Low52}}{{.High52}}{{.Volume}}{{.AvgVolume}}{{.PeRatio}}{{.Dividend}}{{.Yield}}{{.MarketCap}}
{{end}}` {{end}}`
//markup += fmt.Sprintf("[%v]", quotes.profile.Grouped) //markup += fmt.Sprintf("[%v]", quotes.profile.Grouped)
template, err := template.New(`quotes`).Parse(markup) template, err := template.New(`quotes`).Parse(markup)
@ -102,45 +121,46 @@ func (self *Formatter) format_quotes(quotes *Quotes) string {
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func header() string { func (self *Formatter) DoHeader(profile *Profile) string {
str := fmt.Sprintf(`<u>%-7s `, `Ticker`) selected := profile.selected_column
str += fmt.Sprintf(`%9s `, `Last`)
str += fmt.Sprintf(`%9s `, `Change`) str := `<u>`
str += fmt.Sprintf(`%9s `, `%Change`) for i,col := range self.columns {
str += fmt.Sprintf(`%9s `, `Open`) if i != selected {
str += fmt.Sprintf(`%9s `, `Low`) str += fmt.Sprintf(`%*s`, col.width, col.title)
str += fmt.Sprintf(`%9s `, `High`) } else {
str += fmt.Sprintf(`%9s `, `52w Low`) str += fmt.Sprintf(`<r>%*s</r>`, col.width, col.title)
str += fmt.Sprintf(`%9s `, `52w High`) }
str += fmt.Sprintf(`%10s `, `Volume`) }
str += fmt.Sprintf(`%10s `, `AvgVolume`) str += `</u>`
str += fmt.Sprintf(`%9s `, `P/E`)
str += fmt.Sprintf(`%9s `, `Dividend`)
str += fmt.Sprintf(`%9s `, `Yield`)
str += fmt.Sprintf(`%10s</u>`, `MktCap`)
return str return str
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func prettify(quotes *Quotes) []Stock { func (self *Formatter) TotalColumns() int {
return len(self.columns)
}
//-----------------------------------------------------------------------------
func (self *Formatter) prettify(quotes *Quotes) []Stock {
pretty := make([]Stock, len(quotes.stocks)) pretty := make([]Stock, len(quotes.stocks))
for i, q := range group(quotes) { for i, q := range group(quotes) {
pretty[i].Ticker = pad(q.Ticker, -7) pretty[i].Ticker = pad(q.Ticker, self.columns[0].width)
pretty[i].LastTrade = pad(with_currency(q.LastTrade), 9) pretty[i].LastTrade = pad(with_currency(q.LastTrade), self.columns[1].width)
pretty[i].Change = pad(with_currency(q.Change), 9) pretty[i].Change = pad(with_currency(q.Change), self.columns[2].width)
pretty[i].ChangePercent = pad(last_of_pair(q.ChangePercent), 9) pretty[i].ChangePercent = pad(last_of_pair(q.ChangePercent), self.columns[3].width)
pretty[i].Open = pad(with_currency(q.Open), 9) pretty[i].Open = pad(with_currency(q.Open), self.columns[4].width)
pretty[i].Low = pad(with_currency(q.Low), 9) pretty[i].Low = pad(with_currency(q.Low), self.columns[5].width)
pretty[i].High = pad(with_currency(q.High), 9) pretty[i].High = pad(with_currency(q.High), self.columns[6].width)
pretty[i].Low52 = pad(with_currency(q.Low52), 9) pretty[i].Low52 = pad(with_currency(q.Low52), self.columns[7].width)
pretty[i].High52 = pad(with_currency(q.High52), 9) pretty[i].High52 = pad(with_currency(q.High52), self.columns[8].width)
pretty[i].Volume = pad(q.Volume, 10) pretty[i].Volume = pad(q.Volume, self.columns[9].width)
pretty[i].AvgVolume = pad(q.AvgVolume, 10) pretty[i].AvgVolume = pad(q.AvgVolume, self.columns[10].width)
pretty[i].PeRatio = pad(nullify(q.PeRatioX), 9) pretty[i].PeRatio = pad(nullify(q.PeRatioX), self.columns[11].width)
pretty[i].Dividend = pad(with_currency(q.Dividend), 9) pretty[i].Dividend = pad(with_currency(q.Dividend), self.columns[12].width)
pretty[i].Yield = pad(with_percent(q.Yield), 9) pretty[i].Yield = pad(with_percent(q.Yield), self.columns[13].width)
pretty[i].MarketCap = pad(with_currency(q.MarketCapX), 10) pretty[i].MarketCap = pad(with_currency(q.MarketCapX), self.columns[14].width)
} }
return pretty return pretty
} }

@ -17,8 +17,9 @@ type Profile struct {
QuotesRefresh int QuotesRefresh int
Grouped bool Grouped bool
Tickers []string Tickers []string
SortBy string SortColumn int
SortOrder string Ascending bool
selected_column int
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -30,12 +31,14 @@ func (self *Profile) Initialize() *Profile {
self.QuotesRefresh = 5 self.QuotesRefresh = 5
self.Grouped = false self.Grouped = false
self.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`} self.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`}
self.SortBy = `Ticker` self.SortColumn = 0
self.SortOrder = `Desc` self.Ascending = true
self.Save() self.Save()
} else { } else {
json.Unmarshal(data, self) json.Unmarshal(data, self)
} }
self.selected_column = -1
return self return self
} }

@ -105,13 +105,21 @@ func (self *Screen) DrawLine(x int, y int, str string) {
right = open right = open
default: default:
if open { if open {
if value >= termbox.AttrBold {
foreground |= value
} else {
foreground = value foreground = value
}
} else {
if value >= termbox.AttrBold {
foreground &= ^value
} else { } else {
foreground = termbox.ColorDefault foreground = termbox.ColorDefault
} }
} }
} }
} }
}
for i, char := range token { for i, char := range token {
if !right { if !right {
@ -119,7 +127,7 @@ func (self *Screen) DrawLine(x int, y int, str string) {
} else { } else {
termbox.SetCell(self.width-len(token)+i, y, char, foreground, background) termbox.SetCell(self.width-len(token)+i, y, char, foreground, background)
} }
column += 1 column++
} }
} }
termbox.Flush() termbox.Flush()

@ -57,7 +57,7 @@ func (self *Market) Fetch() *Market {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (self *Market) Format() string { func (self *Market) Format() string {
return new(Formatter).Format(self) return new(Formatter).Initialize().DoMarket(self)
} }
// private // private

@ -95,7 +95,7 @@ func (self *Quotes) Fetch() *Quotes {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func (self *Quotes) Format() string { func (self *Quotes) Format() string {
return new(Formatter).Format(self) return new(Formatter).Initialize().DoQuotes(self)
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

@ -11,6 +11,7 @@ import (
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func mainLoop(screen *mop.Screen, profile *mop.Profile) { func mainLoop(screen *mop.Screen, profile *mop.Profile) {
var line_editor *mop.LineEditor var line_editor *mop.LineEditor
var column_editor *mop.ColumnEditor
keyboard_queue := make(chan termbox.Event) keyboard_queue := make(chan termbox.Event)
timestamp_queue := time.NewTicker(1 * time.Second) timestamp_queue := time.NewTicker(1 * time.Second)
quotes_queue := time.NewTicker(5 * time.Second) quotes_queue := time.NewTicker(5 * time.Second)
@ -32,21 +33,28 @@ loop:
case event := <-keyboard_queue: case event := <-keyboard_queue:
switch event.Type { switch event.Type {
case termbox.EventKey: case termbox.EventKey:
if line_editor == nil { if line_editor == nil && column_editor == nil {
if event.Key == termbox.KeyEsc { if event.Key == termbox.KeyEsc {
break loop break loop
} else if event.Ch == '+' || event.Ch == '-' { } else if event.Ch == '+' || event.Ch == '-' {
line_editor = new(mop.LineEditor).Initialize(screen, quotes) line_editor = new(mop.LineEditor).Initialize(screen, quotes)
line_editor.Prompt(event.Ch) line_editor.Prompt(event.Ch)
} else if event.Ch == 'g' { } else if event.Ch == 'o' || event.Ch == 'O' {
column_editor = new(mop.ColumnEditor).Initialize(screen, profile)
} else if event.Ch == 'g' || event.Ch == 'G' {
profile.Regroup() profile.Regroup()
screen.Draw(quotes) screen.Draw(quotes)
} }
} else { } else if line_editor != nil {
done := line_editor.Handle(event) done := line_editor.Handle(event)
if done { if done {
line_editor = nil line_editor = nil
} }
} else if column_editor != nil {
done := column_editor.Handle(event)
if done {
column_editor = nil
}
} }
case termbox.EventResize: case termbox.EventResize:
screen.Resize() screen.Resize()

Loading…
Cancel
Save