gomuks/ui/room-list.go

475 lines
10 KiB
Go
Raw Normal View History

2018-03-25 13:21:59 +02:00
// gomuks - A terminal Matrix client written in Go.
2019-01-17 13:13:25 +01:00
// Copyright (C) 2019 Tulir Asokan
2018-03-25 13:21:59 +02:00
//
// This program is free software: you can redistribute it and/or modify
2019-01-17 13:13:25 +01:00
// it under the terms of the GNU Affero General Public License as published by
2018-03-25 13:21:59 +02:00
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2019-01-17 13:13:25 +01:00
// GNU Affero General Public License for more details.
2018-03-25 13:21:59 +02:00
//
2019-01-17 13:13:25 +01:00
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
2018-03-25 13:21:59 +02:00
package ui
2018-03-25 13:21:59 +02:00
import (
2019-01-17 13:13:25 +01:00
"math"
2018-04-24 01:13:17 +02:00
"regexp"
"strings"
2019-01-17 13:13:25 +01:00
"maunium.net/go/tcell"
"maunium.net/go/tview"
"maunium.net/go/gomuks/debug"
2018-03-25 13:21:59 +02:00
"maunium.net/go/gomuks/matrix/rooms"
)
type RoomList struct {
*tview.Box
2018-04-24 01:13:17 +02:00
// The list of tags in display order.
tags []string
// The list of rooms, in reverse order.
items map[string]*TagRoomList
// The selected room.
2018-04-24 01:13:17 +02:00
selected *rooms.Room
selectedTag string
2018-03-25 13:21:59 +02:00
scrollOffset int
2018-03-25 13:21:59 +02:00
// The item main text color.
mainTextColor tcell.Color
// The text color for selected items.
selectedTextColor tcell.Color
// The background color for selected items.
selectedBackgroundColor tcell.Color
}
func NewRoomList() *RoomList {
list := &RoomList{
Box: tview.NewBox(),
items: make(map[string]*TagRoomList),
2018-04-25 06:01:00 +02:00
tags: []string{"m.favourite", "net.maunium.gomuks.fake.direct", "", "m.lowpriority"},
2018-03-25 13:21:59 +02:00
scrollOffset: 0,
2018-03-25 13:21:59 +02:00
mainTextColor: tcell.ColorWhite,
selectedTextColor: tcell.ColorWhite,
selectedBackgroundColor: tcell.ColorDarkGreen,
}
for _, tag := range list.tags {
list.items[tag] = NewTagRoomList(list, tag)
}
return list
2018-03-25 13:21:59 +02:00
}
func (list *RoomList) Contains(roomID string) bool {
for _, trl := range list.items {
for _, room := range trl.All() {
2018-04-24 01:13:17 +02:00
if room.ID == roomID {
return true
}
}
}
return false
}
2018-03-25 13:21:59 +02:00
func (list *RoomList) Add(room *rooms.Room) {
debug.Print("Adding room to list", room.ID, room.GetTitle(), room.IsDirect, room.Tags())
2018-04-24 01:13:17 +02:00
for _, tag := range room.Tags() {
list.AddToTag(tag, room)
2018-04-24 01:13:17 +02:00
}
}
func (list *RoomList) CheckTag(tag string) {
index := list.IndexTag(tag)
trl, ok := list.items[tag]
2018-04-24 01:13:17 +02:00
if ok && trl.IsEmpty() {
//delete(list.items, tag)
2018-04-24 01:13:17 +02:00
ok = false
}
if ok && index == -1 {
list.tags = append(list.tags, tag)
} /* TODO this doesn't work properly
else if index != -1 {
2018-04-24 01:13:17 +02:00
list.tags = append(list.tags[0:index], list.tags[index+1:]...)
}*/
2018-04-24 01:13:17 +02:00
}
func (list *RoomList) AddToTag(tag rooms.RoomTag, room *rooms.Room) {
trl, ok := list.items[tag.Tag]
2018-04-24 01:13:17 +02:00
if !ok {
2018-05-22 17:38:54 +02:00
list.items[tag.Tag] = NewTagRoomList(list, tag.Tag, NewDefaultOrderedRoom(room))
2018-04-24 01:13:17 +02:00
return
}
trl.Insert(tag.Order, room)
list.CheckTag(tag.Tag)
2018-03-25 13:21:59 +02:00
}
func (list *RoomList) Remove(room *rooms.Room) {
for _, tag := range list.tags {
list.RemoveFromTag(tag, room)
2018-04-24 01:13:17 +02:00
}
}
func (list *RoomList) RemoveFromTag(tag string, room *rooms.Room) {
trl, ok := list.items[tag]
2018-04-24 01:13:17 +02:00
if !ok {
return
}
index := trl.Index(room)
2018-04-24 01:13:17 +02:00
if index == -1 {
return
}
trl.RemoveIndex(index)
2018-04-24 01:13:17 +02:00
if trl.IsEmpty() {
2018-05-17 15:30:02 +02:00
// delete(list.items, tag)
2018-04-24 01:13:17 +02:00
}
if room == list.selected {
if index > 0 {
list.selected = trl.All()[index-1].Room
} else if trl.Length() > 0 {
list.selected = trl.Visible()[0].Room
2018-04-24 01:13:17 +02:00
} else if len(list.items) > 0 {
for _, tag := range list.tags {
moreItems := list.items[tag]
if moreItems.Length() > 0 {
list.selected = moreItems.Visible()[0].Room
2018-04-24 01:13:17 +02:00
list.selectedTag = tag
}
}
2018-04-24 01:13:17 +02:00
} else {
list.selected = nil
list.selectedTag = ""
}
2018-03-25 13:21:59 +02:00
}
2018-04-24 01:13:17 +02:00
list.CheckTag(tag)
}
func (list *RoomList) Bump(room *rooms.Room) {
2018-04-24 01:13:17 +02:00
for _, tag := range room.Tags() {
trl, ok := list.items[tag.Tag]
if !ok {
return
}
trl.Bump(room)
2018-04-24 01:13:17 +02:00
}
2018-03-25 13:21:59 +02:00
}
func (list *RoomList) Clear() {
list.items = make(map[string]*TagRoomList)
list.tags = []string{"m.favourite", "net.maunium.gomuks.fake.direct", "", "m.lowpriority"}
for _, tag := range list.tags {
list.items[tag] = NewTagRoomList(list, tag)
}
2018-03-25 13:21:59 +02:00
list.selected = nil
2018-04-24 01:13:17 +02:00
list.selectedTag = ""
2018-03-25 13:21:59 +02:00
}
2018-04-24 01:13:17 +02:00
func (list *RoomList) SetSelected(tag string, room *rooms.Room) {
2018-03-25 13:21:59 +02:00
list.selected = room
2018-04-24 15:51:40 +02:00
list.selectedTag = tag
pos := list.index(tag, room)
_, _, _, height := list.GetRect()
if pos <= list.scrollOffset {
list.scrollOffset = pos - 1
} else if pos >= list.scrollOffset+height {
list.scrollOffset = pos - height + 1
}
if list.scrollOffset < 0 {
list.scrollOffset = 0
}
2018-04-25 06:01:00 +02:00
debug.Print("Selecting", room.GetTitle(), "in", list.GetTagDisplayName(tag))
2018-03-25 13:21:59 +02:00
}
func (list *RoomList) HasSelected() bool {
return list.selected != nil
}
2018-04-24 01:13:17 +02:00
func (list *RoomList) Selected() (string, *rooms.Room) {
return list.selectedTag, list.selected
}
func (list *RoomList) SelectedRoom() *rooms.Room {
return list.selected
}
func (list *RoomList) AddScrollOffset(offset int) {
list.scrollOffset += offset
_, _, _, viewHeight := list.GetRect()
contentHeight := list.ContentHeight()
if list.scrollOffset > contentHeight-viewHeight {
list.scrollOffset = contentHeight - viewHeight
}
if list.scrollOffset < 0 {
list.scrollOffset = 0
}
}
2018-04-24 01:13:17 +02:00
func (list *RoomList) First() (string, *rooms.Room) {
for _, tag := range list.tags {
trl := list.items[tag]
if trl.HasVisibleRooms() {
return tag, trl.FirstVisible()
2018-04-24 01:13:17 +02:00
}
}
return "", nil
}
func (list *RoomList) Last() (string, *rooms.Room) {
for tagIndex := len(list.tags) - 1; tagIndex >= 0; tagIndex-- {
tag := list.tags[tagIndex]
trl := list.items[tag]
if trl.HasVisibleRooms() {
return tag, trl.LastVisible()
2018-04-24 01:13:17 +02:00
}
}
return "", nil
}
func (list *RoomList) IndexTag(tag string) int {
for index, entry := range list.tags {
if tag == entry {
return index
}
}
return -1
}
func (list *RoomList) Previous() (string, *rooms.Room) {
if len(list.items) == 0 {
2018-04-24 01:13:17 +02:00
return "", nil
} else if list.selected == nil {
2018-04-24 01:13:17 +02:00
return list.First()
}
trl := list.items[list.selectedTag]
index := trl.IndexVisible(list.selected)
indexInvisible := trl.Index(list.selected)
if index == -1 && indexInvisible >= 0 {
num := trl.TotalLength() - indexInvisible
trl.maxShown = int(math.Ceil(float64(num)/10.0) * 10.0)
index = trl.IndexVisible(list.selected)
}
if index == trl.Length()-1 {
2018-04-24 01:13:17 +02:00
tagIndex := list.IndexTag(list.selectedTag)
tagIndex--
for ; tagIndex >= 0; tagIndex-- {
prevTag := list.tags[tagIndex]
prevTRL := list.items[prevTag]
if prevTRL.HasVisibleRooms() {
return prevTag, prevTRL.LastVisible()
2018-04-24 01:13:17 +02:00
}
}
return list.Last()
} else if index >= 0 {
return list.selectedTag, trl.Visible()[index+1].Room
}
return list.First()
}
2018-04-24 01:13:17 +02:00
func (list *RoomList) Next() (string, *rooms.Room) {
if len(list.items) == 0 {
2018-04-24 01:13:17 +02:00
return "", nil
} else if list.selected == nil {
2018-04-24 01:13:17 +02:00
return list.First()
}
trl := list.items[list.selectedTag]
index := trl.IndexVisible(list.selected)
indexInvisible := trl.Index(list.selected)
if index == -1 && indexInvisible >= 0 {
num := trl.TotalLength() - indexInvisible + 1
trl.maxShown = int(math.Ceil(float64(num)/10.0) * 10.0)
index = trl.IndexVisible(list.selected)
}
if index == 0 {
2018-04-24 01:13:17 +02:00
tagIndex := list.IndexTag(list.selectedTag)
tagIndex++
for ; tagIndex < len(list.tags); tagIndex++ {
nextTag := list.tags[tagIndex]
nextTRL := list.items[nextTag]
if nextTRL.HasVisibleRooms() {
return nextTag, nextTRL.FirstVisible()
2018-04-24 01:13:17 +02:00
}
}
return list.First()
} else if index > 0 {
return list.selectedTag, trl.Visible()[index-1].Room
}
return list.Last()
}
// NextWithActivity Returns next room with activity.
//
// Sorted by (in priority):
//
// - Highlights
// - Messages
// - Other traffic (joins, parts, etc)
//
// TODO: Sorting. Now just finds first room with new messages.
func (list *RoomList) NextWithActivity() (string, *rooms.Room) {
for tag, trl := range list.items {
for _, room := range trl.All() {
if room.HasNewMessages() {
return tag, room.Room
}
}
}
// No room with activity found
return "", nil
}
func (list *RoomList) index(tag string, room *rooms.Room) int {
tagIndex := list.IndexTag(tag)
if tagIndex == -1 {
return -1
}
trl, ok := list.items[tag]
localIndex := -1
if ok {
localIndex = trl.IndexVisible(room)
}
if localIndex == -1 {
return -1
}
localIndex = trl.Length() - 1 - localIndex
// Tag header
2018-05-22 17:43:00 +02:00
localIndex++
if tagIndex > 0 {
for i := 0; i < tagIndex; i++ {
prevTag := list.tags[i]
prevTRL := list.items[prevTag]
localIndex += prevTRL.RenderHeight()
}
}
return localIndex
}
func (list *RoomList) ContentHeight() (height int) {
for _, tag := range list.tags {
height += list.items[tag].RenderHeight()
}
return
}
func (list *RoomList) HandleClick(column, line int, mod bool) (string, *rooms.Room) {
line += list.scrollOffset
if line < 0 {
2018-04-24 01:13:17 +02:00
return "", nil
}
for _, tag := range list.tags {
trl := list.items[tag]
if line--; line == -1 {
trl.ToggleCollapse()
2018-05-22 17:43:00 +02:00
break
}
if trl.IsCollapsed() {
continue
}
2018-04-24 01:13:17 +02:00
if line < 0 {
2018-05-22 17:43:00 +02:00
break
} else if line < trl.Length() {
return tag, trl.Visible()[trl.Length()-1-line].Room
2018-04-24 01:13:17 +02:00
}
// Tag items
line -= trl.Length()
hasMore := trl.HasInvisibleRooms()
hasLess := trl.maxShown > 10
if hasMore || hasLess {
if line--; line == -1 {
diff := 10
if mod {
diff = 100
}
_, _, width, _ := list.GetRect()
if column <= 6 && hasLess {
trl.maxShown -= diff
} else if column >= width-6 && hasMore {
trl.maxShown += diff
}
if trl.maxShown < 10 {
trl.maxShown = 10
}
2018-05-22 17:43:00 +02:00
break
}
}
2018-04-24 15:51:40 +02:00
// Tag footer
line--
2018-04-24 01:13:17 +02:00
}
return "", nil
}
var nsRegex = regexp.MustCompile("^[a-z]\\.[a-z](?:\\.[a-z])*$")
func (list *RoomList) GetTagDisplayName(tag string) string {
switch {
case len(tag) == 0:
return "Rooms"
case tag == "m.favourite":
return "Favorites"
case tag == "m.lowpriority":
return "Low Priority"
2018-04-25 06:01:00 +02:00
case tag == "net.maunium.gomuks.fake.direct":
return "People"
2018-04-24 01:13:17 +02:00
case strings.HasPrefix(tag, "u."):
return tag[len("u."):]
case !nsRegex.MatchString(tag):
return tag
default:
return ""
}
}
2018-03-25 13:21:59 +02:00
// Draw draws this primitive onto the screen.
func (list *RoomList) Draw(screen tcell.Screen) {
list.Box.Draw(screen)
x, y, width, height := list.GetRect()
yLimit := y + height
y -= list.scrollOffset
2018-03-25 13:21:59 +02:00
// Draw the list items.
2018-04-24 01:13:17 +02:00
for _, tag := range list.tags {
trl := list.items[tag]
2018-04-25 06:01:00 +02:00
tagDisplayName := list.GetTagDisplayName(tag)
if trl == nil || len(tagDisplayName) == 0 {
2018-04-25 06:01:00 +02:00
continue
}
renderHeight := trl.RenderHeight()
2018-05-22 17:43:00 +02:00
if y+renderHeight >= yLimit {
renderHeight = yLimit - y
2018-03-25 13:21:59 +02:00
}
trl.SetRect(x, y, width, renderHeight)
trl.Draw(screen)
y += renderHeight
if y >= yLimit {
break
}
2018-03-25 13:21:59 +02:00
}
}