192 lines
4.9 KiB
Go
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)
|
|
}
|