/* -*- 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 "bitmaps.hlst" #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) : aHorSBar(VclPtr::Create(pLBView, WB_DRAG | WB_HSCROLL)) , aScrBarBox(VclPtr::Create(pLBView)) , aFctSet(this, pLBView) , bAreChildrenTransient(true) , mbForceMakeVisible (false) , m_pStringSorter(nullptr) , aVerSBar(VclPtr::Create(pLBView, WB_DRAG | WB_VSCROLL)) , aOutputSize(0, 0) , mbNoAutoCurEntry(false) , aSelEng(pLBView, nullptr) , nNextVerVisSize(0) { osl_atomic_increment(&s_nImageRefCount); pView = pLBView; pTree = pLBTree; aSelEng.SetFunctionSet( static_cast(&aFctSet) ); aSelEng.ExpandSelectionOnMouseMove( false ); SetStyle( nWinStyle ); SetSelectionMode( SelectionMode::Single ); SetDragDropMode( DragDropMode::NONE ); aVerSBar->SetScrollHdl( LINK( this, SvImpLBox, ScrollUpDownHdl ) ); aHorSBar->SetScrollHdl( LINK( this, SvImpLBox, ScrollLeftRightHdl ) ); aHorSBar->SetEndScrollHdl( LINK( this, SvImpLBox, EndScrollHdl ) ); aVerSBar->SetEndScrollHdl( LINK( this, SvImpLBox, EndScrollHdl ) ); aVerSBar->SetRange( Range(0,0) ); aVerSBar->Hide(); aHorSBar->SetRange( Range(0,0) ); aHorSBar->SetPageSize( 24 ); // pixels aHorSBar->SetLineSize( 8 ); // pixels nHorSBarHeight = (short)aHorSBar->GetSizePixel().Height(); nVerSBarWidth = (short)aVerSBar->GetSizePixel().Width(); pStartEntry = nullptr; pCursor = nullptr; pAnchor = nullptr; nVisibleCount = 0; // number of rows of data in control nNodeBmpTabDistance = NODE_BMP_TABDIST_NOTVALID; nNodeBmpWidth = 0; bAsyncBeginDrag = false; aAsyncBeginDragIdle.SetPriority( TaskPriority::HIGHEST ); aAsyncBeginDragIdle.SetInvokeHandler( LINK(this,SvImpLBox,BeginDragHdl)); // button animation in listbox pActiveButton = nullptr; pActiveEntry = nullptr; pActiveTab = nullptr; nFlags = LBoxFlags::NONE; nCurTabPos = FIRST_ENTRY_TAB; aEditIdle.SetPriority( TaskPriority::LOWEST ); aEditIdle.SetInvokeHandler( LINK(this,SvImpLBox,EditTimerCall) ); nMostRight = -1; pMostRightEntry = nullptr; nCurUserEvent = nullptr; bUpdateMode = true; bInVScrollHdl = false; nFlags |= LBoxFlags::Filling; bSubLstOpRet = bSubLstOpLR = bContextMenuHandling = bIsCellFocusEnabled = false; } SvImpLBox::~SvImpLBox() { aEditIdle.Stop(); StopUserEvent(); delete m_pStringSorter; if ( osl_atomic_decrement(&s_nImageRefCount) == 0 ) { DELETEZ(s_pDefCollapsed); DELETEZ(s_pDefExpanded); } aVerSBar.disposeAndClear(); aHorSBar.disposeAndClear(); 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 ) { delete m_pStringSorter; m_pStringSorter = nullptr; } } if( !m_pStringSorter ) { m_pStringSorter = new comphelper::string::NaturalStringSorter( ::comphelper::getProcessComponentContext(), rNewLocale); } } short SvImpLBox::UpdateContextBmpWidthVector( SvTreeListEntry const * pEntry, short nWidth ) { DBG_ASSERT( pView->pModel, "View and Model aren't valid!" ); sal_uInt16 nDepth = pView->pModel->GetDepth( pEntry ); // initialize vector if necessary std::vector< short >::size_type nSize = aContextBmpWidthVector.size(); while ( nDepth > nSize ) { aContextBmpWidthVector.resize( nSize + 1 ); aContextBmpWidthVector.at( nSize ) = nWidth; ++nSize; } if( aContextBmpWidthVector.size() == nDepth ) { aContextBmpWidthVector.resize( nDepth + 1 ); aContextBmpWidthVector.at( nDepth ) = 0; } short nContextBmpWidth = aContextBmpWidthVector[ nDepth ]; if( nContextBmpWidth < nWidth ) { 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 = (short)pBmpItem->GetBitmap1().GetSizePixel().Width(); short nColWidth = (short)pBmpItem->GetBitmap2().GetSizePixel().Width(); short nMax = std::max(nExpWidth, nColWidth); UpdateContextBmpWidthVector( pEntry, nMax ); if( pEntry->HasChildren() ) // recursive call, whether expanded or not { SvTreeListEntry* pChild = pView->FirstChild( pEntry ); DBG_ASSERT( pChild, "The first child is invalid!" ); do { UpdateContextBmpWidthVectorFromMovedEntry( pChild ); pChild = pView->Next( pChild ); } while ( pChild ); } } void SvImpLBox::UpdateContextBmpWidthMax( SvTreeListEntry const * pEntry ) { sal_uInt16 nDepth = pView->pModel->GetDepth( pEntry ); if( aContextBmpWidthVector.size() < 1 ) return; short nWidth = aContextBmpWidthVector[ nDepth ]; if( nWidth != pView->nContextBmpWidthMax ) { pView->nContextBmpWidthMax = nWidth; nFlags |= LBoxFlags::IgnoreChangedTabs; pView->SetTabs(); nFlags &= ~LBoxFlags::IgnoreChangedTabs; } } void SvImpLBox::CalcCellFocusRect( SvTreeListEntry const * pEntry, tools::Rectangle& rRect ) { if ( pEntry && bIsCellFocusEnabled ) { if ( nCurTabPos > FIRST_ENTRY_TAB ) { SvLBoxItem& rItem = pCursor->GetItem( nCurTabPos ); rRect.Left() = pView->GetTab( pCursor, &rItem )->GetPos(); } if (pCursor->ItemCount() > static_cast(nCurTabPos+1)) { SvLBoxItem& rNextItem = pCursor->GetItem( nCurTabPos + 1 ); long nRight = pView->GetTab( pCursor, &rNextItem )->GetPos() - 1; if ( nRight < rRect.Right() ) rRect.Right() = nRight; } } } void SvImpLBox::SetStyle( WinBits i_nWinStyle ) { m_nStyle = i_nWinStyle; if ( ( m_nStyle & WB_SIMPLEMODE) && ( aSelEng.GetSelectionMode() == SelectionMode::Multiple ) ) aSelEng.AddAlways( true ); } void SvImpLBox::SetNoAutoCurEntry( bool b ) { mbNoAutoCurEntry = b; } // don't touch the model any more void SvImpLBox::Clear() { StopUserEvent(); pStartEntry = nullptr; pAnchor = nullptr; pActiveButton = nullptr; pActiveEntry = nullptr; pActiveTab = nullptr; nMostRight = -1; pMostRightEntry = nullptr; // don't touch the cursor any more if( pCursor ) { if( pView->HasFocus() ) pView->HideFocus(); pCursor = nullptr; } aVerSBar->Hide(); aVerSBar->SetThumbPos( 0 ); Range aRange( 0, 0 ); aVerSBar->SetRange( aRange ); aOutputSize = pView->Control::GetOutputSizePixel(); aHorSBar->Hide(); aHorSBar->SetThumbPos( 0 ); MapMode aMapMode( pView->GetMapMode()); aMapMode.SetOrigin( Point(0,0) ); pView->Control::SetMapMode( aMapMode ); aHorSBar->SetRange( aRange ); aHorSBar->SetSizePixel(Size(aOutputSize.Width(),nHorSBarHeight)); pView->SetClipRegion(); if( GetUpdateMode() ) pView->Invalidate( GetVisibleArea() ); nFlags |= LBoxFlags::Filling; if( !aHorSBar->IsVisible() && !aVerSBar->IsVisible() ) aScrBarBox->Hide(); aContextBmpWidthVector.clear(); CallEventListeners( VclEventId::ListboxItemRemoved ); } // ********************************************************************* // Paint, navigate, scroll // ********************************************************************* IMPL_LINK_NOARG(SvImpLBox, EndScrollHdl, ScrollBar*, void) { if( nFlags & LBoxFlags::EndScrollSetVisSize ) { aVerSBar->SetVisibleSize( nNextVerVisSize ); nFlags &= ~LBoxFlags::EndScrollSetVisSize; } EndScroll(); } // handler for vertical scrollbar IMPL_LINK( SvImpLBox, ScrollUpDownHdl, ScrollBar *, pScrollBar, void ) { DBG_ASSERT(!bInVScrollHdl,"Scroll handler out-paces itself!"); long nDelta = pScrollBar->GetDelta(); if( !nDelta ) return; nFlags &= (~LBoxFlags::Filling); bInVScrollHdl = true; if( pView->IsEditingActive() ) { pView->EndEditing( true ); // Cancel pView->Update(); } BeginScroll(); if( nDelta > 0 ) { if( nDelta == 1 ) CursorDown(); else PageDown( (sal_uInt16) nDelta ); } else { nDelta *= (-1); if( nDelta == 1 ) CursorUp(); else PageUp( (sal_uInt16) nDelta ); } bInVScrollHdl = false; } void SvImpLBox::CursorDown() { if (!pStartEntry) return; SvTreeListEntry* pNextFirstToDraw = pView->NextVisible(pStartEntry); if( pNextFirstToDraw ) { nFlags &= (~LBoxFlags::Filling); ShowCursor( false ); pView->Update(); pStartEntry = pNextFirstToDraw; tools::Rectangle aArea( GetVisibleArea() ); pView->Scroll( 0, -(pView->GetEntryHeight()), aArea, ScrollFlags::NoChildren ); pView->Update(); ShowCursor( true ); pView->NotifyScrolled(); } } void SvImpLBox::CursorUp() { if (!pStartEntry) return; SvTreeListEntry* pPrevFirstToDraw = pView->PrevVisible(pStartEntry); if( pPrevFirstToDraw ) { nFlags &= (~LBoxFlags::Filling); long nEntryHeight = pView->GetEntryHeight(); ShowCursor( false ); pView->Update(); pStartEntry = pPrevFirstToDraw; tools::Rectangle aArea( GetVisibleArea() ); aArea.Bottom() -= nEntryHeight; pView->Scroll( 0, nEntryHeight, aArea, ScrollFlags::NoChildren ); pView->Update(); ShowCursor( true ); pView->NotifyScrolled(); } } void SvImpLBox::PageDown( sal_uInt16 nDelta ) { sal_uInt16 nRealDelta = nDelta; if( !nDelta ) return; if (!pStartEntry) return; SvTreeListEntry* pNext = pView->NextVisible(pStartEntry, nRealDelta); if( pNext == pStartEntry ) return; ShowCursor( false ); nFlags &= (~LBoxFlags::Filling); pView->Update(); pStartEntry = pNext; if( nRealDelta >= nVisibleCount ) { pView->Invalidate( GetVisibleArea() ); pView->Update(); } else { tools::Rectangle aArea( GetVisibleArea() ); long nScroll = pView->GetEntryHeight() * static_cast(nRealDelta); nScroll = -nScroll; pView->Update(); pView->Scroll( 0, nScroll, aArea, ScrollFlags::NoChildren ); pView->Update(); pView->NotifyScrolled(); } ShowCursor( true ); } void SvImpLBox::PageUp( sal_uInt16 nDelta ) { sal_uInt16 nRealDelta = nDelta; if( !nDelta ) return; if (!pStartEntry) return; SvTreeListEntry* pPrev = pView->PrevVisible(pStartEntry, nRealDelta); if( pPrev == pStartEntry ) return; nFlags &= (~LBoxFlags::Filling); ShowCursor( false ); pView->Update(); pStartEntry = pPrev; if( nRealDelta >= nVisibleCount ) { pView->Invalidate( GetVisibleArea() ); pView->Update(); } else { long nEntryHeight = pView->GetEntryHeight(); tools::Rectangle aArea( GetVisibleArea() ); pView->Update(); pView->Scroll( 0, nEntryHeight*nRealDelta, aArea, ScrollFlags::NoChildren ); pView->Update(); pView->NotifyScrolled(); } ShowCursor( true ); } void SvImpLBox::KeyUp( bool bPageUp ) { if( !aVerSBar->IsVisible() ) return; long nDelta; if( bPageUp ) nDelta = aVerSBar->GetPageSize(); else nDelta = 1; long nThumbPos = aVerSBar->GetThumbPos(); if( nThumbPos < nDelta ) nDelta = nThumbPos; if( nDelta <= 0 ) return; nFlags &= (~LBoxFlags::Filling); BeginScroll(); aVerSBar->SetThumbPos( nThumbPos - nDelta ); if( bPageUp ) PageUp( (short)nDelta ); else CursorUp(); EndScroll(); } void SvImpLBox::KeyDown( bool bPageDown ) { if( !aVerSBar->IsVisible() ) return; long nDelta; if( bPageDown ) nDelta = aVerSBar->GetPageSize(); else nDelta = 1; long nThumbPos = aVerSBar->GetThumbPos(); long nVisibleSize = aVerSBar->GetVisibleSize(); long nRange = aVerSBar->GetRange().Len(); long nTmp = nThumbPos+nVisibleSize; while( (nDelta > 0) && (nTmp+nDelta) >= nRange ) nDelta--; if( nDelta <= 0 ) return; nFlags &= (~LBoxFlags::Filling); BeginScroll(); aVerSBar->SetThumbPos( nThumbPos+nDelta ); if( bPageDown ) PageDown( (short)nDelta ); else CursorDown(); EndScroll(); } void SvImpLBox::InvalidateEntriesFrom( long nY ) const { if( !(nFlags & LBoxFlags::InPaint )) { tools::Rectangle aRect( GetVisibleArea() ); aRect.Top() = nY; pView->Invalidate( aRect ); } } void SvImpLBox::InvalidateEntry( long nY ) const { if( !(nFlags & LBoxFlags::InPaint )) { tools::Rectangle aRect( GetVisibleArea() ); long nMaxBottom = aRect.Bottom(); aRect.Top() = nY; aRect.Bottom() = nY; aRect.Bottom() += pView->GetEntryHeight(); if( aRect.Top() > nMaxBottom ) return; if( aRect.Bottom() > nMaxBottom ) aRect.Bottom() = nMaxBottom; pView->Invalidate( aRect ); } } void SvImpLBox::InvalidateEntry( SvTreeListEntry* pEntry ) { if( GetUpdateMode() ) { long nPrev = nMostRight; SetMostRight( pEntry ); if( nPrev < nMostRight ) ShowVerSBar(); } if( !(nFlags & LBoxFlags::InPaint )) { bool bHasFocusRect = false; if( pEntry==pCursor && pView->HasFocus() ) { bHasFocusRect = true; ShowCursor( false ); } InvalidateEntry( GetEntryLine( pEntry ) ); if( bHasFocusRect ) ShowCursor( true ); } } void SvImpLBox::RecalcFocusRect() { if( pView->HasFocus() && pCursor ) { pView->HideFocus(); long nY = GetEntryLine( pCursor ); tools::Rectangle aRect = pView->GetFocusRect( pCursor, nY ); CalcCellFocusRect( pCursor, aRect ); vcl::Region aOldClip( pView->GetClipRegion()); vcl::Region aClipRegion( GetClipRegionRect() ); pView->SetClipRegion( aClipRegion ); pView->ShowFocus( aRect ); pView->SetClipRegion( aOldClip ); } } // Sets cursor. When using SingleSelection, the selection is adjusted. void SvImpLBox::SetCursor( SvTreeListEntry* pEntry, bool bForceNoSelect ) { SvViewDataEntry* pViewDataNewCur = nullptr; if( pEntry ) pViewDataNewCur= pView->GetViewDataEntry(pEntry); if( pEntry && pEntry == 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 = pView->NextVisible(pEntry); pViewDataNewCur = pEntry ? pView->GetViewDataEntry(pEntry) : nullptr; } SvTreeListEntry* pOldCursor = pCursor; if( pCursor && pEntry != pCursor ) { pView->SetEntryFocus( pCursor, false ); if( bSimpleTravel ) pView->Select( pCursor, false ); pView->HideFocus(); } pCursor = pEntry; if( pCursor ) { if (pViewDataNewCur) pViewDataNewCur->SetFocus( true ); if(!bForceNoSelect && bSimpleTravel && !(nFlags & LBoxFlags::DeselectAll) && GetUpdateMode()) { pView->Select( pCursor ); CallEventListeners( VclEventId::ListboxTreeFocus, pCursor ); } // multiple selection: select in cursor move if we're not in // Add mode (Ctrl-F8) else if( GetUpdateMode() && pView->GetSelectionMode() == SelectionMode::Multiple && !(nFlags & LBoxFlags::DeselectAll) && !aSelEng.IsAddMode() && !bForceNoSelect ) { pView->Select( pCursor ); CallEventListeners( VclEventId::ListboxTreeFocus, pCursor ); } else { ShowCursor( true ); if (bForceNoSelect && GetUpdateMode()) { CallEventListeners( VclEventId::ListboxTreeFocus, pCursor); } } if( pAnchor ) { DBG_ASSERT(aSelEng.GetSelectionMode() != SelectionMode::Single,"Mode?"); SetAnchorSelection( pOldCursor, pCursor ); } } nFlags &= (~LBoxFlags::DeselectAll); pView->OnCurrentEntryChanged(); } void SvImpLBox::ShowCursor( bool bShow ) { if( !bShow || !pCursor || !pView->HasFocus() ) { vcl::Region aOldClip( pView->GetClipRegion()); vcl::Region aClipRegion( GetClipRegionRect() ); pView->SetClipRegion( aClipRegion ); pView->HideFocus(); pView->SetClipRegion( aOldClip ); } else { long nY = GetEntryLine( pCursor ); tools::Rectangle aRect = pView->GetFocusRect( pCursor, nY ); CalcCellFocusRect( pCursor, aRect ); vcl::Region aOldClip( pView->GetClipRegion()); vcl::Region aClipRegion( GetClipRegionRect() ); pView->SetClipRegion( aClipRegion ); pView->ShowFocus( aRect ); pView->SetClipRegion( aOldClip ); } } void SvImpLBox::UpdateAll( bool bInvalidateCompleteView ) { FindMostRight(nullptr); aVerSBar->SetRange( Range(0, pView->GetVisibleCount()-1 ) ); SyncVerThumb(); FillView(); ShowVerSBar(); if( bSimpleTravel && pCursor && pView->HasFocus() ) pView->Select( pCursor ); ShowCursor( true ); if( bInvalidateCompleteView ) pView->Invalidate(); else pView->Invalidate( GetVisibleArea() ); } IMPL_LINK( SvImpLBox, ScrollLeftRightHdl, ScrollBar *, pScrollBar, void ) { long nDelta = pScrollBar->GetDelta(); if( nDelta ) { if( pView->IsEditingActive() ) { pView->EndEditing( true ); // Cancel pView->Update(); } pView->nFocusWidth = -1; KeyLeftRight( nDelta ); } } void SvImpLBox::KeyLeftRight( long nDelta ) { if( !(nFlags & LBoxFlags::InResize) ) pView->Update(); BeginScroll(); nFlags &= (~LBoxFlags::Filling); ShowCursor( false ); // neuen Origin berechnen long nPos = aHorSBar->GetThumbPos(); Point aOrigin( -nPos, 0 ); MapMode aMapMode( pView->GetMapMode() ); aMapMode.SetOrigin( aOrigin ); pView->SetMapMode( aMapMode ); if( !(nFlags & LBoxFlags::InResize) ) { tools::Rectangle aRect( GetVisibleArea() ); pView->Scroll( -nDelta, 0, aRect, ScrollFlags::NoChildren ); } else pView->Invalidate(); RecalcFocusRect(); ShowCursor( true ); pView->NotifyScrolled(); } // returns the last entry if position is just past the last entry SvTreeListEntry* SvImpLBox::GetClickedEntry( const Point& rPoint ) const { DBG_ASSERT( pView->GetModel(), "SvImpLBox::GetClickedEntry: how can this ever happen? Please tell me (frank.schoenheit@sun.com) how to reproduce!" ); if ( !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( pView->GetEntryCount() == 0 || !pStartEntry || !pView->GetEntryHeight()) return nullptr; sal_uInt16 nClickedEntry = (sal_uInt16)(rPoint.Y() / pView->GetEntryHeight() ); sal_uInt16 nTemp = nClickedEntry; SvTreeListEntry* pEntry = pView->NextVisible(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, 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( pView->GetFocusRect( pEntry, nLine )); aRect.Right() = GetOutputSize().Width() - pView->GetMapMode().GetOrigin().X(); SvLBoxContextBmp* pBmp = static_cast(pEntry->GetFirstItem(SvLBoxItemType::ContextBmp)); aRect.Left() -= pBmp->GetSize(pView,pEntry).Width(); aRect.Left() -= 4; // a little tolerance Point aPos( rPosPixel ); aPos -= pView->GetMapMode().GetOrigin(); bRet = aRect.IsInside( aPos ); return bRet; } // returns 0 if position is just past the last entry SvTreeListEntry* SvImpLBox::GetEntry( const Point& rPoint ) const { if( (pView->GetEntryCount() == 0) || !pStartEntry || (rPoint.Y() > aOutputSize.Height()) || !pView->GetEntryHeight()) return nullptr; sal_uInt16 nClickedEntry = (sal_uInt16)(rPoint.Y() / pView->GetEntryHeight() ); sal_uInt16 nTemp = nClickedEntry; SvTreeListEntry* pEntry = pView->NextVisible(pStartEntry, nTemp); if( nTemp != nClickedEntry ) pEntry = nullptr; return pEntry; } SvTreeListEntry* SvImpLBox::MakePointVisible(const Point& rPoint) { if( !pCursor ) return nullptr; long nY = rPoint.Y(); SvTreeListEntry* pEntry = nullptr; long nMax = aOutputSize.Height(); if( nY < 0 || nY >= nMax ) // aOutputSize.Height() ) { if( nY < 0 ) pEntry = pView->PrevVisible(pCursor); else pEntry = pView->NextVisible(pCursor); if( pEntry && pEntry != pCursor ) pView->SetEntryFocus( 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 = pView->NextVisible(pStartEntry, nSteps); } if( pEntry ) { if( pEntry != pCursor && aSelEng.GetSelectionMode() == SelectionMode::Single ) pView->Select( pCursor, false ); } } return pEntry; } tools::Rectangle SvImpLBox::GetClipRegionRect() const { Point aOrigin( pView->GetMapMode().GetOrigin() ); aOrigin.X() *= -1; // conversion document coordinates tools::Rectangle aClipRect( aOrigin, aOutputSize ); aClipRect.Bottom()++; return aClipRect; } void SvImpLBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) { if (!pView->GetVisibleCount()) return; nFlags |= LBoxFlags::InPaint; if (nFlags & LBoxFlags::Filling) { SvTreeListEntry* pFirst = pView->First(); if (pFirst != pStartEntry) { ShowCursor(false); pStartEntry = pView->First(); aVerSBar->SetThumbPos( 0 ); StopUserEvent(); ShowCursor(true); nCurUserEvent = Application::PostUserEvent(LINK(this, SvImpLBox, MyUserEvent), reinterpret_cast(1)); return; } } if (!pStartEntry) { pStartEntry = pView->First(); } if (nNodeBmpTabDistance == NODE_BMP_TABDIST_NOTVALID) SetNodeBmpTabDistance(); long nRectHeight = rRect.GetHeight(); long nEntryHeight = 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 long nY = nStartLine * nEntryHeight; SvTreeListEntry* pEntry = pStartEntry; while (nStartLine && pEntry) { pEntry = pView->NextVisible(pEntry); nStartLine--; } vcl::Region aClipRegion(GetClipRegionRect()); // first draw the lines, then clip them! rRenderContext.SetClipRegion(); if (m_nStyle & (WB_HASLINES | WB_HASLINESATROOT)) DrawNet(rRenderContext); rRenderContext.SetClipRegion(aClipRegion); if (!pCursor && !mbNoAutoCurEntry) { // do not select if multiselection or explicit set bool bNotSelect = (aSelEng.GetSelectionMode() == SelectionMode::Multiple ) || ((m_nStyle & WB_NOINITIALSELECTION) == WB_NOINITIALSELECTION); SetCursor(pStartEntry, bNotSelect); } for(sal_uInt16 n=0; n< nCount && pEntry; n++) { /*long nMaxRight=*/ pView->PaintEntry1(*pEntry, nY, rRenderContext ); nY += nEntryHeight; pEntry = pView->NextVisible(pEntry); } nFlags &= (~LBoxFlags::DeselectAll); rRenderContext.SetClipRegion(); nFlags &= (~LBoxFlags::InPaint); } void SvImpLBox::MakeVisible( SvTreeListEntry* pEntry, bool bMoveToTop ) { if( !pEntry ) return; bool bInView = IsEntryInView( pEntry ); if( bInView && (!bMoveToTop || pStartEntry == pEntry) ) return; // is already visible if( pStartEntry || mbForceMakeVisible ) nFlags &= (~LBoxFlags::Filling); if( !bInView ) { if( !pView->IsEntryVisible(pEntry) ) // Parent(s) collapsed? { SvTreeListEntry* pParent = pView->GetParent( pEntry ); while( pParent ) { if( !pView->IsExpanded( pParent ) ) { bool bRet = pView->Expand( pParent ); DBG_ASSERT(bRet,"Not expanded!"); } pParent = 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 } } pStartEntry = pEntry; ShowCursor( false ); FillView(); aVerSBar->SetThumbPos( (long)(pView->GetVisiblePos( pStartEntry )) ); ShowCursor( true ); pView->Invalidate(); } void SvImpLBox::ScrollToAbsPos( long nPos ) { if( pView->GetVisibleCount() == 0 ) return; long nLastEntryPos = pView->GetAbsPos( pView->Last() ); if( nPos < 0 ) nPos = 0; else if( nPos > nLastEntryPos ) nPos = nLastEntryPos; SvTreeListEntry* pEntry = pView->GetEntryAtAbsPos( nPos ); if( !pEntry || pEntry == pStartEntry ) return; if( pStartEntry || mbForceMakeVisible ) nFlags &= (~LBoxFlags::Filling); if( pView->IsEntryVisible(pEntry) ) { pStartEntry = pEntry; ShowCursor( false ); aVerSBar->SetThumbPos( nPos ); ShowCursor( true ); if (GetUpdateMode()) pView->Invalidate(); } } void SvImpLBox::DrawNet(vcl::RenderContext& rRenderContext) { if (pView->GetVisibleCount() < 2 && !pStartEntry->HasChildrenOnDemand() && !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; } } long nEntryHeight = pView->GetEntryHeight(); long nEntryHeightDIV2 = nEntryHeight / 2; if( nEntryHeightDIV2 && !(nEntryHeight & 0x0001)) nEntryHeightDIV2--; SvTreeListEntry* pChild; SvTreeListEntry* pEntry = pStartEntry; SvLBoxTab* pFirstDynamicTab = pView->GetFirstDynamicTab(); while (pTree->GetDepth( pEntry ) > 0) { pEntry = pView->GetParent(pEntry); } sal_uInt16 nOffs = static_cast(pView->GetVisiblePos(pStartEntry) - pView->GetVisiblePos(pEntry)); long nY = 0; nY -= (nOffs * nEntryHeight); DBG_ASSERT(pFirstDynamicTab,"No Tree!"); rRenderContext.Push(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 = nVisibleCount + nOffs + 1; const Image& rExpandedNodeBitmap = GetExpandedNodeBmp(); for (sal_uLong n=0; n< nMax && pEntry; n++) { if (pView->IsExpanded(pEntry)) { aPos1.X() = pView->GetTabPos(pEntry, pFirstDynamicTab); // if it is not a context bitmap, go a little to the right below the // first text (node bitmap, too) if (!pView->nContextBmpWidthMax) aPos1.X() += rExpandedNodeBitmap.GetSizePixel().Width() / 2; aPos1.Y() = nY; aPos1.Y() += nEntryHeightDIV2; pChild = pView->FirstChild( pEntry ); DBG_ASSERT(pChild,"Child?"); pChild = SvTreeList::LastSibling( pChild ); nDistance = static_cast(pView->GetVisiblePos(pChild) - pView->GetVisiblePos(pEntry)); aPos2 = aPos1; aPos2.Y() += nDistance * nEntryHeight; rRenderContext.DrawLine(aPos1, aPos2); } // visible in control? if (n >= nOffs && ((m_nStyle & WB_HASLINESATROOT) || !pTree->IsAtRootDepth(pEntry))) { // can we recycle aPos1? if (!pView->IsExpanded(pEntry)) { // nope aPos1.X() = pView->GetTabPos(pEntry, pFirstDynamicTab); // if it is not a context bitmap, go a little to the right below // the first text (node bitmap, too) if (!pView->nContextBmpWidthMax) aPos1.X() += rExpandedNodeBitmap.GetSizePixel().Width() / 2; aPos1.Y() = nY; aPos1.Y() += nEntryHeightDIV2; aPos2.X() = aPos1.X(); } aPos2.Y() = aPos1.Y(); aPos2.X() -= pView->GetIndent(); rRenderContext.DrawLine(aPos1, aPos2); } nY += nEntryHeight; pEntry = pView->NextVisible(pEntry); } if (m_nStyle & WB_HASLINESATROOT) { pEntry = pView->First(); aPos1.X() = pView->GetTabPos(pEntry, pFirstDynamicTab); // if it is not a context bitmap, go a little to the right below the // first text (node bitmap, too) if (!pView->nContextBmpWidthMax) aPos1.X() += rExpandedNodeBitmap.GetSizePixel().Width() / 2; aPos1.X() -= pView->GetIndent(); aPos1.Y() = GetEntryLine( pEntry ); aPos1.Y() += nEntryHeightDIV2; pChild = SvTreeList::LastSibling( pEntry ); aPos2.X() = aPos1.X(); aPos2.Y() = GetEntryLine( pChild ); aPos2.Y() += nEntryHeightDIV2; rRenderContext.DrawLine(aPos1, aPos2); } rRenderContext.Pop(); } void SvImpLBox::PositionScrollBars( Size& rSize, sal_uInt16 nMask ) { long nOverlap = 0; Size aVerSize( nVerSBarWidth, rSize.Height() ); Size aHorSize( rSize.Width(), nHorSBarHeight ); if( nMask & 0x0001 ) aHorSize.Width() -= nVerSBarWidth; if( nMask & 0x0002 ) aVerSize.Height() -= nHorSBarHeight; aVerSize.Height() += 2 * nOverlap; Point aVerPos( rSize.Width() - aVerSize.Width() + nOverlap, -nOverlap ); aVerSBar->SetPosSizePixel( aVerPos, aVerSize ); aHorSize.Width() += 2 * nOverlap; Point aHorPos( -nOverlap, rSize.Height() - aHorSize.Height() + nOverlap ); aHorSBar->SetPosSizePixel( aHorPos, aHorSize ); if( nMask & 0x0001 ) rSize.Width() = aVerPos.X(); if( nMask & 0x0002 ) rSize.Height() = aHorPos.Y(); if( (nMask & (0x0001|0x0002)) == (0x0001|0x0002) ) aScrBarBox->Show(); else aScrBarBox->Hide(); } void SvImpLBox::AdjustScrollBars( Size& rSize ) { long nEntryHeight = pView->GetEntryHeight(); if( !nEntryHeight ) return; sal_uInt16 nResult = 0; Size aOSize( pView->Control::GetOutputSizePixel() ); const WinBits nWindowStyle = pView->GetStyle(); bool bVerSBar = ( nWindowStyle & WB_VSCROLL ) != 0; bool bHorBar = false; long nMaxRight = aOSize.Width(); //GetOutputSize().Width(); Point aOrigin( pView->GetMapMode().GetOrigin() ); aOrigin.X() *= -1; nMaxRight += aOrigin.X() - 1; long nVis = nMostRight - aOrigin.X(); if( (nWindowStyle & WB_HSCROLL) && (nVis < nMostRight || nMaxRight < nMostRight) ) { bHorBar = true; } // number of entries that are not collapsed sal_uLong nTotalCount = pView->GetVisibleCount(); // number of entries visible within the view nVisibleCount = aOSize.Height() / nEntryHeight; // do we need a vertical scrollbar? if( bVerSBar || nTotalCount > nVisibleCount ) { nResult = 1; nMaxRight -= nVerSBarWidth; if( !bHorBar ) { if( (nWindowStyle & WB_HSCROLL) && (nVis < nMostRight || nMaxRight < 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. nVisibleCount = (aOSize.Height() - nHorSBarHeight) / nEntryHeight; // we might actually need a vertical scrollbar now if( !(nResult & 0x0001) && ((nTotalCount > 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 ); aSelEng.SetVisibleArea( aRect ); // vertical scrollbar long nTemp = (long)nVisibleCount; nTemp--; if( nTemp != aVerSBar->GetVisibleSize() ) { if( !bInVScrollHdl ) { aVerSBar->SetPageSize( nTemp - 1 ); aVerSBar->SetVisibleSize( nTemp ); } else { nFlags |= LBoxFlags::EndScrollSetVisSize; nNextVerVisSize = nTemp; } } // horizontal scrollbar nTemp = aHorSBar->GetThumbPos(); aHorSBar->SetVisibleSize( aOSize.Width() ); long nNewThumbPos = aHorSBar->GetThumbPos(); Range aRange( aHorSBar->GetRange() ); if( aRange.Max() < nMostRight+25 ) { aRange.Max() = nMostRight+25; aHorSBar->SetRange( aRange ); } if( nTemp != nNewThumbPos ) { nTemp = nNewThumbPos - nTemp; if( pView->IsEditingActive() ) { pView->EndEditing( true ); // Cancel pView->Update(); } pView->nFocusWidth = -1; KeyLeftRight( nTemp ); } if( nResult & 0x0001 ) aVerSBar->Show(); else aVerSBar->Hide(); if( nResult & 0x0002 ) aHorSBar->Show(); else { aHorSBar->Hide(); } rSize = aOSize; } void SvImpLBox::InitScrollBarBox() { aScrBarBox->SetSizePixel( Size(nVerSBarWidth, nHorSBarHeight) ); Size aSize( pView->Control::GetOutputSizePixel() ); aScrBarBox->SetPosPixel( Point(aSize.Width()-nVerSBarWidth, aSize.Height()-nHorSBarHeight)); } void SvImpLBox::Resize() { aOutputSize = pView->Control::GetOutputSizePixel(); if( aOutputSize.Width() <= 0 || aOutputSize.Height() <= 0 ) return; nFlags |= LBoxFlags::InResize; InitScrollBarBox(); if( pView->GetEntryHeight()) { AdjustScrollBars( 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( aHorSBar->IsVisible()) aHorSBar->Invalidate(); if( aVerSBar->IsVisible()) aVerSBar->Invalidate(); nFlags &= ~LBoxFlags::InResize; } void SvImpLBox::FillView() { if( !pStartEntry ) { sal_uInt16 nVisibleViewCount = (sal_uInt16)(pView->GetVisibleCount()); sal_uInt16 nTempThumb = (sal_uInt16)aVerSBar->GetThumbPos(); if( nTempThumb >= nVisibleViewCount ) nTempThumb = nVisibleViewCount - 1; pStartEntry = pView->GetEntryAtVisPos(nTempThumb); } if( pStartEntry ) { sal_uInt16 nLast = (sal_uInt16)(pView->GetVisiblePos(pView->LastVisible())); sal_uInt16 nThumb = (sal_uInt16)(pView->GetVisiblePos( pStartEntry )); sal_uLong nCurDispEntries = nLast-nThumb+1; if( nCurDispEntries < nVisibleCount ) { ShowCursor( false ); // fill window by moving the thumb up incrementally bool bFound = false; SvTreeListEntry* pTemp = pStartEntry; while( nCurDispEntries < nVisibleCount && pTemp ) { pTemp = pView->PrevVisible(pStartEntry); if( pTemp ) { nThumb--; pStartEntry = pTemp; nCurDispEntries++; bFound = true; } } if( bFound ) { aVerSBar->SetThumbPos( nThumb ); ShowCursor( true ); // recalculate focus rectangle pView->Invalidate(); } } } } void SvImpLBox::ShowVerSBar() { bool bVerBar = ( pView->GetStyle() & WB_VSCROLL ) != 0; sal_uLong nVis = 0; if( !bVerBar ) nVis = pView->GetVisibleCount(); if( bVerBar || (nVisibleCount && nVis > (sal_uLong)(nVisibleCount-1)) ) { if( !aVerSBar->IsVisible() ) { pView->nFocusWidth = -1; AdjustScrollBars( aOutputSize ); if( GetUpdateMode() ) aVerSBar->Update(); } } else { if( aVerSBar->IsVisible() ) { pView->nFocusWidth = -1; AdjustScrollBars( aOutputSize ); } } long nMaxRight = GetOutputSize().Width(); Point aPos( pView->GetMapMode().GetOrigin() ); aPos.X() *= -1; // convert document coordinates nMaxRight = nMaxRight + aPos.X() - 1; if( nMaxRight < nMostRight ) { if( !aHorSBar->IsVisible() ) { pView->nFocusWidth = -1; AdjustScrollBars( aOutputSize ); if( GetUpdateMode() ) aHorSBar->Update(); } else { Range aRange( aHorSBar->GetRange() ); if( aRange.Max() < nMostRight+25 ) { aRange.Max() = nMostRight+25; aHorSBar->SetRange( aRange ); } else { pView->nFocusWidth = -1; AdjustScrollBars( aOutputSize ); } } } else { if( aHorSBar->IsVisible() ) { pView->nFocusWidth = -1; AdjustScrollBars( aOutputSize ); } } } void SvImpLBox::SyncVerThumb() { if( pStartEntry ) { long nEntryPos = pView->GetVisiblePos( pStartEntry ); aVerSBar->SetThumbPos( nEntryPos ); } else aVerSBar->SetThumbPos( 0 ); } bool SvImpLBox::IsEntryInView( SvTreeListEntry* pEntry ) const { // parent collapsed if( !pView->IsEntryVisible(pEntry) ) return false; long nY = GetEntryLine( pEntry ); if( nY < 0 ) return false; long nMax = nVisibleCount * pView->GetEntryHeight(); return nY < nMax; } long SvImpLBox::GetEntryLine( SvTreeListEntry* pEntry ) const { if(!pStartEntry ) return -1; // invisible position long nFirstVisPos = pView->GetVisiblePos( pStartEntry ); long nEntryVisPos = pView->GetVisiblePos( pEntry ); nFirstVisPos = nEntryVisPos - nFirstVisPos; nFirstVisPos *= pView->GetEntryHeight(); return nFirstVisPos; } void SvImpLBox::SetEntryHeight() { SetNodeBmpWidth( GetExpandedNodeBmp() ); SetNodeBmpWidth( GetCollapsedNodeBmp() ); if(!pView->HasViewData()) // are we within the Clear? { Size aSize = pView->Control::GetOutputSizePixel(); AdjustScrollBars( aSize ); } else { Resize(); if( GetUpdateMode() ) pView->Invalidate(); } } // *********************************************************************** // Callback Functions // *********************************************************************** void SvImpLBox::EntryExpanded( SvTreeListEntry* pEntry ) { // SelAllDestrAnch( false, true ); //DeselectAll(); if( GetUpdateMode() ) { ShowCursor( false ); long nY = GetEntryLine( pEntry ); if( IsLineVisible(nY) ) { InvalidateEntriesFrom( nY ); FindMostRight( pEntry, nullptr ); } aVerSBar->SetRange( Range(0, 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( !pView->IsEntryVisible( pEntry ) ) return; ShowCursor( false ); if( !pMostRightEntry || pTree->IsChild( pEntry,pMostRightEntry ) ) { FindMostRight(nullptr); } if( pStartEntry ) { long nOldThumbPos = aVerSBar->GetThumbPos(); sal_uLong nVisList = pView->GetVisibleCount(); aVerSBar->SetRange( Range(0, nVisList-1) ); long nNewThumbPos = aVerSBar->GetThumbPos(); if( nNewThumbPos != nOldThumbPos ) { pStartEntry = pView->First(); sal_uInt16 nDistance = (sal_uInt16)nNewThumbPos; if( nDistance ) pStartEntry = pView->NextVisible(pStartEntry, nDistance); if( GetUpdateMode() ) pView->Invalidate(); } else SyncVerThumb(); ShowVerSBar(); } // has the cursor been collapsed? if( pTree->IsChild( pEntry, pCursor ) ) SetCursor( pEntry ); if( GetUpdateMode() ) ShowVerSBar(); ShowCursor( true ); if( GetUpdateMode() && pCursor ) pView->Select( pCursor ); } void SvImpLBox::CollapsingEntry( SvTreeListEntry* pEntry ) { if( !pView->IsEntryVisible( pEntry ) || !pStartEntry ) return; SelAllDestrAnch( false ); // deselect all // is the collapsed cursor visible? long nY = GetEntryLine( pEntry ); if( IsLineVisible(nY) ) { if( GetUpdateMode() ) InvalidateEntriesFrom( nY ); } else { if( pTree->IsChild(pEntry, pStartEntry) ) { pStartEntry = pEntry; if( GetUpdateMode() ) pView->Invalidate(); } } } void SvImpLBox::SetNodeBmpWidth( const Image& rBmp ) { const Size aSize( rBmp.GetSizePixel() ); nNodeBmpWidth = aSize.Width(); } void SvImpLBox::SetNodeBmpTabDistance() { nNodeBmpTabDistance = -pView->GetIndent(); if( pView->nContextBmpWidthMax ) { // only if the first dynamic tab is centered (we currently assume that) Size aSize = GetExpandedNodeBmp().GetSizePixel(); nNodeBmpTabDistance -= aSize.Width() / 2; } } // corrects the cursor when using SingleSelection void SvImpLBox::EntrySelected( SvTreeListEntry* pEntry, bool bSelect ) { if( nFlags & LBoxFlags::IgnoreSelect ) return; nFlags &= (~LBoxFlags::DeselectAll); if( bSelect && aSelEng.GetSelectionMode() == SelectionMode::Single && pEntry != pCursor ) { SetCursor( pEntry ); DBG_ASSERT(pView->GetSelectionCount()==1,"selection count?"); } if( GetUpdateMode() && pView->IsEntryVisible(pEntry) ) { long nY = GetEntryLine( pEntry ); if( IsLineVisible( nY ) ) { ShowCursor(false); InvalidateEntry(pEntry); ShowCursor(true); } } } void SvImpLBox::RemovingEntry( SvTreeListEntry* pEntry ) { CallEventListeners( VclEventId::ListboxItemRemoved , pEntry ); DestroyAnchor(); if( !pView->IsEntryVisible( pEntry ) ) { // if parent is collapsed => bye! nFlags |= LBoxFlags::RemovedEntryInvisible; return; } if( pEntry == pMostRightEntry || ( pEntry->HasChildren() && pView->IsExpanded(pEntry) && pTree->IsChild(pEntry, pMostRightEntry))) { nFlags |= LBoxFlags::RemovedRecalcMostRight; } SvTreeListEntry* pOldStartEntry = pStartEntry; SvTreeListEntry* pParent = pView->GetModel()->GetParent(pEntry); if (pParent && pView->GetModel()->GetChildList(pParent).size() == 1) { DBG_ASSERT( pView->IsExpanded( pParent ), "Parent not expanded"); pParent->SetFlags( pParent->GetFlags() | SvTLEntryFlags::NO_NODEBMP); InvalidateEntry( pParent ); } if( pCursor && pTree->IsChild( pEntry, pCursor) ) pCursor = pEntry; if( pStartEntry && pTree->IsChild(pEntry,pStartEntry) ) pStartEntry = pEntry; SvTreeListEntry* pTemp; if( pCursor && pCursor == pEntry ) { if( bSimpleTravel ) pView->Select( pCursor, false ); ShowCursor( false ); // focus rectangle gone // NextSibling, because we also delete the children of the cursor pTemp = SvTreeListBox::NextSibling( pCursor ); if( !pTemp ) pTemp = pView->PrevVisible(pCursor); SetCursor( pTemp, true ); } if( pStartEntry && pStartEntry == pEntry ) { pTemp = SvTreeListBox::NextSibling( pStartEntry ); if( !pTemp ) pTemp = pView->PrevVisible(pStartEntry); 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( pStartEntry && (pStartEntry != pOldStartEntry || pEntry == pView->GetModel()->Last()) ) { aVerSBar->SetThumbPos( pView->GetVisiblePos( pStartEntry )); pView->Invalidate( GetVisibleArea() ); } else InvalidateEntriesFrom( GetEntryLine( pEntry ) ); } } void SvImpLBox::EntryRemoved() { if( nFlags & LBoxFlags::RemovedEntryInvisible ) { nFlags &= (~LBoxFlags::RemovedEntryInvisible); return; } if( !pStartEntry ) pStartEntry = pTree->First(); if( !pCursor ) SetCursor( pStartEntry, true ); if( pCursor && (bSimpleTravel || !pView->GetSelectionCount() )) pView->Select( pCursor ); if( GetUpdateMode()) { if( nFlags & LBoxFlags::RemovedRecalcMostRight ) FindMostRight(nullptr); aVerSBar->SetRange( Range(0, pView->GetVisibleCount()-1 ) ); FillView(); if( pStartEntry ) // if something above the thumb was deleted aVerSBar->SetThumbPos( pView->GetVisiblePos( pStartEntry) ); ShowVerSBar(); if( pCursor && pView->HasFocus() && !pView->IsSelected(pCursor) ) { if( pView->GetSelectionCount() ) { // is a neighboring entry selected? SvTreeListEntry* pNextCursor = pView->PrevVisible( pCursor ); if( !pNextCursor || !pView->IsSelected( pNextCursor )) pNextCursor = pView->NextVisible( pCursor ); if( !pNextCursor || !pView->IsSelected( pNextCursor )) // no neighbor selected: use first selected pNextCursor = pView->FirstSelected(); SetCursor( pNextCursor ); MakeVisible( pCursor ); } else pView->Select( pCursor ); } ShowCursor( true ); } nFlags &= (~LBoxFlags::RemovedRecalcMostRight); } void SvImpLBox::MovingEntry( SvTreeListEntry* pEntry ) { bool bDeselAll(nFlags & LBoxFlags::DeselectAll); SelAllDestrAnch( false ); // DeselectAll(); if( !bDeselAll ) nFlags &= (~LBoxFlags::DeselectAll); if( pEntry == pCursor ) ShowCursor( false ); if( IsEntryInView( pEntry ) ) pView->Invalidate(); if( pEntry == pStartEntry ) { SvTreeListEntry* pNew = nullptr; if( !pEntry->HasChildren() ) { pNew = pView->NextVisible(pStartEntry); if( !pNew ) pNew = pView->PrevVisible(pStartEntry); } else { pNew = SvTreeList::NextSibling( pEntry ); if( !pNew ) pNew = SvTreeList::PrevSibling( pEntry ); } pStartEntry = pNew; } } void SvImpLBox::EntryMoved( SvTreeListEntry* pEntry ) { UpdateContextBmpWidthVectorFromMovedEntry( pEntry ); if ( !pStartEntry ) // this might happen if the only entry in the view is moved to its very same position // #i97346# pStartEntry = pView->First(); aVerSBar->SetRange( Range(0, pView->GetVisibleCount()-1)); sal_uInt16 nFirstPos = (sal_uInt16)pTree->GetAbsPos( pStartEntry ); sal_uInt16 nNewPos = (sal_uInt16)pTree->GetAbsPos( pEntry ); FindMostRight(nullptr); if( nNewPos < nFirstPos ) // HACK! pStartEntry = pEntry; SyncVerThumb(); if( pEntry == pCursor ) { if( pView->IsEntryVisible( pCursor ) ) ShowCursor( true ); else { SvTreeListEntry* pParent = pEntry; do { pParent = pTree->GetParent( pParent ); } while( !pView->IsEntryVisible( pParent ) ); SetCursor( pParent ); } } if( IsEntryInView( pEntry ) ) pView->Invalidate(); } void SvImpLBox::EntryInserted( SvTreeListEntry* pEntry ) { if( GetUpdateMode() ) { SvTreeListEntry* pParent = pTree->GetParent(pEntry); if (pParent && pTree->GetChildList(pParent).size() == 1) // draw plus sign pTree->InvalidateEntry( pParent ); if( !pView->IsEntryVisible( pEntry ) ) return; bool bDeselAll(nFlags & LBoxFlags::DeselectAll); if( bDeselAll ) SelAllDestrAnch( false ); else DestroyAnchor(); // nFlags &= (~LBoxFlags::DeselectAll); // ShowCursor( false ); // if cursor is moved lower long nY = GetEntryLine( pEntry ); bool bEntryVisible = IsLineVisible( nY ); if( bEntryVisible ) { ShowCursor( false ); // if cursor is moved lower nY -= pView->GetEntryHeight(); // because of lines InvalidateEntriesFrom( nY ); } else if( pStartEntry && nY < GetEntryLine(pStartEntry) ) { // Check if the view is filled completely. If not, then adjust // pStartEntry and the Cursor (automatic scrolling). sal_uInt16 nLast = (sal_uInt16)(pView->GetVisiblePos(pView->LastVisible())); sal_uInt16 nThumb = (sal_uInt16)(pView->GetVisiblePos( pStartEntry )); sal_uInt16 nCurDispEntries = nLast-nThumb+1; if( nCurDispEntries < nVisibleCount ) { // set at the next paint event pStartEntry = nullptr; SetCursor( nullptr ); pView->Invalidate(); } } else if( !pStartEntry ) pView->Invalidate(); SetMostRight( pEntry ); aVerSBar->SetRange( Range(0, pView->GetVisibleCount()-1)); SyncVerThumb(); // if something was inserted before the thumb ShowVerSBar(); ShowCursor( true ); if( pStartEntry != pView->First() && (nFlags & LBoxFlags::Filling) ) pView->Update(); } } // ******************************************************************** // Event handler // ******************************************************************** // ****** Control the control animation bool SvImpLBox::ButtonDownCheckCtrl(const MouseEvent& rMEvt, SvTreeListEntry* pEntry) { SvLBoxItem* pItem = pView->GetItem(pEntry,rMEvt.GetPosPixel().X(),&pActiveTab); if (pItem && pItem->GetType() == SvLBoxItemType::Button) { pActiveButton = static_cast(pItem); pActiveEntry = pEntry; if( pCursor == pActiveEntry ) pView->HideFocus(); pView->CaptureMouse(); pActiveButton->SetStateHilighted( true ); InvalidateEntry(pActiveEntry); return true; } else pActiveButton = nullptr; return false; } bool SvImpLBox::MouseMoveCheckCtrl(const MouseEvent& rMEvt, SvTreeListEntry const * pEntry) { if( pActiveButton ) { long nMouseX = rMEvt.GetPosPixel().X(); if( pEntry == pActiveEntry && pView->GetItem(pActiveEntry, nMouseX) == pActiveButton ) { if( !pActiveButton->IsStateHilighted() ) { pActiveButton->SetStateHilighted(true ); InvalidateEntry(pActiveEntry); } } else { if( pActiveButton->IsStateHilighted() ) { pActiveButton->SetStateHilighted(false ); InvalidateEntry(pActiveEntry); } } return true; } return false; } bool SvImpLBox::ButtonUpCheckCtrl( const MouseEvent& rMEvt ) { if( pActiveButton ) { pView->ReleaseMouse(); SvTreeListEntry* pEntry = GetClickedEntry( rMEvt.GetPosPixel() ); pActiveButton->SetStateHilighted( false ); long nMouseX = rMEvt.GetPosPixel().X(); if (pEntry == pActiveEntry && pView->GetItem(pActiveEntry, nMouseX) == pActiveButton) pActiveButton->ClickHdl(pActiveEntry); InvalidateEntry(pActiveEntry); if (pCursor == pActiveEntry) ShowCursor(true); pActiveButton = nullptr; pActiveEntry = nullptr; 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, SvTreeListEntry* pEntry ) const { if( !pEntry->HasChildren() && !pEntry->HasChildrenOnDemand() ) return false; SvLBoxTab* pFirstDynamicTab = pView->GetFirstDynamicTab(); if( !pFirstDynamicTab ) return false; long nMouseX = rPosPixel.X(); // convert to document coordinates Point aOrigin( pView->GetMapMode().GetOrigin() ); nMouseX -= aOrigin.X(); long nX = pView->GetTabPos( pEntry, pFirstDynamicTab); nX += nNodeBmpTabDistance; if( nMouseX < nX ) return false; nX += nNodeBmpWidth; return nMouseX <= nX; } // false == hit no node button bool SvImpLBox::ButtonDownCheckExpand( const MouseEvent& rMEvt, SvTreeListEntry* pEntry ) { bool bRet = false; if ( pView->IsEditingActive() && pEntry == pView->pEdEntry ) // inplace editing -> nothing to do bRet = true; else if ( IsNodeButton( rMEvt.GetPosPixel(), pEntry ) ) { if ( pView->IsExpanded( pEntry ) ) { pView->EndEditing( true ); pView->Collapse( pEntry ); } else { // you can expand an entry, which is in editing pView->Expand( pEntry ); } bRet = true; } return bRet; } void SvImpLBox::MouseButtonDown( const MouseEvent& rMEvt ) { if ( !rMEvt.IsLeft() && !rMEvt.IsRight()) return; aEditIdle.Stop(); Point aPos( rMEvt.GetPosPixel()); if( aPos.X() > aOutputSize.Width() || aPos.Y() > aOutputSize.Height() ) return; SvTreeListEntry* pEntry = GetEntry( aPos ); if ( pEntry != pCursor ) // new entry selected -> reset current tab position to first tab nCurTabPos = FIRST_ENTRY_TAB; nFlags &= (~LBoxFlags::Filling); pView->GrabFocus(); //fdo#82270 Grabbing focus can invalidate the entries, re-fetch pEntry = GetEntry(aPos); // the entry can still be invalid! if( !pEntry || !pView->GetViewData( pEntry )) return; long nY = GetEntryLine( pEntry ); // Node-Button? if( ButtonDownCheckExpand( rMEvt, pEntry ) ) return; if( !EntryReallyHit(pEntry,aPos,nY)) return; SvLBoxItem* pXItem = pView->GetItem( pEntry, aPos.X() ); if( pXItem ) { SvLBoxTab* pXTab = pView->GetTab( pEntry, pXItem ); if ( !rMEvt.IsMod1() && !rMEvt.IsMod2() && rMEvt.IsLeft() && pXTab->IsEditable() && pEntry == pView->FirstSelected() && nullptr == pView->NextSelected( pEntry ) ) // #i8234# FirstSelected() and NextSelected() ensures, that inplace editing is only triggered, when only one entry is selected nFlags |= LBoxFlags::StartEditTimer; if ( !pView->IsSelected( pEntry ) ) nFlags &= ~LBoxFlags::StartEditTimer; } if( (rMEvt.GetClicks() % 2) == 0 ) { nFlags &= (~LBoxFlags::StartEditTimer); pView->pHdlEntry = pEntry; if( pView->DoubleClickHdl() ) { // if the entry was deleted within the handler pEntry = GetClickedEntry( aPos ); if( !pEntry ) return; if( pEntry != pView->pHdlEntry ) { // select anew & bye if( !bSimpleTravel && !aSelEng.IsAlwaysAdding()) SelAllDestrAnch( false ); // DeselectAll(); SetCursor( pEntry ); return; } if( pEntry->HasChildren() || pEntry->HasChildrenOnDemand() ) { if( pView->IsExpanded(pEntry) ) pView->Collapse( pEntry ); else pView->Expand( pEntry ); if( pEntry == pCursor ) // only if Entryitem was clicked // (Nodebutton is not an Entryitem!) pView->Select( pCursor ); return; } } } else { // CheckButton? (TreeListBox: Check + Info) if( ButtonDownCheckCtrl(rMEvt, pEntry) ) return; // Inplace-Editing? } if ( aSelEng.GetSelectionMode() != SelectionMode::NONE ) aSelEng.SelMouseButtonDown( rMEvt ); } void SvImpLBox::MouseButtonUp( const MouseEvent& rMEvt) { if ( !ButtonUpCheckCtrl( rMEvt ) && ( aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) aSelEng.SelMouseButtonUp( rMEvt ); EndScroll(); if( nFlags & LBoxFlags::StartEditTimer ) { nFlags &= (~LBoxFlags::StartEditTimer); aEditClickPos = rMEvt.GetPosPixel(); aEditIdle.Start(); } } void SvImpLBox::MouseMove( const MouseEvent& rMEvt) { SvTreeListEntry* pEntry = GetClickedEntry( rMEvt.GetPosPixel() ); if ( !MouseMoveCheckCtrl( rMEvt, pEntry ) && ( aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) aSelEng.SelMouseMove( rMEvt ); } bool SvImpLBox::KeyInput( const KeyEvent& rKEvt) { aEditIdle.Stop(); const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); if( rKeyCode.IsMod2() ) return false; // don't evaluate Alt key nFlags &= (~LBoxFlags::Filling); if( !pCursor ) pCursor = pStartEntry; if( !pCursor ) return false; bool bKeyUsed = true; sal_uInt16 nDelta = (sal_uInt16)aVerSBar->GetPageSize(); sal_uInt16 aCode = rKeyCode.GetCode(); bool bShift = rKeyCode.IsShift(); bool bMod1 = rKeyCode.IsMod1(); SvTreeListEntry* pNewCursor; const WinBits nWindowStyle = pView->GetStyle(); switch( aCode ) { case KEY_UP: if( !IsEntryInView( pCursor ) ) MakeVisible( pCursor ); pNewCursor = pCursor; do { pNewCursor = pView->PrevVisible(pNewCursor); } while( pNewCursor && !IsSelectable(pNewCursor) ); if ( pNewCursor ) // new entry selected -> reset current tab position to first tab nCurTabPos = FIRST_ENTRY_TAB; // 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 = pCursor; aSelEng.CursorPosChanging( bShift, bMod1 ); SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on if( !IsEntryInView( pNewCursor ) ) KeyUp( false ); break; case KEY_DOWN: if( !IsEntryInView( pCursor ) ) MakeVisible( pCursor ); pNewCursor = pCursor; do { pNewCursor = pView->NextVisible(pNewCursor); } while( pNewCursor && !IsSelectable(pNewCursor) ); if ( pNewCursor ) // new entry selected -> reset current tab position to first tab nCurTabPos = FIRST_ENTRY_TAB; // 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 && pCursor ) pNewCursor = pCursor; if( pNewCursor ) { aSelEng.CursorPosChanging( bShift, bMod1 ); if( IsEntryInView( pNewCursor ) ) SetCursor( pNewCursor, bMod1 ); // no selection, when Ctrl is on else { if( pCursor ) pView->Select( 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( bSubLstOpLR ) { // only try to expand if sublist is expandable, // otherwise ignore the key press if( IsExpandable() && !pView->IsExpanded( pCursor ) ) pView->Expand( pCursor ); } else if ( bIsCellFocusEnabled && pCursor ) { if ( nCurTabPos < ( pView->TabCount() - 1 /*!2*/ ) ) { ++nCurTabPos; ShowCursor( true ); CallEventListeners( VclEventId::ListboxSelect, pCursor ); } } else if( nWindowStyle & WB_HSCROLL ) { long nThumb = aHorSBar->GetThumbPos(); nThumb += aHorSBar->GetLineSize(); long nOldThumb = aHorSBar->GetThumbPos(); aHorSBar->SetThumbPos( nThumb ); nThumb = nOldThumb; nThumb -= aHorSBar->GetThumbPos(); nThumb *= -1; if( nThumb ) { KeyLeftRight( nThumb ); EndScroll(); } } else bKeyUsed = false; break; } case KEY_LEFT: { if ( bIsCellFocusEnabled && pCursor ) { if ( nCurTabPos > FIRST_ENTRY_TAB ) { --nCurTabPos; ShowCursor( true ); CallEventListeners( VclEventId::ListboxSelect, pCursor ); } } else if ( nWindowStyle & WB_HSCROLL ) { long nThumb = aHorSBar->GetThumbPos(); nThumb -= aHorSBar->GetLineSize(); long nOldThumb = aHorSBar->GetThumbPos(); aHorSBar->SetThumbPos( nThumb ); nThumb = nOldThumb; nThumb -= aHorSBar->GetThumbPos(); if( nThumb ) { KeyLeftRight( -nThumb ); EndScroll(); } else if( bSubLstOpLR ) { if( IsExpandable() && pView->IsExpanded( pCursor ) ) pView->Collapse( pCursor ); else { pNewCursor = pView->GetParent( pCursor ); if( pNewCursor ) SetCursor( pNewCursor ); } } } else if( bSubLstOpLR ) { if( IsExpandable() && pView->IsExpanded( pCursor ) ) pView->Collapse( pCursor ); else { pNewCursor = pView->GetParent( pCursor ); if( pNewCursor ) SetCursor( pNewCursor ); } } else bKeyUsed = false; break; } case KEY_PAGEUP: if( !bMod1 ) { pNewCursor = pView->PrevVisible(pCursor, nDelta); while( nDelta && pNewCursor && !IsSelectable(pNewCursor) ) { pNewCursor = pView->NextVisible(pNewCursor); nDelta--; } if( nDelta ) { DBG_ASSERT(pNewCursor && pNewCursor!=pCursor, "Cursor?"); aSelEng.CursorPosChanging( bShift, bMod1 ); if( IsEntryInView( pNewCursor ) ) SetCursor( pNewCursor ); else { SetCursor( pNewCursor ); KeyUp( true ); } } } else bKeyUsed = false; break; case KEY_PAGEDOWN: if( !bMod1 ) { pNewCursor= pView->NextVisible(pCursor, nDelta); while( nDelta && pNewCursor && !IsSelectable(pNewCursor) ) { pNewCursor = pView->PrevVisible(pNewCursor); nDelta--; } if( nDelta && pNewCursor ) { DBG_ASSERT(pNewCursor && pNewCursor!=pCursor, "Cursor?"); 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 ( pView->GetSelectionMode() != SelectionMode::NONE ) { if ( bMod1 ) { if ( pView->GetSelectionMode() == SelectionMode::Multiple && !bShift ) // toggle selection pView->Select( pCursor, !pView->IsSelected( pCursor ) ); } else if ( !bShift /*&& !bMod1*/ ) { if ( aSelEng.IsAddMode() ) { // toggle selection pView->Select( pCursor, !pView->IsSelected( pCursor ) ); } else if ( !pView->IsSelected( pCursor ) ) { SelAllDestrAnch( false ); pView->Select( pCursor ); } else bKeyUsed = false; } else bKeyUsed = false; } else bKeyUsed = false; break; case KEY_RETURN: if( bSubLstOpRet && IsExpandable() ) { if( pView->IsExpanded( pCursor ) ) pView->Collapse( pCursor ); else pView->Expand( pCursor ); } else bKeyUsed = false; break; case KEY_F2: if( !bShift && !bMod1 ) { aEditClickPos = Point( -1, -1 ); EditTimerCall( nullptr ); } else bKeyUsed = false; break; case KEY_F8: if( bShift && pView->GetSelectionMode()==SelectionMode::Multiple && !(m_nStyle & WB_SIMPLEMODE)) { if( aSelEng.IsAlwaysAdding() ) aSelEng.AddAlways( false ); else aSelEng.AddAlways( true ); } else bKeyUsed = false; break; case KEY_ADD: if( pCursor ) { if( !pView->IsExpanded(pCursor)) pView->Expand( pCursor ); if( bMod1 ) { sal_uInt16 nRefDepth = pTree->GetDepth( pCursor ); SvTreeListEntry* pCur = pTree->Next( pCursor ); while( pCur && pTree->GetDepth(pCur) > nRefDepth ) { if( pCur->HasChildren() && !pView->IsExpanded(pCur)) pView->Expand( pCur ); pCur = pTree->Next( pCur ); } } } else bKeyUsed = false; break; case KEY_A: if( bMod1 ) SelAllDestrAnch( true ); else bKeyUsed = false; break; case KEY_SUBTRACT: if( pCursor ) { if( pView->IsExpanded(pCursor)) pView->Collapse( pCursor ); if( bMod1 ) { // collapse all parents until we get to the root SvTreeListEntry* pParentToCollapse = pTree->GetRootLevelParent(pCursor); if( pParentToCollapse ) { sal_uInt16 nRefDepth; // special case explorer: if the root only has a single // entry, don't collapse the root entry if (pTree->GetChildList(nullptr).size() < 2) { nRefDepth = 1; pParentToCollapse = pCursor; while( pTree->GetParent(pParentToCollapse) && pTree->GetDepth( pTree->GetParent(pParentToCollapse)) > 0) { pParentToCollapse = pTree->GetParent(pParentToCollapse); } } else nRefDepth = 0; if( pView->IsExpanded(pParentToCollapse) ) pView->Collapse( pParentToCollapse ); SvTreeListEntry* pCur = pTree->Next( pParentToCollapse ); while( pCur && pTree->GetDepth(pCur) > nRefDepth ) { if( pCur->HasChildren() && pView->IsExpanded(pCur) ) pView->Collapse( pCur ); pCur = pTree->Next( pCur ); } } } } 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 = pView->GetModel()->First(); while( pNewCursor && !IsSelectable(pNewCursor) ) { pNewCursor = pView->NextVisible(pNewCursor); } if( pNewCursor && pNewCursor != pCursor ) { // SelAllDestrAnch( false ); aSelEng.CursorPosChanging( bShift, bMod1 ); SetCursor( pNewCursor ); if( !IsEntryInView( pNewCursor ) ) MakeVisible( pNewCursor ); } else bKeyUsed = false; break; case KEY_END : pNewCursor = pView->GetModel()->Last(); while( pNewCursor && !IsSelectable(pNewCursor) ) { pNewCursor = pView->PrevVisible(pNewCursor); } if( pNewCursor && pNewCursor != pCursor) { // SelAllDestrAnch( false ); 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( pCursor ) { pView->SetEntryFocus( pCursor, true ); ShowCursor( true ); // auskommentiert wg. deselectall // if( bSimpleTravel && !pView->IsSelected(pCursor) ) // pView->Select( pCursor, true ); } if( m_nStyle & WB_HIDESELECTION ) { SvTreeListEntry* pEntry = pView->FirstSelected(); while( pEntry ) { InvalidateEntry( pEntry ); pEntry = pView->NextSelected( pEntry ); } } } void SvImpLBox::LoseFocus() { aEditIdle.Stop(); if( pCursor ) pView->SetEntryFocus( pCursor,false ); ShowCursor( false ); if( m_nStyle & WB_HIDESELECTION ) { SvTreeListEntry* pEntry = pView ? pView->FirstSelected() : nullptr; while( pEntry ) { InvalidateEntry( pEntry ); pEntry = pView->NextSelected( pEntry ); } } } // ******************************************************************** // SelectionEngine // ******************************************************************** void SvImpLBox::SelectEntry( SvTreeListEntry* pEntry, bool bSelect ) { 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->pAnchor = pImp->pCursor; } void ImpLBSelEng::DestroyAnchor() { pImp->pAnchor = nullptr; } bool ImpLBSelEng::SetCursorAtPoint(const Point& rPoint, bool bDontSelectAtCursor) { SvTreeListEntry* pNewCursor = pImp->MakePointVisible( rPoint ); if( pNewCursor != pImp->pCursor ) pImp->BeginScroll(); 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 ); return true; } return false; } 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->nFlags &= (~LBoxFlags::DeselectAll); } // *********************************************************************** // Selection // *********************************************************************** void SvImpLBox::SetAnchorSelection(SvTreeListEntry* pOldCursor,SvTreeListEntry* pNewCursor) { SvTreeListEntry* pEntry; sal_uLong nAnchorVisPos = pView->GetVisiblePos( pAnchor ); sal_uLong nOldVisPos = pView->GetVisiblePos( pOldCursor ); sal_uLong nNewVisPos = pView->GetVisiblePos( pNewCursor ); if( nOldVisPos > nAnchorVisPos || ( nAnchorVisPos==nOldVisPos && nNewVisPos > nAnchorVisPos) ) { if( nNewVisPos > nOldVisPos ) { pEntry = pOldCursor; while( pEntry && pEntry != pNewCursor ) { pView->Select( pEntry ); pEntry = pView->NextVisible(pEntry); } if( pEntry ) pView->Select( pEntry ); return; } if( nNewVisPos < nAnchorVisPos ) { pEntry = pAnchor; while( pEntry && pEntry != pOldCursor ) { pView->Select( pEntry, false ); pEntry = pView->NextVisible(pEntry); } if( pEntry ) pView->Select( pEntry, false ); pEntry = pNewCursor; while( pEntry && pEntry != pAnchor ) { pView->Select( pEntry ); pEntry = pView->NextVisible(pEntry); } if( pEntry ) pView->Select( pEntry ); return; } if( nNewVisPos < nOldVisPos ) { pEntry = pNewCursor; pEntry = pView->NextVisible(pEntry); while( pEntry && pEntry != pOldCursor ) { pView->Select( pEntry, false ); pEntry = pView->NextVisible(pEntry); } if( pEntry ) pView->Select( pEntry, false ); return; } } else { if( nNewVisPos < nOldVisPos ) // enlarge selection { pEntry = pNewCursor; while( pEntry && pEntry != pOldCursor ) { pView->Select( pEntry ); pEntry = pView->NextVisible(pEntry); } if( pEntry ) pView->Select( pEntry ); return; } if( nNewVisPos > nAnchorVisPos ) { pEntry = pOldCursor; while( pEntry && pEntry != pAnchor ) { pView->Select( pEntry, false ); pEntry = pView->NextVisible(pEntry); } if( pEntry ) pView->Select( pEntry, false ); pEntry = pAnchor; while( pEntry && pEntry != pNewCursor ) { pView->Select( pEntry ); pEntry = pView->NextVisible(pEntry); } if( pEntry ) pView->Select( pEntry ); return; } if( nNewVisPos > nOldVisPos ) { pEntry = pOldCursor; while( pEntry && pEntry != pNewCursor ) { pView->Select( pEntry, false ); pEntry = pView->NextVisible(pEntry); } return; } } } void SvImpLBox::SelAllDestrAnch( bool bSelect, bool bDestroyAnchor, bool bSingleSelToo ) { SvTreeListEntry* pEntry; nFlags &= (~LBoxFlags::DeselectAll); if( bSelect && bSimpleTravel ) { if( pCursor && !pView->IsSelected( pCursor )) { pView->Select( pCursor ); } return; } if( !bSelect && pView->GetSelectionCount() == 0 ) { if( bSimpleTravel && ( !GetUpdateMode() || !pCursor) ) nFlags |= LBoxFlags::DeselectAll; return; } if( bSelect && pView->GetSelectionCount() == pView->GetEntryCount()) return; if( !bSingleSelToo && bSimpleTravel ) return; if( !bSelect && pView->GetSelectionCount()==1 && pCursor && pView->IsSelected( pCursor )) { pView->Select( pCursor, false ); if( bDestroyAnchor ) DestroyAnchor(); // delete anchor & reset SelectionEngine else pAnchor = nullptr; // always delete internal anchor return; } if( bSimpleTravel && !pCursor && !GetUpdateMode() ) nFlags |= LBoxFlags::DeselectAll; ShowCursor( false ); bool bUpdate = GetUpdateMode(); nFlags |= LBoxFlags::IgnoreSelect; // EntryInserted should not do anything pEntry = pTree->First(); while( pEntry ) { if( pView->Select( pEntry, bSelect ) ) { if( bUpdate && pView->IsEntryVisible(pEntry) ) { long nY = GetEntryLine( pEntry ); if( IsLineVisible( nY ) ) InvalidateEntry(pEntry); } } pEntry = pTree->Next( pEntry ); } nFlags &= ~LBoxFlags::IgnoreSelect; if( bDestroyAnchor ) DestroyAnchor(); // delete anchor & reset SelectionEngine else pAnchor = nullptr; // always delete internal anchor ShowCursor( true ); } void SvImpLBox::SetSelectionMode( SelectionMode eSelMode ) { aSelEng.SetSelectionMode( eSelMode); if( eSelMode == SelectionMode::Single ) bSimpleTravel = true; else bSimpleTravel = false; if( (m_nStyle & WB_SIMPLEMODE) && (eSelMode == SelectionMode::Multiple) ) aSelEng.AddAlways( true ); } // *********************************************************************** // Drag & Drop // *********************************************************************** void SvImpLBox::SetDragDropMode( DragDropMode eDDMode ) { if( eDDMode != DragDropMode::NONE && eDDMode != DragDropMode::APP_DROP ) { aSelEng.ExpandSelectionOnMouseMove( false ); aSelEng.EnableDrag( true ); } else { aSelEng.ExpandSelectionOnMouseMove(); aSelEng.EnableDrag( false ); } } void SvImpLBox::BeginDrag() { nFlags &= (~LBoxFlags::Filling); if( !bAsyncBeginDrag ) { BeginScroll(); pView->StartDrag( 0, aSelEng.GetMousePosPixel() ); EndScroll(); } else { aAsyncBeginDragPos = aSelEng.GetMousePosPixel(); aAsyncBeginDragIdle.Start(); } } IMPL_LINK_NOARG(SvImpLBox, BeginDragHdl, Timer *, void) { pView->StartDrag( 0, aAsyncBeginDragPos ); } void SvImpLBox::PaintDDCursor( SvTreeListEntry* pInsertionPos ) { long nY; if( pInsertionPos ) { nY = GetEntryLine( pInsertionPos ); nY += pView->GetEntryHeight(); } else nY = 1; RasterOp eOldOp = pView->GetRasterOp(); pView->SetRasterOp( RasterOp::Invert ); Color aOldLineColor = pView->GetLineColor(); pView->SetLineColor( Color( COL_BLACK ) ); pView->DrawLine( Point( 0, nY ), Point( aOutputSize.Width(), nY ) ); pView->SetLineColor( aOldLineColor ); pView->SetRasterOp( eOldOp ); } void SvImpLBox::Command( const CommandEvent& rCEvt ) { CommandEventId nCommand = rCEvt.GetCommand(); if( nCommand == CommandEventId::ContextMenu ) aEditIdle.Stop(); // scroll mouse event? if( ( ( nCommand == CommandEventId::Wheel ) || ( nCommand == CommandEventId::StartAutoScroll ) || ( nCommand == CommandEventId::AutoScroll ) ) && pView->HandleScrollCommand( rCEvt, aHorSBar.get(), aVerSBar.get() ) ) return; if( bContextMenuHandling && nCommand == CommandEventId::ContextMenu ) { Point aPopupPos; bool bClickedIsFreePlace = false; std::stack aSelRestore; if( rCEvt.IsMouseEvent() ) { // change selection, if mouse position doesn't fit to selection aPopupPos = rCEvt.GetMousePosPixel(); SvTreeListEntry* pClickedEntry = GetEntry( aPopupPos ); if( pClickedEntry ) { // mouse in non empty area bool bClickedIsSelected = false; // collect the currently selected entries SvTreeListEntry* pSelected = pView->FirstSelected(); while( pSelected ) { bClickedIsSelected |= ( pClickedEntry == pSelected ); pSelected = pView->NextSelected( pSelected ); } // if the entry which the user clicked at is not selected if( !bClickedIsSelected ) { // deselect all other and select the clicked one pView->SelectAll( false ); pView->SetCursor( pClickedEntry ); } } else if( aSelEng.GetSelectionMode() == SelectionMode::Single ) { bClickedIsFreePlace = true; sal_Int32 nSelectedEntries = pView->GetSelectionCount(); SvTreeListEntry* pSelected = pView->FirstSelected(); for(sal_Int32 nSel = 0; nSel < nSelectedEntries; nSel++ ) { aSelRestore.push(pSelected); pSelected = pView->NextSelected( pSelected ); } pView->SelectAll( false ); } else { // deselect all pView->SelectAll( false ); } } else { // key event (or at least no mouse event) sal_Int32 nSelectionCount = pView->GetSelectionCount(); if( nSelectionCount ) { // now always take first visible as base for positioning the menu SvTreeListEntry* pSelected = pView->FirstSelected(); while( pSelected ) { if( IsEntryInView( pSelected ) ) break; pSelected = pView->NextSelected( pSelected ); } if( !pSelected ) { // no one was visible pSelected = pView->FirstSelected(); pView->MakeVisible( pSelected ); } aPopupPos = pView->GetFocusRect( pSelected, pView->GetEntryPosition( pSelected ).Y() ).Center(); } else aPopupPos = Point( 0, 0 ); } { VclPtr pPopup = pView->CreateContextMenu(); if (pPopup) { // do action for selected entry in popup menu sal_uInt16 nMenuAction = pPopup->Execute( pView, aPopupPos ); if ( nMenuAction ) pView->ExecuteContextMenuAction( nMenuAction ); pPopup.disposeAndClear(); } } if( bClickedIsFreePlace ) { while(!aSelRestore.empty()) { SvTreeListEntry* pEntry = aSelRestore.top(); //#i19717# the entry is maybe already deleted bool bFound = false; for(sal_uLong nEntry = 0; nEntry < pView->GetEntryCount(); nEntry++) if(pEntry == pView->GetEntry(nEntry)) { bFound = true; break; } if(bFound) SetCurEntry( pEntry ); aSelRestore.pop(); } } } else { const Point& rPos = rCEvt.GetMousePosPixel(); if( rPos.X() < aOutputSize.Width() && rPos.Y() < aOutputSize.Height() ) aSelEng.Command( rCEvt ); } } void SvImpLBox::BeginScroll() { if( !(nFlags & LBoxFlags::InScrolling)) { nFlags |= LBoxFlags::InScrolling; } } void SvImpLBox::EndScroll() { if( nFlags & LBoxFlags::InScrolling) { pView->NotifyEndScroll(); nFlags &= (~LBoxFlags::InScrolling); } } tools::Rectangle SvImpLBox::GetVisibleArea() const { Point aPos( pView->GetMapMode().GetOrigin() ); aPos.X() *= -1; tools::Rectangle aRect( aPos, aOutputSize ); return aRect; } void SvImpLBox::Invalidate() { pView->SetClipRegion(); } void SvImpLBox::SetCurEntry( SvTreeListEntry* pEntry ) { if ( ( aSelEng.GetSelectionMode() != SelectionMode::Single ) && ( aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) SelAllDestrAnch( false ); if ( pEntry ) MakeVisible( pEntry ); SetCursor( pEntry ); if ( pEntry && ( aSelEng.GetSelectionMode() != SelectionMode::NONE ) ) pView->Select( pEntry ); } IMPL_LINK_NOARG(SvImpLBox, EditTimerCall, Timer *, void) { if( pView->IsInplaceEditingEnabled() ) { bool bIsMouseTriggered = aEditClickPos.X() >= 0; if ( bIsMouseTriggered ) { Point aCurrentMousePos = pView->GetPointerPosPixel(); if ( ( std::abs( aCurrentMousePos.X() - aEditClickPos.X() ) > 5 ) || ( std::abs( aCurrentMousePos.Y() - aEditClickPos.Y() ) > 5 ) ) { return; } } SvTreeListEntry* pEntry = GetCurEntry(); if( pEntry ) { ShowCursor( false ); pView->ImplEditEntry( pEntry ); ShowCursor( true ); } } } bool SvImpLBox::RequestHelp( const HelpEvent& rHEvt ) { if( rHEvt.GetMode() & HelpEventMode::QUICK ) { Point aPos( pView->ScreenToOutputPixel( rHEvt.GetMousePosPixel() )); if( !GetVisibleArea().IsInside( aPos )) return false; SvTreeListEntry* pEntry = GetEntry( aPos ); if( pEntry ) { // recalculate text rectangle SvLBoxTab* pTab; SvLBoxItem* pItem = pView->GetItem( pEntry, aPos.X(), &pTab ); if (!pItem || pItem->GetType() != SvLBoxItemType::String) return false; aPos = GetEntryPosition( pEntry ); aPos.X() = pView->GetTabPos( pEntry, pTab ); //pTab->GetPos(); Size aSize( pItem->GetSize( pView, pEntry ) ); SvLBoxTab* pNextTab = NextTab( pTab ); bool bItemClipped = false; // is the item cut off by its right neighbor? if( pNextTab && pView->GetTabPos(pEntry,pNextTab) < aPos.X()+aSize.Width() ) { aSize.Width() = pNextTab->GetPos() - pTab->GetPos(); bItemClipped = true; } tools::Rectangle aItemRect( aPos, aSize ); tools::Rectangle aViewRect( GetVisibleArea() ); if( bItemClipped || !aViewRect.IsInside( 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 = pView->OutputToScreenPixel( aItemRect.TopLeft() ); aItemRect.Left() = aPt.X(); aItemRect.Top() = aPt.Y(); aPt = pView->OutputToScreenPixel( aItemRect.BottomRight() ); aItemRect.Right() = aPt.X(); aItemRect.Bottom() = aPt.Y(); Help::ShowQuickHelp( pView, aItemRect, static_cast(pItem)->GetText(), QuickHelpFlags::Left | QuickHelpFlags::VCenter ); return true; } } } return false; } SvLBoxTab* SvImpLBox::NextTab( SvLBoxTab const * pTab ) { sal_uInt16 nTabCount = pView->TabCount(); if( nTabCount <= 1 ) return nullptr; for( int nTab=0; nTab < (nTabCount-1); nTab++) { if( pView->aTabs[nTab]==pTab ) return pView->aTabs[nTab+1]; } return nullptr; } void SvImpLBox::EndSelection() { DestroyAnchor(); nFlags &= ~LBoxFlags::StartEditTimer; } void SvImpLBox::SetUpdateMode( bool bMode ) { if( bUpdateMode != bMode ) { bUpdateMode = bMode; if( bUpdateMode ) UpdateAll( false ); } } bool SvImpLBox::SetMostRight( SvTreeListEntry* pEntry ) { if( pView->nTreeFlags & SvTreeFlags::RECALCTABS ) { nFlags |= LBoxFlags::IgnoreChangedTabs; pView->SetTabs(); nFlags &= ~LBoxFlags::IgnoreChangedTabs; } sal_uInt16 nLastTab = pView->aTabs.size() - 1; sal_uInt16 nLastItem = pEntry->ItemCount() - 1; if( !pView->aTabs.empty() && nLastItem != USHRT_MAX ) { if( nLastItem < nLastTab ) nLastTab = nLastItem; SvLBoxTab* pTab = pView->aTabs[ nLastTab ]; SvLBoxItem& rItem = pEntry->GetItem( nLastTab ); long nTabPos = pView->GetTabPos( pEntry, pTab ); long nMaxRight = GetOutputSize().Width(); Point aPos( pView->GetMapMode().GetOrigin() ); aPos.X() *= -1; // conversion document coordinates nMaxRight = nMaxRight + aPos.X() - 1; long nNextTab = nTabPos < nMaxRight ? nMaxRight : nMaxRight + 50; long nTabWidth = nNextTab - nTabPos + 1; long nItemSize = rItem.GetSize(pView,pEntry).Width(); long nOffset = pTab->CalcOffset( nItemSize, nTabWidth ); long nRight = nTabPos + nOffset + nItemSize; if( nRight > nMostRight ) { nMostRight = nRight; pMostRightEntry = pEntry; return true; } } return false; } void SvImpLBox::FindMostRight( SvTreeListEntry const * pEntryToIgnore ) { nMostRight = -1; pMostRightEntry = nullptr; if( !pView->GetModel() ) return; SvTreeListEntry* pEntry = pView->FirstVisible(); while( pEntry ) { if( pEntry != pEntryToIgnore ) SetMostRight( pEntry ); pEntry = pView->NextVisible( pEntry ); } } void SvImpLBox::FindMostRight( SvTreeListEntry* pParent, SvTreeListEntry* pEntryToIgnore ) { if( !pParent ) FindMostRight( pEntryToIgnore ); else FindMostRight_Impl( pParent, pEntryToIgnore ); } void SvImpLBox::FindMostRight_Impl( SvTreeListEntry* pParent, SvTreeListEntry* pEntryToIgnore ) { SvTreeListEntries& rList = pTree->GetChildList( pParent ); size_t nCount = rList.size(); for( size_t nCur = 0; nCur < nCount; nCur++ ) { SvTreeListEntry* pChild = rList[nCur].get(); if( pChild != pEntryToIgnore ) { SetMostRight( pChild ); if( pChild->HasChildren() && pView->IsExpanded( pChild )) FindMostRight_Impl( pChild, pEntryToIgnore ); } } } void SvImpLBox::NotifyTabsChanged() { if( GetUpdateMode() && !(nFlags & LBoxFlags::IgnoreChangedTabs ) && nCurUserEvent == nullptr ) { nCurUserEvent = Application::PostUserEvent(LINK(this,SvImpLBox,MyUserEvent)); } } bool SvImpLBox::IsExpandable() const { return pCursor->HasChildren() || pCursor->HasChildrenOnDemand(); } IMPL_LINK(SvImpLBox, MyUserEvent, void*, pArg, void ) { nCurUserEvent = nullptr; if( !pArg ) { pView->Invalidate(); pView->Update(); } else { FindMostRight( nullptr ); ShowVerSBar(); pView->Invalidate( GetVisibleArea() ); } } void SvImpLBox::StopUserEvent() { if( nCurUserEvent != nullptr ) { Application::RemoveUserEvent( nCurUserEvent ); nCurUserEvent = nullptr; } } void SvImpLBox::ShowFocusRect( const SvTreeListEntry* pEntry ) { if( pEntry ) { long nY = GetEntryLine( const_cast(pEntry) ); tools::Rectangle aRect = pView->GetFocusRect( const_cast(pEntry), nY ); vcl::Region aOldClip( pView->GetClipRegion()); vcl::Region aClipRegion( GetClipRegionRect() ); pView->SetClipRegion( aClipRegion ); pView->ShowFocus( aRect ); pView->SetClipRegion( aOldClip ); } else { pView->HideFocus(); } } void SvImpLBox::implInitDefaultNodeImages() { if ( s_pDefCollapsed ) // assume that all or nothing is initialized return; s_pDefCollapsed = new Image(BitmapEx(RID_BMP_TREENODE_COLLAPSED)); s_pDefExpanded = new Image(BitmapEx(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 ( pView ) pView->CallImplEventListeners( nEvent, pData); } bool SvImpLBox::SetCurrentTabPos( sal_uInt16 _nNewPos ) { bool bRet = false; if ( pView && _nNewPos < ( pView->TabCount() - 2 ) ) { nCurTabPos = _nNewPos; ShowCursor( true ); bRet = true; } return bRet; } bool SvImpLBox::IsSelectable( const SvTreeListEntry* pEntry ) { if( pEntry ) { SvViewDataEntry* pViewDataNewCur = pView->GetViewDataEntry(pEntry); return (pViewDataNewCur == nullptr) || pViewDataNewCur->IsSelectable(); } else { return false; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */