mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2025-03-22 09:59:37 +00:00
Minor graphical tweaks, added Disconnection
This commit is contained in:
parent
adeffc7717
commit
0d8d3609be
4
go.mod
4
go.mod
|
@ -6,8 +6,8 @@ replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20200612
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Xuanwo/go-locale v0.2.0
|
github.com/Xuanwo/go-locale v0.2.0
|
||||||
github.com/diamondburned/cchat v0.0.25
|
github.com/diamondburned/cchat v0.0.26
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200613003444-b36f8f47debe
|
github.com/diamondburned/cchat-mock v0.0.0-20200613233949-1e7651c8dd84
|
||||||
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
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -9,8 +9,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/diamondburned/cchat v0.0.25 h1:+kf2gQu5TQs1vD/gCaVlzKu5vOqZz/1Qw87xHdeFYj4=
|
github.com/diamondburned/cchat v0.0.25 h1:+kf2gQu5TQs1vD/gCaVlzKu5vOqZz/1Qw87xHdeFYj4=
|
||||||
github.com/diamondburned/cchat v0.0.25/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
github.com/diamondburned/cchat v0.0.25/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||||
|
github.com/diamondburned/cchat v0.0.26 h1:QBt4d65uzUPJz3jF8b2pJ09Jz8LeBRyG2ol47FOy0g0=
|
||||||
|
github.com/diamondburned/cchat v0.0.26/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200613003444-b36f8f47debe h1:OoTLxpryxB9iQyu3bjw5N9N/3Bvu6FwklJ85X9erCAY=
|
github.com/diamondburned/cchat-mock v0.0.0-20200613003444-b36f8f47debe h1:OoTLxpryxB9iQyu3bjw5N9N/3Bvu6FwklJ85X9erCAY=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200613003444-b36f8f47debe/go.mod h1:vitBma+rd/ah+ujQsp6lPm/AfS2KtLKEh+Owxbv5BQM=
|
github.com/diamondburned/cchat-mock v0.0.0-20200613003444-b36f8f47debe/go.mod h1:vitBma+rd/ah+ujQsp6lPm/AfS2KtLKEh+Owxbv5BQM=
|
||||||
|
github.com/diamondburned/cchat-mock v0.0.0-20200613233949-1e7651c8dd84 h1:NSuksZ9HiLiau93qAz4yNba6Xd7ExOFc956dumONDQ0=
|
||||||
|
github.com/diamondburned/cchat-mock v0.0.0-20200613233949-1e7651c8dd84/go.mod h1:JxTay4MVEqmDisGqDGk8TG0UnKX7wDEImFywyoPfGjk=
|
||||||
github.com/diamondburned/gotk3 v0.0.0-20200612012846-9df87fea4f6d h1:NFTuwBU+CNZDB1iaGC3gDuBRf9FTd1h2WnIh6NF7elg=
|
github.com/diamondburned/gotk3 v0.0.0-20200612012846-9df87fea4f6d h1:NFTuwBU+CNZDB1iaGC3gDuBRf9FTd1h2WnIh6NF7elg=
|
||||||
github.com/diamondburned/gotk3 v0.0.0-20200612012846-9df87fea4f6d/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
github.com/diamondburned/gotk3 v0.0.0-20200612012846-9df87fea4f6d/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=
|
||||||
|
|
|
@ -39,7 +39,7 @@ type Session struct {
|
||||||
Data map[string]string
|
Data map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(ses cchat.Session, name string) *Session {
|
func ConvertSession(ses cchat.Session, name string) *Session {
|
||||||
saver, ok := ses.(cchat.SessionSaver)
|
saver, ok := ses.(cchat.SessionSaver)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
@ -79,3 +79,13 @@ func RestoreSessions(serviceName text.Rich) (sessions []Session) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RestoreSession(serviceName text.Rich, id string) *Session {
|
||||||
|
var sessions = RestoreSessions(serviceName)
|
||||||
|
for _, session := range sessions {
|
||||||
|
if session.ID == id {
|
||||||
|
return &session
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -50,15 +50,15 @@ func New(parent gtk.IWidget, placeholder WidgetUnreferencer) *FaceView {
|
||||||
|
|
||||||
// Reset brings the view to an empty box.
|
// Reset brings the view to an empty box.
|
||||||
func (v *FaceView) Reset() {
|
func (v *FaceView) Reset() {
|
||||||
|
v.Stack.SetVisibleChildName("empty")
|
||||||
v.ensurePlaceholderDestroyed()
|
v.ensurePlaceholderDestroyed()
|
||||||
v.Loading.Spinner.Stop()
|
v.Loading.Spinner.Stop()
|
||||||
v.Stack.SetVisibleChildName("empty")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *FaceView) SetMain() {
|
func (v *FaceView) SetMain() {
|
||||||
|
v.Stack.SetVisibleChildName("main")
|
||||||
v.ensurePlaceholderDestroyed()
|
v.ensurePlaceholderDestroyed()
|
||||||
v.Loading.Spinner.Stop()
|
v.Loading.Spinner.Stop()
|
||||||
v.Stack.SetVisibleChildName("main")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *FaceView) SetLoading() {
|
func (v *FaceView) SetLoading() {
|
||||||
|
@ -68,10 +68,10 @@ func (v *FaceView) SetLoading() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *FaceView) SetError(err error) {
|
func (v *FaceView) SetError(err error) {
|
||||||
|
v.Face.SetError(err)
|
||||||
|
v.Stack.SetVisibleChildName("face")
|
||||||
v.ensurePlaceholderDestroyed()
|
v.ensurePlaceholderDestroyed()
|
||||||
v.Loading.Spinner.Stop()
|
v.Loading.Spinner.Stop()
|
||||||
v.Stack.SetVisibleChildName("face")
|
|
||||||
v.Face.SetError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *FaceView) ensurePlaceholderDestroyed() {
|
func (v *FaceView) ensurePlaceholderDestroyed() {
|
||||||
|
|
|
@ -106,7 +106,13 @@ func SetImageIcon(img *gtk.Image, icon string, sizepx int) {
|
||||||
img.SetSizeRequest(sizepx, sizepx)
|
img.SetSizeRequest(sizepx, sizepx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AppendMenuItems(menu interface{ Append(gtk.IMenuItem) }, items []*gtk.MenuItem) {
|
func PrependMenuItems(menu interface{ Prepend(gtk.IMenuItem) }, items []gtk.IMenuItem) {
|
||||||
|
for i := len(items) - 1; i >= 0; i-- {
|
||||||
|
menu.Prepend(items[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendMenuItems(menu interface{ Append(gtk.IMenuItem) }, items []gtk.IMenuItem) {
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
menu.Append(item)
|
menu.Append(item)
|
||||||
}
|
}
|
||||||
|
@ -118,6 +124,12 @@ func HiddenMenuItem(label string, fn interface{}) *gtk.MenuItem {
|
||||||
return mb
|
return mb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HiddenDisabledMenuItem(label string, fn interface{}) *gtk.MenuItem {
|
||||||
|
mb := HiddenMenuItem(label, fn)
|
||||||
|
mb.SetSensitive(false)
|
||||||
|
return mb
|
||||||
|
}
|
||||||
|
|
||||||
func MenuItem(label string, fn interface{}) *gtk.MenuItem {
|
func MenuItem(label string, fn interface{}) *gtk.MenuItem {
|
||||||
menuitem := HiddenMenuItem(label, fn)
|
menuitem := HiddenMenuItem(label, fn)
|
||||||
menuitem.Show()
|
menuitem.Show()
|
||||||
|
@ -128,7 +140,7 @@ type Connector interface {
|
||||||
Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error)
|
Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BindMenu(menu *gtk.Menu, connector Connector) {
|
func BindMenu(connector Connector, menu *gtk.Menu) {
|
||||||
connector.Connect("event", func(_ *gtk.ToggleButton, ev *gdk.Event) {
|
connector.Connect("event", func(_ *gtk.ToggleButton, ev *gdk.Event) {
|
||||||
if gts.EventIsRightClick(ev) {
|
if gts.EventIsRightClick(ev) {
|
||||||
menu.PopupAtPointer(ev)
|
menu.PopupAtPointer(ev)
|
||||||
|
@ -136,6 +148,16 @@ func BindMenu(menu *gtk.Menu, connector Connector) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BindDynamicMenu(connector Connector, constr func(menu *gtk.Menu)) {
|
||||||
|
connector.Connect("event", func(_ *gtk.ToggleButton, ev *gdk.Event) {
|
||||||
|
if gts.EventIsRightClick(ev) {
|
||||||
|
menu, _ := gtk.MenuNew()
|
||||||
|
constr(menu)
|
||||||
|
menu.PopupAtPointer(ev)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func NewTargetEntry(target string) gtk.TargetEntry {
|
func NewTargetEntry(target string) gtk.TargetEntry {
|
||||||
e, _ := gtk.TargetEntryNew(target, gtk.TARGET_SAME_APP, 0)
|
e, _ := gtk.TargetEntryNew(target, gtk.TARGET_SAME_APP, 0)
|
||||||
return *e
|
return *e
|
||||||
|
|
|
@ -47,7 +47,7 @@ func newHeader(svc cchat.Service) *header {
|
||||||
|
|
||||||
// Spawn the menu on right click.
|
// Spawn the menu on right click.
|
||||||
menu, _ := gtk.MenuNew()
|
menu, _ := gtk.MenuNew()
|
||||||
primitives.BindMenu(menu, reveal)
|
primitives.BindMenu(reveal, menu)
|
||||||
|
|
||||||
return &header{box, reveal, add, menu}
|
return &header{box, reveal, add, menu}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"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/keyring"
|
"github.com/diamondburned/cchat-gtk/internal/keyring"
|
||||||
|
@ -55,6 +57,8 @@ type Controller interface {
|
||||||
// OnSessionRemove is called to remove a session. This should also clear out
|
// OnSessionRemove is called to remove a session. This should also clear out
|
||||||
// the message view in the parent package.
|
// the message view in the parent package.
|
||||||
OnSessionRemove(id string)
|
OnSessionRemove(id string)
|
||||||
|
// OnSessionDisconnect is here to satisfy session's controller.
|
||||||
|
OnSessionDisconnect(id string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container represents a single service, including the button header and the
|
// Container represents a single service, including the button header and the
|
||||||
|
@ -114,7 +118,7 @@ func NewContainer(svc cchat.Service, ctrl Controller) *Container {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Make menu items.
|
// Make menu items.
|
||||||
primitives.AppendMenuItems(header.Menu, []*gtk.MenuItem{
|
primitives.AppendMenuItems(header.Menu, []gtk.IMenuItem{
|
||||||
primitives.MenuItem("Save Sessions", func() {
|
primitives.MenuItem("Save Sessions", func() {
|
||||||
container.SaveAllSessions()
|
container.SaveAllSessions()
|
||||||
}),
|
}),
|
||||||
|
@ -131,7 +135,7 @@ func (c *Container) AddSession(ses cchat.Session) *session.Row {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) AddLoadingSession(id, name string) *session.Row {
|
func (c *Container) AddLoadingSession(id, name string) *session.Row {
|
||||||
srow := session.NewLoading(c, name, c)
|
srow := session.NewLoading(c, id, name, c)
|
||||||
c.children.AddSessionRow(id, srow)
|
c.children.AddSessionRow(id, srow)
|
||||||
return srow
|
return srow
|
||||||
}
|
}
|
||||||
|
@ -149,15 +153,31 @@ func (c *Container) MoveSession(rowID, beneathRowID string) {
|
||||||
c.SaveAllSessions()
|
c.SaveAllSessions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) OnSessionDisconnect(ses *session.Row) {
|
||||||
|
c.Controller.OnSessionDisconnect(ses.ID())
|
||||||
|
}
|
||||||
|
|
||||||
// RestoreSession tries to restore sessions asynchronously. This satisfies
|
// RestoreSession tries to restore sessions asynchronously. This satisfies
|
||||||
// session.Controller.
|
// session.Controller.
|
||||||
func (c *Container) RestoreSession(row *session.Row, krs keyring.Session) {
|
func (c *Container) RestoreSession(row *session.Row, id string) {
|
||||||
// Can this session be restored? If not, exit.
|
// Can this session be restored? If not, exit.
|
||||||
restorer, ok := c.Service.(cchat.SessionRestorer)
|
restorer, ok := c.Service.(cchat.SessionRestorer)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.restoreSession(row, restorer, krs)
|
|
||||||
|
// Do we even have a session stored?
|
||||||
|
krs := keyring.RestoreSession(c.Service.Name(), id)
|
||||||
|
if krs == nil {
|
||||||
|
log.Error(fmt.Errorf(
|
||||||
|
"Missing keyring for service %s, session ID %s",
|
||||||
|
c.Service.Name().Content, id,
|
||||||
|
))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.restoreSession(row, restorer, *krs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal method called on AddService.
|
// internal method called on AddService.
|
||||||
|
@ -186,7 +206,7 @@ func (c *Container) restoreSession(r *session.Row, res cchat.SessionRestorer, k
|
||||||
err = errors.Wrapf(err, "Failed to restore session %s (%s)", k.ID, k.Name)
|
err = errors.Wrapf(err, "Failed to restore session %s (%s)", k.ID, k.Name)
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
|
||||||
gts.ExecAsync(func() { r.SetFailed(k, err) })
|
gts.ExecAsync(func() { r.SetFailed(err) })
|
||||||
} else {
|
} else {
|
||||||
gts.ExecAsync(func() { r.SetSession(s) })
|
gts.ExecAsync(func() { r.SetSession(s) })
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,21 +115,17 @@ func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
||||||
type Children struct {
|
type Children struct {
|
||||||
*gtk.Revealer
|
*gtk.Revealer
|
||||||
Main *gtk.Box
|
Main *gtk.Box
|
||||||
load *loading.Button // nil after init
|
|
||||||
List cchat.ServerList
|
List cchat.ServerList
|
||||||
|
|
||||||
rowctrl Controller
|
rowctrl Controller
|
||||||
|
|
||||||
|
load *loading.Button // nil after init
|
||||||
Rows []*Row
|
Rows []*Row
|
||||||
Parent breadcrumb.Breadcrumber
|
Parent breadcrumb.Breadcrumber
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChildren(parent breadcrumb.Breadcrumber, ctrl Controller) *Children {
|
func NewChildren(parent breadcrumb.Breadcrumber, ctrl Controller) *Children {
|
||||||
load := loading.NewButton()
|
|
||||||
load.Show()
|
|
||||||
|
|
||||||
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
main.Add(load)
|
|
||||||
main.SetMarginStart(ChildrenMargin)
|
main.SetMarginStart(ChildrenMargin)
|
||||||
main.Show()
|
main.Show()
|
||||||
|
|
||||||
|
@ -141,12 +137,38 @@ func NewChildren(parent breadcrumb.Breadcrumber, ctrl Controller) *Children {
|
||||||
return &Children{
|
return &Children{
|
||||||
Revealer: rev,
|
Revealer: rev,
|
||||||
Main: main,
|
Main: main,
|
||||||
load: load,
|
|
||||||
rowctrl: ctrl,
|
rowctrl: ctrl,
|
||||||
Parent: parent,
|
Parent: parent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Children) SetLoading() {
|
||||||
|
// If we're already loading, then exit.
|
||||||
|
if c.load != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.load = loading.NewButton()
|
||||||
|
c.load.Show()
|
||||||
|
c.Main.Add(c.load)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Children) Reset() {
|
||||||
|
// Do we have the spinning circle button? If yes, remove it.
|
||||||
|
if c.load != nil {
|
||||||
|
c.Main.Remove(c.load)
|
||||||
|
c.load = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old servers from the list.
|
||||||
|
for _, row := range c.Rows {
|
||||||
|
c.Main.Remove(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe the list empty.
|
||||||
|
c.Rows = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Children) SetServerList(list cchat.ServerList) {
|
func (c *Children) SetServerList(list cchat.ServerList) {
|
||||||
c.List = list
|
c.List = list
|
||||||
|
|
||||||
|
@ -159,12 +181,6 @@ func (c *Children) SetServerList(list cchat.ServerList) {
|
||||||
|
|
||||||
func (c *Children) SetServers(servers []cchat.Server) {
|
func (c *Children) SetServers(servers []cchat.Server) {
|
||||||
gts.ExecAsync(func() {
|
gts.ExecAsync(func() {
|
||||||
// Do we have the spinning circle button? If yes, remove it.
|
|
||||||
if c.load != nil {
|
|
||||||
c.Main.Remove(c.load)
|
|
||||||
c.load = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the current state.
|
// Save the current state.
|
||||||
var oldID string
|
var oldID string
|
||||||
for _, row := range c.Rows {
|
for _, row := range c.Rows {
|
||||||
|
@ -174,10 +190,8 @@ func (c *Children) SetServers(servers []cchat.Server) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the server list.
|
// Reset before inserting new servers.
|
||||||
for _, row := range c.Rows {
|
c.Reset()
|
||||||
c.Main.Remove(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Rows = make([]*Row, len(servers))
|
c.Rows = make([]*Row, len(servers))
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@ package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
"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/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
|
@ -11,15 +13,27 @@ import (
|
||||||
"github.com/diamondburned/imgutil"
|
"github.com/diamondburned/imgutil"
|
||||||
"github.com/gotk3/gotk3/gdk"
|
"github.com/gotk3/gotk3/gdk"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const IconSize = 32
|
const IconSize = 32
|
||||||
|
|
||||||
// Controller extends server.RowController to add session.
|
// Controller extends server.RowController to add session.
|
||||||
type Controller interface {
|
type Controller interface {
|
||||||
|
// 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.
|
||||||
MessageRowSelected(*Row, *server.Row, cchat.ServerMessage)
|
MessageRowSelected(*Row, *server.Row, cchat.ServerMessage)
|
||||||
RestoreSession(*Row, keyring.Session) // async
|
// 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.
|
||||||
RemoveSession(*Row)
|
RemoveSession(*Row)
|
||||||
|
// MoveSession is called to ask the controller to move the session to
|
||||||
|
// somewhere else in the list of sessions.
|
||||||
MoveSession(id, movingID string)
|
MoveSession(id, movingID string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,24 +45,24 @@ type Row struct {
|
||||||
Session cchat.Session
|
Session cchat.Session
|
||||||
Servers *server.Children
|
Servers *server.Children
|
||||||
|
|
||||||
menu *gtk.Menu
|
ctrl Controller
|
||||||
retry *gtk.MenuItem
|
parent breadcrumb.Breadcrumber
|
||||||
|
menuconstr func(*gtk.Menu)
|
||||||
ctrl Controller
|
sessionID string // used for reconnection
|
||||||
parent breadcrumb.Breadcrumber
|
|
||||||
|
|
||||||
// nil after calling SetSession()
|
// nil after calling SetSession()
|
||||||
krs keyring.Session
|
// krs keyring.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Row {
|
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Row {
|
||||||
row := new(parent, ctrl)
|
row := newRow(parent, ctrl)
|
||||||
row.SetSession(ses)
|
row.SetSession(ses)
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoading(parent breadcrumb.Breadcrumber, name string, ctrl Controller) *Row {
|
func NewLoading(parent breadcrumb.Breadcrumber, id, name string, ctrl Controller) *Row {
|
||||||
row := new(parent, ctrl)
|
row := newRow(parent, ctrl)
|
||||||
|
row.sessionID = id
|
||||||
row.Button.SetLabelUnsafe(text.Rich{Content: name})
|
row.Button.SetLabelUnsafe(text.Rich{Content: name})
|
||||||
row.setLoading()
|
row.setLoading()
|
||||||
|
|
||||||
|
@ -60,12 +74,13 @@ var dragEntries = []gtk.TargetEntry{
|
||||||
}
|
}
|
||||||
var dragAtom = gdk.GdkAtomIntern("GTK_TOGGLE_BUTTON", true)
|
var dragAtom = gdk.GdkAtomIntern("GTK_TOGGLE_BUTTON", true)
|
||||||
|
|
||||||
func new(parent breadcrumb.Breadcrumber, ctrl Controller) *Row {
|
func newRow(parent breadcrumb.Breadcrumber, ctrl Controller) *Row {
|
||||||
row := &Row{
|
row := &Row{
|
||||||
ctrl: ctrl,
|
ctrl: ctrl,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
}
|
}
|
||||||
row.Servers = server.NewChildren(parent, row)
|
row.Servers = server.NewChildren(parent, row)
|
||||||
|
row.Servers.SetLoading()
|
||||||
|
|
||||||
row.Button = rich.NewToggleButtonImage(text.Rich{})
|
row.Button = rich.NewToggleButtonImage(text.Rich{})
|
||||||
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
@ -86,33 +101,86 @@ func new(parent breadcrumb.Breadcrumber, ctrl Controller) *Row {
|
||||||
row.Box.PackStart(row.Button, false, false, 0)
|
row.Box.PackStart(row.Button, false, false, 0)
|
||||||
row.Box.Show()
|
row.Box.Show()
|
||||||
|
|
||||||
|
// Bind the box to .session in CSS.
|
||||||
primitives.AddClass(row.Box, "session")
|
primitives.AddClass(row.Box, "session")
|
||||||
|
// Bind the button to create a new menu.
|
||||||
row.menu, _ = gtk.MenuNew()
|
primitives.BindDynamicMenu(row.Button, func(menu *gtk.Menu) {
|
||||||
primitives.BindMenu(row.menu, row.Button)
|
row.menuconstr(menu)
|
||||||
|
|
||||||
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{
|
// noop, empty menu
|
||||||
row.retry,
|
row.menuconstr = func(menu *gtk.Menu) {}
|
||||||
primitives.MenuItem("Remove", func() {
|
|
||||||
ctrl.RemoveSession(row)
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Row) setLoading() {
|
func (r *Row) setLoading() {
|
||||||
// set the loading icon
|
// set the loading icon
|
||||||
r.Button.Image.SetPlaceholderIcon("content-loading-symbolic", IconSize)
|
r.Button.Image.SetPlaceholderIcon("content-loading-symbolic", IconSize)
|
||||||
|
// set the loading icon in the servers list
|
||||||
|
r.Servers.SetLoading()
|
||||||
// restore the old label's color
|
// restore the old label's color
|
||||||
r.Button.SetLabelUnsafe(r.Button.GetLabel())
|
r.Button.SetLabelUnsafe(r.Button.GetLabel())
|
||||||
// clear the tooltip
|
// clear the tooltip
|
||||||
|
@ -124,19 +192,24 @@ func (r *Row) setLoading() {
|
||||||
// KeyringSession returns a keyring session, or nil if the session cannot be
|
// KeyringSession returns a keyring session, or nil if the session cannot be
|
||||||
// saved.
|
// saved.
|
||||||
func (r *Row) KeyringSession() *keyring.Session {
|
func (r *Row) KeyringSession() *keyring.Session {
|
||||||
return keyring.GetSession(r.Session, r.Button.GetText())
|
return keyring.ConvertSession(r.Session, r.Button.GetText())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the session ID.
|
||||||
|
func (r *Row) ID() string {
|
||||||
|
return r.sessionID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Row) SetSession(ses cchat.Session) {
|
func (r *Row) SetSession(ses cchat.Session) {
|
||||||
// Disable the retry button.
|
|
||||||
r.retry.SetSensitive(false)
|
|
||||||
r.retry.Hide()
|
|
||||||
|
|
||||||
r.Session = ses
|
r.Session = ses
|
||||||
|
r.sessionID = ses.ID()
|
||||||
|
|
||||||
r.Servers.SetServerList(ses)
|
r.Servers.SetServerList(ses)
|
||||||
|
r.Box.PackStart(r.Servers, false, false, 0)
|
||||||
|
|
||||||
r.Button.SetLabelUnsafe(ses.Name())
|
r.Button.SetLabelUnsafe(ses.Name())
|
||||||
r.Button.Image.SetPlaceholderIcon("user-available-symbolic", IconSize)
|
r.Button.Image.SetPlaceholderIcon("user-available-symbolic", IconSize)
|
||||||
r.Box.PackStart(r.Servers, false, false, 0)
|
|
||||||
r.SetSensitive(true)
|
r.SetSensitive(true)
|
||||||
r.SetTooltipText("") // reset
|
r.SetTooltipText("") // reset
|
||||||
|
|
||||||
|
@ -145,22 +218,30 @@ func (r *Row) SetSession(ses cchat.Session) {
|
||||||
r.Button.Image.AsyncSetIcon(iconer.Icon, "Error fetching session icon URL")
|
r.Button.Image.AsyncSetIcon(iconer.Icon, "Error fetching session icon URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wipe the keyring session off.
|
// Set the menu with the disconnect button.
|
||||||
r.krs = keyring.Session{}
|
r.menuconstr = func(menu *gtk.Menu) {
|
||||||
|
primitives.AppendMenuItems(menu, []gtk.IMenuItem{
|
||||||
|
primitives.MenuItem("Disconnect", r.DisconnectSession),
|
||||||
|
primitives.MenuItem("Remove", r.RemoveSession),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Row) SetFailed(krs keyring.Session, err error) {
|
func (r *Row) SetFailed(err error) {
|
||||||
// Set the failed keyring session.
|
|
||||||
r.krs = krs
|
|
||||||
|
|
||||||
// Allow the retry button to be pressed.
|
// Allow the retry button to be pressed.
|
||||||
r.retry.SetSensitive(true)
|
r.menuconstr = func(menu *gtk.Menu) {
|
||||||
r.retry.Show()
|
primitives.AppendMenuItems(menu, []gtk.IMenuItem{
|
||||||
|
primitives.MenuItem("Retry", r.ReconnectSession),
|
||||||
|
primitives.MenuItem("Remove", r.RemoveSession),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
r.SetSensitive(true)
|
r.SetSensitive(true)
|
||||||
r.SetTooltipText(err.Error())
|
r.SetTooltipText(err.Error())
|
||||||
// Intentional side-effect of not changing the actual label state.
|
// Intentional side-effect of not changing the actual label state.
|
||||||
r.Button.Label.SetMarkup(rich.MakeRed(r.Button.GetLabel()))
|
r.Button.Label.SetMarkup(rich.MakeRed(r.Button.GetLabel()))
|
||||||
|
// Set the icon to a failed one.
|
||||||
|
r.Button.Image.SetPlaceholderIcon("computer-fail-symbolic", IconSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Row) MessageRowSelected(server *server.Row, smsg cchat.ServerMessage) {
|
func (r *Row) MessageRowSelected(server *server.Row, smsg cchat.ServerMessage) {
|
||||||
|
|
|
@ -79,6 +79,12 @@ func (app *App) OnSessionRemove(id string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *App) OnSessionDisconnect(id string) {
|
||||||
|
// We're basically doing the same thing as removing a session. Check
|
||||||
|
// OnSessionRemove above.
|
||||||
|
app.OnSessionRemove(id)
|
||||||
|
}
|
||||||
|
|
||||||
func (app *App) MessageRowSelected(ses *session.Row, srv *server.Row, smsg cchat.ServerMessage) {
|
func (app *App) MessageRowSelected(ses *session.Row, srv *server.Row, smsg cchat.ServerMessage) {
|
||||||
// Is there an old row that we should deactivate?
|
// Is there an old row that we should deactivate?
|
||||||
if app.lastDeactivator != nil {
|
if app.lastDeactivator != nil {
|
||||||
|
|
Loading…
Reference in a new issue