diff --git a/include/vcl/treelistentry.hxx b/include/vcl/treelistentry.hxx index 8cc808bb09c1..093fa1751d8e 100644 --- a/include/vcl/treelistentry.hxx +++ b/include/vcl/treelistentry.hxx @@ -39,13 +39,15 @@ enum class SvTLEntryFlags DISABLE_DROP = 0x0002, // is set if RequestingChildren has not set any children NO_NODEBMP = 0x0004, + // is set if this is a separator line + IS_SEPARATOR = 0x0008, // entry had or has children HAD_CHILDREN = 0x0010, SEMITRANSPARENT = 0x8000, // draw semi-transparent entry bitmaps }; namespace o3tl { - template<> struct typed_flags : is_typed_flags {}; + template<> struct typed_flags : is_typed_flags {}; } class VCL_DLLPUBLIC SvTreeListEntry diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx index 9d01a0fc7022..d8d6b1a0492d 100644 --- a/include/vcl/weld.hxx +++ b/include/vcl/weld.hxx @@ -888,6 +888,9 @@ public: insert(nullptr, -1, &rStr, &rId, nullptr, &rImage, nullptr, false, nullptr); } + virtual void insert_separator(int pos, const OUString& rId) = 0; + void append_separator(const OUString& rId) { insert_separator(-1, rId); } + void connect_changed(const Link& rLink) { m_aChangeHdl = rLink; } /* A row is "activated" when the user double clicks a treeview row. It may diff --git a/vcl/inc/strings.hrc b/vcl/inc/strings.hrc index e378b54814bd..d97ef387d9f4 100644 --- a/vcl/inc/strings.hrc +++ b/vcl/inc/strings.hrc @@ -153,6 +153,8 @@ #define STR_WIZDLG_NEXT NC_("STR_WIZDLG_NEXT", "~Next >") #define STR_WIZDLG_PREVIOUS NC_("STR_WIZDLG_PREVIOUS", "< Bac~k") +#define STR_SEPARATOR NC_("STR_SEPARATOR", "Separator") + #endif // INCLUDED_VCL_INC_STRINGS_HRC /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx index 1f0c05e292db..2b0e1c9c28cb 100644 --- a/vcl/source/app/salvtables.cxx +++ b/vcl/source/app/salvtables.cxx @@ -3258,6 +3258,71 @@ private: pEntry->AddItem(std::move(xCell)); } + void do_insert(const weld::TreeIter* pParent, int pos, const OUString* pStr, + const OUString* pId, const OUString* pIconName, + VirtualDevice* pImageSurface, const OUString* pExpanderName, + bool bChildrenOnDemand, weld::TreeIter* pRet, bool bIsSeparator) + { + disable_notify_events(); + const SalInstanceTreeIter* pVclIter = static_cast(pParent); + SvTreeListEntry* iter = pVclIter ? pVclIter->iter : nullptr; + auto nInsertPos = pos == -1 ? TREELIST_APPEND : pos; + void* pUserData; + if (pId) + { + m_aUserData.emplace_back(std::make_unique(*pId)); + pUserData = m_aUserData.back().get(); + } + else + pUserData = nullptr; + + SvTreeListEntry* pEntry = new SvTreeListEntry; + if (bIsSeparator) + pEntry->SetFlags(pEntry->GetFlags() | SvTLEntryFlags::IS_SEPARATOR); + if (pIconName || pImageSurface) + { + Image aImage(pIconName ? createImage(*pIconName) : createImage(*pImageSurface)); + pEntry->AddItem(std::make_unique(aImage, aImage, false)); + } + else + { + Image aDummy; + pEntry->AddItem(std::make_unique(aDummy, aDummy, false)); + } + if (pStr) + AddStringItem(pEntry, *pStr, 0); + pEntry->SetUserData(pUserData); + m_xTreeView->Insert(pEntry, iter, nInsertPos); + + if (pExpanderName) + { + Image aImage(createImage(*pExpanderName)); + m_xTreeView->SetExpandedEntryBmp(pEntry, aImage); + m_xTreeView->SetCollapsedEntryBmp(pEntry, aImage); + } + + if (pRet) + { + SalInstanceTreeIter* pVclRetIter = static_cast(pRet); + pVclRetIter->iter = pEntry; + } + + if (bChildrenOnDemand) + { + SvTreeListEntry* pPlaceHolder = m_xTreeView->InsertEntry("", pEntry, false, 0, nullptr); + SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pPlaceHolder); + pViewData->SetSelectable(false); + } + + if (bIsSeparator) + { + SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pEntry); + pViewData->SetSelectable(false); + } + + enable_notify_events(); + } + public: SalInstanceTreeView(SvTabListBox* pTreeView, SalInstanceBuilder* pBuilder, bool bTakeOwnership) : SalInstanceContainer(pTreeView, pBuilder, bTakeOwnership) @@ -3433,55 +3498,15 @@ public: VirtualDevice* pImageSurface, const OUString* pExpanderName, bool bChildrenOnDemand, weld::TreeIter* pRet) override { - disable_notify_events(); - const SalInstanceTreeIter* pVclIter = static_cast(pParent); - SvTreeListEntry* iter = pVclIter ? pVclIter->iter : nullptr; - auto nInsertPos = pos == -1 ? TREELIST_APPEND : pos; - void* pUserData; - if (pId) - { - m_aUserData.emplace_back(std::make_unique(*pId)); - pUserData = m_aUserData.back().get(); - } - else - pUserData = nullptr; + do_insert(pParent, pos, pStr, pId, pIconName, pImageSurface, pExpanderName, + bChildrenOnDemand, pRet, false); + } - SvTreeListEntry* pEntry = new SvTreeListEntry; - if (pIconName || pImageSurface) - { - Image aImage(pIconName ? createImage(*pIconName) : createImage(*pImageSurface)); - pEntry->AddItem(std::make_unique(aImage, aImage, false)); - } - else - { - Image aDummy; - pEntry->AddItem(std::make_unique(aDummy, aDummy, false)); - } - if (pStr) - AddStringItem(pEntry, *pStr, 0); - pEntry->SetUserData(pUserData); - m_xTreeView->Insert(pEntry, iter, nInsertPos); - - if (pExpanderName) - { - Image aImage(createImage(*pExpanderName)); - m_xTreeView->SetExpandedEntryBmp(pEntry, aImage); - m_xTreeView->SetCollapsedEntryBmp(pEntry, aImage); - } - - if (pRet) - { - SalInstanceTreeIter* pVclRetIter = static_cast(pRet); - pVclRetIter->iter = pEntry; - } - - if (bChildrenOnDemand) - { - SvTreeListEntry* pPlaceHolder = m_xTreeView->InsertEntry("", pEntry, false, 0, nullptr); - SvViewDataEntry* pViewData = m_xTreeView->GetViewDataEntry(pPlaceHolder); - pViewData->SetSelectable(false); - } - enable_notify_events(); + virtual void insert_separator(int pos, const OUString& /*rId*/) override + { + OUString sSep(VclResId(STR_SEPARATOR)); + do_insert(nullptr, pos, &sSep, nullptr, nullptr, nullptr, nullptr, + false, nullptr, true); } virtual void diff --git a/vcl/source/treelist/svlbitm.cxx b/vcl/source/treelist/svlbitm.cxx index da5074283545..6467efcbf8ff 100644 --- a/vcl/source/treelist/svlbitm.cxx +++ b/vcl/source/treelist/svlbitm.cxx @@ -25,6 +25,7 @@ #include #include #include +#include struct SvLBoxButtonData_Impl { @@ -196,12 +197,35 @@ SvLBoxItemType SvLBoxString::GetType() const return SvLBoxItemType::String; } +namespace +{ + void drawSeparator(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRegion) + { + Color aOldLineColor(rRenderContext.GetLineColor()); + const StyleSettings& rStyle = rRenderContext.GetSettings().GetStyleSettings(); + Point aTmpPos = rRegion.TopLeft(); + Size aSize = rRegion.GetSize(); + aTmpPos.AdjustY(aSize.Height() / 2 ); + rRenderContext.SetLineColor(rStyle.GetShadowColor()); + rRenderContext.DrawLine(aTmpPos, Point(aSize.Width() + aTmpPos.X(), aTmpPos.Y())); + rRenderContext.SetLineColor(aOldLineColor); + } +} + void SvLBoxString::Paint( const Point& rPos, SvTreeListBox& rDev, vcl::RenderContext& rRenderContext, const SvViewDataEntry* /*pView*/, const SvTreeListEntry& rEntry) { - Size aSize; DrawTextFlags nStyle = (rDev.IsEnabled() && !mbDisabled) ? DrawTextFlags::NONE : DrawTextFlags::Disable; + if (bool(rEntry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR)) + { + Point aStartPos(0, rPos.Y() - 2); + tools::Rectangle aRegion(aStartPos, Size(rDev.GetSizePixel().Width(), 4)); + drawSeparator(rRenderContext, aRegion); + return; + } + + Size aSize; if (rDev.IsEntryMnemonicsEnabled()) nStyle |= DrawTextFlags::Mnemonic; if (rDev.TextCenterAndClipEnabled()) @@ -267,6 +291,13 @@ void SvLBoxString::InitViewData( if( !pViewData ) pViewData = pView->GetViewDataItem( pEntry, this ); + if (bool(pEntry->GetFlags() & SvTLEntryFlags::IS_SEPARATOR)) + { + pViewData->mnWidth = -1; + pViewData->mnHeight = 0; + return; + } + if (mbEmphasized) { pView->Push(); diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx index c0bc216f0011..7e851797806c 100644 --- a/vcl/unx/gtk3/gtk3gtkinst.cxx +++ b/vcl/unx/gtk3/gtk3gtkinst.cxx @@ -9050,6 +9050,31 @@ tools::Rectangle get_row_area(GtkTreeView* pTreeView, GList* pColumns, GtkTreePa return aRet; } +struct GtkTreeRowReferenceDeleter +{ + void operator()(GtkTreeRowReference* p) const + { + gtk_tree_row_reference_free(p); + } +}; + +bool separator_function(GtkTreePath* path, const std::vector>& rSeparatorRows) +{ + bool bFound = false; + for (auto& a : rSeparatorRows) + { + GtkTreePath* seppath = gtk_tree_row_reference_get_path(a.get()); + if (seppath) + { + bFound = gtk_tree_path_compare(path, seppath) == 0; + gtk_tree_path_free(seppath); + } + if (bFound) + break; + } + return bFound; +} + class GtkInstanceTreeView : public GtkInstanceContainer, public virtual weld::TreeView { private: @@ -9073,6 +9098,8 @@ private: // currently expanding parent that logically, but not currently physically, // contain placeholders o3tl::sorted_vector m_aExpandingPlaceHolderParents; + // which rows are separators (rare) + std::vector> m_aSeparatorRows; std::vector m_aSavedSortTypes; std::vector m_aSavedSortColumns; std::vector m_aViewColToModelCol; @@ -9183,6 +9210,20 @@ private: } } + bool separator_function(GtkTreePath* path) + { + return ::separator_function(path, m_aSeparatorRows); + } + + static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget) + { + GtkInstanceTreeView* pThis = static_cast(widget); + GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter); + bool bRet = pThis->separator_function(path); + gtk_tree_path_free(path); + return bRet; + } + OUString get(const GtkTreeIter& iter, int col) const { GtkTreeModel *pModel = GTK_TREE_MODEL(m_pTreeStore); @@ -9942,6 +9983,20 @@ public: enable_notify_events(); } + virtual void insert_separator(int pos, const OUString& rId) override + { + disable_notify_events(); + GtkTreeIter iter; + if (!gtk_tree_view_get_row_separator_func(m_pTreeView)) + gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr); + insert_row(iter, nullptr, pos, &rId, nullptr, nullptr, nullptr, nullptr); + GtkTreeModel* pTreeModel = GTK_TREE_MODEL(m_pTreeStore); + GtkTreePath* pPath = gtk_tree_model_get_path(pTreeModel, &iter); + m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(pTreeModel, pPath)); + gtk_tree_path_free(pPath); + enable_notify_events(); + } + virtual void set_font_color(int pos, const Color& rColor) override { GtkTreeIter iter; @@ -10018,6 +10073,8 @@ public: virtual void clear() override { disable_notify_events(); + gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr); + m_aSeparatorRows.clear(); gtk_tree_store_clear(m_pTreeStore); enable_notify_events(); } @@ -12668,14 +12725,6 @@ GtkBuilder* makeComboBoxBuilder() return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr()); } -struct GtkTreeRowReferenceDeleter -{ - void operator()(GtkTreeRowReference* p) const - { - gtk_tree_row_reference_free(p); - } -}; - // pop down the toplevel combobox menu when something is activated from a custom // submenu, i.e. wysiwyg style menu class CustomRenderMenuButtonHelper : public MenuHelper @@ -13077,19 +13126,7 @@ private: bool separator_function(GtkTreePath* path) { - bool bFound = false; - for (auto& a : m_aSeparatorRows) - { - GtkTreePath* seppath = gtk_tree_row_reference_get_path(a.get()); - if (seppath) - { - bFound = gtk_tree_path_compare(path, seppath) == 0; - gtk_tree_path_free(seppath); - } - if (bFound) - break; - } - return bFound; + return ::separator_function(path, m_aSeparatorRows); } bool separator_function(int pos)