Quelle treelistbox.cxx
Sprache: C
/* -*- 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 <accessibility/accessiblelistbox.hxx>
#include <vcl/toolkit/treelistbox.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 >& r
NotifyEditEnd,
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; };
void RemoveEscapeAccel() { Application::RemoveAccel(&aAccEscape); }
void InsertEscapeAccel() { Application::InsertAccel(&aAccEscape); }
};
// ***************************************************************
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;
virtual void Command(const CommandEvent& rCEvt) 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();
}
void MyEdit_Impl::Command(const CommandEvent& rCEvt)
{
if (rCEvt.GetCommand() == CommandEventId::ContextMenu)
{
pOwner->RemoveEscapeAccel(); // so escape ends the popup
Edit::Command(rCEvt);
pOwner->InsertEscapeAccel();
}
else
Edit::Command(rCEvt);
}
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 && !pEdit->IsActivePopup()
&& ((!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 ),
mbIsTextColumEnabled(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 );
}
namespace {
/* 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
*/
enum class TreeListButtonType
{
NO_BUTTONS,
NODE_BUTTONS,
NODE_AND_CHECK_BUTTONS,
CHECK_BUTTONS,
};
}
#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()
{
// Moved to SvTreeListBox::Paint to make inplace editing work for X11 in the enhancement patch
// tdf#139663 Rename objects from tree view in navigator.
// 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();
TreeListButtonType eButtonType = TreeListButtonType::NO_BUTTONS;
if ( !(nTreeFlags & SvTreeFlags::CHKBTN) )
{
if ( bHasButtons )
eButtonType = TreeListButtonType::NODE_BUTTONS;
}
else
{
if ( bHasButtons )
eButtonType = TreeListButtonType::NODE_AND_CHECK_BUTTONS;
else
eButtonType = TreeListButtonType::CHECK_BUTTONS;
}
switch (eButtonType)
{
case TreeListButtonType::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 TreeListButtonType::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 TreeListButtonType::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 TreeListButtonType::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;
}
bool SvTreeListBox::GetCheckButtonEnabled(SvTreeListEntry* pEntry) const
{
if (pEntry && (nTreeFlags & SvTreeFlags::CHKBTN))
{
SvLBoxButton* pItem
= static_cast <SvLBoxButton*>(pEntry->GetFirstItem(SvLBoxItemType::Button));
if (pItem)
return pItem->isEnable();
}
return false ;
}
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& rData)
{
pCheckButtonData = &rData;
nTreeFlags |= SvTreeFlags::CHKBTN;
rData.SetLink( LINK(this , SvTreeListBox, CheckButtonClick));
SetTabs();
if ( IsUpdateMode() )
Invalidate();
}
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( u"" _ustr, 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)
{
if (IsEditingActive())
EndEditing(true );
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());
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=95 H=98 G=96
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland
2026-04-04