/* -*- 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 <dp_shared.hxx>
#include <strings.hrc>
#include "dp_gui.h"
#include "dp_gui_extlistbox.hxx"
#include "dp_gui_theextmgr.hxx"
#include <dp_dependencies.hxx>
#include <bitmaps.hlst>
 
#include <comphelper/processfactory.hxx>
#include <com/sun/star/i18n/CollatorOptions.hpp>
#include <com/sun/star/deployment/DependencyException.hpp>
#include <com/sun/star/deployment/DeploymentException.hpp>
#include <com/sun/star/deployment/ExtensionRemovedException.hpp>
#include <com/sun/star/system/XSystemShellExecute.hpp>
#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
#include <com/sun/star/system/SystemShellExecute.hpp>
#include <cppuhelper/weakref.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <rtl/ustrbuf.hxx>
#include <utility>
#include <vcl/event.hxx>
#include <vcl/ptrstyle.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/weld/weldutils.hxx>
#include <algorithm>
 
constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user";
constexpr OUStringLiteral SHARED_PACKAGE_MANAGER = u"shared";
 
using namespace ::com::sun::star;
 
namespace dp_gui {
 
namespace {
 
struct FindWeakRef
{
    const uno::Reference<deployment::XPackage> m_extension;
 
    explicit FindWeakRef( uno::Reference<deployment::XPackage> ext): m_extension(std::move(ext)) {}
    bool operator () (uno::WeakReference< deployment::XPackage >  const & ref);
};
 
bool FindWeakRef::operator () (uno::WeakReference< deployment::XPackage >  const & ref)
{
    const uno::Reference<deployment::XPackage> ext(ref);
    return ext == m_extension;
}
 
} // end namespace
 
//                          struct Entry_Impl
 
Entry_Impl::Entry_Impl( const uno::Reference< deployment::XPackage > &xPackage,
                        const PackageState eState, const bool bReadOnly ) :
    m_bLocked( bReadOnly ),
    m_bHasOptions( false ),
    m_bUser( false ),
    m_bShared( false ),
    m_bNew( false ),
    m_bChecked( false ),
    m_bMissingDeps( false ),
    m_bHasButtons( false ),
    m_bMissingLic( false ),
    m_eState( eState ),
    m_xPackage( xPackage )
{
    try
    {
        m_sTitle = xPackage->getDisplayName();
        m_sVersion = xPackage->getVersion();
        m_sDescription = xPackage->getDescription();
        m_sLicenseText = xPackage->getLicenseText();
 
        beans::StringPair aInfo( m_xPackage->getPublisherInfo() );
        m_sPublisher = aInfo.First;
        m_sPublisherURL = aInfo.Second;
 
        // get the icons for the package if there are any
        uno::Reference< graphic::XGraphic > xGraphic = xPackage->getIcon( false );
        if ( xGraphic.is() )
            m_aIcon = Image( xGraphic );
 
        if (eState == PackageState::AMBIGUOUS)
            m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
        else if (eState == PackageState::NOT_REGISTERED)
            checkDependencies();
    }
    catch (const deployment::ExtensionRemovedException &) {}
    catch (const uno::RuntimeException &) {}
}
 
 
Entry_Impl::~Entry_Impl()
{}
 
 
sal_Int32 Entry_Impl::CompareTo(const CollatorWrapper& rCollator, const Entry_Impl& rEntry) const
{
    sal_Int32 eCompare = rCollator.compareString(m_sTitle, rEntry.m_sTitle);
    if ( eCompare == 0 )
    {
        eCompare = m_sVersion.compareTo(rEntry.m_sVersion);
        if ( eCompare == 0 )
        {
            sal_Int32 nCompare = m_xPackage->getRepositoryName().compareTo(rEntry.m_xPackage->getRepositoryName());
            if ( nCompare < 0 )
                eCompare = -1;
            else if ( nCompare > 0 )
                eCompare = 1;
        }
    }
    return eCompare;
}
 
 
void Entry_Impl::checkDependencies()
{
    try {
        m_xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
    }
    catch ( const deployment::DeploymentException &e )
    {
        deployment::DependencyException depExc;
        if ( e.Cause >>= depExc )
        {
            OUStringBuffer aMissingDep( DpResId( RID_STR_ERROR_MISSING_DEPENDENCIES ) );
            for (const auto& i : depExc.UnsatisfiedDependencies)
            {
                aMissingDep.append("\n"
                    + dp_misc::Dependencies::getErrorText(i));
            }
            aMissingDep.append("\n");
            m_sErrorText = aMissingDep.makeStringAndClear();
            m_bMissingDeps = true;
        }
    }
}
 
// ExtensionRemovedListener
 
void ExtensionRemovedListener::disposing( lang::EventObject const & rEvt )
{
    uno::Reference< deployment::XPackage > xPackage( rEvt.Source, uno::UNO_QUERY );
 
    if ( xPackage.is() )
    {
        m_pParent->removeEntry( xPackage );
    }
}
 
 
ExtensionRemovedListener::~ExtensionRemovedListener()
{
}
 
ExtensionBox::ExtensionBox(std::unique_ptr<weld::ScrolledWindow> xScroll,
                           TheExtensionManager& rManager)
    : m_bHasScrollBar( false )
    , m_bNeedsRecalc( true )
    , m_bInCheckMode( false )
    , m_bAdjustActive( false )
    , m_bInDelete( false )
    , m_nActive(-1)
    , m_nTopIndex( 0 )
    , m_nStdHeight( 0 )
    , m_nActiveHeight( 0 )
    , m_aSharedImage(StockImage::Yes, RID_BMP_SHARED)
    , m_aLockedImage(StockImage::Yes, RID_BMP_LOCKED)
    , m_aWarningImage(StockImage::Yes, RID_BMP_WARNING)
    , m_aDefaultImage(StockImage::Yes, RID_BMP_EXTENSION)
    , m_rManager(rManager)
    , m_xScrollBar(std::move(xScroll))
{
}
 
void ExtensionBox::Init()
{
    m_xScrollBar->connect_vadjustment_value_changed(LINK(this, ExtensionBox, ScrollHdl));
 
    auto nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
    auto nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
    if ( nIconHeight < nTitleHeight )
        m_nStdHeight = nTitleHeight;
    else
        m_nStdHeight = nIconHeight;
    m_nStdHeight += GetTextHeight() + TOP_OFFSET;
 
    nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1;
    if ( m_nStdHeight < nIconHeight )
        m_nStdHeight = nIconHeight;
 
    m_nActiveHeight = m_nStdHeight;
 
    m_xRemoveListener = new ExtensionRemovedListener( this );
 
    m_oCollator.emplace( ::comphelper::getProcessComponentContext() );
    m_oCollator->loadDefaultCollator(Application::GetSettings().GetLanguageTag().getLocale(),
                                     i18n::CollatorOptions::CollatorOptions_IGNORE_CASE);
}
 
ExtensionBox::~ExtensionBox()
{
    if ( ! m_bInDelete )
        DeleteRemoved();
 
    m_bInDelete = true;
 
    for (auto const& entry : m_vEntries)
    {
        entry->m_xPackage->removeEventListener( m_xRemoveListener );
    }
 
    m_vEntries.clear();
 
    m_xRemoveListener.clear();
 
    m_oCollator.reset();
}
 
sal_Int32 ExtensionBox::getSelIndex() const
{
    assert(m_nActive >= -1);
    return static_cast<sal_Int32>(m_nActive);
}
 
 
// Title + description
void ExtensionBox::CalcActiveHeight(const tools::Long nPos)
{
    const ::osl::MutexGuard aGuard( m_entriesMutex );
 
    // get title height
    tools::Long aTextHeight;
    tools::Long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
    tools::Long nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
    if ( nIconHeight < nTitleHeight )
        aTextHeight = nTitleHeight;
    else
        aTextHeight = nIconHeight;
 
    // calc description height
    Size aSize = GetOutputSizePixel();
 
    aSize.AdjustWidth( -(ICON_OFFSET) );
    aSize.setHeight( 10000 );
 
    OUString aText( m_vEntries[ nPos ]->m_sErrorText );
    if ( !aText.isEmpty() )
        aText += "\n";
    aText += m_vEntries[ nPos ]->m_sDescription;
 
    tools::Rectangle aRect = GetDrawingArea()->get_ref_device().GetTextRect(tools::Rectangle( Point(), aSize ), aText,
                                                                            DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
    aTextHeight += aRect.GetHeight();
 
    if ( aTextHeight < m_nStdHeight )
        aTextHeight = m_nStdHeight;
 
    m_nActiveHeight = aTextHeight;
 
    if ( m_vEntries[ nPos ]->m_bHasButtons )
        m_nActiveHeight += 2;
}
 
tools::Rectangle ExtensionBox::GetEntryRect(tools::Long nIndex) const
{
    const ::osl::MutexGuard aGuard(m_entriesMutex);
 
    tools::Long nY = -m_nTopIndex;
    if (m_nActive >= 0 && nIndex > m_nActive)
        nY += (nIndex - 1) * m_nStdHeight + m_nActiveHeight;
    else
        nY += nIndex * m_nStdHeight;
    Point aStart(0, nY);
 
    Size aSize(GetOutputSizePixel());
    aSize.setHeight(nIndex == m_nActive ? m_nActiveHeight : m_nStdHeight);
 
    return tools::Rectangle(aStart, aSize);
}
 
void ExtensionBox::DeleteRemoved()
{
    const ::osl::MutexGuard aGuard( m_entriesMutex );
 
    m_bInDelete = true;
 
    m_vRemovedEntries.clear();
 
    m_bInDelete = false;
}
 
 
//This function may be called with nPos < 0
void ExtensionBox::selectEntry(const tools::Long nPos)
{
    bool invalidate = false;
    {
        //ToDo we should not use the guard at such a big scope here.
        //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be
        //modified in this function.
        //It would be probably best to always use a copy of m_vEntries
        //and some other state variables from ExtensionBox for
        //the whole painting operation. See issue i86993
        ::osl::MutexGuard guard(m_entriesMutex);
 
        if ( m_bInCheckMode )
            return;
 
        if (m_nActive >= 0)
        {
            if ( nPos == m_nActive )
                return;
 
            m_nActive = -1;
        }
 
        if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
        {
            m_nActive = nPos;
 
            if ( IsReallyVisible() )
            {
                m_bAdjustActive = true;
            }
        }
 
        if ( IsReallyVisible() )
        {
            m_bNeedsRecalc = true;
            invalidate = true;
        }
    }
 
    if (invalidate)
    {
        SolarMutexGuard g;
        Invalidate();
    }
}
 
void ExtensionBox::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect,
                           const TEntry_Impl& rEntry, bool bActive)
{
    const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
 
    if (bActive)
        rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
    else if ((rEntry->m_eState != PackageState::REGISTERED)
             && (rEntry->m_eState != PackageState::NOT_AVAILABLE))
        rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
    else
        rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
 
    if (bActive)
    {
        rRenderContext.SetLineColor();
        rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
        rRenderContext.DrawRect(rRect);
    }
    else
    {
        rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
        rRenderContext.SetTextFillColor();
        rRenderContext.Erase(rRect);
    }
 
    // Draw extension icon
    Point aPos( rRect.TopLeft() );
    aPos += Point(TOP_OFFSET, TOP_OFFSET);
    Image aImage;
    if (!rEntry->m_aIcon)
        aImage = m_aDefaultImage;
    else
        aImage = rEntry->m_aIcon;
    Size aImageSize = aImage.GetSizePixel();
    if ((aImageSize.Width() <= ICON_WIDTH ) && ( aImageSize.Height() <= ICON_HEIGHT ) )
        rRenderContext.DrawImage(Point(aPos.X() + ((ICON_WIDTH - aImageSize.Width()) / 2),
                                       aPos.Y() + ((ICON_HEIGHT - aImageSize.Height()) / 2)),
                                 aImage);
    else
        rRenderContext.DrawImage(aPos, Size(ICON_WIDTH, ICON_HEIGHT), aImage);
 
    // Setup fonts
    // expand the point size of the desired font to the equivalent pixel size
    weld::SetPointFont(rRenderContext, GetDrawingArea()->get_font());
    vcl::Font aStdFont(rRenderContext.GetFont());
    vcl::Font aBoldFont(aStdFont);
    aBoldFont.SetWeight(WEIGHT_BOLD);
    rRenderContext.SetFont(aBoldFont);
    auto aTextHeight = rRenderContext.GetTextHeight();
 
    // Get max title width
    auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET;
    nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN);
    rRenderContext.SetFont(aStdFont);
    tools::Long nLinkWidth = 0;
    if (!rEntry->m_sPublisher.isEmpty())
    {
        nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher);
        nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN);
    }
    tools::Long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion);
 
    aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET);
 
    rRenderContext.SetFont(aBoldFont);
    tools::Long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3);
    if (aTitleWidth > nMaxTitleWidth - aVersionWidth)
    {
        aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3);
        OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth);
        rRenderContext.DrawText(aPos, aShortTitle);
        aTitleWidth += (aTextHeight / 3);
    }
    else
        rRenderContext.DrawText(aPos, rEntry->m_sTitle);
 
    rRenderContext.SetFont(aStdFont);
    rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion);
 
    tools::Long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE;
    tools::Long nTitleHeight = TOP_OFFSET + GetTextHeight();
    if ( nIconHeight < nTitleHeight )
        aTextHeight = nTitleHeight;
    else
        aTextHeight = nIconHeight;
 
    // draw description
    OUString sDescription;
    if (!rEntry->m_sErrorText.isEmpty())
    {
        if (bActive)
            sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription;
        else
            sDescription = rEntry->m_sErrorText;
    }
    else
        sDescription = rEntry->m_sDescription;
 
    aPos.AdjustY(aTextHeight );
    if (bActive)
    {
        tools::Long nExtraHeight = 0;
 
        if (rEntry->m_bHasButtons)
            nExtraHeight = 2;
 
        rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight),
                                sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
    }
    else
    {
        //replace LF to space, so words do not stick together in one line view
        sDescription = sDescription.replace(0x000A, ' ');
        const tools::Long nWidth = rRenderContext.GetTextWidth( sDescription );
        if (nWidth > rRect.GetWidth() - aPos.X())
            sDescription = rRenderContext.GetEllipsisString(sDescription, rRect.GetWidth() - aPos.X());
        rRenderContext.DrawText(aPos, sDescription);
    }
 
    // Draw publisher link
    if (!rEntry->m_sPublisher.isEmpty())
    {
        aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET );
 
        auto popIt = rRenderContext.ScopedPush(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR | vcl::PushFlags::TEXTFILLCOLOR);
        rRenderContext.SetTextColor(rStyleSettings.GetLinkColor());
        rRenderContext.SetTextFillColor(rStyleSettings.GetFieldColor());
        vcl::Font aFont = rRenderContext.GetFont();
        // to underline
        aFont.SetUnderline(LINESTYLE_SINGLE);
        rRenderContext.SetFont(aFont);
        rRenderContext.DrawText(aPos, rEntry->m_sPublisher);
        rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight));
    }
 
    // Draw status icons
    if (!rEntry->m_bUser)
    {
        aPos = rRect.TopRight() + Point( -(RIGHT_ICON_OFFSET + SMALL_ICON_SIZE), TOP_OFFSET );
        if (rEntry->m_bLocked)
            rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aLockedImage);
        else
            rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aSharedImage);
    }
    if ((rEntry->m_eState == PackageState::AMBIGUOUS) || rEntry->m_bMissingDeps
        || rEntry->m_bMissingLic)
    {
        aPos = rRect.TopRight() + Point(-(RIGHT_ICON_OFFSET + SPACE_BETWEEN + 2 * SMALL_ICON_SIZE), TOP_OFFSET);
        rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aWarningImage);
    }
 
    rRenderContext.SetLineColor(COL_LIGHTGRAY);
    rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight());
}
 
void ExtensionBox::RecalcAll()
{
    if (m_nActive >= 0)
        CalcActiveHeight( m_nActive );
 
    SetupScrollBar();
 
    if (m_nActive >= 0 && m_bAdjustActive)
    {
        m_bAdjustActive = false;
 
        // If the top of the selected entry isn't visible, make it visible
        tools::Rectangle aEntryRect = GetEntryRect(m_nActive);
        if (aEntryRect.Top() < 0)
        {
            m_nTopIndex += aEntryRect.Top();
            aEntryRect.Move(0, -aEntryRect.Top());
        }
 
        // If the bottom of the selected entry isn't visible, make it visible even if now the top
        // isn't visible any longer ( the buttons are more important )
        Size aOutputSize = GetOutputSizePixel();
        if (aEntryRect.Bottom() > aOutputSize.Height())
        {
            m_nTopIndex += (aEntryRect.Bottom() - aOutputSize.Height());
            aEntryRect.Move(0, -(aEntryRect.Bottom() - aOutputSize.Height()));
        }
 
        // If there is unused space below the last entry but all entries don't fit into the box,
        // move the content down to use the whole space
        const tools::Long nTotalHeight = GetTotalHeight();
        if (m_bHasScrollBar && (aOutputSize.Height() + m_nTopIndex > nTotalHeight))
        {
            tools::Long nOffset = m_nTopIndex;
            m_nTopIndex = nTotalHeight - aOutputSize.Height();
            nOffset -= m_nTopIndex;
            aEntryRect.Move(0, nOffset);
        }
 
        if (m_bHasScrollBar)
            m_xScrollBar->vadjustment_set_value(m_nTopIndex);
    }
 
    m_bNeedsRecalc = false;
}
 
bool ExtensionBox::HandleCursorKey(sal_uInt16 nKeyCode)
{
    if ( m_vEntries.empty() )
        return true;
 
    tools::Long nSelect = 0;
 
    if (HasActive())
    {
        tools::Long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight;
        if ( nPageSize < 2 )
            nPageSize = 2;
 
        if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) )
            nSelect = m_nActive + 1;
        else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) )
            nSelect = m_nActive - 1;
        else if ( nKeyCode == KEY_HOME )
            nSelect = 0;
        else if ( nKeyCode == KEY_END )
            nSelect = m_vEntries.size() - 1;
        else if ( nKeyCode == KEY_PAGEUP )
            nSelect = m_nActive - nPageSize + 1;
        else if ( nKeyCode == KEY_PAGEDOWN )
            nSelect = m_nActive + nPageSize - 1;
    }
    else // when there is no selected entry, we will select the first or the last.
    {
        if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) )
            nSelect = 0;
        else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) )
            nSelect = m_vEntries.size() - 1;
    }
 
    if ( nSelect < 0 )
        nSelect = 0;
    if ( o3tl::make_unsigned(nSelect) >= m_vEntries.size() )
        nSelect = m_vEntries.size() - 1;
 
    selectEntry( nSelect );
 
    return true;
}
 
void ExtensionBox::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/)
{
    if ( !m_bInDelete )
        DeleteRemoved();
 
    if ( m_bNeedsRecalc )
        RecalcAll();
 
    const ::osl::MutexGuard aGuard( m_entriesMutex );
 
    for (tools::Long i = 0; i < GetEntryCount(); ++i)
    {
        const bool bActive = i == m_nActive;
        DrawRow(rRenderContext, GetEntryRect(i), GetEntryData(i), bActive);
    }
}
 
tools::Long ExtensionBox::GetTotalHeight() const
{
    tools::Long nHeight = m_vEntries.size() * m_nStdHeight;
 
    if (HasActive())
    {
        nHeight += m_nActiveHeight - m_nStdHeight;
    }
 
    return nHeight;
}
 
void ExtensionBox::SetupScrollBar()
{
    const Size aSize = GetOutputSizePixel();
    const auto nTotalHeight = GetTotalHeight();
    const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() );
 
    if ( bNeedsScrollBar )
    {
        if ( m_nTopIndex + aSize.Height() > nTotalHeight )
            m_nTopIndex = nTotalHeight - aSize.Height();
 
        m_xScrollBar->vadjustment_configure(m_nTopIndex, nTotalHeight, m_nStdHeight,
                                            (aSize.Height() * 4) / 5, aSize.Height());
 
        if (!m_bHasScrollBar)
            m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS);
    }
    else if ( m_bHasScrollBar )
    {
        m_xScrollBar->set_vpolicy(VclPolicyType::NEVER);
        m_nTopIndex = 0;
    }
 
    m_bHasScrollBar = bNeedsScrollBar;
}
 
void ExtensionBox::Resize()
{
    RecalcAll();
    Invalidate();
}
 
void ExtensionBox::SetDrawingArea(weld::DrawingArea* pDrawingArea)
{
    Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(250, 150), MapMode(MapUnit::MapAppFont));
    pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
    CustomWidgetController::SetDrawingArea(pDrawingArea);
    SetOutputSizePixel(aSize);
 
    Init();
}
 
tools::Long ExtensionBox::PointToPos(const Point& rPos)
{
    tools::Long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight;
 
    if (m_nActive >= 0 && (nPos > m_nActive))
    {
        if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight )
            nPos = m_nActive;
        else
            nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight;
    }
 
    return nPos;
}
 
bool ExtensionBox::MouseMove(const MouseEvent& rMEvt)
{
    bool bOverHyperlink = false;
 
    auto nPos = PointToPos( rMEvt.GetPosPixel() );
    if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
    {
        const auto& rEntry = m_vEntries[nPos];
        bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel());
    }
 
    if (bOverHyperlink)
        SetPointer(PointerStyle::RefHand);
    else
        SetPointer(PointerStyle::Arrow);
 
    return false;
}
 
OUString ExtensionBox::RequestHelp(tools::Rectangle& rRect)
{
    auto nPos = PointToPos( rRect.TopLeft() );
    if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
    {
        const auto& rEntry = m_vEntries[nPos];
        bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rRect);
        if (bOverHyperlink)
        {
            rRect = rEntry->m_aLinkRect;
            return rEntry->m_sPublisherURL;
        }
    }
 
    return OUString();
}
 
bool ExtensionBox::MouseButtonDown(const MouseEvent& rMEvt)
{
    if ( !rMEvt.IsLeft() )
        return false;
 
    GrabFocus();
 
    if (rMEvt.IsMod1() && m_nActive >= 0)
        selectEntry(ExtensionBox::ENTRY_NOTFOUND);   // Selecting a not existing entry will deselect the current one
    else
    {
        auto nPos = PointToPos( rMEvt.GetPosPixel() );
 
        if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
        {
            const auto& rEntry = m_vEntries[nPos];
            if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel()))
            {
                try
                {
                    css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute(
                        css::system::SystemShellExecute::create(comphelper::getProcessComponentContext()));
                    //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
                    xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY);
                }
                catch (...)
                {
                }
                return true;
            }
        }
 
        selectEntry( nPos );
    }
    return true;
}
 
bool ExtensionBox::KeyInput(const KeyEvent& rKEvt)
{
    if ( !m_bInDelete )
        DeleteRemoved();
 
    vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
    sal_uInt16 nKeyCode = aKeyCode.GetCode();
 
    bool bHandled = false;
    if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR)
        bHandled = HandleCursorKey(nKeyCode);
 
    return bHandled;
}
 
bool ExtensionBox::FindEntryPos(const TEntry_Impl& rEntry, const tools::Long nStart,
                                const tools::Long nEnd, tools::Long& nPos)
{
    nPos = nStart;
    if ( nStart > nEnd )
        return false;
 
    sal_Int32 eCompare;
 
    if ( nStart == nEnd )
    {
        eCompare = rEntry->CompareTo(*m_oCollator, *m_vEntries[nStart]);
        if ( eCompare < 0 )
            return false;
        else if ( eCompare == 0 )
        {
            //Workaround. See i86963.
            if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage)
                return false;
 
            if ( m_bInCheckMode )
                m_vEntries[ nStart ]->m_bChecked = true;
            return true;
        }
        else
        {
            nPos = nStart + 1;
            return false;
        }
    }
 
    const tools::Long nMid = nStart + ( ( nEnd - nStart ) / 2 );
    eCompare = rEntry->CompareTo(*m_oCollator, *m_vEntries[nMid]);
 
    if ( eCompare < 0 )
        return FindEntryPos( rEntry, nStart, nMid-1, nPos );
    else if ( eCompare > 0 )
        return FindEntryPos( rEntry, nMid+1, nEnd, nPos );
    else
    {
        //Workaround.See i86963.
        if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage)
            return false;
 
        if ( m_bInCheckMode )
            m_vEntries[ nMid ]->m_bChecked = true;
        nPos = nMid;
        return true;
    }
}
 
void ExtensionBox::cleanVecListenerAdded()
{
    std::erase_if(m_vListenerAdded,
        [](const uno::WeakReference<deployment::XPackage>& rxListener) {
            const uno::Reference<deployment::XPackage> hardRef(rxListener);
            return !hardRef.is();
        });
}
 
void ExtensionBox::addEventListenerOnce(uno::Reference<deployment::XPackage> const& extension)
{
    //make sure to only add the listener once
    cleanVecListenerAdded();
    if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(),
                        FindWeakRef(extension)) )
    {
        extension->addEventListener( m_xRemoveListener );
        m_vListenerAdded.emplace_back(extension);
    }
}
 
void ExtensionBox::addEntry(const uno::Reference<deployment::XPackage>& xPackage,
                            bool bLicenseMissing)
{
    PackageState eState = TheExtensionManager::getPackageState( xPackage );
    bool bLocked = m_rManager.isReadOnly(xPackage);
 
    TEntry_Impl pEntry = std::make_shared<Entry_Impl>( xPackage, eState, bLocked );
 
    // Don't add empty entries
    if ( pEntry->m_sTitle.isEmpty() )
        return;
 
    {
        osl::MutexGuard guard(m_entriesMutex);
        tools::Long nPos = 0;
        if (m_vEntries.empty())
        {
            addEventListenerOnce(xPackage);
            m_vEntries.push_back(pEntry);
        }
        else
        {
            if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos))
            {
                addEventListenerOnce(xPackage);
                m_vEntries.insert(m_vEntries.begin() + nPos, pEntry);
            }
            else if (!m_bInCheckMode)
            {
                OSL_FAIL("ExtensionBox::addEntry(): Will not add duplicate entries");
            }
        }
 
        pEntry->m_bHasOptions = m_rManager.supportsOptions(xPackage);
        pEntry->m_bUser = (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER);
        pEntry->m_bShared = (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER);
        pEntry->m_bNew = m_bInCheckMode;
        pEntry->m_bMissingLic = bLicenseMissing;
 
        if (bLicenseMissing)
            pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE);
 
        //access to m_nActive must be guarded
        if (!m_bInCheckMode && (m_nActive >= nPos))
            m_nActive += 1;
    }
 
    if ( IsReallyVisible() )
        Invalidate();
 
    m_bNeedsRecalc = true;
}
 
void ExtensionBox::updateEntry(const uno::Reference<deployment::XPackage>& xPackage)
{
    for (auto const& entry : m_vEntries)
    {
        if ( entry->m_xPackage == xPackage )
        {
            PackageState eState = TheExtensionManager::getPackageState( xPackage );
            entry->m_bHasOptions = m_rManager.supportsOptions(xPackage);
            entry->m_eState = eState;
            entry->m_sTitle = xPackage->getDisplayName();
            entry->m_sVersion = xPackage->getVersion();
            entry->m_sDescription = xPackage->getDescription();
 
            if (eState == PackageState::REGISTERED)
                entry->m_bMissingLic = false;
 
            if (eState == PackageState::AMBIGUOUS)
                entry->m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
            else if ( ! entry->m_bMissingLic )
                entry->m_sErrorText.clear();
 
            if ( IsReallyVisible() )
                Invalidate();
            break;
        }
    }
}
 
//This function is also called as a result of removing an extension.
//see PackageManagerImpl::removePackage
//The gui is a registered as listener on the package. Removing it will cause the
//listeners to be notified and then this function is called. At this moment xPackage
//is in the disposing state and all calls on it may result in a DisposedException.
void ExtensionBox::removeEntry(const uno::Reference<deployment::XPackage>& xPackage)
{
    if (  m_bInDelete )
        return;
 
    bool invalidate = false;
    {
        ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
 
        auto iIndex = std::find_if(m_vEntries.begin(), m_vEntries.end(),
            [&xPackage](const TEntry_Impl& rxEntry) { return rxEntry->m_xPackage == xPackage; });
        if (iIndex != m_vEntries.end())
        {
            tools::Long nPos = iIndex - m_vEntries.begin();
 
            // Entries mustn't be removed here, because they contain a hyperlink control
            // which can only be deleted when the thread has the solar mutex. Therefore
            // the entry will be moved into the m_vRemovedEntries list which will be
            // cleared on the next paint event
            m_vRemovedEntries.push_back( *iIndex );
            (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener);
            m_vEntries.erase( iIndex );
 
            m_bNeedsRecalc = true;
 
            if ( IsReallyVisible() )
                invalidate = true;
 
            if (m_nActive >= 0)
            {
                if ( nPos < m_nActive )
                    m_nActive -= 1;
                else if ( ( nPos == m_nActive ) &&
                          ( nPos == static_cast<tools::Long>(m_vEntries.size()) ) )
                    m_nActive -= 1;
 
                const tools::Long nActive = m_nActive;
                m_nActive = -1;
                //clear before calling out of this method
                aGuard.clear();
                selectEntry(nActive);
            }
        }
    }
 
    if (invalidate)
    {
        SolarMutexGuard g;
        Invalidate();
    }
}
 
void ExtensionBox::RemoveUnlocked()
{
    bool bAllRemoved = false;
 
    while ( ! bAllRemoved )
    {
        bAllRemoved = true;
 
        ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
 
        for (auto const& entry : m_vEntries)
        {
            if ( !entry->m_bLocked )
            {
                bAllRemoved = false;
                uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage;
                aGuard.clear();
                removeEntry( xPackage );
                break;
            }
        }
    }
}
 
void ExtensionBox::prepareChecking()
{
    m_bInCheckMode = true;
    for (auto const& entry : m_vEntries)
    {
        entry->m_bChecked = false;
        entry->m_bNew = false;
    }
}
 
void ExtensionBox::checkEntries()
{
    tools::Long nNewPos = -1;
    tools::Long nChangedActivePos = -1;
    tools::Long nPos = 0;
    bool bNeedsUpdate = false;
 
    {
        osl::MutexGuard guard(m_entriesMutex);
        auto iIndex = m_vEntries.begin();
        while (iIndex != m_vEntries.end())
        {
            if (!(*iIndex)->m_bChecked)
            {
                (*iIndex)->m_bChecked = true;
                bNeedsUpdate = true;
                nPos = iIndex - m_vEntries.begin();
                if ((*iIndex)->m_bNew)
                { // add entry to list and correct active pos
                    if (nNewPos == -1)
                        nNewPos = nPos;
                    if (nPos <= m_nActive)
                        m_nActive += 1;
                    ++iIndex;
                }
                else
                { // remove entry from list
                    if (nPos < nNewPos)
                    {
                        --nNewPos;
                    }
                    if (nPos < nChangedActivePos)
                    {
                        --nChangedActivePos;
                    }
                    if (nPos < m_nActive)
                        m_nActive -= 1;
                    else if (nPos == m_nActive)
                    {
                        nChangedActivePos = nPos;
                        m_nActive = -1;
                    }
                    m_vRemovedEntries.push_back(*iIndex);
                    (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener);
                    iIndex = m_vEntries.erase(iIndex);
                }
            }
            else
                ++iIndex;
        }
    }
 
    m_bInCheckMode = false;
 
    if ( nNewPos != - 1)
        selectEntry( nNewPos );
    else if (nChangedActivePos != -1) {
        selectEntry(nChangedActivePos);
    }
 
    if ( bNeedsUpdate )
    {
        m_bNeedsRecalc = true;
        if ( IsReallyVisible() )
            Invalidate();
    }
}
 
IMPL_LINK(ExtensionBox, ScrollHdl, weld::ScrolledWindow&, rScrBar, void)
{
    m_nTopIndex = rScrBar.vadjustment_get_value();
    Invalidate();
}
 
} //namespace dp_gui
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1044 Loop break conditions do not depend on the number of iterations.