Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / desktop / source / deployment / gui / dp_gui_extlistbox.cxx
blob9953e0549a7ba3fed3fb2ead4e3f4feca8db875b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <dp_shared.hxx>
21 #include <strings.hrc>
22 #include "dp_gui.h"
23 #include "dp_gui_extlistbox.hxx"
24 #include "dp_gui_theextmgr.hxx"
25 #include <dp_dependencies.hxx>
26 #include <bitmaps.hlst>
28 #include <comphelper/processfactory.hxx>
29 #include <com/sun/star/i18n/CollatorOptions.hpp>
30 #include <com/sun/star/deployment/DependencyException.hpp>
31 #include <com/sun/star/deployment/DeploymentException.hpp>
32 #include <com/sun/star/deployment/ExtensionRemovedException.hpp>
33 #include <com/sun/star/system/XSystemShellExecute.hpp>
34 #include <com/sun/star/system/SystemShellExecuteFlags.hpp>
35 #include <com/sun/star/system/SystemShellExecute.hpp>
36 #include <cppuhelper/weakref.hxx>
37 #include <i18nlangtag/languagetag.hxx>
38 #include <o3tl/safeint.hxx>
39 #include <osl/diagnose.h>
40 #include <rtl/ustrbuf.hxx>
41 #include <utility>
42 #include <vcl/event.hxx>
43 #include <vcl/ptrstyle.hxx>
44 #include <vcl/svapp.hxx>
45 #include <vcl/settings.hxx>
46 #include <vcl/weldutils.hxx>
47 #include <algorithm>
49 constexpr OUStringLiteral USER_PACKAGE_MANAGER = u"user";
50 constexpr OUStringLiteral SHARED_PACKAGE_MANAGER = u"shared";
52 using namespace ::com::sun::star;
54 namespace dp_gui {
56 namespace {
58 struct FindWeakRef
60 const uno::Reference<deployment::XPackage> m_extension;
62 explicit FindWeakRef( uno::Reference<deployment::XPackage> ext): m_extension(std::move(ext)) {}
63 bool operator () (uno::WeakReference< deployment::XPackage > const & ref);
66 bool FindWeakRef::operator () (uno::WeakReference< deployment::XPackage > const & ref)
68 const uno::Reference<deployment::XPackage> ext(ref);
69 return ext == m_extension;
72 } // end namespace
74 // struct Entry_Impl
76 Entry_Impl::Entry_Impl( const uno::Reference< deployment::XPackage > &xPackage,
77 const PackageState eState, const bool bReadOnly ) :
78 m_bActive( false ),
79 m_bLocked( bReadOnly ),
80 m_bHasOptions( false ),
81 m_bUser( false ),
82 m_bShared( false ),
83 m_bNew( false ),
84 m_bChecked( false ),
85 m_bMissingDeps( false ),
86 m_bHasButtons( false ),
87 m_bMissingLic( false ),
88 m_eState( eState ),
89 m_xPackage( xPackage )
91 try
93 m_sTitle = xPackage->getDisplayName();
94 m_sVersion = xPackage->getVersion();
95 m_sDescription = xPackage->getDescription();
96 m_sLicenseText = xPackage->getLicenseText();
98 beans::StringPair aInfo( m_xPackage->getPublisherInfo() );
99 m_sPublisher = aInfo.First;
100 m_sPublisherURL = aInfo.Second;
102 // get the icons for the package if there are any
103 uno::Reference< graphic::XGraphic > xGraphic = xPackage->getIcon( false );
104 if ( xGraphic.is() )
105 m_aIcon = Image( xGraphic );
107 if ( eState == AMBIGUOUS )
108 m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
109 else if ( eState == NOT_REGISTERED )
110 checkDependencies();
112 catch (const deployment::ExtensionRemovedException &) {}
113 catch (const uno::RuntimeException &) {}
117 Entry_Impl::~Entry_Impl()
121 sal_Int32 Entry_Impl::CompareTo( const CollatorWrapper *pCollator, const TEntry_Impl& rEntry ) const
123 sal_Int32 eCompare = pCollator->compareString( m_sTitle, rEntry->m_sTitle );
124 if ( eCompare == 0 )
126 eCompare = m_sVersion.compareTo( rEntry->m_sVersion );
127 if ( eCompare == 0 )
129 sal_Int32 nCompare = m_xPackage->getRepositoryName().compareTo( rEntry->m_xPackage->getRepositoryName() );
130 if ( nCompare < 0 )
131 eCompare = -1;
132 else if ( nCompare > 0 )
133 eCompare = 1;
136 return eCompare;
140 void Entry_Impl::checkDependencies()
142 try {
143 m_xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
145 catch ( const deployment::DeploymentException &e )
147 deployment::DependencyException depExc;
148 if ( e.Cause >>= depExc )
150 OUStringBuffer aMissingDep( DpResId( RID_STR_ERROR_MISSING_DEPENDENCIES ) );
151 for ( const auto& i : std::as_const(depExc.UnsatisfiedDependencies) )
153 aMissingDep.append("\n"
154 + dp_misc::Dependencies::getErrorText(i));
156 aMissingDep.append("\n");
157 m_sErrorText = aMissingDep.makeStringAndClear();
158 m_bMissingDeps = true;
163 // ExtensionRemovedListener
165 void ExtensionRemovedListener::disposing( lang::EventObject const & rEvt )
167 uno::Reference< deployment::XPackage > xPackage( rEvt.Source, uno::UNO_QUERY );
169 if ( xPackage.is() )
171 m_pParent->removeEntry( xPackage );
176 ExtensionRemovedListener::~ExtensionRemovedListener()
181 // ExtensionBox_Impl
182 ExtensionBox_Impl::ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll)
183 : m_bHasScrollBar( false )
184 , m_bHasActive( false )
185 , m_bNeedsRecalc( true )
186 , m_bInCheckMode( false )
187 , m_bAdjustActive( false )
188 , m_bInDelete( false )
189 , m_nActive( 0 )
190 , m_nTopIndex( 0 )
191 , m_nStdHeight( 0 )
192 , m_nActiveHeight( 0 )
193 , m_aSharedImage(StockImage::Yes, RID_BMP_SHARED)
194 , m_aLockedImage(StockImage::Yes, RID_BMP_LOCKED)
195 , m_aWarningImage(StockImage::Yes, RID_BMP_WARNING)
196 , m_aDefaultImage(StockImage::Yes, RID_BMP_EXTENSION)
197 , m_pManager( nullptr )
198 , m_xScrollBar(std::move(xScroll))
202 void ExtensionBox_Impl::Init()
204 m_xScrollBar->connect_vadjustment_changed( LINK( this, ExtensionBox_Impl, ScrollHdl ) );
206 auto nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
207 auto nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
208 if ( nIconHeight < nTitleHeight )
209 m_nStdHeight = nTitleHeight;
210 else
211 m_nStdHeight = nIconHeight;
212 m_nStdHeight += GetTextHeight() + TOP_OFFSET;
214 nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1;
215 if ( m_nStdHeight < nIconHeight )
216 m_nStdHeight = nIconHeight;
218 m_nActiveHeight = m_nStdHeight;
220 m_xRemoveListener = new ExtensionRemovedListener( this );
222 m_pLocale.reset( new lang::Locale( Application::GetSettings().GetLanguageTag().getLocale() ) );
223 m_oCollator.emplace( ::comphelper::getProcessComponentContext() );
224 m_oCollator->loadDefaultCollator( *m_pLocale, i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
227 ExtensionBox_Impl::~ExtensionBox_Impl()
229 if ( ! m_bInDelete )
230 DeleteRemoved();
232 m_bInDelete = true;
234 for (auto const& entry : m_vEntries)
236 entry->m_xPackage->removeEventListener( m_xRemoveListener );
239 m_vEntries.clear();
241 m_xRemoveListener.clear();
243 m_pLocale.reset();
244 m_oCollator.reset();
247 sal_Int32 ExtensionBox_Impl::getItemCount() const
249 return static_cast< sal_Int32 >( m_vEntries.size() );
253 sal_Int32 ExtensionBox_Impl::getSelIndex() const
255 if ( m_bHasActive )
257 OSL_ASSERT( m_nActive >= -1);
258 return static_cast< sal_Int32 >( m_nActive );
260 else
261 return ENTRY_NOTFOUND;
265 // Title + description
266 void ExtensionBox_Impl::CalcActiveHeight( const tools::Long nPos )
268 const ::osl::MutexGuard aGuard( m_entriesMutex );
270 // get title height
271 tools::Long aTextHeight;
272 tools::Long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
273 tools::Long nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
274 if ( nIconHeight < nTitleHeight )
275 aTextHeight = nTitleHeight;
276 else
277 aTextHeight = nIconHeight;
279 // calc description height
280 Size aSize = GetOutputSizePixel();
282 aSize.AdjustWidth( -(ICON_OFFSET) );
283 aSize.setHeight( 10000 );
285 OUString aText( m_vEntries[ nPos ]->m_sErrorText );
286 if ( !aText.isEmpty() )
287 aText += "\n";
288 aText += m_vEntries[ nPos ]->m_sDescription;
290 tools::Rectangle aRect = GetDrawingArea()->get_ref_device().GetTextRect(tools::Rectangle( Point(), aSize ), aText,
291 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
292 aTextHeight += aRect.GetHeight();
294 if ( aTextHeight < m_nStdHeight )
295 aTextHeight = m_nStdHeight;
297 m_nActiveHeight = aTextHeight;
299 if ( m_vEntries[ nPos ]->m_bHasButtons )
300 m_nActiveHeight += 2;
303 tools::Rectangle ExtensionBox_Impl::GetEntryRect( const tools::Long nPos ) const
305 const ::osl::MutexGuard aGuard( m_entriesMutex );
307 Size aSize( GetOutputSizePixel() );
309 if ( m_vEntries[ nPos ]->m_bActive )
310 aSize.setHeight( m_nActiveHeight );
311 else
312 aSize.setHeight( m_nStdHeight );
314 Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight );
315 if ( m_bHasActive && ( nPos < m_nActive ) )
316 aPos.AdjustY(m_nActiveHeight - m_nStdHeight );
318 return tools::Rectangle( aPos, aSize );
322 void ExtensionBox_Impl::DeleteRemoved()
324 const ::osl::MutexGuard aGuard( m_entriesMutex );
326 m_bInDelete = true;
328 m_vRemovedEntries.clear();
330 m_bInDelete = false;
334 //This function may be called with nPos < 0
335 void ExtensionBox_Impl::selectEntry( const tools::Long nPos )
337 bool invalidate = false;
339 //ToDo we should not use the guard at such a big scope here.
340 //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be
341 //modified in this function.
342 //It would be probably best to always use a copy of m_vEntries
343 //and some other state variables from ExtensionBox_Impl for
344 //the whole painting operation. See issue i86993
345 ::osl::MutexGuard guard(m_entriesMutex);
347 if ( m_bInCheckMode )
348 return;
350 if ( m_bHasActive )
352 if ( nPos == m_nActive )
353 return;
355 m_bHasActive = false;
356 m_vEntries[ m_nActive ]->m_bActive = false;
359 if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
361 m_bHasActive = true;
362 m_nActive = nPos;
363 m_vEntries[ nPos ]->m_bActive = true;
365 if ( IsReallyVisible() )
367 m_bAdjustActive = true;
371 if ( IsReallyVisible() )
373 m_bNeedsRecalc = true;
374 invalidate = true;
378 if (invalidate)
380 SolarMutexGuard g;
381 Invalidate();
386 void ExtensionBox_Impl::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry)
388 const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
390 if (rEntry->m_bActive)
391 rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
392 else if ((rEntry->m_eState != REGISTERED) && (rEntry->m_eState != NOT_AVAILABLE))
393 rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
394 else
395 rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
397 if (rEntry->m_bActive)
399 rRenderContext.SetLineColor();
400 rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
401 rRenderContext.DrawRect(rRect);
403 else
405 rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
406 rRenderContext.SetTextFillColor();
407 rRenderContext.Erase(rRect);
410 // Draw extension icon
411 Point aPos( rRect.TopLeft() );
412 aPos += Point(TOP_OFFSET, TOP_OFFSET);
413 Image aImage;
414 if (!rEntry->m_aIcon)
415 aImage = m_aDefaultImage;
416 else
417 aImage = rEntry->m_aIcon;
418 Size aImageSize = aImage.GetSizePixel();
419 if ((aImageSize.Width() <= ICON_WIDTH ) && ( aImageSize.Height() <= ICON_HEIGHT ) )
420 rRenderContext.DrawImage(Point(aPos.X() + ((ICON_WIDTH - aImageSize.Width()) / 2),
421 aPos.Y() + ((ICON_HEIGHT - aImageSize.Height()) / 2)),
422 aImage);
423 else
424 rRenderContext.DrawImage(aPos, Size(ICON_WIDTH, ICON_HEIGHT), aImage);
426 // Setup fonts
427 // expand the point size of the desired font to the equivalent pixel size
428 weld::SetPointFont(rRenderContext, GetDrawingArea()->get_font());
429 vcl::Font aStdFont(rRenderContext.GetFont());
430 vcl::Font aBoldFont(aStdFont);
431 aBoldFont.SetWeight(WEIGHT_BOLD);
432 rRenderContext.SetFont(aBoldFont);
433 auto aTextHeight = rRenderContext.GetTextHeight();
435 // Get max title width
436 auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET;
437 nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN);
438 rRenderContext.SetFont(aStdFont);
439 tools::Long nLinkWidth = 0;
440 if (!rEntry->m_sPublisher.isEmpty())
442 nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher);
443 nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN);
445 tools::Long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion);
447 aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET);
449 rRenderContext.SetFont(aBoldFont);
450 tools::Long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3);
451 if (aTitleWidth > nMaxTitleWidth - aVersionWidth)
453 aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3);
454 OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth);
455 rRenderContext.DrawText(aPos, aShortTitle);
456 aTitleWidth += (aTextHeight / 3);
458 else
459 rRenderContext.DrawText(aPos, rEntry->m_sTitle);
461 rRenderContext.SetFont(aStdFont);
462 rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion);
464 tools::Long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE;
465 tools::Long nTitleHeight = TOP_OFFSET + GetTextHeight();
466 if ( nIconHeight < nTitleHeight )
467 aTextHeight = nTitleHeight;
468 else
469 aTextHeight = nIconHeight;
471 // draw description
472 OUString sDescription;
473 if (!rEntry->m_sErrorText.isEmpty())
475 if (rEntry->m_bActive)
476 sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription;
477 else
478 sDescription = rEntry->m_sErrorText;
480 else
481 sDescription = rEntry->m_sDescription;
483 aPos.AdjustY(aTextHeight );
484 if (rEntry->m_bActive)
486 tools::Long nExtraHeight = 0;
488 if (rEntry->m_bHasButtons)
489 nExtraHeight = 2;
491 rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight),
492 sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
494 else
496 //replace LF to space, so words do not stick together in one line view
497 sDescription = sDescription.replace(0x000A, ' ');
498 const tools::Long nWidth = rRenderContext.GetTextWidth( sDescription );
499 if (nWidth > rRect.GetWidth() - aPos.X())
500 sDescription = rRenderContext.GetEllipsisString(sDescription, rRect.GetWidth() - aPos.X());
501 rRenderContext.DrawText(aPos, sDescription);
504 // Draw publisher link
505 if (!rEntry->m_sPublisher.isEmpty())
507 aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET );
509 rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR | vcl::PushFlags::TEXTFILLCOLOR);
510 rRenderContext.SetTextColor(rStyleSettings.GetLinkColor());
511 rRenderContext.SetTextFillColor(rStyleSettings.GetFieldColor());
512 vcl::Font aFont = rRenderContext.GetFont();
513 // to underline
514 aFont.SetUnderline(LINESTYLE_SINGLE);
515 rRenderContext.SetFont(aFont);
516 rRenderContext.DrawText(aPos, rEntry->m_sPublisher);
517 rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight));
518 rRenderContext.Pop();
521 // Draw status icons
522 if (!rEntry->m_bUser)
524 aPos = rRect.TopRight() + Point( -(RIGHT_ICON_OFFSET + SMALL_ICON_SIZE), TOP_OFFSET );
525 if (rEntry->m_bLocked)
526 rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aLockedImage);
527 else
528 rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aSharedImage);
530 if ((rEntry->m_eState == AMBIGUOUS ) || rEntry->m_bMissingDeps || rEntry->m_bMissingLic)
532 aPos = rRect.TopRight() + Point(-(RIGHT_ICON_OFFSET + SPACE_BETWEEN + 2 * SMALL_ICON_SIZE), TOP_OFFSET);
533 rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aWarningImage);
536 rRenderContext.SetLineColor(COL_LIGHTGRAY);
537 rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight());
541 void ExtensionBox_Impl::RecalcAll()
543 if ( m_bHasActive )
544 CalcActiveHeight( m_nActive );
546 SetupScrollBar();
548 if ( m_bHasActive )
550 tools::Rectangle aEntryRect = GetEntryRect( m_nActive );
552 if ( m_bAdjustActive )
554 m_bAdjustActive = false;
556 // If the top of the selected entry isn't visible, make it visible
557 if ( aEntryRect.Top() < 0 )
559 m_nTopIndex += aEntryRect.Top();
560 aEntryRect.Move( 0, -aEntryRect.Top() );
563 // If the bottom of the selected entry isn't visible, make it visible even if now the top
564 // isn't visible any longer ( the buttons are more important )
565 Size aOutputSize = GetOutputSizePixel();
566 if ( aEntryRect.Bottom() > aOutputSize.Height() )
568 m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() );
569 aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) );
572 // If there is unused space below the last entry but all entries don't fit into the box,
573 // move the content down to use the whole space
574 const tools::Long nTotalHeight = GetTotalHeight();
575 if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) )
577 tools::Long nOffset = m_nTopIndex;
578 m_nTopIndex = nTotalHeight - aOutputSize.Height();
579 nOffset -= m_nTopIndex;
580 aEntryRect.Move( 0, nOffset );
583 if ( m_bHasScrollBar )
584 m_xScrollBar->vadjustment_set_value( m_nTopIndex );
588 m_bNeedsRecalc = false;
592 bool ExtensionBox_Impl::HandleCursorKey( sal_uInt16 nKeyCode )
594 if ( m_vEntries.empty() )
595 return true;
597 tools::Long nSelect = 0;
599 if ( m_bHasActive )
601 tools::Long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight;
602 if ( nPageSize < 2 )
603 nPageSize = 2;
605 if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) )
606 nSelect = m_nActive + 1;
607 else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) )
608 nSelect = m_nActive - 1;
609 else if ( nKeyCode == KEY_HOME )
610 nSelect = 0;
611 else if ( nKeyCode == KEY_END )
612 nSelect = m_vEntries.size() - 1;
613 else if ( nKeyCode == KEY_PAGEUP )
614 nSelect = m_nActive - nPageSize + 1;
615 else if ( nKeyCode == KEY_PAGEDOWN )
616 nSelect = m_nActive + nPageSize - 1;
618 else // when there is no selected entry, we will select the first or the last.
620 if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) )
621 nSelect = 0;
622 else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) )
623 nSelect = m_vEntries.size() - 1;
626 if ( nSelect < 0 )
627 nSelect = 0;
628 if ( o3tl::make_unsigned(nSelect) >= m_vEntries.size() )
629 nSelect = m_vEntries.size() - 1;
631 selectEntry( nSelect );
633 return true;
637 void ExtensionBox_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/)
639 if ( !m_bInDelete )
640 DeleteRemoved();
642 if ( m_bNeedsRecalc )
643 RecalcAll();
645 Point aStart( 0, -m_nTopIndex );
646 Size aSize(GetOutputSizePixel());
648 const ::osl::MutexGuard aGuard( m_entriesMutex );
650 for (auto const& entry : m_vEntries)
652 aSize.setHeight( entry->m_bActive ? m_nActiveHeight : m_nStdHeight );
653 tools::Rectangle aEntryRect( aStart, aSize );
654 DrawRow(rRenderContext, aEntryRect, entry);
655 aStart.AdjustY(aSize.Height() );
660 tools::Long ExtensionBox_Impl::GetTotalHeight() const
662 tools::Long nHeight = m_vEntries.size() * m_nStdHeight;
664 if ( m_bHasActive )
666 nHeight += m_nActiveHeight - m_nStdHeight;
669 return nHeight;
673 void ExtensionBox_Impl::SetupScrollBar()
675 const Size aSize = GetOutputSizePixel();
676 const auto nTotalHeight = GetTotalHeight();
677 const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() );
679 if ( bNeedsScrollBar )
681 if ( m_nTopIndex + aSize.Height() > nTotalHeight )
682 m_nTopIndex = nTotalHeight - aSize.Height();
684 m_xScrollBar->vadjustment_configure(m_nTopIndex, 0, nTotalHeight,
685 m_nStdHeight, ( aSize.Height() * 4 ) / 5,
686 aSize.Height());
688 if (!m_bHasScrollBar)
689 m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS);
691 else if ( m_bHasScrollBar )
693 m_xScrollBar->set_vpolicy(VclPolicyType::NEVER);
694 m_nTopIndex = 0;
697 m_bHasScrollBar = bNeedsScrollBar;
701 void ExtensionBox_Impl::Resize()
703 RecalcAll();
704 Invalidate();
707 void ExtensionBox_Impl::SetDrawingArea(weld::DrawingArea* pDrawingArea)
709 Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(250, 150), MapMode(MapUnit::MapAppFont));
710 pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
711 CustomWidgetController::SetDrawingArea(pDrawingArea);
712 SetOutputSizePixel(aSize);
714 Init();
717 tools::Long ExtensionBox_Impl::PointToPos( const Point& rPos )
719 tools::Long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight;
721 if ( m_bHasActive && ( nPos > m_nActive ) )
723 if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight )
724 nPos = m_nActive;
725 else
726 nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight;
729 return nPos;
732 bool ExtensionBox_Impl::MouseMove( const MouseEvent& rMEvt )
734 bool bOverHyperlink = false;
736 auto nPos = PointToPos( rMEvt.GetPosPixel() );
737 if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
739 const auto& rEntry = m_vEntries[nPos];
740 bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel());
743 if (bOverHyperlink)
744 SetPointer(PointerStyle::RefHand);
745 else
746 SetPointer(PointerStyle::Arrow);
748 return false;
751 OUString ExtensionBox_Impl::RequestHelp(tools::Rectangle& rRect)
753 auto nPos = PointToPos( rRect.TopLeft() );
754 if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
756 const auto& rEntry = m_vEntries[nPos];
757 bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rRect);
758 if (bOverHyperlink)
760 rRect = rEntry->m_aLinkRect;
761 return rEntry->m_sPublisherURL;
765 return OUString();
768 bool ExtensionBox_Impl::MouseButtonDown( const MouseEvent& rMEvt )
770 if ( !rMEvt.IsLeft() )
771 return false;
773 if (rMEvt.IsMod1() && m_bHasActive)
774 selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND); // Selecting a not existing entry will deselect the current one
775 else
777 auto nPos = PointToPos( rMEvt.GetPosPixel() );
779 if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
781 const auto& rEntry = m_vEntries[nPos];
782 if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel()))
786 css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute(
787 css::system::SystemShellExecute::create(comphelper::getProcessComponentContext()));
788 //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
789 xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY);
791 catch (...)
794 return true;
798 selectEntry( nPos );
800 return true;
803 bool ExtensionBox_Impl::KeyInput(const KeyEvent& rKEvt)
805 if ( !m_bInDelete )
806 DeleteRemoved();
808 vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
809 sal_uInt16 nKeyCode = aKeyCode.GetCode();
811 bool bHandled = false;
812 if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR)
813 bHandled = HandleCursorKey(nKeyCode);
815 return bHandled;
818 bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const tools::Long nStart,
819 const tools::Long nEnd, tools::Long &nPos )
821 nPos = nStart;
822 if ( nStart > nEnd )
823 return false;
825 sal_Int32 eCompare;
827 if ( nStart == nEnd )
829 eCompare = rEntry->CompareTo( &*m_oCollator, m_vEntries[ nStart ] );
830 if ( eCompare < 0 )
831 return false;
832 else if ( eCompare == 0 )
834 //Workaround. See i86963.
835 if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage)
836 return false;
838 if ( m_bInCheckMode )
839 m_vEntries[ nStart ]->m_bChecked = true;
840 return true;
842 else
844 nPos = nStart + 1;
845 return false;
849 const tools::Long nMid = nStart + ( ( nEnd - nStart ) / 2 );
850 eCompare = rEntry->CompareTo( &*m_oCollator, m_vEntries[ nMid ] );
852 if ( eCompare < 0 )
853 return FindEntryPos( rEntry, nStart, nMid-1, nPos );
854 else if ( eCompare > 0 )
855 return FindEntryPos( rEntry, nMid+1, nEnd, nPos );
856 else
858 //Workaround.See i86963.
859 if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage)
860 return false;
862 if ( m_bInCheckMode )
863 m_vEntries[ nMid ]->m_bChecked = true;
864 nPos = nMid;
865 return true;
869 void ExtensionBox_Impl::cleanVecListenerAdded()
871 m_vListenerAdded.erase(std::remove_if(m_vListenerAdded.begin(), m_vListenerAdded.end(),
872 [](const uno::WeakReference<deployment::XPackage>& rxListener) {
873 const uno::Reference<deployment::XPackage> hardRef(rxListener);
874 return !hardRef.is();
876 m_vListenerAdded.end());
879 void ExtensionBox_Impl::addEventListenerOnce(
880 uno::Reference<deployment::XPackage > const & extension)
882 //make sure to only add the listener once
883 cleanVecListenerAdded();
884 if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(),
885 FindWeakRef(extension)) )
887 extension->addEventListener( m_xRemoveListener );
888 m_vListenerAdded.emplace_back(extension);
893 void ExtensionBox_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage,
894 bool bLicenseMissing )
896 PackageState eState = TheExtensionManager::getPackageState( xPackage );
897 bool bLocked = m_pManager->isReadOnly( xPackage );
899 TEntry_Impl pEntry = std::make_shared<Entry_Impl>( xPackage, eState, bLocked );
901 // Don't add empty entries
902 if ( pEntry->m_sTitle.isEmpty() )
903 return;
906 osl::MutexGuard guard(m_entriesMutex);
907 tools::Long nPos = 0;
908 if (m_vEntries.empty())
910 addEventListenerOnce(xPackage);
911 m_vEntries.push_back(pEntry);
913 else
915 if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos))
917 addEventListenerOnce(xPackage);
918 m_vEntries.insert(m_vEntries.begin() + nPos, pEntry);
920 else if (!m_bInCheckMode)
922 OSL_FAIL("ExtensionBox_Impl::addEntry(): Will not add duplicate entries");
926 pEntry->m_bHasOptions = m_pManager->supportsOptions(xPackage);
927 pEntry->m_bUser = (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER);
928 pEntry->m_bShared = (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER);
929 pEntry->m_bNew = m_bInCheckMode;
930 pEntry->m_bMissingLic = bLicenseMissing;
932 if (bLicenseMissing)
933 pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE);
935 //access to m_nActive must be guarded
936 if (!m_bInCheckMode && m_bHasActive && (m_nActive >= nPos))
937 m_nActive += 1;
940 if ( IsReallyVisible() )
941 Invalidate();
943 m_bNeedsRecalc = true;
946 void ExtensionBox_Impl::updateEntry( const uno::Reference< deployment::XPackage > &xPackage )
948 for (auto const& entry : m_vEntries)
950 if ( entry->m_xPackage == xPackage )
952 PackageState eState = TheExtensionManager::getPackageState( xPackage );
953 entry->m_bHasOptions = m_pManager->supportsOptions( xPackage );
954 entry->m_eState = eState;
955 entry->m_sTitle = xPackage->getDisplayName();
956 entry->m_sVersion = xPackage->getVersion();
957 entry->m_sDescription = xPackage->getDescription();
959 if ( eState == REGISTERED )
960 entry->m_bMissingLic = false;
962 if ( eState == AMBIGUOUS )
963 entry->m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
964 else if ( ! entry->m_bMissingLic )
965 entry->m_sErrorText.clear();
967 if ( IsReallyVisible() )
968 Invalidate();
969 break;
974 //This function is also called as a result of removing an extension.
975 //see PackageManagerImpl::removePackage
976 //The gui is a registered as listener on the package. Removing it will cause the
977 //listeners to be notified and then this function is called. At this moment xPackage
978 //is in the disposing state and all calls on it may result in a DisposedException.
979 void ExtensionBox_Impl::removeEntry( const uno::Reference< deployment::XPackage > &xPackage )
981 if ( m_bInDelete )
982 return;
984 bool invalidate = false;
986 ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
988 auto iIndex = std::find_if(m_vEntries.begin(), m_vEntries.end(),
989 [&xPackage](const TEntry_Impl& rxEntry) { return rxEntry->m_xPackage == xPackage; });
990 if (iIndex != m_vEntries.end())
992 tools::Long nPos = iIndex - m_vEntries.begin();
994 // Entries mustn't be removed here, because they contain a hyperlink control
995 // which can only be deleted when the thread has the solar mutex. Therefore
996 // the entry will be moved into the m_vRemovedEntries list which will be
997 // cleared on the next paint event
998 m_vRemovedEntries.push_back( *iIndex );
999 (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener);
1000 m_vEntries.erase( iIndex );
1002 m_bNeedsRecalc = true;
1004 if ( IsReallyVisible() )
1005 invalidate = true;
1007 if ( m_bHasActive )
1009 if ( nPos < m_nActive )
1010 m_nActive -= 1;
1011 else if ( ( nPos == m_nActive ) &&
1012 ( nPos == static_cast<tools::Long>(m_vEntries.size()) ) )
1013 m_nActive -= 1;
1015 m_bHasActive = false;
1016 //clear before calling out of this method
1017 aGuard.clear();
1018 selectEntry( m_nActive );
1023 if (invalidate)
1025 SolarMutexGuard g;
1026 Invalidate();
1031 void ExtensionBox_Impl::RemoveUnlocked()
1033 bool bAllRemoved = false;
1035 while ( ! bAllRemoved )
1037 bAllRemoved = true;
1039 ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
1041 for (auto const& entry : m_vEntries)
1043 if ( !entry->m_bLocked )
1045 bAllRemoved = false;
1046 uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage;
1047 aGuard.clear();
1048 removeEntry( xPackage );
1049 break;
1056 void ExtensionBox_Impl::prepareChecking()
1058 m_bInCheckMode = true;
1059 for (auto const& entry : m_vEntries)
1061 entry->m_bChecked = false;
1062 entry->m_bNew = false;
1067 void ExtensionBox_Impl::checkEntries()
1069 tools::Long nNewPos = -1;
1070 tools::Long nChangedActivePos = -1;
1071 tools::Long nPos = 0;
1072 bool bNeedsUpdate = false;
1075 osl::MutexGuard guard(m_entriesMutex);
1076 auto iIndex = m_vEntries.begin();
1077 while (iIndex != m_vEntries.end())
1079 if (!(*iIndex)->m_bChecked)
1081 (*iIndex)->m_bChecked = true;
1082 bNeedsUpdate = true;
1083 nPos = iIndex - m_vEntries.begin();
1084 if ((*iIndex)->m_bNew)
1085 { // add entry to list and correct active pos
1086 if (nNewPos == -1)
1087 nNewPos = nPos;
1088 if (nPos <= m_nActive)
1089 m_nActive += 1;
1090 ++iIndex;
1092 else
1093 { // remove entry from list
1094 if (nPos < nNewPos)
1096 --nNewPos;
1098 if (nPos < nChangedActivePos)
1100 --nChangedActivePos;
1102 if (nPos < m_nActive)
1103 m_nActive -= 1;
1104 else if (nPos == m_nActive)
1106 nChangedActivePos = nPos;
1107 m_nActive = -1;
1108 m_bHasActive = false;
1110 m_vRemovedEntries.push_back(*iIndex);
1111 (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener);
1112 iIndex = m_vEntries.erase(iIndex);
1115 else
1116 ++iIndex;
1120 m_bInCheckMode = false;
1122 if ( nNewPos != - 1)
1123 selectEntry( nNewPos );
1124 else if (nChangedActivePos != -1) {
1125 selectEntry(nChangedActivePos);
1128 if ( bNeedsUpdate )
1130 m_bNeedsRecalc = true;
1131 if ( IsReallyVisible() )
1132 Invalidate();
1136 IMPL_LINK(ExtensionBox_Impl, ScrollHdl, weld::ScrolledWindow&, rScrBar, void)
1138 m_nTopIndex = rScrBar.vadjustment_get_value();
1139 Invalidate();
1142 } //namespace dp_gui
1144 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */