More LibreOffice work.
This commit is contained in:
1 parent
4ac59d4ced
commit
b5a5b86dda
15 files changed
+768
-28
No files matched your search
@@ -0,0 +1,102 @@
|
||||
// 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)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||
)
|
||||
|
||||
func TestLibreOfficeSelectionPreservesGapsAndBoundsOutput(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
selection atspi.LibreOfficeSelection
|
||||
want string
|
||||
}{
|
||||
{"rectangle", atspi.LibreOfficeSelection{Kind: "cells", Count: 4, Complete: true,
|
||||
Cells: []atspi.LibreOfficeCell{{Row: 0, Column: 0}, {Row: 0, Column: 1}, {Row: 1, Column: 0}, {Row: 1, Column: 1}}}, "Cells A1 to B2 selected."},
|
||||
{"disjoint", atspi.LibreOfficeSelection{Kind: "cells", Count: 4, Complete: true,
|
||||
Cells: []atspi.LibreOfficeCell{{Row: 0, Column: 0}, {Row: 0, Column: 1}, {Row: 0, Column: 3}, {Row: 1, Column: 3}}}, "Cells A1 to B1 selected. Cells D1 to D2 selected."},
|
||||
{"row gaps", atspi.LibreOfficeSelection{Kind: "rows", Count: 3, Complete: true,
|
||||
Cells: []atspi.LibreOfficeCell{{Row: 0, Column: 0}, {Row: 1, Column: 0}, {Row: 3, Column: 0}}}, "Rows 1 to 2 selected. Row 4 selected."},
|
||||
{"column labels", atspi.LibreOfficeSelection{Kind: "columns", Count: 2, Complete: true,
|
||||
Cells: []atspi.LibreOfficeCell{{Row: 0, Column: 25}, {Row: 0, Column: 26}}}, "Columns Z to AA selected."},
|
||||
{"large", atspi.LibreOfficeSelection{Kind: "cells", Count: 1000000000,
|
||||
First: atspi.LibreOfficeCell{Row: 0, Column: 0}, Last: atspi.LibreOfficeCell{Row: 999999, Column: 999}}, "1000000000 cells selected."},
|
||||
{"capped", atspi.LibreOfficeSelection{Kind: "cells", Count: 2147483647, CountLimited: true}, "At least 2147483647 cells selected."},
|
||||
{"all", atspi.LibreOfficeSelection{Kind: "all", Complete: true}, "All cells selected."},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if got := LibreOfficeSelectionText(test.selection, "selected"); got != test.want {
|
||||
t.Fatalf("got %q, want %q", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
selection := atspi.LibreOfficeSelection{Kind: "cells", Count: 9, Complete: true}
|
||||
for index := 0; index < 9; index++ {
|
||||
selection.Cells = append(selection.Cells, atspi.LibreOfficeCell{Row: index, Column: index})
|
||||
}
|
||||
if got := LibreOfficeSelectionText(selection, "unselected"); got != "9 cells unselected." {
|
||||
t.Fatalf("fragmented selection = %q", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user