// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package atspi /* #include "adapter_native.h" */ import "C" import "strings" // Sheet names are unique within a Calc document. Conservatively retire choices // for this window when sheet tabs are added, removed or renamed, so a // deleted/recreated name cannot inherit an old sheet's choice. Switching tabs // only changes selected state and does not retire choices. func (adapter *Adapter) retireLibreOfficeHeaders(source *C.AtspiAccessible, eventType string, origin ObjectID) { if eventType == WindowDestroyedEvent || eventType == DefunctStateEvent { adapter.libreOfficeHeaders.forget(origin) } if (eventType != AccessibleNameEvent && !strings.HasPrefix(eventType, ChildrenChangedEvent)) || !adapter.libreOfficeHeaders.hasBus(origin.BusName) { return } role := C.atspi_accessible_get_role(source, nil) if eventType == AccessibleNameEvent && role == C.ATSPI_ROLE_PAGE_TAB || strings.HasPrefix(eventType, ChildrenChangedEvent) && role == C.ATSPI_ROLE_PAGE_TAB_LIST { window := C.cthulhu_window_ancestor(source) if window != nil { adapter.libreOfficeHeaders.forget(nativeObjectID(window)) C.g_object_unref(C.gpointer(window)) } } } // GLib owner only: at most two direct cell lookups, with no sheet enumeration // or retained native references. Read fresh text on each accepted snapshot so // edits to a heading are reflected when the user next visits a data cell. func (adapter *Adapter) captureLibreOfficeHeaders(source *C.AtspiAccessible, snapshot *Snapshot, window ObjectID) { key, valid := libreOfficeHeaderIdentity(*snapshot, window) if !valid { return } choice := adapter.libreOfficeHeaders.get(key) if !choice.rowSet && !choice.columnSet { return } cell := C.atspi_accessible_get_table_cell(source) if cell == nil { return } defer C.g_object_unref(C.gpointer(cell)) accessible := C.atspi_table_cell_get_table(cell, nil) if accessible == nil { return } defer C.g_object_unref(C.gpointer(accessible)) if nativeObjectID(accessible) != NewObjectID(snapshot.BusName, snapshot.TablePath) { return } table := C.atspi_accessible_get_table_iface(accessible) if table == nil { return } defer C.g_object_unref(C.gpointer(table)) if choice.columnSet && choice.column != snapshot.TableColumnIndex { snapshot.TableRowHeaders = libreOfficeHeaderText(table, snapshot.TableRowIndex, choice.column) } if choice.rowSet && choice.row != snapshot.TableRowIndex { snapshot.TableColumnHeaders = libreOfficeHeaderText(table, choice.row, snapshot.TableColumnIndex) } } func libreOfficeHeaderText(table *C.AtspiTable, row, column int) string { cell := C.atspi_table_get_accessible_at(table, C.gint(row), C.gint(column), nil) if cell == nil { return "" } defer C.g_object_unref(C.gpointer(cell)) if C.atspi_accessible_get_role(cell, nil) != C.ATSPI_ROLE_TABLE_CELL { return "" } C.atspi_accessible_clear_cache_single(cell) states := C.atspi_accessible_get_state_set(cell) if states == nil { return "" } defunct := C.atspi_state_set_contains(states, C.ATSPI_STATE_DEFUNCT) != 0 C.g_object_unref(C.gpointer(states)) if defunct { return "" } text := C.atspi_accessible_get_text_iface(cell) if text == nil { return "" } defer C.g_object_unref(C.gpointer(text)) length := max(0, min(int(C.atspi_text_get_character_count(text, nil)), maxSnapshotFieldLength)) content := C.atspi_text_get_text(text, 0, C.gint(length), nil) defer C.g_free(C.gpointer(content)) return sanitizeSnapshotField(C.GoString(content)) }