about summary refs log tree commit diff stats
path: root/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'widgets')
-rw-r--r--widgets/account-wizard.go2
-rw-r--r--widgets/account.go11
-rw-r--r--widgets/aerc.go41
-rw-r--r--widgets/compose.go21
-rw-r--r--widgets/dirlist.go29
-rw-r--r--widgets/msglist.go45
-rw-r--r--widgets/msgviewer.go72
-rw-r--r--widgets/terminal.go14
8 files changed, 212 insertions, 23 deletions
diff --git a/widgets/account-wizard.go b/widgets/account-wizard.go
index 5acd26c..904013f 100644
--- a/widgets/account-wizard.go
+++ b/widgets/account-wizard.go
@@ -523,7 +523,7 @@ func (wizard *AccountWizard) finish(tutorial bool) {
 	}
 	wizard.conf.Accounts = append(wizard.conf.Accounts, account)
 
-	view := NewAccountView(wizard.conf, &account,
+	view := NewAccountView(wizard.aerc, wizard.conf, &account,
 		wizard.aerc.logger, wizard.aerc)
 	wizard.aerc.accounts[account.Name] = view
 	wizard.aerc.NewTab(view, account.Name)
diff --git a/widgets/account.go b/widgets/account.go
index 688b660..1220753 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -17,6 +17,7 @@ import (
 
 type AccountView struct {
 	acct    *config.AccountConfig
+	aerc    *Aerc
 	conf    *config.AercConfig
 	dirlist *DirectoryList
 	grid    *ui.Grid
@@ -26,7 +27,7 @@ type AccountView struct {
 	worker  *types.Worker
 }
 
-func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
+func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountConfig,
 	logger *log.Logger, host TabHost) *AccountView {
 
 	grid := ui.NewGrid().Rows([]ui.GridSpec{
@@ -42,6 +43,7 @@ func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
 			Color(tcell.ColorDefault, tcell.ColorRed)
 		return &AccountView{
 			acct:   acct,
+			aerc:   aerc,
 			grid:   grid,
 			host:   host,
 			logger: logger,
@@ -53,11 +55,12 @@ func NewAccountView(conf *config.AercConfig, acct *config.AccountConfig,
 		grid.AddChild(ui.NewBordered(dirlist, ui.BORDER_RIGHT))
 	}
 
-	msglist := NewMessageList(conf, logger)
+	msglist := NewMessageList(conf, logger, aerc)
 	grid.AddChild(msglist).At(0, 1)
 
 	view := &AccountView{
 		acct:    acct,
+		aerc:    aerc,
 		conf:    conf,
 		dirlist: dirlist,
 		grid:    grid,
@@ -124,6 +127,10 @@ func (acct *AccountView) Draw(ctx *ui.Context) {
 	acct.grid.Draw(ctx)
 }
 
+func (acct *AccountView) MouseEvent(localX int, localY int, event tcell.Event) {
+	acct.grid.MouseEvent(localX, localY, event)
+}
+
 func (acct *AccountView) Focus(focus bool) {
 	// TODO: Unfocus children I guess
 }
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 87009cd..fe3c1e2 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -74,7 +74,7 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
 	conf.Triggers.ExecuteCommand = cmd
 
 	for i, acct := range conf.Accounts {
-		view := NewAccountView(conf, &conf.Accounts[i], logger, aerc)
+		view := NewAccountView(aerc, conf, &conf.Accounts[i], logger, aerc)
 		aerc.accounts[acct.Name] = view
 		tabs.Add(view, acct.Name)
 	}
@@ -85,6 +85,22 @@ func NewAerc(conf *config.AercConfig, logger *log.Logger,
 		aerc.NewTab(wizard, "New account")
 	}
 
+	tabs.CloseTab = func(index int) {
+		switch content := aerc.tabs.Tabs[index].Content.(type) {
+		case *AccountView:
+			return
+		case *AccountWizard:
+			return
+		case *Composer:
+			aerc.RemoveTab(content)
+			content.Close()
+		case *Terminal:
+			content.Close(nil)
+		case *MessageViewer:
+			aerc.RemoveTab(content)
+		}
+	}
+
 	return aerc
 }
 
@@ -235,7 +251,12 @@ func (aerc *Aerc) Event(event tcell.Event) bool {
 			return false
 		}
 	case *tcell.EventMouse:
-		aerc.tabs.MouseEvent(event)
+		if event.Buttons() == tcell.ButtonNone {
+			return false
+		}
+		x, y := event.Position()
+		aerc.grid.MouseEvent(x, y, event)
+		return true
 	}
 	return false
 }
@@ -260,8 +281,8 @@ func (aerc *Aerc) SelectedTab() ui.Drawable {
 	return aerc.tabs.Tabs[aerc.tabs.Selected].Content
 }
 
-func (aerc *Aerc) NewTab(drawable ui.Drawable, name string) *ui.Tab {
-	tab := aerc.tabs.Add(drawable, name)
+func (aerc *Aerc) NewTab(clickable ui.Drawable, name string) *ui.Tab {
+	tab := aerc.tabs.Add(clickable, name)
 	aerc.tabs.Select(len(aerc.tabs.Tabs) - 1)
 	return tab
 }
@@ -275,19 +296,11 @@ func (aerc *Aerc) ReplaceTab(tabSrc ui.Drawable, tabTarget ui.Drawable, name str
 }
 
 func (aerc *Aerc) NextTab() {
-	next := aerc.tabs.Selected + 1
-	if next >= len(aerc.tabs.Tabs) {
-		next = 0
-	}
-	aerc.tabs.Select(next)
+	aerc.tabs.NextTab()
 }
 
 func (aerc *Aerc) PrevTab() {
-	next := aerc.tabs.Selected - 1
-	if next < 0 {
-		next = len(aerc.tabs.Tabs) - 1
-	}
-	aerc.tabs.Select(next)
+	aerc.tabs.PrevTab()
 }
 
 func (aerc *Aerc) SelectTab(name string) bool {
diff --git a/widgets/compose.go b/widgets/compose.go
index bd4301a..0e7f09e 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -40,10 +40,12 @@ type Composer struct {
 	worker      *types.Worker
 
 	layout    HeaderLayout
-	focusable []ui.DrawableInteractive
+	focusable []ui.MouseableDrawableInteractive
 	focused   int
 
 	onClose []func(ti *Composer)
+
+	width int
 }
 
 func NewComposer(conf *config.AercConfig,
@@ -87,10 +89,10 @@ func NewComposer(conf *config.AercConfig,
 func buildComposeHeader(layout HeaderLayout, defaults map[string]string) (
 	newLayout HeaderLayout,
 	editors map[string]*headerEditor,
-	focusable []ui.DrawableInteractive,
+	focusable []ui.MouseableDrawableInteractive,
 ) {
 	editors = make(map[string]*headerEditor)
-	focusable = make([]ui.DrawableInteractive, 0)
+	focusable = make([]ui.MouseableDrawableInteractive, 0)
 
 	for _, row := range layout {
 		for _, h := range row {
@@ -99,7 +101,7 @@ func buildComposeHeader(layout HeaderLayout, defaults map[string]string) (
 			switch h {
 			case "From":
 				// Prepend From to support backtab
-				focusable = append([]ui.DrawableInteractive{e}, focusable...)
+				focusable = append([]ui.MouseableDrawableInteractive{e}, focusable...)
 			default:
 				focusable = append(focusable, e)
 			}
@@ -176,6 +178,7 @@ func (c *Composer) OnClose(fn func(composer *Composer)) {
 }
 
 func (c *Composer) Draw(ctx *ui.Context) {
+	c.width = ctx.Width()
 	c.grid.Draw(ctx)
 }
 
@@ -617,6 +620,16 @@ func (he *headerEditor) Draw(ctx *ui.Context) {
 	he.input.Draw(ctx.Subcontext(size, 0, ctx.Width()-size, 1))
 }
 
+func (he *headerEditor) MouseEvent(localX int, localY int, event tcell.Event) {
+	switch event := event.(type) {
+	case *tcell.EventMouse:
+		width := runewidth.StringWidth(he.name + " ")
+		if localX >= width {
+			he.input.MouseEvent(localX-width, localY, event)
+		}
+	}
+}
+
 func (he *headerEditor) Invalidate() {
 	he.input.Invalidate()
 }
diff --git a/widgets/dirlist.go b/widgets/dirlist.go
index 33119dd..ec73082 100644
--- a/widgets/dirlist.go
+++ b/widgets/dirlist.go
@@ -137,6 +137,35 @@ func (dirlist *DirectoryList) Draw(ctx *ui.Context) {
 	}
 }
 
+func (dirlist *DirectoryList) MouseEvent(localX int, localY int, event tcell.Event) {
+	switch event := event.(type) {
+	case *tcell.EventMouse:
+		switch event.Buttons() {
+		case tcell.Button1:
+			clickedDir, ok := dirlist.Clicked(localX, localY)
+			if ok {
+				dirlist.Select(clickedDir)
+			}
+		case tcell.WheelDown:
+			dirlist.Next()
+		case tcell.WheelUp:
+			dirlist.Prev()
+		}
+	}
+}
+
+func (dirlist *DirectoryList) Clicked(x int, y int) (string, bool) {
+	if dirlist.dirs == nil || len(dirlist.dirs) == 0 {
+		return "", false
+	}
+	for i, name := range dirlist.dirs {
+		if i == y {
+			return name, true
+		}
+	}
+	return "", false
+}
+
 func (dirlist *DirectoryList) NextPrev(delta int) {
 	curIdx := sort.SearchStrings(dirlist.dirs, dirlist.selected)
 	if curIdx == len(dirlist.dirs) {
diff --git a/widgets/msglist.go b/widgets/msglist.go
index 8ed716b..b7c921c 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -25,6 +25,7 @@ type MessageList struct {
 	spinner       *Spinner
 	store         *lib.MessageStore
 	isInitalizing bool
+	aerc          *Aerc
 }
 
 type msgSorter struct {
@@ -55,12 +56,13 @@ func (s *msgSorter) Swap(i, j int) {
 	s.uids[j] = tmp
 }
 
-func NewMessageList(conf *config.AercConfig, logger *log.Logger) *MessageList {
+func NewMessageList(conf *config.AercConfig, logger *log.Logger, aerc *Aerc) *MessageList {
 	ml := &MessageList{
 		conf:          conf,
 		logger:        logger,
 		spinner:       NewSpinner(&conf.Ui),
 		isInitalizing: true,
+		aerc:          aerc,
 	}
 	ml.spinner.OnInvalidate(func(_ ui.Drawable) {
 		ml.Invalidate()
@@ -161,6 +163,47 @@ func (ml *MessageList) Draw(ctx *ui.Context) {
 	}
 }
 
+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
+				}
+				viewer := NewMessageViewer(acct, ml.aerc.Config(), store, msg)
+				ml.aerc.NewTab(viewer, msg.Envelope.Subject)
+			}
+		case tcell.WheelDown:
+			ml.store.Next()
+			ml.Scroll()
+		case tcell.WheelUp:
+			ml.store.Prev()
+			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
+}
+
 func (ml *MessageList) Height() int {
 	return ml.height
 }
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index e210616..c179070 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -42,6 +42,9 @@ type PartSwitcher struct {
 	selected       int
 	showHeaders    bool
 	alwaysShowMime bool
+
+	height int
+	mv     *MessageViewer
 }
 
 func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
@@ -77,7 +80,7 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
 	grid.AddChild(header).At(0, 0)
 	grid.AddChild(switcher).At(1, 0)
 
-	return &MessageViewer{
+	mv := &MessageViewer{
 		acct:     acct,
 		conf:     conf,
 		grid:     grid,
@@ -85,6 +88,9 @@ func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
 		store:    store,
 		switcher: switcher,
 	}
+	switcher.mv = mv
+
+	return mv
 }
 
 func fmtHeader(msg *models.MessageInfo, header string) string {
@@ -194,6 +200,13 @@ func (mv *MessageViewer) Draw(ctx *ui.Context) {
 	mv.grid.Draw(ctx)
 }
 
+func (mv *MessageViewer) MouseEvent(localX int, localY int, event tcell.Event) {
+	if mv.err != nil {
+		return
+	}
+	mv.grid.MouseEvent(localX, localY, event)
+}
+
 func (mv *MessageViewer) Invalidate() {
 	mv.grid.Invalidate()
 }
@@ -295,6 +308,7 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) {
 		return
 	}
 	// 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 {
 		style := tcell.StyleDefault.Reverse(ps.selected == i)
@@ -311,6 +325,62 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) {
 		0, 0, ctx.Width(), ctx.Height()-height))
 }
 
+func (ps *PartSwitcher) MouseEvent(localX int, localY int, event tcell.Event) {
+	switch event := event.(type) {
+	case *tcell.EventMouse:
+		switch event.Buttons() {
+		case tcell.Button1:
+			height := len(ps.parts)
+			y := ps.height - height
+			if localY < y {
+				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.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.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 (mv *MessageViewer) Event(event tcell.Event) bool {
 	return mv.switcher.Event(event)
 }
diff --git a/widgets/terminal.go b/widgets/terminal.go
index 008a36f..6ad6904 100644
--- a/widgets/terminal.go
+++ b/widgets/terminal.go
@@ -311,6 +311,20 @@ func (term *Terminal) Draw(ctx *ui.Context) {
 	}
 }
 
+func (term *Terminal) MouseEvent(localX int, localY int, event tcell.Event) {
+	switch event := event.(type) {
+	case *tcell.EventMouse:
+		if term.OnEvent != nil {
+			if term.OnEvent(event) {
+				return
+			}
+		}
+		if term.closed {
+			return
+		}
+	}
+}
+
 func (term *Terminal) Focus(focus bool) {
 	if term.closed {
 		return