gtkmenu: Hide non-matching entries

This commit is contained in:
Trevor Slocum 2019-08-26 10:17:29 -07:00
parent c1cfe822b0
commit 3a492a6414
6 changed files with 156 additions and 106 deletions

View File

@ -1,3 +1,6 @@
0.2.1:
- Minor GTK interface updates
0.2.0:
- Added GTK interface
- Added Support for link (URL shortcut) desktop entries

View File

@ -31,6 +31,7 @@ var (
execLabel *gtk.Label
)
// TODO: Load list item icon on show
func initList(container *gtk.Box) {
inputView = newTextView()
setNoExpand(&inputView.Widget)
@ -62,13 +63,10 @@ func initList(container *gtk.Box) {
listBox.SetHExpand(false)
listBox.SetFocusOnClick(false)
_, err = listBox.Connect("button-press-event", func(listBox *gtk.ListBox, ev *gdk.Event) {
mouseEvent := &gdk.EventButton{ev}
if mouseEvent.Type() == gdk.EVENT_2BUTTON_PRESS {
err := listSelect(inputView)
if err != nil {
log.Fatal(err)
}
_, err = listBox.Connect("row-activated", func(_ *gtk.ListBox, _ *gtk.ListBoxRow) {
err := listSelect(inputView)
if err != nil {
log.Fatal(err)
}
})
if err != nil {
@ -88,19 +86,23 @@ func initList(container *gtk.Box) {
lastEntry := len(gmenu.FilteredEntries) - 1
for i, entry := range gmenu.FilteredEntries {
var entry *gmenu.ListEntry
for i, label := range gmenu.Names {
// Capture variables
i := i
entry := entry
row, err := gtk.ListBoxRowNew()
if err != nil {
log.Fatal("failed to create ListBoxRow:", err)
}
row.SetFocusOnClick(false)
if i == lastEntry {
entry = &gmenu.ListEntry{Entry: nil, Label: ""}
} else {
row.SetName("#" + strconv.Itoa(i))
row.SetName("#" + strconv.Itoa(i))
entry = &gmenu.ListEntry{Entry: gmenu.Entries[i], Label: label}
}
container := newBox(gtk.ORIENTATION_HORIZONTAL)
row.Add(container)
@ -111,6 +113,7 @@ func initList(container *gtk.Box) {
}
listBox.SetSortFunc(listSort, 0)
listBox.SetFilterFunc(listFilter, 0)
_, err = buffer.Connect("changed", func(tb *gtk.TextBuffer) bool {
gmenu.SetInput(strings.TrimSpace(textViewText(inputView)))
@ -137,17 +140,20 @@ func initRow(container *gtk.Box, entry *gmenu.ListEntry, i int, lastEntry int) {
labelContainer := newBox(gtk.ORIENTATION_VERTICAL)
labelContainer.SetMarginStart(labelMarginStart)
l, err := gtk.LabelNew(fmt.Sprintf("<b>%s</b>", html.EscapeString(entry.Label)))
labelText := "Shell command"
if entry != nil && entry.Entry != nil {
labelText = entry.Label
}
l, err := gtk.LabelNew(fmt.Sprintf("<b>%s</b>", html.EscapeString(labelText)))
if err != nil {
log.Fatal("failed to create Label:", err)
}
l.SetUseMarkup(true)
l.SetHAlign(gtk.ALIGN_START)
setNoExpand(&l.Widget)
l.SetLineWrap(false)
l.SetSingleLineMode(true)
l.SetEllipsize(pango.ELLIPSIZE_END)
initLabel(l)
//glib.IdleAdd(initLabel, l)
if !config.HideAppIcons && !config.HideAppDetails {
l.SetMarginTop(labelMarginTop)
@ -173,11 +179,9 @@ func initRow(container *gtk.Box, entry *gmenu.ListEntry, i int, lastEntry int) {
log.Fatal("failed to create Label:", err)
}
l.SetHAlign(gtk.ALIGN_START)
setNoExpand(&l.Widget)
l.SetLineWrap(false)
l.SetSingleLineMode(true)
l.SetEllipsize(pango.ELLIPSIZE_END)
initLabel(l)
//glib.IdleAdd(initLabel, l) // TODO Faster?
if !config.HideAppIcons {
l.SetMarginTop(labelMarginTopComment)
@ -196,6 +200,13 @@ func initRow(container *gtk.Box, entry *gmenu.ListEntry, i int, lastEntry int) {
setNoExpand(&container.Widget)
}
func initLabel(l *gtk.Label) {
l.SetHAlign(gtk.ALIGN_START)
l.SetLineWrap(false)
l.SetSingleLineMode(true)
l.SetEllipsize(pango.ELLIPSIZE_END)
}
func loadIconImage(img *gtk.Image, entry *gmenu.ListEntry) {
var (
pbuf *gdk.Pixbuf
@ -218,14 +229,21 @@ func loadIconImage(img *gtk.Image, entry *gmenu.ListEntry) {
img.SetMarginEnd(iconMargin)
img.SetMarginBottom(iconMargin)
}
func updateList(input string) {
listBox.InvalidateFilter()
listBox.InvalidateSort()
execLabel.SetText(input)
row := listBox.GetRowAtIndex(0)
if row == nil {
return
var row *gtk.ListBoxRow
for i := 0; i < len(gmenu.Names); i++ {
row = listBox.GetRowAtIndex(i)
if row == nil {
return
} else if row.IsVisible() {
break
}
}
listBox.SelectRow(row)
@ -323,11 +341,32 @@ func listSelect(_ *gtk.TextView) error {
return nil
}
func listSort(row1 *gtk.ListBoxRow, row2 *gtk.ListBoxRow, userData uintptr) int {
func listFilter(row *gtk.ListBoxRow, _ uintptr) bool {
match := gmenu.MatchEntry(rowID(row))
row.SetSelectable(match)
return match
}
func listSort(row1 *gtk.ListBoxRow, row2 *gtk.ListBoxRow, _ uintptr) int {
r1 := rowID(row1)
r2 := rowID(row2)
if r1 < 0 || r2 < 0 {
return 0
if r1 < 0 {
if gmenu.MatchEntry(r2) {
return 1
}
return -1
} else { // r2 < 0
if gmenu.MatchEntry(r1) {
return -1
}
return 1
}
}
if gmenu.Sort(r1, r2) {

View File

@ -5,6 +5,7 @@ import (
"log"
"os"
"path"
"time"
"git.sr.ht/~tslocum/gmenu/pkg/gmenu"
"github.com/gotk3/gotk3/gdk"
@ -31,6 +32,7 @@ var (
listBox *gtk.ListBox
inputView *gtk.TextView
t = time.Now()
loaded = make(chan bool)
)
@ -44,35 +46,9 @@ func init() {
flag.BoolVar(&config.HideAppIcons, "no-icons", false, "hide application icons")
}
func main() {
func load() {
flag.Parse()
go load()
application, err := gtk.ApplicationNew(appID, glib.APPLICATION_HANDLES_COMMAND_LINE)
if err != nil {
log.Fatal("failed to create application:", err)
}
_, err = application.Connect("command-line", func() {
flag.Parse()
onActivate(application)
})
if err != nil {
log.Fatal("failed to connect while creating application:", err)
}
_, err = application.Connect("activate", func() {
onActivate(application)
})
if err != nil {
log.Fatal("failed to connect while creating application:", err)
}
os.Exit(application.Run(os.Args))
}
func load() {
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
@ -84,23 +60,72 @@ func load() {
}
gmenu.LoadEntries(&config.Config)
gmenu.FilterEntries()
for i, l := range gmenu.Names {
gmenu.FilteredEntries = append(gmenu.FilteredEntries, &gmenu.ListEntry{Label: l, Entry: gmenu.Entries[i]})
}
gmenu.Entries = append(gmenu.Entries, nil)
gmenu.Names = append(gmenu.Names, "")
gmenu.FilteredEntries = append(gmenu.FilteredEntries, &gmenu.ListEntry{Label: "", Entry: nil})
loaded <- true
}
func onActivate(application *gtk.Application) {
w, err := gtk.ApplicationWindowNew(application)
func setupKeyBindings(w *gtk.Window) {
_, err := w.Connect("key-press-event", handleKeybinding)
if err != nil {
log.Fatal("failed to connect key-press-event:", err)
}
}
if err != nil {
log.Fatal("failed to create application window:", err)
}
_, err = w.Connect("destroy", func() {
func handleKeybinding(_ *gtk.Window, ev *gdk.Event) bool {
keyEvent := &gdk.EventKey{ev}
switch keyEvent.KeyVal() {
case gdk.KEY_Up, gdk.KEY_Down:
offset := -1
if keyEvent.KeyVal() == gdk.KEY_Down {
offset = 1
}
index := 0
row := listBox.GetSelectedRow()
if row != nil {
index = row.GetIndex()
}
row = listBox.GetRowAtIndex(index + offset)
if row != nil {
listBox.SelectRow(row)
row.GrabFocus()
inputView.GrabFocus()
}
return true
case gdk.KEY_Return:
err := listSelect(inputView)
if err != nil {
log.Fatal(err)
}
return true
case gdk.KEY_Escape:
os.Exit(0)
})
}
return false
}
func main() {
go load()
gtk.Init(nil)
w, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
if err != nil {
log.Fatal("failed to create application window:", err)
}
w.SetTitle("gmenu")
w.SetDecorated(false)
@ -117,6 +142,13 @@ func onActivate(application *gtk.Application) {
w.Fullscreen()
}
_, err = w.Connect("destroy", func() {
os.Exit(0)
})
if err != nil {
log.Fatal("failed to create application window:", err)
}
<-loaded
go gmenu.HandleInput(func(input string) {
@ -126,55 +158,15 @@ func onActivate(application *gtk.Application) {
}
})
gmenu.Entries = append(gmenu.Entries, nil)
gmenu.Names = append(gmenu.Names, "")
gmenu.FilteredEntries = append(gmenu.FilteredEntries, &gmenu.ListEntry{Label: "Shell command", Entry: nil})
container := newBox(gtk.ORIENTATION_VERTICAL)
initList(container)
w.Add(container)
_, err = w.Connect("key-press-event", func(win *gtk.ApplicationWindow, ev *gdk.Event) bool {
keyEvent := &gdk.EventKey{ev}
switch keyEvent.KeyVal() {
case gdk.KEY_Up, gdk.KEY_Down:
offset := -1
if keyEvent.KeyVal() == gdk.KEY_Down {
offset = 1
}
index := 0
row := listBox.GetSelectedRow()
if row != nil {
index = row.GetIndex()
}
row = listBox.GetRowAtIndex(index + offset)
if row != nil {
listBox.SelectRow(row)
row.GrabFocus()
inputView.GrabFocus()
}
return true
case gdk.KEY_Return:
err = listSelect(inputView)
if err != nil {
log.Fatal(err)
}
return true
case gdk.KEY_Escape:
os.Exit(0)
}
return false
})
if err != nil {
log.Fatal("failed to connect key-press-event:", err)
}
setupKeyBindings(w)
w.ShowAll()
gtk.Main()
}

3
go.mod
View File

@ -4,7 +4,7 @@ go 1.12
require (
git.sr.ht/~tslocum/desktop v0.1.1
github.com/gotk3/gotk3 v0.0.0-20190809225113-dc58eba1cccc
github.com/gotk3/gotk3 v0.0.0-20190827191254-95d4bac6fe1b
github.com/jroimartin/gocui v0.4.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/lithammer/fuzzysearch v1.0.2
@ -12,4 +12,5 @@ require (
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/nsf/termbox-go v0.0.0-20190817171036-93860e161317 // indirect
github.com/pkg/errors v0.8.1
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 // indirect
)

6
go.sum
View File

@ -1,7 +1,7 @@
git.sr.ht/~tslocum/desktop v0.1.1 h1:hS1DgT1Ur0DR42Z4vr+Zsasjjd8M9PVwIEmeAd1xLS4=
git.sr.ht/~tslocum/desktop v0.1.1/go.mod h1:cUn0Q8ALjkAq40qSei795yN3CfO5pkeYKo2gmzaZ2SI=
github.com/gotk3/gotk3 v0.0.0-20190809225113-dc58eba1cccc h1:QtXtC6AdJ57L/rw/YMF41a+6YmLTy92IvxsCjDp4dYE=
github.com/gotk3/gotk3 v0.0.0-20190809225113-dc58eba1cccc/go.mod h1:Eew3QBwAOBTrfFFDmsDE5wZWbcagBL1NUslj1GhRveo=
github.com/gotk3/gotk3 v0.0.0-20190827191254-95d4bac6fe1b h1:/ExhbPkho7qhFp96P5JZq5GB2x3ApecVIy0pqPoF0DQ=
github.com/gotk3/gotk3 v0.0.0-20190827191254-95d4bac6fe1b/go.mod h1:Eew3QBwAOBTrfFFDmsDE5wZWbcagBL1NUslj1GhRveo=
github.com/jroimartin/gocui v0.4.0 h1:52jnalstgmc25FmtGcWqa0tcbMEWS6RpFLsOIO+I+E8=
github.com/jroimartin/gocui v0.4.0/go.mod h1:7i7bbj99OgFHzo7kB2zPb8pXLqMBSQegY7azfqXMkyY=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
@ -18,3 +18,5 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -80,6 +80,19 @@ func CloseInput() {
<-inputFlushed
}
func MatchEntry(i int) bool {
if i == -1 {
return true
}
b := strings.ToLower(input)
if b == "" {
return true
}
return fuzzy.MatchFold(b, Names[i])
}
func FilterEntries() {
FilteredEntries = nil
if input == "" {