Files
cthulhu/internal/app/libreoffice_headers.go

91 lines
3.2 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"fmt"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/config"
"git.stormux.org/storm/cthulhu/internal/input"
)
type libreOfficeHeaderConfigurer interface {
ConfigureLibreOfficeHeader(atspi.Snapshot, atspi.ObjectID, bool, bool) error
}
func (handler *eventReviewHandler) configureCalcHeader(command input.Command) {
if handler.focusGate.Locked() || handler.focusGate.Suspended() {
return
}
handler.syncForeground()
state := handler.state
snapshot := state.current
context := snapshot.LibreOffice
text := "Not in a Calc cell."
if handler.calcHeaders != nil && context != nil && context.Cell && context.InSpreadsheet &&
!snapshot.Defunct && atspi.IsLibreOfficeProcess(snapshot.ApplicationProcess) &&
state.owner.window.Valid() && state.owner.applicationBus == snapshot.BusName &&
state.owner.focus == snapshot.ID() && snapshot.TablePositionKnown {
columnHeaders := command == input.SetColumnHeadersRow || command == input.ClearColumnHeadersRow
clear := command == input.ClearColumnHeadersRow || command == input.ClearRowHeadersColumn
if err := handler.calcHeaders.ConfigureLibreOfficeHeader(snapshot, state.owner.window, columnHeaders, clear); err != nil {
text = err.Error()
} else if columnHeaders {
// The command changes this axis immediately. Its text is captured
// again on the next cell event, never queried by the event loop.
state.current.TableColumnHeaders = ""
text = fmt.Sprintf("Row %d set as column headers.", snapshot.TableRowIndex+1)
if clear {
text = "Column headers cleared."
}
} else {
state.current.TableRowHeaders = ""
text = fmt.Sprintf("Column %s set as row headers.", calcColumnLabel(snapshot.TableColumnIndex))
if clear {
text = "Row headers cleared."
}
}
if handler.brailleOutput != nil {
options := config.DefaultSettings().Braille
if handler.live != nil {
options = handler.live.Load().Braille
}
state.brailleSnapshot = state.current
handler.brailleOutput.EnqueueFrame(brailleFocusFrameWithOptions(state.current, options))
}
}
state.pending.clear()
handler.speech.Enqueue(speechRequest{Text: text, Role: "system", Cause: "calc-headers"})
}
func calcColumnLabel(column int) string {
label := ""
for column >= 0 {
label = string(rune('A'+column%26)) + label
column = column/26 - 1
}
return label
}
// Use existing table presentation history, which belongs to the event loop.
// Keep the full snapshot for Where Am I and Braille; only automatic speech
// omits headings for an unchanged axis, following current Orca.
func (state *eventLoopState) calcHeaderSpeechEvent(event atspi.Event) atspi.Event {
snapshot := event.Source
if snapshot.LibreOffice == nil || !snapshot.LibreOffice.Cell ||
!atspi.IsLibreOfficeProcess(snapshot.ApplicationProcess) || !snapshot.TablePositionKnown ||
!state.lastTableCell.Valid() || state.lastTable != calcSheet(snapshot) {
return event
}
if snapshot.TableRowIndex == state.lastTableRow {
event.Source.TableRowHeaders = ""
}
if snapshot.TableColumnIndex == state.lastTableColumn {
event.Source.TableColumnHeaders = ""
}
return event
}