Merge remote-tracking branch 'origin/feature/mouse' into feature/tcell

pull/232/head
Simon Roberts 3 years ago
commit 1ec5735d95
No known key found for this signature in database
GPG Key ID: 0F30F99E6B771FD4

@ -47,6 +47,7 @@ type State struct {
defaultView string
defaultChartRange string
maxChartWidth int
columnLookup []string
favorites map[string]bool
favoritesTableColumns []string

@ -3,6 +3,7 @@ package cointop
import (
"errors"
"fmt"
"regexp"
"sort"
"strings"
@ -301,3 +302,23 @@ func CurrencySymbol(currency string) string {
return "?"
}
// StatusbarMouseLeftClick is called on mouse left click event
func (ct *Cointop) ConversionMouseLeftClick() error {
v, x, y, err := ct.g.GetViewRelativeMousePosition(ct.g.CurrentEvent)
if err != nil {
return err
}
// Find the menu entry that includes the mouse position
line := v.BufferLines()[y]
matches := regexp.MustCompile(`\[ . \] \w+ [^\[]+`).FindAllStringIndex(line, -1)
for _, match := range matches {
if x >= match[0] && x <= match[1] {
s := line[match[0]:match[1]]
convert := strings.Split(s, " ")[3]
return ct.SetCurrencyConverstionFn(convert)()
}
}
return nil
}

@ -85,5 +85,7 @@ func DefaultShortcuts() map[string]string {
"<": "scroll_left",
"+": "show_price_alert_add_menu",
"\\\\": "toggle_table_fullscreen",
"scrollup": "move_up_or_previous_page",
"scrolldown": "move_down_or_next_page",
}
}

@ -4,6 +4,7 @@ import (
"strings"
"github.com/miguelmota/gocui"
log "github.com/sirupsen/logrus"
)
// ParseKeys returns string keyboard key as gocui key type
@ -162,6 +163,10 @@ func (ct *Cointop) ParseKeys(s string) (interface{}, gocui.Modifier) {
key = gocui.KeyEnd
case "\\\\":
key = '\\'
case "scrollup":
key = gocui.MouseWheelUp
case "scrolldown":
key = gocui.MouseWheelDown
}
return key, mod
@ -368,12 +373,13 @@ func (ct *Cointop) SetKeybindings() error {
ct.DeleteKeybindingMod(key, mod, "")
// mouse events
ct.SetKeybindingMod(gocui.MouseRelease, gocui.ModNone, ct.Keyfn(ct.MouseRelease), "")
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.MouseLeftClick), "")
ct.SetKeybindingMod(gocui.MouseMiddle, gocui.ModNone, ct.Keyfn(ct.MouseMiddleClick), "")
ct.SetKeybindingMod(gocui.MouseRight, gocui.ModNone, ct.Keyfn(ct.MouseRightClick), "")
ct.SetKeybindingMod(gocui.MouseWheelUp, gocui.ModNone, ct.Keyfn(ct.MouseWheelUp), "")
ct.SetKeybindingMod(gocui.MouseWheelDown, gocui.ModNone, ct.Keyfn(ct.MouseWheelDown), "")
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.MouseLeftClick), ct.Views.Table.Name()) // click to focus
// clicking table headers sorts table
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.TableHeaderMouseLeftClick), ct.Views.TableHeader.Name())
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.StatusbarMouseLeftClick), ct.Views.Statusbar.Name())
// debug mouse clicks
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.MouseDebug), "")
// character key press to select option
// TODO: use scrolling table
@ -381,7 +387,18 @@ func (ct *Cointop) SetKeybindings() error {
for i, k := range keys {
ct.SetKeybindingMod(alphanumericcharacters[i], gocui.ModNone, ct.Keyfn(ct.SetCurrencyConverstionFn(k)), ct.Views.Menu.Name())
}
ct.SetKeybindingMod(gocui.MouseLeft, gocui.ModNone, ct.Keyfn(ct.ConversionMouseLeftClick), ct.Views.Menu.Name())
return nil
}
// MouseLeftClickDebug emit a debug message about which View and coordinates are in MouseClick
// TODO: delete before merge
func (ct *Cointop) MouseDebug() error {
v, x, y, err := ct.g.GetViewRelativeMousePosition(ct.g.CurrentEvent)
if err != nil {
return err
}
log.Debugf("XXX MouseDebug view=%s %d,%d", v.Name(), x, y)
return nil
}

@ -560,34 +560,9 @@ func (ct *Cointop) TableScrollRight() error {
return nil
}
// MouseRelease is called on mouse releae event
func (ct *Cointop) MouseRelease() error {
return nil
}
// MouseLeftClick is called on mouse left click event
func (ct *Cointop) MouseLeftClick() error {
return nil
}
// MouseMiddleClick is called on mouse middle click event
func (ct *Cointop) MouseMiddleClick() error {
return nil
}
// MouseRightClick is called on mouse right click event
func (ct *Cointop) MouseRightClick() error {
return ct.OpenLink()
}
// MouseWheelUp is called on mouse wheel up event
func (ct *Cointop) MouseWheelUp() error {
return nil
}
// MouseWheelDown is called on mouse wheel down event
func (ct *Cointop) MouseWheelDown() error {
return nil
return ct.g.SetCursorFromCurrentMouseEvent()
}
// TableRowsLen returns the number of table row entries

@ -2,6 +2,8 @@ package cointop
import (
"fmt"
"regexp"
"strings"
"unicode/utf8"
"github.com/cointop-sh/cointop/pkg/open"
@ -83,3 +85,50 @@ func (ct *Cointop) RefreshRowLink() error {
return nil
}
// StatusbarMouseLeftClick is called on mouse left click event
func (ct *Cointop) StatusbarMouseLeftClick() error {
_, x, _, err := ct.g.GetViewRelativeMousePosition(ct.g.CurrentEvent)
if err != nil {
return err
}
// Parse the statusbar text to identify hotspots and actions
b := make([]byte, 1000)
ct.Views.Statusbar.Rewind()
if n, err := ct.Views.Statusbar.Read(b); err == nil {
// Find all the "[X]word" substrings, then look for the one that was clicked
matches := regexp.MustCompile(`\[.*?\]\w+`).FindAllIndex(b[:n], -1)
for _, match := range matches {
if x >= match[0] && x <= match[1] {
s := string(b[match[0]:match[1]])
word := strings.Split(s, "]")[1] // matches the \w+ from regex
// Quit/Return Help Chart Range Search Convert Favorites Portfolio Edit(portfolio) Unfavorite
switch word {
case "Help":
ct.ToggleHelp()
case "Range":
// left hand edge of "Range" is Prev, the rest is Next
if x-match[0] < 3 {
ct.PrevChartRange()
} else {
ct.NextChartRange()
}
case "Search":
ct.OpenSearch()
case "Convert":
ct.ToggleConvertMenu()
case "Favorites":
ct.ToggleSelectedView(FavoritesView)
case "Portfolio":
ct.ToggleSelectedView(PortfolioView)
}
}
}
}
return nil
}

@ -184,6 +184,7 @@ func (ct *Cointop) UpdateTableHeader() error {
cols := ct.GetActiveTableHeaders()
var headers []string
var columnLookup []string // list of column-names or ""
for i, col := range cols {
hc, ok := HeaderColumns[col]
if !ok {
@ -227,15 +228,27 @@ func (ct *Cointop) UpdateTableHeader() error {
if leftAlign {
padfn = pad.Right
}
padded := padfn(label, width+(1-padLeft), " ")
colStr := fmt.Sprintf(
"%s%s%s",
strings.Repeat(" ", padLeft),
colorfn(padfn(label, width+(1-padLeft), " ")),
colorfn(padded),
strings.Repeat(" ", 1),
)
headers = append(headers, colStr)
// Create a lookup table (pos to column)
for i := 0; i < padLeft; i++ {
columnLookup = append(columnLookup, "")
}
for i := 0; i < utf8.RuneCountInString(padded); i++ {
columnLookup = append(columnLookup, hc.Slug)
}
columnLookup = append(columnLookup, "")
}
ct.State.columnLookup = columnLookup
ct.UpdateUI(func() error {
return ct.Views.TableHeader.Update(strings.Join(headers, ""))
})
@ -243,6 +256,21 @@ func (ct *Cointop) UpdateTableHeader() error {
return nil
}
// MouseLeftClick is called on mouse left click event
func (ct *Cointop) TableHeaderMouseLeftClick() error {
_, x, _, err := ct.g.GetViewRelativeMousePosition(ct.g.CurrentEvent)
if err != nil {
return err
}
// Figure out which column they clicked on
if ct.State.columnLookup[x] != "" {
fn := ct.Sortfn(ct.State.columnLookup[x], false)
return fn(ct.g, ct.Views.Table.Backing())
}
return nil
}
// SetTableColumnAlignLeft sets the column alignment direction for header
func (ct *Cointop) SetTableColumnAlignLeft(header string, alignLeft bool) {
ct.State.tableColumnAlignLeft.Store(header, alignLeft)

@ -66,6 +66,9 @@ type Gui struct {
// If ASCII is true then use ASCII instead of unicode to draw the
// interface. Using ASCII is more portable.
ASCII bool
// The current event while in the handlers.
CurrentEvent *termbox.Event
}
// NewGui returns a new Gui object with a given output mode.
@ -601,14 +604,13 @@ func (g *Gui) onKey(ev *termbox.Event) error {
g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
}
case termbox.EventMouse:
mx, my := ev.MouseX, ev.MouseY
v, err := g.ViewByPosition(mx, my)
v, _, _, err := g.GetViewRelativeMousePosition(ev)
if err != nil {
break
}
if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
return err
}
// If the key-binding wants to move the cursor, it should call SetCursorFromCurrentMouseEvent()
// Not all mouse events will want to do this (eg: scroll wheel)
g.CurrentEvent = ev
if _, err := g.execKeybindings(v, ev); err != nil {
return err
}
@ -617,6 +619,29 @@ func (g *Gui) onKey(ev *termbox.Event) error {
return nil
}
// GetViewRelativeMousePosition returns the View and relative x/y for the provided mouse event.
func (g *Gui) GetViewRelativeMousePosition(ev *termbox.Event) (*View, int, int, error) {
// TODO: check ev.Type == termbox.EventMouse ?
mx, my := ev.MouseX, ev.MouseY
v, err := g.ViewByPosition(mx, my)
if err != nil {
return nil, 0, 0, err
}
return v, mx - v.x0 - 1, my - v.y0 - 1, nil
}
// SetCursorFromCurrentMouseEvent updates the cursor position based on the mouse coordinates.
func (g *Gui) SetCursorFromCurrentMouseEvent() error {
v, x, y, err := g.GetViewRelativeMousePosition(g.CurrentEvent)
if err != nil {
return err
}
if err := v.SetCursor(x, y); err != nil {
return err
}
return nil
}
// execKeybindings executes the keybinding handlers that match the passed view
// and event. The value of matched is true if there is a match and no errors.
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {

Loading…
Cancel
Save