mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-11-16 11:12:44 +00:00
208 lines
5.2 KiB
Go
208 lines
5.2 KiB
Go
package service
|
|
|
|
import (
|
|
"github.com/diamondburned/cchat"
|
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
|
"github.com/diamondburned/cchat-gtk/internal/keyring"
|
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
|
"github.com/gotk3/gotk3/gtk"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type View struct {
|
|
*gtk.ScrolledWindow
|
|
Box *gtk.Box
|
|
Services []*Container
|
|
}
|
|
|
|
func NewView() *View {
|
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
box.Show()
|
|
|
|
primitives.AddClass(box, "services")
|
|
|
|
sw, _ := gtk.ScrolledWindowNew(nil, nil)
|
|
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
|
sw.Add(box)
|
|
sw.Show()
|
|
|
|
return &View{
|
|
sw,
|
|
box,
|
|
nil,
|
|
}
|
|
}
|
|
|
|
func (v *View) AddService(svc cchat.Service, ctrl Controller) *Container {
|
|
s := NewContainer(svc, ctrl)
|
|
v.Services = append(v.Services, s)
|
|
v.Box.Add(s)
|
|
|
|
// Try and restore all sessions.
|
|
s.restoreAllSessions()
|
|
|
|
return s
|
|
}
|
|
|
|
type Controller interface {
|
|
// MessageRowSelected is wrapped around session's MessageRowSelected.
|
|
MessageRowSelected(*session.Row, *server.Row, cchat.ServerMessage)
|
|
// AuthenticateSession is called to spawn the authentication dialog.
|
|
AuthenticateSession(*Container, cchat.Service)
|
|
// RemoveSession is called to remove a session. This should also clear out
|
|
// the message view in the parent package.
|
|
RemoveSession(id string)
|
|
}
|
|
|
|
type Container struct {
|
|
*gtk.Box
|
|
Service cchat.Service
|
|
|
|
header *header
|
|
revealer *gtk.Revealer
|
|
children *children
|
|
|
|
// Embed controller and extend it to override RestoreSession.
|
|
Controller
|
|
}
|
|
|
|
// Guarantee that our interface is up-to-date with session's controller.
|
|
var _ session.Controller = (*Container)(nil)
|
|
|
|
func NewContainer(svc cchat.Service, ctrl Controller) *Container {
|
|
children := newChildren()
|
|
|
|
chrev, _ := gtk.RevealerNew()
|
|
chrev.SetRevealChild(true)
|
|
chrev.Add(children)
|
|
chrev.Show()
|
|
|
|
header := newHeader(svc)
|
|
header.reveal.SetActive(chrev.GetRevealChild())
|
|
|
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
box.Show()
|
|
box.PackStart(header, false, false, 0)
|
|
box.PackStart(chrev, false, false, 0)
|
|
|
|
primitives.AddClass(box, "service")
|
|
|
|
container := &Container{
|
|
Box: box,
|
|
Service: svc,
|
|
header: header,
|
|
revealer: chrev,
|
|
children: children,
|
|
Controller: ctrl,
|
|
}
|
|
|
|
// On click, toggle reveal.
|
|
header.reveal.Connect("clicked", func() {
|
|
revealed := !chrev.GetRevealChild()
|
|
chrev.SetRevealChild(revealed)
|
|
header.reveal.SetActive(revealed)
|
|
})
|
|
|
|
// On click, show the auth dialog.
|
|
header.add.Connect("clicked", func() {
|
|
ctrl.AuthenticateSession(container, svc)
|
|
})
|
|
|
|
// Make menu items.
|
|
primitives.AppendMenuItems(header.Menu, []*gtk.MenuItem{
|
|
primitives.MenuItem("Save Sessions", func() {
|
|
container.SaveAllSessions()
|
|
}),
|
|
})
|
|
|
|
return container
|
|
}
|
|
|
|
func (c *Container) AddSession(ses cchat.Session) *session.Row {
|
|
srow := session.New(c, ses, c)
|
|
c.children.addSessionRow(ses.ID(), srow)
|
|
c.SaveAllSessions()
|
|
return srow
|
|
}
|
|
|
|
func (c *Container) AddLoadingSession(id, name string) *session.Row {
|
|
srow := session.NewLoading(c, name, c)
|
|
c.children.addSessionRow(id, srow)
|
|
return srow
|
|
}
|
|
|
|
func (c *Container) RemoveSession(id string) {
|
|
c.children.removeSessionRow(id)
|
|
c.SaveAllSessions()
|
|
// Call the parent's method.
|
|
c.Controller.RemoveSession(id)
|
|
}
|
|
|
|
// RestoreSession tries to restore sessions asynchronously. This satisfies
|
|
// session.Controller.
|
|
func (c *Container) RestoreSession(row *session.Row, krs keyring.Session) {
|
|
// Can this session be restored? If not, exit.
|
|
restorer, ok := c.Service.(cchat.SessionRestorer)
|
|
if !ok {
|
|
return
|
|
}
|
|
c.restoreSession(row, restorer, krs)
|
|
}
|
|
|
|
// internal method called on AddService.
|
|
func (c *Container) restoreAllSessions() {
|
|
// Can this session be restored? If not, exit.
|
|
restorer, ok := c.Service.(cchat.SessionRestorer)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var sessions = keyring.RestoreSessions(c.Service.Name())
|
|
|
|
for _, krs := range sessions {
|
|
// Copy the session to avoid race conditions.
|
|
krs := krs
|
|
row := c.AddLoadingSession(krs.ID, krs.Name)
|
|
|
|
c.restoreSession(row, restorer, krs)
|
|
}
|
|
}
|
|
|
|
func (c *Container) restoreSession(r *session.Row, res cchat.SessionRestorer, k keyring.Session) {
|
|
go func() {
|
|
s, err := res.RestoreSession(k.Data)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "Failed to restore session %s (%s)", k.ID, k.Name)
|
|
log.Error(err)
|
|
|
|
gts.ExecAsync(func() { r.SetFailed(k, err) })
|
|
} else {
|
|
gts.ExecAsync(func() { r.SetSession(s) })
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (c *Container) SaveAllSessions() {
|
|
keyring.SaveSessions(c.Service.Name(), c.keyringSessions())
|
|
}
|
|
|
|
// keyringSessions returns all known keyring sessions. Sessions that can't be
|
|
// saved will not be in the slice.
|
|
func (c *Container) keyringSessions() []keyring.Session {
|
|
var ksessions = make([]keyring.Session, 0, len(c.children.Sessions))
|
|
for _, s := range c.children.Sessions {
|
|
if k := s.KeyringSession(); k != nil {
|
|
ksessions = append(ksessions, *k)
|
|
}
|
|
}
|
|
return ksessions
|
|
}
|
|
|
|
func (c *Container) Breadcrumb() breadcrumb.Breadcrumb {
|
|
return breadcrumb.Try(nil, c.header.reveal.GetText())
|
|
}
|