include/vcl/builder.hxx         |   18 ++++++++++++
 include/vcl/toolkit/svtabbx.hxx |   13 ++++++++
 vcl/source/treelist/svtabbx.cxx |   20 +++++++++++++
 vcl/source/window/builder.cxx   |   60 ++++++++++++++++++++++++++++++++++++++--
 4 files changed, 109 insertions(+), 2 deletions(-)

New commits:
commit 54f0175604e7e6f5f483bb9d0f79d0ddc1c96750
Author:     Caolán McNamara <[email protected]>
AuthorDate: Thu Feb 19 16:55:18 2026 +0000
Commit:     Caolán McNamara <[email protected]>
CommitDate: Fri Feb 20 21:13:45 2026 +0100

    If the treeview has a header or multiple columns it has to be a [tree]grid
    
    tree/listbox don't have headers, so reshuffle to only allow those for
    the single column and headerless case.
    
    Change-Id: I49cac527f22617847a7227979158384f3b7b4247
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199748
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Miklos Vajna <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199898
    Reviewed-by: Caolán McNamara <[email protected]>

diff --git a/include/vcl/builder.hxx b/include/vcl/builder.hxx
index f303073a744c..dabc56f59991 100644
--- a/include/vcl/builder.hxx
+++ b/include/vcl/builder.hxx
@@ -210,6 +210,7 @@ private:
         sal_uInt16 m_nTreeViewExpanders;
         sal_uInt16 m_nTreeViewColumnCount;
         bool m_bTreeViewSeenTextInColumn;
+        bool m_bTreeHasHeader;
 
         VclParserState();
     };
diff --git a/vcl/source/window/builder.cxx b/vcl/source/window/builder.cxx
index 1f73ba20415f..8308aebe23e7 100644
--- a/vcl/source/window/builder.cxx
+++ b/vcl/source/window/builder.cxx
@@ -1753,6 +1753,7 @@ VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window 
*pParent, const OUString
         m_pVclParserState->m_nTreeViewExpanders = 0;
         m_pVclParserState->m_nTreeViewColumnCount = 0;
         m_pVclParserState->m_bTreeViewSeenTextInColumn = false;
+        m_pVclParserState->m_bTreeHasHeader = false;
 
         if (!isLegacy())
         {
@@ -1787,8 +1788,8 @@ VclPtr<vcl::Window> VclBuilder::makeObject(vcl::Window 
*pParent, const OUString
         else
         {
             VclPtr<SvTabListBox> xBox;
-            bool bHeadersVisible = extractHeadersVisible(rMap);
-            if (bHeadersVisible)
+            m_pVclParserState->m_bTreeHasHeader = extractHeadersVisible(rMap);
+            if (m_pVclParserState->m_bTreeHasHeader)
             {
                 VclPtr<VclVBox> xContainer = 
VclPtr<VclVBox>::Create(pRealParent);
                 OUString containerid(id + "-container");
@@ -2552,15 +2553,22 @@ void VclBuilder::tweakInsertedChild(vcl::Window 
*pParent, vcl::Window* pCurrentC
         const bool bTree(pTabListBox->GetStyle() & (WB_HASBUTTONS | 
WB_HASBUTTONSATROOT));
         const sal_uInt16 nRealColumns = 
m_pVclParserState->m_nTreeViewRenderers -
                                         
m_pVclParserState->m_nTreeViewExpanders;
+        const bool bHasHeader = m_pVclParserState->m_bTreeHasHeader;
         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);
+        if (bHasHeader || bMultiColumn)
+        {
+            if (bTree)
+                pTabListBox->SetRole(SvTabListBoxRole::TreeGrid);
+            else
+                pTabListBox->SetRole(SvTabListBoxRole::Grid);
+        }
         else
-            pTabListBox->SetRole(SvTabListBoxRole::ListBox);
+        {
+            if (bTree)
+                pTabListBox->SetRole(SvTabListBoxRole::Tree);
+            else
+                pTabListBox->SetRole(SvTabListBoxRole::ListBox);
+        }
     }
 
     //Select the first page if it's a notebook
@@ -4044,6 +4052,7 @@ VclBuilder::VclParserState::VclParserState()
     , m_nTreeViewExpanders(0)
     , m_nTreeViewColumnCount(0)
     , m_bTreeViewSeenTextInColumn(false)
+    , m_bTreeHasHeader(false)
 {}
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
commit 9fa3d6f1af00dfd7c5aa52d6bc2ca400c38e999e
Author:     Caolán McNamara <[email protected]>
AuthorDate: Tue Feb 17 20:31:02 2026 +0000
Commit:     Caolán McNamara <[email protected]>
CommitDate: Fri Feb 20 21:13:37 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/+/199897
    Reviewed-by: Caolán McNamara <[email protected]>
    Tested-by: Jenkins CollaboraOffice <[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 dcea878ea85e..7a93d0fd7218 100644
--- a/include/vcl/toolkit/svtabbx.hxx
+++ b/include/vcl/toolkit/svtabbx.hxx
@@ -34,11 +34,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 b6ce31c83b5d..0b24dbd49337 100644
--- a/vcl/source/treelist/svtabbx.cxx
+++ b/vcl/source/treelist/svtabbx.cxx
@@ -198,6 +198,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;
@@ -270,6 +289,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 3486f1c0d118..1f73ba20415f 100644
--- a/vcl/source/window/builder.cxx
+++ b/vcl/source/window/builder.cxx
@@ -1749,6 +1749,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");
@@ -1820,6 +1825,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);
@@ -1843,6 +1851,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;
@@ -2520,6 +2547,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)
     {
@@ -3997,6 +4040,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