209 lines
6.3 KiB
Go
209 lines
6.3 KiB
Go
//go:build gtk
|
||||
|
|
|
|||
|
|
package gui
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/diamondburned/gotk4/pkg/gdk/v4"
|
|||
|
|
"github.com/diamondburned/gotk4/pkg/gtk/v4"
|
|||
|
|
"tea.chunkbyte.com/kato/captioneer/src/output/history"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func (a *Application) buildCaptionsPage() *gtk.Box {
|
|||
|
|
page := gtk.NewBox(gtk.OrientationVertical, 7)
|
|||
|
|
page.AddCSSClass("captioneer-content")
|
|||
|
|
|
|||
|
|
toolbar := gtk.NewBox(gtk.OrientationHorizontal, 5)
|
|||
|
|
toolbar.AddCSSClass("toolbar")
|
|||
|
|
toolbar.Append(labelWithClass("CAPTION HISTORY", "section-title"))
|
|||
|
|
spacer := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|||
|
|
spacer.SetHExpand(true)
|
|||
|
|
toolbar.Append(spacer)
|
|||
|
|
toolbar.Append(toolButton("Copy Selected", "Copy the selected caption text", func() {
|
|||
|
|
copyBufferSelection(a.captionBuffer)
|
|||
|
|
}))
|
|||
|
|
toolbar.Append(toolButton("Copy Transcript", "Copy all final captions", func() {
|
|||
|
|
gdk.DisplayGetDefault().Clipboard().SetText(a.history.Transcript())
|
|||
|
|
}))
|
|||
|
|
toolbar.Append(toolButton("Clear", "Clear visible caption history", a.history.Clear))
|
|||
|
|
toolbar.Append(toolButton("Jump to Latest", "Resume at the newest caption", func() {
|
|||
|
|
scrollTextViewToEnd(a.captionView, a.captionScroll)
|
|||
|
|
}))
|
|||
|
|
page.Append(toolbar)
|
|||
|
|
|
|||
|
|
a.captionBuffer = gtk.NewTextBuffer(nil)
|
|||
|
|
a.captionView = gtk.NewTextViewWithBuffer(a.captionBuffer)
|
|||
|
|
a.captionView.SetEditable(false)
|
|||
|
|
a.captionView.SetCursorVisible(true)
|
|||
|
|
a.captionView.SetWrapMode(gtk.WrapWordChar)
|
|||
|
|
a.captionView.SetTopMargin(9)
|
|||
|
|
a.captionView.SetBottomMargin(9)
|
|||
|
|
a.captionView.SetLeftMargin(10)
|
|||
|
|
a.captionView.SetRightMargin(10)
|
|||
|
|
a.captionView.AddCSSClass("caption-view")
|
|||
|
|
a.captionScroll = gtk.NewScrolledWindow()
|
|||
|
|
a.captionScroll.SetPolicy(gtk.PolicyAutomatic, gtk.PolicyAutomatic)
|
|||
|
|
a.captionScroll.SetHExpand(true)
|
|||
|
|
a.captionScroll.SetVExpand(true)
|
|||
|
|
a.captionScroll.SetChild(a.captionView)
|
|||
|
|
a.captionScroll.AddCSSClass("inset-panel")
|
|||
|
|
page.Append(a.captionScroll)
|
|||
|
|
|
|||
|
|
provisional := gtk.NewBox(gtk.OrientationHorizontal, 8)
|
|||
|
|
provisional.AddCSSClass("provisional-row")
|
|||
|
|
caption := labelWithClass("LIVE", "section-title")
|
|||
|
|
caption.SetVAlign(gtk.AlignStart)
|
|||
|
|
provisional.Append(caption)
|
|||
|
|
a.provisionalLabel = gtk.NewLabel("Waiting for speech…")
|
|||
|
|
a.provisionalLabel.SetXAlign(0)
|
|||
|
|
a.provisionalLabel.SetWrap(true)
|
|||
|
|
a.provisionalLabel.SetHExpand(true)
|
|||
|
|
provisional.Append(a.provisionalLabel)
|
|||
|
|
page.Append(provisional)
|
|||
|
|
|
|||
|
|
return page
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (a *Application) buildConsolePage() *gtk.Box {
|
|||
|
|
page := gtk.NewBox(gtk.OrientationVertical, 7)
|
|||
|
|
page.AddCSSClass("captioneer-content")
|
|||
|
|
|
|||
|
|
toolbar := gtk.NewBox(gtk.OrientationHorizontal, 5)
|
|||
|
|
toolbar.AddCSSClass("toolbar")
|
|||
|
|
toolbar.Append(labelWithClass("APPLICATION CONSOLE", "section-title"))
|
|||
|
|
spacer := gtk.NewBox(gtk.OrientationHorizontal, 0)
|
|||
|
|
spacer.SetHExpand(true)
|
|||
|
|
toolbar.Append(spacer)
|
|||
|
|
toolbar.Append(toolButton("Copy Selected", "Copy selected console output", func() {
|
|||
|
|
copyBufferSelection(a.consoleBuffer)
|
|||
|
|
}))
|
|||
|
|
toolbar.Append(toolButton("Copy All", "Copy all buffered console output", func() {
|
|||
|
|
copyBuffer(a.consoleBuffer)
|
|||
|
|
}))
|
|||
|
|
toolbar.Append(toolButton("Clear", "Clear the console display", func() {
|
|||
|
|
a.log.Clear()
|
|||
|
|
a.renderConsole()
|
|||
|
|
}))
|
|||
|
|
toolbar.Append(toolButton("Jump to Latest", "Resume at the newest message", func() {
|
|||
|
|
scrollTextViewToEnd(a.consoleView, a.consoleScroll)
|
|||
|
|
}))
|
|||
|
|
page.Append(toolbar)
|
|||
|
|
|
|||
|
|
a.consoleBuffer = gtk.NewTextBuffer(nil)
|
|||
|
|
a.consoleView = gtk.NewTextViewWithBuffer(a.consoleBuffer)
|
|||
|
|
a.consoleView.SetEditable(false)
|
|||
|
|
a.consoleView.SetCursorVisible(true)
|
|||
|
|
a.consoleView.SetWrapMode(gtk.WrapWordChar)
|
|||
|
|
a.consoleView.SetTopMargin(8)
|
|||
|
|
a.consoleView.SetBottomMargin(8)
|
|||
|
|
a.consoleView.SetLeftMargin(9)
|
|||
|
|
a.consoleView.SetRightMargin(9)
|
|||
|
|
a.consoleView.AddCSSClass("console-view")
|
|||
|
|
a.consoleScroll = gtk.NewScrolledWindow()
|
|||
|
|
a.consoleScroll.SetPolicy(gtk.PolicyAutomatic, gtk.PolicyAutomatic)
|
|||
|
|
a.consoleScroll.SetHExpand(true)
|
|||
|
|
a.consoleScroll.SetVExpand(true)
|
|||
|
|
a.consoleScroll.SetChild(a.consoleView)
|
|||
|
|
a.consoleScroll.AddCSSClass("inset-panel")
|
|||
|
|
page.Append(a.consoleScroll)
|
|||
|
|
|
|||
|
|
return page
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (a *Application) renderHistory(snapshot history.Snapshot) {
|
|||
|
|
follow := isAtBottom(a.captionScroll)
|
|||
|
|
var text strings.Builder
|
|||
|
|
for _, entry := range snapshot.Finals {
|
|||
|
|
fmt.Fprintf(&text, "%s [%s–%s] %s\n",
|
|||
|
|
entry.CreatedAt.Format("15:04:05"),
|
|||
|
|
formatCaptionTime(entry.StartedAt),
|
|||
|
|
formatCaptionTime(entry.EndedAt),
|
|||
|
|
entry.Text,
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
a.captionBuffer.SetText(text.String())
|
|||
|
|
if snapshot.Provisional == nil || strings.TrimSpace(snapshot.Provisional.Text) == "" {
|
|||
|
|
a.provisionalLabel.SetText("Waiting for speech…")
|
|||
|
|
} else {
|
|||
|
|
a.provisionalLabel.SetText(snapshot.Provisional.Text)
|
|||
|
|
}
|
|||
|
|
if follow {
|
|||
|
|
scrollTextViewToEnd(a.captionView, a.captionScroll)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (a *Application) renderConsole() {
|
|||
|
|
follow := isAtBottom(a.consoleScroll)
|
|||
|
|
var text strings.Builder
|
|||
|
|
for _, entry := range a.log.Entries() {
|
|||
|
|
fmt.Fprintf(&text, "%s %-7s %-14s %s\n",
|
|||
|
|
entry.At.Format("15:04:05.000"),
|
|||
|
|
strings.ToUpper(string(entry.Severity)),
|
|||
|
|
"["+entry.Category+"]",
|
|||
|
|
entry.Message,
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
a.consoleBuffer.SetText(text.String())
|
|||
|
|
if follow {
|
|||
|
|
scrollTextViewToEnd(a.consoleView, a.consoleScroll)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func formatCaptionTime(value time.Duration) string {
|
|||
|
|
seconds := value.Seconds()
|
|||
|
|
return fmt.Sprintf("%02d:%05.2f", int(seconds)/60, seconds-float64(int(seconds)/60*60))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func isAtBottom(scroll *gtk.ScrolledWindow) bool {
|
|||
|
|
if scroll == nil {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
adjustment := scroll.VAdjustment()
|
|||
|
|
return adjustment.Value()+adjustment.PageSize() >= adjustment.Upper()-4
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func scrollTextViewToEnd(view *gtk.TextView, scroll *gtk.ScrolledWindow) {
|
|||
|
|
if view == nil || scroll == nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
view.ScrollToIter(view.Buffer().EndIter(), 0, false, 0, 1)
|
|||
|
|
adjustment := scroll.VAdjustment()
|
|||
|
|
adjustment.SetValue(adjustment.Upper())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func copyBufferSelection(buffer *gtk.TextBuffer) {
|
|||
|
|
if buffer == nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
start, end, ok := buffer.SelectionBounds()
|
|||
|
|
if !ok {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
gdk.DisplayGetDefault().Clipboard().SetText(buffer.Text(start, end, true))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func copyBuffer(buffer *gtk.TextBuffer) {
|
|||
|
|
if buffer == nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
start, end := buffer.Bounds()
|
|||
|
|
gdk.DisplayGetDefault().Clipboard().SetText(buffer.Text(start, end, true))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func labelWithClass(text, class string) *gtk.Label {
|
|||
|
|
label := gtk.NewLabel(text)
|
|||
|
|
label.AddCSSClass(class)
|
|||
|
|
label.SetXAlign(0)
|
|||
|
|
return label
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func toolButton(text, tooltip string, callback func()) *gtk.Button {
|
|||
|
|
button := gtk.NewButtonWithLabel(text)
|
|||
|
|
button.SetTooltipText(tooltip)
|
|||
|
|
button.ConnectClicked(callback)
|
|||
|
|
return button
|
|||
|
|
}
|