mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2025-03-20 17:09:19 +00:00
More features, too many to list
This commit is contained in:
parent
43aa4dcef3
commit
8d9c3f2da5
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/gob"
|
||||
"strings"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zalando/go-keyring"
|
||||
|
@ -37,6 +38,31 @@ type Session struct {
|
|||
Data map[string]string
|
||||
}
|
||||
|
||||
func GetSession(ses cchat.Session, name string) *Session {
|
||||
saver, ok := ses.(cchat.SessionSaver)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
s, err := saver.Save()
|
||||
if err != nil {
|
||||
log.Error(errors.Wrapf(err, "Failed to save session ID %s (%s)", ses.ID(), name))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Treat the ID as name if none is provided. This is a shitty hack around
|
||||
// backends that only set the name after returning.
|
||||
if name == "" {
|
||||
name = ses.ID()
|
||||
}
|
||||
|
||||
return &Session{
|
||||
ID: ses.ID(),
|
||||
Name: name,
|
||||
Data: s,
|
||||
}
|
||||
}
|
||||
|
||||
func SaveSessions(serviceName string, sessions []Session) {
|
||||
if err := set(serviceName, sessions); err != nil {
|
||||
log.Warn(errors.Wrap(err, "Error saving session"))
|
||||
|
|
|
@ -40,7 +40,12 @@ func (c *Container) NewMessage(msg cchat.MessageCreate) container.GridMessage {
|
|||
|
||||
func (c *Container) NewPresendMessage(msg input.PresendMessage) container.PresendGridMessage {
|
||||
var presend = NewFullSendingMessage(msg)
|
||||
c.reuseAvatar(msg.AuthorID(), presend.Avatar)
|
||||
|
||||
// Try and see if we can reuse the avatar, and fallback if possible.
|
||||
if !c.reuseAvatar(msg.AuthorID(), presend.Avatar) {
|
||||
presend.overrideAuthorAvatar(msg.AuthorAvatarURL())
|
||||
}
|
||||
|
||||
return presend
|
||||
}
|
||||
|
||||
|
|
|
@ -114,3 +114,18 @@ func NewFullSendingMessage(msg input.PresendMessage) *FullSendingMessage {
|
|||
FullMessage: *WrapFullMessage(msgc.GenericContainer),
|
||||
}
|
||||
}
|
||||
|
||||
// make an exception for sending messages.
|
||||
func (m *FullSendingMessage) overrideAuthorAvatar(url string) {
|
||||
if url == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: put in fn
|
||||
httputil.AsyncImageSized(
|
||||
m.Avatar,
|
||||
url,
|
||||
AvatarSize, AvatarSize,
|
||||
imgutil.Round(true),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
|
@ -71,20 +74,30 @@ func NewField(ctrl Controller) *Field {
|
|||
return field
|
||||
}
|
||||
|
||||
// SetSender changes the sender of the input field. If nil, the input will be
|
||||
// disabled.
|
||||
func (f *Field) SetSender(session cchat.Session, sender cchat.ServerMessageSender) {
|
||||
f.UserID = session.ID()
|
||||
// Reset prepares the field before SetSender() is called.
|
||||
func (f *Field) Reset() {
|
||||
// Paranoia.
|
||||
f.text.SetSensitive(false)
|
||||
|
||||
f.UserID = ""
|
||||
f.sender = nil
|
||||
f.username.Reset()
|
||||
|
||||
// reset the input
|
||||
f.buffer.Delete(f.buffer.GetBounds())
|
||||
}
|
||||
|
||||
// SetSender changes the sender of the input field. If nil, the input will be
|
||||
// disabled. Reset() should be called first.
|
||||
func (f *Field) SetSender(session cchat.Session, sender cchat.ServerMessageSender) {
|
||||
// Update the left username container in the input.
|
||||
f.username.Update(session, sender)
|
||||
|
||||
// Set the sender.
|
||||
f.sender = sender
|
||||
f.text.SetSensitive(sender != nil) // grey if sender is nil
|
||||
|
||||
// reset the input
|
||||
f.buffer.Delete(f.buffer.GetBounds())
|
||||
if sender != nil {
|
||||
f.sender = sender
|
||||
f.text.SetSensitive(true)
|
||||
}
|
||||
}
|
||||
|
||||
// SendMessage yanks the text from the input field and sends it to the backend.
|
||||
|
@ -100,7 +113,13 @@ func (f *Field) SendMessage() {
|
|||
}
|
||||
|
||||
var sender = f.sender
|
||||
var data = NewSendMessageData(text, f.username.GetLabel(), f.UserID)
|
||||
var data = SendMessageData{
|
||||
content: text,
|
||||
author: f.username.GetLabel(),
|
||||
authorID: f.UserID,
|
||||
authorURL: f.username.GetIconURL(),
|
||||
nonce: "cchat-gtk_" + strconv.FormatInt(time.Now().UnixNano(), 10),
|
||||
}
|
||||
|
||||
// presend message into the container through the controller
|
||||
var done = f.ctrl.PresendMessage(data)
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
package input
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
type SendMessageData struct {
|
||||
content string
|
||||
author text.Rich
|
||||
authorID string
|
||||
nonce string
|
||||
content string
|
||||
author text.Rich
|
||||
authorID string
|
||||
authorURL string // avatar
|
||||
nonce string
|
||||
}
|
||||
|
||||
type PresendMessage interface {
|
||||
|
@ -21,6 +19,7 @@ type PresendMessage interface {
|
|||
|
||||
Author() text.Rich
|
||||
AuthorID() string
|
||||
AuthorAvatarURL() string // may be empty
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -28,14 +27,6 @@ var (
|
|||
_ cchat.MessageNonce = (*SendMessageData)(nil)
|
||||
)
|
||||
|
||||
func NewSendMessageData(content string, author text.Rich, authorID string) SendMessageData {
|
||||
return SendMessageData{
|
||||
content: content,
|
||||
author: author,
|
||||
nonce: "cchat-gtk_" + strconv.FormatInt(time.Now().UnixNano(), 10),
|
||||
}
|
||||
}
|
||||
|
||||
func (s SendMessageData) Content() string {
|
||||
return s.content
|
||||
}
|
||||
|
@ -48,6 +39,10 @@ func (s SendMessageData) AuthorID() string {
|
|||
return s.authorID
|
||||
}
|
||||
|
||||
func (s SendMessageData) AuthorAvatarURL() string {
|
||||
return s.authorURL
|
||||
}
|
||||
|
||||
func (s SendMessageData) Nonce() string {
|
||||
return s.nonce
|
||||
}
|
||||
|
|
|
@ -60,6 +60,12 @@ func newUsernameContainer() *usernameContainer {
|
|||
}
|
||||
}
|
||||
|
||||
func (u *usernameContainer) Reset() {
|
||||
u.SetRevealChild(false)
|
||||
u.avatar.Reset()
|
||||
u.label.Reset()
|
||||
}
|
||||
|
||||
// Update is not thread-safe.
|
||||
func (u *usernameContainer) Update(session cchat.Session, sender cchat.ServerMessageSender) {
|
||||
// Does sender (aka Server) implement ServerNickname? If not, we fallback to
|
||||
|
@ -113,3 +119,8 @@ func (u *usernameContainer) SetIcon(url string) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// GetIconURL is not thread-safe.
|
||||
func (u *usernameContainer) GetIconURL() string {
|
||||
return u.avatar.URL()
|
||||
}
|
||||
|
|
|
@ -46,8 +46,8 @@ func NewView() *View {
|
|||
return view
|
||||
}
|
||||
|
||||
// JoinServer is not thread-safe, but it calls backend functions asynchronously.
|
||||
func (v *View) JoinServer(session cchat.Session, server cchat.ServerMessage) {
|
||||
func (v *View) Reset() {
|
||||
// Leave the server if any.
|
||||
if v.current != nil {
|
||||
// Backend should handle synchronizing joins and leaves if it needs to.
|
||||
go func() {
|
||||
|
@ -55,11 +55,19 @@ func (v *View) JoinServer(session cchat.Session, server cchat.ServerMessage) {
|
|||
log.Error(errors.Wrap(err, "Error leaving server"))
|
||||
}
|
||||
}()
|
||||
|
||||
// Clean all messages.
|
||||
v.Container.Reset()
|
||||
}
|
||||
|
||||
// Clean all messages.
|
||||
v.Container.Reset()
|
||||
|
||||
// Reset the input.
|
||||
v.SendInput.Reset()
|
||||
}
|
||||
|
||||
// JoinServer is not thread-safe, but it calls backend functions asynchronously.
|
||||
func (v *View) JoinServer(session cchat.Session, server cchat.ServerMessage) {
|
||||
// Reset before setting.
|
||||
v.Reset()
|
||||
v.current = server
|
||||
|
||||
// Skipping ok check because sender can be nil. Without the empty check, Go
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package primitives
|
||||
|
||||
import "github.com/gotk3/gotk3/gtk"
|
||||
import (
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
type StyleContexter interface {
|
||||
GetStyleContext() (*gtk.StyleContext, error)
|
||||
|
@ -41,20 +46,32 @@ func SetImageIcon(img *gtk.Image, icon string, sizepx int) {
|
|||
img.SetSizeRequest(sizepx, sizepx)
|
||||
}
|
||||
|
||||
type MenuItem struct {
|
||||
Name string
|
||||
Fn func()
|
||||
}
|
||||
|
||||
func AppendMenuItems(menu interface{ Append(gtk.IMenuItem) }, items []MenuItem) {
|
||||
func AppendMenuItems(menu interface{ Append(gtk.IMenuItem) }, items []*gtk.MenuItem) {
|
||||
for _, item := range items {
|
||||
menu.Append(NewMenuItem(item.Name, item.Fn))
|
||||
menu.Append(item)
|
||||
}
|
||||
}
|
||||
|
||||
func NewMenuItem(label string, fn func()) *gtk.MenuItem {
|
||||
func HiddenMenuItem(label string, fn func()) *gtk.MenuItem {
|
||||
mb, _ := gtk.MenuItemNewWithLabel(label)
|
||||
mb.Show()
|
||||
mb.Connect("activate", fn)
|
||||
return mb
|
||||
}
|
||||
|
||||
func MenuItem(label string, fn func()) *gtk.MenuItem {
|
||||
menuitem := HiddenMenuItem(label, fn)
|
||||
menuitem.Show()
|
||||
return menuitem
|
||||
}
|
||||
|
||||
type Connector interface {
|
||||
Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error)
|
||||
}
|
||||
|
||||
func BindMenu(menu *gtk.Menu, connector Connector) {
|
||||
connector.Connect("event", func(_ *gtk.ToggleButton, ev *gdk.Event) {
|
||||
if gts.EventIsRightClick(ev) {
|
||||
menu.PopupAtPointer(ev)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,11 +14,12 @@ import (
|
|||
|
||||
type Icon struct {
|
||||
*gtk.Revealer
|
||||
Image *gtk.Image
|
||||
|
||||
Image *gtk.Image
|
||||
resizer imgutil.Processor
|
||||
procs []imgutil.Processor
|
||||
url string // state
|
||||
|
||||
// state
|
||||
url string
|
||||
}
|
||||
|
||||
const DefaultIconSize = 16
|
||||
|
@ -48,6 +49,12 @@ func NewIcon(sizepx int, procs ...imgutil.Processor) *Icon {
|
|||
}
|
||||
}
|
||||
|
||||
// Reset wipes the state to be just after construction.
|
||||
func (i *Icon) Reset() {
|
||||
i.url = ""
|
||||
i.Revealer.SetRevealChild(false)
|
||||
}
|
||||
|
||||
// URL is not thread-safe.
|
||||
func (i *Icon) URL() string {
|
||||
return i.url
|
||||
|
|
|
@ -44,6 +44,12 @@ func NewLabel(content text.Rich) *Label {
|
|||
return &Label{*label, content}
|
||||
}
|
||||
|
||||
// Reset wipes the state to be just after construction.
|
||||
func (l *Label) Reset() {
|
||||
l.current = text.Rich{}
|
||||
l.Label.SetText("")
|
||||
}
|
||||
|
||||
// SetLabel is thread-safe.
|
||||
func (l *Label) SetLabel(content text.Rich) {
|
||||
gts.ExecAsync(func() {
|
||||
|
|
|
@ -2,12 +2,11 @@ package service
|
|||
|
||||
import (
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/imgutil"
|
||||
"github.com/gotk3/gotk3/gdk"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -47,14 +46,9 @@ func newHeader(svc cchat.Service) *header {
|
|||
}
|
||||
}
|
||||
|
||||
menu, _ := gtk.MenuNew()
|
||||
|
||||
// Spawn the menu on right click.
|
||||
reveal.Connect("event", func(_ *gtk.ToggleButton, ev *gdk.Event) {
|
||||
if gts.EventIsRightClick(ev) {
|
||||
menu.PopupAtPointer(ev)
|
||||
}
|
||||
})
|
||||
menu, _ := gtk.MenuNew()
|
||||
primitives.BindMenu(menu, reveal)
|
||||
|
||||
return &header{box, reveal, add, menu}
|
||||
}
|
||||
|
|
|
@ -2,25 +2,17 @@ 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 Controller interface {
|
||||
session.Controller
|
||||
|
||||
// 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)
|
||||
// SaveAllSessions is called to save all available sessions from the menu.
|
||||
SaveAllSessions(*Container)
|
||||
}
|
||||
|
||||
type View struct {
|
||||
*gtk.ScrolledWindow
|
||||
Box *gtk.Box
|
||||
|
@ -49,9 +41,23 @@ 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
|
||||
|
@ -64,6 +70,9 @@ type Container struct {
|
|||
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()
|
||||
|
||||
|
@ -104,10 +113,10 @@ func NewContainer(svc cchat.Service, ctrl Controller) *Container {
|
|||
})
|
||||
|
||||
// Make menu items.
|
||||
primitives.AppendMenuItems(header.Menu, []primitives.MenuItem{
|
||||
{Name: "Save Sessions", Fn: func() {
|
||||
ctrl.SaveAllSessions(container)
|
||||
}},
|
||||
primitives.AppendMenuItems(header.Menu, []*gtk.MenuItem{
|
||||
primitives.MenuItem("Save Sessions", func() {
|
||||
container.SaveAllSessions()
|
||||
}),
|
||||
})
|
||||
|
||||
return container
|
||||
|
@ -116,20 +125,74 @@ func NewContainer(svc cchat.Service, ctrl Controller) *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
|
||||
}
|
||||
|
||||
// KeyringSessions returns all known keyring sessions. Sessions that can't be
|
||||
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 {
|
||||
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 {
|
||||
|
|
|
@ -62,9 +62,10 @@ func NewRow(parent breadcrumb.Breadcrumber, server cchat.Server, ctrl Controller
|
|||
|
||||
switch server := server.(type) {
|
||||
case cchat.ServerList:
|
||||
row.children = NewChildren(row, server, ctrl)
|
||||
box.PackStart(row.children, false, false, 0)
|
||||
row.children = NewChildren(row, ctrl)
|
||||
row.children.SetServerList(server)
|
||||
|
||||
box.PackStart(row.children, false, false, 0)
|
||||
primitives.AddClass(box, "server-list")
|
||||
|
||||
case cchat.ServerMessage:
|
||||
|
@ -112,7 +113,7 @@ type Children struct {
|
|||
Parent breadcrumb.Breadcrumber
|
||||
}
|
||||
|
||||
func NewChildren(parent breadcrumb.Breadcrumber, list cchat.ServerList, ctrl Controller) *Children {
|
||||
func NewChildren(parent breadcrumb.Breadcrumber, ctrl Controller) *Children {
|
||||
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||
main.SetMarginStart(ChildrenMargin)
|
||||
main.Show()
|
||||
|
@ -122,19 +123,20 @@ func NewChildren(parent breadcrumb.Breadcrumber, list cchat.ServerList, ctrl Con
|
|||
rev.Add(main)
|
||||
rev.Show()
|
||||
|
||||
children := &Children{
|
||||
return &Children{
|
||||
Revealer: rev,
|
||||
Main: main,
|
||||
List: list,
|
||||
rowctrl: ctrl,
|
||||
Parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
if err := list.Servers(children); err != nil {
|
||||
func (c *Children) SetServerList(list cchat.ServerList) {
|
||||
c.List = list
|
||||
|
||||
if err := list.Servers(c); err != nil {
|
||||
log.Error(errors.Wrap(err, "Failed to get servers"))
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
func (c *Children) SetServers(servers []cchat.Server) {
|
||||
|
|
|
@ -3,7 +3,6 @@ package session
|
|||
import (
|
||||
"github.com/diamondburned/cchat"
|
||||
"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/rich"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||
|
@ -11,7 +10,6 @@ import (
|
|||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/imgutil"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const IconSize = 32
|
||||
|
@ -19,18 +17,24 @@ const IconSize = 32
|
|||
// Controller extends server.RowController to add session.
|
||||
type Controller interface {
|
||||
MessageRowSelected(*Row, *server.Row, cchat.ServerMessage)
|
||||
RestoreSession(*Row, cchat.SessionRestorer) // async
|
||||
RestoreSession(*Row, keyring.Session) // async
|
||||
RemoveSession(id string)
|
||||
}
|
||||
|
||||
type Row struct {
|
||||
*gtk.Box
|
||||
Button *rich.ToggleButtonImage
|
||||
Session cchat.Session
|
||||
|
||||
Servers *server.Children
|
||||
|
||||
menu *gtk.Menu
|
||||
retry *gtk.MenuItem
|
||||
|
||||
ctrl Controller
|
||||
parent breadcrumb.Breadcrumber
|
||||
|
||||
// nil after calling SetSession()
|
||||
krs keyring.Session
|
||||
}
|
||||
|
||||
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Row {
|
||||
|
@ -42,8 +46,7 @@ func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Ro
|
|||
func NewLoading(parent breadcrumb.Breadcrumber, name string, ctrl Controller) *Row {
|
||||
row := new(parent, ctrl)
|
||||
row.Button.SetLabelUnsafe(text.Rich{Content: name})
|
||||
row.Button.Image.SetPlaceholderIcon("content-loading-symbolic", IconSize)
|
||||
row.SetSensitive(false)
|
||||
row.setLoading()
|
||||
|
||||
return row
|
||||
}
|
||||
|
@ -53,6 +56,7 @@ func new(parent breadcrumb.Breadcrumber, ctrl Controller) *Row {
|
|||
ctrl: ctrl,
|
||||
parent: parent,
|
||||
}
|
||||
row.Servers = server.NewChildren(parent, row)
|
||||
|
||||
row.Button = rich.NewToggleButtonImage(text.Rich{})
|
||||
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
||||
|
@ -74,48 +78,72 @@ func new(parent breadcrumb.Breadcrumber, ctrl Controller) *Row {
|
|||
|
||||
primitives.AddClass(row.Box, "session")
|
||||
|
||||
row.menu, _ = gtk.MenuNew()
|
||||
primitives.BindMenu(row.menu, row.Button)
|
||||
|
||||
row.retry = primitives.HiddenMenuItem("Retry", func() {
|
||||
// Show the loading stuff.
|
||||
row.setLoading()
|
||||
// Reuse the failed keyring session provided. As this variable is reset
|
||||
// after a success, it relies of the button not triggering.
|
||||
ctrl.RestoreSession(row, row.krs)
|
||||
})
|
||||
row.retry.SetSensitive(false)
|
||||
|
||||
primitives.AppendMenuItems(row.menu, []*gtk.MenuItem{
|
||||
row.retry,
|
||||
primitives.MenuItem("Remove", func() {
|
||||
ctrl.RemoveSession(row.Session.ID())
|
||||
}),
|
||||
})
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
func (r *Row) setLoading() {
|
||||
// set the loading icon
|
||||
r.Button.Image.SetPlaceholderIcon("content-loading-symbolic", IconSize)
|
||||
// restore the old label's color
|
||||
r.Button.SetLabelUnsafe(r.Button.GetLabel())
|
||||
// blur - set the color darker
|
||||
r.SetSensitive(false)
|
||||
}
|
||||
|
||||
// KeyringSession returns a keyring session, or nil if the session cannot be
|
||||
// saved. This function is not cached, as I'd rather not keep the map in memory.
|
||||
// saved.
|
||||
func (r *Row) KeyringSession() *keyring.Session {
|
||||
// Is the session saveable?
|
||||
saver, ok := r.Session.(cchat.SessionSaver)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
ks := keyring.Session{
|
||||
ID: r.Session.ID(),
|
||||
Name: r.Button.GetText(),
|
||||
}
|
||||
|
||||
s, err := saver.Save()
|
||||
if err != nil {
|
||||
log.Error(errors.Wrapf(err, "Failed to save session ID %s (%s)", ks.ID, ks.Name))
|
||||
return nil
|
||||
}
|
||||
ks.Data = s
|
||||
|
||||
return &ks
|
||||
return keyring.GetSession(r.Session, r.Button.GetText())
|
||||
}
|
||||
|
||||
func (r *Row) SetSession(ses cchat.Session) {
|
||||
// Disable the retry button.
|
||||
r.retry.SetSensitive(false)
|
||||
r.retry.Hide()
|
||||
|
||||
r.Session = ses
|
||||
r.Servers = server.NewChildren(r, ses, r)
|
||||
r.Servers.SetServerList(ses)
|
||||
r.Button.Image.SetPlaceholderIcon("user-available-symbolic", IconSize)
|
||||
r.Box.PackStart(r.Servers, false, false, 0)
|
||||
r.SetSensitive(true)
|
||||
|
||||
// Set the session's name to the button.
|
||||
r.Button.Try(ses, "session")
|
||||
|
||||
// Wipe the keyring session off.
|
||||
r.krs = keyring.Session{}
|
||||
}
|
||||
|
||||
func (r *Row) SetFailed(err error) {
|
||||
func (r *Row) SetFailed(krs keyring.Session, err error) {
|
||||
// Set the failed keyring session.
|
||||
r.krs = krs
|
||||
|
||||
// Allow the retry button to be pressed.
|
||||
r.retry.SetSensitive(true)
|
||||
r.retry.Show()
|
||||
|
||||
r.SetSensitive(true)
|
||||
r.SetTooltipText(err.Error())
|
||||
// TODO: setting the label directly here is kind of shitty, as it screws up
|
||||
// the getter. Fix?
|
||||
// Intentional side-effect of not changing the actual label state.
|
||||
r.Button.Label.SetMarkup(rich.MakeRed(r.Button.GetLabel()))
|
||||
}
|
||||
|
||||
|
|
|
@ -3,15 +3,12 @@ package ui
|
|||
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/service"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/auth"
|
||||
"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/markbates/pkger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -45,47 +42,12 @@ func NewApplication() *App {
|
|||
}
|
||||
|
||||
func (app *App) AddService(svc cchat.Service) {
|
||||
var container = app.window.Services.AddService(svc, app)
|
||||
|
||||
// Can this session be restored? If not, exit.
|
||||
restorer, ok := container.Service.(cchat.SessionRestorer)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var sessions = keyring.RestoreSessions(container.Service.Name())
|
||||
|
||||
for _, krs := range sessions {
|
||||
// Copy the session to avoid race conditions.
|
||||
krs := krs
|
||||
row := container.AddLoadingSession(krs.ID, krs.Name)
|
||||
|
||||
go app.restoreSession(row, restorer, krs)
|
||||
}
|
||||
app.window.Services.AddService(svc, app)
|
||||
}
|
||||
|
||||
// RestoreSession attempts to restore the session asynchronously.
|
||||
func (app *App) RestoreSession(row *session.Row, r cchat.SessionRestorer) {
|
||||
// Get the restore data.
|
||||
ks := row.KeyringSession()
|
||||
if ks == nil {
|
||||
log.Warn(errors.New("Attempted restore in ui.go"))
|
||||
return
|
||||
}
|
||||
go app.restoreSession(row, r, *ks)
|
||||
}
|
||||
|
||||
// synchronous op
|
||||
func (app *App) restoreSession(row *session.Row, r cchat.SessionRestorer, k keyring.Session) {
|
||||
s, err := r.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() { row.SetFailed(err) })
|
||||
} else {
|
||||
gts.ExecAsync(func() { row.SetSession(s) })
|
||||
}
|
||||
func (app *App) RemoveSession(string) {
|
||||
app.window.MessageView.Reset()
|
||||
app.header.SetBreadcrumb(nil)
|
||||
}
|
||||
|
||||
func (app *App) MessageRowSelected(ses *session.Row, srv *server.Row, smsg cchat.ServerMessage) {
|
||||
|
@ -107,16 +69,9 @@ func (app *App) MessageRowSelected(ses *session.Row, srv *server.Row, smsg cchat
|
|||
func (app *App) AuthenticateSession(container *service.Container, svc cchat.Service) {
|
||||
auth.NewDialog(svc.Name(), svc.Authenticate(), func(ses cchat.Session) {
|
||||
container.AddSession(ses)
|
||||
|
||||
// Try and save all keyring sessions.
|
||||
app.SaveAllSessions(container)
|
||||
})
|
||||
}
|
||||
|
||||
func (app *App) SaveAllSessions(container *service.Container) {
|
||||
keyring.SaveSessions(container.Service.Name(), container.KeyringSessions())
|
||||
}
|
||||
|
||||
func (app *App) Header() gtk.IWidget {
|
||||
return app.header
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue