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 aed17efe5e2a32899cdbfec8f145a29528a48d4a
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 12:12:45 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]>

diff --git a/include/vcl/builder.hxx b/include/vcl/builder.hxx
index ae7a25af7bd5..9c6299997426 100644
--- a/include/vcl/builder.hxx
+++ b/include/vcl/builder.hxx
@@ -207,6 +207,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 94b0077f44fe..53ebd82926a3 100644
--- a/include/vcl/toolkit/svtabbx.hxx
+++ b/include/vcl/toolkit/svtabbx.hxx
@@ -40,11 +40,21 @@ enum class SvTabJustify
     AdjustCenter = static_cast<int>(SvLBoxTabFlags::ADJUST_CENTER)
 };
 
+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 );
@@ -81,6 +91,9 @@ public:
     void             SetTabJustify( sal_uInt16 nTab, SvTabJustify );
     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 d03d24dcc3dc..a27d7592117c 100644
--- a/vcl/source/treelist/svtabbx.cxx
+++ b/vcl/source/treelist/svtabbx.cxx
@@ -197,6 +197,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;
@@ -287,6 +306,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 01fbbc9c5fe1..7d961167d5ca 100644
--- a/vcl/source/window/builder.cxx
+++ b/vcl/source/window/builder.cxx
@@ -1828,6 +1828,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.find(u"model"_ustr) != rMap.end() && "GtkTreeView must 
have a model");
@@ -1900,6 +1905,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);
@@ -1923,6 +1931,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;
@@ -2597,6 +2624,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)
     {
@@ -4007,6 +4050,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