Files

192 lines
4.9 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package presentation
import (
"fmt"
"strings"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/config"
)
func reading_options(options Options) config.ReadingSettings {
if options.Reading != nil {
return *options.Reading
}
return config.DefaultSettings().Reading
}
func join_speech_parts(parts []string, options Options) string {
if !reading_options(options).EnablePauseBreaks {
return strings.Join(parts, " ")
}
return strings.Join(parts, ", ")
}
func displayed_focus_text(snapshot atspi.Snapshot) string {
var parts []string
for _, text := range []string{snapshot.Name, snapshot.Text, controlValue(snapshot)} {
if text != "" && !contains(parts, text) {
parts = append(parts, text)
}
}
return strings.Join(parts, " ")
}
func control_tutorial(snapshot atspi.Snapshot) string {
switch snapshot.Role {
case "push button", "button":
return "Press Space to activate."
case "check box", "toggle button":
return "Press Space to toggle."
case "radio button":
return "Use arrow keys to select an option."
case "combo box":
return "Press Alt+Down to open the list."
case "slider", "spin button":
return "Use arrow keys to change the value."
case "entry", "text":
if snapshot.Editable && !snapshot.ReadOnly {
return "Type to enter text."
}
case "list", "list box", "tree", "tree table":
return "Use arrow keys to move between items."
}
return ""
}
func FullRowEnabled(snapshot atspi.Snapshot, options Options) bool {
reading := reading_options(options)
if snapshot.Spreadsheet {
return reading.ReadFullRowInSpreadSheet
}
if snapshot.GUITable {
return options.ReadFullRowInGUITable
}
return reading.ReadFullRowInDocumentTable
}
// ContextType matches Python's separate container and landmark preferences.
func ContextType(snapshot atspi.Snapshot) string {
switch snapshot.Role {
case "block quote", "blockquote":
return "blockquote"
case "list", "list box":
return "list"
case "table", "tree table":
if !snapshot.LayoutTable {
return "table"
}
case "panel", "grouping":
return "panel"
case "landmark":
return "landmark"
case "form":
if snapshot.Name == "" {
return "form"
}
return "landmark"
}
for _, role := range strings.Fields(snapshot.XMLRoles) {
switch role {
case "banner", "complementary", "contentinfo", "main", "navigation", "search", "region":
return "landmark"
case "form":
if snapshot.Name != "" {
return "landmark"
}
return "form"
}
}
if strings.EqualFold(snapshot.HTMLTag, "blockquote") {
return "blockquote"
}
return ""
}
func ContextEnabled(kind string, reading config.ReadingSettings) bool {
if reading.OnlySpeakDisplayedText {
return false
}
switch kind {
case "blockquote":
return reading.SpeakContextBlockquote
case "list":
return reading.SpeakContextList
case "table":
return reading.SpeakContextTable
case "panel":
return reading.SpeakContextPanel
case "landmark":
return reading.SpeakContextLandmark
case "form":
return reading.SpeakContextNonLandmarkForm
}
return false
}
// Say All has its own context choices, independent of navigation speech.
func SayAllContextEnabled(kind string, reading config.ReadingSettings) bool {
if reading.OnlySpeakDisplayedText {
return false
}
switch kind {
case "blockquote":
return reading.SayAllContextBlockquote
case "list":
return reading.SayAllContextList
case "table":
return reading.SayAllContextTable
case "panel":
return reading.SayAllContextPanel
case "landmark":
return reading.SayAllContextLandmark
case "form":
return reading.SayAllContextNonLandmarkForm
}
return false
}
func ContextLabel(snapshot atspi.Snapshot) string {
role := ContextType(snapshot)
if role == "blockquote" {
role = "block quote"
}
if role == "landmark" && snapshot.XMLRoles != "" {
role = strings.Fields(snapshot.XMLRoles)[0] + " landmark"
}
if snapshot.Name != "" {
return snapshot.Name + " " + role
}
return role
}
func itemCountText(count int) string {
if count == 1 {
return "1 item"
}
return fmt.Sprintf("%d items", count)
}
// TableHeaderText preserves the provider's header label and adds the role
// wording used by verbose speech. Brief speech keeps the label alone.
func TableHeaderText(label, role string, verbosity int) string {
if label == "" || verbosity == 0 {
return label
}
return label + " " + role
}
// ContextCountLabel adds provider-backed list size to a new context entry.
// The ordinary context label remains stable for exits and brief speech.
func ContextCountLabel(snapshot atspi.Snapshot, verbosity int) string {
if verbosity != 0 && snapshot.ChildCountKnown && snapshot.ChildCount > 0 &&
(snapshot.Role == "list" || snapshot.Role == "list box") {
if snapshot.Name != "" {
return snapshot.Name + ", " + itemCountText(snapshot.ChildCount)
}
return itemCountText(snapshot.ChildCount)
}
return ContextLabel(snapshot)
}