package app
import (
"math"
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib/ui"
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
)
type PartSwitcher struct {
Scrollable
parts []*PartViewer
selected int
height int
offset int
uiConfig *config.UIConfig
}
func (ps *PartSwitcher) PreviousPart() {
for {
ps.selected--
if ps.selected < 0 {
ps.selected = len(ps.parts) - 1
}
if ps.parts[ps.selected].part.MIMEType != "multipart" {
break
}
}
}
func (ps *PartSwitcher) NextPart() {
for {
ps.selected++
if ps.selected >= len(ps.parts) {
ps.selected = 0
}
if ps.parts[ps.selected].part.MIMEType != "multipart" {
break
}
}
}
func (ps *PartSwitcher) SelectedPart() *PartViewer {
return ps.parts[ps.selected]
}
func (ps *PartSwitcher) AttachmentParts(all bool) []*PartInfo {
var attachments []*PartInfo
for _, p := range ps.parts {
if p.part.Disposition == "attachment" || (all && p.part.FileName() != "") {
pi := &PartInfo{
Index: p.index,
Msg: p.msg.MessageInfo(),
Part: p.part,
}
attachments = append(attachments, pi)
}
}
return attachments
}
func (ps *PartSwitcher) Invalidate() {
ui.Invalidate()
}
func (ps *PartSwitcher) Focus(focus bool) {
if ps.parts[ps.selected].term != nil {
ps.parts[ps.selected].term.Focus(focus)
}
}
func (ps *PartSwitcher) Show(visible bool) {
if ps.parts[ps.selected].term != nil {
ps.parts[ps.selected].term.Show(visible)
}
}
func (ps *PartSwitcher) Event(event tcell.Event) bool {
return ps.parts[ps.selected].Event(event)
}
func (ps *PartSwitcher) Draw(ctx *ui.Context) {
uiConfig := ps.uiConfig
n := len(ps.parts)
if n == 1 && !config.Viewer.AlwaysShowMime {
ps.parts[ps.selected].Draw(ctx)
return
}
ps.height = config.Viewer.MaxMimeHeight
if ps.height <= 0 || n < ps.height {
ps.height = n
}
if ps.height > ctx.Height()/2 {
ps.height = ctx.Height() / 2
}
ps.UpdateScroller(ps.height, n)
ps.EnsureScroll(ps.selected)
var styleSwitcher, styleFile, styleMime tcell.Style
scrollbarWidth := 0
if ps.NeedScrollbar() {
scrollbarWidth = 1
}
ps.offset = ctx.Height() - ps.height
y := ps.offset
row := ps.offset
ctx.Fill(0, y, ctx.Width(), ps.height, ' ', uiConfig.GetStyle(config.STYLE_PART_SWITCHER))
for i := ps.Scroll(); i < n; i++ {
part := ps.parts[i]
if ps.selected == i {
styleSwitcher = uiConfig.GetStyleSelected(config.STYLE_PART_SWITCHER)
styleFile = uiConfig.GetStyleSelected(config.STYLE_PART_FILENAME)
styleMime = uiConfig.GetStyleSelected(config.STYLE_PART_MIMETYPE)
} else {
styleSwitcher = uiConfig.GetStyle(config.STYLE_PART_SWITCHER)
styleFile = uiConfig.GetStyle(config.STYLE_PART_FILENAME)
styleMime = uiConfig.GetStyle(config.STYLE_PART_MIMETYPE)
}
ctx.Fill(0, row, ctx.Width(), 1, ' ', styleSwitcher)
left := len(part.index) * 2
if part.part.FileName() != "" {
name := runewidth.Truncate(part.part.FileName(),
ctx.Width()-left-1, "…")
left += ctx.Printf(left, row, styleFile, "%s ", name)
}
t := "(" + part.part.FullMIMEType() + ")"
t = runewidth.Truncate(t, ctx.Width()-left-scrollbarWidth, "…")
ctx.Printf(left, row, styleMime, "%s", t)
row++
if (i - ps.Scroll()) >= ps.height {
break
}
}
if ps.NeedScrollbar() {
ps.drawScrollbar(ctx.Subcontext(ctx.Width()-1, y, 1, ps.height))
}
ps.parts[ps.selected].Draw(ctx.Subcontext(
0, 0, ctx.Width(), ctx.Height()-ps.height))
}
func (ps *PartSwitcher) drawScrollbar(ctx *ui.Context) {
uiConfig := ps.uiConfig
gutterStyle := uiConfig.GetStyle(config.STYLE_MSGLIST_GUTTER)
pillStyle := uiConfig.GetStyle(config.STYLE_MSGLIST_PILL)
// gutter
ctx.Fill(0, 0, 1, ctx.Height(), ' ', gutterStyle)
// pill
pillSize := int(math.Ceil(float64(ctx.Height()) * ps.PercentVisible()))
pillOffset := int(math.Floor(float64(ctx.Height()) * ps.PercentScrolled()))
ctx.Fill(0, pillOffset, 1, pillSize, ' ', pillStyle)
}
func (ps *PartSwitcher) MouseEvent(localX int, localY int, event tcell.Event) {
if localY < ps.offset && ps.parts[ps.selected].term != nil {
ps.parts[ps.selected].term.MouseEvent(localX, localY, event)
return
}
e, ok := event.(*tcell.EventMouse)
if !ok {
return
}
if ps.parts[ps.selected].term != nil {
ps.parts[ps.selected].term.Focus(false)
}
switch e.Buttons() {
case tcell.Button1:
i := localY - ps.offset + ps.Scroll()
if i < 0 || i >= len(ps.parts) {
break
}
if ps.parts[i].part.MIMEType == "multipart" {
break
}
ps.selected = i
ps.Invalidate()
case tcell.WheelDown:
ps.NextPart()
ps.Invalidate()
case tcell.WheelUp:
ps.PreviousPart()
ps.Invalidate()
}
if ps.parts[ps.selected].term != nil {
ps.parts[ps.selected].term.Focus(true)
}
}
func (ps *PartSwitcher) Cleanup() {
for _, partViewer := range ps.parts {
partViewer.Cleanup()
}
}