2020-06-06 07:44:36 +00:00
|
|
|
package message
|
2020-05-26 06:51:06 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/diamondburned/cchat"
|
|
|
|
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
2020-06-13 07:29:32 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
2020-07-17 00:21:14 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
|
2020-06-13 07:29:32 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
2020-07-14 07:24:55 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri"
|
2020-07-18 07:16:47 +00:00
|
|
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
|
2020-05-26 06:51:06 +00:00
|
|
|
"github.com/diamondburned/cchat/text"
|
|
|
|
"github.com/gotk3/gotk3/gtk"
|
|
|
|
"github.com/gotk3/gotk3/pango"
|
|
|
|
)
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
type Container interface {
|
|
|
|
ID() string
|
2020-10-24 21:58:48 +00:00
|
|
|
Time() time.Time
|
2020-06-06 07:44:36 +00:00
|
|
|
AuthorID() string
|
2020-12-31 03:00:00 +00:00
|
|
|
AuthorName() string
|
2021-01-02 09:24:14 +00:00
|
|
|
AuthorMarkup() string
|
2020-06-13 07:29:32 +00:00
|
|
|
AvatarURL() string // avatar
|
2020-06-06 07:44:36 +00:00
|
|
|
Nonce() string
|
|
|
|
|
2020-10-15 06:32:11 +00:00
|
|
|
UpdateAuthor(cchat.Author)
|
2020-06-06 07:44:36 +00:00
|
|
|
UpdateAuthorName(text.Rich)
|
2020-06-13 07:29:32 +00:00
|
|
|
UpdateContent(c text.Rich, edited bool)
|
2020-06-06 07:44:36 +00:00
|
|
|
UpdateTimestamp(time.Time)
|
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
// FillContainer sets the container's contents to the one from MessageCreate.
|
2020-06-07 04:27:28 +00:00
|
|
|
func FillContainer(c Container, msg cchat.MessageCreate) {
|
|
|
|
c.UpdateAuthor(msg.Author())
|
2020-06-13 07:29:32 +00:00
|
|
|
c.UpdateContent(msg.Content(), false)
|
2020-06-07 04:27:28 +00:00
|
|
|
c.UpdateTimestamp(msg.Time())
|
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
// RefreshContainer sets the container's contents to the one from
|
|
|
|
// GenericContainer. This is mainly used for transferring between different
|
|
|
|
// containers.
|
|
|
|
//
|
|
|
|
// Right now, this only works with Timestamp, as that's the only state tracked.
|
|
|
|
func RefreshContainer(c Container, gc *GenericContainer) {
|
|
|
|
c.UpdateTimestamp(gc.time)
|
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
// GenericContainer provides a single generic message container for subpackages
|
|
|
|
// to use.
|
|
|
|
type GenericContainer struct {
|
2021-01-02 09:24:14 +00:00
|
|
|
*gtk.Box
|
|
|
|
row *gtk.ListBoxRow // contains Box
|
|
|
|
class string
|
|
|
|
|
2020-12-31 03:00:00 +00:00
|
|
|
id string
|
|
|
|
time time.Time
|
|
|
|
authorID string
|
|
|
|
authorName string
|
|
|
|
avatarURL string // avatar
|
|
|
|
nonce string
|
2020-05-26 06:51:06 +00:00
|
|
|
|
|
|
|
Timestamp *gtk.Label
|
2020-07-14 07:24:55 +00:00
|
|
|
Username *labeluri.Label
|
2020-07-10 23:26:07 +00:00
|
|
|
Content gtk.IWidget // conceal widget implementation
|
|
|
|
|
|
|
|
contentBox *gtk.Box // basically what is in Content
|
2020-07-14 07:24:55 +00:00
|
|
|
ContentBody *labeluri.Label
|
2020-06-13 07:29:32 +00:00
|
|
|
|
2020-06-17 22:58:38 +00:00
|
|
|
MenuItems []menu.Item
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
var _ Container = (*GenericContainer)(nil)
|
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
var timestampCSS = primitives.PrepareClassCSS("message-time", `
|
2020-07-06 00:19:48 +00:00
|
|
|
.message-time {
|
|
|
|
opacity: 0.3;
|
|
|
|
font-size: 0.8em;
|
|
|
|
margin-top: 0.2em;
|
|
|
|
margin-bottom: 0.2em;
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
var authorCSS = primitives.PrepareClassCSS("message-author", `
|
|
|
|
.message-author {
|
|
|
|
color: mix(@theme_bg_color, @theme_fg_color, 0.8);
|
|
|
|
}
|
|
|
|
`)
|
|
|
|
|
2020-06-07 04:27:28 +00:00
|
|
|
// NewContainer creates a new message container with the given ID and nonce. It
|
|
|
|
// does not update the widgets, so FillContainer should be called afterwards.
|
2020-06-06 07:44:36 +00:00
|
|
|
func NewContainer(msg cchat.MessageCreate) *GenericContainer {
|
|
|
|
c := NewEmptyContainer()
|
|
|
|
c.id = msg.ID()
|
2020-06-13 07:29:32 +00:00
|
|
|
c.time = msg.Time()
|
2020-10-15 06:32:11 +00:00
|
|
|
c.nonce = msg.Nonce()
|
2020-06-07 04:27:28 +00:00
|
|
|
c.authorID = msg.Author().ID()
|
2020-06-04 23:00:41 +00:00
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
return c
|
2020-06-04 23:00:41 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
func NewEmptyContainer() *GenericContainer {
|
2020-05-26 06:51:06 +00:00
|
|
|
ts, _ := gtk.LabelNew("")
|
2020-06-13 07:29:32 +00:00
|
|
|
ts.SetEllipsize(pango.ELLIPSIZE_MIDDLE)
|
2021-01-02 09:24:14 +00:00
|
|
|
ts.SetXAlign(0.5) // centre align
|
2020-06-13 07:29:32 +00:00
|
|
|
ts.SetVAlign(gtk.ALIGN_END)
|
2020-05-28 19:26:55 +00:00
|
|
|
ts.Show()
|
2020-05-26 06:51:06 +00:00
|
|
|
|
2020-07-14 07:24:55 +00:00
|
|
|
user := labeluri.NewLabel(text.Rich{})
|
2021-01-02 09:24:14 +00:00
|
|
|
user.SetXAlign(0) // left align
|
2020-05-28 19:26:55 +00:00
|
|
|
user.SetVAlign(gtk.ALIGN_START)
|
2020-07-17 00:21:14 +00:00
|
|
|
user.SetTrackVisitedLinks(false)
|
2020-05-28 19:26:55 +00:00
|
|
|
user.Show()
|
2020-05-26 06:51:06 +00:00
|
|
|
|
2020-07-14 07:24:55 +00:00
|
|
|
ctbody := labeluri.NewLabel(text.Rich{})
|
|
|
|
ctbody.SetEllipsize(pango.ELLIPSIZE_NONE)
|
2020-07-10 23:26:07 +00:00
|
|
|
ctbody.SetLineWrap(true)
|
|
|
|
ctbody.SetLineWrapMode(pango.WRAP_WORD_CHAR)
|
|
|
|
ctbody.SetXAlign(0) // left align
|
|
|
|
ctbody.SetSelectable(true)
|
2020-07-17 00:21:14 +00:00
|
|
|
ctbody.SetTrackVisitedLinks(false)
|
2020-07-10 23:26:07 +00:00
|
|
|
ctbody.Show()
|
|
|
|
|
|
|
|
// Wrap the content label inside a content box.
|
|
|
|
ctbox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
|
|
ctbox.PackStart(ctbody, false, false, 0)
|
|
|
|
ctbox.Show()
|
2020-05-26 06:51:06 +00:00
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
|
|
|
box.Show()
|
2020-07-09 07:19:18 +00:00
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
row, _ := gtk.ListBoxRowNew()
|
|
|
|
row.Add(box)
|
|
|
|
row.Show()
|
2020-07-08 09:16:13 +00:00
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
// Add CSS classes.
|
|
|
|
primitives.AddClass(ts, "message-time")
|
2021-01-02 09:24:14 +00:00
|
|
|
primitives.AddClass(row, "message-row")
|
2020-06-13 07:29:32 +00:00
|
|
|
primitives.AddClass(user, "message-author")
|
2020-07-10 23:26:07 +00:00
|
|
|
primitives.AddClass(ctbody, "message-content")
|
2021-01-02 09:24:14 +00:00
|
|
|
timestampCSS(ts)
|
|
|
|
authorCSS(ts)
|
2020-07-06 00:19:48 +00:00
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
gc := &GenericContainer{
|
2021-01-02 09:24:14 +00:00
|
|
|
Box: box,
|
|
|
|
row: row,
|
|
|
|
|
2020-07-10 23:26:07 +00:00
|
|
|
Timestamp: ts,
|
|
|
|
Username: user,
|
|
|
|
Content: ctbox,
|
|
|
|
contentBox: ctbox,
|
|
|
|
ContentBody: ctbody,
|
2020-10-24 21:58:48 +00:00
|
|
|
|
|
|
|
// Time is important, as it is used to sort messages, so we have to be
|
|
|
|
// careful with this.
|
|
|
|
time: time.Now(),
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
2020-06-13 07:29:32 +00:00
|
|
|
|
2020-07-10 23:26:07 +00:00
|
|
|
// Bind the custom popup menu to the content label.
|
|
|
|
gc.ContentBody.Connect("populate-popup", func(l *gtk.Label, m *gtk.Menu) {
|
2020-06-28 01:35:26 +00:00
|
|
|
menu.MenuSeparator(m)
|
|
|
|
menu.MenuItems(m, gc.MenuItems)
|
2020-06-13 07:29:32 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
return gc
|
2020-06-04 23:00:41 +00:00
|
|
|
}
|
2020-05-26 06:51:06 +00:00
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
// Row returns the internal list box row. It is used to satisfy MessageRow.
|
|
|
|
func (m *GenericContainer) Row() *gtk.ListBoxRow { return m.row }
|
|
|
|
|
|
|
|
// SetClass sets the internal row's class.
|
|
|
|
func (m *GenericContainer) SetClass(class string) {
|
|
|
|
if m.class != "" {
|
|
|
|
primitives.RemoveClass(m.row, m.class)
|
|
|
|
}
|
|
|
|
|
|
|
|
primitives.AddClass(m.row, class)
|
|
|
|
m.class = class
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetReferenceHighlighter sets the reference highlighter into the message.
|
|
|
|
func (m *GenericContainer) SetReferenceHighlighter(r labeluri.ReferenceHighlighter) {
|
|
|
|
m.Username.SetReferenceHighlighter(r)
|
|
|
|
m.ContentBody.SetReferenceHighlighter(r)
|
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
func (m *GenericContainer) ID() string {
|
|
|
|
return m.id
|
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
func (m *GenericContainer) Time() time.Time {
|
|
|
|
return m.time
|
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
func (m *GenericContainer) AuthorID() string {
|
|
|
|
return m.authorID
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
|
|
|
|
2020-12-31 03:00:00 +00:00
|
|
|
func (m *GenericContainer) AuthorName() string {
|
|
|
|
return m.authorName
|
|
|
|
}
|
|
|
|
|
2021-01-02 09:24:14 +00:00
|
|
|
func (m *GenericContainer) AuthorMarkup() string {
|
|
|
|
return m.Username.Label.Label.GetLabel()
|
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
func (m *GenericContainer) AvatarURL() string {
|
|
|
|
return m.avatarURL
|
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
func (m *GenericContainer) Nonce() string {
|
|
|
|
return m.nonce
|
2020-05-28 19:26:55 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
func (m *GenericContainer) UpdateTimestamp(t time.Time) {
|
2020-06-13 07:29:32 +00:00
|
|
|
m.time = t
|
2020-07-06 00:19:48 +00:00
|
|
|
m.Timestamp.SetText(humanize.TimeAgo(t))
|
2020-06-04 23:00:41 +00:00
|
|
|
m.Timestamp.SetTooltipText(t.Format(time.Stamp))
|
|
|
|
}
|
|
|
|
|
2020-10-15 06:32:11 +00:00
|
|
|
func (m *GenericContainer) UpdateAuthor(author cchat.Author) {
|
2020-06-13 07:29:32 +00:00
|
|
|
m.authorID = author.ID()
|
2020-10-15 06:32:11 +00:00
|
|
|
m.avatarURL = author.Avatar()
|
2020-06-06 07:44:36 +00:00
|
|
|
m.UpdateAuthorName(author.Name())
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
|
|
|
|
2020-06-06 07:44:36 +00:00
|
|
|
func (m *GenericContainer) UpdateAuthorName(name text.Rich) {
|
2020-12-31 03:00:00 +00:00
|
|
|
cfg := markup.RenderConfig{}
|
2021-01-02 09:24:14 +00:00
|
|
|
cfg.NoReferencing = true
|
2020-12-31 03:00:00 +00:00
|
|
|
cfg.SetForegroundAnchor(m.ContentBody)
|
|
|
|
|
|
|
|
m.authorName = name.String()
|
|
|
|
m.Username.SetOutput(markup.RenderCmplxWithConfig(name, cfg))
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
func (m *GenericContainer) UpdateContent(content text.Rich, edited bool) {
|
2020-07-14 07:24:55 +00:00
|
|
|
m.ContentBody.SetLabelUnsafe(content)
|
|
|
|
|
2020-06-13 07:29:32 +00:00
|
|
|
if edited {
|
2020-07-14 07:24:55 +00:00
|
|
|
markup := m.ContentBody.Output().Markup
|
2020-06-28 01:35:26 +00:00
|
|
|
markup += " " + rich.Small("(edited)")
|
2020-07-14 07:24:55 +00:00
|
|
|
m.ContentBody.SetMarkup(markup)
|
2020-06-13 07:29:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AttachMenu connects signal handlers to handle a list of menu items from
|
|
|
|
// the container.
|
2020-06-17 22:58:38 +00:00
|
|
|
func (m *GenericContainer) AttachMenu(newItems []menu.Item) {
|
2020-06-13 07:29:32 +00:00
|
|
|
m.MenuItems = newItems
|
2020-05-26 06:51:06 +00:00
|
|
|
}
|
2020-08-20 23:53:13 +00:00
|
|
|
|
|
|
|
func (m *GenericContainer) Focusable() gtk.IWidget {
|
|
|
|
return m.Content
|
|
|
|
}
|