2019-03-11 03:45:00 +00:00
|
|
|
package widgets
|
|
|
|
|
|
|
|
import (
|
2019-06-07 20:22:04 +00:00
|
|
|
"fmt"
|
2019-03-15 01:37:00 +00:00
|
|
|
"log"
|
|
|
|
|
|
|
|
"github.com/gdamore/tcell"
|
2019-06-07 20:22:04 +00:00
|
|
|
"github.com/mattn/go-runewidth"
|
2019-03-15 01:37:00 +00:00
|
|
|
|
2019-05-18 00:57:10 +00:00
|
|
|
"git.sr.ht/~sircmpwn/aerc/config"
|
|
|
|
"git.sr.ht/~sircmpwn/aerc/lib"
|
2019-07-21 20:01:51 +00:00
|
|
|
"git.sr.ht/~sircmpwn/aerc/lib/format"
|
2019-05-18 00:57:10 +00:00
|
|
|
"git.sr.ht/~sircmpwn/aerc/lib/ui"
|
2019-07-08 02:43:56 +00:00
|
|
|
"git.sr.ht/~sircmpwn/aerc/models"
|
2019-03-11 03:45:00 +00:00
|
|
|
)
|
|
|
|
|
2019-03-15 01:37:00 +00:00
|
|
|
type MessageList struct {
|
2019-04-27 16:47:59 +00:00
|
|
|
ui.Invalidatable
|
2019-07-31 07:50:07 +00:00
|
|
|
conf *config.AercConfig
|
|
|
|
logger *log.Logger
|
|
|
|
height int
|
|
|
|
scroll int
|
|
|
|
nmsgs int
|
|
|
|
spinner *Spinner
|
|
|
|
store *lib.MessageStore
|
|
|
|
isInitalizing bool
|
2019-09-05 22:32:36 +00:00
|
|
|
aerc *Aerc
|
2019-03-15 01:37:00 +00:00
|
|
|
}
|
|
|
|
|
2019-09-05 22:32:36 +00:00
|
|
|
func NewMessageList(conf *config.AercConfig, logger *log.Logger, aerc *Aerc) *MessageList {
|
2019-03-15 01:37:00 +00:00
|
|
|
ml := &MessageList{
|
2019-07-31 07:50:07 +00:00
|
|
|
conf: conf,
|
|
|
|
logger: logger,
|
2019-08-30 01:30:35 +00:00
|
|
|
spinner: NewSpinner(&conf.Ui),
|
2019-07-31 07:50:07 +00:00
|
|
|
isInitalizing: true,
|
2019-09-05 22:32:36 +00:00
|
|
|
aerc: aerc,
|
2019-03-15 01:37:00 +00:00
|
|
|
}
|
|
|
|
ml.spinner.OnInvalidate(func(_ ui.Drawable) {
|
|
|
|
ml.Invalidate()
|
|
|
|
})
|
|
|
|
// TODO: stop spinner, probably
|
|
|
|
ml.spinner.Start()
|
|
|
|
return ml
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ml *MessageList) Invalidate() {
|
2019-04-27 16:47:59 +00:00
|
|
|
ml.DoInvalidate(ml)
|
2019-03-15 01:37:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ml *MessageList) Draw(ctx *ui.Context) {
|
2019-03-16 01:41:18 +00:00
|
|
|
ml.height = ctx.Height()
|
2020-05-27 05:37:02 +00:00
|
|
|
ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ',
|
|
|
|
ml.aerc.SelectedAccount().UiConfig().GetStyle(config.STYLE_MSGLIST_DEFAULT))
|
2019-03-15 01:37:00 +00:00
|
|
|
|
2019-04-28 13:26:22 +00:00
|
|
|
store := ml.Store()
|
|
|
|
if store == nil {
|
2019-07-31 07:50:07 +00:00
|
|
|
if ml.isInitalizing {
|
|
|
|
ml.spinner.Draw(ctx)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
ml.spinner.Stop()
|
|
|
|
ml.drawEmptyMessage(ctx)
|
|
|
|
return
|
|
|
|
}
|
2019-03-15 01:37:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2019-03-15 01:51:29 +00:00
|
|
|
needsHeaders []uint32
|
2019-03-15 01:37:00 +00:00
|
|
|
row int = 0
|
|
|
|
)
|
2019-07-17 07:35:50 +00:00
|
|
|
uids := store.Uids()
|
2019-03-15 01:37:00 +00:00
|
|
|
|
2019-07-17 07:35:50 +00:00
|
|
|
for i := len(uids) - 1 - ml.scroll; i >= 0; i-- {
|
|
|
|
uid := uids[i]
|
2019-04-28 13:26:22 +00:00
|
|
|
msg := store.Messages[uid]
|
2019-03-15 02:19:04 +00:00
|
|
|
|
2019-03-15 01:37:00 +00:00
|
|
|
if row >= ctx.Height() {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg == nil {
|
|
|
|
needsHeaders = append(needsHeaders, uid)
|
|
|
|
ml.spinner.Draw(ctx.Subcontext(0, row, ctx.Width(), 1))
|
2019-03-15 02:19:04 +00:00
|
|
|
row += 1
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-05-27 05:37:02 +00:00
|
|
|
uiConfig := ml.conf.GetUiConfig(map[config.ContextType]string{
|
|
|
|
config.UI_CONTEXT_ACCOUNT: ml.aerc.SelectedAccount().AccountConfig().Name,
|
|
|
|
config.UI_CONTEXT_FOLDER: ml.aerc.SelectedAccount().Directories().Selected(),
|
|
|
|
config.UI_CONTEXT_SUBJECT: msg.Envelope.Subject,
|
|
|
|
})
|
|
|
|
|
|
|
|
so := config.STYLE_MSGLIST_DEFAULT
|
2019-06-08 13:10:14 +00:00
|
|
|
|
|
|
|
// deleted message
|
2019-04-28 13:26:22 +00:00
|
|
|
if _, ok := store.Deleted[msg.Uid]; ok {
|
2020-05-27 05:37:02 +00:00
|
|
|
so = config.STYLE_MSGLIST_DELETED
|
2019-03-30 14:40:55 +00:00
|
|
|
}
|
2019-06-08 13:10:14 +00:00
|
|
|
// unread message
|
|
|
|
seen := false
|
2020-05-27 05:37:02 +00:00
|
|
|
flaged := false
|
2019-06-08 13:10:14 +00:00
|
|
|
for _, flag := range msg.Flags {
|
2020-05-27 05:37:02 +00:00
|
|
|
switch flag {
|
|
|
|
case models.SeenFlag:
|
2019-06-08 13:10:14 +00:00
|
|
|
seen = true
|
2020-05-27 05:37:02 +00:00
|
|
|
case models.FlaggedFlag:
|
|
|
|
flaged = true
|
2019-06-08 13:10:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if !seen {
|
2020-05-27 05:37:02 +00:00
|
|
|
so = config.STYLE_MSGLIST_UNREAD
|
2019-06-08 13:10:14 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 05:37:02 +00:00
|
|
|
if flaged {
|
|
|
|
so = config.STYLE_MSGLIST_FLAGGED
|
|
|
|
}
|
|
|
|
|
|
|
|
// marked message
|
|
|
|
if store.IsMarked(msg.Uid) {
|
|
|
|
so = config.STYLE_MSGLIST_MARKED
|
|
|
|
}
|
2020-01-23 12:56:48 +00:00
|
|
|
|
2020-05-27 05:37:02 +00:00
|
|
|
style := uiConfig.GetStyle(so)
|
|
|
|
|
|
|
|
// current row
|
|
|
|
if row == ml.store.SelectedIndex()-ml.scroll {
|
|
|
|
style = uiConfig.GetStyleSelected(so)
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Fill(0, row, ctx.Width(), 1, ' ', style)
|
2019-07-21 20:01:51 +00:00
|
|
|
fmtStr, args, err := format.ParseMessageFormat(
|
2019-11-12 11:50:00 +00:00
|
|
|
ml.aerc.SelectedAccount().acct.From,
|
2020-01-23 12:56:48 +00:00
|
|
|
uiConfig.IndexFormat,
|
|
|
|
uiConfig.TimestampFormat, "", i, msg, store.IsMarked(uid))
|
2019-06-07 19:35:23 +00:00
|
|
|
if err != nil {
|
|
|
|
ctx.Printf(0, row, style, "%v", err)
|
|
|
|
} else {
|
2019-06-07 20:22:04 +00:00
|
|
|
line := fmt.Sprintf(fmtStr, args...)
|
|
|
|
line = runewidth.Truncate(line, ctx.Width(), "…")
|
|
|
|
ctx.Printf(0, row, style, "%s", line)
|
2019-06-07 19:35:23 +00:00
|
|
|
}
|
2019-03-15 01:37:00 +00:00
|
|
|
|
|
|
|
row += 1
|
|
|
|
}
|
|
|
|
|
2019-07-17 07:35:50 +00:00
|
|
|
if len(uids) == 0 {
|
2020-02-29 04:03:09 +00:00
|
|
|
if store.Sorting {
|
|
|
|
ml.spinner.Start()
|
|
|
|
ml.spinner.Draw(ctx)
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
ml.drawEmptyMessage(ctx)
|
|
|
|
}
|
2019-04-04 18:25:51 +00:00
|
|
|
}
|
|
|
|
|
2019-03-15 01:37:00 +00:00
|
|
|
if len(needsHeaders) != 0 {
|
2019-04-28 13:26:22 +00:00
|
|
|
store.FetchHeaders(needsHeaders, nil)
|
2019-03-15 01:37:00 +00:00
|
|
|
ml.spinner.Start()
|
|
|
|
} else {
|
|
|
|
ml.spinner.Stop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-05 22:32:36 +00:00
|
|
|
func (ml *MessageList) MouseEvent(localX int, localY int, event tcell.Event) {
|
|
|
|
switch event := event.(type) {
|
|
|
|
case *tcell.EventMouse:
|
|
|
|
switch event.Buttons() {
|
|
|
|
case tcell.Button1:
|
|
|
|
if ml.aerc == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
selectedMsg, ok := ml.Clicked(localX, localY)
|
|
|
|
if ok {
|
|
|
|
ml.Select(selectedMsg)
|
|
|
|
acct := ml.aerc.SelectedAccount()
|
|
|
|
if acct.Messages().Empty() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
store := acct.Messages().Store()
|
|
|
|
msg := acct.Messages().Selected()
|
|
|
|
if msg == nil {
|
|
|
|
return
|
|
|
|
}
|
2020-03-03 21:20:07 +00:00
|
|
|
lib.NewMessageStoreView(msg, store, ml.aerc.DecryptKeys,
|
2020-05-19 11:06:46 +00:00
|
|
|
func(view lib.MessageView, err error) {
|
|
|
|
if err != nil {
|
2020-05-27 05:52:13 +00:00
|
|
|
ml.aerc.PushError(err.Error())
|
2020-05-19 11:06:46 +00:00
|
|
|
return
|
|
|
|
}
|
2020-03-03 21:20:07 +00:00
|
|
|
viewer := NewMessageViewer(acct, ml.aerc.Config(), view)
|
|
|
|
ml.aerc.NewTab(viewer, msg.Envelope.Subject)
|
|
|
|
})
|
2019-09-05 22:32:36 +00:00
|
|
|
}
|
|
|
|
case tcell.WheelDown:
|
2020-02-22 16:05:46 +00:00
|
|
|
if ml.store != nil {
|
|
|
|
ml.store.Next()
|
|
|
|
}
|
2019-09-05 22:32:36 +00:00
|
|
|
ml.Scroll()
|
|
|
|
case tcell.WheelUp:
|
2020-02-22 16:05:46 +00:00
|
|
|
if ml.store != nil {
|
|
|
|
ml.store.Prev()
|
|
|
|
}
|
2019-09-05 22:32:36 +00:00
|
|
|
ml.Scroll()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ml *MessageList) Clicked(x, y int) (int, bool) {
|
|
|
|
store := ml.Store()
|
|
|
|
if store == nil || ml.nmsgs == 0 || y >= ml.nmsgs {
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
return y + ml.scroll, true
|
|
|
|
}
|
|
|
|
|
2019-03-16 01:41:18 +00:00
|
|
|
func (ml *MessageList) Height() int {
|
|
|
|
return ml.height
|
|
|
|
}
|
|
|
|
|
2019-03-21 03:23:38 +00:00
|
|
|
func (ml *MessageList) storeUpdate(store *lib.MessageStore) {
|
2019-04-28 13:26:22 +00:00
|
|
|
if ml.Store() != store {
|
2019-03-21 03:23:38 +00:00
|
|
|
return
|
|
|
|
}
|
2019-07-17 07:35:50 +00:00
|
|
|
uids := store.Uids()
|
2019-04-28 13:26:22 +00:00
|
|
|
|
2019-07-17 07:35:50 +00:00
|
|
|
if len(uids) > 0 {
|
2019-05-19 22:08:41 +00:00
|
|
|
// When new messages come in, advance the cursor accordingly
|
|
|
|
// Note that this assumes new messages are appended to the top, which
|
|
|
|
// isn't necessarily true once we implement SORT... ideally we'd look
|
|
|
|
// for the previously selected UID.
|
2019-07-17 07:35:50 +00:00
|
|
|
if len(uids) > ml.nmsgs && ml.nmsgs != 0 {
|
|
|
|
for i := 0; i < len(uids)-ml.nmsgs; i++ {
|
2019-06-11 14:08:44 +00:00
|
|
|
ml.Store().Next()
|
2019-05-19 22:08:41 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-17 07:35:50 +00:00
|
|
|
if len(uids) < ml.nmsgs && ml.nmsgs != 0 {
|
|
|
|
for i := 0; i < ml.nmsgs-len(uids); i++ {
|
2019-06-11 14:08:44 +00:00
|
|
|
ml.Store().Prev()
|
2019-05-19 22:21:02 +00:00
|
|
|
}
|
|
|
|
}
|
2019-07-17 07:35:50 +00:00
|
|
|
ml.nmsgs = len(uids)
|
2019-03-21 03:23:38 +00:00
|
|
|
}
|
2019-04-28 13:26:22 +00:00
|
|
|
|
2019-06-27 00:50:27 +00:00
|
|
|
ml.Scroll()
|
2019-03-21 03:23:38 +00:00
|
|
|
ml.Invalidate()
|
|
|
|
}
|
|
|
|
|
2019-03-16 01:36:06 +00:00
|
|
|
func (ml *MessageList) SetStore(store *lib.MessageStore) {
|
2019-05-19 22:09:16 +00:00
|
|
|
if ml.Store() != store {
|
2019-03-16 01:49:40 +00:00
|
|
|
ml.scroll = 0
|
|
|
|
}
|
2019-05-19 09:49:57 +00:00
|
|
|
ml.store = store
|
2019-03-15 01:37:00 +00:00
|
|
|
if store != nil {
|
|
|
|
ml.spinner.Stop()
|
2019-07-17 07:35:50 +00:00
|
|
|
ml.nmsgs = len(store.Uids())
|
2019-04-28 13:26:22 +00:00
|
|
|
store.OnUpdate(ml.storeUpdate)
|
2019-03-15 01:37:00 +00:00
|
|
|
} else {
|
|
|
|
ml.spinner.Start()
|
|
|
|
}
|
|
|
|
ml.Invalidate()
|
|
|
|
}
|
2019-03-15 03:41:25 +00:00
|
|
|
|
2019-07-31 07:50:07 +00:00
|
|
|
func (ml *MessageList) SetInitDone() {
|
|
|
|
ml.isInitalizing = false
|
|
|
|
}
|
|
|
|
|
2019-03-21 03:23:38 +00:00
|
|
|
func (ml *MessageList) Store() *lib.MessageStore {
|
2019-05-19 09:49:57 +00:00
|
|
|
return ml.store
|
2019-03-21 03:23:38 +00:00
|
|
|
}
|
|
|
|
|
2019-04-09 03:14:14 +00:00
|
|
|
func (ml *MessageList) Empty() bool {
|
2019-04-28 13:26:22 +00:00
|
|
|
store := ml.Store()
|
2019-07-17 07:35:50 +00:00
|
|
|
return store == nil || len(store.Uids()) == 0
|
2019-04-09 03:14:14 +00:00
|
|
|
}
|
|
|
|
|
2019-07-08 02:43:56 +00:00
|
|
|
func (ml *MessageList) Selected() *models.MessageInfo {
|
2019-04-28 13:26:22 +00:00
|
|
|
store := ml.Store()
|
2019-07-17 07:35:50 +00:00
|
|
|
uids := store.Uids()
|
|
|
|
return store.Messages[uids[len(uids)-ml.store.SelectedIndex()-1]]
|
2019-03-21 03:23:38 +00:00
|
|
|
}
|
|
|
|
|
2019-03-16 02:01:20 +00:00
|
|
|
func (ml *MessageList) Select(index int) {
|
2019-04-28 13:26:22 +00:00
|
|
|
store := ml.Store()
|
2019-06-11 05:05:55 +00:00
|
|
|
store.Select(index)
|
2019-06-27 00:52:34 +00:00
|
|
|
ml.Scroll()
|
2019-03-16 02:01:20 +00:00
|
|
|
}
|
|
|
|
|
2019-06-11 05:05:55 +00:00
|
|
|
func (ml *MessageList) Scroll() {
|
2019-04-28 13:26:22 +00:00
|
|
|
store := ml.Store()
|
2019-04-28 13:26:38 +00:00
|
|
|
|
2019-07-17 07:35:50 +00:00
|
|
|
if store == nil || len(store.Uids()) == 0 {
|
2019-03-22 01:00:03 +00:00
|
|
|
return
|
|
|
|
}
|
2019-03-16 01:49:40 +00:00
|
|
|
if ml.Height() != 0 {
|
2019-06-27 00:52:34 +00:00
|
|
|
// I'm too lazy to do the math right now
|
|
|
|
for store.SelectedIndex()-ml.scroll >= ml.Height() {
|
2019-03-16 01:49:40 +00:00
|
|
|
ml.scroll += 1
|
2019-06-27 00:52:34 +00:00
|
|
|
}
|
|
|
|
for store.SelectedIndex()-ml.scroll < 0 {
|
2019-03-16 01:49:40 +00:00
|
|
|
ml.scroll -= 1
|
|
|
|
}
|
|
|
|
}
|
2019-03-15 03:41:25 +00:00
|
|
|
ml.Invalidate()
|
|
|
|
}
|
2019-07-31 07:50:07 +00:00
|
|
|
|
|
|
|
func (ml *MessageList) drawEmptyMessage(ctx *ui.Context) {
|
2020-05-27 05:37:02 +00:00
|
|
|
uiConfig := ml.aerc.SelectedAccount().UiConfig()
|
|
|
|
msg := uiConfig.EmptyMessage
|
2019-07-31 07:50:07 +00:00
|
|
|
ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0,
|
2020-05-27 05:37:02 +00:00
|
|
|
uiConfig.GetStyle(config.STYLE_MSGLIST_DEFAULT), "%s", msg)
|
2019-07-31 07:50:07 +00:00
|
|
|
}
|