From c2253f587b68f7277f30259160155f2bbf7adc78 Mon Sep 17 00:00:00 2001 From: Mike Kaganski Date: Mon, 30 May 2022 10:37:53 +0300 Subject: [PATCH] Introduce weld::IconView::insert_separator MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Needed to eventual re-implementation of starmath's SmElementsControl using IconView. This required re-implementation of IconViewImpl, to layout entries by iteration, because now it's impossible to find an entry position just based on its index. This coincidentally fixed some visual glitches in non-gtk IconView implementation from commit 5813660e7bfe128ac076e592fe31de64a6863780 Author Szymon Kłos Date Tue Feb 16 16:03:30 2016 +0100 icon view for RemoteFilesDialog where any selected element could become first in row when scrolling. SvTreeListBox::SetEntryHeight taking a SvTreeListEntry const* had to be renamed to CalcEntryHeight, to avoid both virtual and non-virtual overloads, additionally having different accessibility. A TODO is implement separators in GtkInstanceIconView. I couldn't find a GTK API for separators in IconView, so possibly a workaround would be needed with some non-selectable narrow elements. Change-Id: Ie8dc35d94049a1c48e4eb49697681ffbe93c17f4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135112 Tested-by: Jenkins Reviewed-by: Mike Kaganski --- include/vcl/toolkit/treelistbox.hxx | 2 +- include/vcl/weld.hxx | 4 + vcl/inc/iconview.hxx | 7 +- vcl/inc/jsdialog/jsdialogbuilder.hxx | 3 + vcl/inc/salvtables.hxx | 2 + vcl/inc/svimpbox.hxx | 2 +- vcl/jsdialog/jsdialogbuilder.cxx | 6 + vcl/source/app/salvtables.cxx | 23 +- vcl/source/treelist/iconview.cxx | 73 +++-- vcl/source/treelist/iconviewimpl.cxx | 523 +++++++++++++++++++++-------------- vcl/source/treelist/iconviewimpl.hxx | 28 ++ vcl/source/treelist/svimpbox.cxx | 4 +- vcl/source/treelist/treelistbox.cxx | 8 +- vcl/unx/gtk3/gtkinst.cxx | 6 + 14 files changed, 442 insertions(+), 249 deletions(-) diff --git a/include/vcl/toolkit/treelistbox.hxx b/include/vcl/toolkit/treelistbox.hxx index 685540b2d858..7e21dfda21e7 100644 --- a/include/vcl/toolkit/treelistbox.hxx +++ b/include/vcl/toolkit/treelistbox.hxx @@ -476,7 +476,7 @@ public: protected: - VCL_DLLPRIVATE void SetEntryHeight( SvTreeListEntry const * pEntry ); + virtual void CalcEntryHeight(SvTreeListEntry const* pEntry); void AdjustEntryHeight( const Image& rBmp ); VCL_DLLPRIVATE void AdjustEntryHeight(); diff --git a/include/vcl/weld.hxx b/include/vcl/weld.hxx index dba513870818..528405643716 100644 --- a/include/vcl/weld.hxx +++ b/include/vcl/weld.hxx @@ -1361,6 +1361,8 @@ public: const VirtualDevice* pIcon, TreeIter* pRet) = 0; + virtual void insert_separator(int pos, const OUString* pId) = 0; + void append(const OUString& rId, const OUString& rStr, const OUString& rImage) { insert(-1, &rStr, &rId, &rImage, nullptr); @@ -1371,6 +1373,8 @@ public: insert(-1, &rStr, &rId, pImage, nullptr); } + void append_separator(const OUString& rId) { insert_separator(-1, &rId); } + void connect_selection_changed(const Link& rLink) { m_aSelectionChangeHdl = rLink; diff --git a/vcl/inc/iconview.hxx b/vcl/inc/iconview.hxx index c1e62bc2ec65..971a638cc6ef 100644 --- a/vcl/inc/iconview.hxx +++ b/vcl/inc/iconview.hxx @@ -27,15 +27,20 @@ class IconView final : public SvTreeListBox public: IconView(vcl::Window* pParent, WinBits nBits); + Size GetEntrySize(const SvTreeListEntry&) const; + virtual void Resize() override; - virtual tools::Rectangle GetFocusRect(const SvTreeListEntry*, tools::Long nEntryPos) override; + virtual tools::Rectangle GetFocusRect(const SvTreeListEntry*, tools::Long) override; void PaintEntry(SvTreeListEntry&, tools::Long nX, tools::Long nY, vcl::RenderContext& rRenderContext); virtual FactoryFunction GetUITestFactory() const override; virtual void DumpAsPropertyTree(tools::JsonWriter& rJsonWriter) override; + +protected: + virtual void CalcEntryHeight(SvTreeListEntry const* pEntry) override; }; #endif diff --git a/vcl/inc/jsdialog/jsdialogbuilder.hxx b/vcl/inc/jsdialog/jsdialogbuilder.hxx index a25aeac55c10..5e7437b2725a 100644 --- a/vcl/inc/jsdialog/jsdialogbuilder.hxx +++ b/vcl/inc/jsdialog/jsdialogbuilder.hxx @@ -670,6 +670,9 @@ public: virtual void insert(int pos, const OUString* pStr, const OUString* pId, const VirtualDevice* pIcon, weld::TreeIter* pRet) override; + + virtual void insert_separator(int pos, const OUString* pId) override; + virtual void clear() override; virtual void select(int pos) override; virtual void unselect(int pos) override; diff --git a/vcl/inc/salvtables.hxx b/vcl/inc/salvtables.hxx index 45e9700ad2c8..9628744fb087 100644 --- a/vcl/inc/salvtables.hxx +++ b/vcl/inc/salvtables.hxx @@ -1774,6 +1774,8 @@ public: virtual void insert(int pos, const OUString* pStr, const OUString* pId, const VirtualDevice* pIcon, weld::TreeIter* pRet) override; + virtual void insert_separator(int pos, const OUString* pId) override; + virtual void connect_query_tooltip(const Link& rLink) override; virtual OUString get_selected_id() const override; diff --git a/vcl/inc/svimpbox.hxx b/vcl/inc/svimpbox.hxx index 994a25f095ee..1acd895743fd 100644 --- a/vcl/inc/svimpbox.hxx +++ b/vcl/inc/svimpbox.hxx @@ -92,7 +92,6 @@ private: SvTreeListEntry* m_pActiveEntry; SvLBoxTab* m_pActiveTab; - VclPtr m_aHorSBar; VclPtr m_aScrBarBox; ::vcl::AccessibleFactoryAccess @@ -185,6 +184,7 @@ private: protected: VclPtr m_pView; + VclPtr m_aHorSBar; VclPtr m_aVerSBar; SvTreeListEntry* m_pCursor; SvTreeListEntry* m_pCursorOld; diff --git a/vcl/jsdialog/jsdialogbuilder.cxx b/vcl/jsdialog/jsdialogbuilder.cxx index 3648d33b3ebc..ab1fdf7636c0 100644 --- a/vcl/jsdialog/jsdialogbuilder.cxx +++ b/vcl/jsdialog/jsdialogbuilder.cxx @@ -1702,6 +1702,12 @@ void JSIconView::insert(int pos, const OUString* pStr, const OUString* pId, sendUpdate(); } +void JSIconView::insert_separator(int pos, const OUString* pId) +{ + SalInstanceIconView::insert_separator(pos, pId); + sendUpdate(); +} + void JSIconView::clear() { SalInstanceIconView::clear(); diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx index 4432b4c5afe6..ba759d0fa3d5 100644 --- a/vcl/source/app/salvtables.cxx +++ b/vcl/source/app/salvtables.cxx @@ -4437,7 +4437,7 @@ void SalInstanceTreeView::set_image(SvTreeListEntry* pEntry, const Image& rImage static_cast(rItem).SetBitmap2(rImage); } - m_xTreeView->SetEntryHeight(pEntry); + m_xTreeView->CalcEntryHeight(pEntry); InvalidateModelEntry(pEntry); } @@ -5309,7 +5309,11 @@ SalInstanceIconView::SalInstanceIconView(::IconView* pIconView, SalInstanceBuild } int SalInstanceIconView::get_item_width() const { return m_xIconView->GetEntryWidth(); } -void SalInstanceIconView::set_item_width(int width) { m_xIconView->SetEntryWidth(width); } +void SalInstanceIconView::set_item_width(int width) +{ + m_xIconView->SetEntryWidth(width); + m_xIconView->Resize(); +} void SalInstanceIconView::freeze() { @@ -5407,6 +5411,21 @@ void SalInstanceIconView::insert(int pos, const OUString* pStr, const OUString* enable_notify_events(); } +void SalInstanceIconView::insert_separator(int pos, const OUString* /* pId */) +{ + const auto nInsertPos = pos == -1 ? TREELIST_APPEND : pos; + const OUString sSep(VclResId(STR_SEPARATOR)); + SvTreeListEntry* pEntry = new SvTreeListEntry; + pEntry->SetFlags(pEntry->GetFlags() | SvTLEntryFlags::IS_SEPARATOR); + const Image aDummy; + pEntry->AddItem(std::make_unique(aDummy, aDummy, false)); + pEntry->AddItem(std::make_unique(sSep)); + pEntry->SetUserData(nullptr); + m_xIconView->Insert(pEntry, nullptr, nInsertPos); + SvViewDataEntry* pViewData = m_xIconView->GetViewDataEntry(pEntry); + pViewData->SetSelectable(false); +} + IMPL_LINK(SalInstanceIconView, TooltipHdl, const HelpEvent&, rHEvt, bool) { if (notify_events_disabled()) diff --git a/vcl/source/treelist/iconview.cxx b/vcl/source/treelist/iconview.cxx index f54616e7fad7..6b8a193fde11 100644 --- a/vcl/source/treelist/iconview.cxx +++ b/vcl/source/treelist/iconview.cxx @@ -28,17 +28,54 @@ #include #include +namespace +{ +const int separatorHeight = 10; +const int nSpacing = 5; // 5 pixels from top, from bottom, between icon and label +} + IconView::IconView(vcl::Window* pParent, WinBits nBits) : SvTreeListBox(pParent, nBits) { nColumns = 1; mbCenterAndClipText = true; - SetEntryHeight(100); SetEntryWidth(100); pImpl.reset(new IconViewImpl(this, GetModel(), GetStyle())); } +Size IconView::GetEntrySize(const SvTreeListEntry& entry) const +{ + if (entry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR) + return { GetEntryWidth() * GetColumnsCount(), separatorHeight }; + return { GetEntryWidth(), GetEntryHeight() }; +} + +void IconView::CalcEntryHeight(SvTreeListEntry const* pEntry) +{ + int nHeight = nSpacing * 2; + SvViewDataEntry* pViewData = GetViewDataEntry(pEntry); + const size_t nCount = pEntry->ItemCount(); + bool bHasIcon = false; + for (size_t nCur = 0; nCur < nCount; ++nCur) + { + nHeight += SvLBoxItem::GetHeight(pViewData, nCur); + + if (!bHasIcon && pEntry->GetItem(nCur).GetType() == SvLBoxItemType::ContextBmp) + bHasIcon = true; + } + + if (bHasIcon && nCount > 1) + nHeight += nSpacing; // between icon and label + + if (nHeight > nEntryHeight) + { + nEntryHeight = nHeight; + Control::SetFont(GetFont()); + pImpl->SetEntryHeight(); + } +} + void IconView::Resize() { Size aBoxSize = Control::GetOutputSizePixel(); @@ -46,46 +83,24 @@ void IconView::Resize() if (!aBoxSize.Width()) return; - nColumns = aBoxSize.Width() / nEntryWidth; + nColumns = nEntryWidth ? aBoxSize.Width() / nEntryWidth : 1; SvTreeListBox::Resize(); } -tools::Rectangle IconView::GetFocusRect(const SvTreeListEntry*, tools::Long nEntryPos) +tools::Rectangle IconView::GetFocusRect(const SvTreeListEntry* pEntry, tools::Long) { - Size aSize; - aSize.setHeight(nEntryHeight); - aSize.setWidth(nEntryWidth); - - Point aPos; - aPos.setX(0); - aPos.setY(0); - - tools::Rectangle aRect; - - short nCols = GetColumnsCount(); - - if (nCols) - { - aPos.setY((nEntryPos / nCols) * nEntryHeight); - aPos.setX((nEntryPos % nCols) * nEntryWidth); - } - - aRect.SetPos(aPos); - aRect.SetSize(aSize); - - return aRect; + return { GetEntryPosition(pEntry), GetEntrySize(*pEntry) }; } void IconView::PaintEntry(SvTreeListEntry& rEntry, tools::Long nX, tools::Long nY, vcl::RenderContext& rRenderContext) { - const int nSpacing = 5; // 5 pixels from top, from bottom, between icon and label - pImpl->UpdateContextBmpWidthMax(&rEntry); - short nTempEntryHeight = GetEntryHeight(); - short nTempEntryWidth = GetEntryWidth(); + const Size entrySize = GetEntrySize(rEntry); + short nTempEntryHeight = entrySize.Height(); + short nTempEntryWidth = entrySize.Width(); Point aEntryPos(nX, nY); diff --git a/vcl/source/treelist/iconviewimpl.cxx b/vcl/source/treelist/iconviewimpl.cxx index 319a0d7c899e..c350eb49c151 100644 --- a/vcl/source/treelist/iconviewimpl.cxx +++ b/vcl/source/treelist/iconviewimpl.cxx @@ -18,6 +18,7 @@ */ #include +#include #include #include #include "iconviewimpl.hxx" @@ -27,29 +28,177 @@ IconViewImpl::IconViewImpl( SvTreeListBox* pTreeListBox, SvTreeList* pTreeList, { } -void IconViewImpl::CursorUp() +static bool IsSeparator(const SvTreeListEntry* entry) { - if (!m_pStartEntry) + return entry && entry->GetFlags() & SvTLEntryFlags::IS_SEPARATOR; +} + +Size IconViewImpl::GetEntrySize(const SvTreeListEntry& entry) const +{ + return static_cast(m_pView.get())->GetEntrySize(entry); +} + +void IconViewImpl::IterateVisibleEntryAreas(const IterateEntriesFunc& f, bool fromStartEntry) const +{ + tools::Long x = 0, y = 0; + short column = 0; + const tools::Long rowWidth = m_pView->GetEntryWidth() * m_pView->GetColumnsCount(); + tools::Long nPrevHeight = 0; + for (auto entry = fromStartEntry ? m_pStartEntry : m_pView->FirstVisible(); entry; + entry = m_pView->NextVisible(entry)) + { + const Size s = GetEntrySize(*entry); + if (x >= rowWidth || IsSeparator(entry)) + { + column = 0; + x = 0; + y += nPrevHeight; + } + EntryAreaInfo info{ entry, column, tools::Rectangle{ Point{ x, y }, s } }; + const auto result = f(info); + if (result == CallbackResult::Stop) + return; + ++column; + x += s.Width(); + nPrevHeight = s.Height(); + } +} + +tools::Long IconViewImpl::GetEntryRow(const SvTreeListEntry* entry) const +{ + tools::Long nEntryRow = -1; + auto GetRow = [entry, &nEntryRow, row = -1](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + ++row; + if (info.entry != entry) + return CallbackResult::Continue; + nEntryRow = row; + return CallbackResult::Stop; + }; + IterateVisibleEntryAreas(GetRow); + return nEntryRow; +} + +void IconViewImpl::SetStartEntry(SvTreeListEntry* entry) +{ + const tools::Long max = m_aVerSBar->GetRangeMax() - m_aVerSBar->GetVisibleSize(); + tools::Long row = -1; + auto GetEntryAndRow = [&entry, &row, max, found = entry](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + { + found = info.entry; + ++row; + } + if (row >= max || info.entry == entry) + { + entry = found; + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(GetEntryAndRow); + + m_pStartEntry = entry; + m_aVerSBar->SetThumbPos(row); + m_pView->Invalidate(GetVisibleArea()); +} + +void IconViewImpl::ScrollTo(SvTreeListEntry* entry) +{ + if (!m_aVerSBar->IsVisible()) return; + const tools::Long entryRow = GetEntryRow(entry); + const tools::Long oldStartRow = m_aVerSBar->GetThumbPos(); + if (entryRow < oldStartRow) + IconViewImpl::SetStartEntry(entry); + const tools::Long visibleRows = m_aVerSBar->GetVisibleSize(); + const tools::Long posRelativeToBottom = entryRow - (oldStartRow + visibleRows) + 1; + if (posRelativeToBottom > 0) + IconViewImpl::SetStartEntry(GoToNextRow(m_pStartEntry, posRelativeToBottom)); +} - SvTreeListEntry* pPrevFirstToDraw = m_pStartEntry; +SvTreeListEntry* IconViewImpl::GoToPrevRow(SvTreeListEntry* pEntry, int nRows) const +{ + SvTreeListEntry* pPrev = pEntry; + auto FindPrev = [this, pEntry, nRows, &pPrev, + prevs = std::vector()](const EntryAreaInfo& info) mutable + { + if (info.column == 0 && !IsSeparator(info.entry)) + prevs.push_back(info.entry); + if (pEntry == info.entry) + { + if (prevs.size() > 1) + { + int i = std::max(0, static_cast(prevs.size()) - nRows - 1); + pPrev = prevs[i]; + for (short column = info.column; column; --column) + { + SvTreeListEntry* pNext = m_pView->NextVisible(pPrev); + if (!pNext || IsSeparator(pNext)) + break; + pPrev = pNext; + } + } + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindPrev); - for(short i = 0; i < m_pView->GetColumnsCount() && pPrevFirstToDraw; i++) - pPrevFirstToDraw = m_pView->PrevVisible(pPrevFirstToDraw); + return pPrev; +} + +SvTreeListEntry* IconViewImpl::GoToNextRow(SvTreeListEntry* pEntry, int nRows) const +{ + SvTreeListEntry* pNext = pEntry; + auto FindNext + = [pEntry, nRows, &pNext, column = -1](const EntryAreaInfo& info) mutable + { + if (info.column <= column && !IsSeparator(info.entry)) + { + if (info.column == 0 && --nRows < 0) + return CallbackResult::Stop; + pNext = info.entry; + if (info.column == column && nRows == 0) + return CallbackResult::Stop; + } + else if (pEntry == info.entry) + { + column = info.column; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindNext); - if( !pPrevFirstToDraw ) + return pNext; +} + +SvTreeListEntry* IconViewImpl::GetFirstInRow(SvTreeListEntry* pEntry) const +{ + SvTreeListEntry* pFirst = nullptr; + auto FindFirst = [pEntry, &pFirst](const EntryAreaInfo& info) + { + if (info.column == 0) + pFirst = info.entry; + return pEntry == info.entry ? CallbackResult::Stop : CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindFirst); + + return pFirst; +} + +void IconViewImpl::CursorUp() +{ + if (!m_pStartEntry) return; + SvTreeListEntry* pPrevFirstToDraw = GoToPrevRow(m_pStartEntry, 1); + m_nFlags &= ~LBoxFlags::Filling; - tools::Long nEntryHeight = m_pView->GetEntryHeight(); ShowCursor( false ); - m_pView->PaintImmediately(); - m_pStartEntry = pPrevFirstToDraw; - tools::Rectangle aArea( GetVisibleArea() ); - if (aArea.GetHeight() > nEntryHeight) - aArea.AdjustBottom(-nEntryHeight); - m_pView->Scroll( 0, nEntryHeight, aArea, ScrollFlags::NoChildren ); - m_pView->PaintImmediately(); + SetStartEntry(pPrevFirstToDraw); ShowCursor( true ); m_pView->NotifyScrolled(); } @@ -59,92 +208,47 @@ void IconViewImpl::CursorDown() if (!m_pStartEntry) return; - SvTreeListEntry* pNextFirstToDraw = m_pStartEntry; + SvTreeListEntry* pNextFirstToDraw = GoToNextRow(m_pStartEntry, 1); - for(short i = 0; i < m_pView->GetColumnsCount(); i++) - pNextFirstToDraw = m_pView->NextVisible(pNextFirstToDraw); - - if( pNextFirstToDraw ) - { - m_nFlags &= ~LBoxFlags::Filling; - ShowCursor( false ); - m_pView->PaintImmediately(); - m_pStartEntry = pNextFirstToDraw; - tools::Rectangle aArea( GetVisibleArea() ); - m_pView->Scroll( 0, -(m_pView->GetEntryHeight()), aArea, ScrollFlags::NoChildren ); - m_pView->PaintImmediately(); - ShowCursor( true ); - m_pView->NotifyScrolled(); - } + m_nFlags &= ~LBoxFlags::Filling; + ShowCursor( false ); + SetStartEntry(pNextFirstToDraw); + ShowCursor( true ); + m_pView->NotifyScrolled(); } void IconViewImpl::PageDown( sal_uInt16 nDelta ) { - sal_uInt16 nRealDelta = nDelta * m_pView->GetColumnsCount(); - if( !nDelta ) return; if (!m_pStartEntry) return; - SvTreeListEntry* pNext = m_pView->NextVisible(m_pStartEntry, nRealDelta); - if( pNext == m_pStartEntry ) - return; + SvTreeListEntry* pNext = GoToNextRow(m_pStartEntry, nDelta); ShowCursor( false ); m_nFlags &= ~LBoxFlags::Filling; - m_pStartEntry = pNext; - - if( nRealDelta >= m_nVisibleCount ) - { - m_pView->Invalidate( GetVisibleArea() ); - } - else - { - tools::Rectangle aArea( GetVisibleArea() ); - tools::Long nScroll = m_pView->GetEntryHeight() * static_cast(nRealDelta); - nScroll = -nScroll; - m_pView->PaintImmediately(); - m_pView->Scroll( 0, nScroll, aArea, ScrollFlags::NoChildren ); - m_pView->PaintImmediately(); - m_pView->NotifyScrolled(); - } + SetStartEntry(pNext); ShowCursor( true ); } void IconViewImpl::PageUp( sal_uInt16 nDelta ) { - sal_uInt16 nRealDelta = nDelta * m_pView->GetColumnsCount(); if( !nDelta ) return; if (!m_pStartEntry) return; - SvTreeListEntry* pPrev = m_pView->PrevVisible(m_pStartEntry, nRealDelta); - if( pPrev == m_pStartEntry ) - return; + SvTreeListEntry* pPrev = GoToPrevRow(m_pStartEntry, nDelta); m_nFlags &= ~LBoxFlags::Filling; ShowCursor( false ); - m_pStartEntry = pPrev; - if( nRealDelta >= m_nVisibleCount ) - { - m_pView->Invalidate( GetVisibleArea() ); - } - else - { - tools::Long nEntryHeight = m_pView->GetEntryHeight(); - tools::Rectangle aArea( GetVisibleArea() ); - m_pView->PaintImmediately(); - m_pView->Scroll( 0, nEntryHeight*nRealDelta, aArea, ScrollFlags::NoChildren ); - m_pView->PaintImmediately(); - m_pView->NotifyScrolled(); - } + SetStartEntry(pPrev); ShowCursor( true ); } @@ -160,14 +264,11 @@ void IconViewImpl::KeyDown( bool bPageDown ) else nDelta = 1; - tools::Long nThumbPos = m_aVerSBar->GetThumbPos(); - if( nDelta <= 0 ) return; m_nFlags &= ~LBoxFlags::Filling; - m_aVerSBar->SetThumbPos( nThumbPos+nDelta ); if( bPageDown ) PageDown( static_cast(nDelta) ); else @@ -185,17 +286,8 @@ void IconViewImpl::KeyUp( bool bPageUp ) else nDelta = 1; - tools::Long nThumbPos = m_aVerSBar->GetThumbPos(); - - if( nThumbPos < nDelta ) - nDelta = nThumbPos; - - if( nDelta < 0 ) - return; - m_nFlags &= ~LBoxFlags::Filling; - m_aVerSBar->SetThumbPos( nThumbPos - nDelta ); if( bPageUp ) PageUp( static_cast(nDelta) ); else @@ -207,21 +299,27 @@ tools::Long IconViewImpl::GetEntryLine(const SvTreeListEntry* pEntry) const if(!m_pStartEntry ) return -1; // invisible position - tools::Long nFirstVisPos = m_pView->GetVisiblePos( m_pStartEntry ); - tools::Long nEntryVisPos = m_pView->GetVisiblePos( pEntry ); - nFirstVisPos = nEntryVisPos - nFirstVisPos; - - return nFirstVisPos; + return IconViewImpl::GetEntryPosition(pEntry).Y(); } Point IconViewImpl::GetEntryPosition(const SvTreeListEntry* pEntry) const { - const int pos = m_pView->GetAbsPos( pEntry ); + Point result{ -m_pView->GetEntryWidth(), -m_pView->GetEntryHeight() }; // invisible + auto FindEntryPos = [pEntry, &result](const EntryAreaInfo& info) + { + if (pEntry == info.entry) + { + result = info.area.TopLeft(); + return CallbackResult::Stop; + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryPos, true); - return Point( ( pos % m_pView->GetColumnsCount() ) * m_pView->GetEntryWidth(), - ( pos / m_pView->GetColumnsCount() ) * m_pView->GetEntryHeight() ); + return result; } +// Returns the last entry (in respective row) if position is just past the last entry SvTreeListEntry* IconViewImpl::GetClickedEntry( const Point& rPoint ) const { DBG_ASSERT( m_pView->GetModel(), "IconViewImpl::GetClickedEntry: how can this ever happen?" ); @@ -230,11 +328,25 @@ SvTreeListEntry* IconViewImpl::GetClickedEntry( const Point& rPoint ) const if( m_pView->GetEntryCount() == 0 || !m_pStartEntry || !m_pView->GetEntryHeight() || !m_pView->GetEntryWidth()) return nullptr; - sal_uInt16 nY = static_cast(rPoint.Y() / m_pView->GetEntryHeight() ); - sal_uInt16 nX = static_cast(rPoint.X() / m_pView->GetEntryWidth() ); - sal_uInt16 nTemp = nY * m_pView->GetColumnsCount() + nX; - - SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp); + SvTreeListEntry* pEntry = nullptr; + auto FindEntryByPos = [&pEntry, &rPoint](const EntryAreaInfo& info) + { + if (info.area.Contains(rPoint)) + { + pEntry = info.entry; + return CallbackResult::Stop; + } + else if (info.area.Top() > rPoint.Y()) + { + return CallbackResult::Stop; // we are already below the clicked row + } + else if (info.area.Bottom() > rPoint.Y()) + { + pEntry = info.entry; // Same row; store the entry in case the click is past all entries + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryByPos, true); return pEntry; } @@ -245,16 +357,15 @@ bool IconViewImpl::IsEntryInView( SvTreeListEntry* pEntry ) const if( !m_pView->IsEntryVisible(pEntry) ) return false; - tools::Long nY = GetEntryLine( pEntry ) / m_pView->GetColumnsCount() * m_pView->GetEntryHeight(); + tools::Long nY = GetEntryLine( pEntry ); if( nY < 0 ) return false; - tools::Long nMax = m_nVisibleCount / m_pView->GetColumnsCount() * m_pView->GetEntryHeight(); - if( nY >= nMax ) + tools::Long height = GetEntrySize(*pEntry).Height(); + if (nY + height > m_aOutputSize.Height()) return false; - tools::Long nStart = GetEntryLine( pEntry ) - GetEntryLine( m_pStartEntry ); - return nStart >= 0; + return true; } void IconViewImpl::AdjustScrollBars( Size& rSize ) @@ -270,20 +381,36 @@ void IconViewImpl::AdjustScrollBars( Size& rSize ) const WinBits nWindowStyle = m_pView->GetStyle(); bool bVerSBar = ( nWindowStyle & WB_VSCROLL ) != 0; - // number of entries that are not collapsed - sal_uLong nTotalCount = m_pView->GetVisibleCount(); - // number of entries visible within the view - m_nVisibleCount = aOSize.Height() / nEntryHeight * m_pView->GetColumnsCount(); + const tools::Long nVisibleRows = aOSize.Height() / nEntryHeight; + m_nVisibleCount = nVisibleRows * m_pView->GetColumnsCount(); - tools::Long nRows = ( nTotalCount / m_pView->GetColumnsCount() ) + 1; + tools::Long nTotalRows = 0; + tools::Long totalHeight = 0; + auto CountRowsAndHeight = [&nTotalRows, &totalHeight](const EntryAreaInfo& info) + { + totalHeight = std::max(totalHeight, info.area.Bottom()); + if (info.column == 0 && !IsSeparator(info.entry)) + ++nTotalRows; + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(CountRowsAndHeight); // do we need a vertical scrollbar? - if( bVerSBar || nTotalCount > m_nVisibleCount ) + if( bVerSBar || totalHeight > aOSize.Height()) { nResult = 1; } + // do we need a Horizontal scrollbar? + bool bHorSBar = (nWindowStyle & WB_HSCROLL) != 0; + if (bHorSBar || m_pView->GetEntryWidth() > aOSize.Width()) + { + nResult += 2; + m_aHorSBar->SetRange(Range(0, m_pView->GetEntryWidth())); + m_aHorSBar->SetVisibleSize(aOSize.Width()); + } + PositionScrollBars( aOSize, nResult ); // adapt Range, VisibleRange etc. @@ -296,8 +423,9 @@ void IconViewImpl::AdjustScrollBars( Size& rSize ) // vertical scrollbar if( !m_bInVScrollHdl ) { - m_aVerSBar->SetPageSize( nTotalCount ); - m_aVerSBar->SetVisibleSize( nTotalCount - nRows ); + m_aVerSBar->SetRange(Range(0, nTotalRows)); + m_aVerSBar->SetPageSize(nVisibleRows); + m_aVerSBar->SetVisibleSize(nVisibleRows); } else { @@ -309,6 +437,11 @@ void IconViewImpl::AdjustScrollBars( Size& rSize ) else m_aVerSBar->Hide(); + if (nResult & 0x0002) + m_aHorSBar->Show(); + else + m_aHorSBar->Hide(); + rSize = aOSize; } @@ -321,29 +454,33 @@ SvTreeListEntry* IconViewImpl::GetEntry( const Point& rPoint ) const || !m_pView->GetEntryWidth()) return nullptr; - sal_uInt16 nClickedEntry = static_cast(rPoint.Y() / m_pView->GetEntryHeight() * m_pView->GetColumnsCount() + rPoint.X() / m_pView->GetEntryWidth() ); - sal_uInt16 nTemp = nClickedEntry; - SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp); - if( nTemp != nClickedEntry ) - pEntry = nullptr; + SvTreeListEntry* pEntry = nullptr; + auto FindEntryByPos = [&pEntry, &rPoint](const EntryAreaInfo& info) + { + if (info.area.Contains(rPoint)) + { + pEntry = info.entry; + return CallbackResult::Stop; + } + else if (info.area.Top() > rPoint.Y()) + { + return CallbackResult::Stop; // we are already below the clicked row + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(FindEntryByPos, true); + return pEntry; } void IconViewImpl::SyncVerThumb() { - if( m_pStartEntry ) - { - tools::Long nEntryPos = m_pView->GetVisiblePos( m_pStartEntry ); - m_aVerSBar->SetThumbPos( nEntryPos ); - } - else - m_aVerSBar->SetThumbPos( 0 ); + m_aVerSBar->SetThumbPos(GetEntryRow(m_pStartEntry)); } void IconViewImpl::UpdateAll( bool bInvalidateCompleteView ) { FindMostRight(); - m_aVerSBar->SetRange( Range( 0, m_pView->GetVisibleCount() ) ); SyncVerThumb(); FillView(); ShowVerSBar(); @@ -384,25 +521,6 @@ void IconViewImpl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectan m_pStartEntry = m_pView->First(); } - tools::Long nRectHeight = rRect.GetHeight(); - tools::Long nRectWidth = rRect.GetWidth(); - tools::Long nEntryHeight = m_pView->GetEntryHeight(); - tools::Long nEntryWidth = m_pView->GetEntryWidth(); - - // calculate area for the entries we want to draw - sal_uInt16 nStartId = static_cast(rRect.Top() / nEntryHeight * m_pView->GetColumnsCount() + (rRect.Left() / nEntryWidth)); - sal_uInt16 nCount = static_cast(( nRectHeight / nEntryHeight + 1 ) * nRectWidth / nEntryWidth); - nCount += 2; // don't miss an entry - - tools::Long nY = nStartId / m_pView->GetColumnsCount() * nEntryHeight; - tools::Long nX = 0; - SvTreeListEntry* pEntry = m_pStartEntry; - while (nStartId && pEntry) - { - pEntry = m_pView->NextVisible(pEntry); - nStartId--; - } - if (!m_pCursor && !mbNoAutoCurEntry) { // do not select if multiselection or explicit set @@ -410,18 +528,20 @@ void IconViewImpl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectan SetCursor(m_pStartEntry, bNotSelect); } - for(sal_uInt16 n = 0; n< nCount && pEntry; n++) + auto PaintEntry = [iconView = static_cast(m_pView.get()), &rRect, + &rRenderContext](const EntryAreaInfo& info) { - static_cast(m_pView.get())->PaintEntry(*pEntry, nX, nY, rRenderContext); - nX += nEntryWidth; - - if(nX + m_pView->GetEntryWidth() > nEntryWidth * m_pView->GetColumnsCount()) + if (!info.area.GetIntersection(rRect).IsEmpty()) { - nY += nEntryHeight; - nX = 0; + iconView->PaintEntry(*info.entry, info.area.Left(), info.area.Top(), rRenderContext); } - pEntry = m_pView->NextVisible(pEntry); - } + else if (info.area.Top() > rRect.Bottom()) + { + return CallbackResult::Stop; // we are already below the last visible row + } + return CallbackResult::Continue; + }; + IterateVisibleEntryAreas(PaintEntry, true); m_nFlags &= ~LBoxFlags::DeselectAll; rRenderContext.SetClipRegion(); @@ -432,16 +552,14 @@ void IconViewImpl::InvalidateEntry( tools::Long nId ) const { if( m_nFlags & LBoxFlags::InPaint ) return; + if (nId < 0) + return; + // nId is a Y coordinate of the top of the element, coming from GetEntryLine tools::Rectangle aRect( GetVisibleArea() ); - tools::Long nMaxBottom = aRect.Bottom(); - aRect.SetTop( nId / m_pView->GetColumnsCount() * m_pView->GetEntryHeight() ); - aRect.SetBottom( aRect.Top() ); aRect.AdjustBottom(m_pView->GetEntryHeight() ); - - if( aRect.Top() > nMaxBottom ) + if (nId > aRect.Bottom()) return; - if( aRect.Bottom() > nMaxBottom ) - aRect.SetBottom( nMaxBottom ); + aRect.SetTop(nId); // Invalidate everything below m_pView->Invalidate( aRect ); } @@ -468,9 +586,6 @@ bool IconViewImpl::KeyInput( const KeyEvent& rKEvt ) bool bHandled = true; - tools::Long i; - tools::Long nColumns = m_pView->GetColumnsCount(); - switch( aCode ) { case KEY_LEFT: @@ -531,72 +646,68 @@ bool IconViewImpl::KeyInput( const KeyEvent& rKEvt ) case KEY_UP: { - if( !IsEntryInView( m_pCursor ) ) - MakeVisible( m_pCursor ); - - pNewCursor = m_pCursor; - for( i = 0; i < nColumns && pNewCursor; i++) - { - do - { - pNewCursor = m_pView->PrevVisible(pNewCursor); - } while( pNewCursor && !IsSelectable(pNewCursor) ); - } - - // if there is no next entry, take the current one - // this ensures that in case of _one_ entry in the list, this entry is selected when pressing - // the cursor key - if ( !pNewCursor && m_pCursor ) - pNewCursor = m_pCursor; + pNewCursor = GoToPrevRow(m_pCursor, 1); if( pNewCursor ) { m_aSelEng.CursorPosChanging( bShift, bMod1 ); SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on - if( !IsEntryInView( pNewCursor ) ) - KeyUp( false ); + ScrollTo(pNewCursor); } break; } case KEY_DOWN: { - if( !IsEntryInView( m_pCursor ) ) - MakeVisible( m_pCursor ); + pNewCursor = GoToNextRow(m_pCursor, 1); - pNewCursor = m_pCursor; - for( i = 0; i < nColumns && pNewCursor; i++) + if( pNewCursor ) { - do - { - pNewCursor = m_pView->NextVisible(pNewCursor); - } while( pNewCursor && !IsSelectable(pNewCursor) ); + m_aSelEng.CursorPosChanging( bShift, bMod1 ); + ScrollTo(pNewCursor); + SetCursor(pNewCursor, bMod1); // no selection, when Ctrl is on } + else + KeyDown( false ); // because scrollbar range might still + // allow scrolling + break; + } - // if there is no next entry, take the current one - // this ensures that in case of _one_ entry in the list, this entry is selected when pressing - // the cursor key - if ( !pNewCursor && m_pCursor ) - pNewCursor = m_pCursor; + case KEY_PAGEUP: + if (!bMod1) + { + const sal_uInt16 nDelta = m_aVerSBar->GetPageSize(); + pNewCursor = GoToPrevRow(m_pCursor, nDelta); - if( pNewCursor ) + if (pNewCursor) + { + m_aSelEng.CursorPosChanging(bShift, bMod1); + ScrollTo(pNewCursor); + SetCursor(pNewCursor); + } + } + else + bHandled = false; + break; + + case KEY_PAGEDOWN: + if (!bMod1) { - m_aSelEng.CursorPosChanging( bShift, bMod1 ); - if( IsEntryInView( pNewCursor ) ) - SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on - else + const sal_uInt16 nDelta = m_aVerSBar->GetPageSize(); + pNewCursor = GoToNextRow(m_pCursor, nDelta); + + if (pNewCursor) { - if( m_pCursor ) - m_pView->Select( m_pCursor, false ); - KeyDown( false ); - SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on + m_aSelEng.CursorPosChanging(bShift, bMod1); + ScrollTo(pNewCursor); + SetCursor(pNewCursor); } + else + KeyDown(false); } else - KeyDown( false ); // because scrollbar range might still - // allow scrolling + bHandled = false; break; - } case KEY_RETURN: { @@ -613,19 +724,13 @@ bool IconViewImpl::KeyInput( const KeyEvent& rKEvt ) pNewCursor = m_pView->PrevVisible(pNewCursor); } - m_pStartEntry = pNewCursor; - - while( m_pStartEntry && m_pView->GetAbsPos( m_pStartEntry ) % m_pView->GetColumnsCount() != 0 ) - { - m_pStartEntry = m_pView->PrevVisible(m_pStartEntry); - } + SetStartEntry(pNewCursor); if( pNewCursor && pNewCursor != m_pCursor) { // SelAllDestrAnch( false ); m_aSelEng.CursorPosChanging( bShift, bMod1 ); SetCursor( pNewCursor ); - SyncVerThumb(); } bHandled = true; diff --git a/vcl/source/treelist/iconviewimpl.hxx b/vcl/source/treelist/iconviewimpl.hxx index be038eee587d..144a5a3adbab 100644 --- a/vcl/source/treelist/iconviewimpl.hxx +++ b/vcl/source/treelist/iconviewimpl.hxx @@ -60,6 +60,34 @@ protected: void SyncVerThumb() override; void AdjustScrollBars(Size& rSize) override; + +private: + enum class CallbackResult + { + Continue, + Stop, // Stop iteration + }; + struct EntryAreaInfo + { + SvTreeListEntry* entry; + short column; + tools::Rectangle area; // The area for the entry + }; + using IterateEntriesFunc = std::function; + + void IterateVisibleEntryAreas(const IterateEntriesFunc& f, bool fromStartEntry = false) const; + + Size GetEntrySize(const SvTreeListEntry& entry) const; + // Get first entry at most n rows above; nullptr if no rows above + SvTreeListEntry* GoToPrevRow(SvTreeListEntry* pEntry, int n) const; + // Get first entry at most n rows below; nullptr if no rows below + SvTreeListEntry* GoToNextRow(SvTreeListEntry* pEntry, int n) const; + + SvTreeListEntry* GetFirstInRow(SvTreeListEntry* pEntry) const; + + tools::Long GetEntryRow(const SvTreeListEntry* entry) const; + void SetStartEntry(SvTreeListEntry* entry); + void ScrollTo(SvTreeListEntry* entry); }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/treelist/svimpbox.cxx b/vcl/source/treelist/svimpbox.cxx index fd3f43263c45..cea89cdbd33c 100644 --- a/vcl/source/treelist/svimpbox.cxx +++ b/vcl/source/treelist/svimpbox.cxx @@ -49,11 +49,11 @@ Image* SvImpLBox::s_pDefExpanded = nullptr; oslInterlockedCount SvImpLBox::s_nImageRefCount = 0; SvImpLBox::SvImpLBox( SvTreeListBox* pLBView, SvTreeList* pLBTree, WinBits nWinStyle) - : m_aHorSBar(VclPtr::Create(pLBView, WB_DRAG | WB_HSCROLL)) - , m_aScrBarBox(VclPtr::Create(pLBView)) + : m_aScrBarBox(VclPtr::Create(pLBView)) , m_aFctSet(this, pLBView) , mbForceMakeVisible (false) , m_aEditIdle("SvImpLBox m_aEditIdle") + , m_aHorSBar(VclPtr::Create(pLBView, WB_DRAG | WB_HSCROLL)) , m_aVerSBar(VclPtr::Create(pLBView, WB_DRAG | WB_VSCROLL)) , m_aOutputSize(0, 0) , mbNoAutoCurEntry(false) diff --git a/vcl/source/treelist/treelistbox.cxx b/vcl/source/treelist/treelistbox.cxx index 5092c64cb83d..962dd60cf53b 100644 --- a/vcl/source/treelist/treelistbox.cxx +++ b/vcl/source/treelist/treelistbox.cxx @@ -1593,7 +1593,7 @@ void SvTreeListBox::SetExpandedEntryBmp( SvTreeListEntry* pEntry, const Image& a pItem->SetBitmap2( aBmp ); ModelHasEntryInvalidated(pEntry); - SetEntryHeight( pEntry ); + CalcEntryHeight( pEntry ); Size aSize = aBmp.GetSizePixel(); short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast(aSize.Width()) ); if( nWidth > nContextBmpWidthMax ) @@ -1611,7 +1611,7 @@ void SvTreeListBox::SetCollapsedEntryBmp(SvTreeListEntry* pEntry,const Image& aB pItem->SetBitmap1( aBmp ); ModelHasEntryInvalidated(pEntry); - SetEntryHeight( pEntry ); + CalcEntryHeight( pEntry ); Size aSize = aBmp.GetSizePixel(); short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast(aSize.Width()) ); if( nWidth > nContextBmpWidthMax ) @@ -1663,7 +1663,7 @@ void SvTreeListBox::ImpEntryInserted( SvTreeListEntry* pEntry ) nTreeFlags |= SvTreeFlags::RECALCTABS; } } - SetEntryHeight( pEntry ); + CalcEntryHeight( pEntry ); if( !(nTreeFlags & SvTreeFlags::CHKBTN) ) return; @@ -1976,7 +1976,7 @@ void SvTreeListBox::SetDragDropMode( DragDropMode nDDMode ) pImpl->SetDragDropMode( nDDMode ); } -void SvTreeListBox::SetEntryHeight( SvTreeListEntry const * pEntry ) +void SvTreeListBox::CalcEntryHeight( SvTreeListEntry const * pEntry ) { short nHeightMax=0; sal_uInt16 nCount = pEntry->ItemCount(); diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx index 0575aa8c3c7a..6536a0b3ffea 100644 --- a/vcl/unx/gtk3/gtkinst.cxx +++ b/vcl/unx/gtk3/gtkinst.cxx @@ -16288,6 +16288,12 @@ public: enable_notify_events(); } + virtual void insert_separator(int /* pos */, const OUString* /* pId */) override + { + // TODO: can't just copy from GtkInstanceTreeView, since there's + // no IconView analog for gtk_tree_view_get_row_separator_func + } + virtual void connect_query_tooltip(const Link& rLink) override { weld::IconView::connect_query_tooltip(rLink); -- 2.11.4.GIT