added message selection, minor bug fixes, etc

This commit is contained in:
diamondburned 2021-01-02 20:52:30 -08:00
parent bfad966cd2
commit 150329e5dd
19 changed files with 365 additions and 109 deletions

2
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/Xuanwo/go-locale v1.0.0
github.com/alecthomas/chroma v0.7.3
github.com/diamondburned/cchat v0.3.17
github.com/diamondburned/cchat-discord v0.0.0-20210102085253-a691813b9041
github.com/diamondburned/cchat-discord v0.0.0-20210103044430-14970d0e05eb
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db
github.com/diamondburned/gspell v0.0.0-20201229064336-e43698fd5828
github.com/diamondburned/handy v0.0.0-20201229063418-ec23c1370374

2
go.sum
View File

@ -80,6 +80,8 @@ github.com/diamondburned/cchat-discord v0.0.0-20210102040711-73b0d3f39c41 h1:cUV
github.com/diamondburned/cchat-discord v0.0.0-20210102040711-73b0d3f39c41/go.mod h1:KL3i+ER58BrJ8JBkpy6WQ0mDZdlkgz7KWm3Ex7i6Mk0=
github.com/diamondburned/cchat-discord v0.0.0-20210102085253-a691813b9041 h1:ZTovoKIyXiK5VFRTVrY6YWHIFR5x98u9Q+k9rMjZzvg=
github.com/diamondburned/cchat-discord v0.0.0-20210102085253-a691813b9041/go.mod h1:KL3i+ER58BrJ8JBkpy6WQ0mDZdlkgz7KWm3Ex7i6Mk0=
github.com/diamondburned/cchat-discord v0.0.0-20210103044430-14970d0e05eb h1:IAitorXxVndnBGEW5uLcjjRLlpNBRPsUlwg9bUfyZLo=
github.com/diamondburned/cchat-discord v0.0.0-20210103044430-14970d0e05eb/go.mod h1:KL3i+ER58BrJ8JBkpy6WQ0mDZdlkgz7KWm3Ex7i6Mk0=
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db h1:VQI2PdbsdsRJ7d669kp35GbCUO44KZ0Xfqdu4o/oqVg=
github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db/go.mod h1:M87kjNzWVPlkZycFNzpGPKQXzkHNnZphuwMf3E9ckgc=
github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091 h1:lQpSWzbi3rQf66aMSip/rIypasIFwqCqF0Wfn5og6gw=

View File

@ -222,6 +222,11 @@ func EventIsRightClick(ev *gdk.Event) bool {
return keyev.Type() == gdk.EVENT_BUTTON_PRESS && keyev.Button() == gdk.BUTTON_SECONDARY
}
func EventIsLeftClick(ev *gdk.Event) bool {
keyev := gdk.EventButtonNewFromEvent(ev)
return keyev.Type() == gdk.EVENT_BUTTON_PRESS && keyev.Button() == gdk.BUTTON_PRIMARY
}
func SpawnUploader(dirpath string, callback func(absolutePaths []string)) {
dialog, _ := gtk.FileChooserNativeDialogNew(
"Upload File", App.Window,

View File

@ -4,8 +4,10 @@ import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
"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/labeluri"
"github.com/diamondburned/handy"
"github.com/gotk3/gotk3/gtk"
)
@ -19,6 +21,8 @@ type MessageRow interface {
Row() *gtk.ListBoxRow
// AttachMenu should override the stored constructor.
AttachMenu(items []menu.Item) // save memory
// MenuItems returns the list of attached menu items.
MenuItems() []menu.Item
// SetReferenceHighlighter sets the reference highlighter into the message.
SetReferenceHighlighter(refer labeluri.ReferenceHighlighter)
}
@ -63,6 +67,8 @@ type Container interface {
// Controller is for menu actions.
type Controller interface {
// Connector is used for button press events to unselect messages.
primitives.Connector
// BindMenu expects the controller to add actioner into the message.
BindMenu(MessageRow)
// Bottomed returns whether or not the message scroller is at the bottom.
@ -70,6 +76,10 @@ type Controller interface {
// AuthorEvent is called on message create/update. This is used to update
// the typer state.
AuthorEvent(a cchat.Author)
// SelectMessage is called when a message is selected.
SelectMessage(list *ListStore, msg MessageRow)
// UnselectMessage is called when the message selection is cleared.
UnselectMessage()
}
// Constructor is an interface for making custom message implementations which
@ -84,7 +94,10 @@ const ColumnSpacing = 8
// ListContainer is an implementation of Container, which allows flexible
// message grids.
type ListContainer struct {
*handy.Clamp
*ListStore
Controller
}
@ -97,8 +110,20 @@ type messageRow struct {
var _ Container = (*ListContainer)(nil)
func NewListContainer(constr Constructor, ctrl Controller) *ListContainer {
listStore := NewListStore(constr, ctrl)
listStore.ListBox.Show()
clamp := handy.ClampNew()
clamp.SetMaximumSize(800)
clamp.SetTighteningThreshold(600)
clamp.SetHExpand(true)
clamp.SetVExpand(true)
clamp.Add(listStore.ListBox)
clamp.Show()
return &ListContainer{
ListStore: NewListStore(constr, ctrl),
Clamp: clamp,
ListStore: listStore,
Controller: ctrl,
}
}
@ -123,3 +148,10 @@ func (c *ListContainer) CleanMessages() bool {
return false
}
func (c *ListContainer) SetFocusHAdjustment(adj *gtk.Adjustment) {
c.ListBox.SetFocusHAdjustment(adj)
}
func (c *ListContainer) SetFocusVAdjustment(adj *gtk.Adjustment) {
c.ListBox.SetFocusVAdjustment(adj)
}

View File

@ -48,8 +48,6 @@ type Container struct {
func NewContainer(ctrl container.Controller) *Container {
c := &Container{}
c.ListContainer = container.NewListContainer(c, ctrl)
// A not-so-generous row padding, as we will rely on margins per widget.
// c.ListContainer.Grid.SetRowSpacing(4)
primitives.AddClass(c, "cozy-container")
return c

View File

@ -26,7 +26,7 @@ var messageListCSS = primitives.PrepareClassCSS("message-list", `
`)
type ListStore struct {
*gtk.ListBox
ListBox *gtk.ListBox
Construct Constructor
Controller Controller
@ -39,17 +39,42 @@ type ListStore struct {
func NewListStore(constr Constructor, ctrl Controller) *ListStore {
listBox, _ := gtk.ListBoxNew()
listBox.SetSelectionMode(gtk.SELECTION_NONE)
listBox.SetSelectionMode(gtk.SELECTION_SINGLE)
listBox.Show()
messageListCSS(listBox)
return &ListStore{
listStore := ListStore{
ListBox: listBox,
Construct: constr,
Controller: ctrl,
messages: make(map[messageKey]*messageRow, BacklogLimit+1),
messageList: list.New(),
}
var selected bool
listBox.Connect("row-selected", func(listBox *gtk.ListBox, r *gtk.ListBoxRow) {
if r == nil || selected {
if selected {
listBox.UnselectAll()
selected = false
}
ctrl.UnselectMessage()
return
}
id, _ := r.GetName()
msg := listStore.Message(id, "")
if msg == nil {
return
}
selected = true
ctrl.SelectMessage(&listStore, msg)
})
return &listStore
}
func (c *ListStore) Reset() {
@ -267,6 +292,8 @@ func (c *ListStore) AddPresendMessage(msg input.PresendMessage) PresendMessageRo
}
func (c *ListStore) bindMessage(msgc *messageRow) {
// Bind the message ID to the row so we can easily do a lookup.
msgc.Row().SetName(msgc.ID())
msgc.SetReferenceHighlighter(c)
c.Controller.BindMenu(msgc.MessageRow)
}

View File

@ -14,12 +14,15 @@ import (
// const BreadcrumbSlash = `<span rise="-1024" size="x-large">❭</span>`
const BreadcrumbSlash = " 〉"
const iconSize = gtk.ICON_SIZE_BUTTON
type Header struct {
handy.HeaderBar
ShowBackBtn *gtk.Revealer
BackButton *gtk.Button
Breadcrumb *gtk.Label
MessageCtrl *MessageControl
ShowMembers *gtk.ToggleButton
breadcrumbs []string
@ -39,7 +42,7 @@ var rightBreadcrumbCSS = primitives.PrepareClassCSS("right-breadcrumb", `
`)
func NewHeader() *Header {
bk, _ := gtk.ButtonNewFromIconName("go-previous-symbolic", gtk.ICON_SIZE_BUTTON)
bk, _ := gtk.ButtonNewFromIconName("go-previous-symbolic", iconSize)
bk.SetVAlign(gtk.ALIGN_CENTER)
bk.Show()
backButtonCSS(bk)
@ -61,7 +64,11 @@ func NewHeader() *Header {
bc.Show()
rightBreadcrumbCSS(bc)
memberIcon, _ := gtk.ImageNewFromIconName("system-users-symbolic", gtk.ICON_SIZE_BUTTON)
msgctrl := NewMessageControl()
msgctrl.Disable()
msgctrl.Show()
memberIcon, _ := gtk.ImageNewFromIconName("system-users-symbolic", iconSize)
memberIcon.Show()
mb, _ := gtk.ToggleButtonNew()
@ -75,6 +82,7 @@ func NewHeader() *Header {
header.PackStart(rbk)
header.PackStart(bc)
header.PackEnd(mb)
header.PackEnd(msgctrl)
header.Show()
// Hack to hide the title.
@ -86,12 +94,14 @@ func NewHeader() *Header {
ShowBackBtn: rbk,
BackButton: bk,
Breadcrumb: bc,
MessageCtrl: msgctrl,
ShowMembers: mb,
}
}
func (h *Header) Reset() {
h.SetBreadcrumber(nil)
h.MessageCtrl.Disable()
}
func (h *Header) OnBackPressed(fn func()) {

View File

@ -11,6 +11,7 @@ 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/handy"
"github.com/gotk3/gotk3/gtk"
"github.com/pkg/errors"
)
@ -54,7 +55,7 @@ var textCSS = primitives.PrepareCSS(`
}
`)
var inputBoxCSS = primitives.PrepareClassCSS("input-box", `
var inputMainBoxCSS = primitives.PrepareClassCSS("input-box", `
.input-box {
background-color: @theme_bg_color;
}
@ -111,14 +112,20 @@ const (
editButtonIcon = "document-edit-symbolic"
replyButtonIcon = "mail-reply-sender-symbolic"
sendButtonSize = gtk.ICON_SIZE_BUTTON
ClampMaxSize = 1000
ClampThreshold = ClampMaxSize
)
type Field struct {
// Box contains the field box and the attachment container.
*gtk.Box
Clamp *handy.Clamp
// MainBox contains the field box and the attachment container.
MainBox *gtk.Box
Attachments *attachment.Container
// FieldBox contains the username container and the input field. It spans
// FieldMainBox contains the username container and the input field. It spans
// horizontally.
FieldBox *gtk.Box
Username *username.Container
@ -212,11 +219,22 @@ func NewField(text *gtk.TextView, ctrl Controller, labeler LabelBorrower) *Field
field.Attachments = attachment.New()
field.Attachments.Show()
field.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 2)
field.Box.PackStart(field.Attachments, false, false, 0)
field.Box.PackStart(field.FieldBox, false, false, 0)
field.MainBox, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 2)
field.MainBox.PackStart(field.Attachments, false, false, 0)
field.MainBox.PackStart(field.FieldBox, false, false, 0)
field.MainBox.Show()
field.Clamp = handy.ClampNew()
field.Clamp.SetMaximumSize(ClampMaxSize)
field.Clamp.SetTighteningThreshold(ClampThreshold)
field.Clamp.SetHExpand(true)
field.Clamp.Add(field.MainBox)
field.Clamp.Show()
field.Box, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
field.Box.Add(field.Clamp)
field.Box.Show()
inputBoxCSS(field.Box)
inputMainBoxCSS(field.Clamp)
text.SetFocusHAdjustment(field.TextScroll.GetHAdjustment())
text.SetFocusVAdjustment(field.TextScroll.GetVAdjustment())

View File

@ -67,7 +67,7 @@ type GenericContainer struct {
contentBox *gtk.Box // basically what is in Content
ContentBody *labeluri.Label
MenuItems []menu.Item
menuItems []menu.Item
}
var _ Container = (*GenericContainer)(nil)
@ -114,7 +114,7 @@ func NewEmptyContainer() *GenericContainer {
ctbody := labeluri.NewLabel(text.Rich{})
ctbody.SetVExpand(true)
ctbody.SetHExpand(true)
ctbody.SetHAlign(gtk.ALIGN_START)
ctbody.SetEllipsize(pango.ELLIPSIZE_NONE)
ctbody.SetLineWrap(true)
ctbody.SetLineWrapMode(pango.WRAP_WORD_CHAR)
@ -125,6 +125,7 @@ func NewEmptyContainer() *GenericContainer {
// Wrap the content label inside a content box.
ctbox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
ctbox.SetHExpand(true)
ctbox.PackStart(ctbody, false, false, 0)
ctbox.Show()
@ -161,7 +162,7 @@ func NewEmptyContainer() *GenericContainer {
// 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)
menu.MenuItems(m, gc.menuItems)
})
return gc
@ -248,7 +249,12 @@ func (m *GenericContainer) UpdateContent(content text.Rich, edited bool) {
// AttachMenu connects signal handlers to handle a list of menu items from
// the container.
func (m *GenericContainer) AttachMenu(newItems []menu.Item) {
m.MenuItems = newItems
m.menuItems = newItems
}
// MenuItems returns the list of menu items for this message.
func (m *GenericContainer) MenuItems() []menu.Item {
return m.menuItems
}
func (m *GenericContainer) Focusable() gtk.IWidget {

View File

@ -0,0 +1,107 @@
package messages
import (
"github.com/diamondburned/cchat-gtk/internal/ui/messages/container"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
)
type bindableButton struct {
gtk.Button
h glib.SignalHandle
}
func newBindableButton(iconName string) *bindableButton {
btn, _ := gtk.ButtonNewFromIconName(iconName, iconSize)
return &bindableButton{
Button: *btn,
}
}
func (btn *bindableButton) bind(fn func()) {
btn.unbind()
if fn != nil {
btn.h = btn.Connect("clicked", func(*gtk.Button) { fn() })
btn.SetSensitive(true)
btn.Show()
}
}
func (btn *bindableButton) unbind() {
if btn.h > 0 {
btn.HandlerDisconnect(btn.h)
btn.h = 0
btn.SetSensitive(false)
btn.Hide()
}
}
// MessageItemNames contains names that MessageControl will use for its menu
// action callbacks.
type MessageItemNames struct {
Reply, Edit, Delete string
}
// MessageControl controls buttons that control a selected message.
type MessageControl struct {
gtk.Revealer
Box *gtk.Box
hide bool
Reply *bindableButton
Edit *bindableButton
Delete *bindableButton // Actions "Delete"
}
func NewMessageControl() *MessageControl {
mc := MessageControl{}
mc.Reply = newBindableButton("mail-reply-sender-symbolic")
mc.Edit = newBindableButton("document-edit-symbolic")
mc.Delete = newBindableButton("edit-delete-symbolic")
mc.Box, _ = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 2)
mc.Box.Add(mc.Reply)
mc.Box.Add(mc.Edit)
mc.Box.Add(mc.Delete)
mc.Box.Show()
r, _ := gtk.RevealerNew()
mc.Revealer = *r
mc.Revealer.SetTransitionDuration(75)
mc.Revealer.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_CROSSFADE)
mc.Revealer.Add(mc.Box)
mc.Disable()
return &mc
}
// Enable enables the MessageControl with the given message.
func (mc *MessageControl) Enable(msg container.MessageRow, names MessageItemNames) {
mc.SetSensitive(true)
mc.SetRevealChild(true && !mc.hide)
items := msg.MenuItems()
mc.Reply.bind(menu.FindItemFunc(items, names.Reply))
mc.Edit.bind(menu.FindItemFunc(items, names.Edit))
mc.Delete.bind(menu.FindItemFunc(items, names.Delete))
}
// SetHidden sets whether or not the control should be hidden.
func (mc *MessageControl) SetHidden(hidden bool) {
mc.hide = hidden
}
// Disable disables the MessageControl and hides it.
func (mc *MessageControl) Disable() {
mc.SetSensitive(false)
mc.SetRevealChild(false)
mc.Reply.unbind()
mc.Edit.unbind()
mc.Delete.unbind()
}

View File

@ -0,0 +1,86 @@
package messages
import (
"time"
"github.com/diamondburned/cchat"
)
// ServerMessage combines Server and ServerMessage from cchat.
type ServerMessage interface {
cchat.Server
cchat.Messenger
}
type state struct {
session cchat.Session
server cchat.Server
actioner cchat.Actioner
backlogger cchat.Backlogger
current func() // stop callback
author string
lastBacklogged time.Time
}
func (s *state) Reset() {
// If we still have the last server to leave, then leave it.
if s.current != nil {
s.current()
}
// Lazy way to reset the state.
*s = state{}
}
func (s *state) hasActions() bool {
return s.actioner != nil
}
// SessionID returns the session ID, or an empty string if there's no session.
func (s *state) SessionID() string {
if s.session != nil {
return s.session.ID()
}
return ""
}
// ServerID returns the server ID, or an empty string if there's no server.
func (s *state) ServerID() string {
if s.server != nil {
return s.server.ID()
}
return ""
}
const backloggingFreq = time.Second * 3
// Backlogger returns the backlogger instance if it's allowed to fetch more
// backlogs.
func (s *state) Backlogger() cchat.Backlogger {
if s.backlogger == nil || s.current == nil {
return nil
}
var now = time.Now()
if s.lastBacklogged.Add(backloggingFreq).After(now) {
return nil
}
s.lastBacklogged = now
return s.backlogger
}
func (s *state) bind(session cchat.Session, server cchat.Server, msgr cchat.Messenger) {
s.session = session
s.server = server
s.actioner = msgr.AsActioner()
s.backlogger = msgr.AsBacklogger()
}
func (s *state) setcurrent(fn func()) {
s.current = fn
}

View File

@ -6,6 +6,7 @@ import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
"github.com/diamondburned/handy"
"github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/pango"
)
@ -33,10 +34,17 @@ var smallfonts = primitives.PrepareCSS(`
* { font-size: 0.9em; }
`)
const (
// Keep the same as input.
ClampMaxSize = 1000 - 6*2 // account for margin
ClampThreshold = ClampMaxSize
)
type Container struct {
*gtk.Revealer
state *State
clamp *handy.Clamp
dots *gtk.Box
label *gtk.Label
@ -62,11 +70,18 @@ func New() *Container {
b.PackStart(l, true, true, 0)
b.Show()
c := handy.ClampNew()
c.SetMaximumSize(ClampMaxSize)
c.SetTighteningThreshold(ClampThreshold)
c.SetHExpand(true)
c.Add(b)
c.Show()
r, _ := gtk.RevealerNew()
r.SetTransitionDuration(100)
r.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_CROSSFADE)
r.SetRevealChild(false)
r.Add(b)
r.Add(c)
typingIndicatorCSS(b)

View File

@ -55,8 +55,8 @@ type Controller interface {
type MessagesContainer interface {
gtk.IWidget
container.Container
cchat.MessagesContainer
container.Container
}
type View struct {
@ -250,11 +250,8 @@ func (v *View) reset() {
func (v *View) SetFolded(folded bool) {
v.parentFolded = folded
// Change to a mini breadcrumb if we're collapsed.
v.Header.SetMiniBreadcrumb(folded)
// Hide the username in the input bar if we're collapsed.
v.Header.MessageCtrl.SetHidden(folded)
v.InputView.Username.SetRevealChild(!folded)
// Hide the member list automatically on folded.
@ -266,7 +263,7 @@ func (v *View) SetFolded(folded bool) {
// MemberListUpdated is called everytime the member list is updated.
func (v *View) MemberListUpdated(c *memberlist.Container) {
// We can show the members list if it's not empty.
var empty = c.IsEmpty()
empty := c.IsEmpty()
v.Header.SetCanShowMembers(!empty)
// If the member list is now empty, then hide the entire thing.
@ -430,6 +427,12 @@ func (v *View) retryMessage(msg input.PresendMessage, presend container.PresendM
}()
}
var messageItemNames = MessageItemNames{
Reply: "Reply",
Edit: "Edit",
Delete: "Delete",
}
// BindMenu attaches the menu constructor into the message with the needed
// states and callbacks.
func (v *View) BindMenu(msg container.MessageRow) {
@ -475,81 +478,13 @@ func (v *View) makeActionItem(action, msgID string) menu.Item {
})
}
// ServerMessage combines Server and ServerMessage from cchat.
type ServerMessage interface {
cchat.Server
cchat.Messenger
// SelectMessage is called when a message is selected.
func (v *View) SelectMessage(_ *container.ListStore, msg container.MessageRow) {
// Hijack the message's action list to search for what we have above.
v.Header.MessageCtrl.Enable(msg, messageItemNames)
}
type state struct {
session cchat.Session
server cchat.Server
actioner cchat.Actioner
backlogger cchat.Backlogger
current func() // stop callback
author string
lastBacklogged time.Time
}
func (s *state) Reset() {
// If we still have the last server to leave, then leave it.
if s.current != nil {
s.current()
}
// Lazy way to reset the state.
*s = state{}
}
func (s *state) hasActions() bool {
return s.actioner != nil
}
// SessionID returns the session ID, or an empty string if there's no session.
func (s *state) SessionID() string {
if s.session != nil {
return s.session.ID()
}
return ""
}
// ServerID returns the server ID, or an empty string if there's no server.
func (s *state) ServerID() string {
if s.server != nil {
return s.server.ID()
}
return ""
}
const backloggingFreq = time.Second * 3
// Backlogger returns the backlogger instance if it's allowed to fetch more
// backlogs.
func (s *state) Backlogger() cchat.Backlogger {
if s.backlogger == nil || s.current == nil {
return nil
}
var now = time.Now()
if s.lastBacklogged.Add(backloggingFreq).After(now) {
return nil
}
s.lastBacklogged = now
return s.backlogger
}
func (s *state) bind(session cchat.Session, server cchat.Server, msgr cchat.Messenger) {
s.session = session
s.server = server
s.actioner = msgr.AsActioner()
s.backlogger = msgr.AsBacklogger()
}
func (s *state) setcurrent(fn func()) {
s.current = fn
// UnselectMessage is called when the message selection is cleared.
func (v *View) UnselectMessage() {
v.Header.MessageCtrl.Disable()
}

View File

@ -72,6 +72,17 @@ func MenuItems(menu MenuAppender, items []Item) {
}
}
// FindItemFunc iterates over the list of items and returns the first item with
// the matching name.
func FindItemFunc(items []Item, name string) func() {
for _, item := range items {
if item.Name == name {
return item.Func
}
}
return nil
}
type ToolbarInserter interface {
Insert(gtk.IToolItem, int)
}

View File

@ -27,8 +27,8 @@ import (
const (
AvatarSize = 96
PopoverWidth = 250
MaxWidth = 350
MaxHeight = 350
MaxWidth = 500
MaxHeight = 500
)
type WidgetConnector interface {

View File

@ -117,7 +117,7 @@ func (a AppendMap) Get(ind int) (tags string) {
// Borrowing appended's backing array to add prepended is probably fine, as
// the length of the actual appended slice is going to stay the same.
return string(append(appended, prepended...))
return string(append(prepended, appended...))
}
func (a *AppendMap) Finalize(strlen int) []int {

View File

@ -36,7 +36,7 @@ func Restore(conf Configurator) {
file := serviceFile(conf)
if err := config.UnmarshalFromFile(file, c); err != nil {
if err := config.UnmarshalFromFile(file, &c); err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal %s config", conf.Name())
}
@ -57,7 +57,7 @@ func Spawn(conf Configurator) error {
file := serviceFile(conf)
err = config.UnmarshalFromFile(file, c)
err = config.UnmarshalFromFile(file, &c)
err = errors.Wrapf(err, "failed to unmarshal %s config", conf.Name())
return func() {

View File

@ -114,9 +114,13 @@ func (s *Servers) SetServers(servers []cchat.Server) {
gts.ExecAsync(func() {
s.Children.SetServersUnsafe(servers)
if servers == nil {
if len(servers) == 0 {
s.ctrl.ClearMessenger()
return
}
// Reload all top-level nodes.
s.Children.LoadAll()
})
}

View File

@ -160,7 +160,7 @@ func (app *App) SessionSelected(svc *service.Service, ses *session.Row) {
func (app *App) ClearMessenger(ses *session.Row) {
if app.MessageView.SessionID() == ses.Session.ID() {
return
app.MessageView.Reset()
}
}