diff options
Diffstat (limited to 'desktop/source/deployment/gui/dp_gui_extlistbox.cxx')
-rw-r--r-- | desktop/source/deployment/gui/dp_gui_extlistbox.cxx | 1143 |
1 files changed, 1143 insertions, 0 deletions
diff --git a/desktop/source/deployment/gui/dp_gui_extlistbox.cxx b/desktop/source/deployment/gui/dp_gui_extlistbox.cxx new file mode 100644 index 0000000000..e7f91f44ab --- /dev/null +++ b/desktop/source/deployment/gui/dp_gui_extlistbox.cxx @@ -0,0 +1,1143 @@ +/* -*- 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/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_bActive( false ), + 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 == AMBIGUOUS ) + m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS ); + else if ( eState == NOT_REGISTERED ) + checkDependencies(); + } + catch (const deployment::ExtensionRemovedException &) {} + catch (const uno::RuntimeException &) {} +} + + +Entry_Impl::~Entry_Impl() +{} + + +sal_Int32 Entry_Impl::CompareTo( const CollatorWrapper *pCollator, const TEntry_Impl& rEntry ) const +{ + sal_Int32 eCompare = pCollator->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 : std::as_const(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_Impl +ExtensionBox_Impl::ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll) + : m_bHasScrollBar( false ) + , m_bHasActive( false ) + , m_bNeedsRecalc( true ) + , m_bInCheckMode( false ) + , m_bAdjustActive( false ) + , m_bInDelete( false ) + , m_nActive( 0 ) + , 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_pManager( nullptr ) + , m_xScrollBar(std::move(xScroll)) +{ +} + +void ExtensionBox_Impl::Init() +{ + m_xScrollBar->connect_vadjustment_changed( LINK( this, ExtensionBox_Impl, 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_pLocale.reset( new lang::Locale( Application::GetSettings().GetLanguageTag().getLocale() ) ); + m_oCollator.emplace( ::comphelper::getProcessComponentContext() ); + m_oCollator->loadDefaultCollator( *m_pLocale, i18n::CollatorOptions::CollatorOptions_IGNORE_CASE ); +} + +ExtensionBox_Impl::~ExtensionBox_Impl() +{ + 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_pLocale.reset(); + m_oCollator.reset(); +} + +sal_Int32 ExtensionBox_Impl::getItemCount() const +{ + return static_cast< sal_Int32 >( m_vEntries.size() ); +} + + +sal_Int32 ExtensionBox_Impl::getSelIndex() const +{ + if ( m_bHasActive ) + { + OSL_ASSERT( m_nActive >= -1); + return static_cast< sal_Int32 >( m_nActive ); + } + else + return ENTRY_NOTFOUND; +} + + +// Title + description +void ExtensionBox_Impl::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_Impl::GetEntryRect( const tools::Long nPos ) const +{ + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + Size aSize( GetOutputSizePixel() ); + + if ( m_vEntries[ nPos ]->m_bActive ) + aSize.setHeight( m_nActiveHeight ); + else + aSize.setHeight( m_nStdHeight ); + + Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight ); + if ( m_bHasActive && ( nPos < m_nActive ) ) + aPos.AdjustY(m_nActiveHeight - m_nStdHeight ); + + return tools::Rectangle( aPos, aSize ); +} + + +void ExtensionBox_Impl::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_Impl::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_Impl for + //the whole painting operation. See issue i86993 + ::osl::MutexGuard guard(m_entriesMutex); + + if ( m_bInCheckMode ) + return; + + if ( m_bHasActive ) + { + if ( nPos == m_nActive ) + return; + + m_bHasActive = false; + m_vEntries[ m_nActive ]->m_bActive = false; + } + + if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) ) + { + m_bHasActive = true; + m_nActive = nPos; + m_vEntries[ nPos ]->m_bActive = true; + + if ( IsReallyVisible() ) + { + m_bAdjustActive = true; + } + } + + if ( IsReallyVisible() ) + { + m_bNeedsRecalc = true; + invalidate = true; + } + } + + if (invalidate) + { + SolarMutexGuard g; + Invalidate(); + } +} + + +void ExtensionBox_Impl::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + if (rEntry->m_bActive) + rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor()); + else if ((rEntry->m_eState != REGISTERED) && (rEntry->m_eState != NOT_AVAILABLE)) + rRenderContext.SetTextColor(rStyleSettings.GetDisableColor()); + else + rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor()); + + if (rEntry->m_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 (rEntry->m_bActive) + sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription; + else + sDescription = rEntry->m_sErrorText; + } + else + sDescription = rEntry->m_sDescription; + + aPos.AdjustY(aTextHeight ); + if (rEntry->m_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 ); + + rRenderContext.Push(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)); + rRenderContext.Pop(); + } + + // 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 == 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_Impl::RecalcAll() +{ + if ( m_bHasActive ) + CalcActiveHeight( m_nActive ); + + SetupScrollBar(); + + if ( m_bHasActive ) + { + tools::Rectangle aEntryRect = GetEntryRect( m_nActive ); + + if ( m_bAdjustActive ) + { + m_bAdjustActive = false; + + // If the top of the selected entry isn't visible, make it visible + 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_Impl::HandleCursorKey( sal_uInt16 nKeyCode ) +{ + if ( m_vEntries.empty() ) + return true; + + tools::Long nSelect = 0; + + if ( m_bHasActive ) + { + 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_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/) +{ + if ( !m_bInDelete ) + DeleteRemoved(); + + if ( m_bNeedsRecalc ) + RecalcAll(); + + Point aStart( 0, -m_nTopIndex ); + Size aSize(GetOutputSizePixel()); + + const ::osl::MutexGuard aGuard( m_entriesMutex ); + + for (auto const& entry : m_vEntries) + { + aSize.setHeight( entry->m_bActive ? m_nActiveHeight : m_nStdHeight ); + tools::Rectangle aEntryRect( aStart, aSize ); + DrawRow(rRenderContext, aEntryRect, entry); + aStart.AdjustY(aSize.Height() ); + } +} + + +tools::Long ExtensionBox_Impl::GetTotalHeight() const +{ + tools::Long nHeight = m_vEntries.size() * m_nStdHeight; + + if ( m_bHasActive ) + { + nHeight += m_nActiveHeight - m_nStdHeight; + } + + return nHeight; +} + + +void ExtensionBox_Impl::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, 0, 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_Impl::Resize() +{ + RecalcAll(); + Invalidate(); +} + +void ExtensionBox_Impl::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_Impl::PointToPos( const Point& rPos ) +{ + tools::Long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight; + + if ( m_bHasActive && ( 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_Impl::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_Impl::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_Impl::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !rMEvt.IsLeft() ) + return false; + + if (rMEvt.IsMod1() && m_bHasActive) + selectEntry(ExtensionBox_Impl::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_Impl::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_Impl::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_Impl::cleanVecListenerAdded() +{ + std::erase_if(m_vListenerAdded, + [](const uno::WeakReference<deployment::XPackage>& rxListener) { + const uno::Reference<deployment::XPackage> hardRef(rxListener); + return !hardRef.is(); + }); +} + +void ExtensionBox_Impl::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_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage, + bool bLicenseMissing ) +{ + PackageState eState = TheExtensionManager::getPackageState( xPackage ); + bool bLocked = m_pManager->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_Impl::addEntry(): Will not add duplicate entries"); + } + } + + pEntry->m_bHasOptions = m_pManager->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_bHasActive && (m_nActive >= nPos)) + m_nActive += 1; + } + + if ( IsReallyVisible() ) + Invalidate(); + + m_bNeedsRecalc = true; +} + +void ExtensionBox_Impl::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_pManager->supportsOptions( xPackage ); + entry->m_eState = eState; + entry->m_sTitle = xPackage->getDisplayName(); + entry->m_sVersion = xPackage->getVersion(); + entry->m_sDescription = xPackage->getDescription(); + + if ( eState == REGISTERED ) + entry->m_bMissingLic = false; + + if ( eState == 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_Impl::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_bHasActive ) + { + if ( nPos < m_nActive ) + m_nActive -= 1; + else if ( ( nPos == m_nActive ) && + ( nPos == static_cast<tools::Long>(m_vEntries.size()) ) ) + m_nActive -= 1; + + m_bHasActive = false; + //clear before calling out of this method + aGuard.clear(); + selectEntry( m_nActive ); + } + } + } + + if (invalidate) + { + SolarMutexGuard g; + Invalidate(); + } +} + + +void ExtensionBox_Impl::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_Impl::prepareChecking() +{ + m_bInCheckMode = true; + for (auto const& entry : m_vEntries) + { + entry->m_bChecked = false; + entry->m_bNew = false; + } +} + + +void ExtensionBox_Impl::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_bHasActive = false; + } + 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_Impl, ScrollHdl, weld::ScrolledWindow&, rScrBar, void) +{ + m_nTopIndex = rScrBar.vadjustment_get_value(); + Invalidate(); +} + +} //namespace dp_gui + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |