summaryrefslogtreecommitdiffstats
path: root/vcl/source/treelist/treelistbox.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/source/treelist/treelistbox.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/treelist/treelistbox.cxx')
-rw-r--r--vcl/source/treelist/treelistbox.cxx3589
1 files changed, 3589 insertions, 0 deletions
diff --git a/vcl/source/treelist/treelistbox.cxx b/vcl/source/treelist/treelistbox.cxx
new file mode 100644
index 0000000000..6dd915e239
--- /dev/null
+++ b/vcl/source/treelist/treelistbox.cxx
@@ -0,0 +1,3589 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+/*
+ TODO:
+ - delete anchor in SelectionEngine when selecting manually
+ - SelectAll( false ) => only repaint the deselected entries
+*/
+
+#include <vcl/toolkit/treelistbox.hxx>
+#include <vcl/accessiblefactory.hxx>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <vcl/help.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/builder.hxx>
+#include <vcl/toolkit/edit.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/uitest/uiobject.hxx>
+#include <sot/formats.hxx>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/toolkit/svlbitm.hxx>
+#include <vcl/toolkit/treelistentry.hxx>
+#include <vcl/toolkit/viewdataentry.hxx>
+#include <accel.hxx>
+#include <svimpbox.hxx>
+
+#include <set>
+#include <string.h>
+#include <vector>
+
+using namespace css::accessibility;
+
+// Drag&Drop
+static VclPtr<SvTreeListBox> g_pDDSource;
+static VclPtr<SvTreeListBox> g_pDDTarget;
+
+#define SVLBOX_ACC_RETURN 1
+#define SVLBOX_ACC_ESCAPE 2
+
+class SvInplaceEdit2
+{
+ Link<SvInplaceEdit2&,void> aCallBackHdl;
+ Accelerator aAccReturn;
+ Accelerator aAccEscape;
+ Idle aIdle { "svtools::SvInplaceEdit2 aIdle" };
+ VclPtr<Edit> pEdit;
+ bool bCanceled;
+ bool bAlreadyInCallBack;
+
+ void CallCallBackHdl_Impl();
+ DECL_LINK( Timeout_Impl, Timer *, void );
+ DECL_LINK( ReturnHdl_Impl, Accelerator&, void );
+ DECL_LINK( EscapeHdl_Impl, Accelerator&, void );
+
+public:
+ SvInplaceEdit2( vcl::Window* pParent, const Point& rPos, const Size& rSize,
+ const OUString& rData, const Link<SvInplaceEdit2&,void>& rNotifyEditEnd,
+ const Selection& );
+ ~SvInplaceEdit2();
+ bool KeyInput( const KeyEvent& rKEvt );
+ void LoseFocus();
+ bool EditingCanceled() const { return bCanceled; }
+ OUString GetText() const;
+ OUString const & GetSavedValue() const;
+ void StopEditing( bool bCancel );
+ void Hide();
+ const VclPtr<Edit> & GetEditWidget() const { return pEdit; };
+};
+
+// ***************************************************************
+
+namespace {
+
+class MyEdit_Impl : public Edit
+{
+ SvInplaceEdit2* pOwner;
+public:
+ MyEdit_Impl( vcl::Window* pParent, SvInplaceEdit2* pOwner );
+ virtual ~MyEdit_Impl() override { disposeOnce(); }
+ virtual void dispose() override { pOwner = nullptr; Edit::dispose(); }
+ virtual void KeyInput( const KeyEvent& rKEvt ) override;
+ virtual void LoseFocus() override;
+};
+
+}
+
+MyEdit_Impl::MyEdit_Impl( vcl::Window* pParent, SvInplaceEdit2* _pOwner ) :
+
+ Edit( pParent, WB_LEFT ),
+
+ pOwner( _pOwner )
+
+{
+}
+
+void MyEdit_Impl::KeyInput( const KeyEvent& rKEvt )
+{
+ if( !pOwner->KeyInput( rKEvt ))
+ Edit::KeyInput( rKEvt );
+}
+
+void MyEdit_Impl::LoseFocus()
+{
+ if (pOwner)
+ pOwner->LoseFocus();
+}
+
+SvInplaceEdit2::SvInplaceEdit2
+(
+ vcl::Window* pParent, const Point& rPos,
+ const Size& rSize,
+ const OUString& rData,
+ const Link<SvInplaceEdit2&,void>& rNotifyEditEnd,
+ const Selection& rSelection
+) :
+
+ aCallBackHdl ( rNotifyEditEnd ),
+ bCanceled ( false ),
+ bAlreadyInCallBack ( false )
+
+{
+
+ pEdit = VclPtr<MyEdit_Impl>::Create( pParent, this );
+
+ vcl::Font aFont( pParent->GetFont() );
+ aFont.SetTransparent( false );
+ Color aColor( pParent->GetBackground().GetColor() );
+ aFont.SetFillColor(aColor );
+ pEdit->SetFont( aFont );
+ pEdit->SetBackground( pParent->GetBackground() );
+ pEdit->SetPosPixel( rPos );
+ pEdit->SetSizePixel( rSize );
+ pEdit->SetText( rData );
+ pEdit->SetSelection( rSelection );
+ pEdit->SaveValue();
+
+ aAccReturn.InsertItem( SVLBOX_ACC_RETURN, vcl::KeyCode(KEY_RETURN) );
+ aAccEscape.InsertItem( SVLBOX_ACC_ESCAPE, vcl::KeyCode(KEY_ESCAPE) );
+
+ aAccReturn.SetActivateHdl( LINK( this, SvInplaceEdit2, ReturnHdl_Impl) );
+ aAccEscape.SetActivateHdl( LINK( this, SvInplaceEdit2, EscapeHdl_Impl) );
+ Application::InsertAccel( &aAccReturn );
+ Application::InsertAccel( &aAccEscape );
+
+ pEdit->Show();
+ pEdit->GrabFocus();
+}
+
+SvInplaceEdit2::~SvInplaceEdit2()
+{
+ if( !bAlreadyInCallBack )
+ {
+ Application::RemoveAccel( &aAccReturn );
+ Application::RemoveAccel( &aAccEscape );
+ }
+ pEdit.disposeAndClear();
+}
+
+OUString const & SvInplaceEdit2::GetSavedValue() const
+{
+ return pEdit->GetSavedValue();
+}
+
+void SvInplaceEdit2::Hide()
+{
+ pEdit->Hide();
+}
+
+
+IMPL_LINK_NOARG(SvInplaceEdit2, ReturnHdl_Impl, Accelerator&, void)
+{
+ bCanceled = false;
+ CallCallBackHdl_Impl();
+}
+
+IMPL_LINK_NOARG(SvInplaceEdit2, EscapeHdl_Impl, Accelerator&, void)
+{
+ bCanceled = true;
+ CallCallBackHdl_Impl();
+}
+
+bool SvInplaceEdit2::KeyInput( const KeyEvent& rKEvt )
+{
+ vcl::KeyCode aCode = rKEvt.GetKeyCode();
+ sal_uInt16 nCode = aCode.GetCode();
+
+ switch ( nCode )
+ {
+ case KEY_ESCAPE:
+ bCanceled = true;
+ CallCallBackHdl_Impl();
+ return true;
+
+ case KEY_RETURN:
+ bCanceled = false;
+ CallCallBackHdl_Impl();
+ return true;
+ }
+ return false;
+}
+
+void SvInplaceEdit2::StopEditing( bool bCancel )
+{
+ if ( !bAlreadyInCallBack )
+ {
+ bCanceled = bCancel;
+ CallCallBackHdl_Impl();
+ }
+}
+
+void SvInplaceEdit2::LoseFocus()
+{
+ if ( !bAlreadyInCallBack
+ && ((!Application::GetFocusWindow()) || !pEdit->IsChild( Application::GetFocusWindow()) )
+ )
+ {
+ bCanceled = false;
+ aIdle.SetPriority(TaskPriority::REPAINT);
+ aIdle.SetInvokeHandler(LINK(this,SvInplaceEdit2,Timeout_Impl));
+ aIdle.Start();
+ }
+}
+
+IMPL_LINK_NOARG(SvInplaceEdit2, Timeout_Impl, Timer *, void)
+{
+ CallCallBackHdl_Impl();
+}
+
+void SvInplaceEdit2::CallCallBackHdl_Impl()
+{
+ aIdle.Stop();
+ if ( !bAlreadyInCallBack )
+ {
+ bAlreadyInCallBack = true;
+ Application::RemoveAccel( &aAccReturn );
+ Application::RemoveAccel( &aAccEscape );
+ pEdit->Hide();
+ aCallBackHdl.Call( *this );
+ }
+}
+
+OUString SvInplaceEdit2::GetText() const
+{
+ return pEdit->GetText();
+}
+
+// ***************************************************************
+// class SvLBoxTab
+// ***************************************************************
+
+
+SvLBoxTab::SvLBoxTab()
+{
+ nPos = 0;
+ nFlags = SvLBoxTabFlags::NONE;
+}
+
+SvLBoxTab::SvLBoxTab( tools::Long nPosition, SvLBoxTabFlags nTabFlags )
+{
+ nPos = nPosition;
+ nFlags = nTabFlags;
+}
+
+SvLBoxTab::SvLBoxTab( const SvLBoxTab& rTab )
+{
+ nPos = rTab.nPos;
+ nFlags = rTab.nFlags;
+}
+
+tools::Long SvLBoxTab::CalcOffset( tools::Long nItemWidth, tools::Long nTabWidth )
+{
+ tools::Long nOffset = 0;
+ if ( nFlags & SvLBoxTabFlags::ADJUST_RIGHT )
+ {
+ nOffset = nTabWidth - nItemWidth;
+ if( nOffset < 0 )
+ nOffset = 0;
+ }
+ else if ( nFlags & SvLBoxTabFlags::ADJUST_CENTER )
+ {
+ if( nFlags & SvLBoxTabFlags::FORCE )
+ {
+ // correct implementation of centering
+ nOffset = ( nTabWidth - nItemWidth ) / 2;
+ if( nOffset < 0 )
+ nOffset = 0;
+ }
+ else
+ {
+ // historically grown, wrong calculation of tabs which is needed by
+ // Abo-Tabbox, Tools/Options/Customize etc.
+ nItemWidth++;
+ nOffset = -( nItemWidth / 2 );
+ }
+ }
+ return nOffset;
+}
+
+// ***************************************************************
+// class SvLBoxItem
+// ***************************************************************
+
+
+SvLBoxItem::SvLBoxItem()
+ : mbDisabled(false)
+{
+}
+
+SvLBoxItem::~SvLBoxItem()
+{
+}
+
+int SvLBoxItem::GetWidth(const SvTreeListBox* pView, const SvTreeListEntry* pEntry) const
+{
+ const SvViewDataItem* pViewData = pView->GetViewDataItem( pEntry, this );
+ int nWidth = pViewData->mnWidth;
+ if (nWidth == -1)
+ {
+ nWidth = CalcWidth(pView);
+ const_cast<SvViewDataItem*>(pViewData)->mnWidth = nWidth;
+ }
+ return nWidth;
+}
+
+int SvLBoxItem::GetHeight(const SvTreeListBox* pView, const SvTreeListEntry* pEntry) const
+{
+ const SvViewDataItem* pViewData = pView->GetViewDataItem( pEntry, this );
+ return pViewData->mnHeight;
+}
+
+int SvLBoxItem::GetWidth(const SvTreeListBox* pView, const SvViewDataEntry* pData, sal_uInt16 nItemPos) const
+{
+ const SvViewDataItem& rIData = pData->GetItem(nItemPos);
+ int nWidth = rIData.mnWidth;
+ if (nWidth == -1)
+ {
+ nWidth = CalcWidth(pView);
+ const_cast<SvViewDataItem&>(rIData).mnWidth = nWidth;
+ }
+ return nWidth;
+}
+
+int SvLBoxItem::GetHeight(const SvViewDataEntry* pData, sal_uInt16 nItemPos)
+{
+ const SvViewDataItem& rIData = pData->GetItem(nItemPos);
+ return rIData.mnHeight;
+}
+
+int SvLBoxItem::CalcWidth(const SvTreeListBox* /*pView*/) const
+{
+ return 0;
+}
+
+struct SvTreeListBoxImpl
+{
+ bool m_bDoingQuickSelection:1;
+
+ vcl::QuickSelectionEngine m_aQuickSelectionEngine;
+
+ explicit SvTreeListBoxImpl(SvTreeListBox& _rBox) :
+ m_bDoingQuickSelection(false),
+ m_aQuickSelectionEngine(_rBox) {}
+};
+
+SvTreeListBox::SvTreeListBox(vcl::Window* pParent, WinBits nWinStyle) :
+ Control(pParent, nWinStyle | WB_CLIPCHILDREN),
+ DropTargetHelper(this),
+ DragSourceHelper(this),
+ mpImpl(new SvTreeListBoxImpl(*this)),
+ mbContextBmpExpanded(false),
+ mbQuickSearch(false),
+ mbActivateOnSingleClick(false),
+ mbHoverSelection(false),
+ mbSelectingByHover(false),
+ mnClicksToToggle(0), //at default clicking on a row won't toggle its default checkbox
+ eSelMode(SelectionMode::NONE),
+ nMinWidthInChars(0),
+ mnDragAction(DND_ACTION_COPYMOVE | DND_ACTION_LINK),
+ mbCenterAndClipText(false)
+{
+ nImpFlags = SvTreeListBoxFlags::NONE;
+ pTargetEntry = nullptr;
+ nDragDropMode = DragDropMode::NONE;
+ pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl ));
+ pHdlEntry = nullptr;
+ eSelMode = SelectionMode::Single;
+ nDragDropMode = DragDropMode::NONE;
+ SetType(WindowType::TREELISTBOX);
+
+ InitTreeView();
+ pImpl->SetModel( pModel.get() );
+
+ SetSublistOpenWithLeftRight();
+}
+
+void SvTreeListBox::Clear()
+{
+ if (pModel)
+ pModel->Clear(); // Model calls SvTreeListBox::ModelHasCleared()
+}
+
+IMPL_LINK( SvTreeListBox, CloneHdl_Impl, SvTreeListEntry*, pEntry, SvTreeListEntry* )
+{
+ return CloneEntry(pEntry);
+}
+
+sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry, SvTreeListEntry* pParent, sal_uInt32 nPos )
+{
+ sal_uInt32 nInsPos = pModel->Insert( pEntry, pParent, nPos );
+ return nInsPos;
+}
+
+sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry,sal_uInt32 nRootPos )
+{
+ sal_uInt32 nInsPos = pModel->Insert( pEntry, nRootPos );
+ return nInsPos;
+}
+
+bool SvTreeListBox::ExpandingHdl()
+{
+ return !aExpandingHdl.IsSet() || aExpandingHdl.Call( this );
+}
+
+void SvTreeListBox::ExpandedHdl()
+{
+ aExpandedHdl.Call( this );
+}
+
+void SvTreeListBox::SelectHdl()
+{
+ aSelectHdl.Call( this );
+}
+
+void SvTreeListBox::DeselectHdl()
+{
+ aDeselectHdl.Call( this );
+}
+
+bool SvTreeListBox::DoubleClickHdl()
+{
+ return !aDoubleClickHdl.IsSet() || aDoubleClickHdl.Call(this);
+}
+
+bool SvTreeListBox::CheckDragAndDropMode( SvTreeListBox const * pSource, sal_Int8 nAction )
+{
+ if ( pSource != this )
+ return false; // no drop
+
+ if ( !(nDragDropMode & DragDropMode::CTRL_MOVE) )
+ return false; // D&D locked within list
+
+ if( DND_ACTION_MOVE == nAction )
+ {
+ if ( !(nDragDropMode & DragDropMode::CTRL_MOVE) )
+ return false; // no local move
+ }
+ else
+ return false; // no local copy
+
+ return true;
+}
+
+
+/*
+ NotifyMoving/Copying
+ ====================
+
+ default behavior:
+
+ 1. target doesn't have children
+ - entry becomes sibling of target. entry comes after target
+ (->Window: below the target)
+ 2. target is an expanded parent
+ - entry inserted at the beginning of the target childlist
+ 3. target is a collapsed parent
+ - entry is inserted at the end of the target childlist
+*/
+TriState SvTreeListBox::NotifyMoving(
+ SvTreeListEntry* pTarget, // D&D dropping position in GetModel()
+ const SvTreeListEntry* pEntry, // entry that we want to move, from
+ // GetSourceListBox()->GetModel()
+ SvTreeListEntry*& rpNewParent, // new target parent
+ sal_uInt32& rNewChildPos) // position in childlist of target parent
+{
+ DBG_ASSERT(pEntry,"NotifyMoving:SourceEntry?");
+ if( !pTarget )
+ {
+ rpNewParent = nullptr;
+ rNewChildPos = 0;
+ return TRISTATE_TRUE;
+ }
+ if ( !pTarget->HasChildren() && !pTarget->HasChildrenOnDemand() )
+ {
+ // case 1
+ rpNewParent = GetParent( pTarget );
+ rNewChildPos = SvTreeList::GetRelPos( pTarget ) + 1;
+ rNewChildPos += nCurEntrySelPos;
+ nCurEntrySelPos++;
+ }
+ else
+ {
+ // cases 2 & 3
+ rpNewParent = pTarget;
+ if( IsExpanded(pTarget))
+ rNewChildPos = 0;
+ else
+ rNewChildPos = TREELIST_APPEND;
+ }
+ return TRISTATE_TRUE;
+}
+
+TriState SvTreeListBox::NotifyCopying(
+ SvTreeListEntry* pTarget, // D&D dropping position in GetModel()
+ const SvTreeListEntry* pEntry, // entry that we want to move, from
+ // GetSourceListBox()->GetModel()
+ SvTreeListEntry*& rpNewParent, // new target parent
+ sal_uInt32& rNewChildPos) // position in childlist of target parent
+{
+ return NotifyMoving(pTarget,pEntry,rpNewParent,rNewChildPos);
+}
+
+SvTreeListEntry* SvTreeListBox::FirstChild( SvTreeListEntry* pParent ) const
+{
+ return pModel->FirstChild(pParent);
+}
+
+// return: all entries copied
+bool SvTreeListBox::CopySelection( SvTreeListBox* pSource, SvTreeListEntry* pTarget )
+{
+ nCurEntrySelPos = 0; // selection counter for NotifyMoving/Copying
+ bool bSuccess = true;
+ std::vector<SvTreeListEntry*> aList;
+ bool bClone = ( pSource->GetModel() != GetModel() );
+ Link<SvTreeListEntry*,SvTreeListEntry*> aCloneLink( pModel->GetCloneLink() );
+ pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl ));
+
+ // cache selection to simplify iterating over the selection when doing a D&D
+ // exchange within the same listbox
+ SvTreeListEntry* pSourceEntry = pSource->FirstSelected();
+ while ( pSourceEntry )
+ {
+ // children are copied automatically
+ pSource->SelectChildren( pSourceEntry, false );
+ aList.push_back( pSourceEntry );
+ pSourceEntry = pSource->NextSelected( pSourceEntry );
+ }
+
+ for (auto const& elem : aList)
+ {
+ pSourceEntry = elem;
+ SvTreeListEntry* pNewParent = nullptr;
+ sal_uInt32 nInsertionPos = TREELIST_APPEND;
+ TriState nOk = NotifyCopying(pTarget,pSourceEntry,pNewParent,nInsertionPos);
+ if ( nOk )
+ {
+ if ( bClone )
+ {
+ sal_uInt32 nCloneCount = 0;
+ pSourceEntry = pModel->Clone(pSourceEntry, nCloneCount);
+ pModel->InsertTree(pSourceEntry, pNewParent, nInsertionPos);
+ }
+ else
+ {
+ sal_uInt32 nListPos = pModel->Copy(pSourceEntry, pNewParent, nInsertionPos);
+ pSourceEntry = GetEntry( pNewParent, nListPos );
+ }
+ }
+ else
+ bSuccess = false;
+
+ if (nOk == TRISTATE_INDET) // HACK: make visible moved entry
+ MakeVisible( pSourceEntry );
+ }
+ pModel->SetCloneLink( aCloneLink );
+ return bSuccess;
+}
+
+// return: all entries were moved
+bool SvTreeListBox::MoveSelectionCopyFallbackPossible( SvTreeListBox* pSource, SvTreeListEntry* pTarget, bool bAllowCopyFallback )
+{
+ nCurEntrySelPos = 0; // selection counter for NotifyMoving/Copying
+ bool bSuccess = true;
+ std::vector<SvTreeListEntry*> aList;
+ bool bClone = ( pSource->GetModel() != GetModel() );
+ Link<SvTreeListEntry*,SvTreeListEntry*> aCloneLink( pModel->GetCloneLink() );
+ if ( bClone )
+ pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl ));
+
+ SvTreeListEntry* pSourceEntry = pSource->FirstSelected();
+ while ( pSourceEntry )
+ {
+ // children are automatically moved
+ pSource->SelectChildren( pSourceEntry, false );
+ aList.push_back( pSourceEntry );
+ pSourceEntry = pSource->NextSelected( pSourceEntry );
+ }
+
+ for (auto const& elem : aList)
+ {
+ pSourceEntry = elem;
+ SvTreeListEntry* pNewParent = nullptr;
+ sal_uInt32 nInsertionPos = TREELIST_APPEND;
+ TriState nOk = NotifyMoving(pTarget,pSourceEntry,pNewParent,nInsertionPos);
+ TriState nCopyOk = nOk;
+ if ( !nOk && bAllowCopyFallback )
+ {
+ nInsertionPos = TREELIST_APPEND;
+ nCopyOk = NotifyCopying(pTarget,pSourceEntry,pNewParent,nInsertionPos);
+ }
+
+ if ( nOk || nCopyOk )
+ {
+ if ( bClone )
+ {
+ sal_uInt32 nCloneCount = 0;
+ pSourceEntry = pModel->Clone(pSourceEntry, nCloneCount);
+ pModel->InsertTree(pSourceEntry, pNewParent, nInsertionPos);
+ }
+ else
+ {
+ if ( nOk )
+ pModel->Move(pSourceEntry, pNewParent, nInsertionPos);
+ else
+ pModel->Copy(pSourceEntry, pNewParent, nInsertionPos);
+ }
+ }
+ else
+ bSuccess = false;
+
+ if (nOk == TRISTATE_INDET) // HACK: make moved entry visible
+ MakeVisible( pSourceEntry );
+ }
+ pModel->SetCloneLink( aCloneLink );
+ return bSuccess;
+}
+
+void SvTreeListBox::RemoveSelection()
+{
+ std::vector<const SvTreeListEntry*> aList;
+ // cache selection, as the implementation deselects everything on the first
+ // remove
+ SvTreeListEntry* pEntry = FirstSelected();
+ while ( pEntry )
+ {
+ aList.push_back( pEntry );
+ if ( pEntry->HasChildren() )
+ // remove deletes all children automatically
+ SelectChildren(pEntry, false);
+ pEntry = NextSelected( pEntry );
+ }
+
+ for (auto const& elem : aList)
+ pModel->Remove(elem);
+}
+
+void SvTreeListBox::RemoveEntry(SvTreeListEntry const * pEntry)
+{
+ pModel->Remove(pEntry);
+}
+
+void SvTreeListBox::RecalcViewData()
+{
+ SvTreeListEntry* pEntry = First();
+ while( pEntry )
+ {
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCurPos = 0;
+ while ( nCurPos < nCount )
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( nCurPos );
+ rItem.InitViewData( this, pEntry );
+ nCurPos++;
+ }
+ pEntry = Next( pEntry );
+ }
+}
+
+void SvTreeListBox::ImplShowTargetEmphasis( SvTreeListEntry* pEntry, bool bShow)
+{
+ if ( bShow && (nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) )
+ return;
+ if ( !bShow && !(nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) )
+ return;
+ pImpl->PaintDDCursor( pEntry, bShow);
+ if( bShow )
+ nImpFlags |= SvTreeListBoxFlags::TARGEMPH_VIS;
+ else
+ nImpFlags &= ~SvTreeListBoxFlags::TARGEMPH_VIS;
+}
+
+void SvTreeListBox::OnCurrentEntryChanged()
+{
+ if ( !mpImpl->m_bDoingQuickSelection )
+ mpImpl->m_aQuickSelectionEngine.Reset();
+}
+
+SvTreeListEntry* SvTreeListBox::GetEntry( SvTreeListEntry* pParent, sal_uInt32 nPos ) const
+{
+ return pModel->GetEntry(pParent, nPos);
+}
+
+SvTreeListEntry* SvTreeListBox::GetEntry( sal_uInt32 nRootPos ) const
+{
+ return pModel->GetEntry(nRootPos);
+}
+
+SvTreeListEntry* SvTreeListBox::GetEntryFromPath( const ::std::deque< sal_Int32 >& _rPath ) const
+{
+
+ SvTreeListEntry* pEntry = nullptr;
+ SvTreeListEntry* pParent = nullptr;
+ for (auto const& elem : _rPath)
+ {
+ pEntry = GetEntry( pParent, elem );
+ if ( !pEntry )
+ break;
+ pParent = pEntry;
+ }
+
+ return pEntry;
+}
+
+void SvTreeListBox::FillEntryPath( SvTreeListEntry* pEntry, ::std::deque< sal_Int32 >& _rPath ) const
+{
+
+ if ( !pEntry )
+ return;
+
+ SvTreeListEntry* pParentEntry = GetParent( pEntry );
+ while ( true )
+ {
+ sal_uInt32 i, nCount = GetLevelChildCount( pParentEntry );
+ for ( i = 0; i < nCount; ++i )
+ {
+ SvTreeListEntry* pTemp = GetEntry( pParentEntry, i );
+ DBG_ASSERT( pEntry, "invalid entry" );
+ if ( pEntry == pTemp )
+ {
+ _rPath.push_front( static_cast<sal_Int32>(i) );
+ break;
+ }
+ }
+
+ if ( pParentEntry )
+ {
+ pEntry = pParentEntry;
+ pParentEntry = GetParent( pParentEntry );
+ }
+ else
+ break;
+ }
+}
+
+SvTreeListEntry* SvTreeListBox::GetParent( SvTreeListEntry* pEntry ) const
+{
+ return pModel->GetParent(pEntry);
+}
+
+sal_uInt32 SvTreeListBox::GetChildCount( SvTreeListEntry const * pParent ) const
+{
+ return pModel->GetChildCount(pParent);
+}
+
+sal_uInt32 SvTreeListBox::GetLevelChildCount( SvTreeListEntry* _pParent ) const
+{
+
+ //if _pParent is 0, then pEntry is the first child of the root.
+ SvTreeListEntry* pEntry = FirstChild( _pParent );
+
+ if( !pEntry )//there is only root, root don't have children
+ return 0;
+
+ if( !_pParent )//root and children of root
+ return pEntry->pParent->m_Children.size();
+
+ return _pParent->m_Children.size();
+}
+
+SvViewDataEntry* SvTreeListBox::GetViewDataEntry( SvTreeListEntry const * pEntry ) const
+{
+ return const_cast<SvViewDataEntry*>(SvListView::GetViewData(pEntry));
+}
+
+SvViewDataItem* SvTreeListBox::GetViewDataItem(SvTreeListEntry const * pEntry, SvLBoxItem const * pItem)
+{
+ return const_cast<SvViewDataItem*>(static_cast<const SvTreeListBox*>(this)->GetViewDataItem(pEntry, pItem));
+}
+
+const SvViewDataItem* SvTreeListBox::GetViewDataItem(const SvTreeListEntry* pEntry, const SvLBoxItem* pItem) const
+{
+ const SvViewDataEntry* pEntryData = SvListView::GetViewData(pEntry);
+ assert(pEntryData && "Entry not in View");
+ sal_uInt16 nItemPos = pEntry->GetPos(pItem);
+ return &pEntryData->GetItem(nItemPos);
+}
+
+void SvTreeListBox::InitViewData( SvViewDataEntry* pData, SvTreeListEntry* pEntry )
+{
+ SvTreeListEntry* pInhEntry = pEntry;
+ SvViewDataEntry* pEntryData = pData;
+
+ pEntryData->Init(pInhEntry->ItemCount());
+ sal_uInt16 nCount = pInhEntry->ItemCount();
+ sal_uInt16 nCurPos = 0;
+ while( nCurPos < nCount )
+ {
+ SvLBoxItem& rItem = pInhEntry->GetItem( nCurPos );
+ SvViewDataItem& rItemData = pEntryData->GetItem(nCurPos);
+ rItem.InitViewData( this, pInhEntry, &rItemData );
+ nCurPos++;
+ }
+}
+
+void SvTreeListBox::EnableSelectionAsDropTarget( bool bEnable )
+{
+ sal_uInt16 nRefDepth;
+ SvTreeListEntry* pTemp;
+
+ SvTreeListEntry* pSelEntry = FirstSelected();
+ while( pSelEntry )
+ {
+ if ( !bEnable )
+ {
+ pSelEntry->nEntryFlags |= SvTLEntryFlags::DISABLE_DROP;
+ nRefDepth = pModel->GetDepth( pSelEntry );
+ pTemp = Next( pSelEntry );
+ while( pTemp && pModel->GetDepth( pTemp ) > nRefDepth )
+ {
+ pTemp->nEntryFlags |= SvTLEntryFlags::DISABLE_DROP;
+ pTemp = Next( pTemp );
+ }
+ }
+ else
+ {
+ pSelEntry->nEntryFlags &= ~SvTLEntryFlags::DISABLE_DROP;
+ nRefDepth = pModel->GetDepth( pSelEntry );
+ pTemp = Next( pSelEntry );
+ while( pTemp && pModel->GetDepth( pTemp ) > nRefDepth )
+ {
+ pTemp->nEntryFlags &= ~SvTLEntryFlags::DISABLE_DROP;
+ pTemp = Next( pTemp );
+ }
+ }
+ pSelEntry = NextSelected( pSelEntry );
+ }
+}
+
+// ******************************************************************
+// InplaceEditing
+// ******************************************************************
+
+VclPtr<Edit> SvTreeListBox::GetEditWidget() const
+{
+ return pEdCtrl ? pEdCtrl->GetEditWidget() : nullptr;
+}
+
+void SvTreeListBox::EditText( const OUString& rStr, const tools::Rectangle& rRect,
+ const Selection& rSel )
+{
+ pEdCtrl.reset();
+ nImpFlags |= SvTreeListBoxFlags::IN_EDT;
+ nImpFlags &= ~SvTreeListBoxFlags::EDTEND_CALLED;
+ HideFocus();
+ pEdCtrl.reset( new SvInplaceEdit2(
+ this, rRect.TopLeft(), rRect.GetSize(), rStr,
+ LINK( this, SvTreeListBox, TextEditEndedHdl_Impl ),
+ rSel ) );
+}
+
+IMPL_LINK_NOARG(SvTreeListBox, TextEditEndedHdl_Impl, SvInplaceEdit2&, void)
+{
+ if ( nImpFlags & SvTreeListBoxFlags::EDTEND_CALLED ) // avoid nesting
+ return;
+ nImpFlags |= SvTreeListBoxFlags::EDTEND_CALLED;
+ OUString aStr;
+ if ( !pEdCtrl->EditingCanceled() )
+ aStr = pEdCtrl->GetText();
+ else
+ aStr = pEdCtrl->GetSavedValue();
+ EditedText( aStr );
+ // Hide may only be called after the new text was put into the entry, so
+ // that we don't call the selection handler in the GetFocus of the listbox
+ // with the old entry text.
+ pEdCtrl->Hide();
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+ GrabFocus();
+}
+
+void SvTreeListBox::CancelTextEditing()
+{
+ if ( pEdCtrl )
+ pEdCtrl->StopEditing( true );
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+}
+
+void SvTreeListBox::EndEditing( bool bCancel )
+{
+ if( pEdCtrl )
+ pEdCtrl->StopEditing( bCancel );
+ nImpFlags &= ~SvTreeListBoxFlags::IN_EDT;
+}
+
+vcl::StringEntryIdentifier SvTreeListBox::CurrentEntry( OUString& _out_entryText ) const
+{
+ // always accept the current entry if there is one
+ SvTreeListEntry* pEntry( GetCurEntry() );
+ if (pEntry)
+ {
+ _out_entryText = GetEntryText(pEntry);
+ return pEntry;
+ }
+
+ pEntry = FirstSelected();
+ if ( !pEntry )
+ pEntry = First();
+
+ if ( pEntry )
+ _out_entryText = GetEntryText( pEntry );
+
+ return pEntry;
+}
+
+vcl::StringEntryIdentifier SvTreeListBox::NextEntry(vcl::StringEntryIdentifier _pCurrentSearchEntry, OUString& _out_entryText) const
+{
+ SvTreeListEntry* pEntry = const_cast< SvTreeListEntry* >( static_cast< const SvTreeListEntry* >( _pCurrentSearchEntry ) );
+
+ if ( ( ( GetChildCount( pEntry ) > 0 )
+ || ( pEntry->HasChildrenOnDemand() )
+ )
+ && !IsExpanded( pEntry )
+ )
+ {
+ SvTreeListEntry* pNextSiblingEntry = pEntry->NextSibling();
+ if ( !pNextSiblingEntry )
+ pEntry = Next( pEntry );
+ else
+ pEntry = pNextSiblingEntry;
+ }
+ else
+ {
+ pEntry = Next( pEntry );
+ }
+
+ if ( !pEntry )
+ pEntry = First();
+
+ if ( pEntry )
+ _out_entryText = GetEntryText( pEntry );
+
+ return pEntry;
+}
+
+void SvTreeListBox::SelectEntry(vcl::StringEntryIdentifier _pEntry)
+{
+ SvTreeListEntry* pEntry = const_cast< SvTreeListEntry* >( static_cast< const SvTreeListEntry* >( _pEntry ) );
+ DBG_ASSERT( pEntry, "SvTreeListBox::SelectSearchEntry: invalid entry!" );
+ if ( !pEntry )
+ return;
+
+ SelectAll( false );
+ SetCurEntry( pEntry );
+ Select( pEntry );
+}
+
+bool SvTreeListBox::HandleKeyInput( const KeyEvent& _rKEvt )
+{
+ if ( _rKEvt.GetKeyCode().IsMod1() )
+ return false;
+
+ if (mbQuickSearch)
+ {
+ mpImpl->m_bDoingQuickSelection = true;
+ const bool bHandled = mpImpl->m_aQuickSelectionEngine.HandleKeyEvent( _rKEvt );
+ mpImpl->m_bDoingQuickSelection = false;
+ if ( bHandled )
+ return true;
+ }
+
+ return false;
+}
+
+
+//JP 28.3.2001: new Drag & Drop API
+sal_Int8 SvTreeListBox::AcceptDrop( const AcceptDropEvent& rEvt )
+{
+ sal_Int8 nRet = DND_ACTION_NONE;
+
+ if (rEvt.mbLeaving || !CheckDragAndDropMode(g_pDDSource, rEvt.mnAction))
+ {
+ ImplShowTargetEmphasis( pTargetEntry, false );
+ }
+ else if( nDragDropMode == DragDropMode::NONE )
+ {
+ SAL_WARN( "svtools.contnr", "SvTreeListBox::QueryDrop(): no target" );
+ }
+ else
+ {
+ SvTreeListEntry* pEntry = GetDropTarget( rEvt.maPosPixel );
+ if( !IsDropFormatSupported( SotClipboardFormatId::TREELISTBOX ) )
+ {
+ SAL_WARN( "svtools.contnr", "SvTreeListBox::QueryDrop(): no format" );
+ }
+ else
+ {
+ DBG_ASSERT(g_pDDSource, "SvTreeListBox::QueryDrop(): SourceBox == 0");
+ if (!( pEntry && g_pDDSource->GetModel() == GetModel()
+ && DND_ACTION_MOVE == rEvt.mnAction
+ && (pEntry->nEntryFlags & SvTLEntryFlags::DISABLE_DROP)))
+ {
+ nRet = rEvt.mnAction;
+ }
+ }
+
+ // **** draw emphasis ****
+ if( DND_ACTION_NONE == nRet )
+ ImplShowTargetEmphasis( pTargetEntry, false );
+ else if( pEntry != pTargetEntry || !(nImpFlags & SvTreeListBoxFlags::TARGEMPH_VIS) )
+ {
+ ImplShowTargetEmphasis( pTargetEntry, false );
+ pTargetEntry = pEntry;
+ ImplShowTargetEmphasis( pTargetEntry, true );
+ }
+ }
+ return nRet;
+}
+
+sal_Int8 SvTreeListBox::ExecuteDrop( const ExecuteDropEvent& rEvt, SvTreeListBox* pSourceView )
+{
+ assert(pSourceView);
+ pSourceView->EnableSelectionAsDropTarget();
+
+ ImplShowTargetEmphasis( pTargetEntry, false );
+ g_pDDTarget = this;
+
+ TransferableDataHelper aData( rEvt.maDropEvent.Transferable );
+
+ sal_Int8 nRet;
+ if( aData.HasFormat( SotClipboardFormatId::TREELISTBOX ))
+ nRet = rEvt.mnAction;
+ else
+ nRet = DND_ACTION_NONE;
+
+ if( DND_ACTION_NONE != nRet )
+ {
+ nRet = DND_ACTION_NONE;
+
+ SvTreeListEntry* pTarget = pTargetEntry; // may be 0!
+
+ if( DND_ACTION_COPY == rEvt.mnAction )
+ {
+ if (CopySelection(g_pDDSource, pTarget))
+ nRet = rEvt.mnAction;
+ }
+ else if( DND_ACTION_MOVE == rEvt.mnAction )
+ {
+ if (MoveSelectionCopyFallbackPossible( g_pDDSource, pTarget, false ))
+ nRet = rEvt.mnAction;
+ }
+ else if( DND_ACTION_COPYMOVE == rEvt.mnAction )
+ {
+ if (MoveSelectionCopyFallbackPossible(g_pDDSource, pTarget, true))
+ nRet = rEvt.mnAction;
+ }
+ }
+ return nRet;
+}
+
+sal_Int8 SvTreeListBox::ExecuteDrop( const ExecuteDropEvent& rEvt )
+{
+ return ExecuteDrop( rEvt, g_pDDSource );
+}
+
+/**
+ * This sets the global variables used to determine the
+ * in-process drag source.
+ */
+void SvTreeListBox::SetupDragOrigin()
+{
+ g_pDDSource = this;
+ g_pDDTarget = nullptr;
+}
+
+void SvTreeListBox::StartDrag( sal_Int8, const Point& rPosPixel )
+{
+ if(!isDisposed())
+ {
+ // tdf#143114 do not start drag when a Button/Checkbox is in
+ // drag-before-ButtonUp mode (CaptureMouse() active)
+ if(pImpl->IsCaptureOnButtonActive())
+ return;
+ }
+
+ nOldDragMode = GetDragDropMode();
+ if ( nOldDragMode == DragDropMode::NONE )
+ return;
+
+ ReleaseMouse();
+
+ SvTreeListEntry* pEntry = GetEntry( rPosPixel ); // GetDropTarget( rPos );
+ if( !pEntry )
+ {
+ DragFinished( DND_ACTION_NONE );
+ return;
+ }
+
+ rtl::Reference<TransferDataContainer> xContainer = m_xTransferHelper;
+
+ if (!xContainer)
+ {
+ xContainer.set(new TransferDataContainer);
+ // apparently some (unused) content is needed
+ xContainer->CopyAnyData( SotClipboardFormatId::TREELISTBOX,
+ "unused", SAL_N_ELEMENTS("unused") );
+ }
+
+ nDragDropMode = NotifyStartDrag();
+ if( nDragDropMode == DragDropMode::NONE || 0 == GetSelectionCount() )
+ {
+ nDragDropMode = nOldDragMode;
+ DragFinished( DND_ACTION_NONE );
+ return;
+ }
+
+ SetupDragOrigin();
+
+ bool bOldUpdateMode = Control::IsUpdateMode();
+ Control::SetUpdateMode( true );
+ PaintImmediately();
+ Control::SetUpdateMode( bOldUpdateMode );
+
+ // Disallow using the selection and its children as drop targets.
+ // Important: If the selection of the SourceListBox is changed in the
+ // DropHandler, the entries have to be allowed as drop targets again:
+ // (GetSourceListBox()->EnableSelectionAsDropTarget( true, true );)
+ EnableSelectionAsDropTarget( false );
+
+ xContainer->StartDrag(this, mnDragAction, GetDragFinishedHdl());
+}
+
+void SvTreeListBox::SetDragHelper(const rtl::Reference<TransferDataContainer>& rHelper, sal_uInt8 eDNDConstants)
+{
+ m_xTransferHelper = rHelper;
+ mnDragAction = eDNDConstants;
+}
+
+void SvTreeListBox::DragFinished( sal_Int8
+#ifndef UNX
+nAction
+#endif
+)
+{
+ EnableSelectionAsDropTarget();
+
+#ifndef UNX
+ if ( (nAction == DND_ACTION_MOVE)
+ && ( (g_pDDTarget && (g_pDDTarget->GetModel() != GetModel()))
+ || !g_pDDTarget))
+ {
+ RemoveSelection();
+ }
+#endif
+
+ UnsetDropTarget();
+ g_pDDSource = nullptr;
+ g_pDDTarget = nullptr;
+ nDragDropMode = nOldDragMode;
+}
+
+void SvTreeListBox::UnsetDropTarget()
+{
+ if (pTargetEntry)
+ {
+ ImplShowTargetEmphasis(pTargetEntry, false);
+ pTargetEntry = nullptr;
+ }
+}
+
+DragDropMode SvTreeListBox::NotifyStartDrag()
+{
+ return DragDropMode(0xffff);
+}
+
+// Handler and methods for Drag - finished handler.
+// The with get GetDragFinishedHdl() get link can set on the
+// TransferDataContainer. This link is a callback for the DragFinished
+// call. AddBox method is called from the GetDragFinishedHdl() and the
+// remove is called in link callback and in the destructor. So it can't
+// called to a deleted object.
+
+namespace
+{
+ // void* to avoid loplugin:vclwidgets, we don't need ownership here
+ std::set<const void*> gSortLBoxes;
+}
+
+void SvTreeListBox::AddBoxToDDList_Impl( const SvTreeListBox& rB )
+{
+ gSortLBoxes.insert( &rB );
+}
+
+void SvTreeListBox::RemoveBoxFromDDList_Impl( const SvTreeListBox& rB )
+{
+ gSortLBoxes.erase( &rB );
+}
+
+IMPL_LINK( SvTreeListBox, DragFinishHdl_Impl, sal_Int8, nAction, void )
+{
+ auto &rSortLBoxes = gSortLBoxes;
+ auto it = rSortLBoxes.find(this);
+ if( it != rSortLBoxes.end() )
+ {
+ DragFinished( nAction );
+ rSortLBoxes.erase( it );
+ }
+}
+
+Link<sal_Int8,void> SvTreeListBox::GetDragFinishedHdl() const
+{
+ AddBoxToDDList_Impl( *this );
+ return LINK( const_cast<SvTreeListBox*>(this), SvTreeListBox, DragFinishHdl_Impl );
+}
+
+/*
+ Bugs/TODO
+
+ - calculate rectangle when editing in-place (bug with some fonts)
+ - SetSpaceBetweenEntries: offset is not taken into account in SetEntryHeight
+*/
+
+#define SV_LBOX_DEFAULT_INDENT_PIXEL 20
+
+void SvTreeListBox::InitTreeView()
+{
+ pCheckButtonData = nullptr;
+ pEdEntry = nullptr;
+ pEdItem = nullptr;
+ nEntryHeight = 0;
+ pEdCtrl = nullptr;
+ nFirstSelTab = 0;
+ nLastSelTab = 0;
+ nFocusWidth = -1;
+ mnCheckboxItemWidth = 0;
+
+ nTreeFlags = SvTreeFlags::RECALCTABS;
+ nIndent = SV_LBOX_DEFAULT_INDENT_PIXEL;
+ nEntryHeightOffs = SV_ENTRYHEIGHTOFFS_PIXEL;
+ pImpl.reset( new SvImpLBox( this, GetModel(), GetStyle() ) );
+
+ mbContextBmpExpanded = true;
+ nContextBmpWidthMax = 0;
+
+ SetFont( GetFont() );
+ AdjustEntryHeightAndRecalc();
+
+ SetSpaceBetweenEntries( 0 );
+ GetOutDev()->SetLineColor();
+ InitSettings();
+ ImplInitStyle();
+ SetTabs();
+}
+
+OUString SvTreeListBox::SearchEntryTextWithHeadTitle( SvTreeListEntry* pEntry )
+{
+ assert(pEntry);
+ OUStringBuffer sRet;
+
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCur = 0;
+ while( nCur < nCount )
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( nCur );
+ if ( (rItem.GetType() == SvLBoxItemType::String) &&
+ !static_cast<SvLBoxString&>( rItem ).GetText().isEmpty() )
+ {
+ sRet.append(static_cast<SvLBoxString&>( rItem ).GetText() + ",");
+ }
+ nCur++;
+ }
+
+ if (!sRet.isEmpty())
+ sRet.remove(sRet.getLength() - 1, 1);
+ return sRet.makeStringAndClear();
+}
+
+SvTreeListBox::~SvTreeListBox()
+{
+ disposeOnce();
+}
+
+void SvTreeListBox::dispose()
+{
+ if (IsMouseCaptured())
+ ReleaseMouse();
+
+ if( pImpl )
+ {
+ pImpl->CallEventListeners( VclEventId::ObjectDying );
+ pImpl.reset();
+ }
+ if( mpImpl )
+ {
+ ClearTabList();
+
+ pEdCtrl.reset();
+
+ SvListView::dispose();
+
+ SvTreeListBox::RemoveBoxFromDDList_Impl( *this );
+
+ if (this == g_pDDSource)
+ g_pDDSource = nullptr;
+ if (this == g_pDDTarget)
+ g_pDDTarget = nullptr;
+ mpImpl.reset();
+ }
+
+ DropTargetHelper::dispose();
+ DragSourceHelper::dispose();
+ Control::dispose();
+}
+
+void SvTreeListBox::SetNoAutoCurEntry( bool b )
+{
+ pImpl->SetNoAutoCurEntry( b );
+}
+
+void SvTreeListBox::SetSublistOpenWithLeftRight()
+{
+ pImpl->m_bSubLstOpLR = true;
+}
+
+void SvTreeListBox::Resize()
+{
+ if( IsEditingActive() )
+ EndEditing( true );
+
+ Control::Resize();
+
+ pImpl->Resize();
+ nFocusWidth = -1;
+ pImpl->ShowCursor( false );
+ pImpl->ShowCursor( true );
+}
+
+/* Cases:
+
+ A) entries have bitmaps
+ 0. no buttons
+ 1. node buttons (can optionally also be on root items)
+ 2. node buttons (can optionally also be on root items) + CheckButton
+ 3. CheckButton
+ B) entries don't have bitmaps (=>via WindowBits because of D&D!)
+ 0. no buttons
+ 1. node buttons (can optionally also be on root items)
+ 2. node buttons (can optionally also be on root items) + CheckButton
+ 3. CheckButton
+*/
+
+#define NO_BUTTONS 0
+#define NODE_BUTTONS 1
+#define NODE_AND_CHECK_BUTTONS 2
+#define CHECK_BUTTONS 3
+
+#define TABFLAGS_TEXT (SvLBoxTabFlags::DYNAMIC | \
+ SvLBoxTabFlags::ADJUST_LEFT | \
+ SvLBoxTabFlags::EDITABLE | \
+ SvLBoxTabFlags::SHOW_SELECTION)
+
+#define TABFLAGS_CONTEXTBMP (SvLBoxTabFlags::DYNAMIC | SvLBoxTabFlags::ADJUST_CENTER)
+
+#define TABFLAGS_CHECKBTN (SvLBoxTabFlags::DYNAMIC | \
+ SvLBoxTabFlags::ADJUST_CENTER)
+
+#define TAB_STARTPOS 2
+
+// take care of GetTextOffset when doing changes
+void SvTreeListBox::SetTabs()
+{
+ if( IsEditingActive() )
+ EndEditing( true );
+ nTreeFlags &= ~SvTreeFlags::RECALCTABS;
+ nFocusWidth = -1;
+ const WinBits nStyle( GetStyle() );
+ bool bHasButtons = (nStyle & WB_HASBUTTONS)!=0;
+ bool bHasButtonsAtRoot = (nStyle & (WB_HASLINESATROOT |
+ WB_HASBUTTONSATROOT))!=0;
+ tools::Long nStartPos = TAB_STARTPOS;
+ tools::Long nNodeWidthPixel = GetExpandedNodeBmp().GetSizePixel().Width();
+
+ // pCheckButtonData->Width() knows nothing about the native checkbox width,
+ // so we have mnCheckboxItemWidth which becomes valid when something is added.
+ tools::Long nCheckWidth = 0;
+ if( nTreeFlags & SvTreeFlags::CHKBTN )
+ nCheckWidth = mnCheckboxItemWidth;
+ tools::Long nCheckWidthDIV2 = nCheckWidth / 2;
+
+ tools::Long nContextWidth = nContextBmpWidthMax;
+ tools::Long nContextWidthDIV2 = nContextWidth / 2;
+
+ ClearTabList();
+
+ int nCase = NO_BUTTONS;
+ if( !(nTreeFlags & SvTreeFlags::CHKBTN) )
+ {
+ if( bHasButtons )
+ nCase = NODE_BUTTONS;
+ }
+ else
+ {
+ if( bHasButtons )
+ nCase = NODE_AND_CHECK_BUTTONS;
+ else
+ nCase = CHECK_BUTTONS;
+ }
+
+ switch( nCase )
+ {
+ case NO_BUTTONS :
+ nStartPos += nContextWidthDIV2; // because of centering
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case NODE_BUTTONS :
+ if( bHasButtonsAtRoot )
+ nStartPos += ( nIndent + (nNodeWidthPixel/2) );
+ else
+ nStartPos += nContextWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ // add an indent if the context bitmap can't be centered without touching the expander
+ if (nContextBmpWidthMax > nIndent + (nNodeWidthPixel / 2))
+ nStartPos += nIndent;
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case NODE_AND_CHECK_BUTTONS :
+ if( bHasButtonsAtRoot )
+ nStartPos += ( nIndent + nNodeWidthPixel );
+ else
+ nStartPos += nCheckWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CHECKBTN );
+ nStartPos += nCheckWidthDIV2; // right edge of CheckButton
+ nStartPos += 3; // distance CheckButton to context bitmap
+ nStartPos += nContextWidthDIV2; // center of context bitmap
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+
+ case CHECK_BUTTONS :
+ nStartPos += nCheckWidthDIV2;
+ AddTab( nStartPos, TABFLAGS_CHECKBTN );
+ nStartPos += nCheckWidthDIV2; // right edge of CheckButton
+ nStartPos += 3; // distance CheckButton to context bitmap
+ nStartPos += nContextWidthDIV2; // center of context bitmap
+ AddTab( nStartPos, TABFLAGS_CONTEXTBMP );
+ nStartPos += nContextWidthDIV2; // right edge of context bitmap
+ // only set a distance if there are bitmaps
+ if( nContextBmpWidthMax )
+ nStartPos += 5; // distance context bitmap to text
+ AddTab( nStartPos, TABFLAGS_TEXT );
+ break;
+ }
+ pImpl->NotifyTabsChanged();
+}
+
+void SvTreeListBox::InitEntry(SvTreeListEntry* pEntry,
+ const OUString& aStr, const Image& aCollEntryBmp, const Image& aExpEntryBmp)
+{
+ if( nTreeFlags & SvTreeFlags::CHKBTN )
+ {
+ pEntry->AddItem(std::make_unique<SvLBoxButton>(pCheckButtonData));
+ }
+
+ pEntry->AddItem(std::make_unique<SvLBoxContextBmp>( aCollEntryBmp,aExpEntryBmp, mbContextBmpExpanded));
+
+ pEntry->AddItem(std::make_unique<SvLBoxString>(aStr));
+}
+
+OUString SvTreeListBox::GetEntryText(SvTreeListEntry* pEntry) const
+{
+ assert(pEntry);
+ SvLBoxString* pItem = static_cast<SvLBoxString*>(pEntry->GetFirstItem(SvLBoxItemType::String));
+ if (pItem) // There may be entries without text items, e.g. in IconView
+ return pItem->GetText();
+ return {};
+}
+
+const Image& SvTreeListBox::GetExpandedEntryBmp(const SvTreeListEntry* pEntry)
+{
+ assert(pEntry);
+ const SvLBoxContextBmp* pItem = static_cast<const SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+ assert(pItem);
+ return pItem->GetBitmap2( );
+}
+
+const Image& SvTreeListBox::GetCollapsedEntryBmp( const SvTreeListEntry* pEntry )
+{
+ assert(pEntry);
+ const SvLBoxContextBmp* pItem = static_cast<const SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+ assert(pItem);
+ return pItem->GetBitmap1( );
+}
+
+IMPL_LINK( SvTreeListBox, CheckButtonClick, SvLBoxButtonData *, pData, void )
+{
+ pHdlEntry = pData->GetActEntry();
+ CheckButtonHdl();
+}
+
+SvTreeListEntry* SvTreeListBox::InsertEntry(
+ const OUString& rText,
+ SvTreeListEntry* pParent,
+ bool bChildrenOnDemand, sal_uInt32 nPos,
+ void* pUser
+)
+{
+ nTreeFlags |= SvTreeFlags::MANINS;
+
+ const Image& rDefExpBmp = pImpl->GetDefaultEntryExpBmp( );
+ const Image& rDefColBmp = pImpl->GetDefaultEntryColBmp( );
+
+ aCurInsertedExpBmp = rDefExpBmp;
+ aCurInsertedColBmp = rDefColBmp;
+
+ SvTreeListEntry* pEntry = new SvTreeListEntry;
+ pEntry->SetUserData( pUser );
+ InitEntry( pEntry, rText, rDefColBmp, rDefExpBmp );
+ pEntry->EnableChildrenOnDemand( bChildrenOnDemand );
+
+ if( !pParent )
+ Insert( pEntry, nPos );
+ else
+ Insert( pEntry, pParent, nPos );
+
+ aPrevInsertedExpBmp = rDefExpBmp;
+ aPrevInsertedColBmp = rDefColBmp;
+
+ nTreeFlags &= ~SvTreeFlags::MANINS;
+
+ return pEntry;
+}
+
+void SvTreeListBox::SetEntryText(SvTreeListEntry* pEntry, const OUString& rStr)
+{
+ SvLBoxString* pItem = static_cast<SvLBoxString*>(pEntry->GetFirstItem(SvLBoxItemType::String));
+ assert(pItem);
+ pItem->SetText(rStr);
+ pItem->InitViewData( this, pEntry );
+ GetModel()->InvalidateEntry( pEntry );
+}
+
+void SvTreeListBox::SetExpandedEntryBmp( SvTreeListEntry* pEntry, const Image& aBmp )
+{
+ SvLBoxContextBmp* pItem = static_cast<SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+
+ assert(pItem);
+ pItem->SetBitmap2( aBmp );
+
+ ModelHasEntryInvalidated(pEntry);
+ CalcEntryHeight( pEntry );
+ Size aSize = aBmp.GetSizePixel();
+ short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast<short>(aSize.Width()) );
+ if( nWidth > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = nWidth;
+ SetTabs();
+ }
+}
+
+void SvTreeListBox::SetCollapsedEntryBmp(SvTreeListEntry* pEntry,const Image& aBmp )
+{
+ SvLBoxContextBmp* pItem = static_cast<SvLBoxContextBmp*>(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp));
+
+ assert(pItem);
+ pItem->SetBitmap1( aBmp );
+
+ ModelHasEntryInvalidated(pEntry);
+ CalcEntryHeight( pEntry );
+ Size aSize = aBmp.GetSizePixel();
+ short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast<short>(aSize.Width()) );
+ if( nWidth > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = nWidth;
+ SetTabs();
+ }
+}
+
+void SvTreeListBox::CheckBoxInserted(SvTreeListEntry* pEntry)
+{
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if( pItem )
+ {
+ auto nWidth = pItem->GetWidth(this, pEntry);
+ if( mnCheckboxItemWidth < nWidth )
+ {
+ mnCheckboxItemWidth = nWidth;
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ }
+ }
+}
+
+void SvTreeListBox::ImpEntryInserted( SvTreeListEntry* pEntry )
+{
+
+ SvTreeListEntry* pParent = pModel->GetParent( pEntry );
+ if( pParent )
+ {
+ SvTLEntryFlags nFlags = pParent->GetFlags();
+ nFlags &= ~SvTLEntryFlags::NO_NODEBMP;
+ pParent->SetFlags( nFlags );
+ }
+
+ if(!((nTreeFlags & SvTreeFlags::MANINS) &&
+ (aPrevInsertedExpBmp == aCurInsertedExpBmp) &&
+ (aPrevInsertedColBmp == aCurInsertedColBmp) ))
+ {
+ Size aSize = GetCollapsedEntryBmp( pEntry ).GetSizePixel();
+ if( aSize.Width() > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = static_cast<short>(aSize.Width());
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ }
+ aSize = GetExpandedEntryBmp( pEntry ).GetSizePixel();
+ if( aSize.Width() > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = static_cast<short>(aSize.Width());
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ }
+ }
+ CalcEntryHeight( pEntry );
+
+ if( !(nTreeFlags & SvTreeFlags::CHKBTN) )
+ return;
+
+ CheckBoxInserted(pEntry);
+}
+
+void SvTreeListBox::SetCheckButtonState( SvTreeListEntry* pEntry, SvButtonState eState)
+{
+ if( !(nTreeFlags & SvTreeFlags::CHKBTN) )
+ return;
+
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if(!pItem)
+ return ;
+ switch( eState )
+ {
+ case SvButtonState::Checked:
+ pItem->SetStateChecked();
+ break;
+
+ case SvButtonState::Unchecked:
+ pItem->SetStateUnchecked();
+ break;
+
+ case SvButtonState::Tristate:
+ pItem->SetStateTristate();
+ break;
+ }
+ InvalidateEntry( pEntry );
+}
+
+SvButtonState SvTreeListBox::GetCheckButtonState( SvTreeListEntry* pEntry ) const
+{
+ SvButtonState eState = SvButtonState::Unchecked;
+ if( pEntry && ( nTreeFlags & SvTreeFlags::CHKBTN ) )
+ {
+ SvLBoxButton* pItem = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if(!pItem)
+ return SvButtonState::Tristate;
+ SvItemStateFlags nButtonFlags = pItem->GetButtonFlags();
+ eState = SvLBoxButtonData::ConvertToButtonState( nButtonFlags );
+ }
+ return eState;
+}
+
+void SvTreeListBox::CheckButtonHdl()
+{
+ if ( pCheckButtonData )
+ pImpl->CallEventListeners( VclEventId::CheckboxToggle, static_cast<void*>(pCheckButtonData->GetActEntry()) );
+}
+
+
+// TODO: Currently all data is cloned so that they conform to the default tree
+// view format. Actually, the model should be used as a reference here. This
+// leads to us _not_ calling SvTreeListEntry::Clone, but only its base class
+// SvTreeListEntry.
+
+
+SvTreeListEntry* SvTreeListBox::CloneEntry( SvTreeListEntry* pSource )
+{
+ OUString aStr;
+ Image aCollEntryBmp;
+ Image aExpEntryBmp;
+
+ SvLBoxString* pStringItem = static_cast<SvLBoxString*>(pSource->GetFirstItem(SvLBoxItemType::String));
+ if( pStringItem )
+ aStr = pStringItem->GetText();
+ SvLBoxContextBmp* pBmpItem = static_cast<SvLBoxContextBmp*>(pSource->GetFirstItem(SvLBoxItemType::ContextBmp));
+ if( pBmpItem )
+ {
+ aCollEntryBmp = pBmpItem->GetBitmap1( );
+ aExpEntryBmp = pBmpItem->GetBitmap2( );
+ }
+ SvTreeListEntry* pClone = new SvTreeListEntry;
+ InitEntry( pClone, aStr, aCollEntryBmp, aExpEntryBmp );
+ pClone->SvTreeListEntry::Clone( pSource );
+ pClone->EnableChildrenOnDemand( pSource->HasChildrenOnDemand() );
+ pClone->SetUserData( pSource->GetUserData() );
+
+ return pClone;
+}
+
+const Image& SvTreeListBox::GetDefaultExpandedEntryBmp( ) const
+{
+ return pImpl->GetDefaultEntryExpBmp( );
+}
+
+const Image& SvTreeListBox::GetDefaultCollapsedEntryBmp( ) const
+{
+ return pImpl->GetDefaultEntryColBmp( );
+}
+
+void SvTreeListBox::SetDefaultExpandedEntryBmp( const Image& aBmp )
+{
+ Size aSize = aBmp.GetSizePixel();
+ if( aSize.Width() > nContextBmpWidthMax )
+ nContextBmpWidthMax = static_cast<short>(aSize.Width());
+ SetTabs();
+
+ pImpl->SetDefaultEntryExpBmp( aBmp );
+}
+
+void SvTreeListBox::SetDefaultCollapsedEntryBmp( const Image& aBmp )
+{
+ Size aSize = aBmp.GetSizePixel();
+ if( aSize.Width() > nContextBmpWidthMax )
+ nContextBmpWidthMax = static_cast<short>(aSize.Width());
+ SetTabs();
+
+ pImpl->SetDefaultEntryColBmp( aBmp );
+}
+
+void SvTreeListBox::EnableCheckButton( SvLBoxButtonData* pData )
+{
+ if( !pData )
+ nTreeFlags &= ~SvTreeFlags::CHKBTN;
+ else
+ {
+ SetCheckButtonData( pData );
+ nTreeFlags |= SvTreeFlags::CHKBTN;
+ pData->SetLink( LINK(this, SvTreeListBox, CheckButtonClick));
+ }
+
+ SetTabs();
+ if( IsUpdateMode() )
+ Invalidate();
+}
+
+void SvTreeListBox::SetCheckButtonData( SvLBoxButtonData* pData )
+{
+ if ( pData )
+ pCheckButtonData = pData;
+}
+
+const Image& SvTreeListBox::GetDefaultExpandedNodeImage( )
+{
+ return SvImpLBox::GetDefaultExpandedNodeImage( );
+}
+
+const Image& SvTreeListBox::GetDefaultCollapsedNodeImage( )
+{
+ return SvImpLBox::GetDefaultCollapsedNodeImage( );
+}
+
+void SvTreeListBox::SetNodeDefaultImages()
+{
+ SetExpandedNodeBmp(GetDefaultExpandedNodeImage());
+ SetCollapsedNodeBmp(GetDefaultCollapsedNodeImage());
+ SetTabs();
+}
+
+bool SvTreeListBox::EditingEntry( SvTreeListEntry* )
+{
+ return true;
+}
+
+bool SvTreeListBox::EditedEntry( SvTreeListEntry* /*pEntry*/,const OUString& /*rNewText*/)
+{
+ return true;
+}
+
+void SvTreeListBox::EnableInplaceEditing( bool bOn )
+{
+ if (bOn)
+ nImpFlags |= SvTreeListBoxFlags::EDT_ENABLED;
+ else
+ nImpFlags &= ~SvTreeListBoxFlags::EDT_ENABLED;
+}
+
+void SvTreeListBox::KeyInput( const KeyEvent& rKEvt )
+{
+ // under OS/2, we get key up/down even while editing
+ if( IsEditingActive() )
+ return;
+
+ if( !pImpl->KeyInput( rKEvt ) )
+ {
+ bool bHandled = HandleKeyInput( rKEvt );
+ if ( !bHandled )
+ Control::KeyInput( rKEvt );
+ }
+}
+
+void SvTreeListBox::RequestingChildren( SvTreeListEntry* pParent )
+{
+ if( !pParent->HasChildren() )
+ InsertEntry( "<dummy>", pParent );
+}
+
+void SvTreeListBox::GetFocus()
+{
+ //If there is no item in the tree, draw focus.
+ if( !First())
+ {
+ Invalidate();
+ }
+ pImpl->GetFocus();
+ Control::GetFocus();
+
+ SvTreeListEntry* pEntry = FirstSelected();
+ if ( !pEntry )
+ {
+ pEntry = pImpl->GetCurEntry();
+ }
+ if (pImpl->m_pCursor)
+ {
+ if (pEntry != pImpl->m_pCursor)
+ pEntry = pImpl->m_pCursor;
+ }
+ if ( pEntry )
+ pImpl->CallEventListeners( VclEventId::ListboxTreeFocus, pEntry );
+
+}
+
+void SvTreeListBox::LoseFocus()
+{
+ // If there is no item in the tree, delete visual focus.
+ if ( !First() )
+ Invalidate();
+ if ( pImpl )
+ pImpl->LoseFocus();
+ Control::LoseFocus();
+}
+
+void SvTreeListBox::ModelHasCleared()
+{
+ pImpl->m_pCursor = nullptr; // else we crash in GetFocus when editing in-place
+ pTargetEntry = nullptr;
+ pEdCtrl.reset();
+ pImpl->Clear();
+ nFocusWidth = -1;
+
+ nContextBmpWidthMax = 0;
+ SetDefaultExpandedEntryBmp( GetDefaultExpandedEntryBmp() );
+ SetDefaultCollapsedEntryBmp( GetDefaultCollapsedEntryBmp() );
+
+ if( !(nTreeFlags & SvTreeFlags::FIXEDHEIGHT ))
+ nEntryHeight = 0;
+ AdjustEntryHeight();
+ AdjustEntryHeight( GetDefaultExpandedEntryBmp() );
+ AdjustEntryHeight( GetDefaultCollapsedEntryBmp() );
+
+ SvListView::ModelHasCleared();
+}
+
+bool SvTreeListBox::PosOverBody(const Point& rPos) const
+{
+ if (rPos.X() < 0 || rPos.Y() < 0)
+ return false;
+ Size aSize(GetSizePixel());
+ if (rPos.X() > aSize.Width() || rPos.Y() > aSize.Height())
+ return false;
+ if (pImpl->m_aVerSBar->IsVisible())
+ {
+ tools::Rectangle aRect(pImpl->m_aVerSBar->GetPosPixel(), pImpl->m_aVerSBar->GetSizePixel());
+ if (aRect.Contains(rPos))
+ return false;
+ }
+ if (pImpl->m_aHorSBar->IsVisible())
+ {
+ tools::Rectangle aRect(pImpl->m_aHorSBar->GetPosPixel(), pImpl->m_aHorSBar->GetSizePixel());
+ if (aRect.Contains(rPos))
+ return false;
+ }
+ return true;
+}
+
+void SvTreeListBox::ScrollOutputArea( short nDeltaEntries )
+{
+ if( !nDeltaEntries || !pImpl->m_aVerSBar->IsVisible() )
+ return;
+
+ tools::Long nThumb = pImpl->m_aVerSBar->GetThumbPos();
+ tools::Long nMax = pImpl->m_aVerSBar->GetRange().Max();
+
+ if( nDeltaEntries < 0 )
+ {
+ // move window up
+ nDeltaEntries *= -1;
+ tools::Long nVis = pImpl->m_aVerSBar->GetVisibleSize();
+ tools::Long nTemp = nThumb + nVis;
+ if( nDeltaEntries > (nMax - nTemp) )
+ nDeltaEntries = static_cast<short>(nMax - nTemp);
+ pImpl->PageDown( static_cast<sal_uInt16>(nDeltaEntries) );
+ }
+ else
+ {
+ if( nDeltaEntries > nThumb )
+ nDeltaEntries = static_cast<short>(nThumb);
+ pImpl->PageUp( static_cast<sal_uInt16>(nDeltaEntries) );
+ }
+ pImpl->SyncVerThumb();
+}
+
+void SvTreeListBox::ScrollToAbsPos( tools::Long nPos )
+{
+ pImpl->ScrollToAbsPos( nPos );
+}
+
+void SvTreeListBox::SetSelectionMode( SelectionMode eSelectMode )
+{
+ eSelMode = eSelectMode;
+ pImpl->SetSelectionMode( eSelectMode );
+}
+
+void SvTreeListBox::SetDragDropMode( DragDropMode nDDMode )
+{
+ nDragDropMode = nDDMode;
+ pImpl->SetDragDropMode( nDDMode );
+}
+
+void SvTreeListBox::CalcEntryHeight( SvTreeListEntry const * pEntry )
+{
+ short nHeightMax=0;
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCur = 0;
+ SvViewDataEntry* pViewData = GetViewDataEntry( pEntry );
+ while( nCur < nCount )
+ {
+ auto nHeight = SvLBoxItem::GetHeight(pViewData, nCur);
+ if( nHeight > nHeightMax )
+ nHeightMax = nHeight;
+ nCur++;
+ }
+
+ if( nHeightMax > nEntryHeight )
+ {
+ nEntryHeight = nHeightMax;
+ Control::SetFont( GetFont() );
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::SetEntryHeight( short nHeight )
+{
+ if( nHeight > nEntryHeight )
+ {
+ nEntryHeight = nHeight;
+ if( nEntryHeight )
+ nTreeFlags |= SvTreeFlags::FIXEDHEIGHT;
+ else
+ nTreeFlags &= ~SvTreeFlags::FIXEDHEIGHT;
+ Control::SetFont( GetFont() );
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::SetEntryWidth( short nWidth )
+{
+ nEntryWidth = nWidth;
+}
+
+void SvTreeListBox::AdjustEntryHeight( const Image& rBmp )
+{
+ const Size aSize( rBmp.GetSizePixel() );
+ if( aSize.Height() > nEntryHeight )
+ {
+ nEntryHeight = static_cast<short>(aSize.Height()) + nEntryHeightOffs;
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::AdjustEntryHeight()
+{
+ tools::Long nHeight = GetTextHeight();
+ if( nHeight > nEntryHeight )
+ {
+ nEntryHeight = static_cast<short>(nHeight) + nEntryHeightOffs;
+ pImpl->SetEntryHeight();
+ }
+}
+
+bool SvTreeListBox::Expand( SvTreeListEntry* pParent )
+{
+ pHdlEntry = pParent;
+ bool bExpanded = false;
+ SvTLEntryFlags nFlags;
+
+ if( pParent->HasChildrenOnDemand() )
+ RequestingChildren( pParent );
+ bool bExpandAllowed = pParent->HasChildren() && ExpandingHdl();
+ // double check if the expander callback ended up removing all children
+ if (pParent->HasChildren())
+ {
+ if (bExpandAllowed)
+ {
+ bExpanded = true;
+ ExpandListEntry( pParent );
+ pImpl->EntryExpanded( pParent );
+ pHdlEntry = pParent;
+ ExpandedHdl();
+ }
+ nFlags = pParent->GetFlags();
+ nFlags &= ~SvTLEntryFlags::NO_NODEBMP;
+ nFlags |= SvTLEntryFlags::HAD_CHILDREN;
+ pParent->SetFlags( nFlags );
+ }
+ else
+ {
+ nFlags = pParent->GetFlags();
+ nFlags |= SvTLEntryFlags::NO_NODEBMP;
+ pParent->SetFlags( nFlags );
+ GetModel()->InvalidateEntry( pParent ); // repaint
+ }
+
+ // #i92103#
+ if ( bExpanded )
+ {
+ pImpl->CallEventListeners( VclEventId::ItemExpanded, pParent );
+ }
+
+ return bExpanded;
+}
+
+bool SvTreeListBox::Collapse( SvTreeListEntry* pParent )
+{
+ pHdlEntry = pParent;
+ bool bCollapsed = false;
+
+ if( ExpandingHdl() )
+ {
+ bCollapsed = true;
+ pImpl->CollapsingEntry( pParent );
+ CollapseListEntry( pParent );
+ pImpl->EntryCollapsed( pParent );
+ pHdlEntry = pParent;
+ ExpandedHdl();
+ }
+
+ // #i92103#
+ if ( bCollapsed )
+ {
+ pImpl->CallEventListeners( VclEventId::ItemCollapsed, pParent );
+ }
+
+ return bCollapsed;
+}
+
+bool SvTreeListBox::Select( SvTreeListEntry* pEntry, bool bSelect )
+{
+ DBG_ASSERT(pEntry,"Select: Null-Ptr");
+ bool bRetVal = SelectListEntry( pEntry, bSelect );
+ DBG_ASSERT(IsSelected(pEntry)==bSelect,"Select failed");
+ if( bRetVal )
+ {
+ pImpl->EntrySelected( pEntry, bSelect );
+ pHdlEntry = pEntry;
+ if( bSelect )
+ {
+ SelectHdl();
+ CallEventListeners( VclEventId::ListboxTreeSelect, pEntry);
+ }
+ else
+ DeselectHdl();
+ }
+ return bRetVal;
+}
+
+sal_uInt32 SvTreeListBox::SelectChildren( SvTreeListEntry* pParent, bool bSelect )
+{
+ pImpl->DestroyAnchor();
+ sal_uInt32 nRet = 0;
+ if( !pParent->HasChildren() )
+ return 0;
+ sal_uInt16 nRefDepth = pModel->GetDepth( pParent );
+ SvTreeListEntry* pChild = FirstChild( pParent );
+ do {
+ nRet++;
+ Select( pChild, bSelect );
+ pChild = Next( pChild );
+ } while( pChild && pModel->GetDepth( pChild ) > nRefDepth );
+ return nRet;
+}
+
+void SvTreeListBox::SelectAll( bool bSelect )
+{
+ pImpl->SelAllDestrAnch(
+ bSelect,
+ true, // delete anchor,
+ true ); // even when using SelectionMode::Single, deselect the cursor
+}
+
+void SvTreeListBox::ModelHasInsertedTree( SvTreeListEntry* pEntry )
+{
+ sal_uInt16 nRefDepth = pModel->GetDepth( pEntry );
+ SvTreeListEntry* pTmp = pEntry;
+ do
+ {
+ ImpEntryInserted( pTmp );
+ pTmp = Next( pTmp );
+ } while( pTmp && nRefDepth < pModel->GetDepth( pTmp ) );
+ pImpl->TreeInserted( pEntry );
+}
+
+void SvTreeListBox::ModelHasInserted( SvTreeListEntry* pEntry )
+{
+ ImpEntryInserted( pEntry );
+ pImpl->EntryInserted( pEntry );
+}
+
+void SvTreeListBox::ModelIsMoving(SvTreeListEntry* pSource )
+{
+ pImpl->MovingEntry( pSource );
+}
+
+void SvTreeListBox::ModelHasMoved( SvTreeListEntry* pSource )
+{
+ pImpl->EntryMoved( pSource );
+}
+
+void SvTreeListBox::ModelIsRemoving( SvTreeListEntry* pEntry )
+{
+ if(pEdEntry == pEntry)
+ pEdEntry = nullptr;
+
+ pImpl->RemovingEntry( pEntry );
+}
+
+void SvTreeListBox::ModelHasRemoved( SvTreeListEntry* pEntry )
+{
+ if (pEntry == pHdlEntry)
+ pHdlEntry = nullptr;
+
+ if (pEntry == pTargetEntry)
+ pTargetEntry = nullptr;
+
+ pImpl->EntryRemoved();
+}
+
+void SvTreeListBox::SetCollapsedNodeBmp( const Image& rBmp)
+{
+ AdjustEntryHeight( rBmp );
+ pImpl->SetCollapsedNodeBmp( rBmp );
+}
+
+void SvTreeListBox::SetExpandedNodeBmp( const Image& rBmp )
+{
+ AdjustEntryHeight( rBmp );
+ pImpl->SetExpandedNodeBmp( rBmp );
+}
+
+
+void SvTreeListBox::SetFont( const vcl::Font& rFont )
+{
+ vcl::Font aTempFont( rFont );
+ vcl::Font aOrigFont( GetFont() );
+ aTempFont.SetTransparent( true );
+ if (aTempFont == aOrigFont)
+ return;
+ Control::SetFont( aTempFont );
+
+ aTempFont.SetColor(aOrigFont.GetColor());
+ aTempFont.SetFillColor(aOrigFont.GetFillColor());
+ aTempFont.SetTransparent(aOrigFont.IsTransparent());
+
+ if (aTempFont == aOrigFont)
+ return;
+
+ AdjustEntryHeightAndRecalc();
+}
+
+void SvTreeListBox::AdjustEntryHeightAndRecalc()
+{
+ AdjustEntryHeight();
+ // always invalidate, else things go wrong in SetEntryHeight
+ RecalcViewData();
+}
+
+void SvTreeListBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
+{
+ Control::Paint(rRenderContext, rRect);
+ if (nTreeFlags & SvTreeFlags::RECALCTABS)
+ SetTabs();
+ pImpl->Paint(rRenderContext, rRect);
+
+ //Add visual focus draw
+ if (First())
+ return;
+
+ if (HasFocus())
+ {
+ tools::Long nHeight = rRenderContext.GetTextHeight();
+ tools::Rectangle aRect(Point(0, 0), Size(GetSizePixel().Width(), nHeight));
+ ShowFocus(aRect);
+ }
+ else
+ {
+ HideFocus();
+ }
+}
+
+void SvTreeListBox::MouseButtonDown( const MouseEvent& rMEvt )
+{
+ // tdf#143114 remember the *correct* starting entry
+ pImpl->m_pCursorOld = (rMEvt.IsLeft() && (nTreeFlags & SvTreeFlags::CHKBTN) && mnClicksToToggle > 0)
+ ? GetEntry(rMEvt.GetPosPixel())
+ : nullptr;
+
+ pImpl->MouseButtonDown( rMEvt );
+}
+
+void SvTreeListBox::MouseButtonUp( const MouseEvent& rMEvt )
+{
+ // tdf#116675 clicking on an entry should toggle its checkbox
+ // tdf#143114 use the already created starting entry and if it exists
+ if (nullptr != pImpl->m_pCursorOld)
+ {
+ const Point aPnt = rMEvt.GetPosPixel();
+ SvTreeListEntry* pEntry = GetEntry(aPnt);
+
+ // compare if MouseButtonUp *is* on the same entry, regardless of scrolling
+ // or other things
+ if (pEntry && pEntry->m_Items.size() > 0 && 1 == mnClicksToToggle && pEntry == pImpl->m_pCursorOld)
+ {
+ SvLBoxItem* pItem = GetItem(pEntry, aPnt.X());
+ // if the checkbox button was clicked, that will be toggled later, do not toggle here
+ // anyway users probably don't want to toggle the checkbox by clickink on another button
+ if (!pItem || pItem->GetType() != SvLBoxItemType::Button)
+ {
+ SvLBoxButton* pItemCheckBox
+ = static_cast<SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
+ if (pItemCheckBox && pItemCheckBox->isEnable() && GetItemPos(pEntry, 0).first < aPnt.X() - GetMapMode().GetOrigin().X())
+ {
+ pItemCheckBox->ClickHdl(pEntry);
+ InvalidateEntry(pEntry);
+ }
+ }
+ }
+ }
+
+ pImpl->MouseButtonUp( rMEvt );
+}
+
+void SvTreeListBox::MouseMove( const MouseEvent& rMEvt )
+{
+ pImpl->MouseMove( rMEvt );
+}
+
+void SvTreeListBox::SetUpdateMode( bool bUpdate )
+{
+ pImpl->SetUpdateMode( bUpdate );
+}
+
+void SvTreeListBox::SetSpaceBetweenEntries( short nOffsLogic )
+{
+ if( nOffsLogic != nEntryHeightOffs )
+ {
+ nEntryHeight = nEntryHeight - nEntryHeightOffs;
+ nEntryHeightOffs = nOffsLogic;
+ nEntryHeight = nEntryHeight + nOffsLogic;
+ AdjustEntryHeightAndRecalc();
+ pImpl->SetEntryHeight();
+ }
+}
+
+void SvTreeListBox::SetCurEntry( SvTreeListEntry* pEntry )
+{
+ pImpl->SetCurEntry( pEntry );
+}
+
+Image const & SvTreeListBox::GetExpandedNodeBmp( ) const
+{
+ return pImpl->GetExpandedNodeBmp( );
+}
+
+Point SvTreeListBox::GetEntryPosition(const SvTreeListEntry* pEntry) const
+{
+ return pImpl->GetEntryPosition( pEntry );
+}
+
+void SvTreeListBox::MakeVisible( SvTreeListEntry* pEntry )
+{
+ pImpl->MakeVisible(pEntry);
+}
+
+void SvTreeListBox::MakeVisible( SvTreeListEntry* pEntry, bool bMoveToTop )
+{
+ pImpl->MakeVisible( pEntry, bMoveToTop );
+}
+
+void SvTreeListBox::ModelHasEntryInvalidated( SvTreeListEntry* pEntry )
+{
+
+ // reinitialize the separate items of the entries
+ sal_uInt16 nCount = pEntry->ItemCount();
+ for( sal_uInt16 nIdx = 0; nIdx < nCount; nIdx++ )
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( nIdx );
+ rItem.InitViewData( this, pEntry );
+ }
+
+ // repaint
+ pImpl->InvalidateEntry( pEntry );
+}
+
+void SvTreeListBox::EditItemText(SvTreeListEntry* pEntry, SvLBoxString* pItem, const Selection& rSelection)
+{
+ assert(pEntry && pItem);
+ if( IsSelected( pEntry ))
+ {
+ pImpl->ShowCursor( false );
+ SelectListEntry( pEntry, false );
+ pImpl->InvalidateEntry(pEntry);
+ SelectListEntry( pEntry, true );
+ pImpl->ShowCursor( true );
+ }
+ pEdEntry = pEntry;
+ pEdItem = pItem;
+ SvLBoxTab* pTab = GetTab( pEntry, pItem );
+ DBG_ASSERT(pTab,"EditItemText:Tab not found");
+
+ auto nItemHeight( pItem->GetHeight(this, pEntry) );
+ Point aPos = GetEntryPosition( pEntry );
+ aPos.AdjustY(( nEntryHeight - nItemHeight ) / 2 );
+ aPos.setX( GetTabPos( pEntry, pTab ) );
+ tools::Long nOutputWidth = pImpl->GetOutputSize().Width();
+ Size aSize( nOutputWidth - aPos.X(), nItemHeight );
+ sal_uInt16 nPos = std::find_if( aTabs.begin(), aTabs.end(),
+ [pTab](const std::unique_ptr<SvLBoxTab>& p) { return p.get() == pTab; })
+ - aTabs.begin();
+ if( nPos+1 < static_cast<sal_uInt16>(aTabs.size()) )
+ {
+ SvLBoxTab* pRightTab = aTabs[ nPos + 1 ].get();
+ tools::Long nRight = GetTabPos( pEntry, pRightTab );
+ if( nRight <= nOutputWidth )
+ aSize.setWidth( nRight - aPos.X() );
+ }
+ Point aOrigin( GetMapMode().GetOrigin() );
+ aPos += aOrigin; // convert to win coordinates
+ aSize.AdjustWidth( -(aOrigin.X()) );
+ tools::Rectangle aRect( aPos, aSize );
+ EditText( pItem->GetText(), aRect, rSelection );
+}
+
+void SvTreeListBox::EditEntry( SvTreeListEntry* pEntry )
+{
+ pImpl->m_aEditClickPos = Point( -1, -1 );
+ ImplEditEntry( pEntry );
+}
+
+void SvTreeListBox::ImplEditEntry( SvTreeListEntry* pEntry )
+{
+ if( IsEditingActive() )
+ EndEditing();
+ if( !pEntry )
+ pEntry = GetCurEntry();
+ if( !pEntry )
+ return;
+
+ tools::Long nClickX = pImpl->m_aEditClickPos.X();
+ bool bIsMouseTriggered = nClickX >= 0;
+
+ SvLBoxString* pItem = nullptr;
+ sal_uInt16 nCount = pEntry->ItemCount();
+ tools::Long nTabPos, nNextTabPos = 0;
+ for( sal_uInt16 i = 0 ; i < nCount ; i++ )
+ {
+ SvLBoxItem& rTmpItem = pEntry->GetItem( i );
+ if (rTmpItem.GetType() != SvLBoxItemType::String)
+ continue;
+
+ SvLBoxTab* pTab = GetTab( pEntry, &rTmpItem );
+ nNextTabPos = -1;
+ if( i < nCount - 1 )
+ {
+ SvLBoxItem& rNextItem = pEntry->GetItem( i + 1 );
+ SvLBoxTab* pNextTab = GetTab( pEntry, &rNextItem );
+ nNextTabPos = pNextTab->GetPos();
+ }
+
+ if( pTab && pTab->IsEditable() )
+ {
+ nTabPos = pTab->GetPos();
+ if( !bIsMouseTriggered || (nClickX > nTabPos && (nNextTabPos == -1 || nClickX < nNextTabPos ) ) )
+ {
+ pItem = static_cast<SvLBoxString*>( &rTmpItem );
+ break;
+ }
+ }
+ }
+
+ if( pItem && EditingEntry( pEntry ) )
+ {
+ Selection aSel( SELECTION_MIN, SELECTION_MAX );
+ SelectAll( false );
+ MakeVisible( pEntry );
+ EditItemText( pEntry, pItem, aSel );
+ }
+}
+
+void SvTreeListBox::EditedText( const OUString& rStr )
+
+{
+ if(pEdEntry) // we have to check if this entry is null that means that it is removed while editing
+ {
+ if( EditedEntry( pEdEntry, rStr ) )
+ {
+ pEdItem->SetText( rStr );
+ pModel->InvalidateEntry( pEdEntry );
+ }
+ if( GetSelectionCount() == 0 )
+ Select( pEdEntry );
+ if( GetSelectionMode() == SelectionMode::Multiple && !GetCurEntry() )
+ SetCurEntry( pEdEntry );
+ }
+}
+
+SvTreeListEntry* SvTreeListBox::GetDropTarget( const Point& rPos )
+{
+ // scroll
+ if( rPos.Y() < 12 )
+ {
+ ImplShowTargetEmphasis(pTargetEntry, false);
+ ScrollOutputArea( +1 );
+ }
+ else
+ {
+ Size aSize( pImpl->GetOutputSize() );
+ if( rPos.Y() > aSize.Height() - 12 )
+ {
+ ImplShowTargetEmphasis(pTargetEntry, false);
+ ScrollOutputArea( -1 );
+ }
+ }
+
+ SvTreeListEntry* pTarget = pImpl->GetEntry( rPos );
+ // when dropping in a vacant space, use the last entry
+ if( !pTarget )
+ return LastVisible();
+ else if( (GetDragDropMode() & DragDropMode::ENABLE_TOP) &&
+ pTarget == First() && rPos.Y() < 6 )
+ return nullptr;
+
+ return pTarget;
+}
+
+
+SvTreeListEntry* SvTreeListBox::GetEntry( const Point& rPos, bool bHit ) const
+{
+ SvTreeListEntry* pEntry = pImpl->GetEntry( rPos );
+ if( pEntry && bHit )
+ {
+ tools::Long nLine = pImpl->GetEntryLine( pEntry );
+ if( !(pImpl->EntryReallyHit( pEntry, rPos, nLine)) )
+ return nullptr;
+ }
+ return pEntry;
+}
+
+SvTreeListEntry* SvTreeListBox::GetCurEntry() const
+{
+ return pImpl ? pImpl->GetCurEntry() : nullptr;
+}
+
+void SvTreeListBox::ImplInitStyle()
+{
+ const WinBits nWindowStyle = GetStyle();
+
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ if (nWindowStyle & WB_SORT)
+ {
+ GetModel()->SetSortMode(SvSortMode::Ascending);
+ GetModel()->SetCompareHdl(LINK(this, SvTreeListBox, DefaultCompare));
+ }
+ else
+ {
+ GetModel()->SetSortMode(SvSortMode::None);
+ GetModel()->SetCompareHdl(Link<const SvSortData&,sal_Int32>());
+ }
+ pImpl->SetStyle(nWindowStyle);
+ pImpl->Resize();
+ Invalidate();
+}
+
+void SvTreeListBox::InvalidateEntry(SvTreeListEntry* pEntry)
+{
+ DBG_ASSERT(pEntry,"InvalidateEntry:No Entry");
+ if (pEntry)
+ {
+ GetModel()->InvalidateEntry(pEntry);
+ }
+}
+
+void SvTreeListBox::PaintEntry1(SvTreeListEntry& rEntry, tools::Long nLine, vcl::RenderContext& rRenderContext)
+{
+ tools::Rectangle aRect; // multi purpose
+
+ bool bHorSBar = pImpl->HasHorScrollBar();
+
+ pImpl->UpdateContextBmpWidthMax(&rEntry);
+
+ if (nTreeFlags & SvTreeFlags::RECALCTABS)
+ SetTabs();
+
+ short nTempEntryHeight = GetEntryHeight();
+ tools::Long nWidth = pImpl->GetOutputSize().Width();
+
+ // Did we turn on the scrollbar within PreparePaints? If yes, we have to set
+ // the ClipRegion anew.
+ if (!bHorSBar && pImpl->HasHorScrollBar())
+ rRenderContext.SetClipRegion(vcl::Region(pImpl->GetClipRegionRect()));
+
+ Point aEntryPos(rRenderContext.GetMapMode().GetOrigin());
+ aEntryPos.setX( aEntryPos.X() * -1 ); // conversion document coordinates
+ tools::Long nMaxRight = nWidth + aEntryPos.X() - 1;
+
+ Color aBackupTextColor(rRenderContext.GetTextColor());
+ vcl::Font aBackupFont(rRenderContext.GetFont());
+ Color aBackupColor = rRenderContext.GetFillColor();
+
+ bool bCurFontIsSel = false;
+ // if a ClipRegion was set from outside, we don't have to reset it
+ const WinBits nWindowStyle = GetStyle();
+ const bool bHideSelection = (nWindowStyle & WB_HIDESELECTION) !=0 && !HasFocus();
+ const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
+
+ vcl::Font aHighlightFont(rRenderContext.GetFont());
+ const Color aHighlightTextColor(rSettings.GetHighlightTextColor());
+ aHighlightFont.SetColor(aHighlightTextColor);
+
+ Size aRectSize(0, nTempEntryHeight);
+
+ SvViewDataEntry* pViewDataEntry = GetViewDataEntry( &rEntry );
+ const bool bSeparator(rEntry.GetFlags() & SvTLEntryFlags::IS_SEPARATOR);
+
+ const auto nMaxContextBmpWidthBeforeIndentIsNeeded =
+ nIndent + GetExpandedNodeBmp().GetSizePixel().Width() / 2;
+ const bool bHasButtonsAtRoot = nWindowStyle & WB_HASBUTTONSATROOT;
+
+ const size_t nTabCount = aTabs.size();
+ const size_t nItemCount = rEntry.ItemCount();
+ size_t nCurTab = 0;
+ size_t nCurItem = 0;
+
+ while (nCurTab < nTabCount && nCurItem < nItemCount)
+ {
+ SvLBoxTab* pTab = aTabs[nCurTab].get();
+ const size_t nNextTab = nCurTab + 1;
+ SvLBoxTab* pNextTab = nNextTab < nTabCount ? aTabs[nNextTab].get() : nullptr;
+ SvLBoxItem& rItem = rEntry.GetItem(nCurItem);
+
+ SvLBoxTabFlags nFlags = pTab->nFlags;
+ Size aSize(rItem.GetWidth(this, pViewDataEntry, nCurItem),
+ SvLBoxItem::GetHeight(pViewDataEntry, nCurItem));
+ tools::Long nTabPos = GetTabPos(&rEntry, pTab);
+
+ tools::Long nNextTabPos;
+ if (pNextTab)
+ nNextTabPos = GetTabPos(&rEntry, pNextTab);
+ else
+ {
+ nNextTabPos = nMaxRight;
+ if (nTabPos > nMaxRight)
+ nNextTabPos += 50;
+ }
+
+ tools::Long nX;
+ if( pTab->nFlags & SvLBoxTabFlags::ADJUST_RIGHT )
+ // avoid cutting the right edge off the tab separation
+ nX = nTabPos + pTab->CalcOffset(aSize.Width(), (nNextTabPos - SV_TAB_BORDER - 1) - nTabPos);
+ else
+ nX = nTabPos + pTab->CalcOffset(aSize.Width(), nNextTabPos - nTabPos);
+
+ // add an indent if the context bitmap can't be centered without touching the expander
+ if (nCurTab == 0 && !(nTreeFlags & SvTreeFlags::CHKBTN) && bHasButtonsAtRoot &&
+ pTab->nFlags & SvLBoxTabFlags::ADJUST_CENTER &&
+ !(pTab->nFlags & SvLBoxTabFlags::FORCE) &&
+ aSize.Width() > nMaxContextBmpWidthBeforeIndentIsNeeded)
+ nX += nIndent;
+
+ aEntryPos.setX( nX );
+ aEntryPos.setY( nLine );
+
+ // set background pattern/color
+
+ Wallpaper aWallpaper = rRenderContext.GetBackground();
+
+ bool bSelTab = bool(nFlags & SvLBoxTabFlags::SHOW_SELECTION);
+
+ if (pViewDataEntry->IsHighlighted() && bSelTab)
+ {
+ Color aNewWallColor = rSettings.GetHighlightColor();
+ // if the face color is bright then the deactivate color is also bright
+ // -> so you can't see any deactivate selection
+ if (bHideSelection && !rSettings.GetFaceColor().IsBright()
+ && aWallpaper.GetColor().IsBright() != rSettings.GetDeactiveColor().IsBright())
+ {
+ aNewWallColor = rSettings.GetDeactiveColor();
+ }
+ // set font color to highlight
+ if (!bCurFontIsSel)
+ {
+ rRenderContext.SetTextColor(aHighlightTextColor);
+ rRenderContext.SetFont(aHighlightFont);
+ bCurFontIsSel = true;
+ }
+ aWallpaper.SetColor(aNewWallColor);
+ }
+ else // no selection
+ {
+ if (bCurFontIsSel || rEntry.GetTextColor())
+ {
+ bCurFontIsSel = false;
+ if (const auto & xCustomTextColor = rEntry.GetTextColor())
+ rRenderContext.SetTextColor(*xCustomTextColor);
+ else
+ rRenderContext.SetTextColor(aBackupTextColor);
+ rRenderContext.SetFont(aBackupFont);
+ }
+ }
+
+ // draw background
+ if (!(nTreeFlags & SvTreeFlags::USESEL))
+ {
+ // only draw the area that is used by the item
+ aRectSize.setWidth( aSize.Width() );
+ aRect.SetPos(aEntryPos);
+ aRect.SetSize(aRectSize);
+ }
+ else
+ {
+ // draw from the current to the next tab
+ if (nCurTab != 0)
+ aRect.SetLeft( nTabPos );
+ else
+ // if we're in the 0th tab, always draw from column 0 --
+ // else we get problems with centered tabs
+ aRect.SetLeft( 0 );
+ aRect.SetTop( nLine );
+ aRect.SetBottom( nLine + nTempEntryHeight - 1 );
+ if (pNextTab)
+ {
+ tools::Long nRight;
+ nRight = GetTabPos(&rEntry, pNextTab) - 1;
+ if (nRight > nMaxRight)
+ nRight = nMaxRight;
+ aRect.SetRight( nRight );
+ }
+ else
+ {
+ aRect.SetRight( nMaxRight );
+ }
+ }
+ // A custom selection that starts at a tab position > 0, do not fill
+ // the background of the 0th item, else e.g. we might not be able to
+ // realize tab listboxes with lines.
+ if (!(nCurTab == 0 && (nTreeFlags & SvTreeFlags::USESEL) && nFirstSelTab))
+ {
+ Color aBackgroundColor = aWallpaper.GetColor();
+ if (aBackgroundColor != COL_TRANSPARENT)
+ {
+ rRenderContext.SetFillColor(aBackgroundColor);
+ // this case may occur for smaller horizontal resizes
+ if (aRect.Left() < aRect.Right())
+ rRenderContext.DrawRect(aRect);
+ }
+ }
+ // draw item
+ // center vertically
+ aEntryPos.AdjustY((nTempEntryHeight - aSize.Height()) / 2 );
+
+ rItem.Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry);
+
+ // division line between tabs (but not if this is a separator line)
+ if (!bSeparator && pNextTab && rItem.GetType() == SvLBoxItemType::String &&
+ // not at the right edge of the window!
+ aRect.Right() < nMaxRight)
+ {
+ aRect.SetLeft( aRect.Right() - SV_TAB_BORDER );
+ rRenderContext.DrawRect(aRect);
+ }
+
+ rRenderContext.SetFillColor(aBackupColor);
+
+ nCurItem++;
+ nCurTab++;
+ }
+
+ if (pViewDataEntry->IsDragTarget())
+ {
+ rRenderContext.Push();
+ rRenderContext.SetLineColor(rSettings.GetDeactiveColor());
+ rRenderContext.SetFillColor(rSettings.GetDeactiveColor());
+
+ const bool bAsTree = GetStyle() & (WB_HASLINES | WB_HASLINESATROOT);
+ if (bAsTree)
+ {
+ rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine + nTempEntryHeight - 2), Size(nWidth, 2)));
+ rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine), Size(nWidth, 2)));
+ }
+ else
+ {
+ rRenderContext.DrawRect(tools::Rectangle(Point(0, nLine), Size(nWidth, 2)));
+ }
+
+ rRenderContext.Pop();
+ }
+
+ if (bCurFontIsSel || rEntry.GetTextColor())
+ {
+ rRenderContext.SetTextColor(aBackupTextColor);
+ rRenderContext.SetFont(aBackupFont);
+ }
+
+ sal_uInt16 nFirstDynTabPos(0);
+ SvLBoxTab* pFirstDynamicTab = GetFirstDynamicTab(nFirstDynTabPos);
+ tools::Long nDynTabPos = GetTabPos(&rEntry, pFirstDynamicTab);
+ nDynTabPos += pImpl->m_nNodeBmpTabDistance;
+ nDynTabPos += pImpl->m_nNodeBmpWidth / 2;
+ nDynTabPos += 4; // 4 pixels of buffer, so the node bitmap is not too close
+ // to the next tab
+
+ if( !((!(rEntry.GetFlags() & SvTLEntryFlags::NO_NODEBMP)) &&
+ (nWindowStyle & WB_HASBUTTONS) && pFirstDynamicTab &&
+ (rEntry.HasChildren() || rEntry.HasChildrenOnDemand())))
+ return;
+
+ // find first tab and check if the node bitmap extends into it
+ sal_uInt16 nNextTab = nFirstDynTabPos;
+ SvLBoxTab* pNextTab;
+ do
+ {
+ nNextTab++;
+ pNextTab = nNextTab < nTabCount ? aTabs[nNextTab].get() : nullptr;
+ } while (pNextTab && pNextTab->IsDynamic());
+
+ if (pNextTab && (GetTabPos( &rEntry, pNextTab ) <= nDynTabPos))
+ return;
+
+ if (!((nWindowStyle & WB_HASBUTTONSATROOT) || pModel->GetDepth(&rEntry) > 0))
+ return;
+
+ Point aPos(GetTabPos(&rEntry, pFirstDynamicTab), nLine);
+ aPos.AdjustX(pImpl->m_nNodeBmpTabDistance );
+
+ const Image* pImg = nullptr;
+
+ const bool bExpanded = IsExpanded(&rEntry);
+ if (bExpanded)
+ pImg = &pImpl->GetExpandedNodeBmp();
+ else
+ pImg = &pImpl->GetCollapsedNodeBmp();
+ const bool bDefaultImage = bExpanded ? *pImg == GetDefaultExpandedNodeImage()
+ : *pImg == GetDefaultCollapsedNodeImage();
+ aPos.AdjustY((nTempEntryHeight - pImg->GetSizePixel().Height()) / 2 );
+
+ if (!bDefaultImage)
+ {
+ // If it's a custom image then draw what was explicitly set to use
+ DrawImageFlags nStyle = DrawImageFlags::NONE;
+ if (!IsEnabled())
+ nStyle |= DrawImageFlags::Disable;
+ rRenderContext.DrawImage(aPos, *pImg, nStyle);
+ }
+ else
+ {
+ bool bNativeOK = false;
+ // native
+ if (rRenderContext.IsNativeControlSupported(ControlType::ListNode, ControlPart::Entire))
+ {
+ ImplControlValue aControlValue;
+ tools::Rectangle aCtrlRegion(aPos, pImg->GetSizePixel());
+ ControlState nState = ControlState::NONE;
+
+ if (IsEnabled())
+ nState |= ControlState::ENABLED;
+
+ if (bExpanded)
+ aControlValue.setTristateVal(ButtonValue::On); //expanded node
+ else
+ {
+ if ((!rEntry.HasChildren()) && rEntry.HasChildrenOnDemand() &&
+ (!(rEntry.GetFlags() & SvTLEntryFlags::HAD_CHILDREN)))
+ {
+ aControlValue.setTristateVal( ButtonValue::DontKnow ); //don't know
+ }
+ else
+ {
+ aControlValue.setTristateVal( ButtonValue::Off ); //collapsed node
+ }
+ }
+
+ bNativeOK = rRenderContext.DrawNativeControl(ControlType::ListNode, ControlPart::Entire, aCtrlRegion, nState, aControlValue, OUString());
+ }
+ if (!bNativeOK)
+ {
+ DecorationView aDecoView(&rRenderContext);
+ DrawSymbolFlags nSymbolStyle = DrawSymbolFlags::NONE;
+ if (!IsEnabled())
+ nSymbolStyle |= DrawSymbolFlags::Disable;
+
+ Color aCol = aBackupTextColor;
+ if (pViewDataEntry->IsHighlighted())
+ aCol = aHighlightTextColor;
+
+ SymbolType eSymbol = bExpanded ? SymbolType::SPIN_DOWN : SymbolType::SPIN_RIGHT;
+ aDecoView.DrawSymbol(tools::Rectangle(aPos, pImg->GetSizePixel()), eSymbol, aCol, nSymbolStyle);
+ }
+ }
+}
+
+void SvTreeListBox::DrawCustomEntry(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const SvTreeListEntry& rEntry)
+{
+ aCustomRenderHdl.Call(std::tuple<vcl::RenderContext&, const tools::Rectangle&, const SvTreeListEntry&>(rRenderContext, rRect, rEntry));
+}
+
+Size SvTreeListBox::MeasureCustomEntry(vcl::RenderContext& rRenderContext, const SvTreeListEntry& rEntry) const
+{
+ return aCustomMeasureHdl.Call(std::pair<vcl::RenderContext&, const SvTreeListEntry&>(rRenderContext, rEntry));
+}
+
+tools::Rectangle SvTreeListBox::GetFocusRect(const SvTreeListEntry* pEntry, tools::Long nLine )
+{
+ pImpl->UpdateContextBmpWidthMax( pEntry );
+
+ Size aSize;
+ tools::Rectangle aRect;
+ aRect.SetTop( nLine );
+ aSize.setHeight( GetEntryHeight() );
+
+ tools::Long nRealWidth = pImpl->GetOutputSize().Width();
+ nRealWidth -= GetMapMode().GetOrigin().X();
+
+ sal_uInt16 nCurTab;
+ SvLBoxTab* pTab = GetFirstTab( SvLBoxTabFlags::SHOW_SELECTION, nCurTab );
+ tools::Long nTabPos = 0;
+ if( pTab )
+ nTabPos = GetTabPos( pEntry, pTab );
+ tools::Long nNextTabPos;
+ if( pTab && nCurTab < aTabs.size() - 1 )
+ {
+ SvLBoxTab* pNextTab = aTabs[ nCurTab + 1 ].get();
+ nNextTabPos = GetTabPos( pEntry, pNextTab );
+ }
+ else
+ {
+ nNextTabPos = nRealWidth;
+ if( nTabPos > nRealWidth )
+ nNextTabPos += 50;
+ }
+
+ bool bUserSelection = bool( nTreeFlags & SvTreeFlags::USESEL );
+ if( !bUserSelection )
+ {
+ if( pTab && nCurTab < pEntry->ItemCount() )
+ {
+ const SvLBoxItem& rItem = pEntry->GetItem( nCurTab );
+ aSize.setWidth(rItem.GetWidth(this, pEntry));
+ if( !aSize.Width() )
+ aSize.setWidth( 15 );
+ tools::Long nX = nTabPos; //GetTabPos( pEntry, pTab );
+ // alignment
+ nX += pTab->CalcOffset( aSize.Width(), nNextTabPos - nTabPos );
+ aRect.SetLeft( nX );
+ // make sure that first and last letter aren't cut off slightly
+ aRect.SetSize( aSize );
+ if( aRect.Left() > 0 )
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustRight( 1 );
+ }
+ }
+ else
+ {
+ // if SelTab != 0, we have to calculate also
+ if( nFocusWidth == -1 || nFirstSelTab )
+ {
+ SvLBoxTab* pLastTab = nullptr; // default to select whole width
+
+ sal_uInt16 nLastTab;
+ GetLastTab(SvLBoxTabFlags::SHOW_SELECTION,nLastTab);
+ nLastTab++;
+ if( nLastTab < aTabs.size() ) // is there another one?
+ pLastTab = aTabs[ nLastTab ].get();
+
+ aSize.setWidth( pLastTab ? pLastTab->GetPos() : 0x0fffffff );
+ nFocusWidth = static_cast<short>(aSize.Width());
+ if( pTab )
+ nFocusWidth = nFocusWidth - static_cast<short>(nTabPos); //pTab->GetPos();
+ }
+ else
+ {
+ aSize.setWidth( nFocusWidth );
+ if( pTab )
+ {
+ if( nCurTab )
+ aSize.AdjustWidth(nTabPos );
+ else
+ aSize.AdjustWidth(pTab->GetPos() ); // Tab0 always from the leftmost position
+ }
+ }
+ // if selection starts with 0th tab, draw from column 0 on
+ if( nCurTab != 0 )
+ {
+ aRect.SetLeft( nTabPos );
+ aSize.AdjustWidth( -nTabPos );
+ }
+ aRect.SetSize( aSize );
+ }
+ // adjust right edge because of clipping
+ if( aRect.Right() >= nRealWidth )
+ {
+ aRect.SetRight( nRealWidth-1 );
+ nFocusWidth = static_cast<short>(aRect.GetWidth());
+ }
+ return aRect;
+}
+
+sal_IntPtr SvTreeListBox::GetTabPos(const SvTreeListEntry* pEntry, const SvLBoxTab* pTab) const
+{
+ assert(pTab);
+ sal_IntPtr nPos = pTab->GetPos();
+ if( pTab->IsDynamic() )
+ {
+ sal_uInt16 nDepth = pModel->GetDepth( pEntry );
+ nDepth = nDepth * static_cast<sal_uInt16>(nIndent);
+ nPos += static_cast<sal_IntPtr>(nDepth);
+ }
+ return nPos + (pEntry->GetExtraIndent() * nIndent);
+}
+
+SvLBoxItem* SvTreeListBox::GetItem_Impl( SvTreeListEntry* pEntry, tools::Long nX,
+ SvLBoxTab** ppTab )
+{
+ SvLBoxItem* pItemClicked = nullptr;
+ sal_uInt16 nTabCount = aTabs.size();
+ sal_uInt16 nItemCount = pEntry->ItemCount();
+ SvLBoxTab* pTab = aTabs.front().get();
+ SvLBoxItem* pItem = &pEntry->GetItem(0);
+ sal_uInt16 nNextItem = 1;
+ nX -= GetMapMode().GetOrigin().X();
+ tools::Long nRealWidth = pImpl->GetOutputSize().Width();
+ nRealWidth -= GetMapMode().GetOrigin().X();
+
+ while( true )
+ {
+ SvLBoxTab* pNextTab=nNextItem<nTabCount ? aTabs[nNextItem].get() : nullptr;
+ tools::Long nStart = GetTabPos( pEntry, pTab );
+
+ tools::Long nNextTabPos;
+ if( pNextTab )
+ nNextTabPos = GetTabPos( pEntry, pNextTab );
+ else
+ {
+ nNextTabPos = nRealWidth;
+ if( nStart > nRealWidth )
+ nNextTabPos += 50;
+ }
+
+ auto nItemWidth(pItem->GetWidth(this, pEntry));
+ nStart += pTab->CalcOffset(nItemWidth, nNextTabPos - nStart);
+ auto nLen = nItemWidth;
+ if( pNextTab )
+ {
+ tools::Long nTabWidth = GetTabPos( pEntry, pNextTab ) - nStart;
+ if( nTabWidth < nLen )
+ nLen = nTabWidth;
+ }
+
+ if( nX >= nStart && nX < (nStart+nLen ) )
+ {
+ pItemClicked = pItem;
+ if( ppTab )
+ {
+ *ppTab = pTab;
+ break;
+ }
+ }
+ if( nNextItem >= nItemCount || nNextItem >= nTabCount)
+ break;
+ pTab = aTabs[ nNextItem ].get();
+ pItem = &pEntry->GetItem( nNextItem );
+ nNextItem++;
+ }
+ return pItemClicked;
+}
+
+std::pair<tools::Long, tools::Long> SvTreeListBox::GetItemPos(SvTreeListEntry* pEntry, sal_uInt16 nTabIdx)
+{
+ sal_uInt16 nTabCount = aTabs.size();
+ sal_uInt16 nItemCount = pEntry->ItemCount();
+ if (nTabIdx >= nItemCount || nTabIdx >= nTabCount)
+ return std::make_pair(-1, -1);
+
+ SvLBoxTab* pTab = aTabs.front().get();
+ SvLBoxItem* pItem = &pEntry->GetItem(nTabIdx);
+ sal_uInt16 nNextItem = nTabIdx + 1;
+
+ tools::Long nRealWidth = pImpl->GetOutputSize().Width();
+ nRealWidth -= GetMapMode().GetOrigin().X();
+
+ SvLBoxTab* pNextTab = nNextItem < nTabCount ? aTabs[nNextItem].get() : nullptr;
+ tools::Long nStart = GetTabPos(pEntry, pTab);
+
+ tools::Long nNextTabPos;
+ if (pNextTab)
+ nNextTabPos = GetTabPos(pEntry, pNextTab);
+ else
+ {
+ nNextTabPos = nRealWidth;
+ if (nStart > nRealWidth)
+ nNextTabPos += 50;
+ }
+
+ auto nItemWidth(pItem->GetWidth(this, pEntry));
+ nStart += pTab->CalcOffset(nItemWidth, nNextTabPos - nStart);
+ auto nLen = nItemWidth;
+ if (pNextTab)
+ {
+ tools::Long nTabWidth = GetTabPos(pEntry, pNextTab) - nStart;
+ if (nTabWidth < nLen)
+ nLen = nTabWidth;
+ }
+ return std::make_pair(nStart, nLen);
+}
+
+tools::Long SvTreeListBox::getPreferredDimensions(std::vector<tools::Long> &rWidths) const
+{
+ tools::Long nHeight = 0;
+ rWidths.clear();
+ SvTreeListEntry* pEntry = First();
+ while (pEntry)
+ {
+ sal_uInt16 nCount = pEntry->ItemCount();
+ sal_uInt16 nCurPos = 0;
+ if (nCount > rWidths.size())
+ rWidths.resize(nCount);
+ while (nCurPos < nCount)
+ {
+ SvLBoxItem& rItem = pEntry->GetItem( nCurPos );
+ auto nWidth = rItem.GetWidth(this, pEntry);
+ if (nWidth)
+ {
+ nWidth += SV_TAB_BORDER * 2;
+ if (nWidth > rWidths[nCurPos])
+ rWidths[nCurPos] = nWidth;
+ }
+ ++nCurPos;
+ }
+ pEntry = Next( pEntry );
+ nHeight += GetEntryHeight();
+ }
+ return nHeight;
+}
+
+Size SvTreeListBox::GetOptimalSize() const
+{
+ std::vector<tools::Long> aWidths;
+ Size aRet(0, getPreferredDimensions(aWidths));
+ for (tools::Long aWidth : aWidths)
+ aRet.AdjustWidth(aWidth );
+
+ sal_Int32 nLeftBorder(0), nTopBorder(0), nRightBorder(0), nBottomBorder(0);
+ GetBorder(nLeftBorder, nTopBorder, nRightBorder, nBottomBorder);
+ aRet.AdjustWidth(nLeftBorder + nRightBorder);
+ aRet.AdjustHeight(nTopBorder + nBottomBorder);
+
+ tools::Long nMinWidth = nMinWidthInChars * approximate_char_width();
+ aRet.setWidth( std::max(aRet.Width(), nMinWidth) );
+
+ if (GetStyle() & WB_VSCROLL)
+ aRet.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize());
+
+ return aRet;
+}
+
+void SvTreeListBox::SetForceMakeVisible( bool bEnable )
+{
+ pImpl->SetForceMakeVisible(bEnable);
+}
+
+SvLBoxItem* SvTreeListBox::GetItem(SvTreeListEntry* pEntry,tools::Long nX,SvLBoxTab** ppTab)
+{
+ return GetItem_Impl( pEntry, nX, ppTab );
+}
+
+SvLBoxItem* SvTreeListBox::GetItem(SvTreeListEntry* pEntry,tools::Long nX )
+{
+ SvLBoxTab* pDummyTab;
+ return GetItem_Impl( pEntry, nX, &pDummyTab );
+}
+
+void SvTreeListBox::AddTab(tools::Long nTabPos, SvLBoxTabFlags nFlags )
+{
+ nFocusWidth = -1;
+ SvLBoxTab* pTab = new SvLBoxTab( nTabPos, nFlags );
+ aTabs.emplace_back( pTab );
+ if( nTreeFlags & SvTreeFlags::USESEL )
+ {
+ sal_uInt16 nPos = aTabs.size() - 1;
+ if( nPos >= nFirstSelTab && nPos <= nLastSelTab )
+ pTab->nFlags |= SvLBoxTabFlags::SHOW_SELECTION;
+ else
+ // string items usually have to be selected -- turn this off
+ // explicitly
+ pTab->nFlags &= ~SvLBoxTabFlags::SHOW_SELECTION;
+ }
+}
+
+
+SvLBoxTab* SvTreeListBox::GetFirstDynamicTab( sal_uInt16& rPos ) const
+{
+ sal_uInt16 nCurTab = 0;
+ sal_uInt16 nTabCount = aTabs.size();
+ while( nCurTab < nTabCount )
+ {
+ SvLBoxTab* pTab = aTabs[nCurTab].get();
+ if( pTab->nFlags & SvLBoxTabFlags::DYNAMIC )
+ {
+ rPos = nCurTab;
+ return pTab;
+ }
+ nCurTab++;
+ }
+ return nullptr;
+}
+
+SvLBoxTab* SvTreeListBox::GetFirstDynamicTab() const
+{
+ sal_uInt16 nDummy;
+ return GetFirstDynamicTab( nDummy );
+}
+
+SvLBoxTab* SvTreeListBox::GetTab( SvTreeListEntry const * pEntry, SvLBoxItem const * pItem) const
+{
+ sal_uInt16 nPos = pEntry->GetPos( pItem );
+ return aTabs[ nPos ].get();
+}
+
+void SvTreeListBox::ClearTabList()
+{
+ aTabs.clear();
+}
+
+
+Size SvTreeListBox::GetOutputSizePixel() const
+{
+ Size aSize = pImpl->GetOutputSize();
+ return aSize;
+}
+
+void SvTreeListBox::NotifyScrolled()
+{
+ aScrolledHdl.Call( this );
+}
+
+void SvTreeListBox::ImplInvalidate( const vcl::Region* pRegion, InvalidateFlags nInvalidateFlags )
+{
+ if (!pImpl)
+ return;
+ if( nFocusWidth == -1 )
+ // to make sure that the control doesn't show the wrong focus rectangle
+ // after painting
+ pImpl->RecalcFocusRect();
+ Control::ImplInvalidate( pRegion, nInvalidateFlags );
+ pImpl->Invalidate();
+}
+
+void SvTreeListBox::SetHighlightRange( sal_uInt16 nStart, sal_uInt16 nEnd)
+{
+
+ nTreeFlags |= SvTreeFlags::USESEL;
+ if( nStart > nEnd )
+ std::swap(nStart, nEnd);
+ // select all tabs that lie within the area
+ nTreeFlags |= SvTreeFlags::RECALCTABS;
+ nFirstSelTab = nStart;
+ nLastSelTab = nEnd;
+ pImpl->RecalcFocusRect();
+}
+
+void SvTreeListBox::Command(const CommandEvent& rCEvt)
+{
+ if (!aPopupMenuHdl.Call(rCEvt))
+ pImpl->Command(rCEvt);
+ //pass at least alt press/release to parent impl
+ if (rCEvt.GetCommand() == CommandEventId::ModKeyChange)
+ Control::Command(rCEvt);
+}
+
+SvLBoxTab* SvTreeListBox::GetFirstTab( SvLBoxTabFlags nFlagMask, sal_uInt16& rPos )
+{
+ sal_uInt16 nTabCount = aTabs.size();
+ for( sal_uInt16 nPos = 0; nPos < nTabCount; nPos++ )
+ {
+ SvLBoxTab* pTab = aTabs[ nPos ].get();
+ if( pTab->nFlags & nFlagMask )
+ {
+ rPos = nPos;
+ return pTab;
+ }
+ }
+ rPos = 0xffff;
+ return nullptr;
+}
+
+void SvTreeListBox::GetLastTab( SvLBoxTabFlags nFlagMask, sal_uInt16& rTabPos )
+{
+ sal_uInt16 nPos = static_cast<sal_uInt16>(aTabs.size());
+ while( nPos )
+ {
+ --nPos;
+ SvLBoxTab* pTab = aTabs[ nPos ].get();
+ if( pTab->nFlags & nFlagMask )
+ {
+ rTabPos = nPos;
+ return;
+ }
+ }
+ rTabPos = 0xffff;
+}
+
+void SvTreeListBox::RequestHelp( const HelpEvent& rHEvt )
+{
+ if (aTooltipHdl.IsSet())
+ {
+ const Point pos(ScreenToOutputPixel(rHEvt.GetMousePosPixel()));
+ if (SvTreeListEntry* entry = GetEntry(pos))
+ {
+ const OUString tooltip = aTooltipHdl.Call(entry);
+ if (!tooltip.isEmpty())
+ {
+ const Size size(GetOutputSizePixel().Width(), GetEntryHeight());
+ tools::Rectangle screenRect(OutputToScreenPixel(GetEntryPosition(entry)), size);
+ Help::ShowQuickHelp(this, screenRect, tooltip);
+ return;
+ }
+ }
+ }
+
+ if( !pImpl->RequestHelp( rHEvt ) )
+ Control::RequestHelp( rHEvt );
+}
+
+sal_Int32 SvTreeListBox::DefaultCompare(const SvLBoxString* pLeftText, const SvLBoxString* pRightText)
+{
+ OUString aLeft = pLeftText ? pLeftText->GetText() : OUString();
+ OUString aRight = pRightText ? pRightText->GetText() : OUString();
+ pImpl->UpdateStringSorter();
+ return pImpl->m_pStringSorter->compare(aLeft, aRight);
+}
+
+IMPL_LINK( SvTreeListBox, DefaultCompare, const SvSortData&, rData, sal_Int32 )
+{
+ const SvTreeListEntry* pLeft = rData.pLeft;
+ const SvTreeListEntry* pRight = rData.pRight;
+ const SvLBoxString* pLeftText = static_cast<const SvLBoxString*>(pLeft->GetFirstItem(SvLBoxItemType::String));
+ const SvLBoxString* pRightText = static_cast<const SvLBoxString*>(pRight->GetFirstItem(SvLBoxItemType::String));
+ return DefaultCompare(pLeftText, pRightText);
+}
+
+void SvTreeListBox::ModelNotification( SvListAction nActionId, SvTreeListEntry* pEntry1,
+ SvTreeListEntry* pEntry2, sal_uInt32 nPos )
+{
+ SolarMutexGuard aSolarGuard;
+
+ if( nActionId == SvListAction::CLEARING )
+ CancelTextEditing();
+
+ SvListView::ModelNotification( nActionId, pEntry1, pEntry2, nPos );
+ switch( nActionId )
+ {
+ case SvListAction::INSERTED:
+ {
+ SvLBoxContextBmp* pBmpItem = static_cast< SvLBoxContextBmp* >( pEntry1->GetFirstItem( SvLBoxItemType::ContextBmp ) );
+ if ( !pBmpItem )
+ break;
+ const Image& rBitmap1( pBmpItem->GetBitmap1() );
+ const Image& rBitmap2( pBmpItem->GetBitmap2() );
+ short nMaxWidth = short( std::max( rBitmap1.GetSizePixel().Width(), rBitmap2.GetSizePixel().Width() ) );
+ nMaxWidth = pImpl->UpdateContextBmpWidthVector( pEntry1, nMaxWidth );
+ if( nMaxWidth > nContextBmpWidthMax )
+ {
+ nContextBmpWidthMax = nMaxWidth;
+ SetTabs();
+ }
+ if (get_width_request() == -1)
+ queue_resize();
+ }
+ break;
+
+ case SvListAction::RESORTING:
+ SetUpdateMode( false );
+ break;
+
+ case SvListAction::RESORTED:
+ // after a selection: show first entry and also keep the selection
+ MakeVisible( pModel->First(), true );
+ SetUpdateMode( true );
+ break;
+
+ case SvListAction::CLEARED:
+ if( IsUpdateMode() )
+ PaintImmediately();
+ break;
+
+ default: break;
+ }
+}
+
+SvTreeListEntry* SvTreeListBox::GetFirstEntryInView() const
+{
+ return GetEntry( Point() );
+}
+
+SvTreeListEntry* SvTreeListBox::GetNextEntryInView(SvTreeListEntry* pEntry ) const
+{
+ SvTreeListEntry* pNext = NextVisible( pEntry );
+ if( pNext )
+ {
+ Point aPos( GetEntryPosition(pNext) );
+ const Size& rSize = pImpl->GetOutputSize();
+ if( aPos.Y() < 0 || aPos.Y() >= rSize.Height() )
+ return nullptr;
+ }
+ return pNext;
+}
+
+
+void SvTreeListBox::DataChanged( const DataChangedEvent& rDCEvt )
+{
+ if( (rDCEvt.GetType()==DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) )
+ {
+ nEntryHeight = 0; // _together_ with true of 1. par (bFont) of InitSettings() a zero-height
+ // forces complete recalc of heights!
+ InitSettings();
+ Invalidate();
+ }
+ else
+ Control::DataChanged( rDCEvt );
+}
+
+void SvTreeListBox::StateChanged( StateChangedType eType )
+{
+ if( eType == StateChangedType::Enable )
+ Invalidate( InvalidateFlags::Children );
+
+ Control::StateChanged( eType );
+
+ if ( eType == StateChangedType::Style )
+ ImplInitStyle();
+}
+
+void SvTreeListBox::ApplySettings(vcl::RenderContext& rRenderContext)
+{
+ SetPointFont(rRenderContext, GetPointFont(*GetOutDev()));
+
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
+ rRenderContext.SetTextFillColor();
+ rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
+
+ // always try to re-create default-SvLBoxButtonData
+ if (pCheckButtonData && pCheckButtonData->HasDefaultImages())
+ pCheckButtonData->SetDefaultImages(this);
+}
+
+void SvTreeListBox::InitSettings()
+{
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ vcl::Font aFont = rStyleSettings.GetFieldFont();
+ SetPointFont(*GetOutDev(), aFont);
+ AdjustEntryHeightAndRecalc();
+
+ SetTextColor(rStyleSettings.GetFieldTextColor());
+ SetTextFillColor();
+
+ SetBackground(rStyleSettings.GetFieldColor());
+
+ // always try to re-create default-SvLBoxButtonData
+ if( pCheckButtonData && pCheckButtonData->HasDefaultImages() )
+ pCheckButtonData->SetDefaultImages(this);
+}
+
+css::uno::Reference< XAccessible > SvTreeListBox::CreateAccessible()
+{
+ vcl::Window* pParent = GetAccessibleParentWindow();
+ DBG_ASSERT( pParent, "SvTreeListBox::CreateAccessible - accessible parent not found" );
+
+ css::uno::Reference< XAccessible > xAccessible;
+ if ( pParent )
+ {
+ css::uno::Reference< XAccessible > xAccParent = pParent->GetAccessible();
+ if ( xAccParent.is() )
+ {
+ // need to be done here to get the vclxwindow later on in the accessible
+ css::uno::Reference< css::awt::XVclWindowPeer > xHoldAlive(GetComponentInterface());
+ xAccessible = pImpl->m_aFactoryAccess.getFactory().createAccessibleTreeListBox( *this, xAccParent );
+ }
+ }
+ return xAccessible;
+}
+
+void SvTreeListBox::FillAccessibleEntryStateSet( SvTreeListEntry* pEntry, sal_Int64& rStateSet ) const
+{
+ assert(pEntry && "SvTreeListBox::FillAccessibleEntryStateSet: invalid entry");
+
+ if ( pEntry->HasChildrenOnDemand() || pEntry->HasChildren() )
+ {
+ rStateSet |= AccessibleStateType::EXPANDABLE;
+ if ( IsExpanded( pEntry ) )
+ rStateSet |= AccessibleStateType::EXPANDED;
+ }
+
+ if (nTreeFlags & SvTreeFlags::CHKBTN)
+ rStateSet |= AccessibleStateType::CHECKABLE;
+ if ( GetCheckButtonState( pEntry ) == SvButtonState::Checked )
+ rStateSet |= AccessibleStateType::CHECKED;
+ if ( IsEntryVisible( pEntry ) )
+ rStateSet |= AccessibleStateType::VISIBLE;
+ if ( IsSelected( pEntry ) )
+ rStateSet |= AccessibleStateType::SELECTED;
+ if ( IsEnabled() )
+ {
+ rStateSet |= AccessibleStateType::ENABLED;
+ rStateSet |= AccessibleStateType::FOCUSABLE;
+ rStateSet |= AccessibleStateType::SELECTABLE;
+ SvViewDataEntry* pViewDataNewCur = GetViewDataEntry(pEntry);
+ if (pViewDataNewCur && pViewDataNewCur->HasFocus())
+ rStateSet |= AccessibleStateType::FOCUSED;
+ }
+}
+
+OUString SvTreeListBox::GetEntryAccessibleDescription(SvTreeListEntry* pEntry) const
+{
+ assert(pEntry);
+
+ //want to count the real column number in the list box.
+ sal_uInt16 iRealItemCount = 0;
+ for (size_t i = 0; i < pEntry->ItemCount(); ++i)
+ {
+ const SvLBoxItem& rItem = pEntry->GetItem(i);
+ if (rItem.GetType() == SvLBoxItemType::String &&
+ !static_cast<const SvLBoxString&>(rItem).GetText().isEmpty())
+ {
+ iRealItemCount++;
+ }
+ }
+ // No idea why <= 1; that was in AccessibleListBoxEntry::getAccessibleDescription
+ // since the "Integrate branch of IAccessible2" commit
+ if (iRealItemCount <= 1)
+ {
+ return {};
+ }
+ else
+ {
+ return SearchEntryTextWithHeadTitle(pEntry);
+ }
+}
+
+tools::Rectangle SvTreeListBox::GetBoundingRect(const SvTreeListEntry* pEntry)
+{
+ Point aPos = GetEntryPosition( pEntry );
+ tools::Rectangle aRect = GetFocusRect( pEntry, aPos.Y() );
+ return aRect;
+}
+
+void SvTreeListBox::CallImplEventListeners(VclEventId nEvent, void* pData)
+{
+ CallEventListeners(nEvent, pData);
+}
+
+void SvTreeListBox::set_min_width_in_chars(sal_Int32 nChars)
+{
+ nMinWidthInChars = nChars;
+ queue_resize();
+}
+
+bool SvTreeListBox::set_property(const OUString &rKey, const OUString &rValue)
+{
+ if (rKey == "min-width-chars")
+ {
+ set_min_width_in_chars(rValue.toInt32());
+ }
+ else if (rKey == "enable-tree-lines")
+ {
+ auto nStyle = GetStyle();
+ nStyle &= ~(WB_HASLINES | WB_HASLINESATROOT);
+ if (toBool(rValue))
+ nStyle |= (WB_HASLINES | WB_HASLINESATROOT);
+ SetStyle(nStyle);
+ }
+ else if (rKey == "show-expanders")
+ {
+ auto nStyle = GetStyle();
+ nStyle &= ~(WB_HASBUTTONS | WB_HASBUTTONSATROOT);
+ if (toBool(rValue))
+ nStyle |= (WB_HASBUTTONS | WB_HASBUTTONSATROOT);
+ SetStyle(nStyle);
+ }
+ else if (rKey == "enable-search")
+ {
+ SetQuickSearch(toBool(rValue));
+ }
+ else if (rKey == "activate-on-single-click")
+ {
+ SetActivateOnSingleClick(toBool(rValue));
+ }
+ else if (rKey == "hover-selection")
+ {
+ SetHoverSelection(toBool(rValue));
+ }
+ else if (rKey == "reorderable")
+ {
+ if (toBool(rValue))
+ SetDragDropMode(DragDropMode::CTRL_MOVE | DragDropMode::ENABLE_TOP);
+ }
+ else
+ return Control::set_property(rKey, rValue);
+ return true;
+}
+
+void SvTreeListBox::EnableRTL(bool bEnable)
+{
+ Control::EnableRTL(bEnable);
+ pImpl->m_aHorSBar->EnableRTL(bEnable);
+ pImpl->m_aVerSBar->EnableRTL(bEnable);
+ pImpl->m_aScrBarBox->EnableRTL(bEnable);
+}
+
+FactoryFunction SvTreeListBox::GetUITestFactory() const
+{
+ return TreeListUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */