2020-05-26 06:51:06 +00:00
|
|
|
package session
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/diamondburned/cchat"
|
2020-06-14 18:19:06 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
2020-06-07 04:27:28 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/keyring"
|
2020-06-14 18:19:06 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
2020-05-28 19:26:55 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
2020-06-04 23:00:41 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
2020-06-06 00:47:28 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
2020-05-26 06:51:06 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
2020-06-04 23:00:41 +00:00
|
|
|
"github.com/diamondburned/cchat/text"
|
2020-06-07 04:27:28 +00:00
|
|
|
"github.com/diamondburned/imgutil"
|
2020-06-13 07:29:32 +00:00
|
|
|
"github.com/gotk3/gotk3/gdk"
|
2020-05-26 06:51:06 +00:00
|
|
|
"github.com/gotk3/gotk3/gtk"
|
2020-06-14 18:19:06 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-05-26 06:51:06 +00:00
|
|
|
)
|
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
const IconSize = 32
|
|
|
|
|
|
|
|
// Controller extends server.RowController to add session.
|
|
|
|
type Controller interface {
|
2020-06-14 18:19:06 +00:00
|
|
|
// OnSessionDisconnect is called before a session is disconnected. This
|
|
|
|
// function is used for cleanups.
|
|
|
|
OnSessionDisconnect(*Row)
|
|
|
|
// MessageRowSelected is called when a server that can display messages (aka
|
|
|
|
// implements ServerMessage) is called.
|
2020-06-04 23:00:41 +00:00
|
|
|
MessageRowSelected(*Row, *server.Row, cchat.ServerMessage)
|
2020-06-14 18:19:06 +00:00
|
|
|
// RestoreSession is called with the session ID to ask the controller to
|
|
|
|
// restore it from keyring information.
|
|
|
|
RestoreSession(*Row, string) // ID string, async
|
|
|
|
// RemoveSession is called to ask the controller to remove the session from
|
|
|
|
// the list of sessions.
|
2020-06-13 07:29:32 +00:00
|
|
|
RemoveSession(*Row)
|
2020-06-14 18:19:06 +00:00
|
|
|
// MoveSession is called to ask the controller to move the session to
|
|
|
|
// somewhere else in the list of sessions.
|
2020-06-13 07:29:32 +00:00
|
|
|
MoveSession(id, movingID string)
|
2020-06-04 23:00:41 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
// Row represents a single session, including the button header and the
|
|
|
|
// children servers.
|
2020-05-26 06:51:06 +00:00
|
|
|
type Row struct {
|
|
|
|
*gtk.Box
|
2020-06-04 23:00:41 +00:00
|
|
|
Button *rich.ToggleButtonImage
|
2020-05-26 06:51:06 +00:00
|
|
|
Session cchat.Session
|
|
|
|
Servers *server.Children
|
2020-06-04 23:00:41 +00:00
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
ctrl Controller
|
|
|
|
parent breadcrumb.Breadcrumber
|
|
|
|
menuconstr func(*gtk.Menu)
|
|
|
|
sessionID string // used for reconnection
|
2020-06-07 07:06:13 +00:00
|
|
|
|
|
|
|
// nil after calling SetSession()
|
2020-06-14 18:19:06 +00:00
|
|
|
// krs keyring.Session
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 00:47:28 +00:00
|
|
|
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Row {
|
2020-06-14 18:19:06 +00:00
|
|
|
row := newRow(parent, ctrl)
|
2020-06-07 04:27:28 +00:00
|
|
|
row.SetSession(ses)
|
|
|
|
return row
|
|
|
|
}
|
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
func NewLoading(parent breadcrumb.Breadcrumber, id, name string, ctrl Controller) *Row {
|
|
|
|
row := newRow(parent, ctrl)
|
|
|
|
row.sessionID = id
|
2020-06-07 04:27:28 +00:00
|
|
|
row.Button.SetLabelUnsafe(text.Rich{Content: name})
|
2020-06-07 07:06:13 +00:00
|
|
|
row.setLoading()
|
2020-06-07 04:27:28 +00:00
|
|
|
|
|
|
|
return row
|
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
var dragEntries = []gtk.TargetEntry{
|
|
|
|
primitives.NewTargetEntry("GTK_TOGGLE_BUTTON"),
|
|
|
|
}
|
|
|
|
var dragAtom = gdk.GdkAtomIntern("GTK_TOGGLE_BUTTON", true)
|
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
func newRow(parent breadcrumb.Breadcrumber, ctrl Controller) *Row {
|
2020-06-04 23:00:41 +00:00
|
|
|
row := &Row{
|
2020-06-07 04:27:28 +00:00
|
|
|
ctrl: ctrl,
|
|
|
|
parent: parent,
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
2020-06-07 07:06:13 +00:00
|
|
|
row.Servers = server.NewChildren(parent, row)
|
2020-06-14 18:19:06 +00:00
|
|
|
row.Servers.SetLoading()
|
2020-05-26 06:51:06 +00:00
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
row.Button = rich.NewToggleButtonImage(text.Rich{})
|
2020-06-04 23:00:41 +00:00
|
|
|
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
2020-06-07 04:27:28 +00:00
|
|
|
row.Button.Image.AddProcessors(imgutil.Round(true))
|
|
|
|
// Set the loading icon.
|
2020-06-04 23:00:41 +00:00
|
|
|
row.Button.SetRelief(gtk.RELIEF_NONE)
|
2020-06-13 07:29:32 +00:00
|
|
|
row.Button.Show()
|
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
// On click, toggle reveal.
|
|
|
|
row.Button.Connect("clicked", func() {
|
|
|
|
revealed := !row.Servers.GetRevealChild()
|
|
|
|
row.Servers.SetRevealChild(revealed)
|
|
|
|
row.Button.SetActive(revealed)
|
|
|
|
})
|
2020-05-28 19:26:55 +00:00
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
row.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
|
|
row.Box.SetMarginStart(server.ChildrenMargin)
|
|
|
|
row.Box.PackStart(row.Button, false, false, 0)
|
|
|
|
row.Box.Show()
|
2020-05-28 19:26:55 +00:00
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
// Bind the box to .session in CSS.
|
2020-06-04 23:00:41 +00:00
|
|
|
primitives.AddClass(row.Box, "session")
|
2020-06-14 18:19:06 +00:00
|
|
|
// Bind the button to create a new menu.
|
|
|
|
primitives.BindDynamicMenu(row.Button, func(menu *gtk.Menu) {
|
|
|
|
row.menuconstr(menu)
|
2020-06-07 07:06:13 +00:00
|
|
|
})
|
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
// noop, empty menu
|
|
|
|
row.menuconstr = func(menu *gtk.Menu) {}
|
2020-06-07 07:06:13 +00:00
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
return row
|
|
|
|
}
|
2020-05-26 06:51:06 +00:00
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
// RemoveSession removes itself from the session list.
|
|
|
|
func (r *Row) RemoveSession() {
|
|
|
|
// Remove the session off the list.
|
|
|
|
r.ctrl.RemoveSession(r)
|
|
|
|
|
|
|
|
// Asynchrously disconnect.
|
|
|
|
go func() {
|
|
|
|
if err := r.Session.Disconnect(); err != nil {
|
|
|
|
log.Error(errors.Wrap(err, "Non-fatal, failed to disconnect removed session"))
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReconnectSession tries to reconnect with the keyring data. This is a slow
|
|
|
|
// method but it's also a very cold path.
|
|
|
|
func (r *Row) ReconnectSession() {
|
|
|
|
// If we haven't ever connected:
|
|
|
|
if r.sessionID == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.setLoading()
|
|
|
|
r.ctrl.RestoreSession(r, r.sessionID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DisconnectSession disconnects the current session.
|
|
|
|
func (r *Row) DisconnectSession() {
|
|
|
|
// Call the disconnect function from the controller first.
|
|
|
|
r.ctrl.OnSessionDisconnect(r)
|
|
|
|
|
|
|
|
// Show visually that we're disconnected first by wiping all servers.
|
|
|
|
r.Box.Remove(r.Servers)
|
|
|
|
r.Servers.Reset()
|
|
|
|
|
|
|
|
// Set the offline icon to the button.
|
|
|
|
r.Button.Image.SetPlaceholderIcon("user-invisible-symbolic", IconSize)
|
|
|
|
// Also unselect the button.
|
|
|
|
r.Button.SetActive(false)
|
|
|
|
|
|
|
|
// Disable the button because we're busy disconnecting. We'll re-enable them
|
|
|
|
// once we're done reconnecting.
|
|
|
|
r.SetSensitive(false)
|
|
|
|
|
|
|
|
// Try and disconnect asynchronously.
|
|
|
|
gts.Async(func() (func(), error) {
|
|
|
|
// Disconnect and wrap the error if any. Wrap works with a nil error.
|
|
|
|
err := errors.Wrap(r.Session.Disconnect(), "Failed to disconnect.")
|
|
|
|
return func() {
|
|
|
|
// allow access to the menu
|
|
|
|
r.SetSensitive(true)
|
|
|
|
|
|
|
|
// set the menu to allow disconnection.
|
|
|
|
r.menuconstr = func(menu *gtk.Menu) {
|
|
|
|
primitives.AppendMenuItems(menu, []gtk.IMenuItem{
|
|
|
|
primitives.MenuItem("Connect", r.ReconnectSession),
|
|
|
|
primitives.MenuItem("Remove", r.RemoveSession),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}, err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-07 07:06:13 +00:00
|
|
|
func (r *Row) setLoading() {
|
|
|
|
// set the loading icon
|
|
|
|
r.Button.Image.SetPlaceholderIcon("content-loading-symbolic", IconSize)
|
2020-06-14 18:19:06 +00:00
|
|
|
// set the loading icon in the servers list
|
|
|
|
r.Servers.SetLoading()
|
2020-06-07 07:06:13 +00:00
|
|
|
// restore the old label's color
|
|
|
|
r.Button.SetLabelUnsafe(r.Button.GetLabel())
|
2020-06-13 07:46:51 +00:00
|
|
|
// clear the tooltip
|
|
|
|
r.SetTooltipText("")
|
2020-06-07 07:06:13 +00:00
|
|
|
// blur - set the color darker
|
|
|
|
r.SetSensitive(false)
|
|
|
|
}
|
|
|
|
|
2020-06-07 04:27:28 +00:00
|
|
|
// KeyringSession returns a keyring session, or nil if the session cannot be
|
2020-06-07 07:06:13 +00:00
|
|
|
// saved.
|
2020-06-07 04:27:28 +00:00
|
|
|
func (r *Row) KeyringSession() *keyring.Session {
|
2020-06-14 18:19:06 +00:00
|
|
|
return keyring.ConvertSession(r.Session, r.Button.GetText())
|
2020-06-07 04:27:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
// ID returns the session ID.
|
|
|
|
func (r *Row) ID() string {
|
|
|
|
return r.sessionID
|
|
|
|
}
|
2020-06-07 07:06:13 +00:00
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
func (r *Row) SetSession(ses cchat.Session) {
|
2020-06-07 04:27:28 +00:00
|
|
|
r.Session = ses
|
2020-06-14 18:19:06 +00:00
|
|
|
r.sessionID = ses.ID()
|
|
|
|
|
2020-06-07 07:06:13 +00:00
|
|
|
r.Servers.SetServerList(ses)
|
2020-06-14 18:19:06 +00:00
|
|
|
r.Box.PackStart(r.Servers, false, false, 0)
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
r.Button.SetLabelUnsafe(ses.Name())
|
2020-06-07 04:27:28 +00:00
|
|
|
r.Button.Image.SetPlaceholderIcon("user-available-symbolic", IconSize)
|
2020-06-14 18:19:06 +00:00
|
|
|
|
2020-06-07 04:27:28 +00:00
|
|
|
r.SetSensitive(true)
|
2020-06-13 07:29:32 +00:00
|
|
|
r.SetTooltipText("") // reset
|
2020-06-07 04:27:28 +00:00
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
// Try and set the session's icon.
|
|
|
|
if iconer, ok := ses.(cchat.Icon); ok {
|
|
|
|
r.Button.Image.AsyncSetIcon(iconer.Icon, "Error fetching session icon URL")
|
|
|
|
}
|
2020-06-07 07:06:13 +00:00
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
// Set the menu with the disconnect button.
|
|
|
|
r.menuconstr = func(menu *gtk.Menu) {
|
|
|
|
primitives.AppendMenuItems(menu, []gtk.IMenuItem{
|
|
|
|
primitives.MenuItem("Disconnect", r.DisconnectSession),
|
|
|
|
primitives.MenuItem("Remove", r.RemoveSession),
|
|
|
|
})
|
|
|
|
}
|
2020-06-07 04:27:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-14 18:19:06 +00:00
|
|
|
func (r *Row) SetFailed(err error) {
|
2020-06-07 07:06:13 +00:00
|
|
|
// Allow the retry button to be pressed.
|
2020-06-14 18:19:06 +00:00
|
|
|
r.menuconstr = func(menu *gtk.Menu) {
|
|
|
|
primitives.AppendMenuItems(menu, []gtk.IMenuItem{
|
|
|
|
primitives.MenuItem("Retry", r.ReconnectSession),
|
|
|
|
primitives.MenuItem("Remove", r.RemoveSession),
|
|
|
|
})
|
|
|
|
}
|
2020-06-07 07:06:13 +00:00
|
|
|
|
|
|
|
r.SetSensitive(true)
|
2020-06-07 04:27:28 +00:00
|
|
|
r.SetTooltipText(err.Error())
|
2020-06-07 07:06:13 +00:00
|
|
|
// Intentional side-effect of not changing the actual label state.
|
2020-06-07 04:27:28 +00:00
|
|
|
r.Button.Label.SetMarkup(rich.MakeRed(r.Button.GetLabel()))
|
2020-06-14 18:19:06 +00:00
|
|
|
// Set the icon to a failed one.
|
|
|
|
r.Button.Image.SetPlaceholderIcon("computer-fail-symbolic", IconSize)
|
2020-06-07 04:27:28 +00:00
|
|
|
}
|
|
|
|
|
2020-06-04 23:00:41 +00:00
|
|
|
func (r *Row) MessageRowSelected(server *server.Row, smsg cchat.ServerMessage) {
|
|
|
|
r.ctrl.MessageRowSelected(r, server, smsg)
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
2020-06-06 00:47:28 +00:00
|
|
|
|
|
|
|
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
|
|
|
return breadcrumb.Try(r.parent, r.Button.GetLabel().Content)
|
|
|
|
}
|
2020-06-13 07:29:32 +00:00
|
|
|
|
|
|
|
// BindMover binds with the ID stored in the parent container to be used in the
|
|
|
|
// method itself. The ID may or may not have to do with session.
|
|
|
|
func (r *Row) BindMover(id string) {
|
|
|
|
primitives.BindDragSortable(r.Button, "GTK_TOGGLE_BUTTON", id, r.ctrl.MoveSession)
|
|
|
|
}
|