/* -*- 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),
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 );
}
/* 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;
}
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* 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( u"<dummy>"_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)
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 if (rKey == "text-column")
{
SetTextColumnEnabled(toBool(rValue));
}
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: */
↑ V530 The return value of function 'remove' is required to be utilized.
↑ V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.
↑ V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.
↑ V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.
↑ V1029 Numeric Truncation Error. Return value of the 'size' function is written to the 16-bit variable.