/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace css::accessibility; // Drag&Drop static VclPtr g_pDDSource; static VclPtr g_pDDTarget; #define SVLBOX_ACC_RETURN 1 #define SVLBOX_ACC_ESCAPE 2 class SvInplaceEdit2 { Link aCallBackHdl; Accelerator aAccReturn; Accelerator aAccEscape; Idle aIdle { "svtools::SvInplaceEdit2 aIdle" }; VclPtr 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& 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(); }; // *************************************************************** 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& rNotifyEditEnd, const Selection& rSelection ) : aCallBackHdl ( rNotifyEditEnd ), bCanceled ( false ), bAlreadyInCallBack ( false ) { pEdit = VclPtr::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(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(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), mbAlternatingRowColors(false), mbUpdateAlternatingRows(false), mbQuickSearch(false), mbActivateOnSingleClick(false), mbHoverSelection(false), mbSelectingByHover(false), mnClicksToToggle(0), //at default clicking on a row won't toggle its default checkbox eSelMode(SelectionMode::NONE), nMinWidthInChars(0), mnDragAction(DND_ACTION_COPYMOVE | DND_ACTION_LINK), mbCenterAndClipText(false) { nImpFlags = SvTreeListBoxFlags::NONE; pTargetEntry = nullptr; nDragDropMode = DragDropMode::NONE; pModel->SetCloneLink( LINK(this, SvTreeListBox, CloneHdl_Impl )); pHdlEntry = nullptr; eSelMode = SelectionMode::Single; nDragDropMode = DragDropMode::NONE; SetType(WindowType::TREELISTBOX); InitTreeView(); pImpl->SetModel( pModel.get() ); SetSublistOpenWithLeftRight(); } void SvTreeListBox::Clear() { if (pModel) pModel->Clear(); // Model calls SvTreeListBox::ModelHasCleared() } IMPL_LINK( SvTreeListBox, CloneHdl_Impl, SvTreeListEntry*, pEntry, SvTreeListEntry* ) { return CloneEntry(pEntry); } sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry, SvTreeListEntry* pParent, sal_uInt32 nPos ) { sal_uInt32 nInsPos = pModel->Insert( pEntry, pParent, nPos ); pEntry->SetBackColor( GetBackground().GetColor() ); SetAlternatingRowColors( mbAlternatingRowColors ); return nInsPos; } sal_uInt32 SvTreeListBox::Insert( SvTreeListEntry* pEntry,sal_uInt32 nRootPos ) { sal_uInt32 nInsPos = pModel->Insert( pEntry, nRootPos ); pEntry->SetBackColor( GetBackground().GetColor() ); SetAlternatingRowColors( mbAlternatingRowColors ); 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 aList; bool bClone = ( pSource->GetModel() != GetModel() ); Link 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 aList; bool bClone = ( pSource->GetModel() != GetModel() ); Link 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 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(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(SvListView::GetViewData(pEntry)); } SvViewDataItem* SvTreeListBox::GetViewDataItem(SvTreeListEntry const * pEntry, SvLBoxItem const * pItem) { return const_cast(static_cast(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 // ****************************************************************** 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 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& 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 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 SvTreeListBox::GetDragFinishedHdl() const { AddBoxToDDList_Impl( *this ); return LINK( const_cast(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( rItem ).GetText().isEmpty() ) { sRet.append(static_cast( 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 ); 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(pCheckButtonData)); } pEntry->AddItem(std::make_unique( aCollEntryBmp,aExpEntryBmp, mbContextBmpExpanded)); pEntry->AddItem(std::make_unique(aStr)); } OUString SvTreeListBox::GetEntryText(SvTreeListEntry* pEntry) const { assert(pEntry); SvLBoxString* pItem = static_cast(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(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); assert(pItem); return pItem->GetBitmap2( ); } const Image& SvTreeListBox::GetCollapsedEntryBmp( const SvTreeListEntry* pEntry ) { assert(pEntry); const SvLBoxContextBmp* pItem = static_cast(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(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(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); assert(pItem); pItem->SetBitmap2( aBmp ); ModelHasEntryInvalidated(pEntry); CalcEntryHeight( pEntry ); Size aSize = aBmp.GetSizePixel(); short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast(aSize.Width()) ); if( nWidth > nContextBmpWidthMax ) { nContextBmpWidthMax = nWidth; SetTabs(); } } void SvTreeListBox::SetCollapsedEntryBmp(SvTreeListEntry* pEntry,const Image& aBmp ) { SvLBoxContextBmp* pItem = static_cast(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); assert(pItem); pItem->SetBitmap1( aBmp ); ModelHasEntryInvalidated(pEntry); CalcEntryHeight( pEntry ); Size aSize = aBmp.GetSizePixel(); short nWidth = pImpl->UpdateContextBmpWidthVector( pEntry, static_cast(aSize.Width()) ); if( nWidth > nContextBmpWidthMax ) { nContextBmpWidthMax = nWidth; SetTabs(); } } void SvTreeListBox::CheckBoxInserted(SvTreeListEntry* pEntry) { SvLBoxButton* pItem = static_cast(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(aSize.Width()); nTreeFlags |= SvTreeFlags::RECALCTABS; } aSize = GetExpandedEntryBmp( pEntry ).GetSizePixel(); if( aSize.Width() > nContextBmpWidthMax ) { nContextBmpWidthMax = static_cast(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(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(pEntry->GetFirstItem(SvLBoxItemType::Button)); if(!pItem) return SvButtonState::Tristate; SvItemStateFlags nButtonFlags = pItem->GetButtonFlags(); eState = SvLBoxButtonData::ConvertToButtonState( nButtonFlags ); } return eState; } void SvTreeListBox::CheckButtonHdl() { if ( pCheckButtonData ) pImpl->CallEventListeners( VclEventId::CheckboxToggle, static_cast(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(pSource->GetFirstItem(SvLBoxItemType::String)); if( pStringItem ) aStr = pStringItem->GetText(); SvLBoxContextBmp* pBmpItem = static_cast(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(aSize.Width()); SetTabs(); pImpl->SetDefaultEntryExpBmp( aBmp ); } void SvTreeListBox::SetDefaultCollapsedEntryBmp( const Image& aBmp ) { Size aSize = aBmp.GetSizePixel(); if( aSize.Width() > nContextBmpWidthMax ) nContextBmpWidthMax = static_cast(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( "", 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(nMax - nTemp); pImpl->PageDown( static_cast(nDeltaEntries) ); } else { if( nDeltaEntries > nThumb ) nDeltaEntries = static_cast(nThumb); pImpl->PageUp( static_cast(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(aSize.Height()) + nEntryHeightOffs; pImpl->SetEntryHeight(); } } void SvTreeListBox::AdjustEntryHeight() { tools::Long nHeight = GetTextHeight(); if( nHeight > nEntryHeight ) { nEntryHeight = static_cast(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(); SetAlternatingRowColors( mbAlternatingRowColors ); } 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(); SetAlternatingRowColors( mbAlternatingRowColors ); } // #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(pEntry->GetFirstItem(SvLBoxItemType::Button)); if (pItemCheckBox && 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 ); mbUpdateAlternatingRows = bUpdate; SetAlternatingRowColors( mbAlternatingRowColors ); } 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& p) { return p.get() == pTab; }) - aTabs.begin(); if( nPos+1 < static_cast(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( &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 ) ) { static_cast(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()); } 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 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); 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); } else { aWallpaper.SetColor(rEntry.GetBackColor()); } } // 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(rRenderContext, rRect, rEntry)); } Size SvTreeListBox::MeasureCustomEntry(vcl::RenderContext& rRenderContext, const SvTreeListEntry& rEntry) const { return aCustomMeasureHdl.Call(std::pair(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(aSize.Width()); if( pTab ) nFocusWidth = nFocusWidth - static_cast(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(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(nIndent); nPos += static_cast(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 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 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 &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 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::SetAlternatingRowColors( bool bEnable ) { if( !mbUpdateAlternatingRows ) { mbAlternatingRowColors = bEnable; return; } if( bEnable ) { SvTreeListEntry* pEntry = pModel->First(); for(size_t i = 0; pEntry; ++i) { pEntry->SetBackColor( i % 2 == 0 ? GetBackground().GetColor() : GetSettings().GetStyleSettings().GetAlternatingRowColor()); SvTreeListEntry *pNextEntry = nullptr; if( IsExpanded( pEntry ) ) pNextEntry = pModel->FirstChild( pEntry ); else pNextEntry = pEntry->NextSibling(); if( !pNextEntry ) pEntry = pModel->Next( pEntry ); else pEntry = pNextEntry; } } else if( mbAlternatingRowColors ) for(SvTreeListEntry* pEntry = pModel->First(); pEntry; pEntry = pModel->Next(pEntry)) pEntry->SetBackColor( GetBackground().GetColor() ); mbAlternatingRowColors = bEnable; pImpl->UpdateAll(true); } 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::Invalidate( 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::Invalidate( nInvalidateFlags ); pImpl->Invalidate(); } void SvTreeListBox::Invalidate( const tools::Rectangle& rRect, InvalidateFlags nInvalidateFlags ) { if( nFocusWidth == -1 ) // to make sure that the control doesn't show the wrong focus rectangle // after painting pImpl->RecalcFocusRect(); Control::Invalidate( rRect, nInvalidateFlags ); } 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(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() && aTooltipHdl.Call(rHEvt)) 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(pLeft->GetFirstItem(SvLBoxItemType::String)); const SvLBoxString* pRightText = static_cast(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::XWindowPeer > xHoldAlive(GetComponentInterface()); xAccessible = pImpl->m_aFactoryAccess.getFactory().createAccessibleTreeListBox( *this, xAccParent ); } } return xAccessible; } void SvTreeListBox::FillAccessibleEntryStateSet( SvTreeListEntry* pEntry, ::utl::AccessibleStateSetHelper& rStateSet ) const { assert(pEntry && "SvTreeListBox::FillAccessibleEntryStateSet: invalid entry"); if ( pEntry->HasChildrenOnDemand() || pEntry->HasChildren() ) { rStateSet.AddState( AccessibleStateType::EXPANDABLE ); if ( IsExpanded( pEntry ) ) rStateSet.AddState( sal_Int16(AccessibleStateType::EXPANDED) ); } if ( GetCheckButtonState( pEntry ) == SvButtonState::Checked ) rStateSet.AddState( AccessibleStateType::CHECKED ); if ( IsEntryVisible( pEntry ) ) rStateSet.AddState( AccessibleStateType::VISIBLE ); if ( IsSelected( pEntry ) ) rStateSet.AddState( AccessibleStateType::SELECTED ); if ( IsEnabled() ) { rStateSet.AddState( AccessibleStateType::ENABLED ); rStateSet.AddState( AccessibleStateType::FOCUSABLE ); rStateSet.AddState( AccessibleStateType::SELECTABLE ); SvViewDataEntry* pViewDataNewCur = GetViewDataEntry(pEntry); if (pViewDataNewCur && pViewDataNewCur->HasFocus()) rStateSet.AddState( 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(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 OString &rKey, const OUString &rValue) { if (rKey == "min-width-chars") { set_min_width_in_chars(rValue.toInt32()); } else if (rKey == "enable-tree-lines") { auto nStyle = GetStyle(); nStyle &= ~(WB_HASLINES | WB_HASLINESATROOT); if (toBool(rValue)) nStyle |= (WB_HASLINES | WB_HASLINESATROOT); SetStyle(nStyle); } else if (rKey == "show-expanders") { auto nStyle = GetStyle(); nStyle &= ~(WB_HASBUTTONS | WB_HASBUTTONSATROOT); if (toBool(rValue)) nStyle |= (WB_HASBUTTONS | WB_HASBUTTONSATROOT); SetStyle(nStyle); } else if (rKey == "enable-search") { SetQuickSearch(toBool(rValue)); } else if (rKey == "activate-on-single-click") { SetActivateOnSingleClick(toBool(rValue)); } else if (rKey == "hover-selection") { SetHoverSelection(toBool(rValue)); } else if (rKey == "reorderable") { if (toBool(rValue)) SetDragDropMode(DragDropMode::CTRL_MOVE | DragDropMode::ENABLE_TOP); } else return Control::set_property(rKey, rValue); return true; } void SvTreeListBox::EnableRTL(bool bEnable) { Control::EnableRTL(bEnable); pImpl->m_aHorSBar->EnableRTL(bEnable); pImpl->m_aVerSBar->EnableRTL(bEnable); pImpl->m_aScrBarBox->EnableRTL(bEnable); } FactoryFunction SvTreeListBox::GetUITestFactory() const { return TreeListUIObject::create; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */