/* -*- 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #i27063# (pl), #i32300# (pb) never access VCL after DeInitVCL - also no destructors Image* SvImpLBox::s_pDefCollapsed = nullptr; Image* SvImpLBox::s_pDefExpanded = nullptr; oslInterlockedCount SvImpLBox::s_nImageRefCount = 0; SvImpLBox::SvImpLBox( SvTreeListBox* pLBView, SvTreeList* pLBTree, WinBits nWinStyle) : m_aScrBarBox(VclPtr::Create(pLBView)) , m_aFctSet(this, pLBView) , mbForceMakeVisible (false) , m_aEditIdle("SvImpLBox m_aEditIdle") , m_aHorSBar(VclPtr::Create(pLBView, WB_DRAG | WB_HSCROLL)) , m_aVerSBar(VclPtr::Create(pLBView, WB_DRAG | WB_VSCROLL)) , m_aOutputSize(0, 0) , mbNoAutoCurEntry(false) , m_aSelEng(pLBView, nullptr) , m_nNextVerVisSize(0) { osl_atomic_increment(&s_nImageRefCount); m_pView = pLBView; m_pTree = pLBTree; m_aSelEng.SetFunctionSet( static_cast(&m_aFctSet) ); m_aSelEng.ExpandSelectionOnMouseMove( false ); SetStyle( nWinStyle ); SetSelectionMode( SelectionMode::Single ); SetDragDropMode( DragDropMode::NONE ); m_aVerSBar->SetScrollHdl( LINK( this, SvImpLBox, ScrollUpDownHdl ) ); m_aHorSBar->SetScrollHdl( LINK( this, SvImpLBox, ScrollLeftRightHdl ) ); m_aHorSBar->SetEndScrollHdl( LINK( this, SvImpLBox, EndScrollHdl ) ); m_aVerSBar->SetEndScrollHdl( LINK( this, SvImpLBox, EndScrollHdl ) ); m_aVerSBar->SetRange( Range(0,0) ); m_aVerSBar->Hide(); m_aHorSBar->SetRange( Range(0,0) ); m_aHorSBar->SetPageSize( 24 ); // pixels m_aHorSBar->SetLineSize( 8 ); // pixels m_nHorSBarHeight = static_cast(m_aHorSBar->GetSizePixel().Height()); m_nVerSBarWidth = static_cast(m_aVerSBar->GetSizePixel().Width()); m_pStartEntry = nullptr; m_pCursor = nullptr; m_pCursorOld = nullptr; m_pAnchor = nullptr; m_nVisibleCount = 0; // number of rows of data in control m_nNodeBmpTabDistance = NODE_BMP_TABDIST_NOTVALID; m_nNodeBmpWidth = 0; // button animation in listbox m_pActiveButton = nullptr; m_pActiveEntry = nullptr; m_pActiveTab = nullptr; m_nFlags = LBoxFlags::NONE; m_aEditIdle.SetPriority( TaskPriority::LOWEST ); m_aEditIdle.SetInvokeHandler( LINK(this,SvImpLBox,EditTimerCall) ); m_nMostRight = -1; m_pMostRightEntry = nullptr; m_nCurUserEvent = nullptr; m_bUpdateMode = true; m_bInVScrollHdl = false; m_nFlags |= LBoxFlags::Filling; m_bSubLstOpLR = false; } SvImpLBox::~SvImpLBox() { m_aEditIdle.Stop(); StopUserEvent(); if ( osl_atomic_decrement(&s_nImageRefCount) == 0 ) { delete s_pDefCollapsed; s_pDefCollapsed = nullptr; delete s_pDefExpanded; s_pDefExpanded = nullptr; } m_aVerSBar.disposeAndClear(); m_aHorSBar.disposeAndClear(); m_aScrBarBox.disposeAndClear(); } void SvImpLBox::UpdateStringSorter() { const css::lang::Locale& rNewLocale = Application::GetSettings().GetLanguageTag().getLocale(); if( m_pStringSorter ) { // different Locale from the older one, drop it and force recreate const css::lang::Locale &aLocale = m_pStringSorter->getLocale(); if( aLocale.Language != rNewLocale.Language || aLocale.Country != rNewLocale.Country || aLocale.Variant != rNewLocale.Variant ) m_pStringSorter.reset(); } if( !m_pStringSorter ) { m_pStringSorter.reset(new comphelper::string::NaturalStringSorter( ::comphelper::getProcessComponentContext(), rNewLocale)); } } short SvImpLBox::UpdateContextBmpWidthVector( SvTreeListEntry const * pEntry, short nWidth ) { DBG_ASSERT( m_pView->pModel, "View and Model aren't valid!" ); sal_uInt16 nDepth = m_pView->pModel->GetDepth( pEntry ); // initialize vector if necessary std::vector< short >::size_type nSize = m_aContextBmpWidthVector.size(); while ( nDepth > nSize ) { m_aContextBmpWidthVector.resize( nSize + 1 ); m_aContextBmpWidthVector.at( nSize ) = nWidth; ++nSize; } if( m_aContextBmpWidthVector.size() == nDepth ) { m_aContextBmpWidthVector.resize( nDepth + 1 ); m_aContextBmpWidthVector.at( nDepth ) = 0; } short nContextBmpWidth = m_aContextBmpWidthVector[ nDepth ]; if( nContextBmpWidth < nWidth ) { m_aContextBmpWidthVector.at( nDepth ) = nWidth; return nWidth; } else return nContextBmpWidth; } void SvImpLBox::UpdateContextBmpWidthVectorFromMovedEntry( SvTreeListEntry* pEntry ) { DBG_ASSERT( pEntry, "Moved Entry is invalid!" ); SvLBoxContextBmp* pBmpItem = static_cast< SvLBoxContextBmp* >( pEntry->GetFirstItem(SvLBoxItemType::ContextBmp) ); short nExpWidth = static_cast(pBmpItem->GetBitmap1().GetSizePixel().Width()); short nColWidth = static_cast(pBmpItem->GetBitmap2().GetSizePixel().Width()); short nMax = std::max(nExpWidth, nColWidth); UpdateContextBmpWidthVector( pEntry, nMax ); if( pEntry->HasChildren() ) // recursive call, whether expanded or not { SvTreeListEntry* pChild = m_pView->FirstChild( pEntry ); DBG_ASSERT( pChild, "The first child is invalid!" ); do { UpdateContextBmpWidthVectorFromMovedEntry( pChild ); pChild = m_pView->Next( pChild ); } while ( pChild ); } } void SvImpLBox::UpdateContextBmpWidthMax( SvTreeListEntry const * pEntry ) { sal_uInt16 nDepth = m_pView->pModel->GetDepth( pEntry ); if( m_aContextBmpWidthVector.empty() ) return; short nWidth = m_aContextBmpWidthVector[ nDepth ]; if( nWidth != m_pView->nContextBmpWidthMax ) { m_pView->nContextBmpWidthMax = nWidth; m_nFlags |= LBoxFlags::IgnoreChangedTabs; m_pView->SetTabs(); m_nFlags &= ~LBoxFlags::IgnoreChangedTabs; } } void SvImpLBox::SetStyle( WinBits i_nWinStyle ) { m_nStyle = i_nWinStyle; if ( ( m_nStyle & WB_SIMPLEMODE) && ( m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) ) m_aSelEng.AddAlways( true ); } void SvImpLBox::SetNoAutoCurEntry( bool b ) { mbNoAutoCurEntry = b; } // don't touch the model any more void SvImpLBox::Clear() { StopUserEvent(); m_pStartEntry = nullptr; m_pAnchor = nullptr; m_pActiveButton = nullptr; m_pActiveEntry = nullptr; m_pActiveTab = nullptr; m_nMostRight = -1; m_pMostRightEntry = nullptr; // don't touch the cursor any more if( m_pCursor ) { if( m_pView->HasFocus() ) m_pView->HideFocus(); m_pCursor = nullptr; } m_pCursorOld = nullptr; m_aVerSBar->Hide(); m_aVerSBar->SetThumbPos( 0 ); Range aRange( 0, 0 ); m_aVerSBar->SetRange( aRange ); m_aOutputSize = m_pView->Control::GetOutputSizePixel(); m_aHorSBar->Hide(); m_aHorSBar->SetThumbPos( 0 ); MapMode aMapMode( m_pView->GetMapMode()); aMapMode.SetOrigin( Point(0,0) ); m_pView->Control::SetMapMode( aMapMode ); m_aHorSBar->SetRange( aRange ); m_aHorSBar->SetSizePixel(Size(m_aOutputSize.Width(),m_nHorSBarHeight)); m_pView->GetOutDev()->SetClipRegion(); if( GetUpdateMode() ) m_pView->Invalidate( GetVisibleArea() ); m_nFlags |= LBoxFlags::Filling; if( !m_aHorSBar->IsVisible() && !m_aVerSBar->IsVisible() ) m_aScrBarBox->Hide(); m_aContextBmpWidthVector.clear(); CallEventListeners( VclEventId::ListboxItemRemoved ); } // ********************************************************************* // Paint, navigate, scroll // ********************************************************************* IMPL_LINK_NOARG(SvImpLBox, EndScrollHdl, ScrollBar*, void) { if( m_nFlags & LBoxFlags::EndScrollSetVisSize ) { m_aVerSBar->SetVisibleSize( m_nNextVerVisSize ); m_nFlags &= ~LBoxFlags::EndScrollSetVisSize; } } // handler for vertical scrollbar IMPL_LINK( SvImpLBox, ScrollUpDownHdl, ScrollBar *, pScrollBar, void ) { DBG_ASSERT(!m_bInVScrollHdl,"Scroll handler out-paces itself!"); tools::Long nDelta = pScrollBar->GetDelta(); if( !nDelta ) return; // when only one row don't skip lines if (pScrollBar->GetPageSize() == 1) nDelta = nDelta > 0 ? 1 : -1; m_nFlags &= ~LBoxFlags::Filling; m_bInVScrollHdl = true; if( m_pView->IsEditingActive() ) { m_pView->EndEditing( true ); // Cancel m_pView->PaintImmediately(); } if( nDelta > 0 ) { if( nDelta == 1 && pScrollBar->GetPageSize() > 1) CursorDown(); else PageDown( static_cast(nDelta) ); } else { nDelta *= -1; if( nDelta == 1 && pScrollBar->GetPageSize() > 1) CursorUp(); else PageUp( static_cast(nDelta) ); } m_bInVScrollHdl = false; } void SvImpLBox::CursorDown() { if (!m_pStartEntry) return; SvTreeListEntry* pNextFirstToDraw = m_pView->NextVisible(m_pStartEntry); if( pNextFirstToDraw ) { m_nFlags &= ~LBoxFlags::Filling; ShowCursor( false ); m_pView->PaintImmediately(); m_pStartEntry = pNextFirstToDraw; tools::Rectangle aArea( GetVisibleArea() ); m_pView->Scroll( 0, -(m_pView->GetEntryHeight()), aArea, ScrollFlags::NoChildren ); m_pView->PaintImmediately(); ShowCursor( true ); m_pView->NotifyScrolled(); } } void SvImpLBox::CursorUp() { if (!m_pStartEntry) return; SvTreeListEntry* pPrevFirstToDraw = m_pView->PrevVisible(m_pStartEntry); if( !pPrevFirstToDraw ) return; m_nFlags &= ~LBoxFlags::Filling; tools::Long nEntryHeight = m_pView->GetEntryHeight(); ShowCursor( false ); m_pView->PaintImmediately(); m_pStartEntry = pPrevFirstToDraw; tools::Rectangle aArea( GetVisibleArea() ); if (aArea.GetHeight() > nEntryHeight) aArea.AdjustBottom(-nEntryHeight); m_pView->Scroll( 0, nEntryHeight, aArea, ScrollFlags::NoChildren ); m_pView->PaintImmediately(); ShowCursor( true ); m_pView->NotifyScrolled(); } void SvImpLBox::PageDown( sal_uInt16 nDelta ) { sal_uInt16 nRealDelta = nDelta; if( !nDelta ) return; if (!m_pStartEntry) return; SvTreeListEntry* pNext = m_pView->NextVisible(m_pStartEntry, nRealDelta); if( pNext == m_pStartEntry ) return; ShowCursor( false ); m_nFlags &= ~LBoxFlags::Filling; m_pStartEntry = pNext; if( nRealDelta >= m_nVisibleCount ) { m_pView->Invalidate( GetVisibleArea() ); m_pView->PaintImmediately(); } else { tools::Rectangle aArea( GetVisibleArea() ); tools::Long nScroll = m_pView->GetEntryHeight() * static_cast(nRealDelta); nScroll = -nScroll; m_pView->PaintImmediately(); m_pView->Scroll( 0, nScroll, aArea, ScrollFlags::NoChildren ); m_pView->PaintImmediately(); } ShowCursor( true ); m_pView->NotifyScrolled(); } void SvImpLBox::PageUp( sal_uInt16 nDelta ) { sal_uInt16 nRealDelta = nDelta; if( !nDelta ) return; if (!m_pStartEntry) return; SvTreeListEntry* pPrev = m_pView->PrevVisible(m_pStartEntry, nRealDelta); if( pPrev == m_pStartEntry ) return; m_nFlags &= ~LBoxFlags::Filling; ShowCursor( false ); m_pStartEntry = pPrev; if( nRealDelta >= m_nVisibleCount ) { m_pView->Invalidate( GetVisibleArea() ); m_pView->PaintImmediately(); } else { tools::Long nEntryHeight = m_pView->GetEntryHeight(); tools::Rectangle aArea( GetVisibleArea() ); m_pView->PaintImmediately(); m_pView->Scroll( 0, nEntryHeight*nRealDelta, aArea, ScrollFlags::NoChildren ); m_pView->PaintImmediately(); } ShowCursor( true ); m_pView->NotifyScrolled(); } void SvImpLBox::KeyUp( bool bPageUp ) { if( !m_aVerSBar->IsVisible() ) return; tools::Long nDelta; if( bPageUp ) nDelta = m_aVerSBar->GetPageSize(); else nDelta = 1; tools::Long nThumbPos = m_aVerSBar->GetThumbPos(); if( nThumbPos < nDelta ) nDelta = nThumbPos; if( nDelta <= 0 ) return; m_nFlags &= ~LBoxFlags::Filling; m_aVerSBar->SetThumbPos( nThumbPos - nDelta ); if( bPageUp ) PageUp( static_cast(nDelta) ); else CursorUp(); } void SvImpLBox::KeyDown( bool bPageDown ) { if( !m_aVerSBar->IsVisible() ) return; tools::Long nDelta; if( bPageDown ) nDelta = m_aVerSBar->GetPageSize(); else nDelta = 1; tools::Long nThumbPos = m_aVerSBar->GetThumbPos(); tools::Long nVisibleSize = m_aVerSBar->GetVisibleSize(); tools::Long nRange = m_aVerSBar->GetRange().Len(); tools::Long nTmp = nThumbPos+nVisibleSize; while( (nDelta > 0) && (nTmp+nDelta) >= nRange ) nDelta--; if( nDelta <= 0 ) return; m_nFlags &= ~LBoxFlags::Filling; m_aVerSBar->SetThumbPos( nThumbPos+nDelta ); if( bPageDown ) PageDown( static_cast(nDelta) ); else CursorDown(); } void SvImpLBox::InvalidateEntriesFrom( tools::Long nY ) const { if( !(m_nFlags & LBoxFlags::InPaint )) { tools::Rectangle aRect( GetVisibleArea() ); aRect.SetTop( nY ); m_pView->Invalidate( aRect ); } } void SvImpLBox::InvalidateEntry( tools::Long nY ) const { if( m_nFlags & LBoxFlags::InPaint ) return; tools::Rectangle aRect( GetVisibleArea() ); tools::Long nMaxBottom = aRect.Bottom(); aRect.SetTop( nY ); aRect.SetBottom( nY ); aRect.AdjustBottom(m_pView->GetEntryHeight() ); if( aRect.Top() > nMaxBottom ) return; if( aRect.Bottom() > nMaxBottom ) aRect.SetBottom( nMaxBottom ); if (m_pView->SupportsDoubleBuffering()) // Perform full paint when flicker is to be avoided explicitly. m_pView->Invalidate(); else m_pView->Invalidate(aRect); } void SvImpLBox::InvalidateEntry( SvTreeListEntry* pEntry ) { if( GetUpdateMode() ) { tools::Long nPrev = m_nMostRight; SetMostRight( pEntry ); if( nPrev < m_nMostRight ) ShowVerSBar(); } if( !(m_nFlags & LBoxFlags::InPaint )) { bool bHasFocusRect = false; if( pEntry==m_pCursor && m_pView->HasFocus() ) { bHasFocusRect = true; ShowCursor( false ); } InvalidateEntry( GetEntryLine( pEntry ) ); if( bHasFocusRect ) ShowCursor( true ); } } void SvImpLBox::RecalcFocusRect() { if( m_pView->HasFocus() && m_pCursor ) { m_pView->HideFocus(); tools::Long nY = GetEntryLine( m_pCursor ); tools::Rectangle aRect = m_pView->GetFocusRect( m_pCursor, nY ); vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion()); vcl::Region aClipRegion( GetClipRegionRect() ); m_pView->GetOutDev()->SetClipRegion( aClipRegion ); m_pView->ShowFocus( aRect ); m_pView->GetOutDev()->SetClipRegion( aOldClip ); } } // Sets cursor. When using SingleSelection, the selection is adjusted. void SvImpLBox::SetCursor( SvTreeListEntry* pEntry, bool bForceNoSelect ) { SvViewDataEntry* pViewDataNewCur = nullptr; if( pEntry ) pViewDataNewCur= m_pView->GetViewDataEntry(pEntry); if( pEntry && pEntry == m_pCursor && pViewDataNewCur && pViewDataNewCur->HasFocus() && pViewDataNewCur->IsSelected()) { return; } // if this cursor is not selectable, find first visible that is and use it while( pEntry && pViewDataNewCur && !pViewDataNewCur->IsSelectable() ) { pEntry = m_pView->NextVisible(pEntry); pViewDataNewCur = pEntry ? m_pView->GetViewDataEntry(pEntry) : nullptr; } SvTreeListEntry* pOldCursor = m_pCursor; if( m_pCursor && pEntry != m_pCursor ) { m_pView->SetEntryFocus( m_pCursor, false ); if( m_bSimpleTravel ) m_pView->Select( m_pCursor, false ); m_pView->HideFocus(); } m_pCursor = pEntry; if( m_pCursor ) { if (pViewDataNewCur) pViewDataNewCur->SetFocus( true ); if(!bForceNoSelect && m_bSimpleTravel && !(m_nFlags & LBoxFlags::DeselectAll) && GetUpdateMode()) { m_pView->Select( m_pCursor ); CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor ); } // multiple selection: select in cursor move if we're not in // Add mode (Ctrl-F8) else if( GetUpdateMode() && m_pView->GetSelectionMode() == SelectionMode::Multiple && !(m_nFlags & LBoxFlags::DeselectAll) && !m_aSelEng.IsAddMode() && !bForceNoSelect ) { m_pView->Select( m_pCursor ); CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor ); } else { ShowCursor( true ); if (bForceNoSelect && GetUpdateMode()) { CallEventListeners( VclEventId::ListboxTreeFocus, m_pCursor); } } if( m_pAnchor ) { DBG_ASSERT(m_aSelEng.GetSelectionMode() != SelectionMode::Single,"Mode?"); SetAnchorSelection( pOldCursor, m_pCursor ); } } m_nFlags &= ~LBoxFlags::DeselectAll; m_pView->OnCurrentEntryChanged(); } void SvImpLBox::ShowCursor( bool bShow ) { if( !bShow || !m_pCursor || !m_pView->HasFocus() ) { vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion()); vcl::Region aClipRegion( GetClipRegionRect() ); m_pView->GetOutDev()->SetClipRegion( aClipRegion ); m_pView->HideFocus(); m_pView->GetOutDev()->SetClipRegion( aOldClip ); } else { tools::Long nY = GetEntryLine( m_pCursor ); tools::Rectangle aRect = m_pView->GetFocusRect( m_pCursor, nY ); vcl::Region aOldClip( m_pView->GetOutDev()->GetClipRegion()); vcl::Region aClipRegion( GetClipRegionRect() ); m_pView->GetOutDev()->SetClipRegion( aClipRegion ); m_pView->ShowFocus( aRect ); m_pView->GetOutDev()->SetClipRegion( aOldClip ); } } void SvImpLBox::UpdateAll( bool bInvalidateCompleteView ) { FindMostRight(); m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) ); SyncVerThumb(); FillView(); ShowVerSBar(); if( m_bSimpleTravel && m_pCursor && m_pView->HasFocus() ) m_pView->Select( m_pCursor ); ShowCursor( true ); if( bInvalidateCompleteView ) m_pView->Invalidate(); else m_pView->Invalidate( GetVisibleArea() ); } IMPL_LINK( SvImpLBox, ScrollLeftRightHdl, ScrollBar *, pScrollBar, void ) { tools::Long nDelta = pScrollBar->GetDelta(); if( nDelta ) { if( m_pView->IsEditingActive() ) { m_pView->EndEditing( true ); // Cancel m_pView->PaintImmediately(); } m_pView->nFocusWidth = -1; KeyLeftRight( nDelta ); } } void SvImpLBox::KeyLeftRight( tools::Long nDelta ) { if( !(m_nFlags & LBoxFlags::InResize) ) m_pView->PaintImmediately(); m_nFlags &= ~LBoxFlags::Filling; ShowCursor( false ); // calculate new origin tools::Long nPos = m_aHorSBar->GetThumbPos(); Point aOrigin( -nPos, 0 ); MapMode aMapMode( m_pView->GetMapMode() ); aMapMode.SetOrigin( aOrigin ); m_pView->SetMapMode( aMapMode ); if( !(m_nFlags & LBoxFlags::InResize) ) { tools::Rectangle aRect( GetVisibleArea() ); m_pView->Scroll( -nDelta, 0, aRect, ScrollFlags::NoChildren ); } else m_pView->Invalidate(); RecalcFocusRect(); ShowCursor( true ); m_pView->NotifyScrolled(); } // returns the last entry if position is just past the last entry SvTreeListEntry* SvImpLBox::GetClickedEntry( const Point& rPoint ) const { DBG_ASSERT( m_pView->GetModel(), "SvImpLBox::GetClickedEntry: how can this ever happen? Please tell me (frank.schoenheit@sun.com) how to reproduce!" ); if ( !m_pView->GetModel() ) // this is quite impossible. Nevertheless, stack traces from the crash reporter // suggest it isn't. Okay, make it safe, and wait for somebody to reproduce it // reliably :-\ ... // #122359# / 2005-05-23 / frank.schoenheit@sun.com return nullptr; if( m_pView->GetEntryCount() == 0 || !m_pStartEntry || !m_pView->GetEntryHeight()) return nullptr; sal_uInt16 nClickedEntry = static_cast(rPoint.Y() / m_pView->GetEntryHeight() ); sal_uInt16 nTemp = nClickedEntry; SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp); return pEntry; } // checks if the entry was hit "the right way" // (Focusrect+ ContextBitmap at TreeListBox) bool SvImpLBox::EntryReallyHit(SvTreeListEntry* pEntry, const Point& rPosPixel, tools::Long nLine) { bool bRet; // we are not too exact when it comes to "special" entries // (with CheckButtons etc.) if( pEntry->ItemCount() >= 3 ) return true; tools::Rectangle aRect( m_pView->GetFocusRect( pEntry, nLine )); aRect.SetRight( GetOutputSize().Width() - m_pView->GetMapMode().GetOrigin().X() ); SvLBoxContextBmp* pBmp = static_cast(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); aRect.AdjustLeft( -pBmp->GetWidth(m_pView,pEntry) ); aRect.AdjustLeft( -4 ); // a little tolerance Point aPos( rPosPixel ); aPos -= m_pView->GetMapMode().GetOrigin(); bRet = aRect.Contains( aPos ); return bRet; } // returns 0 if position is just past the last entry SvTreeListEntry* SvImpLBox::GetEntry( const Point& rPoint ) const { if( (m_pView->GetEntryCount() == 0) || !m_pStartEntry || (rPoint.Y() > m_aOutputSize.Height()) || !m_pView->GetEntryHeight()) return nullptr; sal_uInt16 nClickedEntry = static_cast(rPoint.Y() / m_pView->GetEntryHeight() ); sal_uInt16 nTemp = nClickedEntry; SvTreeListEntry* pEntry = m_pView->NextVisible(m_pStartEntry, nTemp); if( nTemp != nClickedEntry ) pEntry = nullptr; return pEntry; } SvTreeListEntry* SvImpLBox::MakePointVisible(const Point& rPoint) { if( !m_pCursor ) return nullptr; tools::Long nY = rPoint.Y(); SvTreeListEntry* pEntry = nullptr; tools::Long nMax = m_aOutputSize.Height(); if( nY < 0 || nY >= nMax ) // aOutputSize.Height() ) { if( nY < 0 ) pEntry = m_pView->PrevVisible(m_pCursor); else pEntry = m_pView->NextVisible(m_pCursor); if( pEntry && pEntry != m_pCursor ) m_pView->SetEntryFocus( m_pCursor, false ); if( nY < 0 ) KeyUp( false ); else KeyDown( false ); } else { pEntry = GetClickedEntry( rPoint ); if( !pEntry ) { sal_uInt16 nSteps = 0xFFFF; // TODO: LastVisible is not yet implemented! pEntry = m_pView->NextVisible(m_pStartEntry, nSteps); } if( pEntry ) { if( pEntry != m_pCursor && m_aSelEng.GetSelectionMode() == SelectionMode::Single ) m_pView->Select( m_pCursor, false ); } } return pEntry; } tools::Rectangle SvImpLBox::GetClipRegionRect() const { Point aOrigin( m_pView->GetMapMode().GetOrigin() ); aOrigin.setX( aOrigin.X() * -1 ); // conversion document coordinates tools::Rectangle aClipRect( aOrigin, m_aOutputSize ); aClipRect.AdjustBottom( 1 ); return aClipRect; } void SvImpLBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) { if (!m_pView->GetVisibleCount()) return; m_nFlags |= LBoxFlags::InPaint; if (m_nFlags & LBoxFlags::Filling) { SvTreeListEntry* pFirst = m_pView->First(); if (pFirst != m_pStartEntry) { ShowCursor(false); m_pStartEntry = m_pView->First(); m_aVerSBar->SetThumbPos( 0 ); StopUserEvent(); ShowCursor(true); m_nCurUserEvent = Application::PostUserEvent(LINK(this, SvImpLBox, MyUserEvent), reinterpret_cast(1)); return; } } if (!m_pStartEntry) { m_pStartEntry = m_pView->First(); } if (m_nNodeBmpTabDistance == NODE_BMP_TABDIST_NOTVALID) SetNodeBmpTabDistance(); tools::Long nRectHeight = rRect.GetHeight(); tools::Long nEntryHeight = m_pView->GetEntryHeight(); // calculate area for the entries we want to draw sal_uInt16 nStartLine = static_cast(rRect.Top() / nEntryHeight); sal_uInt16 nCount = static_cast(nRectHeight / nEntryHeight); nCount += 2; // don't miss a row tools::Long nY = nStartLine * nEntryHeight; SvTreeListEntry* pEntry = m_pStartEntry; while (nStartLine && pEntry) { pEntry = m_pView->NextVisible(pEntry); nStartLine--; } if (!m_pCursor && !mbNoAutoCurEntry) { // do not select if multiselection or explicit set bool bNotSelect = (m_aSelEng.GetSelectionMode() == SelectionMode::Multiple ) || ((m_nStyle & WB_NOINITIALSELECTION) == WB_NOINITIALSELECTION); SetCursor(m_pStartEntry, bNotSelect); } for(sal_uInt16 n=0; n< nCount && pEntry; n++) { /*long nMaxRight=*/ m_pView->PaintEntry1(*pEntry, nY, rRenderContext ); nY += nEntryHeight; pEntry = m_pView->NextVisible(pEntry); } if (m_nStyle & (WB_HASLINES | WB_HASLINESATROOT)) DrawNet(rRenderContext); m_nFlags &= ~LBoxFlags::DeselectAll; m_nFlags &= ~LBoxFlags::InPaint; } void SvImpLBox::MakeVisible( SvTreeListEntry* pEntry, bool bMoveToTop ) { if( !pEntry ) return; bool bInView = IsEntryInView( pEntry ); if( bInView && (!bMoveToTop || m_pStartEntry == pEntry) ) return; // is already visible if( m_pStartEntry || mbForceMakeVisible ) m_nFlags &= ~LBoxFlags::Filling; if( !bInView ) { if( !m_pView->IsEntryVisible(pEntry) ) // Parent(s) collapsed? { SvTreeListEntry* pParent = m_pView->GetParent( pEntry ); while( pParent ) { if( !m_pView->IsExpanded( pParent ) ) { bool bRet = m_pView->Expand( pParent ); DBG_ASSERT(bRet,"Not expanded!"); } pParent = m_pView->GetParent( pParent ); } // do the parent's children fit into the view or do we have to scroll? if( IsEntryInView( pEntry ) && !bMoveToTop ) return; // no need to scroll } } m_pStartEntry = pEntry; ShowCursor( false ); FillView(); m_aVerSBar->SetThumbPos( static_cast(m_pView->GetVisiblePos( m_pStartEntry )) ); ShowCursor( true ); m_pView->NotifyScrolled(); m_pView->Invalidate(); } void SvImpLBox::ScrollToAbsPos( tools::Long nPos ) { if( m_pView->GetVisibleCount() == 0 ) return; tools::Long nLastEntryPos = m_pView->GetAbsPos( m_pView->Last() ); if( nPos < 0 ) nPos = 0; else if( nPos > nLastEntryPos ) nPos = nLastEntryPos; SvTreeListEntry* pEntry = m_pView->GetEntryAtAbsPos( nPos ); if( !pEntry || pEntry == m_pStartEntry ) return; if( m_pStartEntry || mbForceMakeVisible ) m_nFlags &= ~LBoxFlags::Filling; if( m_pView->IsEntryVisible(pEntry) ) { m_pStartEntry = pEntry; ShowCursor( false ); m_aVerSBar->SetThumbPos( nPos ); ShowCursor( true ); if (GetUpdateMode()) m_pView->Invalidate(); } } void SvImpLBox::DrawNet(vcl::RenderContext& rRenderContext) { if (m_pView->GetVisibleCount() < 2 && !m_pStartEntry->HasChildrenOnDemand() && !m_pStartEntry->HasChildren()) { return; } // for platforms that don't have nets, DrawNativeControl does nothing and returns true // so that SvImpLBox::DrawNet() doesn't draw anything either if (rRenderContext.IsNativeControlSupported(ControlType::ListNet, ControlPart::Entire)) { ImplControlValue aControlValue; if (rRenderContext.DrawNativeControl(ControlType::ListNet, ControlPart::Entire, tools::Rectangle(), ControlState::ENABLED, aControlValue, OUString())) { return; } } tools::Long nEntryHeight = m_pView->GetEntryHeight(); tools::Long nEntryHeightDIV2 = nEntryHeight / 2; if( nEntryHeightDIV2 && !(nEntryHeight & 0x0001)) nEntryHeightDIV2--; SvTreeListEntry* pChild; SvTreeListEntry* pEntry = m_pStartEntry; SvLBoxTab* pFirstDynamicTab = m_pView->GetFirstDynamicTab(); while (m_pTree->GetDepth( pEntry ) > 0) { pEntry = m_pView->GetParent(pEntry); } sal_uInt16 nOffs = static_cast(m_pView->GetVisiblePos(m_pStartEntry) - m_pView->GetVisiblePos(pEntry)); tools::Long nY = 0; nY -= (nOffs * nEntryHeight); DBG_ASSERT(pFirstDynamicTab,"No Tree!"); rRenderContext.Push(vcl::PushFlags::LINECOLOR); const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); Color aCol = rStyleSettings.GetFaceColor(); if (aCol.IsRGBEqual(rRenderContext.GetBackground().GetColor())) aCol = rStyleSettings.GetShadowColor(); rRenderContext.SetLineColor(aCol); Point aPos1, aPos2; sal_uInt16 nDistance; sal_uLong nMax = m_nVisibleCount + nOffs + 1; const Image& rExpandedNodeBitmap = GetExpandedNodeBmp(); for (sal_uLong n=0; n< nMax && pEntry; n++) { if (m_pView->IsExpanded(pEntry)) { // draw vertical line aPos1.setX(m_pView->GetTabPos(pEntry, pFirstDynamicTab) + m_nNodeBmpTabDistance + rExpandedNodeBitmap.GetSizePixel().Width() / 2); aPos1.setY(nY + nEntryHeight); pChild = m_pView->FirstChild(pEntry); assert(pChild && "Child?"); pChild = pChild->LastSibling(); nDistance = static_cast(m_pView->GetVisiblePos(pChild) - m_pView->GetVisiblePos(pEntry)); aPos2 = aPos1; aPos2.AdjustY((nDistance * nEntryHeight) - (nEntryHeightDIV2 + 2)); rRenderContext.DrawLine(aPos1, aPos2); } // visible in control? if (n >= nOffs && !m_pTree->IsAtRootDepth(pEntry)) { // draw horizontal line aPos1.setX(m_pView->GetTabPos(m_pView->GetParent(pEntry), pFirstDynamicTab) + m_nNodeBmpTabDistance + rExpandedNodeBitmap.GetSizePixel().Width() / 2); aPos1.setY(nY + nEntryHeightDIV2); aPos2 = aPos1; aPos2.AdjustX(m_pView->GetIndent() / 2); rRenderContext.DrawLine(aPos1, aPos2); } nY += nEntryHeight; pEntry = m_pView->NextVisible(pEntry); } rRenderContext.Pop(); } void SvImpLBox::PositionScrollBars( Size& rSize, sal_uInt16 nMask ) { tools::Long nOverlap = 0; Size aVerSize( m_nVerSBarWidth, rSize.Height() ); Size aHorSize( rSize.Width(), m_nHorSBarHeight ); if( nMask & 0x0001 ) aHorSize.AdjustWidth( -m_nVerSBarWidth ); if( nMask & 0x0002 ) aVerSize.AdjustHeight( -m_nHorSBarHeight ); aVerSize.AdjustHeight(2 * nOverlap ); Point aVerPos( rSize.Width() - aVerSize.Width() + nOverlap, -nOverlap ); m_aVerSBar->SetPosSizePixel( aVerPos, aVerSize ); aHorSize.AdjustWidth(2 * nOverlap ); Point aHorPos( -nOverlap, rSize.Height() - aHorSize.Height() + nOverlap ); m_aHorSBar->SetPosSizePixel( aHorPos, aHorSize ); if( nMask & 0x0001 ) rSize.setWidth( aVerPos.X() ); if( nMask & 0x0002 ) rSize.setHeight( aHorPos.Y() ); if( (nMask & (0x0001|0x0002)) == (0x0001|0x0002) ) m_aScrBarBox->Show(); else m_aScrBarBox->Hide(); } void SvImpLBox::AdjustScrollBars( Size& rSize ) { tools::Long nEntryHeight = m_pView->GetEntryHeight(); if( !nEntryHeight ) return; sal_uInt16 nResult = 0; Size aOSize( m_pView->Control::GetOutputSizePixel() ); const WinBits nWindowStyle = m_pView->GetStyle(); bool bVerSBar = ( nWindowStyle & WB_VSCROLL ) != 0; bool bHorBar = false; tools::Long nMaxRight = aOSize.Width(); //GetOutputSize().Width(); Point aOrigin( m_pView->GetMapMode().GetOrigin() ); aOrigin.setX( aOrigin.X() * -1 ); nMaxRight += aOrigin.X() - 1; tools::Long nVis = m_nMostRight - aOrigin.X(); if( (nWindowStyle & (WB_AUTOHSCROLL|WB_HSCROLL)) && (nVis < m_nMostRight || nMaxRight < m_nMostRight) ) { bHorBar = true; } // number of entries that are not collapsed sal_uLong nTotalCount = m_pView->GetVisibleCount(); // number of entries visible within the view m_nVisibleCount = o3tl::make_unsigned(aOSize.Height() / nEntryHeight); // do we need a vertical scrollbar? if( bVerSBar || nTotalCount > m_nVisibleCount ) { nResult = 1; nMaxRight -= m_nVerSBarWidth; if( !bHorBar ) { if( (nWindowStyle & (WB_AUTOHSCROLL|WB_HSCROLL)) && (nVis < m_nMostRight || nMaxRight < m_nMostRight) ) bHorBar = true; } } // do we need a horizontal scrollbar? if( bHorBar ) { nResult |= 0x0002; // the number of entries visible within the view has to be recalculated // because the horizontal scrollbar is now visible. m_nVisibleCount = o3tl::make_unsigned(std::max(0, aOSize.Height() - m_nHorSBarHeight) / nEntryHeight); // we might actually need a vertical scrollbar now if( !(nResult & 0x0001) && ((nTotalCount > m_nVisibleCount) || bVerSBar) ) { nResult = 3; } } PositionScrollBars( aOSize, nResult ); // adapt Range, VisibleRange etc. // refresh output size, in case we have to scroll tools::Rectangle aRect; aRect.SetSize( aOSize ); m_aSelEng.SetVisibleArea( aRect ); // vertical scrollbar tools::Long nTemp = static_cast(m_nVisibleCount); nTemp--; if( nTemp != m_aVerSBar->GetVisibleSize() ) { if( !m_bInVScrollHdl ) { m_aVerSBar->SetPageSize( nTemp - 1 ); m_aVerSBar->SetVisibleSize( nTemp ); } else { m_nFlags |= LBoxFlags::EndScrollSetVisSize; m_nNextVerVisSize = nTemp; } } // horizontal scrollbar nTemp = m_aHorSBar->GetThumbPos(); m_aHorSBar->SetVisibleSize( aOSize.Width() ); tools::Long nNewThumbPos = m_aHorSBar->GetThumbPos(); Range aRange( m_aHorSBar->GetRange() ); if( aRange.Max() < m_nMostRight+25 ) { aRange.Max() = m_nMostRight+25; m_aHorSBar->SetRange( aRange ); } if( nTemp != nNewThumbPos ) { nTemp = nNewThumbPos - nTemp; if( m_pView->IsEditingActive() ) { m_pView->EndEditing( true ); // Cancel m_pView->PaintImmediately(); } m_pView->nFocusWidth = -1; KeyLeftRight( nTemp ); } if( nResult & 0x0001 ) m_aVerSBar->Show(); else m_aVerSBar->Hide(); if( nResult & 0x0002 ) m_aHorSBar->Show(); else { m_aHorSBar->Hide(); } rSize = aOSize; } void SvImpLBox::InitScrollBarBox() { m_aScrBarBox->SetSizePixel( Size(m_nVerSBarWidth, m_nHorSBarHeight) ); Size aSize( m_pView->Control::GetOutputSizePixel() ); m_aScrBarBox->SetPosPixel( Point(aSize.Width()-m_nVerSBarWidth, aSize.Height()-m_nHorSBarHeight)); } void SvImpLBox::Resize() { m_aOutputSize = m_pView->Control::GetOutputSizePixel(); if( m_aOutputSize.IsEmpty() ) return; m_nFlags |= LBoxFlags::InResize; InitScrollBarBox(); if( m_pView->GetEntryHeight()) { AdjustScrollBars( m_aOutputSize ); UpdateAll(false); } // HACK, as in floating and docked windows the scrollbars might not be drawn // correctly/not be drawn at all after resizing! if( m_aHorSBar->IsVisible()) m_aHorSBar->Invalidate(); if( m_aVerSBar->IsVisible()) m_aVerSBar->Invalidate(); m_nFlags &= ~LBoxFlags::InResize; } void SvImpLBox::FillView() { if( !m_pStartEntry ) { sal_uLong nVisibleViewCount = m_pView->GetVisibleCount(); tools::Long nTempThumb = m_aVerSBar->GetThumbPos(); if( nTempThumb < 0 ) nTempThumb = 0; else if( o3tl::make_unsigned(nTempThumb) >= nVisibleViewCount ) nTempThumb = nVisibleViewCount == 0 ? 0 : nVisibleViewCount - 1; m_pStartEntry = m_pView->GetEntryAtVisPos(nTempThumb); } if( !m_pStartEntry ) return; sal_uInt16 nLast = static_cast(m_pView->GetVisiblePos(m_pView->LastVisible())); sal_uInt16 nThumb = static_cast(m_pView->GetVisiblePos( m_pStartEntry )); sal_uLong nCurDispEntries = nLast-nThumb+1; if( nCurDispEntries >= m_nVisibleCount ) return; ShowCursor( false ); // fill window by moving the thumb up incrementally bool bFound = false; SvTreeListEntry* pTemp = m_pStartEntry; while( nCurDispEntries < m_nVisibleCount && pTemp ) { pTemp = m_pView->PrevVisible(m_pStartEntry); if( pTemp ) { nThumb--; m_pStartEntry = pTemp; nCurDispEntries++; bFound = true; } } if( bFound ) { m_aVerSBar->SetThumbPos( nThumb ); ShowCursor( true ); // recalculate focus rectangle m_pView->Invalidate(); } } void SvImpLBox::ShowVerSBar() { bool bVerBar = ( m_pView->GetStyle() & WB_VSCROLL ) != 0; sal_uLong nVis = 0; if( !bVerBar ) nVis = m_pView->GetVisibleCount(); if( bVerBar || (m_nVisibleCount && nVis > static_cast(m_nVisibleCount-1)) ) { if( !m_aVerSBar->IsVisible() ) { m_pView->nFocusWidth = -1; AdjustScrollBars( m_aOutputSize ); if( GetUpdateMode() ) m_aVerSBar->Invalidate(); } } else { if( m_aVerSBar->IsVisible() ) { m_pView->nFocusWidth = -1; AdjustScrollBars( m_aOutputSize ); } } tools::Long nMaxRight = GetOutputSize().Width(); Point aPos( m_pView->GetMapMode().GetOrigin() ); aPos.setX( aPos.X() * -1 ); // convert document coordinates nMaxRight = nMaxRight + aPos.X() - 1; if( nMaxRight < m_nMostRight ) { if( !m_aHorSBar->IsVisible() ) { m_pView->nFocusWidth = -1; AdjustScrollBars( m_aOutputSize ); if( GetUpdateMode() ) m_aHorSBar->Invalidate(); } else { Range aRange( m_aHorSBar->GetRange() ); if( aRange.Max() < m_nMostRight+25 ) { aRange.Max() = m_nMostRight+25; m_aHorSBar->SetRange( aRange ); } else { m_pView->nFocusWidth = -1; AdjustScrollBars( m_aOutputSize ); } } } else { if( m_aHorSBar->IsVisible() ) { m_pView->nFocusWidth = -1; AdjustScrollBars( m_aOutputSize ); } } } void SvImpLBox::SyncVerThumb() { if( m_pStartEntry ) { tools::Long nEntryPos = m_pView->GetVisiblePos( m_pStartEntry ); m_aVerSBar->SetThumbPos( nEntryPos ); } else m_aVerSBar->SetThumbPos( 0 ); } bool SvImpLBox::IsEntryInView( SvTreeListEntry* pEntry ) const { // parent collapsed if( !m_pView->IsEntryVisible(pEntry) ) return false; tools::Long nY = GetEntryLine( pEntry ); if( nY < 0 ) return false; tools::Long nMax = m_nVisibleCount * m_pView->GetEntryHeight(); return nY < nMax; } tools::Long SvImpLBox::GetEntryLine(const SvTreeListEntry* pEntry) const { if(!m_pStartEntry ) return -1; // invisible position tools::Long nFirstVisPos = m_pView->GetVisiblePos( m_pStartEntry ); tools::Long nEntryVisPos = m_pView->GetVisiblePos( pEntry ); nFirstVisPos = nEntryVisPos - nFirstVisPos; nFirstVisPos *= m_pView->GetEntryHeight(); return nFirstVisPos; } void SvImpLBox::SetEntryHeight() { SetNodeBmpWidth( GetExpandedNodeBmp() ); SetNodeBmpWidth( GetCollapsedNodeBmp() ); if(!m_pView->HasViewData()) // are we within the Clear? { Size aSize = m_pView->Control::GetOutputSizePixel(); AdjustScrollBars( aSize ); } else { Resize(); if( GetUpdateMode() ) m_pView->Invalidate(); } } // *********************************************************************** // Callback Functions // *********************************************************************** void SvImpLBox::EntryExpanded( SvTreeListEntry* pEntry ) { // SelAllDestrAnch( false, true ); //DeselectAll(); if( !GetUpdateMode() ) return; ShowCursor( false ); tools::Long nY = GetEntryLine( pEntry ); if( IsLineVisible(nY) ) { InvalidateEntriesFrom( nY ); FindMostRight( pEntry ); } m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) ); // if we expanded before the thumb, the thumb's position has to be // corrected SyncVerThumb(); ShowVerSBar(); ShowCursor( true ); } void SvImpLBox::EntryCollapsed( SvTreeListEntry* pEntry ) { if( !m_pView->IsEntryVisible( pEntry ) ) return; ShowCursor( false ); if( !m_pMostRightEntry || m_pTree->IsChild( pEntry,m_pMostRightEntry ) ) { FindMostRight(); } if( m_pStartEntry ) { tools::Long nOldThumbPos = m_aVerSBar->GetThumbPos(); sal_uLong nVisList = m_pView->GetVisibleCount(); m_aVerSBar->SetRange( Range(0, nVisList-1) ); tools::Long nNewThumbPos = m_aVerSBar->GetThumbPos(); if( nNewThumbPos != nOldThumbPos ) { m_pStartEntry = m_pView->First(); sal_uInt16 nDistance = static_cast(nNewThumbPos); if( nDistance ) m_pStartEntry = m_pView->NextVisible(m_pStartEntry, nDistance); if( GetUpdateMode() ) m_pView->Invalidate(); } else SyncVerThumb(); ShowVerSBar(); } // has the cursor been collapsed? if( m_pTree->IsChild( pEntry, m_pCursor ) ) SetCursor( pEntry ); if( GetUpdateMode() ) ShowVerSBar(); ShowCursor( true ); if( GetUpdateMode() && m_pCursor ) m_pView->Select( m_pCursor ); } void SvImpLBox::CollapsingEntry( SvTreeListEntry* pEntry ) { if( !m_pView->IsEntryVisible( pEntry ) || !m_pStartEntry ) return; SelAllDestrAnch( false ); // deselect all // is the collapsed cursor visible? tools::Long nY = GetEntryLine( pEntry ); if( IsLineVisible(nY) ) { if( GetUpdateMode() ) InvalidateEntriesFrom( nY ); } else { if( m_pTree->IsChild(pEntry, m_pStartEntry) ) { m_pStartEntry = pEntry; if( GetUpdateMode() ) m_pView->Invalidate(); } } } void SvImpLBox::SetNodeBmpWidth( const Image& rBmp ) { const Size aSize( rBmp.GetSizePixel() ); m_nNodeBmpWidth = aSize.Width(); } void SvImpLBox::SetNodeBmpTabDistance() { m_nNodeBmpTabDistance = -m_pView->GetIndent(); if( m_pView->nContextBmpWidthMax ) { // only if the first dynamic tab is centered (we currently assume that) Size aSize = GetExpandedNodeBmp().GetSizePixel(); m_nNodeBmpTabDistance -= aSize.Width() / 2; } } // corrects the cursor when using SingleSelection void SvImpLBox::EntrySelected( SvTreeListEntry* pEntry, bool bSelect ) { if( m_nFlags & LBoxFlags::IgnoreSelect ) return; m_nFlags &= ~LBoxFlags::DeselectAll; if( bSelect && m_aSelEng.GetSelectionMode() == SelectionMode::Single && pEntry != m_pCursor ) { SetCursor( pEntry ); DBG_ASSERT(m_pView->GetSelectionCount()==1,"selection count?"); } if( GetUpdateMode() && m_pView->IsEntryVisible(pEntry) ) { tools::Long nY = GetEntryLine( pEntry ); if( IsLineVisible( nY ) ) { ShowCursor(false); InvalidateEntry(pEntry); ShowCursor(true); } } } void SvImpLBox::RemovingEntry( SvTreeListEntry* pEntry ) { CallEventListeners( VclEventId::ListboxItemRemoved , pEntry ); DestroyAnchor(); if( !m_pView->IsEntryVisible( pEntry ) ) { // if parent is collapsed => bye! m_nFlags |= LBoxFlags::RemovedEntryInvisible; return; } if( pEntry == m_pMostRightEntry || ( pEntry->HasChildren() && m_pView->IsExpanded(pEntry) && m_pTree->IsChild(pEntry, m_pMostRightEntry))) { m_nFlags |= LBoxFlags::RemovedRecalcMostRight; } SvTreeListEntry* pOldStartEntry = m_pStartEntry; SvTreeListEntry* pParent = m_pView->GetModel()->GetParent(pEntry); if (pParent && m_pView->GetModel()->GetChildList(pParent).size() == 1) { DBG_ASSERT( m_pView->IsExpanded( pParent ), "Parent not expanded"); pParent->SetFlags( pParent->GetFlags() | SvTLEntryFlags::NO_NODEBMP); InvalidateEntry( pParent ); } if( m_pCursor && m_pTree->IsChild( pEntry, m_pCursor) ) m_pCursor = pEntry; if( m_pStartEntry && m_pTree->IsChild(pEntry,m_pStartEntry) ) m_pStartEntry = pEntry; SvTreeListEntry* pTemp; if( m_pCursor && m_pCursor == pEntry ) { if( m_bSimpleTravel ) m_pView->Select( m_pCursor, false ); ShowCursor( false ); // focus rectangle gone // NextSibling, because we also delete the children of the cursor pTemp = m_pCursor->NextSibling(); if( !pTemp ) pTemp = m_pView->PrevVisible(m_pCursor); SetCursor( pTemp, true ); } if( m_pStartEntry && m_pStartEntry == pEntry ) { pTemp = m_pStartEntry->NextSibling(); if( !pTemp ) pTemp = m_pView->PrevVisible(m_pStartEntry); m_pStartEntry = pTemp; } if( GetUpdateMode()) { // if it is the last one, we have to invalidate it, so the lines are // drawn correctly (in this case they're deleted) if( m_pStartEntry && (m_pStartEntry != pOldStartEntry || pEntry == m_pView->GetModel()->Last()) ) { m_aVerSBar->SetThumbPos( m_pView->GetVisiblePos( m_pStartEntry )); m_pView->Invalidate( GetVisibleArea() ); } else InvalidateEntriesFrom( GetEntryLine( pEntry ) ); } } void SvImpLBox::EntryRemoved() { if( m_nFlags & LBoxFlags::RemovedEntryInvisible ) { m_nFlags &= ~LBoxFlags::RemovedEntryInvisible; return; } if( !m_pStartEntry ) m_pStartEntry = m_pTree->First(); if( !m_pCursor ) SetCursor( m_pStartEntry, true ); if( m_pCursor && (m_bSimpleTravel || !m_pView->GetSelectionCount() )) m_pView->Select( m_pCursor ); if( GetUpdateMode()) { if( m_nFlags & LBoxFlags::RemovedRecalcMostRight ) FindMostRight(); m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1 ) ); FillView(); if( m_pStartEntry ) // if something above the thumb was deleted m_aVerSBar->SetThumbPos( m_pView->GetVisiblePos( m_pStartEntry) ); ShowVerSBar(); if( m_pCursor && m_pView->HasFocus() && !m_pView->IsSelected(m_pCursor) ) { if( m_pView->GetSelectionCount() ) { // is a neighboring entry selected? SvTreeListEntry* pNextCursor = m_pView->PrevVisible( m_pCursor ); if( !pNextCursor || !m_pView->IsSelected( pNextCursor )) pNextCursor = m_pView->NextVisible( m_pCursor ); if( !pNextCursor || !m_pView->IsSelected( pNextCursor )) // no neighbor selected: use first selected pNextCursor = m_pView->FirstSelected(); SetCursor( pNextCursor ); MakeVisible( m_pCursor ); } else m_pView->Select( m_pCursor ); } ShowCursor( true ); } m_nFlags &= ~LBoxFlags::RemovedRecalcMostRight; } void SvImpLBox::MovingEntry( SvTreeListEntry* pEntry ) { bool bDeselAll(m_nFlags & LBoxFlags::DeselectAll); SelAllDestrAnch( false ); // DeselectAll(); if( !bDeselAll ) m_nFlags &= ~LBoxFlags::DeselectAll; if( pEntry == m_pCursor ) ShowCursor( false ); if( IsEntryInView( pEntry ) ) m_pView->Invalidate(); if( pEntry != m_pStartEntry ) return; SvTreeListEntry* pNew = nullptr; if( !pEntry->HasChildren() ) { pNew = m_pView->NextVisible(m_pStartEntry); if( !pNew ) pNew = m_pView->PrevVisible(m_pStartEntry); } else { pNew = pEntry->NextSibling(); if( !pNew ) pNew = pEntry->PrevSibling(); } m_pStartEntry = pNew; } void SvImpLBox::EntryMoved( SvTreeListEntry* pEntry ) { UpdateContextBmpWidthVectorFromMovedEntry( pEntry ); if ( !m_pStartEntry ) // this might happen if the only entry in the view is moved to its very same position // #i97346# m_pStartEntry = m_pView->First(); m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1)); sal_uInt16 nFirstPos = static_cast(m_pTree->GetAbsPos( m_pStartEntry )); sal_uInt16 nNewPos = static_cast(m_pTree->GetAbsPos( pEntry )); FindMostRight(); if( nNewPos < nFirstPos ) // HACK! m_pStartEntry = pEntry; SyncVerThumb(); if( pEntry == m_pCursor ) { if( m_pView->IsEntryVisible( m_pCursor ) ) ShowCursor( true ); else { SvTreeListEntry* pParent = pEntry; do { pParent = m_pTree->GetParent( pParent ); } while( !m_pView->IsEntryVisible( pParent ) ); SetCursor( pParent ); } } if( IsEntryInView( pEntry ) ) m_pView->Invalidate(); } void SvImpLBox::EntryInserted( SvTreeListEntry* pEntry ) { if( !GetUpdateMode() ) return; SvTreeListEntry* pParent = m_pTree->GetParent(pEntry); if (pParent && m_pTree->GetChildList(pParent).size() == 1) // draw plus sign m_pTree->InvalidateEntry( pParent ); if( !m_pView->IsEntryVisible( pEntry ) ) return; bool bDeselAll(m_nFlags & LBoxFlags::DeselectAll); if( bDeselAll ) SelAllDestrAnch( false ); else DestroyAnchor(); // nFlags &= (~LBoxFlags::DeselectAll); // ShowCursor( false ); // if cursor is moved lower tools::Long nY = GetEntryLine( pEntry ); bool bEntryVisible = IsLineVisible( nY ); if( bEntryVisible ) { ShowCursor( false ); // if cursor is moved lower nY -= m_pView->GetEntryHeight(); // because of lines InvalidateEntriesFrom( nY ); } else if( m_pStartEntry && nY < GetEntryLine(m_pStartEntry) ) { // Check if the view is filled completely. If not, then adjust // pStartEntry and the Cursor (automatic scrolling). sal_uInt16 nLast = static_cast(m_pView->GetVisiblePos(m_pView->LastVisible())); sal_uInt16 nThumb = static_cast(m_pView->GetVisiblePos( m_pStartEntry )); sal_uInt16 nCurDispEntries = nLast-nThumb+1; if( nCurDispEntries < m_nVisibleCount ) { // set at the next paint event m_pStartEntry = nullptr; SetCursor( nullptr ); m_pView->Invalidate(); } } else if( !m_pStartEntry ) m_pView->Invalidate(); SetMostRight( pEntry ); m_aVerSBar->SetRange( Range(0, m_pView->GetVisibleCount()-1)); SyncVerThumb(); // if something was inserted before the thumb ShowVerSBar(); ShowCursor( true ); if( m_pStartEntry != m_pView->First() && (m_nFlags & LBoxFlags::Filling) ) m_pView->PaintImmediately(); } // ******************************************************************** // Event handler // ******************************************************************** // ****** Control the control animation bool SvImpLBox::ButtonDownCheckCtrl(const MouseEvent& rMEvt, SvTreeListEntry* pEntry) { SvLBoxItem* pItem = m_pView->GetItem(pEntry,rMEvt.GetPosPixel().X(),&m_pActiveTab); if (pItem && pItem->GetType() == SvLBoxItemType::Button) { m_pActiveButton = static_cast(pItem); m_pActiveEntry = pEntry; if( m_pCursor == m_pActiveEntry ) m_pView->HideFocus(); m_pView->CaptureMouse(); m_pActiveButton->SetStateHilighted( true ); InvalidateEntry(m_pActiveEntry); return true; } else m_pActiveButton = nullptr; return false; } bool SvImpLBox::MouseMoveCheckCtrl(const MouseEvent& rMEvt, SvTreeListEntry const * pEntry) { if( m_pActiveButton ) { tools::Long nMouseX = rMEvt.GetPosPixel().X(); if( pEntry == m_pActiveEntry && m_pView->GetItem(m_pActiveEntry, nMouseX) == m_pActiveButton ) { if( !m_pActiveButton->IsStateHilighted() ) { m_pActiveButton->SetStateHilighted(true ); InvalidateEntry(m_pActiveEntry); } } else { if( m_pActiveButton->IsStateHilighted() ) { m_pActiveButton->SetStateHilighted(false ); InvalidateEntry(m_pActiveEntry); } } return true; } return false; } bool SvImpLBox::ButtonUpCheckCtrl( const MouseEvent& rMEvt ) { if( m_pActiveButton ) { m_pView->ReleaseMouse(); SvTreeListEntry* pEntry = GetClickedEntry( rMEvt.GetPosPixel() ); m_pActiveButton->SetStateHilighted( false ); tools::Long nMouseX = rMEvt.GetPosPixel().X(); if (pEntry == m_pActiveEntry && m_pView->GetItem(m_pActiveEntry, nMouseX) == m_pActiveButton) m_pActiveButton->ClickHdl(m_pActiveEntry); InvalidateEntry(m_pActiveEntry); if (m_pCursor == m_pActiveEntry) ShowCursor(true); m_pActiveButton = nullptr; m_pActiveEntry = nullptr; m_pActiveTab = nullptr; return true; } return false; } // ******* Control plus/minus button for expanding/collapsing // false == no expand/collapse button hit bool SvImpLBox::IsNodeButton( const Point& rPosPixel, const SvTreeListEntry* pEntry ) const { if( !pEntry->HasChildren() && !pEntry->HasChildrenOnDemand() ) return false; SvLBoxTab* pFirstDynamicTab = m_pView->GetFirstDynamicTab(); if( !pFirstDynamicTab ) return false; tools::Long nMouseX = rPosPixel.X(); // convert to document coordinates Point aOrigin( m_pView->GetMapMode().GetOrigin() ); nMouseX -= aOrigin.X(); tools::Long nX = m_pView->GetTabPos( pEntry, pFirstDynamicTab); nX += m_nNodeBmpTabDistance; if( nMouseX < nX ) return false; nX += m_nNodeBmpWidth; return nMouseX <= nX; } // false == hit no node button bool SvImpLBox::ButtonDownCheckExpand( const MouseEvent& rMEvt, SvTreeListEntry* pEntry ) { bool bRet = false; if ( m_pView->IsEditingActive() && pEntry == m_pView->pEdEntry ) // inplace editing -> nothing to do bRet = true; else if ( IsNodeButton( rMEvt.GetPosPixel(), pEntry ) ) { if ( m_pView->IsExpanded( pEntry ) ) { m_pView->EndEditing( true ); m_pView->Collapse( pEntry ); } else { // you can expand an entry, which is in editing m_pView->Expand( pEntry ); } bRet = true; } return bRet; } void SvImpLBox::MouseButtonDown( const MouseEvent& rMEvt ) { if ( !rMEvt.IsLeft() && !rMEvt.IsRight()) return; m_aEditIdle.Stop(); Point aPos( rMEvt.GetPosPixel()); if( aPos.X() > m_aOutputSize.Width() || aPos.Y() > m_aOutputSize.Height() ) return; if( !m_pCursor ) m_pCursor = m_pStartEntry; m_nFlags &= ~LBoxFlags::Filling; m_pView->GrabFocus(); //fdo#82270 Grabbing focus can invalidate the entries, re-fetch SvTreeListEntry* pEntry = GetEntry(aPos); // the entry can still be invalid! if( !pEntry || !m_pView->GetViewData( pEntry )) return; tools::Long nY = GetEntryLine( pEntry ); // Node-Button? if( ButtonDownCheckExpand( rMEvt, pEntry ) ) return; if( !EntryReallyHit(pEntry,aPos,nY)) return; SvLBoxItem* pXItem = m_pView->GetItem( pEntry, aPos.X() ); if( pXItem ) { SvLBoxTab* pXTab = m_pView->GetTab( pEntry, pXItem ); if ( !rMEvt.IsMod1() && !rMEvt.IsMod2() && rMEvt.IsLeft() && pXTab->IsEditable() && pEntry == m_pView->FirstSelected() && nullptr == m_pView->NextSelected( pEntry ) ) // #i8234# FirstSelected() and NextSelected() ensures, that inplace editing is only triggered, when only one entry is selected m_nFlags |= LBoxFlags::StartEditTimer; if ( !m_pView->IsSelected( pEntry ) ) m_nFlags &= ~LBoxFlags::StartEditTimer; } if( (rMEvt.GetClicks() % 2) == 0) { m_nFlags &= ~LBoxFlags::StartEditTimer; m_pView->pHdlEntry = pEntry; if( !m_pView->DoubleClickHdl() ) { // Handler signals nothing to be done anymore, bail out, 'this' may // even be dead and destroyed. return; } else { // if the entry was deleted within the handler pEntry = GetClickedEntry( aPos ); if( !pEntry ) return; if( pEntry != m_pView->pHdlEntry ) { // select anew & bye if( !m_bSimpleTravel && !m_aSelEng.IsAlwaysAdding()) SelAllDestrAnch( false ); // DeselectAll(); SetCursor( pEntry ); return; } if( pEntry->HasChildren() || pEntry->HasChildrenOnDemand() ) { if( m_pView->IsExpanded(pEntry) ) m_pView->Collapse( pEntry ); else m_pView->Expand( pEntry ); if( pEntry == m_pCursor ) // only if Entryitem was clicked // (Nodebutton is not an Entryitem!) m_pView->Select( m_pCursor ); return; } } } else { // CheckButton? (TreeListBox: Check + Info) if( ButtonDownCheckCtrl(rMEvt, pEntry) ) return; // Inplace-Editing? } if ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE && !rMEvt.IsRight() ) // tdf#128824 m_aSelEng.SelMouseButtonDown( rMEvt ); } void SvImpLBox::MouseButtonUp( const MouseEvent& rMEvt) { if ( !ButtonUpCheckCtrl( rMEvt ) && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) m_aSelEng.SelMouseButtonUp( rMEvt ); if( m_nFlags & LBoxFlags::StartEditTimer ) { m_nFlags &= ~LBoxFlags::StartEditTimer; m_aEditClickPos = rMEvt.GetPosPixel(); m_aEditIdle.Start(); } if (m_pView->mbActivateOnSingleClick) { Point aPos(rMEvt.GetPosPixel()); SvTreeListEntry* pEntry = GetEntry(aPos); // tdf#143245 ActivateOnSingleClick only // if the 'up' is at the active entry // typically selected by the 'down' if (!pEntry || pEntry != m_pCursor) return; m_pView->DoubleClickHdl(); } } void SvImpLBox::MouseMove( const MouseEvent& rMEvt) { Point aPos = rMEvt.GetPosPixel(); SvTreeListEntry* pEntry = GetClickedEntry(aPos); if ( MouseMoveCheckCtrl( rMEvt, pEntry ) || ( m_aSelEng.GetSelectionMode() == SelectionMode::NONE ) ) return; m_aSelEng.SelMouseMove(rMEvt); if (m_pView->mbHoverSelection) { if (aPos.X() < 0 || aPos.Y() < 0 || aPos.X() > m_aOutputSize.Width() || aPos.Y() > m_aOutputSize.Height()) pEntry = nullptr; else pEntry = GetEntry(aPos); if (!pEntry) m_pView->SelectAll(false); else if (!m_pView->IsSelected(pEntry) && IsSelectable(pEntry)) { m_pView->mbSelectingByHover = true; m_pView->Select(pEntry); m_pView->mbSelectingByHover = false; } } } void SvImpLBox::ExpandAll() { sal_uInt16 nRefDepth = m_pTree->GetDepth(m_pCursor); SvTreeListEntry* pCur = m_pTree->Next(m_pCursor); while (pCur && m_pTree->GetDepth(pCur) > nRefDepth) { if (pCur->HasChildren() && !m_pView->IsExpanded(pCur)) m_pView->Expand(pCur); pCur = m_pTree->Next(pCur); } } void SvImpLBox::CollapseTo(SvTreeListEntry* pParentToCollapse) { // collapse all parents until we get to the given parent to collapse if (!pParentToCollapse) return; sal_uInt16 nRefDepth; // special case explorer: if the root only has a single // entry, don't collapse the root entry if (m_pTree->GetChildList(nullptr).size() < 2) { nRefDepth = 1; pParentToCollapse = m_pCursor; while (m_pTree->GetParent(pParentToCollapse) && m_pTree->GetDepth(m_pTree->GetParent(pParentToCollapse)) > 0) { pParentToCollapse = m_pTree->GetParent(pParentToCollapse); } } else nRefDepth = m_pTree->GetDepth(pParentToCollapse); if (m_pView->IsExpanded(pParentToCollapse)) m_pView->Collapse(pParentToCollapse); SvTreeListEntry* pCur = m_pTree->Next(pParentToCollapse); while (pCur && m_pTree->GetDepth(pCur) > nRefDepth) { if (pCur->HasChildren() && m_pView->IsExpanded(pCur)) m_pView->Collapse(pCur); pCur = m_pTree->Next(pCur); } } bool SvImpLBox::KeyInput( const KeyEvent& rKEvt) { m_aEditIdle.Stop(); const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); if( rKeyCode.IsMod2() ) return false; // don't evaluate Alt key m_nFlags &= ~LBoxFlags::Filling; if( !m_pCursor ) m_pCursor = m_pStartEntry; if( !m_pCursor ) return false; bool bKeyUsed = true; sal_uInt16 nDelta = static_cast(m_aVerSBar->GetPageSize()); sal_uInt16 aCode = rKeyCode.GetCode(); bool bShift = rKeyCode.IsShift(); bool bMod1 = rKeyCode.IsMod1(); SvTreeListEntry* pNewCursor; switch( aCode ) { case KEY_UP: if( !IsEntryInView( m_pCursor ) ) MakeVisible( m_pCursor ); pNewCursor = m_pCursor; do { pNewCursor = m_pView->PrevVisible(pNewCursor); } while( pNewCursor && !IsSelectable(pNewCursor) ); // if there is no next entry, take the current one // this ensures that in case of _one_ entry in the list, this entry is selected when pressing // the cursor key if (!pNewCursor) pNewCursor = m_pCursor; m_aSelEng.CursorPosChanging( bShift, bMod1 ); SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on if( !IsEntryInView( pNewCursor ) ) KeyUp( false ); break; case KEY_DOWN: if( !IsEntryInView( m_pCursor ) ) MakeVisible( m_pCursor ); pNewCursor = m_pCursor; do { pNewCursor = m_pView->NextVisible(pNewCursor); } while( pNewCursor && !IsSelectable(pNewCursor) ); // if there is no next entry, take the current one // this ensures that in case of _one_ entry in the list, this entry is selected when pressing // the cursor key // 06.09.20001 - 83416 - frank.schoenheit@sun.com if ( !pNewCursor && m_pCursor ) pNewCursor = m_pCursor; if( pNewCursor ) { m_aSelEng.CursorPosChanging( bShift, bMod1 ); if( IsEntryInView( pNewCursor ) ) SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on else { if( m_pCursor ) m_pView->Select( m_pCursor, false ); KeyDown( false ); SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on } } else KeyDown( false ); // because scrollbar range might still // allow scrolling break; case KEY_RIGHT: { if( m_bSubLstOpLR ) { // only try to expand if sublist is expandable, // otherwise ignore the key press if( IsExpandable() && !m_pView->IsExpanded( m_pCursor ) ) m_pView->Expand( m_pCursor ); } else if (m_aHorSBar->IsVisible()) { tools::Long nThumb = m_aHorSBar->GetThumbPos(); nThumb += m_aHorSBar->GetLineSize(); tools::Long nOldThumb = m_aHorSBar->GetThumbPos(); m_aHorSBar->SetThumbPos( nThumb ); nThumb = nOldThumb; nThumb -= m_aHorSBar->GetThumbPos(); nThumb *= -1; if( nThumb ) { KeyLeftRight( nThumb ); } } else bKeyUsed = false; break; } case KEY_LEFT: { if (m_aHorSBar->IsVisible()) { tools::Long nThumb = m_aHorSBar->GetThumbPos(); nThumb -= m_aHorSBar->GetLineSize(); tools::Long nOldThumb = m_aHorSBar->GetThumbPos(); m_aHorSBar->SetThumbPos( nThumb ); nThumb = nOldThumb; nThumb -= m_aHorSBar->GetThumbPos(); if( nThumb ) { KeyLeftRight( -nThumb ); } else if( m_bSubLstOpLR ) { if( IsExpandable() && m_pView->IsExpanded( m_pCursor ) ) m_pView->Collapse( m_pCursor ); else { pNewCursor = m_pView->GetParent( m_pCursor ); if( pNewCursor ) SetCursor( pNewCursor ); } } } else if( m_bSubLstOpLR ) { if( IsExpandable() && m_pView->IsExpanded( m_pCursor ) ) m_pView->Collapse( m_pCursor ); else { pNewCursor = m_pView->GetParent( m_pCursor ); if( pNewCursor ) SetCursor( pNewCursor ); } } else bKeyUsed = false; break; } case KEY_PAGEUP: if( !bMod1 ) { pNewCursor = m_pView->PrevVisible(m_pCursor, nDelta); while( nDelta && pNewCursor && !IsSelectable(pNewCursor) ) { pNewCursor = m_pView->NextVisible(pNewCursor); nDelta--; } if( nDelta ) { DBG_ASSERT(pNewCursor && pNewCursor!=m_pCursor, "Cursor?"); m_aSelEng.CursorPosChanging( bShift, bMod1 ); if( IsEntryInView( pNewCursor ) ) SetCursor( pNewCursor ); else { SetCursor( pNewCursor ); KeyUp( true ); } } } else bKeyUsed = false; break; case KEY_PAGEDOWN: if( !bMod1 ) { pNewCursor= m_pView->NextVisible(m_pCursor, nDelta); while( nDelta && pNewCursor && !IsSelectable(pNewCursor) ) { pNewCursor = m_pView->PrevVisible(pNewCursor); nDelta--; } if( nDelta && pNewCursor ) { DBG_ASSERT(pNewCursor && pNewCursor!=m_pCursor, "Cursor?"); m_aSelEng.CursorPosChanging( bShift, bMod1 ); if( IsEntryInView( pNewCursor ) ) SetCursor( pNewCursor ); else { SetCursor( pNewCursor ); KeyDown( true ); } } else KeyDown( false ); // see also: KEY_DOWN } else bKeyUsed = false; break; case KEY_SPACE: if ( m_pView->GetSelectionMode() != SelectionMode::NONE ) { if ( bMod1 ) { if ( m_pView->GetSelectionMode() == SelectionMode::Multiple && !bShift ) // toggle selection m_pView->Select( m_pCursor, !m_pView->IsSelected( m_pCursor ) ); } else if ( !bShift /*&& !bMod1*/ ) { if ( m_aSelEng.IsAddMode() ) { // toggle selection m_pView->Select( m_pCursor, !m_pView->IsSelected( m_pCursor ) ); } else if ( !m_pView->IsSelected( m_pCursor ) ) { SelAllDestrAnch( false ); m_pView->Select( m_pCursor ); } else bKeyUsed = false; } else bKeyUsed = false; } else bKeyUsed = false; break; case KEY_RETURN: bKeyUsed = !m_pView->DoubleClickHdl(); break; case KEY_F2: if( !bShift && !bMod1 ) { m_aEditClickPos = Point( -1, -1 ); EditTimerCall( nullptr ); } else bKeyUsed = false; break; case KEY_F8: if( bShift && m_pView->GetSelectionMode()==SelectionMode::Multiple && !(m_nStyle & WB_SIMPLEMODE)) { if( m_aSelEng.IsAlwaysAdding() ) m_aSelEng.AddAlways( false ); else m_aSelEng.AddAlways( true ); } else bKeyUsed = false; break; case KEY_ADD: if (!m_pView->IsExpanded(m_pCursor)) m_pView->Expand(m_pCursor); if (bMod1) ExpandAll(); break; case KEY_A: if( bMod1 ) SelAllDestrAnch( true ); else bKeyUsed = false; break; case KEY_SUBTRACT: if (m_pView->IsExpanded(m_pCursor)) m_pView->Collapse(m_pCursor); if (bMod1) CollapseTo(m_pTree->GetRootLevelParent(m_pCursor)); break; case KEY_MULTIPLY: if( bMod1 ) { // only try to expand/collapse if sublist is expandable, // otherwise ignore the key press if( IsExpandable() ) { if (!m_pView->IsAllExpanded(m_pCursor)) { m_pView->Expand(m_pCursor); ExpandAll(); } else CollapseTo(m_pCursor); } } else bKeyUsed = false; break; case KEY_DIVIDE : if( bMod1 ) SelAllDestrAnch( true ); else bKeyUsed = false; break; case KEY_COMMA : if( bMod1 ) SelAllDestrAnch( false ); else bKeyUsed = false; break; case KEY_HOME : pNewCursor = m_pView->GetModel()->First(); while( pNewCursor && !IsSelectable(pNewCursor) ) { pNewCursor = m_pView->NextVisible(pNewCursor); } if( pNewCursor && pNewCursor != m_pCursor ) { // SelAllDestrAnch( false ); m_aSelEng.CursorPosChanging( bShift, bMod1 ); SetCursor( pNewCursor ); if( !IsEntryInView( pNewCursor ) ) MakeVisible( pNewCursor ); } else bKeyUsed = false; break; case KEY_END : pNewCursor = m_pView->GetModel()->Last(); while( pNewCursor && !IsSelectable(pNewCursor) ) { pNewCursor = m_pView->PrevVisible(pNewCursor); } if( pNewCursor && pNewCursor != m_pCursor) { // SelAllDestrAnch( false ); m_aSelEng.CursorPosChanging( bShift, bMod1 ); SetCursor( pNewCursor ); if( !IsEntryInView( pNewCursor ) ) MakeVisible( pNewCursor ); } else bKeyUsed = false; break; case KEY_ESCAPE: case KEY_TAB: case KEY_DELETE: case KEY_BACKSPACE: // must not be handled because this quits dialogs and does other magic things... // if there are other single keys which should not be handled, they can be added here bKeyUsed = false; break; default: // is there any reason why we should eat the events here? The only place where this is called // is from SvTreeListBox::KeyInput. If we set bKeyUsed to true here, then the key input // is just silenced. However, we want SvLBox::KeyInput to get a chance, to do the QuickSelection // handling. // (The old code here which intentionally set bKeyUsed to sal_True said this was because of "quick search" // handling, but actually there was no quick search handling anymore. We just re-implemented it.) // #i31275# / 2009-06-16 / frank.schoenheit@sun.com bKeyUsed = false; break; } return bKeyUsed; } void SvImpLBox::GetFocus() { if( m_pCursor ) { m_pView->SetEntryFocus( m_pCursor, true ); ShowCursor( true ); // auskommentiert wg. deselectall // if( bSimpleTravel && !pView->IsSelected(pCursor) ) // pView->Select( pCursor, true ); } if( m_nStyle & WB_HIDESELECTION ) { SvTreeListEntry* pEntry = m_pView->FirstSelected(); while( pEntry ) { InvalidateEntry( pEntry ); pEntry = m_pView->NextSelected( pEntry ); } } } void SvImpLBox::LoseFocus() { m_aEditIdle.Stop(); if( m_pCursor ) m_pView->SetEntryFocus( m_pCursor,false ); ShowCursor( false ); if( m_nStyle & WB_HIDESELECTION ) { SvTreeListEntry* pEntry = m_pView ? m_pView->FirstSelected() : nullptr; while( pEntry ) { InvalidateEntry( pEntry ); pEntry = m_pView->NextSelected( pEntry ); } } } // ******************************************************************** // SelectionEngine // ******************************************************************** void SvImpLBox::SelectEntry( SvTreeListEntry* pEntry, bool bSelect ) { m_pView->Select( pEntry, bSelect ); } ImpLBSelEng::ImpLBSelEng( SvImpLBox* pImpl, SvTreeListBox* pV ) { pImp = pImpl; pView = pV; } ImpLBSelEng::~ImpLBSelEng() { } void ImpLBSelEng::BeginDrag() { pImp->BeginDrag(); } void ImpLBSelEng::CreateAnchor() { pImp->m_pAnchor = pImp->m_pCursor; } void ImpLBSelEng::DestroyAnchor() { pImp->m_pAnchor = nullptr; } void ImpLBSelEng::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor) { SvTreeListEntry* pNewCursor = pImp->MakePointVisible( rPoint ); if( pNewCursor ) { // at SimpleTravel, the SetCursor is selected and the select handler is // called //if( !bDontSelectAtCursor && !pImp->bSimpleTravel ) // pImp->SelectEntry( pNewCursor, true ); pImp->SetCursor( pNewCursor, bDontSelectAtCursor ); } } bool ImpLBSelEng::IsSelectionAtPoint( const Point& rPoint ) { SvTreeListEntry* pEntry = pImp->MakePointVisible( rPoint ); if( pEntry ) return pView->IsSelected(pEntry); return false; } void ImpLBSelEng::DeselectAtPoint( const Point& rPoint ) { SvTreeListEntry* pEntry = pImp->MakePointVisible( rPoint ); if( !pEntry ) return; pImp->SelectEntry( pEntry, false ); } void ImpLBSelEng::DeselectAll() { pImp->SelAllDestrAnch( false, false ); // don't reset SelectionEngine! pImp->m_nFlags &= ~LBoxFlags::DeselectAll; } // *********************************************************************** // Selection // *********************************************************************** void SvImpLBox::SetAnchorSelection(SvTreeListEntry* pOldCursor,SvTreeListEntry* pNewCursor) { SvTreeListEntry* pEntry; sal_uLong nAnchorVisPos = m_pView->GetVisiblePos( m_pAnchor ); sal_uLong nOldVisPos = m_pView->GetVisiblePos( pOldCursor ); sal_uLong nNewVisPos = m_pView->GetVisiblePos( pNewCursor ); if( nOldVisPos > nAnchorVisPos || ( nAnchorVisPos==nOldVisPos && nNewVisPos > nAnchorVisPos) ) { if( nNewVisPos > nOldVisPos ) { pEntry = pOldCursor; while( pEntry && pEntry != pNewCursor ) { m_pView->Select( pEntry ); pEntry = m_pView->NextVisible(pEntry); } if( pEntry ) m_pView->Select( pEntry ); return; } if( nNewVisPos < nAnchorVisPos ) { pEntry = m_pAnchor; while( pEntry && pEntry != pOldCursor ) { m_pView->Select( pEntry, false ); pEntry = m_pView->NextVisible(pEntry); } if( pEntry ) m_pView->Select( pEntry, false ); pEntry = pNewCursor; while( pEntry && pEntry != m_pAnchor ) { m_pView->Select( pEntry ); pEntry = m_pView->NextVisible(pEntry); } if( pEntry ) m_pView->Select( pEntry ); return; } if( nNewVisPos < nOldVisPos ) { pEntry = m_pView->NextVisible(pNewCursor); while( pEntry && pEntry != pOldCursor ) { m_pView->Select( pEntry, false ); pEntry = m_pView->NextVisible(pEntry); } if( pEntry ) m_pView->Select( pEntry, false ); return; } } else { if( nNewVisPos < nOldVisPos ) // enlarge selection { pEntry = pNewCursor; while( pEntry && pEntry != pOldCursor ) { m_pView->Select( pEntry ); pEntry = m_pView->NextVisible(pEntry); } if( pEntry ) m_pView->Select( pEntry ); return; } if( nNewVisPos > nAnchorVisPos ) { pEntry = pOldCursor; while( pEntry && pEntry != m_pAnchor ) { m_pView->Select( pEntry, false ); pEntry = m_pView->NextVisible(pEntry); } if( pEntry ) m_pView->Select( pEntry, false ); pEntry = m_pAnchor; while( pEntry && pEntry != pNewCursor ) { m_pView->Select( pEntry ); pEntry = m_pView->NextVisible(pEntry); } if( pEntry ) m_pView->Select( pEntry ); return; } if( nNewVisPos > nOldVisPos ) { pEntry = pOldCursor; while( pEntry && pEntry != pNewCursor ) { m_pView->Select( pEntry, false ); pEntry = m_pView->NextVisible(pEntry); } return; } } } void SvImpLBox::SelAllDestrAnch( bool bSelect, bool bDestroyAnchor, bool bSingleSelToo ) { SvTreeListEntry* pEntry; m_nFlags &= ~LBoxFlags::DeselectAll; if( bSelect && m_bSimpleTravel ) { if( m_pCursor && !m_pView->IsSelected( m_pCursor )) { m_pView->Select( m_pCursor ); } return; } if( !bSelect && m_pView->GetSelectionCount() == 0 ) { if( m_bSimpleTravel && ( !GetUpdateMode() || !m_pCursor) ) m_nFlags |= LBoxFlags::DeselectAll; return; } if( bSelect && m_pView->GetSelectionCount() == m_pView->GetEntryCount()) return; if( !bSingleSelToo && m_bSimpleTravel ) return; if( !bSelect && m_pView->GetSelectionCount()==1 && m_pCursor && m_pView->IsSelected( m_pCursor )) { m_pView->Select( m_pCursor, false ); if( bDestroyAnchor ) DestroyAnchor(); // delete anchor & reset SelectionEngine else m_pAnchor = nullptr; // always delete internal anchor return; } if( m_bSimpleTravel && !m_pCursor && !GetUpdateMode() ) m_nFlags |= LBoxFlags::DeselectAll; ShowCursor( false ); bool bUpdate = GetUpdateMode(); m_nFlags |= LBoxFlags::IgnoreSelect; // EntryInserted should not do anything pEntry = m_pTree->First(); while( pEntry ) { if( m_pView->Select( pEntry, bSelect ) ) { if( bUpdate && m_pView->IsEntryVisible(pEntry) ) { tools::Long nY = GetEntryLine( pEntry ); if( IsLineVisible( nY ) ) InvalidateEntry(pEntry); } } pEntry = m_pTree->Next( pEntry ); } m_nFlags &= ~LBoxFlags::IgnoreSelect; if( bDestroyAnchor ) DestroyAnchor(); // delete anchor & reset SelectionEngine else m_pAnchor = nullptr; // always delete internal anchor ShowCursor( true ); } void SvImpLBox::SetSelectionMode( SelectionMode eSelMode ) { m_aSelEng.SetSelectionMode( eSelMode); if( eSelMode == SelectionMode::Single ) m_bSimpleTravel = true; else m_bSimpleTravel = false; if( (m_nStyle & WB_SIMPLEMODE) && (eSelMode == SelectionMode::Multiple) ) m_aSelEng.AddAlways( true ); } // *********************************************************************** // Drag & Drop // *********************************************************************** void SvImpLBox::SetDragDropMode( DragDropMode eDDMode ) { if( eDDMode != DragDropMode::NONE ) { m_aSelEng.ExpandSelectionOnMouseMove( false ); m_aSelEng.EnableDrag( true ); } else { m_aSelEng.ExpandSelectionOnMouseMove(); m_aSelEng.EnableDrag( false ); } } void SvImpLBox::BeginDrag() { m_nFlags &= ~LBoxFlags::Filling; m_pView->StartDrag( 0, m_aSelEng.GetMousePosPixel() ); } void SvImpLBox::PaintDDCursor(SvTreeListEntry* pEntry, bool bShow) { if (pEntry) { SvViewDataEntry* pViewData = m_pView->GetViewData(pEntry); pViewData->SetDragTarget(bShow); #ifdef MACOSX // in MacOS we need to draw directly (as we are synchronous) or no invalidation happens m_pView->PaintEntry1(*pEntry, GetEntryLine(pEntry), *m_pView->GetOutDev()); #else InvalidateEntry(pEntry); #endif } } void SvImpLBox::Command( const CommandEvent& rCEvt ) { CommandEventId nCommand = rCEvt.GetCommand(); if( nCommand == CommandEventId::ContextMenu ) m_aEditIdle.Stop(); // scroll mouse event? if (nCommand == CommandEventId::Wheel || nCommand == CommandEventId::StartAutoScroll || nCommand == CommandEventId::AutoScroll || nCommand == CommandEventId::Gesture) { if (m_pView->HandleScrollCommand(rCEvt, m_aHorSBar.get(), m_aVerSBar.get())) return; } const Point& rPos = rCEvt.GetMousePosPixel(); if( rPos.X() < m_aOutputSize.Width() && rPos.Y() < m_aOutputSize.Height() ) m_aSelEng.Command( rCEvt ); } tools::Rectangle SvImpLBox::GetVisibleArea() const { Point aPos( m_pView->GetMapMode().GetOrigin() ); aPos.setX( aPos.X() * -1 ); tools::Rectangle aRect( aPos, m_aOutputSize ); return aRect; } void SvImpLBox::Invalidate() { m_pView->GetOutDev()->SetClipRegion(); } void SvImpLBox::SetCurEntry( SvTreeListEntry* pEntry ) { if ( ( m_aSelEng.GetSelectionMode() != SelectionMode::Single ) && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) SelAllDestrAnch( false ); if ( pEntry ) MakeVisible( pEntry ); SetCursor( pEntry ); if ( pEntry && ( m_aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) m_pView->Select( pEntry ); } IMPL_LINK_NOARG(SvImpLBox, EditTimerCall, Timer *, void) { if( !m_pView->IsInplaceEditingEnabled() ) return; bool bIsMouseTriggered = m_aEditClickPos.X() >= 0; if ( bIsMouseTriggered ) { Point aCurrentMousePos = m_pView->GetPointerPosPixel(); if ( ( std::abs( aCurrentMousePos.X() - m_aEditClickPos.X() ) > 5 ) || ( std::abs( aCurrentMousePos.Y() - m_aEditClickPos.Y() ) > 5 ) ) { return; } } SvTreeListEntry* pEntry = GetCurEntry(); if( pEntry ) { ShowCursor( false ); m_pView->ImplEditEntry( pEntry ); ShowCursor( true ); } } bool SvImpLBox::RequestHelp( const HelpEvent& rHEvt ) { if( rHEvt.GetMode() & HelpEventMode::QUICK ) { Point aPos( m_pView->ScreenToOutputPixel( rHEvt.GetMousePosPixel() )); if( !GetVisibleArea().Contains( aPos )) return false; SvTreeListEntry* pEntry = GetEntry( aPos ); if( pEntry ) { // recalculate text rectangle SvLBoxTab* pTab; SvLBoxItem* pItem = m_pView->GetItem( pEntry, aPos.X(), &pTab ); if (!pItem || pItem->GetType() != SvLBoxItemType::String) return false; aPos = GetEntryPosition( pEntry ); aPos.setX( m_pView->GetTabPos( pEntry, pTab ) ); //pTab->GetPos(); Size aSize(pItem->GetWidth(m_pView, pEntry), pItem->GetHeight(m_pView, pEntry)); SvLBoxTab* pNextTab = NextTab( pTab ); bool bItemClipped = false; // is the item cut off by its right neighbor? if( pNextTab && m_pView->GetTabPos(pEntry,pNextTab) < aPos.X()+aSize.Width() ) { aSize.setWidth( pNextTab->GetPos() - pTab->GetPos() ); bItemClipped = true; } tools::Rectangle aItemRect( aPos, aSize ); tools::Rectangle aViewRect( GetVisibleArea() ); if( bItemClipped || !aViewRect.Contains( aItemRect ) ) { // clip the right edge of the item at the edge of the view //if( aItemRect.Right() > aViewRect.Right() ) // aItemRect.Right() = aViewRect.Right(); Point aPt = m_pView->OutputToScreenPixel( aItemRect.TopLeft() ); aItemRect.SetLeft( aPt.X() ); aItemRect.SetTop( aPt.Y() ); aPt = m_pView->OutputToScreenPixel( aItemRect.BottomRight() ); aItemRect.SetRight( aPt.X() ); aItemRect.SetBottom( aPt.Y() ); Help::ShowQuickHelp( m_pView, aItemRect, static_cast(pItem)->GetText(), QuickHelpFlags::Left | QuickHelpFlags::VCenter ); return true; } } } return false; } SvLBoxTab* SvImpLBox::NextTab( SvLBoxTab const * pTab ) { sal_uInt16 nTabCount = m_pView->TabCount(); if( nTabCount <= 1 ) return nullptr; for( int nTab=0; nTab < (nTabCount-1); nTab++) { if( m_pView->aTabs[nTab].get() == pTab ) return m_pView->aTabs[nTab+1].get(); } return nullptr; } void SvImpLBox::SetUpdateMode( bool bMode ) { if( m_bUpdateMode != bMode ) { m_bUpdateMode = bMode; if( m_bUpdateMode ) UpdateAll( false ); } } void SvImpLBox::SetMostRight( SvTreeListEntry* pEntry ) { if( m_pView->nTreeFlags & SvTreeFlags::RECALCTABS ) { m_nFlags |= LBoxFlags::IgnoreChangedTabs; m_pView->SetTabs(); m_nFlags &= ~LBoxFlags::IgnoreChangedTabs; } sal_uInt16 nLastTab = m_pView->aTabs.size() - 1; sal_uInt16 nLastItem = pEntry->ItemCount() - 1; if( m_pView->aTabs.empty() || nLastItem == USHRT_MAX ) return; if( nLastItem < nLastTab ) nLastTab = nLastItem; SvLBoxTab* pTab = m_pView->aTabs[ nLastTab ].get(); SvLBoxItem& rItem = pEntry->GetItem( nLastTab ); tools::Long nTabPos = m_pView->GetTabPos( pEntry, pTab ); tools::Long nMaxRight = GetOutputSize().Width(); Point aPos( m_pView->GetMapMode().GetOrigin() ); aPos.setX( aPos.X() * -1 ); // conversion document coordinates nMaxRight = nMaxRight + aPos.X() - 1; tools::Long nNextTab = nTabPos < nMaxRight ? nMaxRight : nMaxRight + 50; tools::Long nTabWidth = nNextTab - nTabPos + 1; auto nItemSize = rItem.GetWidth(m_pView,pEntry); tools::Long nOffset = pTab->CalcOffset( nItemSize, nTabWidth ); tools::Long nRight = nTabPos + nOffset + nItemSize; if( nRight > m_nMostRight ) { m_nMostRight = nRight; m_pMostRightEntry = pEntry; } } void SvImpLBox::FindMostRight() { m_nMostRight = -1; m_pMostRightEntry = nullptr; if( !m_pView->GetModel() ) return; SvTreeListEntry* pEntry = m_pView->FirstVisible(); while( pEntry ) { SetMostRight( pEntry ); pEntry = m_pView->NextVisible( pEntry ); } } void SvImpLBox::FindMostRight( SvTreeListEntry* pParent ) { if( !pParent ) FindMostRight(); else FindMostRight_Impl( pParent ); } void SvImpLBox::FindMostRight_Impl( SvTreeListEntry* pParent ) { SvTreeListEntries& rList = m_pTree->GetChildList( pParent ); size_t nCount = rList.size(); for( size_t nCur = 0; nCur < nCount; nCur++ ) { SvTreeListEntry* pChild = rList[nCur].get(); SetMostRight( pChild ); if( pChild->HasChildren() && m_pView->IsExpanded( pChild )) FindMostRight_Impl( pChild ); } } void SvImpLBox::NotifyTabsChanged() { if( GetUpdateMode() && !(m_nFlags & LBoxFlags::IgnoreChangedTabs ) && m_nCurUserEvent == nullptr ) { m_nCurUserEvent = Application::PostUserEvent(LINK(this,SvImpLBox,MyUserEvent)); } } bool SvImpLBox::IsExpandable() const { return m_pCursor->HasChildren() || m_pCursor->HasChildrenOnDemand(); } IMPL_LINK(SvImpLBox, MyUserEvent, void*, pArg, void ) { m_nCurUserEvent = nullptr; if( !pArg ) { m_pView->Invalidate(); m_pView->PaintImmediately(); } else { FindMostRight(); ShowVerSBar(); m_pView->Invalidate( GetVisibleArea() ); } } void SvImpLBox::StopUserEvent() { if( m_nCurUserEvent != nullptr ) { Application::RemoveUserEvent( m_nCurUserEvent ); m_nCurUserEvent = nullptr; } } void SvImpLBox::implInitDefaultNodeImages() { if ( s_pDefCollapsed ) // assume that all or nothing is initialized return; s_pDefCollapsed = new Image(StockImage::Yes, RID_BMP_TREENODE_COLLAPSED); s_pDefExpanded = new Image(StockImage::Yes, RID_BMP_TREENODE_EXPANDED); } const Image& SvImpLBox::GetDefaultExpandedNodeImage( ) { implInitDefaultNodeImages(); return *s_pDefExpanded; } const Image& SvImpLBox::GetDefaultCollapsedNodeImage( ) { implInitDefaultNodeImages(); return *s_pDefCollapsed; } void SvImpLBox::CallEventListeners( VclEventId nEvent, void* pData ) { if ( m_pView ) m_pView->CallImplEventListeners( nEvent, pData); } bool SvImpLBox::IsSelectable( const SvTreeListEntry* pEntry ) const { if( pEntry ) { SvViewDataEntry* pViewDataNewCur = m_pView->GetViewDataEntry(pEntry); return (pViewDataNewCur == nullptr) || pViewDataNewCur->IsSelectable(); } else { return false; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */