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 (
|
||||
github.com/Xuanwo/go-locale v1.0.0
|
||||
github.com/alecthomas/chroma v0.7.3
|
||||
github.com/diamondburned/cchat v0.3.7
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20201023215116-2209348d23bd
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20201023061026-155813b08c2c
|
||||
github.com/diamondburned/cchat v0.3.11
|
||||
github.com/diamondburned/cchat-discord v0.0.0-20201027213927-37165658e17c
|
||||
github.com/diamondburned/cchat-mock v0.0.0-20201027204251-4f6dfbfc2424
|
||||
github.com/diamondburned/gspell v0.0.0-20200830182722-77e5d27d6894
|
||||
github.com/diamondburned/handy v0.0.0-20200829011954-4667e7a918f4
|
||||
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.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.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/go.mod h1:+hSrIVYj5tIPLAorDsHj2Tbt2fWlZtOanzfEUHX53HM=
|
||||
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-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-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/go.mod h1:+bAf0m2o5qH54DmYJ/lR1HeITV53ol0JaoKyFFx3m3E=
|
||||
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-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-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/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
||||
github.com/diamondburned/gotk3 v0.0.0-20200816224505-3cd69b83a48a h1:wEldljb421/Jp84RNb0zBfqmiWt/TTQzUE6R1ap6UuQ=
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
package preferences
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||
"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/gotk3/gotk3/gtk"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Dialog struct {
|
||||
*gtk.Dialog
|
||||
*dialog.Dialog
|
||||
|
||||
switcher *gtk.StackSwitcher
|
||||
stack *gtk.Stack
|
||||
|
@ -18,6 +18,10 @@ type Dialog struct {
|
|||
|
||||
func NewDialog() *Dialog {
|
||||
stack, _ := gtk.StackNew()
|
||||
stack.SetMarginTop(8)
|
||||
stack.SetMarginBottom(8)
|
||||
stack.SetMarginStart(16)
|
||||
stack.SetMarginEnd(16)
|
||||
stack.Show()
|
||||
|
||||
switcher, _ := gtk.StackSwitcherNew()
|
||||
|
@ -29,18 +33,9 @@ func NewDialog() *Dialog {
|
|||
h.SetCustomTitle(switcher)
|
||||
h.Show()
|
||||
|
||||
d, _ := gts.NewModalDialog()
|
||||
d := dialog.NewCSD(stack, h)
|
||||
d.SetDefaultSize(400, 300)
|
||||
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{
|
||||
Dialog: d,
|
||||
|
|
|
@ -3,12 +3,13 @@ package dialog
|
|||
import (
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||
"github.com/gotk3/gotk3/glib"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
type Dialog = gtk.Dialog
|
||||
|
||||
type Modal struct {
|
||||
*gtk.Dialog
|
||||
*Dialog
|
||||
Cancel *gtk.Button
|
||||
Action *gtk.Button
|
||||
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 {
|
||||
cancel, _ := gtk.ButtonNewWithMnemonic("_Cancel")
|
||||
cancel.Show()
|
||||
cancel.SetHAlign(gtk.ALIGN_START)
|
||||
cancel.SetRelief(gtk.RELIEF_NONE)
|
||||
cancel.Show()
|
||||
|
||||
action, _ := gtk.ButtonNewWithMnemonic(button)
|
||||
action.Show()
|
||||
action.SetHAlign(gtk.ALIGN_END)
|
||||
action.SetRelief(gtk.RELIEF_NONE)
|
||||
action.Show()
|
||||
|
||||
header, _ := gtk.HeaderBarNew()
|
||||
header.Show()
|
||||
header.SetTitle(title)
|
||||
header.PackStart(cancel)
|
||||
header.PackEnd(action)
|
||||
header.Show()
|
||||
|
||||
primitives.AddClass(header, "modal-header")
|
||||
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 {
|
||||
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 {
|
||||
dialog.Destroy()
|
||||
}
|
||||
|
@ -69,7 +70,10 @@ 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.Add(body)
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"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/scrollinput"
|
||||
"github.com/diamondburned/gspell"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
"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 {
|
||||
// Box contains the field box and the attachment container.
|
||||
*gtk.Box
|
||||
|
@ -104,9 +106,8 @@ type Field struct {
|
|||
Username *username.Container
|
||||
|
||||
TextScroll *gtk.ScrolledWindow
|
||||
text *gtk.TextView // const
|
||||
speller *gspell.TextView // const
|
||||
buffer *gtk.TextBuffer // const
|
||||
text *gtk.TextView // const
|
||||
buffer *gtk.TextBuffer // const
|
||||
|
||||
send *gtk.Button
|
||||
attach *gtk.Button
|
||||
|
@ -150,8 +151,6 @@ var scrolledInputCSS = primitives.PrepareClassCSS("scrolled-input", `
|
|||
func NewField(text *gtk.TextView, ctrl Controller) *Field {
|
||||
field := &Field{text: text, ctrl: ctrl}
|
||||
field.buffer, _ = text.GetBuffer()
|
||||
field.speller = gspell.GetFromGtkTextView(text)
|
||||
field.speller.BasicSetup()
|
||||
|
||||
field.Username = username.NewContainer()
|
||||
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
|
||||
|
||||
import (
|
||||
"html"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||
"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/handy"
|
||||
"github.com/gotk3/gotk3/gtk"
|
||||
)
|
||||
|
||||
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
|
||||
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
|
||||
// is authenticated successfully inside the Gtk main thread.
|
||||
func NewDialog(name text.Rich, auther 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")
|
||||
|
||||
func NewDialog(name text.Rich, authers []cchat.Authenticator, auth func(cchat.Session)) *Dialog {
|
||||
d := &Dialog{
|
||||
Auther: auther,
|
||||
Auther: nil,
|
||||
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.spin(nil)
|
||||
d.Show()
|
||||
|
||||
d.spinner = spinner.NewVisible()
|
||||
d.spinner.SetSizeRequest(50, 50)
|
||||
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
|
||||
}
|
||||
|
||||
func (d *Dialog) runOnAuth(ses cchat.Session) {
|
||||
// finalize
|
||||
d.Destroy()
|
||||
d.onAuth(ses)
|
||||
func (d *Dialog) setAuthers(authers []cchat.Authenticator) {
|
||||
primitives.RemoveChildren(d.leaflet)
|
||||
|
||||
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) {
|
||||
// Print the error.
|
||||
if err != nil {
|
||||
d.label.SetMarkup(`<span color="red">` + html.EscapeString(err.Error()) + `</span>`)
|
||||
} else {
|
||||
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) setAuther(auther cchat.Authenticator) {
|
||||
d.Auther = auther
|
||||
d.request.SetRequest(auther, d.onContinue)
|
||||
d.backRev.SetRevealChild(d.leaflet.GetFolded())
|
||||
d.leaflet.SetVisibleChild(d.request)
|
||||
}
|
||||
|
||||
func (d *Dialog) ok(m *dialog.Modal) {
|
||||
// Disable the buttons.
|
||||
func (d *Dialog) onContinue() {
|
||||
request := d.request.Request()
|
||||
values := request.values()
|
||||
auther := d.Auther
|
||||
|
||||
d.Dialog.SetSensitive(false)
|
||||
|
||||
// Switch to the spinner screen.
|
||||
d.back.Hide()
|
||||
d.stack.SetVisibleChildName("spinner")
|
||||
|
||||
// Get the values of all fields.
|
||||
var values = d.request.values()
|
||||
d.spinner.Start()
|
||||
|
||||
gts.Async(func() (func(), error) {
|
||||
s, err := d.Auther.Authenticate(values)
|
||||
if err != nil {
|
||||
return func() { d.spin(err) }, nil
|
||||
}
|
||||
s, err := auther.Authenticate(values)
|
||||
|
||||
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> {} }:
|
||||
|
||||
let libhandy = pkgs.libhandy.overrideAttrs(old: {
|
||||
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 {
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
name = "cchat-gtk";
|
||||
version = "0.0.2";
|
||||
|
||||
buildInputs =
|
||||
[ libhandy ]
|
||||
++ (with pkgs; [ gnome3.gspell gnome3.glib gnome3.gtk ]);
|
||||
buildInputs = with pkgs; [
|
||||
libhandy gnome3.gspell gnome3.glib gnome3.gtk
|
||||
];
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
pkgconfig go
|
||||
|
|
Loading…
Reference in New Issue