    /* -*- 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/.
     */

    #include <sfx2/thumbnailview.hxx>
    #include <sfx2/thumbnailviewitem.hxx>

    #include <utility>

    #include "thumbnailviewacc.hxx"

    #include <basegfx/color/bcolortools.hxx>
    #include <basegfx/matrix/b2dhommatrixtools.hxx>
    #include <basegfx/range/b2drectangle.hxx>
    #include <basegfx/polygon/b2dpolygon.hxx>
    #include <basegfx/vector/b2dsize.hxx>
    #include <basegfx/vector/b2dvector.hxx>
    #include <comphelper/processfactory.hxx>
    #include <drawinglayer/attribute/fillgraphicattribute.hxx>
    #include <drawinglayer/attribute/fontattribute.hxx>
    #include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
    #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
    #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
    #include <drawinglayer/primitive2d/textprimitive2d.hxx>
    #include <drawinglayer/processor2d/baseprocessor2d.hxx>
    #include <drawinglayer/processor2d/processorfromoutputdevice.hxx>
    #include <rtl/ustring.hxx>
    #include <sal/log.hxx>
    #include <svtools/optionsdrawinglayer.hxx>
    #include <unotools/ucbstreamhelper.hxx>
    #include <vcl/decoview.hxx>
    #include <vcl/svapp.hxx>
    #include <vcl/scrbar.hxx>
    #include <vcl/help.hxx>
    #include <vcl/settings.hxx>

    #include <com/sun/star/accessibility/AccessibleEventId.hpp>
    #include <com/sun/star/accessibility/AccessibleStateType.hpp>

    #include <memory>

    using namespace basegfx;
    using namespace basegfx::utils;
    using namespace drawinglayer::attribute;
    using namespace drawinglayer::primitive2d;

    ThumbnailView::ThumbnailView (vcl::Window *pParent, WinBits nWinStyle)
        : Control( pParent, nWinStyle )
        , mpItemAttrs(new ThumbnailItemAttributes)
    {
        ImplInit();
        mbIsTransientChildrenDisabled = false;
    }

    ThumbnailView::~ThumbnailView()
    {
        disposeOnce();
    }

    void ThumbnailView::dispose()
    {
        css::uno::Reference< css::lang::XComponent> xComponent(GetAccessible(false), css::uno::UNO_QUERY);

        if (xComponent.is())
            xComponent->dispose ();

        mpScrBar.disposeAndClear();
        mpItemAttrs.reset();

        ImplDeleteItems();
        Control::dispose();
    }

    void ThumbnailView::MouseMove(const MouseEvent& rMEvt)
    {
        size_t nItemCount = mFilteredItemList.size();
        Point aPoint = rMEvt.GetPosPixel();
        OUString aHelp;

        for (size_t i = 0; i < nItemCount; i++)
        {
            ThumbnailViewItem *pItem = mFilteredItemList[i];

            if (pItem->mbVisible && !rMEvt.IsLeaveWindow() && pItem->getDrawArea().IsInside(aPoint))
            {
                aHelp = pItem->getHelpText();
            }

            ::tools::Rectangle aToInvalidate(pItem->updateHighlight(pItem->mbVisible && !rMEvt.IsLeaveWindow(), aPoint));

            if (!aToInvalidate.IsEmpty() && IsReallyVisible() && IsUpdateMode())
                Invalidate(aToInvalidate);
        }

        if (mbShowTooltips)
            SetQuickHelpText(aHelp);
    }

    void ThumbnailView::AppendItem(ThumbnailViewItem *pItem)
    {
        if (maFilterFunc(pItem))
        {
            // Save current start,end range, iterator might get invalidated
            size_t nSelStartPos = 0;
            ThumbnailViewItem *pSelStartItem = nullptr;

            if (mpStartSelRange != mFilteredItemList.end())
            {
                pSelStartItem = *mpStartSelRange;
                nSelStartPos = mpStartSelRange - mFilteredItemList.begin();
            }

            mFilteredItemList.push_back(pItem);
            mpStartSelRange = pSelStartItem != nullptr ? mFilteredItemList.begin() + nSelStartPos : mFilteredItemList.end();
        }

        mItemList.push_back(pItem);
    }

    void ThumbnailView::ImplInit()
    {
        mpScrBar = nullptr;
        mnItemWidth = 0;
        mnItemHeight = 0;
        mnItemPadding = 0;
        mnVisLines = 0;
        mnLines = 0;
        mnFineness = 5;
        mnFirstLine = 0;
        mnCols = 0;
        mbScroll = false;
        mbHasVisibleItems = false;
        mbShowTooltips = false;
        mbIsMultiSelectionEnabled = true;
        maFilterFunc = ViewFilterAll();
        maFillColor = GetSettings().GetStyleSettings().GetFieldColor();
        maTextColor = GetSettings().GetStyleSettings().GetWindowTextColor();
        maHighlightColor = GetSettings().GetStyleSettings().GetHighlightColor();
        maHighlightTextColor = GetSettings().GetStyleSettings().GetWindowTextColor();
        maSelectHighlightColor = GetSettings().GetStyleSettings().GetActiveColor();
        maSelectHighlightTextColor = GetSettings().GetStyleSettings().GetActiveTextColor();

        const SvtOptionsDrawinglayer aSvtOptionsDrawinglayer;
        mfHighlightTransparence = aSvtOptionsDrawinglayer.GetTransparentSelectionPercent() * 0.01;

        mpStartSelRange = mFilteredItemList.end();

        ApplySettings(*this);
    }

    void ThumbnailView::ImplDeleteItems()
    {
        const size_t n = mItemList.size();

        for ( size_t i = 0; i < n; ++i )
        {
            ThumbnailViewItem *const pItem = mItemList[i];

            // deselect all current selected items and fire events
            if (pItem->isSelected())
            {
                pItem->setSelection(false);
                maItemStateHdl.Call(pItem);

                // fire accessible event???
            }

            if ( pItem->isVisible() && ImplHasAccessibleListeners() )
            {
                css::uno::Any aOldAny, aNewAny;

                aOldAny <<= pItem->GetAccessible( mbIsTransientChildrenDisabled );
                ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny );
            }

            delete pItem;
        }

        mItemList.clear();
        mFilteredItemList.clear();

        mpStartSelRange = mFilteredItemList.end();
    }

    void ThumbnailView::ApplySettings(vcl::RenderContext& rRenderContext)
    {
        const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();

        ApplyControlFont(*this, rStyleSettings.GetAppFont());
        ApplyControlForeground(*this, rStyleSettings.GetButtonTextColor());
        rRenderContext.SetTextFillColor();
        rRenderContext.SetBackground(maFillColor);

        mpItemAttrs->aFillColor = maFillColor.getBColor();
        mpItemAttrs->aTextColor = maTextColor.getBColor();
        mpItemAttrs->aHighlightColor = maHighlightColor.getBColor();
        mpItemAttrs->aHighlightTextColor = maHighlightTextColor.getBColor();
        mpItemAttrs->aSelectHighlightColor = maSelectHighlightColor.getBColor();
        mpItemAttrs->aSelectHighlightTextColor = maSelectHighlightTextColor.getBColor();
        mpItemAttrs->fHighlightTransparence = mfHighlightTransparence;
        mpItemAttrs->aFontAttr = getFontAttributeFromVclFont(mpItemAttrs->aFontSize,GetFont(),false,true);
        mpItemAttrs->nMaxTextLength = 0;
    }

    void ThumbnailView::DrawItem(ThumbnailViewItem const *pItem)
    {
        if (pItem->isVisible())
        {
            ::tools::Rectangle aRect = pItem->getDrawArea();

            if ((aRect.GetHeight() > 0) && (aRect.GetWidth() > 0))
                Invalidate(aRect);
        }
    }

    void ThumbnailView::OnItemDblClicked (ThumbnailViewItem*)
    {
    }

    css::uno::Reference< css::accessibility::XAccessible > ThumbnailView::CreateAccessible()
    {
        return new ThumbnailViewAcc( this, mbIsTransientChildrenDisabled );
    }

    void ThumbnailView::CalculateItemPositions (bool bScrollBarUsed)
    {
        if (!mnItemHeight || !mnItemWidth)
            return;

        Size        aWinSize = GetOutputSizePixel();
        size_t      nItemCount = mFilteredItemList.size();
        WinBits     nStyle = GetStyle();
        VclPtr<ScrollBar>  pDelScrBar;

        // consider the scrolling
        if ( nStyle & WB_VSCROLL )
        {
            if ( !mpScrBar )
            {
                mpScrBar = VclPtr<ScrollBar>::Create( this, WB_VSCROLL | WB_DRAG );
                mpScrBar->SetScrollHdl( LINK( this, ThumbnailView, ImplScrollHdl ) );
            }
            else
            {
                // adapt the width because of the changed settings
                long nScrBarWidth = GetSettings().GetStyleSettings().GetScrollBarSize();
                mpScrBar->setPosSizePixel( 0, 0, nScrBarWidth, 0, PosSizeFlags::Width );
            }
        }
        else
        {
            if ( mpScrBar )
            {
                // delete ScrollBar not until later, to prevent recursive calls
                pDelScrBar = mpScrBar;
                mpScrBar = nullptr;
            }
        }

        // calculate window scroll ratio
        float nScrollRatio;
        if( bScrollBarUsed && mpScrBar )
            nScrollRatio = static_cast<float>(mpScrBar->GetThumbPos()) /
                            static_cast<float>(mpScrBar->GetRangeMax()-2);
        else
            nScrollRatio = 0;

        // calculate ScrollBar width
        long nScrBarWidth = 0;
        if ( mpScrBar )
            nScrBarWidth = mpScrBar->GetSizePixel().Width();

        // calculate maximum number of visible columns
        mnCols = static_cast<sal_uInt16>((aWinSize.Width()-nScrBarWidth) / (mnItemWidth));

        if (!mnCols)
            mnCols = 1;

        // calculate maximum number of visible rows
        mnVisLines = static_cast<sal_uInt16>(aWinSize.Height() / mnItemHeight);

        // calculate empty space
        long nHSpace = aWinSize.Width()-nScrBarWidth - mnCols*mnItemWidth;
        long nVSpace = aWinSize.Height() - mnVisLines*mnItemHeight;
        long nHItemSpace = nHSpace / (mnCols+1);
        long nVItemSpace = nVSpace / (mnVisLines+1);

        // calculate maximum number of rows
        // Floor( (M+N-1)/N )==Ceiling( M/N )
        mnLines = (static_cast<long>(nItemCount)+mnCols-1) / mnCols;

        if ( !mnLines )
            mnLines = 1;

        if ( mnLines <= mnVisLines )
            mnFirstLine = 0;
        else if ( mnFirstLine > static_cast<sal_uInt16>(mnLines-mnVisLines) )
            mnFirstLine = static_cast<sal_uInt16>(mnLines-mnVisLines);

        mbHasVisibleItems = true;

        long nItemHeightOffset = mnItemHeight + nVItemSpace;
        long nHiddenLines = (static_cast<long>(
            ( mnLines - 1 ) * nItemHeightOffset * nScrollRatio ) -
            nVItemSpace ) /
            nItemHeightOffset;

        // calculate offsets
        long nStartX = nHItemSpace;
        long nStartY = nVItemSpace;

        // calculate and draw items
        long x = nStartX;
        long y = nStartY - ( mnLines - 1 ) * nItemHeightOffset * nScrollRatio +
            nHiddenLines * nItemHeightOffset;

        // draw items
        // Unless we are scrolling (via scrollbar) we just use the precalculated
        // mnFirstLine -- our nHiddenLines calculation takes into account only
        // what the user has done with the scrollbar but not any changes of selection
        // using the keyboard, meaning we could accidentally hide the selected item
        // if we believe the scrollbar (fdo#72287).
        size_t nFirstItem = (bScrollBarUsed ? nHiddenLines : mnFirstLine) * mnCols;
        size_t nLastItem = nFirstItem + (mnVisLines + 1) * mnCols;

        // If want also draw parts of items in the last line,
        // then we add one more line if parts of these line are
        // visible

        size_t nCurCount = 0;
        for ( size_t i = 0; i < nItemCount; i++ )
        {
            ThumbnailViewItem *const pItem = mFilteredItemList[i];

            if ((nCurCount >= nFirstItem) && (nCurCount < nLastItem))
            {
                if( !pItem->isVisible())
                {
                    if ( ImplHasAccessibleListeners() )
                    {
                        css::uno::Any aOldAny, aNewAny;

                        aNewAny <<= pItem->GetAccessible( mbIsTransientChildrenDisabled );
                        ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny );
                    }

                    pItem->show(true);

                    maItemStateHdl.Call(pItem);
                }

                pItem->setDrawArea(::tools::Rectangle( Point(x,y), Size(mnItemWidth, mnItemHeight) ));
                pItem->calculateItemsPosition(mnThumbnailHeight,mnDisplayHeight,mnItemPadding,mpItemAttrs->nMaxTextLength,mpItemAttrs.get());

                if ( !((nCurCount+1) % mnCols) )
                {
                    x = nStartX;
                    y += mnItemHeight+nVItemSpace;
                }
                else
                    x += mnItemWidth+nHItemSpace;
            }
            else
            {
                if( pItem->isVisible())
                {
                    if ( ImplHasAccessibleListeners() )
                    {
                        css::uno::Any aOldAny, aNewAny;

                        aOldAny <<= pItem->GetAccessible( mbIsTransientChildrenDisabled );
                        ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny );
                    }

                    pItem->show(false);

                    maItemStateHdl.Call(pItem);
                }

            }

            ++nCurCount;
        }

        // arrange ScrollBar, set values and show it
        if ( mpScrBar )
        {
            mnLines = (nCurCount+mnCols-1)/mnCols;

            // check if scroll is needed
            mbScroll = mnLines > mnVisLines;

            Point aPos( aWinSize.Width() - nScrBarWidth, 0 );
            Size aSize( nScrBarWidth, aWinSize.Height() );

            mpScrBar->SetPosSizePixel( aPos, aSize );
            mpScrBar->SetRangeMax( (nCurCount+mnCols-1)*mnFineness/mnCols);
            mpScrBar->SetVisibleSize( mnVisLines );
            if (!bScrollBarUsed)
                mpScrBar->SetThumbPos( static_cast<long>(mnFirstLine)*mnFineness );
            long nPageSize = mnVisLines;
            if ( nPageSize < 1 )
                nPageSize = 1;
            mpScrBar->SetPageSize( nPageSize );
            mpScrBar->Show( mbScroll );
        }

        // delete ScrollBar
        pDelScrBar.disposeAndClear();
    }

    size_t ThumbnailView::ImplGetItem( const Point& rPos ) const
    {
        if ( !mbHasVisibleItems )
        {
            return THUMBNAILVIEW_ITEM_NOTFOUND;
        }

        for (size_t i = 0; i < mFilteredItemList.size(); ++i)
        {
            if (mFilteredItemList[i]->isVisible() && mFilteredItemList[i]->getDrawArea().IsInside(rPos))
                return i;
        }

        return THUMBNAILVIEW_ITEM_NOTFOUND;
    }

    ThumbnailViewItem* ThumbnailView::ImplGetItem( size_t nPos )
    {
        return ( nPos < mFilteredItemList.size() ) ? mFilteredItemList[nPos] : nullptr;
    }

    sal_uInt16 ThumbnailView::ImplGetVisibleItemCount() const
    {
        sal_uInt16 nRet = 0;
        const size_t nItemCount = mItemList.size();

        for ( size_t n = 0; n < nItemCount; ++n )
        {
            if ( mItemList[n]->isVisible() )
                ++nRet;
        }

        return nRet;
    }

    ThumbnailViewItem* ThumbnailView::ImplGetVisibleItem( sal_uInt16 nVisiblePos )
    {
        const size_t nItemCount = mItemList.size();

        for ( size_t n = 0; n < nItemCount; ++n )
        {
            ThumbnailViewItem *const pItem = mItemList[n];

            if ( pItem->isVisible() && !nVisiblePos-- )
                return pItem;
        }

        return nullptr;
    }

    void ThumbnailView::ImplFireAccessibleEvent( short nEventId, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue )
    {
        ThumbnailViewAcc* pAcc = ThumbnailViewAcc::getImplementation( GetAccessible( false ) );

        if( pAcc )
            pAcc->FireAccessibleEvent( nEventId, rOldValue, rNewValue );
    }

    bool ThumbnailView::ImplHasAccessibleListeners()
    {
        ThumbnailViewAcc* pAcc = ThumbnailViewAcc::getImplementation( GetAccessible( false ) );
        return( pAcc && pAcc->HasAccessibleListeners() );
    }

    IMPL_LINK( ThumbnailView,ImplScrollHdl, ScrollBar*, pScrollBar, void )
    {
        if ( pScrollBar->GetDelta() )
        {
            CalculateItemPositions(true);

            if ( IsReallyVisible() && IsUpdateMode() )
                Invalidate();
        }
    }

    void ThumbnailView::KeyInput( const KeyEvent& rKEvt )
    {
        // Get the last selected item in the list
        size_t nLastPos = 0;
        bool bFoundLast = false;
        for ( long i = mFilteredItemList.size() - 1; !bFoundLast && i >= 0; --i )
        {
            ThumbnailViewItem* pItem = mFilteredItemList[i];
            if ( pItem->isSelected() )
            {
                nLastPos = i;
                bFoundLast = true;
            }
        }

        bool bValidRange = false;
        bool bHasSelRange = mpStartSelRange != mFilteredItemList.end();
        size_t nNextPos = nLastPos;
        vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
        ThumbnailViewItem* pNext = nullptr;

        if (aKeyCode.IsShift() && bHasSelRange)
        {
            //If the last element selected is the start range position
            //search for the first selected item
            size_t nSelPos = mpStartSelRange - mFilteredItemList.begin();

            if (nLastPos == nSelPos)
            {
                while (nLastPos && mFilteredItemList[nLastPos-1]->isSelected())
                    --nLastPos;
            }
        }

        switch ( aKeyCode.GetCode() )
        {
            case KEY_RIGHT:
                if (!mFilteredItemList.empty())
                {
                    if ( bFoundLast && nLastPos + 1 < mFilteredItemList.size() )
                    {
                        bValidRange = true;
                        nNextPos = nLastPos + 1;
                    }

                    pNext = mFilteredItemList[nNextPos];
                }
                break;
            case KEY_LEFT:
                if (!mFilteredItemList.empty())
                {
                    if ( nLastPos > 0 )
                    {
                        bValidRange = true;
                        nNextPos = nLastPos - 1;
                    }

                    pNext = mFilteredItemList[nNextPos];
                }
                break;
            case KEY_DOWN:
                if (!mFilteredItemList.empty())
                {
                    if ( bFoundLast )
                    {
                        //If we are in the second last row just go the one in
                        //the row below, if there's not row below just go to the
                        //last item but for the last row don't do anything.
                        if ( nLastPos + mnCols < mFilteredItemList.size( ) )
                        {
                            bValidRange = true;
                            nNextPos = nLastPos + mnCols;
                        }
                        else
                        {
                            int curRow = nLastPos/mnCols;

                            if (curRow < mnLines-1)
                                nNextPos = mFilteredItemList.size()-1;
                        }
                    }

                    pNext = mFilteredItemList[nNextPos];
                }
                break;
            case KEY_UP:
                if (!mFilteredItemList.empty())
                {
                    if ( nLastPos >= mnCols )
                    {
                        bValidRange = true;
                        nNextPos = nLastPos - mnCols;
                    }

                    pNext = mFilteredItemList[nNextPos];
                }
                break;
            case KEY_RETURN:
                {
                    if ( bFoundLast )
                        OnItemDblClicked( mFilteredItemList[nLastPos] );
                }
                SAL_FALLTHROUGH;
            default:
                Control::KeyInput( rKEvt );
        }

        if ( pNext  && mbIsMultiSelectionEnabled)
        {
            if (aKeyCode.IsShift() && bValidRange)
            {
                std::pair<size_t,size_t> aRange;
                size_t nSelPos = mpStartSelRange - mFilteredItemList.begin();

                if (nLastPos < nSelPos)
                {
                    if (nNextPos > nLastPos)
                    {
                        if ( nNextPos > nSelPos)
                            aRange = std::make_pair(nLastPos,nNextPos);
                        else
                            aRange = std::make_pair(nLastPos,nNextPos-1);
                    }
                    else
                        aRange = std::make_pair(nNextPos,nLastPos-1);
                }
                else if (nLastPos == nSelPos)
                {
                    if (nNextPos > nLastPos)
                        aRange = std::make_pair(nLastPos+1,nNextPos);
                    else
                        aRange = std::make_pair(nNextPos,nLastPos-1);
                }
                else
                {
                    if (nNextPos > nLastPos)
                        aRange = std::make_pair(nLastPos+1,nNextPos);
                    else
                    {
                        if ( nNextPos < nSelPos)
                            aRange = std::make_pair(nNextPos,nLastPos);
                        else
                            aRange = std::make_pair(nNextPos+1,nLastPos);
                    }
                }

                for (size_t i = aRange.first; i <= aRange.second; ++i)
                {
                    if (i != nSelPos)
                    {
                        ThumbnailViewItem *pCurItem = mFilteredItemList[i];

                        pCurItem->setSelection(!pCurItem->isSelected());

                        if (pCurItem->isVisible())
                            DrawItem(pCurItem);

                        maItemStateHdl.Call(pCurItem);
                    }
                }
            }
            else if (!aKeyCode.IsShift())
            {
                deselectItems();
                SelectItem(pNext->mnId);

                //Mark it as the selection range start position
                mpStartSelRange = mFilteredItemList.begin() + nNextPos;
            }

            MakeItemVisible(pNext->mnId);
        }
        else if(pNext && !mbIsMultiSelectionEnabled)
        {
            deselectItems();
            SelectItem(pNext->mnId);
            MakeItemVisible(pNext->mnId);
        }
    }

    void ThumbnailView::MakeItemVisible( sal_uInt16 nItemId )
    {
        // Get the item row
        size_t nPos = 0;
        bool bFound = false;
        for ( size_t i = 0; !bFound && i < mFilteredItemList.size(); ++i )
        {
            ThumbnailViewItem* pItem = mFilteredItemList[i];
            if ( pItem->mnId == nItemId )
            {
                nPos = i;
                bFound = true;
            }
        }
        sal_uInt16 nRow = mnCols ? nPos / mnCols : 0;

        // Move the visible rows as little as possible to include that one
        if ( nRow < mnFirstLine )
            mnFirstLine = nRow;
        else if ( nRow > mnFirstLine + mnVisLines )
            mnFirstLine = nRow - mnVisLines;

        CalculateItemPositions();
        Invalidate();
    }

    void ThumbnailView::MouseButtonDown( const MouseEvent& rMEvt )
    {
        if ( !rMEvt.IsLeft() )
        {
            Control::MouseButtonDown( rMEvt );
            return;
        }

        size_t nPos = ImplGetItem(rMEvt.GetPosPixel());
        ThumbnailViewItem* pItem = ImplGetItem(nPos);

        if ( !pItem )
        {
            deselectItems();
            Control::MouseButtonDown( rMEvt );
            return;
        }

        if ( rMEvt.GetClicks() == 2 )
        {
            OnItemDblClicked(pItem);
            return;
        }

        if ( rMEvt.GetClicks() == 1 && !mbIsMultiSelectionEnabled )
        {
            deselectItems();
            pItem->setSelection(!pItem->isSelected());

            if (!pItem->isHighlighted())
                DrawItem(pItem);

            maItemStateHdl.Call(pItem);
        }
        else if(rMEvt.GetClicks() == 1)
        {
            if (rMEvt.IsMod1())
            {
                //Keep selected item group state and just invert current desired one state
                pItem->setSelection(!pItem->isSelected());

                //This one becomes the selection range start position if it changes its state to selected otherwise resets it
                mpStartSelRange = pItem->isSelected() ? mFilteredItemList.begin() + nPos : mFilteredItemList.end();
            }
            else if (rMEvt.IsShift() && mpStartSelRange != mFilteredItemList.end())
            {
                std::pair<size_t,size_t> aNewRange;
                aNewRange.first = mpStartSelRange - mFilteredItemList.begin();
                aNewRange.second = nPos;

                if (aNewRange.first > aNewRange.second)
                    std::swap(aNewRange.first,aNewRange.second);

                //Deselect the ones outside of it
                for (size_t i = 0, n = mFilteredItemList.size(); i < n; ++i)
                {
                    ThumbnailViewItem *pCurItem  = mFilteredItemList[i];

                    if (pCurItem->isSelected() && (i < aNewRange.first || i > aNewRange.second))
                    {
                        pCurItem->setSelection(false);

                        if (pCurItem->isVisible())
                            DrawItem(pCurItem);

                        maItemStateHdl.Call(pCurItem);
                    }
                }

                size_t nSelPos = mpStartSelRange - mFilteredItemList.begin();

                //Select the items between start range and the selected item
                if (nSelPos != nPos)
                {
                    int dir = nSelPos < nPos ? 1 : -1;
                    size_t nCurPos = nSelPos + dir;

                    while (nCurPos != nPos)
                    {
                        ThumbnailViewItem *pCurItem  = mFilteredItemList[nCurPos];

                        if (!pCurItem->isSelected())
                        {
                            pCurItem->setSelection(true);

                            if (pCurItem->isVisible())
                                DrawItem(pCurItem);

                            maItemStateHdl.Call(pCurItem);
                        }

                        nCurPos += dir;
                    }
                }

                pItem->setSelection(true);
            }
            else
            {
                //If we got a group of selected items deselect the rest and only keep the desired one
                //mark items as not selected to not fire unnecessary change state events.
                pItem->setSelection(false);
                deselectItems();
                pItem->setSelection(true);

                //Mark as initial selection range position and reset end one
                mpStartSelRange = mFilteredItemList.begin() + nPos;
            }

            if (!pItem->isHighlighted())
                DrawItem(pItem);

            maItemStateHdl.Call(pItem);

            //fire accessible event??
        }
    }

    void ThumbnailView::Command( const CommandEvent& rCEvt )
    {
        if ( (rCEvt.GetCommand() == CommandEventId::Wheel) ||
             (rCEvt.GetCommand() == CommandEventId::StartAutoScroll) ||
             (rCEvt.GetCommand() == CommandEventId::AutoScroll) )
        {
            if ( HandleScrollCommand( rCEvt, nullptr, mpScrBar ) )
                return;
        }

        Control::Command( rCEvt );
    }

    void ThumbnailView::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect)
    {
        size_t nItemCount = mItemList.size();

        // Draw background
        drawinglayer::primitive2d::Primitive2DContainer aSeq(1);
        aSeq[0] = drawinglayer::primitive2d::Primitive2DReference(
                new PolyPolygonColorPrimitive2D(
                        B2DPolyPolygon( ::tools::Polygon(::tools::Rectangle(Point(), GetOutputSizePixel()), 0, 0).getB2DPolygon()),
                        maFillColor.getBColor()));

        // Create the processor and process the primitives
        const drawinglayer::geometry::ViewInformation2D aNewViewInfos;

        std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor(
            drawinglayer::processor2d::createBaseProcessor2DFromOutputDevice(rRenderContext, aNewViewInfos));
        pProcessor->process(aSeq);

        // draw items
        for (size_t i = 0; i < nItemCount; i++)
        {
            ThumbnailViewItem *const pItem = mItemList[i];

            if (pItem->isVisible())
            {
                pItem->Paint(pProcessor.get(), mpItemAttrs.get());
            }
        }

        if (mpScrBar && mpScrBar->IsVisible())
            mpScrBar->Invalidate(rRect);
    }

    void ThumbnailView::GetFocus()
    {
        // Select the first item if nothing selected
        int nSelected = -1;
        for (size_t i = 0, n = mItemList.size(); i < n && nSelected == -1; ++i)
        {
            if (mItemList[i]->isSelected())
                nSelected = i;
        }

        if (nSelected == -1 && mItemList.size() > 0)
        {
            SelectItem(1);
        }

        // Tell the accessible object that we got the focus.
        ThumbnailViewAcc* pAcc = ThumbnailViewAcc::getImplementation( GetAccessible( false ) );
        if( pAcc )
            pAcc->GetFocus();

        Control::GetFocus();
    }

    void ThumbnailView::LoseFocus()
    {
        Control::LoseFocus();

        // Tell the accessible object that we lost the focus.
        ThumbnailViewAcc* pAcc = ThumbnailViewAcc::getImplementation( GetAccessible( false ) );
        if( pAcc )
            pAcc->LoseFocus();
    }

    void ThumbnailView::Resize()
    {
        Control::Resize();
        CalculateItemPositions();

        if ( IsReallyVisible() && IsUpdateMode() )
            Invalidate();
    }

    void ThumbnailView::StateChanged( StateChangedType nType )
    {
        Control::StateChanged( nType );

        if ( nType == StateChangedType::InitShow )
        {
            if ( IsReallyVisible() && IsUpdateMode() )
                Invalidate();
        }
        else if ( nType == StateChangedType::UpdateMode )
        {
            if ( IsReallyVisible() && IsUpdateMode() )
                Invalidate();
        }
        else if ( nType == StateChangedType::Text )
        {
        }
        else if ( (nType == StateChangedType::Zoom) ||
                  (nType == StateChangedType::ControlFont) )
        {
            Invalidate();
        }
        else if ( nType == StateChangedType::ControlForeground )
        {
            Invalidate();
        }
        else if ( nType == StateChangedType::ControlBackground )
        {
            Invalidate();
        }
        else if ( (nType == StateChangedType::Style) || (nType == StateChangedType::Enable) )
        {
            Invalidate();
        }
    }

    void ThumbnailView::DataChanged( const DataChangedEvent& rDCEvt )
    {
        Control::DataChanged( rDCEvt );

        if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
             (rDCEvt.GetType() == DataChangedEventType::DISPLAY) ||
             (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
             ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
              (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
        {
            Invalidate();
        }
    }

    void ThumbnailView::RemoveItem( sal_uInt16 nItemId )
    {
        size_t nPos = GetItemPos( nItemId );

        if ( nPos == THUMBNAILVIEW_ITEM_NOTFOUND )
            return;

        if ( nPos < mFilteredItemList.size() ) {

            // delete item from the thumbnail list
            for (size_t i = 0, n = mItemList.size(); i < n; ++i)
            {
                if (mItemList[i]->mnId == nItemId)
                {
                    mItemList.erase(mItemList.begin()+i);
                    break;
                }
            }

            // delete item from the filter item list
            ThumbnailValueItemList::iterator it = mFilteredItemList.begin();
            ::std::advance( it, nPos );

            if ((*it)->isSelected())
            {
                (*it)->setSelection(false);
                maItemStateHdl.Call(*it);
            }

            delete *it;
            mFilteredItemList.erase( it );
            mpStartSelRange = mFilteredItemList.end();
        }

        CalculateItemPositions();

        if ( IsReallyVisible() && IsUpdateMode() )
            Invalidate();
    }

    void ThumbnailView::Clear()
    {
        ImplDeleteItems();

        // reset variables
        mnFirstLine     = 0;

        CalculateItemPositions();

        if ( IsReallyVisible() && IsUpdateMode() )
            Invalidate();
    }

    void ThumbnailView::updateItems (const std::vector<ThumbnailViewItem*> &items)
    {
        ImplDeleteItems();

        // reset variables
        mnFirstLine     = 0;

        mItemList = items;

        filterItems(maFilterFunc);
    }

    size_t ThumbnailView::GetItemPos( sal_uInt16 nItemId ) const
    {
        for ( size_t i = 0, n = mFilteredItemList.size(); i < n; ++i ) {
            if ( mFilteredItemList[i]->mnId == nItemId ) {
                return i;
            }
        }
        return THUMBNAILVIEW_ITEM_NOTFOUND;
    }

    sal_uInt16 ThumbnailView::GetItemId( size_t nPos ) const
    {
        return ( nPos < mFilteredItemList.size() ) ? mFilteredItemList[nPos]->mnId : 0 ;
    }

    sal_uInt16 ThumbnailView::GetItemId( const Point& rPos ) const
    {
        size_t nItemPos = ImplGetItem( rPos );
        if ( nItemPos != THUMBNAILVIEW_ITEM_NOTFOUND )
            return GetItemId( nItemPos );

        return 0;
    }

    sal_uInt16 ThumbnailView::getNextItemId() const
    {
        return mItemList.empty() ? 1 : mItemList.back()->mnId + 1;
    }

    void ThumbnailView::setItemMaxTextLength(sal_uInt32 nLength)
    {
        mpItemAttrs->nMaxTextLength = nLength;
    }

    void ThumbnailView::setItemDimensions(long itemWidth, long thumbnailHeight, long displayHeight, int itemPadding)
    {
        mnItemWidth = itemWidth + 2*itemPadding;
        mnThumbnailHeight = thumbnailHeight;
        mnDisplayHeight = displayHeight;
        mnItemPadding = itemPadding;
        mnItemHeight = mnDisplayHeight + mnThumbnailHeight + 2*itemPadding;
    }

    void ThumbnailView::SelectItem( sal_uInt16 nItemId )
    {
        size_t nItemPos = GetItemPos( nItemId );
        if ( nItemPos == THUMBNAILVIEW_ITEM_NOTFOUND )
            return;

        ThumbnailViewItem* pItem = mFilteredItemList[nItemPos];
        if (!pItem->isSelected())
        {
            pItem->setSelection(true);
            maItemStateHdl.Call(pItem);

            if (IsReallyVisible() && IsUpdateMode())
                Invalidate();

            bool bNewOut = IsReallyVisible() && IsUpdateMode();

            // if necessary scroll to the visible area
            if (mbScroll && nItemId && mnCols)
            {
                sal_uInt16 nNewLine = static_cast<sal_uInt16>(nItemPos / mnCols);
                if ( nNewLine < mnFirstLine )
                {
                    mnFirstLine = nNewLine;
                }
                else if ( nNewLine > static_cast<sal_uInt16>(mnFirstLine+mnVisLines-1) )
                {
                    mnFirstLine = static_cast<sal_uInt16>(nNewLine-mnVisLines+1);
                }
            }

            if ( bNewOut )
            {
                if ( IsReallyVisible() && IsUpdateMode() )
                    Invalidate();
            }

            if( ImplHasAccessibleListeners() )
            {
                // focus event (select)
                ThumbnailViewItemAcc* pItemAcc = ThumbnailViewItemAcc::getImplementation( pItem->GetAccessible( mbIsTransientChildrenDisabled ) );

                if( pItemAcc )
                {
                    css::uno::Any aOldAny, aNewAny;
                    if( !mbIsTransientChildrenDisabled )
                    {
                        aNewAny <<= css::uno::Reference< css::uno::XInterface >(
                            static_cast< ::cppu::OWeakObject* >( pItemAcc ));
                        ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldAny, aNewAny );
                    }
                    else
                    {
                        aNewAny <<= css::accessibility::AccessibleStateType::FOCUSED;
                        pItemAcc->FireAccessibleEvent( css::accessibility::AccessibleEventId::STATE_CHANGED, aOldAny, aNewAny );
                    }
                }

                // selection event
                css::uno::Any aOldAny, aNewAny;
                ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::SELECTION_CHANGED, aOldAny, aNewAny );
            }
        }
    }

    bool ThumbnailView::IsItemSelected( sal_uInt16 nItemId ) const
    {
        size_t nItemPos = GetItemPos( nItemId );
        if ( nItemPos == THUMBNAILVIEW_ITEM_NOTFOUND )
            return false;

        ThumbnailViewItem* pItem = mFilteredItemList[nItemPos];
        return pItem->isSelected();
    }

    void ThumbnailView::deselectItems()
    {
        for (ThumbnailViewItem* p : mItemList)
        {
            if (p->isSelected())
            {
                p->setSelection(false);

                maItemStateHdl.Call(p);
            }
        }

        if (IsReallyVisible() && IsUpdateMode())
            Invalidate();
    }

    void ThumbnailView::ShowTooltips( bool bShowTooltips )
    {
        mbShowTooltips = bShowTooltips;
    }

    void ThumbnailView::SetMultiSelectionEnabled( bool bIsMultiSelectionEnabled )
    {
        mbIsMultiSelectionEnabled = bIsMultiSelectionEnabled;
    }

    void ThumbnailView::filterItems(const std::function<bool (const ThumbnailViewItem*)> &func)
    {
        mnFirstLine = 0;        // start at the top of the list instead of the current position
        maFilterFunc = func;

        size_t nSelPos = 0;
        bool bHasSelRange = false;
        ThumbnailViewItem *curSel = mpStartSelRange != mFilteredItemList.end() ? *mpStartSelRange : nullptr;

        mFilteredItemList.clear();

        for (size_t i = 0, n = mItemList.size(); i < n; ++i)
        {
            ThumbnailViewItem *const pItem = mItemList[i];

            if (maFilterFunc(pItem))
            {
                if (curSel == pItem)
                {
                    nSelPos = i;
                    bHasSelRange = true;
                }

                mFilteredItemList.push_back(pItem);
            }
            else
            {
                if( pItem->isVisible())
                {
                    if ( ImplHasAccessibleListeners() )
                    {
                        css::uno::Any aOldAny, aNewAny;

                        aOldAny <<= pItem->GetAccessible( mbIsTransientChildrenDisabled );
                        ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny );
                    }

                    pItem->show(false);
                    pItem->setSelection(false);

                    maItemStateHdl.Call(pItem);
                }
            }
        }

        mpStartSelRange = bHasSelRange ? mFilteredItemList.begin()  + nSelPos : mFilteredItemList.end();
        CalculateItemPositions();

        Invalidate();
    }

    bool ThumbnailView::renameItem(ThumbnailViewItem*, const OUString&)
    {
        // Do nothing by default
        return false;
    }

    BitmapEx ThumbnailView::readThumbnail(const OUString &msURL)
    {
        using namespace ::com::sun::star;
        using namespace ::com::sun::star::uno;

        // Load the thumbnail from a template document.
        uno::Reference<io::XInputStream> xIStream;

        uno::Reference< uno::XComponentContext > xContext(::comphelper::getProcessComponentContext());
        try
        {
            uno::Reference<lang::XSingleServiceFactory> xStorageFactory = embed::StorageFactory::create(xContext);

            uno::Sequence<uno::Any> aArgs (2);
            aArgs[0] <<= msURL;
            aArgs[1] <<= embed::ElementModes::READ;
            uno::Reference<embed::XStorage> xDocStorage (
                xStorageFactory->createInstanceWithArguments(aArgs),
                uno::UNO_QUERY);

            try
            {
                if (xDocStorage.is())
                {
                    uno::Reference<embed::XStorage> xStorage (
                        xDocStorage->openStorageElement(
                            "Thumbnails",
                            embed::ElementModes::READ));
                    if (xStorage.is())
                    {
                        uno::Reference<io::XStream> xThumbnailCopy (
                            xStorage->cloneStreamElement("thumbnail.png"));
                        if (xThumbnailCopy.is())
                            xIStream = xThumbnailCopy->getInputStream();
                    }
                }
            }
            catch (const uno::Exception& rException)
            {
                SAL_WARN("sfx",
                    "caught exception while trying to access Thumbnail/thumbnail.png of "
                     << msURL << ": " << rException);
            }

            try
            {
                // An (older) implementation had a bug - The storage
                // name was "Thumbnail" instead of "Thumbnails".  The
                // old name is still used as fallback but this code can
                // be removed soon.
                if ( ! xIStream.is())
                {
                    uno::Reference<embed::XStorage> xStorage (
                        xDocStorage->openStorageElement( "Thumbnail",
                            embed::ElementModes::READ));
                    if (xStorage.is())
                    {
                        uno::Reference<io::XStream> xThumbnailCopy (
                            xStorage->cloneStreamElement("thumbnail.png"));
                        if (xThumbnailCopy.is())
                            xIStream = xThumbnailCopy->getInputStream();
                    }
                }
            }
            catch (const uno::Exception& rException)
            {
                SAL_WARN("sfx",
                    "caught exception while trying to access Thumbnails/thumbnail.png of "
                    << msURL << ": " << rException);
            }
        }
        catch (const uno::Exception& rException)
        {
            SAL_WARN("sfx",
                "caught exception while trying to access thumbnail of "
                << msURL << ": " << rException);
        }

        // Extract the image from the stream.
        BitmapEx aThumbnail;
        if (xIStream.is())
        {
            std::unique_ptr<SvStream> pStream (
                ::utl::UcbStreamHelper::CreateStream (xIStream));
            vcl::PNGReader aReader (*pStream);
            aThumbnail = aReader.Read ();
        }

        // Note that the preview is returned without scaling it to the desired
        // width.  This gives the caller the chance to take advantage of a
        // possibly larger resolution then was asked for.
        return aThumbnail;
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: mnThumbnailHeight, mnDisplayHeight.