gmenu/cmd/gtkmenu/gui_list.go

353 lines
7.3 KiB
Go

package main
import (
"flag"
"fmt"
"html"
"log"
"os"
"path"
"strconv"
"strings"
"github.com/gotk3/gotk3/pango"
"git.sr.ht/~tslocum/desktop"
"git.sr.ht/~tslocum/gmenu/pkg/gmenu"
"github.com/gotk3/gotk3/gdk"
"github.com/kballard/go-shellquote"
"github.com/tslocum/gotk3/gtk"
gtkfork "github.com/tslocum/gotk3/gtk"
)
const (
iconSize = 48
iconMargin = 4
iconMarginStart = 2
labelMarginStart = 4
labelMarginTop = 8
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 ListBox:", err)
}
container.Add(inputView)
listScroll, err := gtk.ScrolledWindowNew(nil, nil)
if err != nil {
log.Fatal("failed to create ListBox:", 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)
_, err = listBox.Connect("button-press-event", func(listBox *gtkfork.ListBox, ev *gdk.Event) {
mouseEvent := &gdk.EventButton{ev}
if mouseEvent.Type() == gdk.EVENT_2BUTTON_PRESS {
err := listSelect(inputView)
if err != nil {
log.Fatal(err)
}
}
})
if err != nil {
log.Fatal("failed to create ListBox:", err)
}
listScroll.Add(listBox)
container.PackEnd(listScroll, true, true, 0)
lastEntry := len(gmenu.FilteredEntries) - 1
for i, entry := range gmenu.FilteredEntries {
row, err := gtk.ListBoxRowNew()
if err != nil {
log.Fatal("failed to create ListBoxRow:", err)
}
row.SetName("#" + strconv.Itoa(i))
container := newBox(gtk.ORIENTATION_HORIZONTAL)
if !config.HideAppIcons {
s, _ := container.GetScreen()
theme, err := gtk.IconThemeGetForScreen(*s)
if err != nil {
log.Fatal("failed to get icon theme:", err)
}
var (
pbuf *gdk.Pixbuf
img *gtk.Image
)
if entry.Entry != nil && entry.Icon != "" {
if path.IsAbs(entry.Icon) {
pbuf, err = gdk.PixbufNewFromFileAtSize(entry.Icon, iconSize, iconSize)
} else {
pbuf, err = theme.LoadIcon(entry.Icon, iconSize, gtk.ICON_LOOKUP_USE_BUILTIN)
}
}
if pbuf == nil || err != nil {
var icon string
if entry.Entry == nil {
icon = "utilities-terminal"
} else if entry.Type == desktop.Application {
icon = "application-x-executable"
} else {
icon = "text-html"
}
pbuf, err = theme.LoadIcon(icon, iconSize, gtk.ICON_LOOKUP_USE_BUILTIN)
}
if err != nil {
// Failed to load icon
img, err = gtk.ImageNew()
if err == nil {
img.SetSizeRequest(iconSize, iconSize)
}
} else {
if pbuf.GetWidth() != iconSize && pbuf.GetHeight() != iconSize {
pbuf, _ = pbuf.ScaleSimple(iconSize, iconSize, gdk.INTERP_BILINEAR)
}
img, err = gtk.ImageNewFromPixbuf(pbuf)
}
if err != nil {
log.Fatal("failed to create Icon:", err)
}
img.SetMarginStart(iconMarginStart)
img.SetMarginTop(iconMargin)
img.SetMarginEnd(iconMargin)
img.SetMarginBottom(iconMargin)
container.PackStart(img, false, false, 0)
}
labelContainer := newBox(gtk.ORIENTATION_VERTICAL)
labelContainer.SetMarginStart(labelMarginStart)
l, err := gtk.LabelNew(fmt.Sprintf("<b>%s</b>", html.EscapeString(entry.Label)))
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)
if !config.HideAppIcons {
l.SetMarginTop(labelMarginTop)
}
labelContainer.PackStart(l, false, false, 0)
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)
}
l.SetHAlign(gtk.ALIGN_START)
setNoExpand(&l.Widget)
l.SetLineWrap(false)
l.SetSingleLineMode(true)
l.SetEllipsize(pango.ELLIPSIZE_END)
if !config.HideAppIcons {
l.SetMarginTop(labelMarginTopComment)
}
labelContainer.Add(l)
if i == lastEntry {
execLabel = l
}
}
setNoExpand(&labelContainer.Widget)
container.Add(labelContainer)
setNoExpand(&container.Widget)
row.Add(container)
listBox.Add(row)
}
listBox.SetSortFunc(listSort, 0)
_, err = buffer.Connect("changed", func(tb *gtk.TextBuffer) bool {
gmenu.SetInput(strings.TrimSpace(textViewText(inputView)))
return false
})
if err != nil {
log.Fatal("failed to create ListBox:", err)
}
gmenu.SetInput("")
}
func updateList(input string) {
listBox.InvalidateSort()
execLabel.SetText(input)
row := listBox.GetRowAtIndex(0)
if row == nil {
return
}
listBox.SelectRow(row)
inputView.GrabFocus()
}
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 selectedIndex() int {
if listBox == nil {
return -1
}
return rowID(listBox.GetSelectedRow())
}
func setNoExpand(v *gtk.Widget) {
v.SetHExpand(false)
v.SetVExpand(false)
}
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) error {
gmenu.CloseInput()
var (
execute string
runInTerminal bool
waitUntilFinished bool
)
entry := selectedEntry()
if entry == nil {
waitUntilFinished = true
execute = textViewText(inputView)
} else if entry.Type == desktop.Application {
runInTerminal = entry.Terminal
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, waitUntilFinished)
if err != nil {
return err
}
os.Exit(0)
return nil
}
func listSort(row1 *gtk.ListBoxRow, row2 *gtk.ListBoxRow, userData uintptr) int {
r1 := rowID(row1)
r2 := rowID(row2)
if r1 < 0 || r2 < 0 {
return 0
}
if gmenu.Sort(r1, r2) {
return -1
}
return 1
}
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
}