include/vcl/builder.hxx         |   17 ++++++++++++++
 include/vcl/toolkit/svtabbx.hxx |   13 +++++++++++
 vcl/source/treelist/svtabbx.cxx |   20 +++++++++++++++++
 vcl/source/window/builder.cxx   |   47 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 97 insertions(+)

New commits:
commit cf30457071e8d7f72139652302b3210869085f95
Author:     Caolán McNamara <[email protected]>
AuthorDate: Tue Feb 17 20:31:02 2026 +0000
Commit:     Caolán McNamara <[email protected]>
CommitDate: Thu Feb 19 14:46:39 2026 +0100

    extract from .ui the TreeView structure, tree, treegrid, grid or listbox
    
    The renderer/column structure in .ui files for GtkTreeView is a set of
    compromise rules of expressing a layout in gtk terms which the vcl
    equivalent is also able to do.
    
    The ui files end up containing information as to what each TreeView is
    limited to, whether it will be used as a tree or a list and whether
    there are multiple columns or not, so here we extract enough of that
    information in the generic loader to determine what type of role this
    TreeView has.
    
    Change-Id: I7758a3cdab070c013301d519ab2ffbfb97010183
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199579
    Reviewed-by: Miklos Vajna <[email protected]>
    Tested-by: Caolán McNamara <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199711
    Tested-by: Jenkins
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/include/vcl/builder.hxx b/include/vcl/builder.hxx
index 73fea3f62337..f303073a744c 100644
--- a/include/vcl/builder.hxx
+++ b/include/vcl/builder.hxx
@@ -194,6 +194,23 @@ private:
 
         sal_uInt16 m_nLastMenuItemId;
 
+        /* The renderer/column structure in .ui files for GtkTreeView is a set
+           of compromise rules of expressing a layout in gtk terms which the
+           vcl equivalent is also able to do.
+
+           The GtkInstanceTreeView ctor has some more details of that.
+
+           The ui files end up containing information as to what each TreeView
+           is limited to, whether it will be used as a tree or a list and
+           whether there are multiple columns or not, so here we extract enough
+           of that information in the generic loader to determine what type of
+           role this TreeView has.
+        */
+        sal_uInt16 m_nTreeViewRenderers;
+        sal_uInt16 m_nTreeViewExpanders;
+        sal_uInt16 m_nTreeViewColumnCount;
+        bool m_bTreeViewSeenTextInColumn;
+
         VclParserState();
     };
 
diff --git a/include/vcl/toolkit/svtabbx.hxx b/include/vcl/toolkit/svtabbx.hxx
index 0beddea9cf51..819d8ab75c89 100644
--- a/include/vcl/toolkit/svtabbx.hxx
+++ b/include/vcl/toolkit/svtabbx.hxx
@@ -35,11 +35,21 @@
 #include <memory>
 #include <vector>
 
+enum class SvTabListBoxRole
+{
+    Unknown,
+    Tree,       // hierarchical, single-column
+    TreeGrid,   // hierarchical, multi-column
+    ListBox,    // flat, single-column
+    Grid        // flat, multi-column
+};
+
 class UNLESS_MERGELIBS_MORE(VCL_DLLPUBLIC) SvTabListBox : public SvTreeListBox
 {
 private:
     std::vector<SvLBoxTab>      mvTabList;
     OUString                    aCurEntry;
+    SvTabListBoxRole            m_eRole;
 
 protected:
     static std::u16string_view  GetToken( std::u16string_view sStr, sal_Int32 
&nIndex );
@@ -75,6 +85,9 @@ public:
     void             SetTabAlignCenter(sal_uInt16 nTab);
     void             SetTabEditable( sal_uInt16 nTab, bool bEditable );
 
+    void             SetRole(SvTabListBoxRole e) { m_eRole = e; }
+    SvTabListBoxRole GetRole() const { return m_eRole; }
+
     virtual void     DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) 
override;
 };
 
diff --git a/vcl/source/treelist/svtabbx.cxx b/vcl/source/treelist/svtabbx.cxx
index 805cd7d02808..c21102898dd0 100644
--- a/vcl/source/treelist/svtabbx.cxx
+++ b/vcl/source/treelist/svtabbx.cxx
@@ -195,6 +195,25 @@ void SvTabListBox::DumpAsPropertyTree(tools::JsonWriter& 
rJsonWriter)
 
     rJsonWriter.put("singleclickactivate", GetActivateOnSingleClick());
 
+    switch (m_eRole)
+    {
+        case SvTabListBoxRole::Unknown:
+            assert(false && "this shouldn't be possible on load from .ui");
+            break;
+        case SvTabListBoxRole::Tree:
+            rJsonWriter.put("role", "tree");
+            break;
+        case SvTabListBoxRole::TreeGrid:
+            rJsonWriter.put("role", "treegrid");
+            break;
+        case SvTabListBoxRole::ListBox:
+            rJsonWriter.put("role", "listbox");
+            break;
+        case SvTabListBoxRole::Grid:
+            rJsonWriter.put("role", "grid");
+            break;
+    }
+
     bool bCheckButtons = static_cast<int>(nTreeFlags & SvTreeFlags::CHKBTN);
 
     bool isRadioButton = false;
