akiyosi.goneovim/editor/message.go
2023-07-19 08:38:12 +09:00

510 lines
11 KiB
Go

package editor
import (
"bytes"
"fmt"
"strings"
"time"
"github.com/akiyosi/qt/core"
"github.com/akiyosi/qt/gui"
"github.com/akiyosi/qt/svg"
"github.com/akiyosi/qt/widgets"
"github.com/akiyosi/goneovim/util"
)
const DummyText = "dummy text"
// Message is
type Message struct {
ws *Workspace
widget *widgets.QWidget
layout *widgets.QGridLayout
pos *core.QPoint
items []*MessageItem
width int
expires int
isDrag bool
isExpand bool
}
// MessageItem is
type MessageItem struct {
hideAt time.Time
m *Message
icon *svg.QSvgWidget
label *widgets.QLabel
widget *widgets.QWidget
text string
kind string
attrId int
textLength int
expired bool
active bool
}
func initMessage() *Message {
width := 250
widget := widgets.NewQWidget(nil, 0)
widget.SetObjectName("message")
widget.SetContentsMargins(0, 0, 0, 0)
layout := widgets.NewQGridLayout2()
layout.SetContentsMargins(0, 0, 0, 0)
layout.SetSpacing(0)
layout.SetSizeConstraint(widgets.QLayout__SetMinAndMaxSize)
widget.SetLayout(layout)
m := &Message{
width: width,
widget: widget,
layout: layout,
expires: 120,
}
margin := 5
items := []*MessageItem{}
for i := 0; i < 10; i++ {
w := widgets.NewQWidget(m.widget, 0)
w.SetContentsMargins(0, 0, 0, 0)
w.SetStyleSheet(" * { background-color: rgba(0, 0, 0, 0); border: 0px solid #000;}")
w.SetFixedWidth(editor.iconSize)
icon := svg.NewQSvgWidget(nil)
// icon.SetStyleSheet("* { background-color: rgba(0, 0, 0, 0); border: 0px solid #000;}")
icon.SetFixedSize2(editor.iconSize, editor.iconSize)
icon.SetParent(w)
icon.Move2(margin, margin)
l := widgets.NewQLabel(nil, 0)
// l.SetStyleSheet("* { background-color: rgba(0, 0, 0, 0); border: 0px solid #000;}")
l.SetContentsMargins(margin, margin, margin, margin)
l.SetWordWrap(true)
l.SetText(DummyText)
item := &MessageItem{
m: m,
text: DummyText,
label: l,
icon: icon,
widget: w,
active: true,
expired: true,
}
items = append(items, item)
}
m.items = items
// Enable widget dragging
m.widget.ConnectMousePressEvent(func(event *gui.QMouseEvent) {
m.widget.Raise()
m.isDrag = true
m.pos = event.Pos()
})
m.widget.ConnectMouseReleaseEvent(func(*gui.QMouseEvent) {
m.isDrag = false
})
m.widget.ConnectMouseMoveEvent(func(event *gui.QMouseEvent) {
if m.isDrag {
x := event.Pos().X() - m.pos.X()
y := event.Pos().Y() - m.pos.Y()
newPos := core.NewQPoint2(x, y)
trans := m.widget.MapToParent(newPos)
m.widget.Move(trans)
}
})
// layout message item
for i, item := range items {
layout.AddWidget2(item.widget, i, 0, 0)
layout.SetAlignment(item.widget, core.Qt__AlignTop)
layout.AddWidget2(item.label, i, 1, 0)
layout.SetAlignment(item.label, core.Qt__AlignTop)
}
// Drop shadow to widget
m.widget.SetGraphicsEffect(util.DropShadow(-2, 4, 40, 200))
m.widget.Hide()
// hide messages at startup.
m.update()
return m
}
func (m *Message) setColor() {
if m == nil {
return
}
fg := editor.colors.fg.String()
bg := warpColor(editor.colors.bg, -15)
transparent := transparent() * transparent()
if editor.config.Message.Transparent < 1.0 {
transparent = editor.config.Message.Transparent
}
m.widget.SetStyleSheet(fmt.Sprintf(
" #message { background-color: rgba(%d, %d, %d, %f); color: %s; }",
bg.R,
bg.G,
bg.B,
transparent,
fg,
))
}
func (m *Message) updateFont() {
if m == nil {
return
}
margin := m.ws.font.height / 3
for _, item := range m.items {
item.widget.SetFixedSize2(m.ws.font.height*5/3, m.ws.font.height*5/3)
item.icon.SetFixedSize2(m.ws.font.height, m.ws.font.height)
item.label.SetContentsMargins(margin/2, margin, margin, margin)
item.icon.Move2(margin*5/4, margin)
item.label.SetFont(m.ws.font.fontNew)
}
}
func (m *Message) connectUI() {
if m == nil {
return
}
m.ws.signal.ConnectMessageSignal(func() {
m.update()
})
}
func (m *Message) update() {
if m == nil {
return
}
now := time.Now()
hasExpired := false
for _, item := range m.items {
if !item.active {
break
}
if item.active && item.hideAt.Before(now) {
item.expired = true
hasExpired = true
}
}
if !hasExpired {
return
}
i := 0
for _, item := range m.items {
if item.active && !item.expired {
m.items[i].copy(item)
item.expired = true
i++
}
}
for _, item := range m.items {
if item.active && item.expired {
item.hide()
}
}
}
func (m *Message) resize() {
if m == nil {
return
}
if m.ws == nil {
return
}
if m.ws.screen == nil {
return
}
leftPadding := 0
if editor.side != nil {
if editor.side.scrollarea != nil {
leftPadding = editor.side.scrollarea.Width()
}
}
scrollbarwidth := 0
if editor.config.ScrollBar.Visible {
if m.ws.scrollBar != nil {
scrollbarwidth = m.ws.scrollBar.widget.Width()
}
}
var x, y int
var ok bool
if !m.isExpand {
m.width = m.ws.screen.widget.Width() / 3
ok = m.resizeMessages()
if !ok {
m.isExpand = true
m.width = m.ws.screen.widget.Width() - scrollbarwidth - 12
_ = m.resizeMessages()
}
}
m.widget.Resize2(m.width+editor.iconSize, 0)
if !ok {
x = 10 + leftPadding
y = m.ws.widget.Height() - m.widget.Height()
} else {
x = m.ws.width + leftPadding - m.width - editor.iconSize - scrollbarwidth - 12
// y = 6 + m.ws.tabline.widget.Height()
y = 6
if m.ws.tabline != nil {
y += m.ws.tabline.widget.Height()
}
}
m.widget.Move2(x, y)
}
func (m *Message) resizeMessages() bool {
ok := true
width := m.ws.font.cellwidth
for _, item := range m.items {
item.widget.SetFixedSize2(m.ws.font.height*5/3, m.ws.font.height*5/3)
item.label.SetMinimumHeight(0)
item.label.SetMinimumHeight(item.label.HeightForWidth(m.width))
item.label.SetAlignment(core.Qt__AlignTop)
if !item.active {
continue
}
labelWidth := m.width - item.widget.Width()
messageWidth := int(width * float64(item.textLength))
if labelWidth < messageWidth {
ok = false
}
}
return ok
}
func (m *Message) msgShow(args []interface{}) {
if m == nil {
return
}
prevKind := ""
isActiveState := editor.window.IsActiveWindow()
notifyText := ""
for _, arg := range args {
kind, ok := arg.([]interface{})[0].(string)
if !ok {
continue
}
// text := ""
var buffer bytes.Buffer
length := 0
lineLen := 0
scrollbarwidth := 0
if editor.config.ScrollBar.Visible {
if m.ws.scrollBar != nil {
scrollbarwidth = m.ws.scrollBar.widget.Width()
}
}
maxLen := m.ws.screen.widget.Width() - scrollbarwidth - 12
var attrId int
for _, tuple := range arg.([]interface{})[1].([]interface{}) {
chunk, ok := tuple.([]interface{})
if !ok {
continue
}
if len(chunk) != 2 {
continue
}
attrId = util.ReflectToInt(chunk[0])
if !ok {
continue
}
msg, ok := chunk[1].(string)
if !ok {
continue
}
var color *RGBA
if m.ws.screen.hlAttrDef[attrId] != nil {
color = (m.ws.screen.hlAttrDef[attrId]).foreground
} else {
color = m.ws.foreground
}
if msg == "" || msg == "\n" || msg == "\r\n" {
continue
}
// If window is minimize, then message notified as a desktop notifications
if !isActiveState {
notifyText += msg
}
length += len(msg)
if !strings.Contains(msg, "\n") {
var cBuffer bytes.Buffer
for _, c := range msg {
lineLen += len(string(c))
msgLen := int(m.ws.font.cellwidth * float64(lineLen))
if msgLen >= maxLen {
cBuffer.WriteString(`<br>`)
lineLen = 0
}
cBuffer.WriteString(string(c))
}
msg = cBuffer.String()
}
msg = strings.Replace(msg, "\r\n", `<br>`, -1)
msg = strings.Replace(msg, "\n", `<br>`, -1)
msg = strings.Replace(msg, " ", `&nbsp;`, -1)
formattedMsg := fmt.Sprintf("<font color='%s'>%s</font>", warpColor(color, -20).Hex(), msg)
buffer.WriteString(formattedMsg)
}
// If window is minimize, then message notified as a desktop notifications
if !isActiveState && notifyText != "" {
editor.sysTray.ShowMessage("GoNeovim", notifyText, widgets.QSystemTrayIcon__NoIcon, 2000)
return
}
replaceLast := false
if len(arg.([]interface{})) > 2 {
replaceLast, _ = arg.([]interface{})[2].(bool)
}
if kind == prevKind {
// Do not show message icon if the same kind as the previous kind
m.makeMessage("_dup", attrId, buffer.String(), length, replaceLast)
} else {
m.makeMessage(kind, attrId, buffer.String(), length, replaceLast)
}
prevKind = kind
}
m.isExpand = false
}
func (m *Message) makeMessage(kind string, attrId int, text string, length int, replaceLast bool) {
defer m.resize()
var item *MessageItem
allActive := true
for _, item = range m.items {
item.textLength = length
if !item.active {
allActive = false
break
}
}
if allActive {
for i := 0; i < len(m.items)-1; i++ {
m.items[i].copy(m.items[i+1])
}
}
if replaceLast {
for _, i := range m.items {
i.hide()
}
}
MessageSignal := m.ws.signal.MessageSignal
item.hideAt = time.Now().Add(time.Duration(m.expires) * time.Second)
time.AfterFunc(time.Duration(m.expires+1)*time.Second, func() {
MessageSignal()
})
item.attrId = attrId
item.setKind(kind)
if item.text == DummyText {
item.show()
}
item.setText(text)
item.show()
}
func (m *Message) msgClear() {
if m == nil {
return
}
for _, item := range m.items {
item.hide()
}
}
func (m *Message) msgHistoryShow(args []interface{}) {
if m == nil {
return
}
for _, arg := range args {
m.msgShow((arg.([]interface{})[0]).([]interface{}))
}
}
func (i *MessageItem) setText(text string) {
i.text = text
i.label.SetMinimumHeight(0)
i.label.SetText(text)
height := i.label.HeightForWidth(i.m.width)
i.label.SetMinimumHeight(height)
i.label.SetMaximumHeight(height)
i.widget.SetMinimumHeight(height)
i.widget.SetMaximumHeight(height)
i.widget.SetFixedSize2(i.m.ws.font.height*5/3, i.m.ws.font.height*5/3)
}
func (i *MessageItem) copy(item *MessageItem) {
i.hideAt = item.hideAt
i.setKind(item.kind)
i.setText(item.text)
i.show()
}
func (i *MessageItem) hide() {
i.expired = false
if !i.active {
return
}
i.active = false
i.widget.Hide()
i.icon.Hide()
i.label.Hide()
}
func (i *MessageItem) show() {
i.expired = false
if i.active {
return
}
i.active = true
i.label.Show()
i.icon.Show()
i.widget.Show()
i.m.widget.Show()
i.m.widget.Raise()
i.widget.Raise()
}
func (i *MessageItem) setKind(kind string) {
i.kind = kind
var color *RGBA
if i.m.ws == nil {
return
}
if i.m.ws.screen.hlAttrDef[i.attrId] == nil {
return
}
color = warpColor((i.m.ws.screen.hlAttrDef[i.attrId]).foreground, -15)
switch i.kind {
case "emsg", "echo", "echomsg", "echoerr", "return_prompt", "quickfix":
svgContent := editor.getSvg(i.kind, color)
i.icon.Load2(core.NewQByteArray2(svgContent, len(svgContent)))
case "_dup":
// hide
svgContent := editor.getSvg("echo", warpColor(editor.colors.bg, -15))
i.icon.Load2(core.NewQByteArray2(svgContent, len(svgContent)))
default:
svgContent := editor.getSvg("echo", color)
i.icon.Load2(core.NewQByteArray2(svgContent, len(svgContent)))
}
}