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/gotk3/gotk3/gtk" ) type Dialog struct { *gtk.Dialog 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 string, auther cchat.Authenticator, auth func(cchat.Session)) *Dialog { label, _ := gtk.LabelNew("") 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{ Auther: auther, onAuth: auth, stack: stack, scroll: sw, body: box, label: label, } d.Dialog = dialog.NewModal(stack, "Log in to "+name, "Log in", d.ok) d.Dialog.SetDefaultSize(400, 300) d.spin(nil) d.Show() return d } func (d *Dialog) runOnAuth(ses cchat.Session) { // finalize d.Destroy() d.onAuth(ses) } func (d *Dialog) spin(err error) { // Remove old request. if d.request != nil { d.body.Remove(d.request) } // Print the error. if err != nil { d.label.SetMarkup(`` + html.EscapeString(err.Error()) + ``) } else { d.label.SetText("") } // Restore the old widget states. d.stack.SetVisibleChildName("main") d.Dialog.SetSensitive(true) d.request = NewRequest(d.Auther.AuthenticateForm()) d.body.Add(d.request) } func (d *Dialog) ok() { // Disable the buttons. d.Dialog.SetSensitive(false) // Switch to the spinner screen. d.stack.SetVisibleChildName("spinner") // Get the values of all fields. var values = d.request.values() gts.Async(func() (func(), error) { s, err := d.Auther.Authenticate(values) if err != nil { return func() { d.spin(err) }, nil } return func() { d.runOnAuth(s) }, nil }) } type Request struct { *gtk.Grid labels []*gtk.Label entries []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, labels: make([]*gtk.Label, len(authEntries)), entries: make([]Texter, len(authEntries)), } for i, authEntry := range authEntries { label, entry := newEntry(authEntry) req.labels[i] = label req.entries[i] = entry grid.Attach(label, 0, i, 1, 1) grid.Attach(entry, 1, i, 3, 1) // triple the width } return req } func (r *Request) values() []string { var values = make([]string, len(r.entries)) for i, entry := range r.entries { values[i] = entry.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) }