diff --git a/at.go b/at.go new file mode 100644 index 0000000..4121058 --- /dev/null +++ b/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) +} diff --git a/lib/column_editor.go b/lib/column_editor.go new file mode 100644 index 0000000..b3534be --- /dev/null +++ b/lib/column_editor.go @@ -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() +} + diff --git a/lib/format.go b/lib/format.go index f48c6e5..62a4f33 100644 --- a/lib/format.go +++ b/lib/format.go @@ -11,21 +11,40 @@ import ( `time` ) -type Formatter struct {} +type Column struct { + width int + title string +} + +type Formatter struct { + columns []Column +} //----------------------------------------------------------------------------- -func (self *Formatter) Format(entity interface{}) string { - switch entity.(type) { - case *Market: - return self.format_market(entity.(*Market)) - case *Quotes: - return self.format_quotes(entity.(*Quotes)) - } - return `` +func (self *Formatter) Initialize() *Formatter { + self.columns = make([]Column, 15) + + self.columns[0] = Column{ -7, `Ticker`} + self.columns[1] = Column{ 10, `Last`} + self.columns[2] = Column{ 10, `Change`} + self.columns[3] = Column{ 10, `%Change`} + 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}}: ` if m.Dow[`change`][0:1] != `-` { markup += `{{.Dow.change}} ({{.Dow.percent}}) 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 { Now string Header string Stocks []Stock }{ time.Now().Format(`3:04:05pm PST`), - header(), - prettify(quotes), + self.DoHeader(quotes.profile), + self.prettify(quotes), } markup := `{{.Now}} @@ -84,7 +103,7 @@ func (self *Formatter) format_quotes(quotes *Quotes) string { {{.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}}` //markup += fmt.Sprintf("[%v]", quotes.profile.Grouped) template, err := template.New(`quotes`).Parse(markup) @@ -102,45 +121,46 @@ func (self *Formatter) format_quotes(quotes *Quotes) string { } //----------------------------------------------------------------------------- -func header() string { - str := fmt.Sprintf(`%-7s `, `Ticker`) - str += fmt.Sprintf(`%9s `, `Last`) - str += fmt.Sprintf(`%9s `, `Change`) - str += fmt.Sprintf(`%9s `, `%Change`) - str += fmt.Sprintf(`%9s `, `Open`) - str += fmt.Sprintf(`%9s `, `Low`) - str += fmt.Sprintf(`%9s `, `High`) - str += fmt.Sprintf(`%9s `, `52w Low`) - str += fmt.Sprintf(`%9s `, `52w High`) - str += fmt.Sprintf(`%10s `, `Volume`) - str += fmt.Sprintf(`%10s `, `AvgVolume`) - str += fmt.Sprintf(`%9s `, `P/E`) - str += fmt.Sprintf(`%9s `, `Dividend`) - str += fmt.Sprintf(`%9s `, `Yield`) - str += fmt.Sprintf(`%10s`, `MktCap`) +func (self *Formatter) DoHeader(profile *Profile) string { + selected := profile.selected_column + + str := `` + for i,col := range self.columns { + if i != selected { + str += fmt.Sprintf(`%*s`, col.width, col.title) + } else { + str += fmt.Sprintf(`%*s`, col.width, col.title) + } + } + 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)) for i, q := range group(quotes) { - pretty[i].Ticker = pad(q.Ticker, -7) - pretty[i].LastTrade = pad(with_currency(q.LastTrade), 9) - pretty[i].Change = pad(with_currency(q.Change), 9) - pretty[i].ChangePercent = pad(last_of_pair(q.ChangePercent), 9) - pretty[i].Open = pad(with_currency(q.Open), 9) - pretty[i].Low = pad(with_currency(q.Low), 9) - pretty[i].High = pad(with_currency(q.High), 9) - pretty[i].Low52 = pad(with_currency(q.Low52), 9) - pretty[i].High52 = pad(with_currency(q.High52), 9) - pretty[i].Volume = pad(q.Volume, 10) - pretty[i].AvgVolume = pad(q.AvgVolume, 10) - pretty[i].PeRatio = pad(nullify(q.PeRatioX), 9) - pretty[i].Dividend = pad(with_currency(q.Dividend), 9) - pretty[i].Yield = pad(with_percent(q.Yield), 9) - pretty[i].MarketCap = pad(with_currency(q.MarketCapX), 10) + pretty[i].Ticker = pad(q.Ticker, self.columns[0].width) + pretty[i].LastTrade = pad(with_currency(q.LastTrade), self.columns[1].width) + pretty[i].Change = pad(with_currency(q.Change), self.columns[2].width) + pretty[i].ChangePercent = pad(last_of_pair(q.ChangePercent), self.columns[3].width) + pretty[i].Open = pad(with_currency(q.Open), self.columns[4].width) + pretty[i].Low = pad(with_currency(q.Low), self.columns[5].width) + pretty[i].High = pad(with_currency(q.High), self.columns[6].width) + pretty[i].Low52 = pad(with_currency(q.Low52), self.columns[7].width) + pretty[i].High52 = pad(with_currency(q.High52), self.columns[8].width) + pretty[i].Volume = pad(q.Volume, self.columns[9].width) + pretty[i].AvgVolume = pad(q.AvgVolume, self.columns[10].width) + pretty[i].PeRatio = pad(nullify(q.PeRatioX), self.columns[11].width) + pretty[i].Dividend = pad(with_currency(q.Dividend), self.columns[12].width) + pretty[i].Yield = pad(with_percent(q.Yield), self.columns[13].width) + pretty[i].MarketCap = pad(with_currency(q.MarketCapX), self.columns[14].width) } return pretty } diff --git a/lib/profile.go b/lib/profile.go index 9d845e7..86f5290 100644 --- a/lib/profile.go +++ b/lib/profile.go @@ -13,12 +13,13 @@ import ( const moprc = `/.moprc` type Profile struct { - MarketRefresh int - QuotesRefresh int - Grouped bool - Tickers []string - SortBy string - SortOrder string + MarketRefresh int + QuotesRefresh int + Grouped bool + Tickers []string + SortColumn int + Ascending bool + selected_column int } //----------------------------------------------------------------------------- @@ -30,12 +31,14 @@ func (self *Profile) Initialize() *Profile { self.QuotesRefresh = 5 self.Grouped = false self.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`} - self.SortBy = `Ticker` - self.SortOrder = `Desc` + self.SortColumn = 0 + self.Ascending = true self.Save() } else { json.Unmarshal(data, self) } + self.selected_column = -1 + return self } diff --git a/lib/screen.go b/lib/screen.go index 8e67d27..b3b78db 100644 --- a/lib/screen.go +++ b/lib/screen.go @@ -105,9 +105,17 @@ func (self *Screen) DrawLine(x int, y int, str string) { right = open default: if open { - foreground = value + if value >= termbox.AttrBold { + foreground |= value + } else { + foreground = value + } } else { - foreground = termbox.ColorDefault + if value >= termbox.AttrBold { + foreground &= ^value + } else { + foreground = termbox.ColorDefault + } } } } @@ -119,7 +127,7 @@ func (self *Screen) DrawLine(x int, y int, str string) { } else { termbox.SetCell(self.width-len(token)+i, y, char, foreground, background) } - column += 1 + column++ } } termbox.Flush() diff --git a/lib/yahoo_market.go b/lib/yahoo_market.go index 1930989..a3cbaf0 100644 --- a/lib/yahoo_market.go +++ b/lib/yahoo_market.go @@ -57,7 +57,7 @@ func (self *Market) Fetch() *Market { //----------------------------------------------------------------------------- func (self *Market) Format() string { - return new(Formatter).Format(self) + return new(Formatter).Initialize().DoMarket(self) } // private diff --git a/lib/yahoo_quotes.go b/lib/yahoo_quotes.go index 35eb6ce..0b3aa76 100644 --- a/lib/yahoo_quotes.go +++ b/lib/yahoo_quotes.go @@ -57,9 +57,9 @@ type Stock struct { } type Quotes struct { - market *Market - profile *Profile - stocks []Stock + market *Market + profile *Profile + stocks []Stock } //----------------------------------------------------------------------------- @@ -95,7 +95,7 @@ func (self *Quotes) Fetch() *Quotes { //----------------------------------------------------------------------------- func (self *Quotes) Format() string { - return new(Formatter).Format(self) + return new(Formatter).Initialize().DoQuotes(self) } //----------------------------------------------------------------------------- diff --git a/mop.go b/mop.go index 8941cb4..6637dc1 100644 --- a/mop.go +++ b/mop.go @@ -11,6 +11,7 @@ import ( //----------------------------------------------------------------------------- func mainLoop(screen *mop.Screen, profile *mop.Profile) { var line_editor *mop.LineEditor + var column_editor *mop.ColumnEditor keyboard_queue := make(chan termbox.Event) timestamp_queue := time.NewTicker(1 * time.Second) quotes_queue := time.NewTicker(5 * time.Second) @@ -32,21 +33,28 @@ loop: case event := <-keyboard_queue: switch event.Type { case termbox.EventKey: - if line_editor == nil { + if line_editor == nil && column_editor == nil { if event.Key == termbox.KeyEsc { break loop } else if event.Ch == '+' || event.Ch == '-' { line_editor = new(mop.LineEditor).Initialize(screen, quotes) 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() screen.Draw(quotes) } - } else { + } else if line_editor != nil { done := line_editor.Handle(event) if done { line_editor = nil } + } else if column_editor != nil { + done := column_editor.Handle(event) + if done { + column_editor = nil + } } case termbox.EventResize: screen.Resize()