From 3b1bc60f4e8f11c3244097c714db15bbf34d806e Mon Sep 17 00:00:00 2001 From: Koni Marti Date: Mon, 2 Oct 2023 16:29:14 +0200 Subject: msgviewer: separate part switcher from viewer Separate part switcher from message viewer. The part switcher implementation was woven into the message viewer code. Decouple them to make the code more readable. Signed-off-by: Koni Marti Tested-by: Inwit Acked-by: Robin Jarry --- app/msgviewer.go | 170 +++---------------------------------------------- app/partswitcher.go | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 162 deletions(-) create mode 100644 app/partswitcher.go diff --git a/app/msgviewer.go b/app/msgviewer.go index 5da37552..2189779a 100644 --- a/app/msgviewer.go +++ b/app/msgviewer.go @@ -37,15 +37,6 @@ type MessageViewer struct { uiConfig *config.UIConfig } -type PartSwitcher struct { - parts []*PartViewer - selected int - alwaysShowMime bool - - height int - mv *MessageViewer -} - func NewMessageViewer( acct *AccountView, msg lib.MessageView, ) *MessageViewer { @@ -326,9 +317,7 @@ func (mv *MessageViewer) ToggleKeyPassthrough() bool { } func (mv *MessageViewer) SelectedMessagePart() *PartInfo { - switcher := mv.switcher - part := switcher.parts[switcher.selected] - + part := mv.switcher.SelectedPart() return &PartInfo{ Index: part.index, Msg: part.msg.MessageInfo(), @@ -338,47 +327,22 @@ func (mv *MessageViewer) SelectedMessagePart() *PartInfo { } func (mv *MessageViewer) AttachmentParts(all bool) []*PartInfo { - var attachments []*PartInfo - - for _, p := range mv.switcher.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 + return mv.switcher.AttachmentParts(all) } func (mv *MessageViewer) PreviousPart() { - switcher := mv.switcher - for { - switcher.selected-- - if switcher.selected < 0 { - switcher.selected = len(switcher.parts) - 1 - } - if switcher.parts[switcher.selected].part.MIMEType != "multipart" { - break - } + if mv.switcher == nil { + return } + mv.switcher.PreviousPart() mv.Invalidate() } func (mv *MessageViewer) NextPart() { - switcher := mv.switcher - for { - switcher.selected++ - if switcher.selected >= len(switcher.parts) { - switcher.selected = 0 - } - if switcher.parts[switcher.selected].part.MIMEType != "multipart" { - break - } + if mv.switcher == nil { + return } + mv.switcher.NextPart() mv.Invalidate() } @@ -396,124 +360,6 @@ func (mv *MessageViewer) Close() { } } -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) { - height := len(ps.parts) - if height == 1 && !config.Viewer.AlwaysShowMime { - ps.parts[ps.selected].Draw(ctx) - return - } - - var styleSwitcher, styleFile, styleMime tcell.Style - - // TODO: cap height and add scrolling for messages with many parts - ps.height = ctx.Height() - y := ctx.Height() - height - for i, part := range ps.parts { - if ps.selected == i { - styleSwitcher = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_SWITCHER) - styleFile = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_FILENAME) - styleMime = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_MIMETYPE) - } else { - styleSwitcher = ps.mv.uiConfig.GetStyle(config.STYLE_PART_SWITCHER) - styleFile = ps.mv.uiConfig.GetStyle(config.STYLE_PART_FILENAME) - styleMime = ps.mv.uiConfig.GetStyle(config.STYLE_PART_MIMETYPE) - } - ctx.Fill(0, y+i, 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, y+i, styleFile, "%s ", name) - } - t := "(" + part.part.FullMIMEType() + ")" - t = runewidth.Truncate(t, ctx.Width()-left, "…") - ctx.Printf(left, y+i, styleMime, "%s", t) - } - ps.parts[ps.selected].Draw(ctx.Subcontext( - 0, 0, ctx.Width(), ctx.Height()-height)) -} - -func (ps *PartSwitcher) MouseEvent(localX int, localY int, event tcell.Event) { - if event, ok := event.(*tcell.EventMouse); ok { - switch event.Buttons() { - case tcell.Button1: - height := len(ps.parts) - y := ps.height - height - if localY < y && ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.MouseEvent(localX, localY, event) - } - for i := range ps.parts { - if localY != y+i { - continue - } - if ps.parts[i].part.MIMEType == "multipart" { - continue - } - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(false) - } - ps.selected = i - ps.Invalidate() - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(true) - } - } - case tcell.WheelDown: - height := len(ps.parts) - y := ps.height - height - if localY < y && ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.MouseEvent(localX, localY, event) - } - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(false) - } - ps.mv.NextPart() - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(true) - } - case tcell.WheelUp: - height := len(ps.parts) - y := ps.height - height - if localY < y && ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.MouseEvent(localX, localY, event) - } - if ps.parts[ps.selected].term != nil { - ps.parts[ps.selected].term.Focus(false) - } - ps.mv.PreviousPart() - 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() - } -} - func (mv *MessageViewer) Event(event tcell.Event) bool { return mv.switcher.Event(event) } diff --git a/app/partswitcher.go b/app/partswitcher.go new file mode 100644 index 00000000..fa2f56b2 --- /dev/null +++ b/app/partswitcher.go @@ -0,0 +1,178 @@ +package app + +import ( + "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 { + parts []*PartViewer + selected int + alwaysShowMime bool + + height int + mv *MessageViewer +} + +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) { + height := len(ps.parts) + if height == 1 && !config.Viewer.AlwaysShowMime { + ps.parts[ps.selected].Draw(ctx) + return + } + + var styleSwitcher, styleFile, styleMime tcell.Style + + // TODO: cap height and add scrolling for messages with many parts + ps.height = ctx.Height() + y := ctx.Height() - height + for i, part := range ps.parts { + if ps.selected == i { + styleSwitcher = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_SWITCHER) + styleFile = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_FILENAME) + styleMime = ps.mv.uiConfig.GetStyleSelected(config.STYLE_PART_MIMETYPE) + } else { + styleSwitcher = ps.mv.uiConfig.GetStyle(config.STYLE_PART_SWITCHER) + styleFile = ps.mv.uiConfig.GetStyle(config.STYLE_PART_FILENAME) + styleMime = ps.mv.uiConfig.GetStyle(config.STYLE_PART_MIMETYPE) + } + ctx.Fill(0, y+i, 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, y+i, styleFile, "%s ", name) + } + t := "(" + part.part.FullMIMEType() + ")" + t = runewidth.Truncate(t, ctx.Width()-left, "…") + ctx.Printf(left, y+i, styleMime, "%s", t) + } + ps.parts[ps.selected].Draw(ctx.Subcontext( + 0, 0, ctx.Width(), ctx.Height()-height)) +} + +func (ps *PartSwitcher) MouseEvent(localX int, localY int, event tcell.Event) { + if event, ok := event.(*tcell.EventMouse); ok { + switch event.Buttons() { + case tcell.Button1: + height := len(ps.parts) + y := ps.height - height + if localY < y && ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.MouseEvent(localX, localY, event) + } + for i := range ps.parts { + if localY != y+i { + continue + } + if ps.parts[i].part.MIMEType == "multipart" { + continue + } + if ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.Focus(false) + } + ps.selected = i + ps.Invalidate() + if ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.Focus(true) + } + } + case tcell.WheelDown: + height := len(ps.parts) + y := ps.height - height + if localY < y && ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.MouseEvent(localX, localY, event) + } + if ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.Focus(false) + } + ps.mv.NextPart() + if ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.Focus(true) + } + case tcell.WheelUp: + height := len(ps.parts) + y := ps.height - height + if localY < y && ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.MouseEvent(localX, localY, event) + } + if ps.parts[ps.selected].term != nil { + ps.parts[ps.selected].term.Focus(false) + } + ps.mv.PreviousPart() + 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() + } +} -- cgit