103 lines
3.1 KiB
Go
103 lines
3.1 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package presentation
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
)
|
|
|
|
func calcColumnLabel(column int) string {
|
|
label := ""
|
|
for column++; column > 0; column = (column - 1) / 26 {
|
|
label = string(rune('A'+(column-1)%26)) + label
|
|
}
|
|
return label
|
|
}
|
|
|
|
func calcCellLabel(cell atspi.LibreOfficeCell) string {
|
|
return fmt.Sprintf("%s%d", calcColumnLabel(cell.Column), cell.Row+1)
|
|
}
|
|
|
|
// LibreOfficeSelectionText formats copied metadata only. A large selection's
|
|
// endpoints never imply a filled rectangle. Small disjoint selections retain
|
|
// their gaps; at most eight rectangles are spoken before using a count.
|
|
func LibreOfficeSelectionText(selection atspi.LibreOfficeSelection, action string) string {
|
|
if selection.Kind == "all" {
|
|
return "All cells " + action + "."
|
|
}
|
|
if selection.Count == 0 {
|
|
return ""
|
|
}
|
|
if !selection.Complete || len(selection.Cells) > 64 {
|
|
return libreOfficeSelectionCount(selection, action)
|
|
}
|
|
cells := slices.Clone(selection.Cells)
|
|
slices.SortFunc(cells, func(a, b atspi.LibreOfficeCell) int {
|
|
if a.Row != b.Row {
|
|
return a.Row - b.Row
|
|
}
|
|
return a.Column - b.Column
|
|
})
|
|
cells = slices.Compact(cells)
|
|
type rectangle struct{ first, last atspi.LibreOfficeCell }
|
|
var ranges []rectangle
|
|
for index := 0; index < len(cells); {
|
|
first, last := cells[index], cells[index]
|
|
index++
|
|
for index < len(cells) && (selection.Kind == "rows" && cells[index].Row == last.Row+1 ||
|
|
selection.Kind != "rows" && cells[index].Row == last.Row && cells[index].Column == last.Column+1) {
|
|
last = cells[index]
|
|
index++
|
|
}
|
|
merged := false
|
|
if selection.Kind == "cells" {
|
|
for i := range ranges {
|
|
previous := &ranges[i]
|
|
if previous.first.Column == first.Column && previous.last.Column == last.Column && previous.last.Row+1 == first.Row {
|
|
previous.last.Row = last.Row
|
|
merged = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !merged {
|
|
ranges = append(ranges, rectangle{first, last})
|
|
}
|
|
}
|
|
if len(ranges) > 8 {
|
|
return libreOfficeSelectionCount(selection, action)
|
|
}
|
|
var messages []string
|
|
for _, span := range ranges {
|
|
first, last := calcCellLabel(span.first), calcCellLabel(span.last)
|
|
singular, plural := "Cell", "Cells"
|
|
if selection.Kind == "rows" {
|
|
first, last = fmt.Sprint(span.first.Row+1), fmt.Sprint(span.last.Row+1)
|
|
singular, plural = "Row", "Rows"
|
|
} else if selection.Kind == "columns" {
|
|
first, last = calcColumnLabel(span.first.Column), calcColumnLabel(span.last.Column)
|
|
singular, plural = "Column", "Columns"
|
|
}
|
|
if first == last {
|
|
messages = append(messages, fmt.Sprintf("%s %s %s.", singular, first, action))
|
|
} else {
|
|
messages = append(messages, fmt.Sprintf("%s %s to %s %s.", plural, first, last, action))
|
|
}
|
|
}
|
|
return strings.Join(messages, " ")
|
|
}
|
|
|
|
func libreOfficeSelectionCount(selection atspi.LibreOfficeSelection, action string) string {
|
|
prefix := ""
|
|
if selection.CountLimited {
|
|
prefix = "At least "
|
|
}
|
|
return fmt.Sprintf("%s%d %s %s.", prefix, selection.Count, selection.Kind, action)
|
|
}
|