aerc

Working clone of aerc-mail.org
git clone git://git.danielmoch.com/aerc.git
Log | Files | Refs | README | LICENSE

msglist.go (4257B)


      1 package widgets
      2 
      3 import (
      4 	"log"
      5 
      6 	"github.com/gdamore/tcell"
      7 
      8 	"git.sr.ht/~sircmpwn/aerc/config"
      9 	"git.sr.ht/~sircmpwn/aerc/lib"
     10 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
     11 	"git.sr.ht/~sircmpwn/aerc/worker/types"
     12 )
     13 
     14 type MessageList struct {
     15 	ui.Invalidatable
     16 	conf     *config.AercConfig
     17 	logger   *log.Logger
     18 	height   int
     19 	scroll   int
     20 	selected int
     21 	nmsgs    int
     22 	spinner  *Spinner
     23 	store    *lib.MessageStore
     24 }
     25 
     26 func NewMessageList(conf *config.AercConfig, logger *log.Logger) *MessageList {
     27 	ml := &MessageList{
     28 		conf:     conf,
     29 		logger:   logger,
     30 		selected: 0,
     31 		spinner:  NewSpinner(),
     32 	}
     33 	ml.spinner.OnInvalidate(func(_ ui.Drawable) {
     34 		ml.Invalidate()
     35 	})
     36 	// TODO: stop spinner, probably
     37 	ml.spinner.Start()
     38 	return ml
     39 }
     40 
     41 func (ml *MessageList) Invalidate() {
     42 	ml.DoInvalidate(ml)
     43 }
     44 
     45 func (ml *MessageList) Draw(ctx *ui.Context) {
     46 	ml.height = ctx.Height()
     47 	ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault)
     48 
     49 	store := ml.Store()
     50 	if store == nil {
     51 		ml.spinner.Draw(ctx)
     52 		return
     53 	}
     54 
     55 	var (
     56 		needsHeaders []uint32
     57 		row          int = 0
     58 	)
     59 
     60 	for i := len(store.Uids) - 1 - ml.scroll; i >= 0; i-- {
     61 		uid := store.Uids[i]
     62 		msg := store.Messages[uid]
     63 
     64 		if row >= ctx.Height() {
     65 			break
     66 		}
     67 
     68 		if msg == nil {
     69 			needsHeaders = append(needsHeaders, uid)
     70 			ml.spinner.Draw(ctx.Subcontext(0, row, ctx.Width(), 1))
     71 			row += 1
     72 			continue
     73 		}
     74 
     75 		style := tcell.StyleDefault
     76 		if row == ml.selected-ml.scroll {
     77 			style = style.Foreground(tcell.ColorBlue).Reverse(true)
     78 		}
     79 		if _, ok := store.Deleted[msg.Uid]; ok {
     80 			style = style.Foreground(tcell.ColorGray)
     81 		}
     82 		ctx.Fill(0, row, ctx.Width(), 1, ' ', style)
     83 		ctx.Printf(0, row, style, "%s", msg.Envelope.Subject)
     84 
     85 		row += 1
     86 	}
     87 
     88 	if len(store.Uids) == 0 {
     89 		msg := ml.conf.Ui.EmptyMessage
     90 		ctx.Printf((ctx.Width()/2)-(len(msg)/2), 0,
     91 			tcell.StyleDefault, "%s", msg)
     92 	}
     93 
     94 	if len(needsHeaders) != 0 {
     95 		store.FetchHeaders(needsHeaders, nil)
     96 		ml.spinner.Start()
     97 	} else {
     98 		ml.spinner.Stop()
     99 	}
    100 }
    101 
    102 func (ml *MessageList) Height() int {
    103 	return ml.height
    104 }
    105 
    106 func (ml *MessageList) storeUpdate(store *lib.MessageStore) {
    107 	if ml.Store() != store {
    108 		return
    109 	}
    110 
    111 	if len(store.Uids) > 0 {
    112 		// When new messages come in, advance the cursor accordingly
    113 		// Note that this assumes new messages are appended to the top, which
    114 		// isn't necessarily true once we implement SORT... ideally we'd look
    115 		// for the previously selected UID.
    116 		if len(store.Uids) > ml.nmsgs && ml.nmsgs != 0 {
    117 			for i := 0; i < len(store.Uids)-ml.nmsgs; i++ {
    118 				ml.Next()
    119 			}
    120 		}
    121 		if len(store.Uids) < ml.nmsgs && ml.nmsgs != 0 {
    122 			for i := 0; i < ml.nmsgs-len(store.Uids); i++ {
    123 				ml.Prev()
    124 			}
    125 		}
    126 		ml.nmsgs = len(store.Uids)
    127 	}
    128 
    129 	ml.Invalidate()
    130 }
    131 
    132 func (ml *MessageList) SetStore(store *lib.MessageStore) {
    133 	if ml.Store() != store {
    134 		ml.scroll = 0
    135 		ml.selected = 0
    136 	}
    137 	ml.store = store
    138 	if store != nil {
    139 		ml.spinner.Stop()
    140 		ml.nmsgs = len(store.Uids)
    141 		store.OnUpdate(ml.storeUpdate)
    142 	} else {
    143 		ml.spinner.Start()
    144 	}
    145 	ml.Invalidate()
    146 }
    147 
    148 func (ml *MessageList) Store() *lib.MessageStore {
    149 	return ml.store
    150 }
    151 
    152 func (ml *MessageList) Empty() bool {
    153 	store := ml.Store()
    154 	return store == nil || len(store.Uids) == 0
    155 }
    156 
    157 func (ml *MessageList) Selected() *types.MessageInfo {
    158 	store := ml.Store()
    159 	return store.Messages[store.Uids[len(store.Uids)-ml.selected-1]]
    160 }
    161 
    162 func (ml *MessageList) Select(index int) {
    163 	store := ml.Store()
    164 
    165 	ml.selected = index
    166 	for ; ml.selected < 0; ml.selected = len(store.Uids) + ml.selected {
    167 	}
    168 	if ml.selected > len(store.Uids) {
    169 		ml.selected = len(store.Uids)
    170 	}
    171 	// I'm too lazy to do the math right now
    172 	for ml.selected-ml.scroll >= ml.Height() {
    173 		ml.scroll += 1
    174 	}
    175 	for ml.selected-ml.scroll < 0 {
    176 		ml.scroll -= 1
    177 	}
    178 }
    179 
    180 func (ml *MessageList) nextPrev(delta int) {
    181 	store := ml.Store()
    182 
    183 	if store == nil || len(store.Uids) == 0 {
    184 		return
    185 	}
    186 	ml.selected += delta
    187 	if ml.selected < 0 {
    188 		ml.selected = 0
    189 	}
    190 	if ml.selected >= len(store.Uids) {
    191 		ml.selected = len(store.Uids) - 1
    192 	}
    193 	if ml.Height() != 0 {
    194 		if ml.selected-ml.scroll >= ml.Height() {
    195 			ml.scroll += 1
    196 		} else if ml.selected-ml.scroll < 0 {
    197 			ml.scroll -= 1
    198 		}
    199 	}
    200 	ml.Invalidate()
    201 }
    202 
    203 func (ml *MessageList) Next() {
    204 	ml.nextPrev(1)
    205 }
    206 
    207 func (ml *MessageList) Prev() {
    208 	ml.nextPrev(-1)
    209 }