@@ -267,6 +286,7 @@ void SvTabListBox::InitEntry(SvTreeListEntry* pEntry, const 
OUString& rStr,
 
 SvTabListBox::SvTabListBox( vcl::Window* pParent, WinBits nBits )
     : SvTreeListBox( pParent, nBits )
+    , m_eRole(SvTabListBoxRole::Unknown)
 {
     SetHighlightRange();    // select full width
 }
diff --git a/vcl/source/window/builder.cxx b/vcl/source/window/builder.cxx
index afe6aaf68bb2..211eedb0bb8d 100644
--- a/vcl/source/window/builder.cxx
+++ b/vcl/source/window/builder.cxx
@@ -1560,6 +1560,11 @@ VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window 
*pParent, const OUString
     }
     else if (name == "GtkTreeView")
     {
+        m_pVclParserState->m_nTreeViewRenderers = 0;
+        m_pVclParserState->m_nTreeViewExpanders = 0;
+        m_pVclParserState->m_nTreeViewColumnCount = 0;
+        m_pVclParserState->m_bTreeViewSeenTextInColumn = false;
+
         if (!isLegacy())
         {
             assert(rMap.contains(u"model"_ustr) && "GtkTreeView must have a 
model");
@@ -1631,6 +1636,9 @@ VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window 
*pParent, const OUString
     }
     else if (name == "GtkTreeViewColumn")
     {
+        m_pVclParserState->m_nTreeViewColumnCount++;
+        m_pVclParserState->m_bTreeViewSeenTextInColumn = false;
+
         if (!isLegacy())
         {
             SvHeaderTabListBox* pTreeView = 
dynamic_cast<SvHeaderTabListBox*>(pParent);
@@ -1654,6 +1662,25 @@ VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window 
*pParent, const OUString
             }
         }
     }
+    // The somewhat convoluted GtkCellRenderer* rules here are intended to
+    // match those of the GtkInstanceTreeView so we can take advantage of the
+    // consistency of the .ui format to determine the role of a GtkTreeView in
+    // terms of tree/treegrid/grid/listbox
+    else if (name == "GtkCellRendererText")
+    {
+        m_pVclParserState->m_nTreeViewRenderers++;
+        m_pVclParserState->m_bTreeViewSeenTextInColumn = true;
+    }
+    else if (name == "GtkCellRendererPixbuf" || name == 
"GtkCellRendererToggle")
+    {
+        m_pVclParserState->m_nTreeViewRenderers++;
+        // leading non-text renderers in the first column are expander 
decorations
+        if (m_pVclParserState->m_nTreeViewColumnCount == 1
+            && !m_pVclParserState->m_bTreeViewSeenTextInColumn)
+        {
+            m_pVclParserState->m_nTreeViewExpanders++;
+        }
+    }
     else if (name == "GtkLabel")
     {
         WinBits nWinStyle = WB_CENTER|WB_VCENTER|WB_3DLOOK;
@@ -2331,6 +2358,22 @@ void VclBuilder::tweakInsertedChild(vcl::Window 
*pParent, vcl::Window* pCurrentC
 {
     assert(pCurrentChild);
 
+    if (SvTabListBox* pTabListBox = dynamic_cast<SvTabListBox*>(pCurrentChild))
+    {
+        const bool bTree(pTabListBox->GetStyle() & (WB_HASBUTTONS | 
WB_HASBUTTONSATROOT));
+        const sal_uInt16 nRealColumns = 
m_pVclParserState->m_nTreeViewRenderers -
+                                        
m_pVclParserState->m_nTreeViewExpanders;
+        const bool bMultiColumn = nRealColumns > 1;
+        if (bTree && bMultiColumn)
+            pTabListBox->SetRole(SvTabListBoxRole::TreeGrid);
+        else if (bTree)
+            pTabListBox->SetRole(SvTabListBoxRole::Tree);
+        else if (bMultiColumn)
+            pTabListBox->SetRole(SvTabListBoxRole::Grid);
+        else
+            pTabListBox->SetRole(SvTabListBoxRole::ListBox);
+    }
+
     //Select the first page if it's a notebook
     if (pCurrentChild->GetType() == WindowType::TABCONTROL)
     {
@@ -3809,6 +3852,10 @@ void VclBuilder::mungeTextBuffer(VclMultiLineEdit 
&rTarget, const TextBuffer &rT
 VclBuilder::VclParserState::VclParserState()
     : m_nLastToolbarId(0)
     , m_nLastMenuItemId(0)
+    , m_nTreeViewRenderers(0)
+    , m_nTreeViewExpanders(0)
+    , m_nTreeViewColumnCount(0)
+    , m_bTreeViewSeenTextInColumn(false)
 {}
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to