upgraded libhandy; new cchat auth API dialog
This commit is contained in:
parent
18c9e9535c
commit
cf4f8ce245
6
go.mod
6
go.mod
|
@ -7,9 +7,9 @@ replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20200816
|
||||||
require (
|
require (
|
||||||
github.com/Xuanwo/go-locale v1.0.0
|
github.com/Xuanwo/go-locale v1.0.0
|
||||||
github.com/alecthomas/chroma v0.7.3
|
github.com/alecthomas/chroma v0.7.3
|
||||||
github.com/diamondburned/cchat v0.3.7
|
github.com/diamondburned/cchat v0.3.11
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201023215116-2209348d23bd
|
github.com/diamondburned/cchat-discord v0.0.0-20201027213927-37165658e17c
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201023061026-155813b08c2c
|
github.com/diamondburned/cchat-mock v0.0.0-20201027204251-4f6dfbfc2424
|
||||||
github.com/diamondburned/gspell v0.0.0-20200830182722-77e5d27d6894
|
github.com/diamondburned/gspell v0.0.0-20200830182722-77e5d27d6894
|
||||||
github.com/diamondburned/handy v0.0.0-20200829011954-4667e7a918f4
|
github.com/diamondburned/handy v0.0.0-20200829011954-4667e7a918f4
|
||||||
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
|
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
|
||||||
|
|
12
go.sum
12
go.sum
|
@ -89,6 +89,10 @@ github.com/diamondburned/cchat v0.3.3/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A
|
||||||
github.com/diamondburned/cchat v0.3.5/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
github.com/diamondburned/cchat v0.3.5/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
||||||
github.com/diamondburned/cchat v0.3.7 h1:0t3FkbzC/pBRAR3w0uYznJ+7dYqcR1M48a9wgz4JkIg=
|
github.com/diamondburned/cchat v0.3.7 h1:0t3FkbzC/pBRAR3w0uYznJ+7dYqcR1M48a9wgz4JkIg=
|
||||||
github.com/diamondburned/cchat v0.3.7/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
github.com/diamondburned/cchat v0.3.7/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
||||||
|
github.com/diamondburned/cchat v0.3.8 h1:vgFe8giVfwsAO+WpTYsTDIXvRUN48osVPNu0pZNvPEk=
|
||||||
|
github.com/diamondburned/cchat v0.3.8/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
||||||
|
github.com/diamondburned/cchat v0.3.11 h1:C1f9Tp7Kz3t+T1SlepL1RS7b/kACAKWAIZXAgJEpCHg=
|
||||||
|
github.com/diamondburned/cchat v0.3.11/go.mod h1:IlMtF+XIvAJh0GL/2yFdf0/34w+Hdy5A1GgvSwAXtQI=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20200719175346-af912db55401 h1:llmx/8UiJoTcHUw+GE5/rESVVmmnLh1HEPx3wRj+oQY=
|
github.com/diamondburned/cchat-discord v0.0.0-20200719175346-af912db55401 h1:llmx/8UiJoTcHUw+GE5/rESVVmmnLh1HEPx3wRj+oQY=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20200719175346-af912db55401/go.mod h1:+hSrIVYj5tIPLAorDsHj2Tbt2fWlZtOanzfEUHX53HM=
|
github.com/diamondburned/cchat-discord v0.0.0-20200719175346-af912db55401/go.mod h1:+hSrIVYj5tIPLAorDsHj2Tbt2fWlZtOanzfEUHX53HM=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20200730000036-2c93cdc1974e h1:EA5Vg0x57qLURJP80XhABBW+X0sbQSh2gw5qvPbZTs4=
|
github.com/diamondburned/cchat-discord v0.0.0-20200730000036-2c93cdc1974e h1:EA5Vg0x57qLURJP80XhABBW+X0sbQSh2gw5qvPbZTs4=
|
||||||
|
@ -119,6 +123,10 @@ github.com/diamondburned/cchat-discord v0.0.0-20201015062850-090259a6b4ca h1:36M
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201015062850-090259a6b4ca/go.mod h1:S0PDR6aj2qE871JSy94YvwtprQJCWwkIJWzRu7S1Asc=
|
github.com/diamondburned/cchat-discord v0.0.0-20201015062850-090259a6b4ca/go.mod h1:S0PDR6aj2qE871JSy94YvwtprQJCWwkIJWzRu7S1Asc=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201023215116-2209348d23bd h1:OspKIwR8s5tYf6OLh2LR6mMS4Xv0eOuPGGTzze9h7dA=
|
github.com/diamondburned/cchat-discord v0.0.0-20201023215116-2209348d23bd h1:OspKIwR8s5tYf6OLh2LR6mMS4Xv0eOuPGGTzze9h7dA=
|
||||||
github.com/diamondburned/cchat-discord v0.0.0-20201023215116-2209348d23bd/go.mod h1:7M/aCFl4EKe/rQEgyXiAWeAydaJoqqmyiSv086TOwE4=
|
github.com/diamondburned/cchat-discord v0.0.0-20201023215116-2209348d23bd/go.mod h1:7M/aCFl4EKe/rQEgyXiAWeAydaJoqqmyiSv086TOwE4=
|
||||||
|
github.com/diamondburned/cchat-discord v0.0.0-20201027050455-7ce513cf68e6 h1:uJSI/6U6SDi7FNL1cx1QQ9qADJQ19pG519HJ31Cfv5Y=
|
||||||
|
github.com/diamondburned/cchat-discord v0.0.0-20201027050455-7ce513cf68e6/go.mod h1:NdURsIvOA+Sxk0QHzaRTX5dBmoRLr/K5u3vSfzrI3n4=
|
||||||
|
github.com/diamondburned/cchat-discord v0.0.0-20201027213927-37165658e17c h1:x55N39x3lrGDrovVLR0sVyzNQzrUJsXWQfFZFNdYGi4=
|
||||||
|
github.com/diamondburned/cchat-discord v0.0.0-20201027213927-37165658e17c/go.mod h1:utku2TVk4IrnyuwNehgaIaAvtB6l4AO5RbzF+Uii8ko=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b h1:sq0MXjJc3yAOZvuolRxOpKQNvpMLyTmsECxQqdYgF5E=
|
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b h1:sq0MXjJc3yAOZvuolRxOpKQNvpMLyTmsECxQqdYgF5E=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b/go.mod h1:+bAf0m2o5qH54DmYJ/lR1HeITV53ol0JaoKyFFx3m3E=
|
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b/go.mod h1:+bAf0m2o5qH54DmYJ/lR1HeITV53ol0JaoKyFFx3m3E=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201004204741-b841407af381 h1:8JWNJMgoa3fL2py3gXSeC3NiAC+39EZp+JmvaoDBTUU=
|
github.com/diamondburned/cchat-mock v0.0.0-20201004204741-b841407af381 h1:8JWNJMgoa3fL2py3gXSeC3NiAC+39EZp+JmvaoDBTUU=
|
||||||
|
@ -131,6 +139,10 @@ github.com/diamondburned/cchat-mock v0.0.0-20201014202453-b9838fab0ab0 h1:Gwceon
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201014202453-b9838fab0ab0/go.mod h1:hYNki0Ic/d7zFVXTJIjp/td1W4OpxDNcVY8layxgTyc=
|
github.com/diamondburned/cchat-mock v0.0.0-20201014202453-b9838fab0ab0/go.mod h1:hYNki0Ic/d7zFVXTJIjp/td1W4OpxDNcVY8layxgTyc=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201023061026-155813b08c2c h1:9dACu5WbTPHLzGEY9sDrXCF5DW6AV2s7R85OoIXiTbo=
|
github.com/diamondburned/cchat-mock v0.0.0-20201023061026-155813b08c2c h1:9dACu5WbTPHLzGEY9sDrXCF5DW6AV2s7R85OoIXiTbo=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20201023061026-155813b08c2c/go.mod h1:hYNki0Ic/d7zFVXTJIjp/td1W4OpxDNcVY8layxgTyc=
|
github.com/diamondburned/cchat-mock v0.0.0-20201023061026-155813b08c2c/go.mod h1:hYNki0Ic/d7zFVXTJIjp/td1W4OpxDNcVY8layxgTyc=
|
||||||
|
github.com/diamondburned/cchat-mock v0.0.0-20201027045549-6d6056f11a5f h1:g3C5VHwrO6e3+5Z/+s+hN9yprTnI7PXS4jTVnaEH/pQ=
|
||||||
|
github.com/diamondburned/cchat-mock v0.0.0-20201027045549-6d6056f11a5f/go.mod h1:HwztxR71SXp2yqYPreLgWEBBVZrA/eMXMp5v7+9P5PY=
|
||||||
|
github.com/diamondburned/cchat-mock v0.0.0-20201027204251-4f6dfbfc2424 h1:VGsH+Xq34EB1lzcHrDHgiE0/6DoeKju2N3JhfCWSqB8=
|
||||||
|
github.com/diamondburned/cchat-mock v0.0.0-20201027204251-4f6dfbfc2424/go.mod h1:ylbEOpFJ6uPfjkw/+xpNvFfUDqkQobCJOAQwEt2AiDg=
|
||||||
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI=
|
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI=
|
||||||
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
||||||
github.com/diamondburned/gotk3 v0.0.0-20200816224505-3cd69b83a48a h1:wEldljb421/Jp84RNb0zBfqmiWt/TTQzUE6R1ap6UuQ=
|
github.com/diamondburned/gotk3 v0.0.0-20200816224505-3cd69b83a48a h1:wEldljb421/Jp84RNb0zBfqmiWt/TTQzUE6R1ap6UuQ=
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
package preferences
|
package preferences
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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/config"
|
"github.com/diamondburned/cchat-gtk/internal/ui/config"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/dialog"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dialog struct {
|
type Dialog struct {
|
||||||
*gtk.Dialog
|
*dialog.Dialog
|
||||||
|
|
||||||
switcher *gtk.StackSwitcher
|
switcher *gtk.StackSwitcher
|
||||||
stack *gtk.Stack
|
stack *gtk.Stack
|
||||||
|
@ -18,6 +18,10 @@ type Dialog struct {
|
||||||
|
|
||||||
func NewDialog() *Dialog {
|
func NewDialog() *Dialog {
|
||||||
stack, _ := gtk.StackNew()
|
stack, _ := gtk.StackNew()
|
||||||
|
stack.SetMarginTop(8)
|
||||||
|
stack.SetMarginBottom(8)
|
||||||
|
stack.SetMarginStart(16)
|
||||||
|
stack.SetMarginEnd(16)
|
||||||
stack.Show()
|
stack.Show()
|
||||||
|
|
||||||
switcher, _ := gtk.StackSwitcherNew()
|
switcher, _ := gtk.StackSwitcherNew()
|
||||||
|
@ -29,18 +33,9 @@ func NewDialog() *Dialog {
|
||||||
h.SetCustomTitle(switcher)
|
h.SetCustomTitle(switcher)
|
||||||
h.Show()
|
h.Show()
|
||||||
|
|
||||||
d, _ := gts.NewModalDialog()
|
d := dialog.NewCSD(stack, h)
|
||||||
d.SetDefaultSize(400, 300)
|
d.SetDefaultSize(400, 300)
|
||||||
d.SetTitle("Preferences")
|
d.SetTitle("Preferences")
|
||||||
d.SetTitlebar(h)
|
|
||||||
|
|
||||||
b, _ := d.GetContentArea()
|
|
||||||
b.SetMarginTop(8)
|
|
||||||
b.SetMarginBottom(8)
|
|
||||||
b.SetMarginStart(16)
|
|
||||||
b.SetMarginEnd(16)
|
|
||||||
b.PackStart(stack, true, true, 0)
|
|
||||||
b.Show()
|
|
||||||
|
|
||||||
return &Dialog{
|
return &Dialog{
|
||||||
Dialog: d,
|
Dialog: d,
|
||||||
|
|
|
@ -3,12 +3,13 @@ package dialog
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/gotk3/gotk3/glib"
|
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Dialog = gtk.Dialog
|
||||||
|
|
||||||
type Modal struct {
|
type Modal struct {
|
||||||
*gtk.Dialog
|
*Dialog
|
||||||
Cancel *gtk.Button
|
Cancel *gtk.Button
|
||||||
Action *gtk.Button
|
Action *gtk.Button
|
||||||
Header *gtk.HeaderBar
|
Header *gtk.HeaderBar
|
||||||
|
@ -26,20 +27,20 @@ func ShowModal(body gtk.IWidget, title, button string, clicked func(m *Modal)) {
|
||||||
|
|
||||||
func NewModal(body gtk.IWidget, title, button string, clicked func(m *Modal)) *Modal {
|
func NewModal(body gtk.IWidget, title, button string, clicked func(m *Modal)) *Modal {
|
||||||
cancel, _ := gtk.ButtonNewWithMnemonic("_Cancel")
|
cancel, _ := gtk.ButtonNewWithMnemonic("_Cancel")
|
||||||
cancel.Show()
|
|
||||||
cancel.SetHAlign(gtk.ALIGN_START)
|
cancel.SetHAlign(gtk.ALIGN_START)
|
||||||
cancel.SetRelief(gtk.RELIEF_NONE)
|
cancel.SetRelief(gtk.RELIEF_NONE)
|
||||||
|
cancel.Show()
|
||||||
|
|
||||||
action, _ := gtk.ButtonNewWithMnemonic(button)
|
action, _ := gtk.ButtonNewWithMnemonic(button)
|
||||||
action.Show()
|
|
||||||
action.SetHAlign(gtk.ALIGN_END)
|
action.SetHAlign(gtk.ALIGN_END)
|
||||||
action.SetRelief(gtk.RELIEF_NONE)
|
action.SetRelief(gtk.RELIEF_NONE)
|
||||||
|
action.Show()
|
||||||
|
|
||||||
header, _ := gtk.HeaderBarNew()
|
header, _ := gtk.HeaderBarNew()
|
||||||
header.Show()
|
|
||||||
header.SetTitle(title)
|
header.SetTitle(title)
|
||||||
header.PackStart(cancel)
|
header.PackStart(cancel)
|
||||||
header.PackEnd(action)
|
header.PackEnd(action)
|
||||||
|
header.Show()
|
||||||
|
|
||||||
primitives.AddClass(header, "modal-header")
|
primitives.AddClass(header, "modal-header")
|
||||||
primitives.AttachCSS(header, headerCSS)
|
primitives.AttachCSS(header, headerCSS)
|
||||||
|
@ -60,7 +61,7 @@ func NewModal(body gtk.IWidget, title, button string, clicked func(m *Modal)) *M
|
||||||
|
|
||||||
func NewCSD(body, header gtk.IWidget) *gtk.Dialog {
|
func NewCSD(body, header gtk.IWidget) *gtk.Dialog {
|
||||||
dialog := newCSD(body, header)
|
dialog := newCSD(body, header)
|
||||||
dialog.Connect("response", func(_ *glib.Object, resp gtk.ResponseType) {
|
dialog.Connect("response", func(_ *gtk.Dialog, resp gtk.ResponseType) {
|
||||||
if resp == gtk.RESPONSE_DELETE_EVENT {
|
if resp == gtk.RESPONSE_DELETE_EVENT {
|
||||||
dialog.Destroy()
|
dialog.Destroy()
|
||||||
}
|
}
|
||||||
|
@ -69,7 +70,10 @@ func NewCSD(body, header gtk.IWidget) *gtk.Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCSD(body, header gtk.IWidget) *gtk.Dialog {
|
func newCSD(body, header gtk.IWidget) *gtk.Dialog {
|
||||||
dialog, _ := gts.NewEmptyModalDialog()
|
dialog, err := gts.NewEmptyModalDialog()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
dialog.SetDefaultSize(450, 300)
|
dialog.SetDefaultSize(450, 300)
|
||||||
dialog.Add(body)
|
dialog.Add(body)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/completion"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/scrollinput"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/scrollinput"
|
||||||
"github.com/diamondburned/gspell"
|
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -93,6 +92,9 @@ func (v *InputView) SetMessenger(session cchat.Session, messenger cchat.Messenge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wrapSpellCheck is a no-op but is replaced by gspell in ./spellcheck.go.
|
||||||
|
var wrapSpellCheck = func(textView *gtk.TextView) {}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
// Box contains the field box and the attachment container.
|
// Box contains the field box and the attachment container.
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
|
@ -104,9 +106,8 @@ type Field struct {
|
||||||
Username *username.Container
|
Username *username.Container
|
||||||
|
|
||||||
TextScroll *gtk.ScrolledWindow
|
TextScroll *gtk.ScrolledWindow
|
||||||
text *gtk.TextView // const
|
text *gtk.TextView // const
|
||||||
speller *gspell.TextView // const
|
buffer *gtk.TextBuffer // const
|
||||||
buffer *gtk.TextBuffer // const
|
|
||||||
|
|
||||||
send *gtk.Button
|
send *gtk.Button
|
||||||
attach *gtk.Button
|
attach *gtk.Button
|
||||||
|
@ -150,8 +151,6 @@ var scrolledInputCSS = primitives.PrepareClassCSS("scrolled-input", `
|
||||||
func NewField(text *gtk.TextView, ctrl Controller) *Field {
|
func NewField(text *gtk.TextView, ctrl Controller) *Field {
|
||||||
field := &Field{text: text, ctrl: ctrl}
|
field := &Field{text: text, ctrl: ctrl}
|
||||||
field.buffer, _ = text.GetBuffer()
|
field.buffer, _ = text.GetBuffer()
|
||||||
field.speller = gspell.GetFromGtkTextView(text)
|
|
||||||
field.speller.BasicSetup()
|
|
||||||
|
|
||||||
field.Username = username.NewContainer()
|
field.Username = username.NewContainer()
|
||||||
field.Username.Show()
|
field.Username.Show()
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
// +build !nogspell
|
||||||
|
|
||||||
|
package input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/gspell"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
wrapSpellCheck = func(textView *gtk.TextView) {
|
||||||
|
speller := gspell.GetFromGtkTextView(textView)
|
||||||
|
speller.BasicSetup()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,267 +1,163 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html"
|
|
||||||
|
|
||||||
"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/ui/dialog"
|
"github.com/diamondburned/cchat-gtk/internal/ui/dialog"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/spinner"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
|
"github.com/diamondburned/handy"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dialog struct {
|
type Dialog struct {
|
||||||
*dialog.Modal
|
*dialog.Dialog
|
||||||
|
|
||||||
|
header *gtk.HeaderBar
|
||||||
|
backRev *gtk.Revealer
|
||||||
|
back *gtk.Button // might be hidden
|
||||||
|
|
||||||
|
stack *gtk.Stack
|
||||||
|
|
||||||
|
spinner *spinner.Boxed
|
||||||
|
leaflet *handy.Leaflet
|
||||||
|
|
||||||
|
stageList *StageList
|
||||||
|
request *RequestStack // might be nil
|
||||||
|
|
||||||
Auther cchat.Authenticator
|
Auther cchat.Authenticator
|
||||||
onAuth func(cchat.Session)
|
onAuth func(cchat.Session)
|
||||||
|
|
||||||
stack *gtk.Stack // dialog stack
|
|
||||||
scroll *gtk.ScrolledWindow
|
|
||||||
body *gtk.Box
|
|
||||||
label *gtk.Label
|
|
||||||
|
|
||||||
// filled on spin()
|
|
||||||
request *Request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDialog makes a new authentication dialog. Auth() is called when the user
|
// NewDialog makes a new authentication dialog. Auth() is called when the user
|
||||||
// is authenticated successfully inside the Gtk main thread.
|
// is authenticated successfully inside the Gtk main thread.
|
||||||
func NewDialog(name text.Rich, auther cchat.Authenticator, auth func(cchat.Session)) *Dialog {
|
func NewDialog(name text.Rich, authers []cchat.Authenticator, auth func(cchat.Session)) *Dialog {
|
||||||
label, _ := gtk.LabelNew("")
|
|
||||||
label.SetMarginStart(10)
|
|
||||||
label.SetMarginEnd(10)
|
|
||||||
label.SetMarginTop(10)
|
|
||||||
label.SetMarginBottom(10)
|
|
||||||
label.Show()
|
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
||||||
box.Show()
|
|
||||||
box.Add(label)
|
|
||||||
|
|
||||||
sw, _ := gtk.ScrolledWindowNew(nil, nil)
|
|
||||||
sw.Show()
|
|
||||||
sw.Add(box)
|
|
||||||
|
|
||||||
spinner, _ := gtk.SpinnerNew()
|
|
||||||
spinner.Show()
|
|
||||||
spinner.Start()
|
|
||||||
spinner.SetSizeRequest(50, 50)
|
|
||||||
|
|
||||||
stack, _ := gtk.StackNew()
|
|
||||||
stack.Show()
|
|
||||||
stack.SetVExpand(true)
|
|
||||||
stack.SetHExpand(true)
|
|
||||||
stack.AddNamed(sw, "main")
|
|
||||||
stack.AddNamed(spinner, "spinner")
|
|
||||||
|
|
||||||
d := &Dialog{
|
d := &Dialog{
|
||||||
Auther: auther,
|
Auther: nil,
|
||||||
onAuth: auth,
|
onAuth: auth,
|
||||||
stack: stack,
|
|
||||||
scroll: sw,
|
|
||||||
body: box,
|
|
||||||
label: label,
|
|
||||||
}
|
}
|
||||||
d.Modal = dialog.NewModal(stack, "Log in to "+name.Content, "Log in", d.ok)
|
|
||||||
d.Modal.SetDefaultSize(400, 300)
|
d.spinner = spinner.NewVisible()
|
||||||
d.spin(nil)
|
d.spinner.SetSizeRequest(50, 50)
|
||||||
d.Show()
|
d.spinner.Stop()
|
||||||
|
d.spinner.Show()
|
||||||
|
|
||||||
|
d.request = NewRequestStack()
|
||||||
|
d.request.SetHExpand(true)
|
||||||
|
d.request.SetName("request")
|
||||||
|
d.request.Show()
|
||||||
|
|
||||||
|
d.leaflet = handy.LeafletNew()
|
||||||
|
d.leaflet.SetCanSwipeBack(true)
|
||||||
|
d.leaflet.SetCanSwipeForward(false)
|
||||||
|
d.leaflet.SetVExpand(true)
|
||||||
|
d.leaflet.SetTransitionType(handy.LeafletTransitionTypeSlide)
|
||||||
|
d.leaflet.Show()
|
||||||
|
|
||||||
|
d.stack, _ = gtk.StackNew()
|
||||||
|
d.stack.SetVExpand(true)
|
||||||
|
d.stack.SetHExpand(true)
|
||||||
|
d.stack.AddNamed(d.leaflet, "leaflet")
|
||||||
|
d.stack.AddNamed(d.spinner, "spinner")
|
||||||
|
d.stack.SetVisibleChildName("leaflet")
|
||||||
|
d.stack.Show()
|
||||||
|
|
||||||
|
d.back, _ = gtk.ButtonNewFromIconName("go-previous-symbolic", gtk.ICON_SIZE_BUTTON)
|
||||||
|
d.back.Show()
|
||||||
|
d.back.Connect("clicked", func() {
|
||||||
|
// If check just in case.
|
||||||
|
if d.stageList != nil {
|
||||||
|
d.leaflet.SetVisibleChild(d.stageList)
|
||||||
|
d.backRev.SetRevealChild(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
d.backRev, _ = gtk.RevealerNew()
|
||||||
|
d.backRev.SetTransitionDuration(50)
|
||||||
|
d.backRev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
|
||||||
|
d.backRev.Add(d.back)
|
||||||
|
d.backRev.Show()
|
||||||
|
|
||||||
|
d.header, _ = gtk.HeaderBarNew()
|
||||||
|
d.header.SetShowCloseButton(true)
|
||||||
|
d.header.SetTitle("Log in to " + name.Content)
|
||||||
|
d.header.PackStart(d.backRev)
|
||||||
|
d.header.Show()
|
||||||
|
|
||||||
|
d.setAuthers(authers)
|
||||||
|
|
||||||
|
primitives.LeafletOnFold(d.leaflet, func(folded bool) {
|
||||||
|
visibleChildName := primitives.GetName(d.leaflet.GetVisibleChild().ToWidget())
|
||||||
|
|
||||||
|
if folded && visibleChildName == "request" {
|
||||||
|
d.backRev.SetRevealChild(true)
|
||||||
|
} else {
|
||||||
|
d.backRev.SetRevealChild(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
d.Dialog = dialog.NewCSD(d.stack, d.header)
|
||||||
|
d.Dialog.SetDefaultSize(500, 350)
|
||||||
|
d.Dialog.Show()
|
||||||
|
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialog) runOnAuth(ses cchat.Session) {
|
func (d *Dialog) setAuthers(authers []cchat.Authenticator) {
|
||||||
// finalize
|
primitives.RemoveChildren(d.leaflet)
|
||||||
d.Destroy()
|
|
||||||
d.onAuth(ses)
|
d.request.SetRequest(nil, nil)
|
||||||
|
|
||||||
|
d.stageList = NewStageList(authers, d.setAuther)
|
||||||
|
d.stageList.SetName("stagelist")
|
||||||
|
d.stageList.Show()
|
||||||
|
|
||||||
|
d.leaflet.Add(d.stageList)
|
||||||
|
d.leaflet.Add(d.request)
|
||||||
|
|
||||||
|
d.stageList.SelectFirst()
|
||||||
|
d.leaflet.SetVisibleChild(d.stageList)
|
||||||
|
d.backRev.SetRevealChild(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialog) spin(err error) {
|
func (d *Dialog) setAuther(auther cchat.Authenticator) {
|
||||||
// Print the error.
|
d.Auther = auther
|
||||||
if err != nil {
|
d.request.SetRequest(auther, d.onContinue)
|
||||||
d.label.SetMarkup(`<span color="red">` + html.EscapeString(err.Error()) + `</span>`)
|
d.backRev.SetRevealChild(d.leaflet.GetFolded())
|
||||||
} else {
|
d.leaflet.SetVisibleChild(d.request)
|
||||||
d.label.SetText("")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the old widget states.
|
|
||||||
d.stack.SetVisibleChildName("main")
|
|
||||||
d.Dialog.SetSensitive(true)
|
|
||||||
|
|
||||||
form := d.Auther.AuthenticateForm()
|
|
||||||
|
|
||||||
// See if we need to remove the current request page. We should keep
|
|
||||||
// everything the same if the key matches, as then forms aren't reset.
|
|
||||||
if d.request != nil {
|
|
||||||
if d.request.equalEntries(form) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
d.body.Remove(d.request)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.request = NewRequest(form)
|
|
||||||
d.body.Add(d.request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialog) ok(m *dialog.Modal) {
|
func (d *Dialog) onContinue() {
|
||||||
// Disable the buttons.
|
request := d.request.Request()
|
||||||
|
values := request.values()
|
||||||
|
auther := d.Auther
|
||||||
|
|
||||||
d.Dialog.SetSensitive(false)
|
d.Dialog.SetSensitive(false)
|
||||||
|
d.back.Hide()
|
||||||
// Switch to the spinner screen.
|
|
||||||
d.stack.SetVisibleChildName("spinner")
|
d.stack.SetVisibleChildName("spinner")
|
||||||
|
d.spinner.Start()
|
||||||
// Get the values of all fields.
|
|
||||||
var values = d.request.values()
|
|
||||||
|
|
||||||
gts.Async(func() (func(), error) {
|
gts.Async(func() (func(), error) {
|
||||||
s, err := d.Auther.Authenticate(values)
|
s, err := auther.Authenticate(values)
|
||||||
if err != nil {
|
|
||||||
return func() { d.spin(err) }, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return func() { d.runOnAuth(s) }, nil
|
return func() {
|
||||||
|
if err == nil {
|
||||||
|
d.Destroy()
|
||||||
|
d.onAuth(s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.spinner.Stop()
|
||||||
|
d.stack.SetVisibleChildName("leaflet")
|
||||||
|
d.back.Show()
|
||||||
|
d.Dialog.SetSensitive(true)
|
||||||
|
|
||||||
|
if nextStage := err.NextStage(); nextStage != nil {
|
||||||
|
d.setAuthers(nextStage)
|
||||||
|
} else {
|
||||||
|
request.SetError(err)
|
||||||
|
}
|
||||||
|
}, err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
*gtk.Grid
|
|
||||||
entries []cchat.AuthenticateEntry
|
|
||||||
labels []*gtk.Label
|
|
||||||
texts []Texter
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequest(authEntries []cchat.AuthenticateEntry) *Request {
|
|
||||||
grid, _ := gtk.GridNew()
|
|
||||||
grid.Show()
|
|
||||||
grid.SetRowSpacing(7)
|
|
||||||
grid.SetColumnHomogeneous(true)
|
|
||||||
grid.SetColumnSpacing(5)
|
|
||||||
|
|
||||||
req := &Request{
|
|
||||||
Grid: grid,
|
|
||||||
entries: authEntries,
|
|
||||||
labels: make([]*gtk.Label, len(authEntries)),
|
|
||||||
texts: make([]Texter, len(authEntries)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, authEntry := range req.entries {
|
|
||||||
label, texter := newEntry(authEntry)
|
|
||||||
|
|
||||||
req.labels[i] = label
|
|
||||||
req.texts[i] = texter
|
|
||||||
|
|
||||||
grid.Attach(label, 0, i, 1, 1)
|
|
||||||
grid.Attach(texter, 1, i, 3, 1) // triple the width
|
|
||||||
}
|
|
||||||
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
// equalEntries compares the current request with a list of entries. It returns
|
|
||||||
// false if there are inequalities.
|
|
||||||
func (r *Request) equalEntries(entries []cchat.AuthenticateEntry) bool {
|
|
||||||
if len(r.entries) != len(entries) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, entry := range r.entries {
|
|
||||||
if entry != entries[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Request) values() []string {
|
|
||||||
var values = make([]string, len(r.entries))
|
|
||||||
for i, texter := range r.texts {
|
|
||||||
values[i] = texter.GetText()
|
|
||||||
}
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEntry(authEntry cchat.AuthenticateEntry) (*gtk.Label, Texter) {
|
|
||||||
label, _ := gtk.LabelNew(authEntry.Name)
|
|
||||||
label.Show()
|
|
||||||
label.SetXAlign(1) // right align
|
|
||||||
label.SetJustify(gtk.JUSTIFY_RIGHT)
|
|
||||||
label.SetLineWrap(true)
|
|
||||||
|
|
||||||
var texter Texter
|
|
||||||
|
|
||||||
if authEntry.Multiline {
|
|
||||||
texter = NewMultilineInput()
|
|
||||||
} else {
|
|
||||||
var input = NewEntryInput()
|
|
||||||
if authEntry.Secret {
|
|
||||||
input.SetInputPurpose(gtk.INPUT_PURPOSE_PASSWORD)
|
|
||||||
input.SetVisibility(false)
|
|
||||||
input.SetInvisibleChar('●')
|
|
||||||
} else {
|
|
||||||
// usually; this is just an assumption
|
|
||||||
input.SetInputPurpose(gtk.INPUT_PURPOSE_EMAIL)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,271 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/singlestack"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/gotk3/gotk3/pango"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RequestStack struct {
|
||||||
|
*singlestack.Stack
|
||||||
|
request *Request
|
||||||
|
iconBox *gtk.Box
|
||||||
|
icon *gtk.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequestStack() *RequestStack {
|
||||||
|
icon, _ := gtk.ImageNewFromIconName("document-edit-symbolic", gtk.ICON_SIZE_DIALOG)
|
||||||
|
icon.SetHAlign(gtk.ALIGN_CENTER)
|
||||||
|
icon.SetVAlign(gtk.ALIGN_CENTER)
|
||||||
|
icon.SetHExpand(true)
|
||||||
|
icon.SetVExpand(true)
|
||||||
|
icon.SetOpacity(0.5)
|
||||||
|
icon.Show()
|
||||||
|
|
||||||
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
|
box.SetHExpand(true)
|
||||||
|
box.SetVExpand(true)
|
||||||
|
box.Add(icon)
|
||||||
|
box.Show()
|
||||||
|
|
||||||
|
stack := singlestack.NewStack()
|
||||||
|
stack.SetTransitionDuration(50)
|
||||||
|
stack.SetTransitionType(gtk.STACK_TRANSITION_TYPE_CROSSFADE)
|
||||||
|
stack.SetHExpand(true)
|
||||||
|
stack.Add(box)
|
||||||
|
|
||||||
|
return &RequestStack{
|
||||||
|
Stack: stack,
|
||||||
|
iconBox: box,
|
||||||
|
icon: icon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRequest sets the request into the stack. If auther is nil, then the
|
||||||
|
// placeholder icon is displayed. If auther is not nil, then Show() will be
|
||||||
|
// called.
|
||||||
|
func (rs *RequestStack) SetRequest(auther cchat.Authenticator, done func()) {
|
||||||
|
if auther == nil {
|
||||||
|
rs.request = nil
|
||||||
|
rs.Stack.Add(rs.iconBox)
|
||||||
|
} else {
|
||||||
|
rs.request = NewRequest(auther, done)
|
||||||
|
rs.request.Show()
|
||||||
|
rs.Stack.Add(rs.request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *RequestStack) Request() *Request {
|
||||||
|
return rs.request
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request is a single page of authenticator fields.
|
||||||
|
type Request struct {
|
||||||
|
*gtk.ScrolledWindow
|
||||||
|
Box *gtk.Box
|
||||||
|
Grid *gtk.Grid
|
||||||
|
ErrRev *gtk.Revealer
|
||||||
|
ErrLabel *gtk.Label
|
||||||
|
|
||||||
|
auther cchat.Authenticator
|
||||||
|
labels []*gtk.Label
|
||||||
|
texts []Texter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRequest(auther cchat.Authenticator, done func()) *Request {
|
||||||
|
authEntries := auther.AuthenticateForm()
|
||||||
|
|
||||||
|
errLabel, _ := gtk.LabelNew("")
|
||||||
|
errLabel.SetUseMarkup(true)
|
||||||
|
errLabel.SetMarginTop(8)
|
||||||
|
errLabel.SetMarginStart(8)
|
||||||
|
errLabel.SetMarginEnd(8)
|
||||||
|
errLabel.SetLineWrap(true)
|
||||||
|
errLabel.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
||||||
|
errLabel.Show()
|
||||||
|
|
||||||
|
errRev, _ := gtk.RevealerNew()
|
||||||
|
errRev.SetTransitionDuration(50)
|
||||||
|
errRev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_DOWN)
|
||||||
|
errRev.Add(errLabel)
|
||||||
|
errRev.SetRevealChild(false)
|
||||||
|
errRev.Show()
|
||||||
|
|
||||||
|
grid, _ := gtk.GridNew()
|
||||||
|
grid.SetRowSpacing(7)
|
||||||
|
grid.SetColumnHomogeneous(false)
|
||||||
|
grid.SetColumnSpacing(5)
|
||||||
|
grid.SetMarginStart(12)
|
||||||
|
grid.SetMarginEnd(12)
|
||||||
|
grid.SetMarginTop(8)
|
||||||
|
grid.Show()
|
||||||
|
|
||||||
|
continueBtn, _ := gtk.ButtonNewWithLabel("Continue")
|
||||||
|
continueBtn.SetHAlign(gtk.ALIGN_CENTER)
|
||||||
|
continueBtn.Connect("clicked", done)
|
||||||
|
continueBtn.SetBorderWidth(12)
|
||||||
|
continueBtn.Show()
|
||||||
|
|
||||||
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
|
box.PackStart(errRev, false, false, 0)
|
||||||
|
box.PackStart(grid, true, true, 0)
|
||||||
|
box.PackStart(continueBtn, false, false, 0)
|
||||||
|
box.Show()
|
||||||
|
|
||||||
|
sw, _ := gtk.ScrolledWindowNew(nil, nil)
|
||||||
|
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||||
|
sw.SetHExpand(true)
|
||||||
|
sw.SetVExpand(true)
|
||||||
|
sw.Add(box)
|
||||||
|
|
||||||
|
req := &Request{
|
||||||
|
ScrolledWindow: sw,
|
||||||
|
Box: box,
|
||||||
|
Grid: grid,
|
||||||
|
ErrRev: errRev,
|
||||||
|
ErrLabel: errLabel,
|
||||||
|
|
||||||
|
auther: auther,
|
||||||
|
labels: make([]*gtk.Label, len(authEntries)),
|
||||||
|
texts: make([]Texter, len(authEntries)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, authEntry := range authEntries {
|
||||||
|
label, texter := newEntry(authEntry)
|
||||||
|
|
||||||
|
req.labels[i] = label
|
||||||
|
req.texts[i] = texter
|
||||||
|
|
||||||
|
grid.Attach(label, 0, i, 1, 1)
|
||||||
|
grid.Attach(texter, 1, i, 1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetError prints the error. If err is nil, then the label is cleared.
|
||||||
|
func (r *Request) SetError(err error) {
|
||||||
|
var markup string
|
||||||
|
if err != nil {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
builder.WriteString(`<span color="red"><b>Error!</b>`)
|
||||||
|
builder.WriteByte('\n')
|
||||||
|
builder.WriteString(html.EscapeString(capitalizeFirst(err.Error())))
|
||||||
|
builder.WriteString(`</span>`)
|
||||||
|
markup = builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reveal if err is not nil.
|
||||||
|
r.ErrRev.SetRevealChild(err != nil)
|
||||||
|
r.ErrLabel.SetMarkup(markup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// capitalizeFirst capitalizes the first letter.
|
||||||
|
func capitalizeFirst(str string) string {
|
||||||
|
r, l := utf8.DecodeRuneInString(str)
|
||||||
|
if l > 0 {
|
||||||
|
return string(unicode.ToUpper(r)) + str[l:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Request) values() []string {
|
||||||
|
var values = make([]string, len(r.texts))
|
||||||
|
for i, texter := range r.texts {
|
||||||
|
values[i] = texter.GetText()
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEntry(authEntry cchat.AuthenticateEntry) (*gtk.Label, Texter) {
|
||||||
|
label, _ := gtk.LabelNew(authEntry.Name)
|
||||||
|
label.SetXAlign(1) // right align
|
||||||
|
label.SetJustify(gtk.JUSTIFY_RIGHT)
|
||||||
|
label.SetLineWrap(true)
|
||||||
|
label.Show()
|
||||||
|
|
||||||
|
var texter Texter
|
||||||
|
|
||||||
|
if authEntry.Multiline {
|
||||||
|
texter = NewMultilineInput()
|
||||||
|
} else {
|
||||||
|
var input = NewEntryInput()
|
||||||
|
if authEntry.Secret {
|
||||||
|
input.SetInputPurpose(gtk.INPUT_PURPOSE_PASSWORD)
|
||||||
|
input.SetVisibility(false)
|
||||||
|
input.SetInvisibleChar('●')
|
||||||
|
} else {
|
||||||
|
// usually; this is just an assumption
|
||||||
|
input.SetInputPurpose(gtk.INPUT_PURPOSE_EMAIL)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.SetHExpand(true)
|
||||||
|
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.SetHExpand(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)
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/handy"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StageList struct {
|
||||||
|
*gtk.ScrolledWindow
|
||||||
|
ListBox *gtk.ListBox
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStageList(authers []cchat.Authenticator, fn func(cchat.Authenticator)) *StageList {
|
||||||
|
list, _ := gtk.ListBoxNew()
|
||||||
|
list.SetSelectionMode(gtk.SELECTION_BROWSE)
|
||||||
|
list.SetActivateOnSingleClick(true)
|
||||||
|
list.Connect("row-activated", func(_ *gtk.ListBox, row *gtk.ListBoxRow) {
|
||||||
|
fn(authers[row.GetIndex()])
|
||||||
|
})
|
||||||
|
list.Show()
|
||||||
|
|
||||||
|
for _, auth := range authers {
|
||||||
|
row := handy.ActionRowNew()
|
||||||
|
row.SetActivatable(true)
|
||||||
|
row.SetTitle(auth.Name().String())
|
||||||
|
row.SetSubtitle(auth.Description().String())
|
||||||
|
row.Show()
|
||||||
|
|
||||||
|
list.Add(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
sw, _ := gtk.ScrolledWindowNew(nil, nil)
|
||||||
|
sw.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||||||
|
sw.SetSizeRequest(200, 0)
|
||||||
|
sw.SetHAlign(gtk.ALIGN_FILL)
|
||||||
|
sw.SetHExpand(false)
|
||||||
|
sw.Add(list)
|
||||||
|
|
||||||
|
return &StageList{
|
||||||
|
ScrolledWindow: sw,
|
||||||
|
ListBox: list,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (slist *StageList) SelectFirst() {
|
||||||
|
if first := slist.ListBox.GetRowAtIndex(0); first != nil {
|
||||||
|
first.Activate()
|
||||||
|
}
|
||||||
|
}
|
22
shell.nix
22
shell.nix
|
@ -1,26 +1,12 @@
|
||||||
{ pkgs ? import <nixpkgs> {} }:
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
let libhandy = pkgs.libhandy.overrideAttrs(old: {
|
pkgs.stdenv.mkDerivation rec {
|
||||||
name = "libhandy-0.90.0";
|
|
||||||
src = builtins.fetchGit {
|
|
||||||
url = "https://gitlab.gnome.org/GNOME/libhandy.git";
|
|
||||||
rev = "c7aaf6f4f50b64ee55fcfee84000e9525fc5f93a";
|
|
||||||
};
|
|
||||||
patches = [];
|
|
||||||
|
|
||||||
buildInputs = old.buildInputs ++ (with pkgs; [
|
|
||||||
gnome3.librsvg
|
|
||||||
gdk-pixbuf
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
in pkgs.stdenv.mkDerivation rec {
|
|
||||||
name = "cchat-gtk";
|
name = "cchat-gtk";
|
||||||
version = "0.0.2";
|
version = "0.0.2";
|
||||||
|
|
||||||
buildInputs =
|
buildInputs = with pkgs; [
|
||||||
[ libhandy ]
|
libhandy gnome3.gspell gnome3.glib gnome3.gtk
|
||||||
++ (with pkgs; [ gnome3.gspell gnome3.glib gnome3.gtk ]);
|
];
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
pkgconfig go
|
pkgconfig go
|
||||||
|
|
Loading…
Reference in New Issue