mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-10-31 19:44:23 +00:00
Final commit before partial rewrite
This commit is contained in:
parent
ee657a8177
commit
b852498ee4
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
cchat-gtk
|
8
go.mod
8
go.mod
|
@ -2,12 +2,16 @@ module github.com/diamondburned/cchat-gtk
|
||||||
|
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
|
replace github.com/diamondburned/cchat-mock => ../cchat-mock/
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Xuanwo/go-locale v0.2.0
|
github.com/Xuanwo/go-locale v0.2.0
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/diamondburned/cchat v0.0.9
|
github.com/diamondburned/cchat v0.0.15
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200525222906-807afeffb7d4
|
github.com/diamondburned/cchat-mock v0.0.0-20200604043646-de5384bd320d
|
||||||
github.com/goodsign/monday v1.0.0
|
github.com/goodsign/monday v1.0.0
|
||||||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717
|
||||||
|
golang.org/x/tools v0.0.0-20200529172331-a64b76657301 // indirect
|
||||||
)
|
)
|
||||||
|
|
37
go.sum
37
go.sum
|
@ -2,15 +2,23 @@ github.com/Pallinder/go-randomdata v1.2.0 h1:DZ41wBchNRb/0GfsePLiSwb0PHZmT67XY00
|
||||||
github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y=
|
github.com/Pallinder/go-randomdata v1.2.0/go.mod h1:yHmJgulpD2Nfrm0cR9tI/+oAgRqCQQixsA8HyRZfV9Y=
|
||||||
github.com/Xuanwo/go-locale v0.2.0 h1:1N8SGG2VNpLl6VVa8ueZm3Nm+dxvk8ffY9aviKHl4IE=
|
github.com/Xuanwo/go-locale v0.2.0 h1:1N8SGG2VNpLl6VVa8ueZm3Nm+dxvk8ffY9aviKHl4IE=
|
||||||
github.com/Xuanwo/go-locale v0.2.0/go.mod h1:6qbT9M726OJgyiGZro2YwPmx63wQzlH+VvtjJWQoftw=
|
github.com/Xuanwo/go-locale v0.2.0/go.mod h1:6qbT9M726OJgyiGZro2YwPmx63wQzlH+VvtjJWQoftw=
|
||||||
|
github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU=
|
||||||
|
github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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.9 h1:+F96eDDuaOg4v4dz3GBDWbEW4dZ/k5uGrDp33/yeXR8=
|
github.com/diamondburned/cchat v0.0.10 h1:aiUVgGre5E/HV+Iw6tmBVbuGctQI+JndV9nIDoYuRPY=
|
||||||
github.com/diamondburned/cchat v0.0.9/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
github.com/diamondburned/cchat v0.0.10/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200525222906-807afeffb7d4 h1:k6vfDs6NR8yIi2V7YEpNp9Vujtl6mpDL5cWh7Pg09kk=
|
github.com/diamondburned/cchat v0.0.13 h1:p8SyFjiRVCTjvwSJ4FsICGVYVZ3g0Iu02FrwmLuKiKE=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200525222906-807afeffb7d4/go.mod h1:mOf8RsTQUOf9qJ1Z/OKbsMKnCMtA2gRexH12fc9LxiQ=
|
github.com/diamondburned/cchat v0.0.13/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||||
|
github.com/diamondburned/cchat v0.0.15 h1:1o4OX8zw/CdSv3Idaylz7vjHVOZKEi/xkg8BpEvtsHY=
|
||||||
|
github.com/diamondburned/cchat v0.0.15/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||||
|
github.com/diamondburned/cchat-mock v0.0.0-20200529184140-47fa2491d2fc h1:xYSN3re1QOd5af5zG15pLKfBM+fergw7Rg62UHmE22g=
|
||||||
|
github.com/diamondburned/cchat-mock v0.0.0-20200529184140-47fa2491d2fc/go.mod h1:rQm5EKhNyBRYHKtirSkf+Db23nr3mTs2bnOThfTfzec=
|
||||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
|
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||||
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
||||||
github.com/goodsign/monday v1.0.0/go.mod h1:r4T4breXpoFwspQNM+u2sLxJb2zyTaxVGqUfTBjWOu8=
|
github.com/goodsign/monday v1.0.0/go.mod h1:r4T4breXpoFwspQNM+u2sLxJb2zyTaxVGqUfTBjWOu8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
@ -27,17 +35,38 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717 h1:3M/uUZajYn/082wzUajekePxpUAZhMTfXvI9R+26SJ0=
|
||||||
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200529172331-a64b76657301 h1:G6CNEgFU8/XwexSnuFw+Jq/WePjRitgy6ofBcPnAIPo=
|
||||||
|
golang.org/x/tools v0.0.0-20200529172331-a64b76657301/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
|
|
@ -27,7 +27,7 @@ func init() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
App.Application, _ = gtk.ApplicationNew("com.github.diamondburned.gtkcord3", 0)
|
App.Application, _ = gtk.ApplicationNew("com.github.diamondburned.cchat-gtk", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Windower interface {
|
type Windower interface {
|
||||||
|
@ -52,7 +52,7 @@ func Main(wfn func() WindowHeaderer) {
|
||||||
App.Header.Show()
|
App.Header.Show()
|
||||||
|
|
||||||
App.Window, _ = gtk.ApplicationWindowNew(App.Application)
|
App.Window, _ = gtk.ApplicationWindowNew(App.Application)
|
||||||
App.Window.SetDefaultSize(750, 400)
|
App.Window.SetDefaultSize(1000, 500)
|
||||||
App.Window.SetTitlebar(App.Header)
|
App.Window.SetTitlebar(App.Header)
|
||||||
App.Window.Show()
|
App.Window.Show()
|
||||||
|
|
||||||
|
|
89
internal/keyring/keyring.go
Normal file
89
internal/keyring/keyring.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package keyring
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zalando/go-keyring"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getThenDestroy(service string, v interface{}) error {
|
||||||
|
s, err := keyring.Get("cchat-gtk", service)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deleting immediately does not work on a successful start-up.
|
||||||
|
// keyring.Delete("cchat-gtk", service)
|
||||||
|
|
||||||
|
return gob.NewDecoder(strings.NewReader(s)).Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func set(service string, v interface{}) error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := gob.NewEncoder(&b).Encode(v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyring.Set("cchat-gtk", service, b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveSessions(service cchat.Service, sessions []cchat.Session) (saveErrs []error) {
|
||||||
|
var sessionData = make([]map[string]string, 0, len(sessions))
|
||||||
|
|
||||||
|
for _, session := range sessions {
|
||||||
|
sv, ok := session.(cchat.SessionSaver)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := sv.Save()
|
||||||
|
if err != nil {
|
||||||
|
saveErrs = append(saveErrs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionData = append(sessionData, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := set(service.Name(), sessionData); err != nil {
|
||||||
|
log.Warn(errors.Wrap(err, "Error saving session"))
|
||||||
|
saveErrs = append(saveErrs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreSessions restores all sessions of the service asynchronously, then
|
||||||
|
// calls the auth callback inside the Gtk main thread.
|
||||||
|
func RestoreSessions(service cchat.Service, auth func(cchat.Session)) {
|
||||||
|
// If the service doesn't support restoring, treat it as a non-error.
|
||||||
|
restorer, ok := service.(cchat.SessionRestorer)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var sessionData []map[string]string
|
||||||
|
|
||||||
|
// Ignore the error, it's not important.
|
||||||
|
if err := getThenDestroy(service.Name(), &sessionData); err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, data := range sessionData {
|
||||||
|
gts.Async(func() (func(), error) {
|
||||||
|
s, err := restorer.RestoreSession(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Failed to restore")
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() { auth(s) }, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,10 @@ func Error(err error) {
|
||||||
Write("Error: " + err.Error())
|
Write("Error: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Warn(err error) {
|
||||||
|
Write("Warn: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
func Write(msg string) {
|
func Write(msg string) {
|
||||||
WriteEntry(Entry{
|
WriteEntry(Entry{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
|
|
41
internal/ui/message/autoscroll/autoscroll.go
Normal file
41
internal/ui/message/autoscroll/autoscroll.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package autoscroll
|
||||||
|
|
||||||
|
import "github.com/gotk3/gotk3/gtk"
|
||||||
|
|
||||||
|
type ScrolledWindow struct {
|
||||||
|
gtk.ScrolledWindow
|
||||||
|
vadj gtk.Adjustment
|
||||||
|
bottomed bool // :floshed:
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScrolledWindow() *ScrolledWindow {
|
||||||
|
gtksw, _ := gtk.ScrolledWindowNew(nil, nil)
|
||||||
|
gtksw.SetProperty("propagate-natural-height", true)
|
||||||
|
|
||||||
|
sw := &ScrolledWindow{*gtksw, *gtksw.GetVAdjustment(), true} // bottomed by default
|
||||||
|
sw.Connect("size-allocate", func(_ *gtk.ScrolledWindow) {
|
||||||
|
// We can't really trust Gtk to be competent.
|
||||||
|
if sw.bottomed {
|
||||||
|
sw.ScrollToBottom()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sw.vadj.Connect("value-changed", func(adj *gtk.Adjustment) {
|
||||||
|
// Manually check if we're anchored on scroll.
|
||||||
|
sw.bottomed = (adj.GetUpper() - adj.GetPageSize()) <= adj.GetValue()
|
||||||
|
})
|
||||||
|
|
||||||
|
return sw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScrolledWindow) Bottomed() bool {
|
||||||
|
return s.bottomed
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVAdjustment overrides gtk.ScrolledWindow's.
|
||||||
|
func (s *ScrolledWindow) GetVAdjustment() *gtk.Adjustment {
|
||||||
|
return &s.vadj
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ScrolledWindow) ScrollToBottom() {
|
||||||
|
s.vadj.SetValue(s.vadj.GetUpper())
|
||||||
|
}
|
156
internal/ui/message/compact/compact.go
Normal file
156
internal/ui/message/compact/compact.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package compact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/message/autoscroll"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/message/input"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
*autoscroll.ScrolledWindow
|
||||||
|
main *gtk.Grid
|
||||||
|
messages map[string]*Message
|
||||||
|
nonceMsgs map[string]*Message
|
||||||
|
|
||||||
|
bottomed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContainer() *Container {
|
||||||
|
grid, _ := gtk.GridNew()
|
||||||
|
grid.SetColumnSpacing(10)
|
||||||
|
grid.SetRowSpacing(5)
|
||||||
|
grid.SetMarginStart(5)
|
||||||
|
grid.SetMarginEnd(5)
|
||||||
|
grid.SetMarginBottom(5)
|
||||||
|
grid.Show()
|
||||||
|
|
||||||
|
sw := autoscroll.NewScrolledWindow()
|
||||||
|
sw.Add(grid)
|
||||||
|
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
|
||||||
|
sw.Show()
|
||||||
|
|
||||||
|
container := Container{
|
||||||
|
ScrolledWindow: sw,
|
||||||
|
main: grid,
|
||||||
|
messages: map[string]*Message{},
|
||||||
|
nonceMsgs: map[string]*Message{},
|
||||||
|
bottomed: true, // bottomed by default.
|
||||||
|
}
|
||||||
|
|
||||||
|
return &container
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) Reset() {
|
||||||
|
// does this actually work?
|
||||||
|
var rows = c.len()
|
||||||
|
for i := 0; i < rows; i++ {
|
||||||
|
c.main.RemoveRow(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.messages = map[string]*Message{}
|
||||||
|
c.nonceMsgs = map[string]*Message{}
|
||||||
|
|
||||||
|
// default to being bottomed
|
||||||
|
c.bottomed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) len() int {
|
||||||
|
return len(c.messages) + len(c.nonceMsgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PresendMessage is not thread-safe.
|
||||||
|
func (c *Container) PresendMessage(msg input.PresendMessage) func(error) {
|
||||||
|
msgc := NewPresendMessage(msg.Content(), msg.Author(), msg.AuthorID(), msg.Nonce())
|
||||||
|
msgc.index = c.len()
|
||||||
|
|
||||||
|
c.nonceMsgs[msgc.Nonce] = &msgc
|
||||||
|
msgc.Attach(c.main, msgc.index)
|
||||||
|
|
||||||
|
return func(err error) {
|
||||||
|
msgc.SetSensitive(true)
|
||||||
|
|
||||||
|
// Did we fail?
|
||||||
|
if err != nil {
|
||||||
|
msgc.Content.SetMarkup(fmt.Sprintf(
|
||||||
|
`<span color="red">%s</span>`,
|
||||||
|
html.EscapeString(msgc.Content.GetLabel()),
|
||||||
|
))
|
||||||
|
msgc.Content.SetTooltipText(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindMessage is not thread-safe.
|
||||||
|
func (c *Container) FindMessage(msg cchat.MessageHeader) *Message {
|
||||||
|
// Search using the ID first.
|
||||||
|
m, ok := c.messages[msg.ID()]
|
||||||
|
if ok {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this an existing message?
|
||||||
|
if noncer, ok := msg.(cchat.MessageNonce); ok {
|
||||||
|
var nonce = noncer.Nonce()
|
||||||
|
|
||||||
|
m, ok := c.nonceMsgs[nonce]
|
||||||
|
if ok {
|
||||||
|
// Move the message outside nonceMsgs.
|
||||||
|
delete(c.nonceMsgs, nonce)
|
||||||
|
c.messages[msg.ID()] = m
|
||||||
|
|
||||||
|
// Set the right ID.
|
||||||
|
m.ID = msg.ID()
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) CreateMessage(msg cchat.MessageCreate) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
// Attempt update before insert (aka upsert).
|
||||||
|
if msgc := c.FindMessage(msg); msgc != nil {
|
||||||
|
msgc.SetSensitive(true)
|
||||||
|
msgc.UpdateAuthor(msg.Author())
|
||||||
|
msgc.UpdateContent(msg.Content())
|
||||||
|
msgc.UpdateTimestamp(msg.Time())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msgc := NewMessage(msg)
|
||||||
|
msgc.index = c.len() // unsure
|
||||||
|
|
||||||
|
c.messages[msgc.ID] = &msgc
|
||||||
|
msgc.Attach(c.main, msgc.index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) UpdateMessage(msg cchat.MessageUpdate) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
if msgc := c.FindMessage(msg); msgc != nil {
|
||||||
|
if author := msg.Author(); author != nil {
|
||||||
|
msgc.UpdateAuthor(author)
|
||||||
|
}
|
||||||
|
if content := msg.Content(); !content.Empty() {
|
||||||
|
msgc.UpdateContent(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) DeleteMessage(msg cchat.MessageDelete) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
// TODO: add nonce check.
|
||||||
|
if m, ok := c.messages[msg.ID()]; ok {
|
||||||
|
delete(c.messages, msg.ID())
|
||||||
|
c.main.RemoveRow(m.index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package message
|
package compact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
@ -13,6 +13,7 @@ import (
|
||||||
type Message struct {
|
type Message struct {
|
||||||
index int
|
index int
|
||||||
ID string
|
ID string
|
||||||
|
AuthorID string
|
||||||
Nonce string
|
Nonce string
|
||||||
|
|
||||||
Timestamp *gtk.Label
|
Timestamp *gtk.Label
|
||||||
|
@ -21,34 +22,8 @@ type Message struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessage(msg cchat.MessageCreate) Message {
|
func NewMessage(msg cchat.MessageCreate) Message {
|
||||||
ts, _ := gtk.LabelNew("")
|
m := NewEmptyMessage()
|
||||||
ts.SetLineWrap(true)
|
m.ID = msg.ID()
|
||||||
ts.SetLineWrapMode(pango.WRAP_WORD)
|
|
||||||
ts.SetHAlign(gtk.ALIGN_END)
|
|
||||||
ts.SetVAlign(gtk.ALIGN_START)
|
|
||||||
ts.Show()
|
|
||||||
|
|
||||||
user, _ := gtk.LabelNew("")
|
|
||||||
user.SetLineWrap(true)
|
|
||||||
user.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
|
||||||
user.SetHAlign(gtk.ALIGN_END)
|
|
||||||
user.SetVAlign(gtk.ALIGN_START)
|
|
||||||
user.Show()
|
|
||||||
|
|
||||||
content, _ := gtk.LabelNew("")
|
|
||||||
content.SetHExpand(true)
|
|
||||||
content.SetXAlign(0) // left-align with size filled
|
|
||||||
content.SetVAlign(gtk.ALIGN_START)
|
|
||||||
content.SetLineWrap(true)
|
|
||||||
content.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
|
||||||
content.Show()
|
|
||||||
|
|
||||||
m := Message{
|
|
||||||
ID: msg.ID(),
|
|
||||||
Timestamp: ts,
|
|
||||||
Username: user,
|
|
||||||
Content: content,
|
|
||||||
}
|
|
||||||
m.UpdateTimestamp(msg.Time())
|
m.UpdateTimestamp(msg.Time())
|
||||||
m.UpdateAuthor(msg.Author())
|
m.UpdateAuthor(msg.Author())
|
||||||
m.UpdateContent(msg.Content())
|
m.UpdateContent(msg.Content())
|
||||||
|
@ -60,6 +35,58 @@ func NewMessage(msg cchat.MessageCreate) Message {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPresendMessage(content string, author text.Rich, authorID, nonce string) Message {
|
||||||
|
msgc := NewEmptyMessage()
|
||||||
|
msgc.Nonce = nonce
|
||||||
|
msgc.AuthorID = authorID
|
||||||
|
msgc.SetSensitive(false)
|
||||||
|
msgc.UpdateContent(text.Rich{Content: content})
|
||||||
|
msgc.UpdateTimestamp(time.Now())
|
||||||
|
msgc.updateAuthorName(author)
|
||||||
|
|
||||||
|
return msgc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEmptyMessage() Message {
|
||||||
|
ts, _ := gtk.LabelNew("")
|
||||||
|
ts.SetLineWrap(true)
|
||||||
|
ts.SetLineWrapMode(pango.WRAP_WORD)
|
||||||
|
ts.SetHAlign(gtk.ALIGN_END)
|
||||||
|
ts.SetVAlign(gtk.ALIGN_START)
|
||||||
|
ts.SetSelectable(true)
|
||||||
|
ts.Show()
|
||||||
|
|
||||||
|
user, _ := gtk.LabelNew("")
|
||||||
|
user.SetMaxWidthChars(35)
|
||||||
|
user.SetLineWrap(true)
|
||||||
|
user.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
||||||
|
user.SetHAlign(gtk.ALIGN_END)
|
||||||
|
user.SetVAlign(gtk.ALIGN_START)
|
||||||
|
user.SetSelectable(true)
|
||||||
|
user.Show()
|
||||||
|
|
||||||
|
content, _ := gtk.LabelNew("")
|
||||||
|
content.SetHExpand(true)
|
||||||
|
content.SetXAlign(0) // left-align with size filled
|
||||||
|
content.SetVAlign(gtk.ALIGN_START)
|
||||||
|
content.SetLineWrap(true)
|
||||||
|
content.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
||||||
|
content.SetSelectable(true)
|
||||||
|
content.Show()
|
||||||
|
|
||||||
|
return Message{
|
||||||
|
Timestamp: ts,
|
||||||
|
Username: user,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) SetSensitive(sensitive bool) {
|
||||||
|
m.Timestamp.SetSensitive(sensitive)
|
||||||
|
m.Username.SetSensitive(sensitive)
|
||||||
|
m.Content.SetSensitive(sensitive)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Message) Attach(grid *gtk.Grid, row int) {
|
func (m *Message) Attach(grid *gtk.Grid, row int) {
|
||||||
grid.Attach(m.Timestamp, 0, row, 1, 1)
|
grid.Attach(m.Timestamp, 0, row, 1, 1)
|
||||||
grid.Attach(m.Username, 1, row, 1, 1)
|
grid.Attach(m.Username, 1, row, 1, 1)
|
||||||
|
@ -68,10 +95,17 @@ func (m *Message) Attach(grid *gtk.Grid, row int) {
|
||||||
|
|
||||||
func (m *Message) UpdateTimestamp(t time.Time) {
|
func (m *Message) UpdateTimestamp(t time.Time) {
|
||||||
m.Timestamp.SetLabel(humanize.TimeAgo(t))
|
m.Timestamp.SetLabel(humanize.TimeAgo(t))
|
||||||
|
m.Timestamp.SetTooltipText(t.Format(time.Stamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) UpdateAuthor(author text.Rich) {
|
func (m *Message) UpdateAuthor(author cchat.MessageAuthor) {
|
||||||
m.Username.SetLabel(author.Content)
|
m.AuthorID = author.ID()
|
||||||
|
m.updateAuthorName(author.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) updateAuthorName(name text.Rich) {
|
||||||
|
m.Username.SetLabel(name.Content)
|
||||||
|
m.Username.SetTooltipText(name.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) UpdateContent(content text.Rich) {
|
func (m *Message) UpdateContent(content text.Rich) {
|
|
@ -1,74 +0,0 @@
|
||||||
package message
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/diamondburned/cchat"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
|
||||||
"github.com/gotk3/gotk3/gtk"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Container struct {
|
|
||||||
*gtk.ScrolledWindow
|
|
||||||
main *gtk.Grid
|
|
||||||
messages map[string]Message
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewContainer() *Container {
|
|
||||||
grid, _ := gtk.GridNew()
|
|
||||||
grid.SetColumnSpacing(8)
|
|
||||||
grid.SetRowSpacing(5)
|
|
||||||
grid.SetMarginStart(5)
|
|
||||||
grid.SetMarginEnd(5)
|
|
||||||
grid.Show()
|
|
||||||
|
|
||||||
sw, _ := gtk.ScrolledWindowNew(nil, nil)
|
|
||||||
sw.Add(grid)
|
|
||||||
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
|
|
||||||
sw.Show()
|
|
||||||
|
|
||||||
return &Container{sw, grid, map[string]Message{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) Reset() {
|
|
||||||
// does this actually work?
|
|
||||||
var rows = len(c.messages)
|
|
||||||
for i := 0; i < rows; i++ {
|
|
||||||
c.main.RemoveRow(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.messages = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) CreateMessage(msg cchat.MessageCreate) {
|
|
||||||
gts.ExecAsync(func() {
|
|
||||||
msgc := NewMessage(msg)
|
|
||||||
msgc.index = len(c.messages) // unsure
|
|
||||||
|
|
||||||
c.messages[msgc.ID] = msgc
|
|
||||||
msgc.Attach(c.main, msgc.index)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) UpdateMessage(msg cchat.MessageUpdate) {
|
|
||||||
gts.ExecAsync(func() {
|
|
||||||
mc, ok := c.messages[msg.ID()]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if author := msg.Author(); !author.Empty() {
|
|
||||||
mc.UpdateAuthor(author)
|
|
||||||
}
|
|
||||||
if content := msg.Content(); !content.Empty() {
|
|
||||||
mc.UpdateContent(content)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) DeleteMessage(msg cchat.MessageDelete) {
|
|
||||||
gts.ExecAsync(func() {
|
|
||||||
if m, ok := c.messages[msg.ID()]; ok {
|
|
||||||
delete(c.messages, msg.ID())
|
|
||||||
c.main.RemoveRow(m.index)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -2,42 +2,115 @@ package input
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
*gtk.ScrolledWindow
|
*gtk.Box
|
||||||
|
|
||||||
|
namerev *gtk.Revealer
|
||||||
|
username *rich.Label // TODO
|
||||||
|
|
||||||
|
TextScroll *gtk.ScrolledWindow
|
||||||
text *gtk.TextView
|
text *gtk.TextView
|
||||||
buffer *gtk.TextBuffer
|
buffer *gtk.TextBuffer
|
||||||
|
|
||||||
|
UserID string
|
||||||
|
|
||||||
sender cchat.ServerMessageSender
|
sender cchat.ServerMessageSender
|
||||||
|
ctrl Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewField() *Field {
|
type Controller interface {
|
||||||
|
PresendMessage(msg PresendMessage) (onErr func(error))
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputmargin = 3
|
||||||
|
|
||||||
|
func NewField(ctrl Controller) *Field {
|
||||||
|
username := rich.NewLabel(text.Rich{})
|
||||||
|
username.SetMaxWidthChars(35)
|
||||||
|
username.SetVAlign(gtk.ALIGN_START)
|
||||||
|
username.SetMarginTop(inputmargin)
|
||||||
|
username.SetMarginBottom(inputmargin)
|
||||||
|
username.SetMarginStart(10)
|
||||||
|
username.SetMarginEnd(10)
|
||||||
|
username.Show()
|
||||||
|
|
||||||
|
namerev, _ := gtk.RevealerNew()
|
||||||
|
namerev.SetRevealChild(false)
|
||||||
|
namerev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
|
||||||
|
namerev.SetTransitionDuration(50)
|
||||||
|
namerev.Add(username)
|
||||||
|
namerev.Show()
|
||||||
|
|
||||||
text, _ := gtk.TextViewNew()
|
text, _ := gtk.TextViewNew()
|
||||||
|
text.SetSensitive(false)
|
||||||
|
text.SetWrapMode(gtk.WRAP_WORD_CHAR)
|
||||||
|
text.SetProperty("top-margin", inputmargin)
|
||||||
|
text.SetProperty("left-margin", inputmargin)
|
||||||
|
text.SetProperty("right-margin", inputmargin)
|
||||||
|
text.SetProperty("bottom-margin", inputmargin)
|
||||||
text.Show()
|
text.Show()
|
||||||
|
|
||||||
buf, _ := text.GetBuffer()
|
buf, _ := text.GetBuffer()
|
||||||
|
|
||||||
sw, _ := gtk.ScrolledWindowNew(nil, nil)
|
sw, _ := gtk.ScrolledWindowNew(nil, nil)
|
||||||
sw.Show()
|
|
||||||
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
|
||||||
sw.SetProperty("max-content-height", 150)
|
|
||||||
sw.Add(text)
|
sw.Add(text)
|
||||||
|
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||||
|
sw.SetProperty("propagate-natural-height", true)
|
||||||
|
sw.SetProperty("max-content-height", 150)
|
||||||
|
sw.Show()
|
||||||
|
|
||||||
return &Field{
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
sw,
|
box.PackStart(namerev, false, false, 0)
|
||||||
text,
|
box.PackStart(sw, true, true, 0)
|
||||||
buf,
|
box.Show()
|
||||||
nil,
|
|
||||||
|
field := &Field{
|
||||||
|
Box: box,
|
||||||
|
namerev: namerev,
|
||||||
|
username: username,
|
||||||
|
TextScroll: sw,
|
||||||
|
text: text,
|
||||||
|
buffer: buf,
|
||||||
|
ctrl: ctrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text.SetFocusHAdjustment(sw.GetHAdjustment())
|
||||||
|
text.SetFocusVAdjustment(sw.GetVAdjustment())
|
||||||
|
text.Connect("key-press-event", field.keyDown)
|
||||||
|
|
||||||
|
return field
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSender changes the sender of the input field. If nil, the input will be
|
// SetSender changes the sender of the input field. If nil, the input will be
|
||||||
// disabled.
|
// disabled.
|
||||||
func (f *Field) SetSender(sender cchat.ServerMessageSender) {
|
func (f *Field) SetSender(session cchat.Session, sender cchat.ServerMessageSender) {
|
||||||
|
f.UserID = session.ID()
|
||||||
|
|
||||||
|
// Does sender (aka Server) implement ServerNickname?
|
||||||
|
var err error
|
||||||
|
if nicknamer, ok := sender.(cchat.ServerNickname); ok {
|
||||||
|
err = errors.Wrap(nicknamer.Nickname(f.username), "Failed to get nickname")
|
||||||
|
} else {
|
||||||
|
err = errors.Wrap(session.Name(f.username), "Failed to get username")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a bit of trivial error handling.
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reveal if the name is not empty.
|
||||||
|
f.namerev.SetRevealChild(!f.username.GetLabel().Empty())
|
||||||
|
|
||||||
|
// Set the sender.
|
||||||
f.sender = sender
|
f.sender = sender
|
||||||
f.text.SetSensitive(sender != nil) // grey if sender is nil
|
f.text.SetSensitive(sender != nil) // grey if sender is nil
|
||||||
|
|
||||||
|
@ -58,18 +131,24 @@ func (f *Field) SendMessage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var sender = f.sender
|
var sender = f.sender
|
||||||
|
var data = NewSendMessageData(text, f.username.GetLabel(), f.UserID)
|
||||||
|
|
||||||
|
// presend message into the container through the controller
|
||||||
|
var done = f.ctrl.PresendMessage(data)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := sender.SendMessage(SendMessageData(text)); err != nil {
|
err := sender.SendMessage(data)
|
||||||
|
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
done(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
log.Error(errors.Wrap(err, "Failed to send message"))
|
log.Error(errors.Wrap(err, "Failed to send message"))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendMessageData string
|
|
||||||
|
|
||||||
func (s SendMessageData) Content() string { return string(s) }
|
|
||||||
|
|
||||||
// yankText cuts the text from the input field and returns it.
|
// yankText cuts the text from the input field and returns it.
|
||||||
func (f *Field) yankText() string {
|
func (f *Field) yankText() string {
|
||||||
start, end := f.buffer.GetBounds()
|
start, end := f.buffer.GetBounds()
|
||||||
|
|
40
internal/ui/message/input/keydown.go
Normal file
40
internal/ui/message/input/keydown.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gotk3/gotk3/gdk"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shiftMask = uint(gdk.SHIFT_MASK)
|
||||||
|
const cntrlMask = uint(gdk.CONTROL_MASK)
|
||||||
|
|
||||||
|
func bithas(bit, has uint) bool {
|
||||||
|
return bit&has == has
|
||||||
|
}
|
||||||
|
|
||||||
|
func convEvent(ev *gdk.Event) (key, mask uint) {
|
||||||
|
var keyEvent = gdk.EventKeyNewFromEvent(ev)
|
||||||
|
return keyEvent.KeyVal(), keyEvent.State()
|
||||||
|
}
|
||||||
|
|
||||||
|
// connects to key-press-event
|
||||||
|
func (f *Field) keyDown(tv *gtk.TextView, ev *gdk.Event) bool {
|
||||||
|
var key, mask = convEvent(ev)
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
// If Enter is pressed.
|
||||||
|
case gdk.KEY_Return:
|
||||||
|
// If Shift is being held, insert a new line.
|
||||||
|
if bithas(mask, shiftMask) {
|
||||||
|
f.buffer.InsertAtCursor("\n")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Else, send the message.
|
||||||
|
f.SendMessage()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passthrough.
|
||||||
|
return false
|
||||||
|
}
|
53
internal/ui/message/input/send.go
Normal file
53
internal/ui/message/input/send.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type PresendMessage interface {
|
||||||
|
cchat.SendableMessage
|
||||||
|
cchat.MessageNonce
|
||||||
|
|
||||||
|
Author() text.Rich
|
||||||
|
AuthorID() string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ cchat.SendableMessage = (*SendMessageData)(nil)
|
||||||
|
_ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SendMessageData) Author() text.Rich {
|
||||||
|
return s.author
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SendMessageData) AuthorID() string {
|
||||||
|
return s.authorID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SendMessageData) Nonce() string {
|
||||||
|
return s.nonce
|
||||||
|
}
|
|
@ -3,40 +3,55 @@ package message
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/message/compact"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/message/input"
|
"github.com/diamondburned/cchat-gtk/internal/ui/message/input"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Container interface {
|
||||||
|
gtk.IWidget
|
||||||
|
cchat.MessagesContainer
|
||||||
|
|
||||||
|
Reset()
|
||||||
|
ScrollToBottom()
|
||||||
|
|
||||||
|
// PresendMessage is for unsent messages.
|
||||||
|
PresendMessage(input.PresendMessage) (done func(sendError error))
|
||||||
|
}
|
||||||
|
|
||||||
type View struct {
|
type View struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
Container *Container
|
Container Container
|
||||||
SendInput *input.Field
|
SendInput *input.Field
|
||||||
|
|
||||||
current cchat.ServerMessage
|
current cchat.ServerMessage
|
||||||
|
author string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewView() *View {
|
func NewView() *View {
|
||||||
container := NewContainer()
|
view := &View{}
|
||||||
sendinput := input.NewField()
|
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
view.Container = compact.NewContainer()
|
||||||
box.PackStart(container, true, true, 0)
|
view.SendInput = input.NewField(view)
|
||||||
box.PackStart(sendinput, false, false, 0)
|
|
||||||
box.Show()
|
|
||||||
|
|
||||||
return &View{
|
view.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
Box: box,
|
view.Box.PackStart(view.Container, true, true, 0)
|
||||||
Container: container,
|
view.Box.PackStart(view.SendInput, false, false, 0)
|
||||||
SendInput: sendinput,
|
view.Box.Show()
|
||||||
}
|
|
||||||
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) JoinServer(server cchat.ServerMessage) {
|
// JoinServer is not thread-safe, but it calls backend functions asynchronously.
|
||||||
|
func (v *View) JoinServer(session cchat.Session, server cchat.ServerMessage) {
|
||||||
if v.current != nil {
|
if v.current != nil {
|
||||||
|
// Backend should handle synchronizing joins and leaves if it needs to.
|
||||||
|
go func() {
|
||||||
if err := v.current.LeaveServer(); err != nil {
|
if err := v.current.LeaveServer(); err != nil {
|
||||||
log.Error(errors.Wrap(err, "Error leaving server"))
|
log.Error(errors.Wrap(err, "Error leaving server"))
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Clean all messages.
|
// Clean all messages.
|
||||||
v.Container.Reset()
|
v.Container.Reset()
|
||||||
|
@ -47,9 +62,15 @@ func (v *View) JoinServer(server cchat.ServerMessage) {
|
||||||
// Skipping ok check because sender can be nil. Without the empty check, Go
|
// Skipping ok check because sender can be nil. Without the empty check, Go
|
||||||
// will panic.
|
// will panic.
|
||||||
sender, _ := server.(cchat.ServerMessageSender)
|
sender, _ := server.(cchat.ServerMessageSender)
|
||||||
v.SendInput.SetSender(sender)
|
v.SendInput.SetSender(session, sender)
|
||||||
|
|
||||||
|
go func() {
|
||||||
if err := v.current.JoinServer(v.Container); err != nil {
|
if err := v.current.JoinServer(v.Container); err != nil {
|
||||||
log.Error(errors.Wrap(err, "Failed to join server"))
|
log.Error(errors.Wrap(err, "Failed to join server"))
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PresendMessage(msg input.PresendMessage) func(error) {
|
||||||
|
return v.Container.PresendMessage(msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,5 +21,22 @@ var _ Bin = (*gtk.Bin)(nil)
|
||||||
|
|
||||||
func BinLeftAlignLabel(bin Bin) {
|
func BinLeftAlignLabel(bin Bin) {
|
||||||
widget, _ := bin.GetChild()
|
widget, _ := bin.GetChild()
|
||||||
widget.(interface{ SetXAlign(float64) }).SetXAlign(0)
|
widget.(interface{ SetHAlign(gtk.Align) }).SetHAlign(gtk.ALIGN_START)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewButtonIcon(icon string) *gtk.Image {
|
||||||
|
img, _ := gtk.ImageNewFromIconName(icon, gtk.ICON_SIZE_BUTTON)
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageIconPx(icon string, sizepx int) *gtk.Image {
|
||||||
|
img, _ := gtk.ImageNew()
|
||||||
|
SetImageIcon(img, icon, sizepx)
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetImageIcon(img *gtk.Image, icon string, sizepx int) {
|
||||||
|
img.SetProperty("icon-name", icon)
|
||||||
|
img.SetProperty("pixel-size", sizepx)
|
||||||
|
img.SetSizeRequest(sizepx, sizepx)
|
||||||
}
|
}
|
||||||
|
|
1
internal/ui/rich/parser/parser.go
Normal file
1
internal/ui/rich/parser/parser.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package parser
|
111
internal/ui/rich/rich.go
Normal file
111
internal/ui/rich/rich.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package rich
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat/text"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: parser
|
||||||
|
|
||||||
|
type Labeler interface {
|
||||||
|
cchat.LabelContainer // thread-safe
|
||||||
|
GetLabel() text.Rich // not thread-safe
|
||||||
|
}
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
gtk.Label
|
||||||
|
current text.Rich
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ gtk.IWidget = (*Label)(nil)
|
||||||
|
_ Labeler = (*Label)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLabel(content text.Rich) *Label {
|
||||||
|
label, _ := gtk.LabelNew(content.Content)
|
||||||
|
label.SetHAlign(gtk.ALIGN_START)
|
||||||
|
return &Label{*label, content}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLabel is thread-safe.
|
||||||
|
func (l *Label) SetLabel(content text.Rich) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
l.current = content
|
||||||
|
l.SetText(content.Content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabel is NOT thread-safe.
|
||||||
|
func (l *Label) GetLabel() text.Rich {
|
||||||
|
return l.current
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToggleButton struct {
|
||||||
|
gtk.ToggleButton
|
||||||
|
Label
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ gtk.IWidget = (*ToggleButton)(nil)
|
||||||
|
_ cchat.LabelContainer = (*ToggleButton)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewToggleButton(content text.Rich) *ToggleButton {
|
||||||
|
l := NewLabel(content)
|
||||||
|
l.Show()
|
||||||
|
|
||||||
|
b, _ := gtk.ToggleButtonNew()
|
||||||
|
primitives.BinLeftAlignLabel(b)
|
||||||
|
|
||||||
|
b.Add(l)
|
||||||
|
|
||||||
|
return &ToggleButton{*b, *l}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToggleButtonImage struct {
|
||||||
|
gtk.ToggleButton
|
||||||
|
Labeler
|
||||||
|
|
||||||
|
Label gtk.Label
|
||||||
|
Image gtk.Image
|
||||||
|
|
||||||
|
Box gtk.Box
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ gtk.IWidget = (*ToggleButton)(nil)
|
||||||
|
_ cchat.LabelContainer = (*ToggleButton)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewToggleButtonImage(content text.Rich, iconName string) *ToggleButtonImage {
|
||||||
|
l := NewLabel(content)
|
||||||
|
l.Show()
|
||||||
|
|
||||||
|
var i *gtk.Image
|
||||||
|
if iconName != "" {
|
||||||
|
i, _ = gtk.ImageNewFromIconName(iconName, gtk.ICON_SIZE_BUTTON)
|
||||||
|
} else {
|
||||||
|
i, _ = gtk.ImageNew()
|
||||||
|
}
|
||||||
|
i.Show()
|
||||||
|
|
||||||
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
|
box.PackStart(i, false, false, 0)
|
||||||
|
box.PackStart(l, true, true, 5)
|
||||||
|
box.Show()
|
||||||
|
|
||||||
|
b, _ := gtk.ToggleButtonNew()
|
||||||
|
b.Add(box)
|
||||||
|
|
||||||
|
return &ToggleButtonImage{
|
||||||
|
ToggleButton: *b,
|
||||||
|
Labeler: l, // easy inheritance of methods
|
||||||
|
Label: l.Label,
|
||||||
|
Image: *i,
|
||||||
|
Box: *box,
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/dialog"
|
"github.com/diamondburned/cchat-gtk/internal/ui/dialog"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/gotk3/gotk3/pango"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dialog struct {
|
type Dialog struct {
|
||||||
|
@ -116,19 +115,20 @@ func (d *Dialog) ok() {
|
||||||
type Request struct {
|
type Request struct {
|
||||||
*gtk.Grid
|
*gtk.Grid
|
||||||
labels []*gtk.Label
|
labels []*gtk.Label
|
||||||
entries []*gtk.Entry
|
entries []Texter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRequest(authEntries []cchat.AuthenticateEntry) *Request {
|
func NewRequest(authEntries []cchat.AuthenticateEntry) *Request {
|
||||||
grid, _ := gtk.GridNew()
|
grid, _ := gtk.GridNew()
|
||||||
grid.Show()
|
grid.Show()
|
||||||
grid.SetRowHomogeneous(true)
|
grid.SetRowSpacing(7)
|
||||||
grid.SetRowSpacing(2)
|
grid.SetColumnHomogeneous(true)
|
||||||
|
grid.SetColumnSpacing(5)
|
||||||
|
|
||||||
req := &Request{
|
req := &Request{
|
||||||
Grid: grid,
|
Grid: grid,
|
||||||
labels: make([]*gtk.Label, len(authEntries)),
|
labels: make([]*gtk.Label, len(authEntries)),
|
||||||
entries: make([]*gtk.Entry, len(authEntries)),
|
entries: make([]Texter, len(authEntries)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, authEntry := range authEntries {
|
for i, authEntry := range authEntries {
|
||||||
|
@ -147,21 +147,25 @@ func NewRequest(authEntries []cchat.AuthenticateEntry) *Request {
|
||||||
func (r *Request) values() []string {
|
func (r *Request) values() []string {
|
||||||
var values = make([]string, len(r.entries))
|
var values = make([]string, len(r.entries))
|
||||||
for i, entry := range r.entries {
|
for i, entry := range r.entries {
|
||||||
values[i], _ = entry.GetText()
|
values[i] = entry.GetText()
|
||||||
}
|
}
|
||||||
|
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEntry(authEntry cchat.AuthenticateEntry) (*gtk.Label, *gtk.Entry) {
|
func newEntry(authEntry cchat.AuthenticateEntry) (*gtk.Label, Texter) {
|
||||||
label, _ := gtk.LabelNew(authEntry.Name)
|
label, _ := gtk.LabelNew(authEntry.Name)
|
||||||
label.Show()
|
label.Show()
|
||||||
label.SetXAlign(1) // right align
|
label.SetXAlign(1) // right align
|
||||||
label.SetEllipsize(pango.ELLIPSIZE_END)
|
label.SetJustify(gtk.JUSTIFY_RIGHT)
|
||||||
|
label.SetLineWrap(true)
|
||||||
|
|
||||||
input, _ := gtk.EntryNew()
|
var texter Texter
|
||||||
input.Show()
|
|
||||||
|
|
||||||
|
if authEntry.Multiline {
|
||||||
|
texter = NewMultilineInput()
|
||||||
|
} else {
|
||||||
|
var input = NewEntryInput()
|
||||||
if authEntry.Secret {
|
if authEntry.Secret {
|
||||||
input.SetInputPurpose(gtk.INPUT_PURPOSE_PASSWORD)
|
input.SetInputPurpose(gtk.INPUT_PURPOSE_PASSWORD)
|
||||||
input.SetVisibility(false)
|
input.SetVisibility(false)
|
||||||
|
@ -171,5 +175,63 @@ func newEntry(authEntry cchat.AuthenticateEntry) (*gtk.Label, *gtk.Entry) {
|
||||||
input.SetInputPurpose(gtk.INPUT_PURPOSE_EMAIL)
|
input.SetInputPurpose(gtk.INPUT_PURPOSE_EMAIL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return label, input
|
texter = input
|
||||||
|
}
|
||||||
|
|
||||||
|
return label, texter
|
||||||
|
}
|
||||||
|
|
||||||
|
type Texter interface {
|
||||||
|
gtk.IWidget
|
||||||
|
GetText() string
|
||||||
|
SetText(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntryInput struct {
|
||||||
|
*gtk.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Texter = (*EntryInput)(nil)
|
||||||
|
|
||||||
|
func NewEntryInput() EntryInput {
|
||||||
|
input, _ := gtk.EntryNew()
|
||||||
|
input.SetVAlign(gtk.ALIGN_CENTER)
|
||||||
|
input.Show()
|
||||||
|
|
||||||
|
return EntryInput{
|
||||||
|
input,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i EntryInput) GetText() (text string) {
|
||||||
|
text, _ = i.Entry.GetText()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type MultilineInput struct {
|
||||||
|
*gtk.TextView
|
||||||
|
Buffer *gtk.TextBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Texter = (*MultilineInput)(nil)
|
||||||
|
|
||||||
|
func NewMultilineInput() MultilineInput {
|
||||||
|
view, _ := gtk.TextViewNew()
|
||||||
|
view.SetWrapMode(gtk.WRAP_WORD_CHAR)
|
||||||
|
view.SetEditable(true)
|
||||||
|
view.Show()
|
||||||
|
|
||||||
|
buf, _ := view.GetBuffer()
|
||||||
|
|
||||||
|
return MultilineInput{view, buf}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i MultilineInput) GetText() (text string) {
|
||||||
|
start, end := i.Buffer.GetBounds()
|
||||||
|
text, _ = i.Buffer.GetText(start, end, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i MultilineInput) SetText(text string) {
|
||||||
|
i.Buffer.SetText(text)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,19 @@ package service
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/auth"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const IconSize = 32
|
||||||
|
|
||||||
|
type Controller interface {
|
||||||
|
session.Controller
|
||||||
|
AuthenticateSession(*Container, cchat.Service)
|
||||||
|
}
|
||||||
|
|
||||||
type View struct {
|
type View struct {
|
||||||
*gtk.ScrolledWindow
|
*gtk.ScrolledWindow
|
||||||
Box *gtk.Box
|
Box *gtk.Box
|
||||||
|
@ -33,10 +40,11 @@ func NewView() *View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) AddService(svc cchat.Service, rowctrl server.RowController) {
|
func (v *View) AddService(svc cchat.Service, ctrl Controller) *Container {
|
||||||
s := NewContainer(svc, rowctrl)
|
s := NewContainer(svc, ctrl)
|
||||||
v.Services = append(v.Services, s)
|
v.Services = append(v.Services, s)
|
||||||
v.Box.Add(s)
|
v.Box.Add(s)
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
|
@ -44,19 +52,20 @@ type Container struct {
|
||||||
header *header
|
header *header
|
||||||
revealer *gtk.Revealer
|
revealer *gtk.Revealer
|
||||||
children *children
|
children *children
|
||||||
rowctrl server.RowController
|
rowctrl Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContainer(svc cchat.Service, rowctrl server.RowController) *Container {
|
func NewContainer(svc cchat.Service, ctrl Controller) *Container {
|
||||||
header := newHeader(svc)
|
|
||||||
|
|
||||||
children := newChildren()
|
children := newChildren()
|
||||||
|
|
||||||
chrev, _ := gtk.RevealerNew()
|
chrev, _ := gtk.RevealerNew()
|
||||||
chrev.SetRevealChild(false)
|
chrev.SetRevealChild(true)
|
||||||
chrev.Add(children)
|
chrev.Add(children)
|
||||||
chrev.Show()
|
chrev.Show()
|
||||||
|
|
||||||
|
header := newHeader(svc)
|
||||||
|
header.reveal.SetActive(chrev.GetRevealChild())
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
box.Show()
|
box.Show()
|
||||||
box.PackStart(header, false, false, 0)
|
box.PackStart(header, false, false, 0)
|
||||||
|
@ -64,7 +73,7 @@ func NewContainer(svc cchat.Service, rowctrl server.RowController) *Container {
|
||||||
|
|
||||||
primitives.AddClass(box, "service")
|
primitives.AddClass(box, "service")
|
||||||
|
|
||||||
var container = &Container{box, header, chrev, children, rowctrl}
|
var container = &Container{box, header, chrev, children, ctrl}
|
||||||
|
|
||||||
// On click, toggle reveal.
|
// On click, toggle reveal.
|
||||||
header.reveal.Connect("clicked", func() {
|
header.reveal.Connect("clicked", func() {
|
||||||
|
@ -75,31 +84,41 @@ func NewContainer(svc cchat.Service, rowctrl server.RowController) *Container {
|
||||||
|
|
||||||
// On click, show the auth dialog.
|
// On click, show the auth dialog.
|
||||||
header.add.Connect("clicked", func() {
|
header.add.Connect("clicked", func() {
|
||||||
auth.NewDialog(svc.Name(), svc.Authenticate(), container.addSession)
|
ctrl.AuthenticateSession(container, svc)
|
||||||
})
|
})
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) addSession(ses cchat.Session) {
|
func (c *Container) AddSession(ses cchat.Session) {
|
||||||
srow := session.New(ses, c.rowctrl)
|
srow := session.New(ses, c.rowctrl)
|
||||||
c.children.addSessionRow(srow)
|
c.children.addSessionRow(srow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) Sessions() []cchat.Session {
|
||||||
|
var sessions = make([]cchat.Session, len(c.children.Sessions))
|
||||||
|
for i, s := range c.children.Sessions {
|
||||||
|
sessions[i] = s.Session
|
||||||
|
}
|
||||||
|
return sessions
|
||||||
|
}
|
||||||
|
|
||||||
type header struct {
|
type header struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
reveal *gtk.ToggleButton
|
reveal *rich.ToggleButtonImage // no rich text here but it's left aligned
|
||||||
add *gtk.Button
|
add *gtk.Button
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHeader(svc cchat.Service) *header {
|
func newHeader(svc cchat.Service) *header {
|
||||||
reveal, _ := gtk.ToggleButtonNewWithLabel(svc.Name())
|
reveal := rich.NewToggleButtonImage(text.Rich{Content: svc.Name()}, "")
|
||||||
primitives.BinLeftAlignLabel(reveal) // do this first
|
reveal.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
|
||||||
reveal.SetRelief(gtk.RELIEF_NONE)
|
reveal.SetRelief(gtk.RELIEF_NONE)
|
||||||
reveal.SetMode(true)
|
reveal.SetMode(true)
|
||||||
reveal.Show()
|
reveal.Show()
|
||||||
|
|
||||||
|
// Set a custom icon.
|
||||||
|
primitives.SetImageIcon(&reveal.Image, "folder-remote-symbolic", IconSize)
|
||||||
|
|
||||||
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.SetRelief(gtk.RELIEF_NONE)
|
||||||
add.Show()
|
add.Show()
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
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/gotk3/gotk3/gtk"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ChildrenMargin = 24
|
|
||||||
|
|
||||||
type RowController interface {
|
|
||||||
MessageRowSelected(*Row, cchat.ServerMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Row struct {
|
|
||||||
*gtk.Box
|
|
||||||
Button *gtk.Button
|
|
||||||
Server cchat.Server
|
|
||||||
|
|
||||||
ctrl RowController
|
|
||||||
|
|
||||||
// enum 1
|
|
||||||
message cchat.ServerMessage
|
|
||||||
|
|
||||||
// enum 2
|
|
||||||
children *Children
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(server cchat.Server, ctrl RowController) *Row {
|
|
||||||
name, err := server.Name()
|
|
||||||
if err != nil {
|
|
||||||
log.Error(errors.Wrap(err, "Failed to get the server name"))
|
|
||||||
name = "no name"
|
|
||||||
}
|
|
||||||
|
|
||||||
button, _ := gtk.ButtonNewWithLabel(name)
|
|
||||||
primitives.BinLeftAlignLabel(button)
|
|
||||||
|
|
||||||
button.SetRelief(gtk.RELIEF_NONE)
|
|
||||||
button.Show()
|
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
||||||
box.PackStart(button, false, false, 0)
|
|
||||||
box.Show()
|
|
||||||
|
|
||||||
primitives.AddClass(box, "server")
|
|
||||||
|
|
||||||
// TODO: images
|
|
||||||
|
|
||||||
var row = &Row{
|
|
||||||
Box: box,
|
|
||||||
Button: button,
|
|
||||||
Server: server,
|
|
||||||
ctrl: ctrl,
|
|
||||||
}
|
|
||||||
button.Connect("clicked", row.onClick)
|
|
||||||
|
|
||||||
switch server := server.(type) {
|
|
||||||
case cchat.ServerList:
|
|
||||||
row.children = NewChildren(server, ctrl)
|
|
||||||
box.PackStart(row.children, false, false, 0)
|
|
||||||
|
|
||||||
primitives.AddClass(box, "server-list")
|
|
||||||
|
|
||||||
case cchat.ServerMessage:
|
|
||||||
row.message = server
|
|
||||||
|
|
||||||
primitives.AddClass(box, "server-message")
|
|
||||||
}
|
|
||||||
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
func (row *Row) onClick() {
|
|
||||||
switch {
|
|
||||||
case row.message != nil:
|
|
||||||
row.ctrl.MessageRowSelected(row, row.message)
|
|
||||||
case row.children != nil:
|
|
||||||
row.children.SetRevealChild(!row.children.GetRevealChild())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Children struct {
|
|
||||||
*gtk.Revealer
|
|
||||||
Main *gtk.Box
|
|
||||||
List cchat.ServerList
|
|
||||||
|
|
||||||
Rows []*Row
|
|
||||||
rowctrl RowController
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewChildren(list cchat.ServerList, ctrl RowController) *Children {
|
|
||||||
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
||||||
main.SetMarginStart(ChildrenMargin)
|
|
||||||
main.Show()
|
|
||||||
|
|
||||||
rev, _ := gtk.RevealerNew()
|
|
||||||
rev.SetRevealChild(false)
|
|
||||||
rev.Add(main)
|
|
||||||
rev.Show()
|
|
||||||
|
|
||||||
children := &Children{
|
|
||||||
Revealer: rev,
|
|
||||||
Main: main,
|
|
||||||
List: list,
|
|
||||||
rowctrl: ctrl,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := list.Servers(children); err != nil {
|
|
||||||
log.Error(errors.Wrap(err, "Failed to get servers"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Children) SetServers(servers []cchat.Server) {
|
|
||||||
gts.ExecAsync(func() {
|
|
||||||
for _, row := range c.Rows {
|
|
||||||
c.Main.Remove(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Rows = make([]*Row, len(servers))
|
|
||||||
|
|
||||||
for i, server := range servers {
|
|
||||||
row := New(server, c.rowctrl)
|
|
||||||
c.Rows[i] = row
|
|
||||||
c.Main.Add(row)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
87
internal/ui/service/session/server/server_children.go
Normal file
87
internal/ui/service/session/server/server_children.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ChildrenMargin = 24
|
||||||
|
|
||||||
|
type Controller interface {
|
||||||
|
MessageRowSelected(*Row, cchat.ServerMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children is a children server with a reference to the parent.
|
||||||
|
type Children struct {
|
||||||
|
*gtk.Revealer
|
||||||
|
Main *gtk.Box
|
||||||
|
List cchat.ServerList
|
||||||
|
|
||||||
|
rowctrl Controller
|
||||||
|
|
||||||
|
Rows []*Row
|
||||||
|
ParentRow *Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChildren(parent *Row, list cchat.ServerList, ctrl Controller) *Children {
|
||||||
|
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
|
main.SetMarginStart(ChildrenMargin)
|
||||||
|
main.Show()
|
||||||
|
|
||||||
|
rev, _ := gtk.RevealerNew()
|
||||||
|
rev.SetRevealChild(false)
|
||||||
|
rev.Add(main)
|
||||||
|
rev.Show()
|
||||||
|
|
||||||
|
children := &Children{
|
||||||
|
Revealer: rev,
|
||||||
|
Main: main,
|
||||||
|
List: list,
|
||||||
|
rowctrl: ctrl,
|
||||||
|
ParentRow: parent,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := list.Servers(children); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to get servers"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Children) SetServers(servers []cchat.Server) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
// Save the current state.
|
||||||
|
var oldID string
|
||||||
|
for _, row := range c.Rows {
|
||||||
|
if row.GetActive() {
|
||||||
|
oldID = row.Server.ID()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the server list.
|
||||||
|
for _, row := range c.Rows {
|
||||||
|
c.Main.Remove(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Rows = make([]*Row, len(servers))
|
||||||
|
|
||||||
|
for i, server := range servers {
|
||||||
|
row := NewRow(c, server, c.rowctrl)
|
||||||
|
c.Rows[i] = row
|
||||||
|
c.Main.Add(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update parent reference? Only if it's activated.
|
||||||
|
if oldID != "" {
|
||||||
|
for _, row := range c.Rows {
|
||||||
|
if row.Server.ID() == oldID {
|
||||||
|
row.Button.SetActive(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
100
internal/ui/service/session/server/server_row.go
Normal file
100
internal/ui/service/session/server/server_row.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"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/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Row struct {
|
||||||
|
*gtk.Box
|
||||||
|
Button *rich.ToggleButtonImage
|
||||||
|
Server cchat.Server
|
||||||
|
Parent *Children
|
||||||
|
|
||||||
|
ctrl Controller
|
||||||
|
|
||||||
|
// enum 1
|
||||||
|
message cchat.ServerMessage
|
||||||
|
|
||||||
|
// enum 2
|
||||||
|
children *Children
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRow(parent *Children, server cchat.Server, ctrl Controller) *Row {
|
||||||
|
button := rich.NewToggleButtonImage(text.Rich{}, "")
|
||||||
|
button.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
button.SetRelief(gtk.RELIEF_NONE)
|
||||||
|
button.Show()
|
||||||
|
|
||||||
|
if err := server.Name(button); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to get the server name"))
|
||||||
|
button.SetLabel(text.Rich{Content: "Unknown"})
|
||||||
|
}
|
||||||
|
|
||||||
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
|
box.PackStart(button, false, false, 0)
|
||||||
|
box.Show()
|
||||||
|
|
||||||
|
primitives.AddClass(box, "server")
|
||||||
|
|
||||||
|
// TODO: images
|
||||||
|
|
||||||
|
var row = &Row{
|
||||||
|
Box: box,
|
||||||
|
Button: button,
|
||||||
|
Server: server,
|
||||||
|
Parent: parent,
|
||||||
|
ctrl: ctrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch server := server.(type) {
|
||||||
|
case cchat.ServerList:
|
||||||
|
row.children = NewChildren(row, server, ctrl)
|
||||||
|
box.PackStart(row.children, false, false, 0)
|
||||||
|
|
||||||
|
primitives.AddClass(box, "server-list")
|
||||||
|
|
||||||
|
case cchat.ServerMessage:
|
||||||
|
row.message = server
|
||||||
|
|
||||||
|
primitives.AddClass(box, "server-message")
|
||||||
|
}
|
||||||
|
|
||||||
|
button.Connect("clicked", row.onClick)
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
func (row *Row) GetActive() bool {
|
||||||
|
return row.Button.GetActive()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (row *Row) onClick() {
|
||||||
|
switch {
|
||||||
|
|
||||||
|
// If the server is a message server. We're only selected if the button is
|
||||||
|
// pressed.
|
||||||
|
case row.message != nil && row.GetActive():
|
||||||
|
row.ctrl.MessageRowSelected(row, row.message)
|
||||||
|
|
||||||
|
// If the server is a list of smaller servers.
|
||||||
|
case row.children != nil:
|
||||||
|
row.children.SetRevealChild(!row.children.GetRevealChild())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Row) Breadcrumb() string {
|
||||||
|
var label = r.Button.GetLabel().Content
|
||||||
|
|
||||||
|
// Does the row have a parent?
|
||||||
|
if r.Parent != nil {
|
||||||
|
return r.Parent.ParentRow.Breadcrumb() + "/" + label
|
||||||
|
}
|
||||||
|
|
||||||
|
return label
|
||||||
|
}
|
|
@ -4,53 +4,66 @@ import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"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/rich"
|
||||||
"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/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const IconSize = 32
|
||||||
|
|
||||||
|
// Controller extends server.RowController to add session.
|
||||||
|
type Controller interface {
|
||||||
|
MessageRowSelected(*Row, *server.Row, cchat.ServerMessage)
|
||||||
|
}
|
||||||
|
|
||||||
type Row struct {
|
type Row struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
Button *gtk.ToggleButton
|
Button *rich.ToggleButtonImage
|
||||||
Session cchat.Session
|
Session cchat.Session
|
||||||
|
|
||||||
Servers *server.Children
|
Servers *server.Children
|
||||||
|
|
||||||
|
ctrl Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ses cchat.Session, rowctrl server.RowController) *Row {
|
func New(ses cchat.Session, ctrl Controller) *Row {
|
||||||
n, err := ses.Name()
|
row := &Row{
|
||||||
if err != nil {
|
Session: ses,
|
||||||
log.Error(errors.Wrap(err, "Failed to get the username"))
|
ctrl: ctrl,
|
||||||
n = "no name"
|
|
||||||
}
|
}
|
||||||
|
row.Servers = server.NewChildren(ses, row)
|
||||||
|
|
||||||
button, _ := gtk.ToggleButtonNewWithLabel(n)
|
row.Button = rich.NewToggleButtonImage(text.Rich{}, "")
|
||||||
primitives.BinLeftAlignLabel(button)
|
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
row.Button.SetRelief(gtk.RELIEF_NONE)
|
||||||
button.SetRelief(gtk.RELIEF_NONE)
|
row.Button.Show()
|
||||||
button.Show()
|
|
||||||
|
|
||||||
servers := server.NewChildren(ses, rowctrl)
|
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
||||||
box.Show()
|
|
||||||
box.SetMarginStart(server.ChildrenMargin)
|
|
||||||
box.PackStart(button, false, false, 0)
|
|
||||||
box.PackStart(servers, false, false, 0)
|
|
||||||
|
|
||||||
primitives.AddClass(box, "session")
|
|
||||||
|
|
||||||
// On click, toggle reveal.
|
// On click, toggle reveal.
|
||||||
button.Connect("clicked", func() {
|
row.Button.Connect("clicked", func() {
|
||||||
revealed := !servers.GetRevealChild()
|
revealed := !row.Servers.GetRevealChild()
|
||||||
servers.SetRevealChild(revealed)
|
row.Servers.SetRevealChild(revealed)
|
||||||
button.SetActive(revealed)
|
row.Button.SetActive(revealed)
|
||||||
})
|
})
|
||||||
|
|
||||||
return &Row{
|
primitives.SetImageIcon(&row.Button.Image, "user-available-symbolic", IconSize)
|
||||||
Box: box,
|
|
||||||
Button: button,
|
if err := ses.Name(row.Button); err != nil {
|
||||||
Session: ses,
|
log.Error(errors.Wrap(err, "Failed to get the username"))
|
||||||
Servers: servers,
|
row.Button.SetLabel(text.Rich{Content: "Unknown"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
row.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
|
row.Box.SetMarginStart(server.ChildrenMargin)
|
||||||
|
row.Box.PackStart(row.Button, false, false, 0)
|
||||||
|
row.Box.PackStart(row.Servers, false, false, 0)
|
||||||
|
row.Box.Show()
|
||||||
|
|
||||||
|
primitives.AddClass(row.Box, "session")
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Row) MessageRowSelected(server *server.Row, smsg cchat.ServerMessage) {
|
||||||
|
r.ctrl.MessageRowSelected(r, server, smsg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,25 +3,33 @@ package ui
|
||||||
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/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/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const LeftWidth = 220
|
const LeftWidth = 220
|
||||||
|
|
||||||
type Application struct {
|
type App struct {
|
||||||
window *window
|
window *window
|
||||||
header *header
|
header *header
|
||||||
|
|
||||||
|
// used to keep track of what row to highlight and unhighlight
|
||||||
|
lastRowHighlighter func(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ gts.Windower = (*Application)(nil)
|
_ gts.Windower = (*App)(nil)
|
||||||
_ gts.Headerer = (*Application)(nil)
|
_ gts.Headerer = (*App)(nil)
|
||||||
_ server.RowController = (*Application)(nil)
|
_ service.Controller = (*App)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewApplication() *Application {
|
func NewApplication() *App {
|
||||||
app := &Application{
|
app := &App{
|
||||||
window: newWindow(),
|
window: newWindow(),
|
||||||
header: newHeader(),
|
header: newHeader(),
|
||||||
}
|
}
|
||||||
|
@ -29,18 +37,44 @@ func NewApplication() *Application {
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) AddService(svc cchat.Service) {
|
func (app *App) AddService(svc cchat.Service) {
|
||||||
app.window.Services.AddService(svc, app)
|
var container = app.window.Services.AddService(svc, app)
|
||||||
|
|
||||||
|
// Attempt to restore sessions asynchronously.
|
||||||
|
keyring.RestoreSessions(svc, container.AddSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) MessageRowSelected(_ *server.Row, smsg cchat.ServerMessage) {
|
func (app *App) MessageRowSelected(ses *session.Row, srv *server.Row, smsg cchat.ServerMessage) {
|
||||||
app.window.MessageView.JoinServer(smsg)
|
// Is there an old row that we should unhighlight?
|
||||||
|
if app.lastRowHighlighter != nil {
|
||||||
|
app.lastRowHighlighter(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) Header() gtk.IWidget {
|
// Set the new row and highlight it.
|
||||||
|
app.lastRowHighlighter = srv.Button.SetActive
|
||||||
|
app.lastRowHighlighter(true)
|
||||||
|
|
||||||
|
log.Println("Breadcrumb:")
|
||||||
|
|
||||||
|
// Show the messages.
|
||||||
|
app.window.MessageView.JoinServer(ses.Session, smsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) AuthenticateSession(container *service.Container, svc cchat.Service) {
|
||||||
|
auth.NewDialog(svc.Name(), svc.Authenticate(), func(ses cchat.Session) {
|
||||||
|
container.AddSession(ses)
|
||||||
|
|
||||||
|
// Save all sessions.
|
||||||
|
for _, err := range keyring.SaveSessions(svc, container.Sessions()) {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *App) Header() gtk.IWidget {
|
||||||
return app.header
|
return app.header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) Window() gtk.IWidget {
|
func (app *App) Window() gtk.IWidget {
|
||||||
return app.window
|
return app.window
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue