mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-11-16 19:22:51 +00:00
188 lines
4.9 KiB
Go
188 lines
4.9 KiB
Go
package message
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/diamondburned/cchat"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri"
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
|
|
"github.com/diamondburned/cchat/text"
|
|
"github.com/gotk3/gotk3/gtk"
|
|
"github.com/gotk3/gotk3/pango"
|
|
)
|
|
|
|
const AvatarSize = 40
|
|
|
|
// Container describes a message container that wraps a state. These methods are
|
|
// made for containers to override; methods not meant to be override are not
|
|
// exposed and will be done directly on the State.
|
|
type Container interface {
|
|
// Unwrap returns the internal message state.
|
|
Unwrap() *State
|
|
// Revert unwraps and reverts all widget changes to the internal state then
|
|
// returns that state.
|
|
Revert() *State
|
|
|
|
// UpdateContent updates the underlying content widget.
|
|
UpdateContent(content text.Rich, edited bool)
|
|
|
|
// SetReferenceHighlighter sets the reference highlighter into the message.
|
|
SetReferenceHighlighter(refer labeluri.ReferenceHighlighter)
|
|
}
|
|
|
|
// State provides a single generic message container for subpackages
|
|
// to use.
|
|
type State struct {
|
|
gtk.Box
|
|
Row *gtk.ListBoxRow // contains Box
|
|
class string
|
|
|
|
ID cchat.ID
|
|
Time time.Time
|
|
Nonce string
|
|
Author *Author
|
|
|
|
Content *gtk.Box
|
|
ContentBody *labeluri.Label
|
|
ContentBodyStyle *gtk.StyleContext
|
|
|
|
MenuItems []menu.Item
|
|
}
|
|
|
|
// NewState creates a new message state with the given MessageCreate.
|
|
func NewState(msg cchat.MessageCreate) *State {
|
|
author := msg.Author()
|
|
|
|
c := NewEmptyState()
|
|
c.Author.ID = author.ID()
|
|
c.Author.Name.QueueNamer(context.Background(), author)
|
|
c.ID = msg.ID()
|
|
c.Time = msg.Time()
|
|
c.Nonce = msg.Nonce()
|
|
c.UpdateContent(msg.Content(), false)
|
|
|
|
return c
|
|
}
|
|
|
|
// NewEmptyState creates a new empty message state. The author should be set
|
|
// immediately afterwards; it is invalid once the state is used.
|
|
func NewEmptyState() *State {
|
|
ctbody := labeluri.NewLabel(text.Rich{})
|
|
ctbody.Tooltip = false
|
|
ctbody.SetHAlign(gtk.ALIGN_FILL)
|
|
ctbody.SetEllipsize(pango.ELLIPSIZE_NONE)
|
|
ctbody.SetLineWrap(true)
|
|
ctbody.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
|
ctbody.SetXAlign(0) // left align
|
|
ctbody.SetSelectable(true)
|
|
ctbody.SetTrackVisitedLinks(false)
|
|
ctbody.Show()
|
|
|
|
ctbodyStyle, _ := ctbody.GetStyleContext()
|
|
ctbodyStyle.AddClass("message-content")
|
|
|
|
// Wrap the content label inside a content box.
|
|
ctbox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
ctbox.PackStart(ctbody, false, false, 0)
|
|
ctbox.SetHAlign(gtk.ALIGN_FILL)
|
|
ctbox.Show()
|
|
|
|
// Box that belongs to the implementations of messages.
|
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
|
box.Show()
|
|
|
|
row, _ := gtk.ListBoxRowNew()
|
|
row.Add(box)
|
|
row.Show()
|
|
primitives.AddClass(row, "message-row")
|
|
|
|
gc := &State{
|
|
Box: *box,
|
|
Row: row,
|
|
Author: &Author{},
|
|
|
|
Content: ctbox,
|
|
ContentBody: ctbody,
|
|
ContentBodyStyle: ctbodyStyle,
|
|
|
|
// Time is important, as it is used to sort messages, so we have to be
|
|
// careful with this.
|
|
Time: time.Now(),
|
|
}
|
|
|
|
// This may either work, or it may cause memory leaks.
|
|
row.Connect("destroy", func() { gc.Author.Name.Stop() })
|
|
|
|
// Bind the custom popup menu to the content label.
|
|
gc.ContentBody.Connect("populate-popup", func(l *gtk.Label, m *gtk.Menu) {
|
|
menu.MenuSeparator(m)
|
|
menu.MenuItems(m, gc.MenuItems)
|
|
})
|
|
|
|
return gc
|
|
}
|
|
|
|
// ClearBox clears the state's widget container.
|
|
func (m *State) ClearBox() {
|
|
primitives.RemoveChildren(m)
|
|
m.SetClass("")
|
|
}
|
|
|
|
// // For debugging use only.
|
|
// func (m *State) PackStart(child gtk.IWidget, expand bool, fill bool, padding uint) {
|
|
// paths := make([]string, 0, 5)
|
|
// for i := 1; i < 5; i++ {
|
|
// _, file, line, ok := runtime.Caller(i)
|
|
// if !ok {
|
|
// break
|
|
// }
|
|
//
|
|
// paths = append(paths, fmt.Sprintf("%s:%d", filepath.Base(file), line))
|
|
// }
|
|
//
|
|
// log.Println("child packstart", m.ID, "at", strings.Join(paths, " < "))
|
|
// m.Box.PackStart(child, expand, fill, padding)
|
|
// }
|
|
|
|
// SetClass sets the internal row's class.
|
|
func (m *State) SetClass(class string) {
|
|
if m.class != "" {
|
|
primitives.RemoveClass(m.Row, m.class)
|
|
}
|
|
|
|
if class != "" {
|
|
primitives.AddClass(m.Row, class)
|
|
}
|
|
|
|
m.class = class
|
|
}
|
|
|
|
// SetReferenceHighlighter sets the reference highlighter into the message.
|
|
func (m *State) SetReferenceHighlighter(r labeluri.ReferenceHighlighter) {
|
|
m.ContentBody.SetReferenceHighlighter(r)
|
|
}
|
|
|
|
// UpdateContent replaces the internal content and the widget.
|
|
func (m *State) UpdateContent(content text.Rich, edited bool) {
|
|
m.ContentBody.SetLabel(content)
|
|
|
|
if edited {
|
|
m.ContentBody.SetRenderer(func(content text.Rich) markup.RenderOutput {
|
|
output := markup.RenderCmplx(content)
|
|
output.Markup += rich.Small(text.Plain("(edited)")).Markup
|
|
return output
|
|
})
|
|
}
|
|
}
|
|
|
|
func (m *State) Focusable() gtk.IWidget {
|
|
return m.Content
|
|
}
|
|
|
|
// Unwrap returns itself.
|
|
func (m *State) Unwrap() *State { return m }
|