Added partial support for the Commander API
This commit is contained in:
parent
0a353ff128
commit
47e3e67b95
3
go.mod
3
go.mod
|
@ -2,7 +2,7 @@ module github.com/diamondburned/cchat-gtk
|
||||||
|
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20200619213419-0533bcce0dd6
|
replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Xuanwo/go-locale v0.2.0
|
github.com/Xuanwo/go-locale v0.2.0
|
||||||
|
@ -13,6 +13,7 @@ require (
|
||||||
github.com/diamondburned/imgutil v0.0.0-20200611215339-650ac7cfaf64
|
github.com/diamondburned/imgutil v0.0.0-20200611215339-650ac7cfaf64
|
||||||
github.com/goodsign/monday v1.0.0
|
github.com/goodsign/monday v1.0.0
|
||||||
github.com/google/btree v1.0.0 // indirect
|
github.com/google/btree v1.0.0 // indirect
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
||||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
|
||||||
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20200424224625-be1b05b0b279
|
github.com/ianlancetaylor/cgosymbolizer v0.0.0-20200424224625-be1b05b0b279
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -69,6 +69,8 @@ github.com/diamondburned/cchat-mock v0.0.0-20200630025821-605d61d89288 h1:ApNV7D
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200630025821-605d61d89288/go.mod h1:Tu+8b1iz9NGeQb2jmndXn+dQ9zBUa8a8ktK9hL5aaxw=
|
github.com/diamondburned/cchat-mock v0.0.0-20200630025821-605d61d89288/go.mod h1:Tu+8b1iz9NGeQb2jmndXn+dQ9zBUa8a8ktK9hL5aaxw=
|
||||||
github.com/diamondburned/gotk3 v0.0.0-20200619213419-0533bcce0dd6 h1:ZzLrfQqszhzWI7zqwltzQIWtppfcL7m2aIEpB4kuqx0=
|
github.com/diamondburned/gotk3 v0.0.0-20200619213419-0533bcce0dd6 h1:ZzLrfQqszhzWI7zqwltzQIWtppfcL7m2aIEpB4kuqx0=
|
||||||
github.com/diamondburned/gotk3 v0.0.0-20200619213419-0533bcce0dd6/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
github.com/diamondburned/gotk3 v0.0.0-20200619213419-0533bcce0dd6/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
||||||
|
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI=
|
||||||
|
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
||||||
github.com/diamondburned/imgutil v0.0.0-20200611215339-650ac7cfaf64 h1:/ykUYHuYyj+NN/aaqe6lfaCZQc3EMZs93wAGVJTh5j0=
|
github.com/diamondburned/imgutil v0.0.0-20200611215339-650ac7cfaf64 h1:/ykUYHuYyj+NN/aaqe6lfaCZQc3EMZs93wAGVJTh5j0=
|
||||||
github.com/diamondburned/imgutil v0.0.0-20200611215339-650ac7cfaf64/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
|
github.com/diamondburned/imgutil v0.0.0-20200611215339-650ac7cfaf64/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
|
||||||
github.com/diamondburned/ningen v0.1.0 h1:cTnRNrN0g2Wr/kgjLLpa3pqlbEd6JPNa1yGDer8uV4U=
|
github.com/diamondburned/ningen v0.1.0 h1:cTnRNrN0g2Wr/kgjLLpa3pqlbEd6JPNa1yGDer8uV4U=
|
||||||
|
@ -119,6 +121,8 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
|
|
@ -3,7 +3,7 @@ package container
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/autoscroll"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/autoscroll"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
|
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package buttonoverlay
|
||||||
|
|
||||||
|
import "github.com/gotk3/gotk3/gtk"
|
||||||
|
|
||||||
|
type Widget interface {
|
||||||
|
gtk.IWidget
|
||||||
|
SetMarginEnd(int)
|
||||||
|
SetSizeRequest(int, int)
|
||||||
|
SetHAlign(gtk.Align)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Widget = (*gtk.Widget)(nil)
|
||||||
|
|
||||||
|
type Button interface {
|
||||||
|
Widget
|
||||||
|
// Bin
|
||||||
|
GetChild() (gtk.IWidget, error)
|
||||||
|
// Container
|
||||||
|
Add(gtk.IWidget)
|
||||||
|
Remove(gtk.IWidget)
|
||||||
|
// Button
|
||||||
|
SetRelief(gtk.ReliefStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Button = (*gtk.Button)(nil)
|
||||||
|
|
||||||
|
// Wrap wraps maincontent inside an overlay with smallbutton placed rightmost on
|
||||||
|
// top of the content. It will also set the margins and aligns widgets.
|
||||||
|
func Wrap(maincontent Widget, smallbutton Button, size int) *gtk.Overlay {
|
||||||
|
maincontent.SetMarginEnd(size)
|
||||||
|
smallbutton.SetSizeRequest(size, size)
|
||||||
|
smallbutton.SetHAlign(gtk.ALIGN_END)
|
||||||
|
smallbutton.SetRelief(gtk.RELIEF_NONE)
|
||||||
|
|
||||||
|
o, _ := gtk.OverlayNew()
|
||||||
|
o.Add(maincontent)
|
||||||
|
o.AddOverlay(smallbutton)
|
||||||
|
o.Show()
|
||||||
|
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take takes over the given button and replaces its content with the wrapped
|
||||||
|
// overlay, which has the old content as well as the smaller button on top.
|
||||||
|
func Take(b, smallbutton Button, size int) {
|
||||||
|
childv, _ := b.GetChild()
|
||||||
|
widget := childv.ToWidget()
|
||||||
|
|
||||||
|
// As GetChild doesn't reference, we'll want our own reference.
|
||||||
|
widget.Ref()
|
||||||
|
defer widget.Unref()
|
||||||
|
|
||||||
|
// This will unreference.
|
||||||
|
b.Remove(widget)
|
||||||
|
// Wrap will reference.
|
||||||
|
b.Add(Wrap(widget, smallbutton, size))
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/buttonoverlay"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/config"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/config"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||||
|
@ -12,62 +13,35 @@ import (
|
||||||
const IconSize = 32
|
const IconSize = 32
|
||||||
|
|
||||||
type header struct {
|
type header struct {
|
||||||
*gtk.ToggleButton // no rich text here but it's left aligned
|
*rich.ToggleButtonImage
|
||||||
|
Add *gtk.Button
|
||||||
box *gtk.Box
|
|
||||||
label *rich.Label
|
|
||||||
icon *rich.Icon
|
|
||||||
Add *gtk.Button
|
|
||||||
|
|
||||||
Menu *menu.LazyMenu
|
Menu *menu.LazyMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHeader(svc cchat.Service) *header {
|
func newHeader(svc cchat.Service) *header {
|
||||||
i := rich.NewIcon(0)
|
b := rich.NewToggleButtonImage(svc.Name())
|
||||||
i.AddProcessors(imgutil.Round(true))
|
b.Image.AddProcessors(imgutil.Round(true))
|
||||||
i.SetPlaceholderIcon("folder-remote-symbolic", IconSize)
|
b.Image.SetPlaceholderIcon("folder-remote-symbolic", IconSize)
|
||||||
i.Show()
|
b.SetRelief(gtk.RELIEF_NONE)
|
||||||
|
b.SetMode(true)
|
||||||
|
b.Show()
|
||||||
|
|
||||||
if iconer, ok := svc.(cchat.Icon); ok {
|
if iconer, ok := svc.(cchat.Icon); ok {
|
||||||
i.AsyncSetIconer(iconer, "Error getting session logo")
|
b.Image.AsyncSetIconer(iconer, "Error getting session logo")
|
||||||
}
|
}
|
||||||
|
|
||||||
l := rich.NewLabel(svc.Name())
|
|
||||||
l.Show()
|
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
|
||||||
box.PackStart(i, false, false, 0)
|
|
||||||
box.PackStart(l, true, true, 5)
|
|
||||||
box.SetMarginEnd(IconSize) // spare space for the add button
|
|
||||||
box.Show()
|
|
||||||
|
|
||||||
add, _ := gtk.ButtonNewFromIconName("list-add-symbolic", gtk.ICON_SIZE_BUTTON)
|
add, _ := gtk.ButtonNewFromIconName("list-add-symbolic", gtk.ICON_SIZE_BUTTON)
|
||||||
add.SetRelief(gtk.RELIEF_NONE)
|
|
||||||
add.SetSizeRequest(IconSize, IconSize)
|
|
||||||
add.SetHAlign(gtk.ALIGN_END)
|
|
||||||
add.Show()
|
add.Show()
|
||||||
|
|
||||||
// Do jank stuff to overlay the add button on top of our button.
|
// Add the button overlay into the main button.
|
||||||
overlay, _ := gtk.OverlayNew()
|
buttonoverlay.Take(b, add, IconSize)
|
||||||
overlay.Add(box)
|
|
||||||
overlay.AddOverlay(add)
|
|
||||||
overlay.Show()
|
|
||||||
|
|
||||||
reveal, _ := gtk.ToggleButtonNew()
|
|
||||||
reveal.Add(overlay)
|
|
||||||
reveal.SetRelief(gtk.RELIEF_NONE)
|
|
||||||
reveal.SetMode(true)
|
|
||||||
reveal.Show()
|
|
||||||
|
|
||||||
// Construct a menu and its items.
|
// Construct a menu and its items.
|
||||||
var menu = menu.NewLazyMenu(reveal)
|
var menu = menu.NewLazyMenu(b)
|
||||||
if configurator, ok := svc.(config.Configurator); ok {
|
if configurator, ok := svc.(config.Configurator); ok {
|
||||||
menu.AddItems(config.MenuItem(configurator))
|
menu.AddItems(config.MenuItem(configurator))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &header{reveal, box, l, i, add, menu}
|
return &header{b, add, menu}
|
||||||
}
|
|
||||||
|
|
||||||
func (h *header) GetText() string {
|
|
||||||
return h.label.GetText()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,10 @@ func NewContainer(svc cchat.Service, ctrl Controller) *Container {
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) GetService() cchat.Service {
|
||||||
|
return c.Service
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Container) Sessions() []*session.Row {
|
func (c *Container) Sessions() []*session.Row {
|
||||||
return c.children.Sessions()
|
return c.children.Sessions()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
package commander
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/autoscroll"
|
||||||
|
"github.com/google/shlex"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionCommander interface {
|
||||||
|
cchat.Session
|
||||||
|
cchat.Commander
|
||||||
|
}
|
||||||
|
|
||||||
|
type Buffer struct {
|
||||||
|
*gtk.TextBuffer
|
||||||
|
svcname string
|
||||||
|
cmder SessionCommander
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuffer creates a new buffer with the given SessionCommander, or returns
|
||||||
|
// nil if cmder is nil.
|
||||||
|
func NewBuffer(svc cchat.Service, cmder SessionCommander) *Buffer {
|
||||||
|
if cmder == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ := gtk.TextBufferNew(nil)
|
||||||
|
b.CreateTag("error", map[string]interface{}{
|
||||||
|
"foreground": "#FF0000",
|
||||||
|
})
|
||||||
|
return &Buffer{b, svc.Name().Content, cmder}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteError is not thread-safe.
|
||||||
|
func (b *Buffer) WriteError(err error) {
|
||||||
|
b.InsertWithTagByName(b.GetEndIter(), err.Error()+"\n", "error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUnsafe is not thread-safe.
|
||||||
|
func (b *Buffer) WriteUnsafe(bytes []byte) {
|
||||||
|
b.Insert(b.GetEndIter(), string(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printlnf is not thread-safe.
|
||||||
|
func (b *Buffer) Printlnf(f string, v ...interface{}) {
|
||||||
|
b.WriteUnsafe([]byte(fmt.Sprintf(f+"\n", v...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write is thread-safe.
|
||||||
|
func (b *Buffer) Write(bytes []byte) (int, error) {
|
||||||
|
gts.ExecAsync(func() { b.WriteUnsafe(bytes) })
|
||||||
|
return len(bytes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) ShowDialog() {
|
||||||
|
SpawnDialog(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryCSS = primitives.PrepareCSS(`
|
||||||
|
* {
|
||||||
|
font-family: monospace;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
*gtk.Box
|
||||||
|
words []string
|
||||||
|
cmder cchat.Commander
|
||||||
|
buffer *Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpawnDialog(buf *Buffer) {
|
||||||
|
s := NewSession(buf.cmder, buf)
|
||||||
|
s.Show()
|
||||||
|
|
||||||
|
h, _ := gtk.HeaderBarNew()
|
||||||
|
h.SetTitle(fmt.Sprintf(
|
||||||
|
"Commander: %s on %s",
|
||||||
|
buf.cmder.Name().Content, buf.svcname,
|
||||||
|
))
|
||||||
|
h.SetShowCloseButton(true)
|
||||||
|
h.Show()
|
||||||
|
|
||||||
|
d, _ := gts.NewEmptyModalDialog()
|
||||||
|
d.SetDefaultSize(450, 250)
|
||||||
|
d.SetTitlebar(h)
|
||||||
|
d.Add(s)
|
||||||
|
d.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSession(cmder cchat.Commander, buf *Buffer) *Session {
|
||||||
|
v, _ := gtk.TextViewNewWithBuffer(buf.TextBuffer)
|
||||||
|
v.SetEditable(false)
|
||||||
|
v.SetProperty("monospace", true)
|
||||||
|
v.SetBorderWidth(8)
|
||||||
|
v.SetPixelsAboveLines(1)
|
||||||
|
v.SetWrapMode(gtk.WRAP_WORD_CHAR)
|
||||||
|
v.Show()
|
||||||
|
|
||||||
|
s := autoscroll.NewScrolledWindow()
|
||||||
|
s.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||||
|
s.Add(v)
|
||||||
|
s.Show()
|
||||||
|
|
||||||
|
i, _ := gtk.EntryNew()
|
||||||
|
primitives.AttachCSS(i, entryCSS)
|
||||||
|
i.Show()
|
||||||
|
|
||||||
|
b, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
|
b.PackStart(s, true, true, 0)
|
||||||
|
b.PackStart(i, false, false, 0)
|
||||||
|
|
||||||
|
session := &Session{
|
||||||
|
Box: b,
|
||||||
|
cmder: cmder,
|
||||||
|
buffer: buf,
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Connect("activate", session.inputActivate)
|
||||||
|
// Split words on typing to provide live errors.
|
||||||
|
i.Connect("changed", func(i *gtk.Entry) {
|
||||||
|
t, _ := i.GetText()
|
||||||
|
|
||||||
|
w, err := shlex.Split(t)
|
||||||
|
if err != nil {
|
||||||
|
i.SetIconFromIconName(gtk.ENTRY_ICON_SECONDARY, "dialog-error")
|
||||||
|
i.SetIconTooltipText(gtk.ENTRY_ICON_SECONDARY, err.Error())
|
||||||
|
session.words = nil
|
||||||
|
} else {
|
||||||
|
i.SetIconFromIconName(gtk.ENTRY_ICON_SECONDARY, "")
|
||||||
|
session.words = w
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Focus on the input by default.
|
||||||
|
i.GrabFocus()
|
||||||
|
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) inputActivate(e *gtk.Entry) {
|
||||||
|
// If the input is empty, then ignore.
|
||||||
|
if len(s.words) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := s.cmder.RunCommand(s.words)
|
||||||
|
if err != nil {
|
||||||
|
s.buffer.WriteError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the entry.
|
||||||
|
e.SetText("")
|
||||||
|
|
||||||
|
var then = time.Now()
|
||||||
|
s.buffer.Printlnf("%s: Running command...", then.Format(time.Kitchen))
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_, err := io.Copy(s.buffer, r)
|
||||||
|
r.Close()
|
||||||
|
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
if err != nil {
|
||||||
|
s.buffer.WriteError(errors.Wrap(err, "Internal error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = time.Now()
|
||||||
|
s.buffer.Printlnf(
|
||||||
|
"%s: Finished running command, took %s.",
|
||||||
|
now.Format(time.Kitchen),
|
||||||
|
now.Sub(then).String(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
}
|
|
@ -89,12 +89,12 @@ func (r *Row) Reset() {
|
||||||
|
|
||||||
// Remove the children container itself.
|
// Remove the children container itself.
|
||||||
r.Box.Remove(r.children)
|
r.Box.Remove(r.children)
|
||||||
|
|
||||||
// Reset the state.
|
|
||||||
r.loaded = false
|
|
||||||
r.serverList = nil
|
|
||||||
r.children = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the state.
|
||||||
|
r.loaded = false
|
||||||
|
r.serverList = nil
|
||||||
|
r.children = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLoading is called by the parent struct.
|
// SetLoading is called by the parent struct.
|
||||||
|
|
|
@ -6,10 +6,13 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/keyring"
|
"github.com/diamondburned/cchat-gtk/internal/keyring"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/buttonoverlay"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/commander"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,6 +20,8 @@ const IconSize = 32
|
||||||
|
|
||||||
// Controller extends server.RowController to add session.
|
// Controller extends server.RowController to add session.
|
||||||
type Controller interface {
|
type Controller interface {
|
||||||
|
// GetService asks the controller for its service.
|
||||||
|
GetService() cchat.Service
|
||||||
// OnSessionDisconnect is called before a session is disconnected. This
|
// OnSessionDisconnect is called before a session is disconnected. This
|
||||||
// function is used for cleanups.
|
// function is used for cleanups.
|
||||||
OnSessionDisconnect(*Row)
|
OnSessionDisconnect(*Row)
|
||||||
|
@ -42,6 +47,9 @@ type Row struct {
|
||||||
sessionID string // used for reconnection
|
sessionID string // used for reconnection
|
||||||
|
|
||||||
ctrl Controller
|
ctrl Controller
|
||||||
|
|
||||||
|
cmder *commander.Buffer
|
||||||
|
cmdbtn *gtk.Button
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Row {
|
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Row {
|
||||||
|
@ -58,14 +66,36 @@ func NewLoading(parent breadcrumb.Breadcrumber, id, name string, ctrl Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRow(parent breadcrumb.Breadcrumber, name text.Rich, ctrl Controller) *Row {
|
func newRow(parent breadcrumb.Breadcrumber, name text.Rich, ctrl Controller) *Row {
|
||||||
// Bind the row to .session in CSS.
|
srow := server.NewRow(parent, name)
|
||||||
row := server.NewRow(parent, name)
|
srow.Button.SetPlaceholderIcon("user-invisible-symbolic", IconSize)
|
||||||
row.Button.SetPlaceholderIcon("user-invisible-symbolic", IconSize)
|
srow.Show()
|
||||||
row.Show()
|
|
||||||
primitives.AddClass(row, "session")
|
|
||||||
primitives.AddClass(row, "server-list")
|
|
||||||
|
|
||||||
return &Row{Row: row, ctrl: ctrl}
|
// Bind the row to .session in CSS.
|
||||||
|
primitives.AddClass(srow, "session")
|
||||||
|
primitives.AddClass(srow, "server-list")
|
||||||
|
|
||||||
|
// Make a commander button that's hidden by default in case.
|
||||||
|
cmdbtn, _ := gtk.ButtonNewFromIconName("utilities-terminal-symbolic", gtk.ICON_SIZE_BUTTON)
|
||||||
|
buttonoverlay.Take(srow.Button, cmdbtn, server.IconSize)
|
||||||
|
|
||||||
|
row := &Row{
|
||||||
|
Row: srow,
|
||||||
|
ctrl: ctrl,
|
||||||
|
cmdbtn: cmdbtn,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdbtn.Connect("clicked", row.ShowCommander)
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset extends the server row's Reset function and resets additional states.
|
||||||
|
// It resets all states back to nil, but the session ID stays.
|
||||||
|
func (r *Row) Reset() {
|
||||||
|
r.Row.Reset()
|
||||||
|
r.Session = nil
|
||||||
|
r.cmder = nil
|
||||||
|
r.cmdbtn.Hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveSession removes itself from the session list.
|
// RemoveSession removes itself from the session list.
|
||||||
|
@ -162,6 +192,16 @@ func (r *Row) SetSession(ses cchat.Session) {
|
||||||
r.SetLabelUnsafe(ses.Name())
|
r.SetLabelUnsafe(ses.Name())
|
||||||
r.SetIconer(ses)
|
r.SetIconer(ses)
|
||||||
|
|
||||||
|
// Set the commander, if any. The function will return nil if the assertion
|
||||||
|
// returns nil. As such, we assert with an ignored ok bool, allowing cmd to
|
||||||
|
// be nil.
|
||||||
|
cmd, _ := ses.(commander.SessionCommander)
|
||||||
|
r.cmder = commander.NewBuffer(r.ctrl.GetService(), cmd)
|
||||||
|
// Show the command button if the session actually supports the commander.
|
||||||
|
if r.cmder != nil {
|
||||||
|
r.cmdbtn.Show()
|
||||||
|
}
|
||||||
|
|
||||||
// Bind extra menu items before loading. These items won't be clickable
|
// Bind extra menu items before loading. These items won't be clickable
|
||||||
// during loading.
|
// during loading.
|
||||||
r.SetNormalExtraMenu([]menu.Item{
|
r.SetNormalExtraMenu([]menu.Item{
|
||||||
|
@ -183,3 +223,12 @@ func (r *Row) RowSelected(server *server.ServerRow, smsg cchat.ServerMessage) {
|
||||||
func (r *Row) BindMover(id string) {
|
func (r *Row) BindMover(id string) {
|
||||||
primitives.BindDragSortable(r.Button, "GTK_TOGGLE_BUTTON", id, r.ctrl.MoveSession)
|
primitives.BindDragSortable(r.Button, "GTK_TOGGLE_BUTTON", id, r.ctrl.MoveSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShowCommander shows the commander dialog, or it does nothing if session does
|
||||||
|
// not implement commander.
|
||||||
|
func (r *Row) ShowCommander() {
|
||||||
|
if r.cmder == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.cmder.ShowDialog()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue