372 lines
7.3 KiB
Go
372 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"html"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"code.rocketnine.space/tslocum/desktop"
|
|
"code.rocketnine.space/tslocum/gmenu/pkg/gmenu"
|
|
"github.com/gotk3/gotk3/glib"
|
|
"github.com/gotk3/gotk3/gtk"
|
|
"github.com/gotk3/gotk3/pango"
|
|
"github.com/kballard/go-shellquote"
|
|
)
|
|
|
|
const (
|
|
iconSize = 48
|
|
rowMargin = 4
|
|
labelMarginStart = 12
|
|
labelMarginTop = 4
|
|
labelMarginTopComment = 4
|
|
)
|
|
|
|
var execLabel *gtk.Label
|
|
|
|
func initList(container *gtk.Box) {
|
|
inputView = newTextView()
|
|
setNoExpand(&inputView.Widget)
|
|
inputView.SetProperty("accepts-tab", false)
|
|
inputView.SetProperty("wrap-mode", gtk.WRAP_CHAR)
|
|
inputView.SetProperty("cursor-visible", false)
|
|
inputView.SetProperty("left-margin", 2)
|
|
inputView.SetProperty("right-margin", 2)
|
|
|
|
buffer, err := inputView.GetBuffer()
|
|
if err != nil {
|
|
log.Fatal("failed to create input buffer:", err)
|
|
}
|
|
|
|
buffer.Connect("changed", func(tb *gtk.TextBuffer) bool {
|
|
gmenu.SetInput(strings.TrimSpace(textViewText(inputView)))
|
|
|
|
return false
|
|
})
|
|
if err != nil {
|
|
log.Fatal("failed to connect to changed event of input buffer:", err)
|
|
}
|
|
|
|
container.Add(inputView)
|
|
|
|
listScroll, err := gtk.ScrolledWindowNew(nil, nil)
|
|
if err != nil {
|
|
log.Fatal("failed to create ScrolledWindow:", err)
|
|
}
|
|
listScroll.SetHExpand(true)
|
|
listScroll.SetVExpand(false)
|
|
|
|
listBox, err = gtk.ListBoxNew()
|
|
if err != nil {
|
|
log.Fatal("failed to create ListBox:", err)
|
|
}
|
|
listBox.SetSelectionMode(gtk.SELECTION_BROWSE)
|
|
listBox.SetHExpand(false)
|
|
listBox.SetFocusOnClick(false)
|
|
|
|
listScroll.Add(listBox)
|
|
container.PackEnd(listScroll, true, true, 0)
|
|
|
|
if !config.HideAppIcons {
|
|
s, _ := container.GetScreen()
|
|
iconTheme, err = gtk.IconThemeGetForScreen(*s)
|
|
if err != nil {
|
|
log.Fatal("failed to get icon theme:", err)
|
|
}
|
|
}
|
|
|
|
lastEntry := len(gmenu.FilteredEntries) - 1
|
|
|
|
var (
|
|
entry *gmenu.ListEntry
|
|
wg = new(sync.WaitGroup)
|
|
)
|
|
for i, label := range gmenu.Names {
|
|
wg.Add(1)
|
|
|
|
glib.IdleAdd(rowInitFunc(i, label, entry, lastEntry, wg))
|
|
}
|
|
|
|
listBox.SetSortFunc(listSort)
|
|
listBox.SetFilterFunc(listFilter)
|
|
|
|
go func() {
|
|
wg.Wait()
|
|
|
|
glib.IdleAdd(func() {
|
|
listBox.Connect("row-activated", func(_ *gtk.ListBox, _ *gtk.ListBoxRow) {
|
|
err := listSelect(inputView, false)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
})
|
|
if err != nil {
|
|
log.Fatal("failed to connect to row-activated event of ListBox:", err)
|
|
}
|
|
})
|
|
}()
|
|
}
|
|
|
|
func initRow(container *gtk.Box, entry *gmenu.ListEntry, i int, lastEntry int) {
|
|
if !config.HideAppIcons {
|
|
img, _ := gtk.ImageNew()
|
|
img.SetSizeRequest(iconSize, iconSize)
|
|
|
|
container.PackStart(img, false, false, 0)
|
|
|
|
glib.IdleAdd(func() { loadIconImage(img, entry) })
|
|
}
|
|
|
|
labelContainer := newBox(gtk.ORIENTATION_VERTICAL)
|
|
labelContainer.SetMarginStart(labelMarginStart)
|
|
|
|
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)
|
|
|
|
setNoExpand(&l.Widget)
|
|
initLabel(l)
|
|
|
|
if !config.HideAppIcons && !config.HideAppDetails {
|
|
l.SetMarginTop(labelMarginTop)
|
|
}
|
|
|
|
if !config.HideAppDetails {
|
|
labelContainer.PackStart(l, false, false, 0)
|
|
} else {
|
|
labelContainer.PackStart(l, true, true, 0)
|
|
}
|
|
|
|
if config.HideAppDetails {
|
|
if i == lastEntry {
|
|
execLabel = l
|
|
}
|
|
} else if entry.Entry == nil || (entry.Entry != nil && entry.Comment != "") {
|
|
comment := ""
|
|
if entry.Entry != nil {
|
|
comment = entry.Comment
|
|
}
|
|
l, err := gtk.LabelNew(comment)
|
|
if err != nil {
|
|
log.Fatal("failed to create Label:", err)
|
|
}
|
|
|
|
setNoExpand(&l.Widget)
|
|
initLabel(l)
|
|
|
|
if !config.HideAppIcons {
|
|
l.SetMarginTop(labelMarginTopComment)
|
|
}
|
|
|
|
labelContainer.Add(l)
|
|
|
|
if i == lastEntry {
|
|
execLabel = l
|
|
}
|
|
}
|
|
|
|
setNoExpand(&labelContainer.Widget)
|
|
container.Add(labelContainer)
|
|
|
|
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 updateList(input string) {
|
|
execLabel.SetText(input)
|
|
|
|
listBox.InvalidateFilter()
|
|
listBox.InvalidateSort()
|
|
|
|
listBox.SelectRow(listBox.GetRowAtIndex(0))
|
|
}
|
|
|
|
func newBox(orient gtk.Orientation) *gtk.Box {
|
|
box, err := gtk.BoxNew(orient, 0)
|
|
if err != nil {
|
|
log.Fatal("Unable to create box:", err)
|
|
}
|
|
return box
|
|
}
|
|
|
|
func newTextView() *gtk.TextView {
|
|
tv, err := gtk.TextViewNew()
|
|
if err != nil {
|
|
log.Fatal("Unable to create TextView:", err)
|
|
}
|
|
return tv
|
|
}
|
|
|
|
func textViewBuffer(tv *gtk.TextView) *gtk.TextBuffer {
|
|
buffer, err := tv.GetBuffer()
|
|
if err != nil {
|
|
log.Fatal("Unable to get TextView buffer:", err)
|
|
}
|
|
return buffer
|
|
}
|
|
|
|
func textViewText(tv *gtk.TextView) string {
|
|
buffer := textViewBuffer(tv)
|
|
start, end := buffer.GetBounds()
|
|
|
|
text, err := buffer.GetText(start, end, true)
|
|
if err != nil {
|
|
log.Fatal("Unable to get TextView text:", err)
|
|
}
|
|
return text
|
|
}
|
|
|
|
func setNoExpand(v *gtk.Widget) {
|
|
v.SetHExpand(false)
|
|
v.SetVExpand(false)
|
|
}
|
|
|
|
func rowID(row *gtk.ListBoxRow) int {
|
|
if row == nil {
|
|
return -1
|
|
}
|
|
|
|
name, err := row.GetName()
|
|
if err != nil || len(name) < 2 || name[0] != '#' {
|
|
return -1
|
|
}
|
|
|
|
id, err := strconv.Atoi(name[1:])
|
|
if err != nil {
|
|
return -1
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
func selectedIndex() int {
|
|
if listBox == nil {
|
|
return -1
|
|
}
|
|
|
|
return rowID(listBox.GetSelectedRow())
|
|
}
|
|
|
|
func selectedEntry() *desktop.Entry {
|
|
i := selectedIndex()
|
|
if len(gmenu.FilteredEntries) == 0 || i < 0 || i > len(gmenu.FilteredEntries)-1 {
|
|
return nil
|
|
}
|
|
|
|
return gmenu.FilteredEntries[i].Entry
|
|
}
|
|
|
|
func listSelect(_ *gtk.TextView, runInTerminal bool) error {
|
|
gmenu.CloseInput()
|
|
|
|
var execute string
|
|
entry := selectedEntry()
|
|
if entry == nil {
|
|
execute = textViewText(inputView)
|
|
} else if entry.Type == desktop.Application {
|
|
if entry.Terminal {
|
|
runInTerminal = true
|
|
}
|
|
|
|
execute = entry.ExpandExec(shellquote.Join(flag.Args()...))
|
|
} else { // Type == desktop.Link
|
|
execute = shellquote.Join(config.BrowserCommand(), entry.URL)
|
|
}
|
|
|
|
path := ""
|
|
if entry != nil {
|
|
path = entry.Path
|
|
}
|
|
|
|
err := gmenu.Run(&config.Config, execute, path, runInTerminal, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
os.Exit(0)
|
|
return nil
|
|
}
|
|
|
|
func listSort(row1 *gtk.ListBoxRow, row2 *gtk.ListBoxRow) int {
|
|
r1 := rowID(row1)
|
|
r2 := rowID(row2)
|
|
|
|
if r1 < 0 {
|
|
if gmenu.MatchEntry(r2) {
|
|
return 1
|
|
}
|
|
|
|
return -1
|
|
} else if r2 < 0 {
|
|
if gmenu.MatchEntry(r1) {
|
|
return -1
|
|
}
|
|
|
|
return 1
|
|
}
|
|
|
|
if gmenu.Sort(r1, r2) {
|
|
return -1
|
|
}
|
|
|
|
return 1
|
|
}
|
|
|
|
func listFilter(row *gtk.ListBoxRow) bool {
|
|
match := gmenu.MatchEntry(rowID(row))
|
|
|
|
row.SetSelectable(match)
|
|
|
|
return match
|
|
}
|
|
|
|
func rowInitFunc(i int, label string, entry *gmenu.ListEntry, lastEntry int, wg *sync.WaitGroup) func() {
|
|
return func() {
|
|
row, err := gtk.ListBoxRowNew()
|
|
if err != nil {
|
|
log.Fatal("failed to create ListBoxRow:", err)
|
|
}
|
|
|
|
if i == lastEntry {
|
|
entry = &gmenu.ListEntry{Entry: nil, Label: ""}
|
|
} else {
|
|
row.SetName("#" + strconv.Itoa(i))
|
|
|
|
entry = &gmenu.ListEntry{Entry: gmenu.Entries[i], Label: label}
|
|
}
|
|
|
|
container := newBox(gtk.ORIENTATION_HORIZONTAL)
|
|
container.SetMarginStart(rowMargin)
|
|
container.SetMarginTop(rowMargin)
|
|
container.SetMarginBottom(rowMargin)
|
|
container.SetMarginEnd(rowMargin)
|
|
|
|
initRow(container, entry, i, lastEntry)
|
|
|
|
row.Add(container)
|
|
listBox.Add(row)
|
|
|
|
listBox.SelectRow(listBox.GetRowAtIndex(0))
|
|
row.ShowAll()
|
|
|
|
wg.Done()
|
|
}
|
|
}
|