How to decide if a given TextTable cell belongs to the range of a given TextTableCursor

A TextTableCursor object is extremely dumb. It neither knows its parent (the table its range is part of) nor can it tell if a given cell (by name e.g.) belongs to the range it covers. The .RangeName property isn’t really helpful in the case of a “complex table”.
Did I miss something? Is there a handy workaround?

In A.Pitonyak book OOME_4_0.odt, section 14.9.15 it is written:

Text table cursors are used to split and merge table cells. In general, I consider this to be the primary use of a text table cursor.

From my point of view, object TextTableCursor is useless for navigating through the cells of a text table. It is more promising to work with the getCellNames and getCellByName methods.

BTW, there seems to be a problem with parent objects in LO. I still don’t know the “normal” way to get the parent (document) for a spreadsheet sheet. :slightly_smiling_face:

The background of this question was Macro for Each Cells(y,x) in Selection (View topic) • Apache OpenOffice Community Forum.
If you want to get access to each cell of a TextTable whose area was selected by the user, or part of of a TextTable which is selected in a view, you have no choice. The selection simply is a TextTableCursor, and you need to get all the info based on it. As I wrote in the linked thread I judge the respective service to be extremely poor - but that’s as it is.

In the mentioned thread @whiteshark, @JeJe, and myself considered to use an otherwise unused (probably outdated or deprecated) property of TextTable cells, to mark a selection in a way, that an inspection of the complete table can distinguish the originally selected cells from the others.
First of all you need anyway to get the TextTable as an object from the selection. Already this step is complicated.

My example attached below is rather well working, but looks crude and is inefficient. It shows the current state, however, of what I found. It will return a list of cells designated as tablename.cellname for the current selection which may be TextRanges or TextTableCursor. If TextRanges, a cell is listed, for the ranges with a non-empty TextTable property - that’s inside a cell content.

disask70291TextTableCursorRangeCellNames.odt (15.0 KB)

1 Like

Thanks for the interesting (almost math) problem!
I am (almost) sure that the task can be solved without any actions to modify the cells. When the time comes, I’ll think about it (if someone doesn’t do it earlier :slightly_smiling_face: ).

Hmmm…
Am curious concerning

  • a solution
  • how you can be (almost) sure…

Let’s try. On the example of a complex A.Pitonyak table, the macro works.

Option Explicit

Sub Test
  Dim v
  v=GetTextTableSelectedCells
  If Not IsEmpty(v) Then
    Msgbox "Selected: " & Join(v, " ")
  Else
    Msgbox "Selected cells not found"
  End If
End Sub

' --------------------------------------------------
' Returns array of names of selected cells or Empty.
' - oDoc Text document. If is missing then ThisComponent.
Function GetTextTableSelectedCells(Optional ByVal oDoc)
  Dim arr(), i As Long, j As Long, oAcc, oAccChild
  If IsMissing(oDoc) Then oDoc=ThisComponent
  GetTextTableSelectedCells=Empty
  oDoc.CurrentController.Select oDoc.CurrentSelection   ' Cells must be on screen
  oAcc=FindAccessibleRole(oDoc.CurrentController.ComponentWindow.AccessibleContext, _   
       com.sun.star.accessibility.AccessibleRole.DOCUMENT_TEXT)
  For i=0 To oAcc.AccessibleChildCount-1
    oAccChild=oAcc.getAccessibleChild(i).AccessibleContext 
    With oAccChild
      If .AccessibleRole=com.sun.star.accessibility.AccessibleRole.TABLE Then
        If .selectedAccessibleChildCount>0 Then 
          ReDim arr(.selectedAccessibleChildCount-1)
          For j=0 To .selectedAccessibleChildCount-1
            arr(j)=.getSelectedAccessibleChild(j).AccessibleName
          Next j
          GetTextTableSelectedCells=arr
          Exit Function
        End If  
      End If
    End With      
  Next i  
End Function

' --------------------------------------------------
' Hierarchically search AccessibleContext for role value.
' - oAccContext Object (supports AccessibleContext service).
' - role        Role value.
' - index       Which item is returned by a count. If less than zero, then the search is performed in reverse order.
'               If missed or 0, then 1 is assigned. 
' 
' If the object has more than 500 child objects, then the child objects are not processed.
Function FindAccessibleRole(ByVal oAccContext, ByVal role As Long, Optional index As Long) As Object
  Dim oAccChild, oResult, i As Long, j As Long, n As Long
  If IsMissing(index) Then index=1
  If index=0 Then index=1
  FindAccessibleRole=Nothing
  n=oAccContext.AccessibleChildCount
  If n=0 Or n>500 Then Exit Function
  For i=0 To n-1
    oAccChild=oAccContext.getAccessibleChild(IIf(index<0, n-1-i, i)).AccessibleContext
    If oAccChild.AccessibleRole=role Then
      index=index+IIf(index<0, 1, -1)
      If index=0 Then 
        FindAccessibleRole=oAccChild
        Exit Function
      End If  
    End If
    ' recursion
    oResult=FindAccessibleRole(oAccChild, role, index)
    If Not (oResult Is Nothing) Then
      FindAccessibleRole=oResult
      Exit Function
    End If
  Next i
End Function

A really respectable piece of work!

However, a user should note that the solution is based on a view, and therefore depending on effects of the zoom, of window size, and of page breaks (if a table spans over them).
(I didn’t research all the expectable effects.)
The poor functionality of the TextTableCursor service remains a problem.

For the TextRange it is possible to get the table name and range address from the View Cursor. It is easy.
Also it is possible to get the information from Status Bar, but it is not safe - sometimes there is some delay than the Statusbar is rendered, so if you click to the normal text (no in table) and then quickly to the macro button, sometimes it writes the position in the table, but real cursor is already in normal text.
(both in the example)

I also tested the RangeName and it is more complicated and unsafe.
test-KL1.odt (16.3 kB)
The CharFlash is easier. But also no 100% sureness. Select the C5:I11, it is 13 cells, and the macros fail (both: your with CharFlash or @sokol92 Test from Pitonyak) → only 8 addresses is written.

And also there is apparently the bug (Libre 7.2.2.2 Win10x64).

It seems no bug. The Table3 (selected on the image below) is probably inserted table in one cell of the Table1.

As you already supposed, this was intentional. (The algorithm isn’t looking for nested tables. It coluld do, of course. The parameter pMode is included to allow for conditionally applicable enhancements of the kind. You may use it the flags way with powers of 2 (additive).

The irritations are related to the fact (bug) that a nested table isn’t in the foreground. (I tried to apply a different Background without success.

Next not so comfortable way. It is possible to test the Anchor of the Table. If some table has the Anchor in the Cell, then it is inserted table.

Sub IsTableInCell 'test the Anchors of all tables to ascertain if some table is inserted in the cell of some other table
	dim oDoc as object, oTables as object, oTable as object, i&
	oDoc=ThisComponent
	oTables=oDoc.TextTables
	for i=0 to oTables.Count-1
		oTable=oTables(i) 'one table
		if NOT isEmpty(oTable.Anchor.Cell) then 'test the Anchor of the table
			msgbox oTable.Name & " is inside " & oTable.Anchor.TextTable.Name & "." & oTable.Anchor.Cell.CellName,,"Inserted table" 
		end if
	next i
End Sub