110 lines
3.4 KiB
Go
110 lines
3.4 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package atspi
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
)
|
|
|
|
// Choices contain only identities and coordinates, never provider objects or
|
|
// cell text. They survive focus/lock/tab changes and expire on document/window
|
|
// destruction, sheet topology/name changes and shutdown. A fixed limit bounds
|
|
// missed deaths. Calc recreates its table objects on every sheet switch.
|
|
type libreOfficeHeaderKey struct {
|
|
window, document ObjectID
|
|
sheet string
|
|
}
|
|
|
|
type libreOfficeHeaderChoice struct {
|
|
row, column int
|
|
rowSet, columnSet bool
|
|
}
|
|
|
|
type libreOfficeHeaderStore struct {
|
|
mutex sync.Mutex
|
|
choices map[libreOfficeHeaderKey]libreOfficeHeaderChoice
|
|
}
|
|
|
|
func libreOfficeHeaderIdentity(snapshot Snapshot, window ObjectID) (libreOfficeHeaderKey, bool) {
|
|
context := snapshot.LibreOffice
|
|
key := libreOfficeHeaderKey{window: window}
|
|
if context != nil {
|
|
key.document = context.Document
|
|
key.sheet = context.SheetName
|
|
}
|
|
valid := IsLibreOfficeProcess(snapshot.ApplicationProcess) && !snapshot.Defunct &&
|
|
snapshot.Role == "table cell" && context != nil && context.InSpreadsheet && context.Cell &&
|
|
snapshot.TablePositionKnown && snapshot.TableRowIndex >= 0 && snapshot.TableColumnIndex >= 0 &&
|
|
key.window.Valid() && key.document.Valid() && key.sheet != "" && snapshot.TablePath != "" &&
|
|
key.window.BusName == snapshot.BusName && key.document.BusName == snapshot.BusName
|
|
return key, valid
|
|
}
|
|
|
|
// ConfigureLibreOfficeHeader is called only for the event loop's owned cell.
|
|
// It copies a choice without querying AT-SPI or waiting for the GLib thread.
|
|
// columnHeaders selects the row that supplies column headings; otherwise the
|
|
// current column supplies row headings. clear removes only that axis.
|
|
func (adapter *Adapter) ConfigureLibreOfficeHeader(snapshot Snapshot, window ObjectID, columnHeaders, clear bool) error {
|
|
key, valid := libreOfficeHeaderIdentity(snapshot, window)
|
|
if !valid || adapter.secure.Load() {
|
|
return errors.New("Not in a Calc cell.")
|
|
}
|
|
store := &adapter.libreOfficeHeaders
|
|
store.mutex.Lock()
|
|
defer store.mutex.Unlock()
|
|
choice, exists := store.choices[key]
|
|
if !exists && !clear && len(store.choices) >= 64 {
|
|
return errors.New("Header choices are full. Clear a sheet's headers first.")
|
|
}
|
|
if columnHeaders {
|
|
choice.row, choice.rowSet = snapshot.TableRowIndex, !clear
|
|
} else {
|
|
choice.column, choice.columnSet = snapshot.TableColumnIndex, !clear
|
|
}
|
|
if !choice.rowSet && !choice.columnSet {
|
|
delete(store.choices, key)
|
|
return nil
|
|
}
|
|
if store.choices == nil {
|
|
store.choices = make(map[libreOfficeHeaderKey]libreOfficeHeaderChoice)
|
|
}
|
|
store.choices[key] = choice
|
|
return nil
|
|
}
|
|
|
|
func (store *libreOfficeHeaderStore) get(key libreOfficeHeaderKey) libreOfficeHeaderChoice {
|
|
store.mutex.Lock()
|
|
defer store.mutex.Unlock()
|
|
return store.choices[key]
|
|
}
|
|
|
|
func (store *libreOfficeHeaderStore) forget(id ObjectID) {
|
|
store.mutex.Lock()
|
|
defer store.mutex.Unlock()
|
|
for key := range store.choices {
|
|
if id == key.window || id == key.document {
|
|
delete(store.choices, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (store *libreOfficeHeaderStore) hasBus(bus string) bool {
|
|
store.mutex.Lock()
|
|
defer store.mutex.Unlock()
|
|
for key := range store.choices {
|
|
if key.window.BusName == bus {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (store *libreOfficeHeaderStore) reset() {
|
|
store.mutex.Lock()
|
|
defer store.mutex.Unlock()
|
|
store.choices = nil
|
|
}
|