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: */
