use insert function instead of for loop
[LibreOffice.git] / desktop / source / deployment / gui / dp_gui_extlistbox.cxx
blob77c1c65a757c3fd9fbc33a022b2049861e58a951
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 : 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 // coverity[ tainted_data_return : FALSE ] version 2023.12.2
437 auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET;
438 nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN);
439 rRenderContext.SetFont(aStdFont);
440 tools::Long nLinkWidth = 0;
441 if (!rEntry->m_sPublisher.isEmpty())
443 nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher);
444 nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN);
446 tools::Long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion);
448 aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET);
450 rRenderContext.SetFont(aBoldFont);
451 tools::Long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3);
452 if (aTitleWidth > nMaxTitleWidth - aVersionWidth)
454 aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3);
455 OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth);
456 rRenderContext.DrawText(aPos, aShortTitle);
457 aTitleWidth += (aTextHeight / 3);
459 else
460 rRenderContext.DrawText(aPos, rEntry->m_sTitle);
462 rRenderContext.SetFont(aStdFont);
463 rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion);
465 tools::Long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE;
466 tools::Long nTitleHeight = TOP_OFFSET + GetTextHeight();
467 if ( nIconHeight < nTitleHeight )
468 aTextHeight = nTitleHeight;
469 else
470 aTextHeight = nIconHeight;
472 // draw description
473 OUString sDescription;
474 if (!rEntry->m_sErrorText.isEmpty())
476 if (rEntry->m_bActive)
477 sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription;
478 else
479 sDescription = rEntry->m_sErrorText;
481 else
482 sDescription = rEntry->m_sDescription;
484 aPos.AdjustY(aTextHeight );
485 if (rEntry->m_bActive)
487 tools::Long nExtraHeight = 0;
489 if (rEntry->m_bHasButtons)
490 nExtraHeight = 2;
492 rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight),
493 sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
495 else
497 //replace LF to space, so words do not stick together in one line view
498 sDescription = sDescription.replace(0x000A, ' ');
499 const tools::Long nWidth = rRenderContext.GetTextWidth( sDescription );
500 if (nWidth > rRect.GetWidth() - aPos.X())
501 sDescription = rRenderContext.GetEllipsisString(sDescription, rRect.GetWidth() - aPos.X());
502 rRenderContext.DrawText(aPos, sDescription);
505 // Draw publisher link
506 if (!rEntry->m_sPublisher.isEmpty())
508 aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET );
510 rRenderContext.Push(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR | vcl::PushFlags::TEXTFILLCOLOR);
511 rRenderContext.SetTextColor(rStyleSettings.GetLinkColor());
512 rRenderContext.SetTextFillColor(rStyleSettings.GetFieldColor());
513 vcl::Font aFont = rRenderContext.GetFont();
514 // to underline
515 aFont.SetUnderline(LINESTYLE_SINGLE);
516 rRenderContext.SetFont(aFont);
517 rRenderContext.DrawText(aPos, rEntry->m_sPublisher);
518 rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight));
519 rRenderContext.Pop();
522 // Draw status icons
523 if (!rEntry->m_bUser)
525 aPos = rRect.TopRight() + Point( -(RIGHT_ICON_OFFSET + SMALL_ICON_SIZE), TOP_OFFSET );
526 if (rEntry->m_bLocked)
527 rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aLockedImage);
528 else
529 rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aSharedImage);
531 if ((rEntry->m_eState == AMBIGUOUS ) || rEntry->m_bMissingDeps || rEntry->m_bMissingLic)
533 aPos = rRect.TopRight() + Point(-(RIGHT_ICON_OFFSET + SPACE_BETWEEN + 2 * SMALL_ICON_SIZE), TOP_OFFSET);
534 rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aWarningImage);
537 rRenderContext.SetLineColor(COL_LIGHTGRAY);
538 rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight());
542 void ExtensionBox_Impl::RecalcAll()
544 if ( m_bHasActive )
545 CalcActiveHeight( m_nActive );
547 SetupScrollBar();
549 if ( m_bHasActive )
551 tools::Rectangle aEntryRect = GetEntryRect( m_nActive );
553 if ( m_bAdjustActive )
555 m_bAdjustActive = false;
557 // If the top of the selected entry isn't visible, make it visible
558 if ( aEntryRect.Top() < 0 )
560 m_nTopIndex += aEntryRect.Top();
561 aEntryRect.Move( 0, -aEntryRect.Top() );
564 // If the bottom of the selected entry isn't visible, make it visible even if now the top
565 // isn't visible any longer ( the buttons are more important )
566 Size aOutputSize = GetOutputSizePixel();
567 if ( aEntryRect.Bottom() > aOutputSize.Height() )
569 m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() );
570 aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) );
573 // If there is unused space below the last entry but all entries don't fit into the box,
574 // move the content down to use the whole space
575 const tools::Long nTotalHeight = GetTotalHeight();
576 if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) )
578 tools::Long nOffset = m_nTopIndex;
579 m_nTopIndex = nTotalHeight - aOutputSize.Height();
580 nOffset -= m_nTopIndex;
581 aEntryRect.Move( 0, nOffset );
584 if ( m_bHasScrollBar )
585 m_xScrollBar->vadjustment_set_value( m_nTopIndex );
589 m_bNeedsRecalc = false;
593 bool ExtensionBox_Impl::HandleCursorKey( sal_uInt16 nKeyCode )
595 if ( m_vEntries.empty() )
596 return true;
598 tools::Long nSelect = 0;
600 if ( m_bHasActive )
602 tools::Long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight;
603 if ( nPageSize < 2 )
604 nPageSize = 2;
606 if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) )
607 nSelect = m_nActive + 1;
608 else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) )
609 nSelect = m_nActive - 1;
610 else if ( nKeyCode == KEY_HOME )
611 nSelect = 0;
612 else if ( nKeyCode == KEY_END )
613 nSelect = m_vEntries.size() - 1;
614 else if ( nKeyCode == KEY_PAGEUP )
615 nSelect = m_nActive - nPageSize + 1;
616 else if ( nKeyCode == KEY_PAGEDOWN )
617 nSelect = m_nActive + nPageSize - 1;
619 else // when there is no selected entry, we will select the first or the last.
621 if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) )
622 nSelect = 0;
623 else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) )
624 nSelect = m_vEntries.size() - 1;
627 if ( nSelect < 0 )
628 nSelect = 0;
629 if ( o3tl::make_unsigned(nSelect) >= m_vEntries.size() )
630 nSelect = m_vEntries.size() - 1;
632 selectEntry( nSelect );
634 return true;
638 void ExtensionBox_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/)
640 if ( !m_bInDelete )
641 DeleteRemoved();
643 if ( m_bNeedsRecalc )
644 RecalcAll();
646 Point aStart( 0, -m_nTopIndex );
647 Size aSize(GetOutputSizePixel());
649 const ::osl::MutexGuard aGuard( m_entriesMutex );
651 for (auto const& entry : m_vEntries)
653 aSize.setHeight( entry->m_bActive ? m_nActiveHeight : m_nStdHeight );
654 tools::Rectangle aEntryRect( aStart, aSize );
655 DrawRow(rRenderContext, aEntryRect, entry);
656 aStart.AdjustY(aSize.Height() );
661 tools::Long ExtensionBox_Impl::GetTotalHeight() const
663 tools::Long nHeight = m_vEntries.size() * m_nStdHeight;
665 if ( m_bHasActive )
667 nHeight += m_nActiveHeight - m_nStdHeight;
670 return nHeight;
674 void ExtensionBox_Impl::SetupScrollBar()
676 const Size aSize = GetOutputSizePixel();
677 const auto nTotalHeight = GetTotalHeight();
678 const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() );
680 if ( bNeedsScrollBar )
682 if ( m_nTopIndex + aSize.Height() > nTotalHeight )
683 m_nTopIndex = nTotalHeight - aSize.Height();
685 m_xScrollBar->vadjustment_configure(m_nTopIndex, 0, nTotalHeight,
686 m_nStdHeight, ( aSize.Height() * 4 ) / 5,
687 aSize.Height());
689 if (!m_bHasScrollBar)
690 m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS);
692 else if ( m_bHasScrollBar )
694 m_xScrollBar->set_vpolicy(VclPolicyType::NEVER);
695 m_nTopIndex = 0;
698 m_bHasScrollBar = bNeedsScrollBar;
702 void ExtensionBox_Impl::Resize()
704 RecalcAll();
705 Invalidate();
708 void ExtensionBox_Impl::SetDrawingArea(weld::DrawingArea* pDrawingArea)
710 Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(250, 150), MapMode(MapUnit::MapAppFont));
711 pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
712 CustomWidgetController::SetDrawingArea(pDrawingArea);
713 SetOutputSizePixel(aSize);
715 Init();
718 tools::Long ExtensionBox_Impl::PointToPos( const Point& rPos )
720 tools::Long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight;
722 if ( m_bHasActive && ( nPos > m_nActive ) )
724 if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight )
725 nPos = m_nActive;
726 else
727 nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight;
730 return nPos;
733 bool ExtensionBox_Impl::MouseMove( const MouseEvent& rMEvt )
735 bool bOverHyperlink = false;
737 auto nPos = PointToPos( rMEvt.GetPosPixel() );
738 if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
740 const auto& rEntry = m_vEntries[nPos];
741 bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel());
744 if (bOverHyperlink)
745 SetPointer(PointerStyle::RefHand);
746 else
747 SetPointer(PointerStyle::Arrow);
749 return false;
752 OUString ExtensionBox_Impl::RequestHelp(tools::Rectangle& rRect)
754 auto nPos = PointToPos( rRect.TopLeft() );
755 if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
757 const auto& rEntry = m_vEntries[nPos];
758 bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rRect);
759 if (bOverHyperlink)
761 rRect = rEntry->m_aLinkRect;
762 return rEntry->m_sPublisherURL;
766 return OUString();
769 bool ExtensionBox_Impl::MouseButtonDown( const MouseEvent& rMEvt )
771 if ( !rMEvt.IsLeft() )
772 return false;
774 if (rMEvt.IsMod1() && m_bHasActive)
775 selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND); // Selecting a not existing entry will deselect the current one
776 else
778 auto nPos = PointToPos( rMEvt.GetPosPixel() );
780 if ( ( nPos >= 0 ) && ( o3tl::make_unsigned(nPos) < m_vEntries.size() ) )
782 const auto& rEntry = m_vEntries[nPos];
783 if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.Contains(rMEvt.GetPosPixel()))
787 css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute(
788 css::system::SystemShellExecute::create(comphelper::getProcessComponentContext()));
789 //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
790 xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY);
792 catch (...)
795 return true;
799 selectEntry( nPos );
801 return true;
804 bool ExtensionBox_Impl::KeyInput(const KeyEvent& rKEvt)
806 if ( !m_bInDelete )
807 DeleteRemoved();
809 vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
810 sal_uInt16 nKeyCode = aKeyCode.GetCode();
812 bool bHandled = false;
813 if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR)
814 bHandled = HandleCursorKey(nKeyCode);
816 return bHandled;
819 bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const tools::Long nStart,
820 const tools::Long nEnd, tools::Long &nPos )
822 nPos = nStart;
823 if ( nStart > nEnd )
824 return false;
826 sal_Int32 eCompare;
828 if ( nStart == nEnd )
830 eCompare = rEntry->CompareTo( &*m_oCollator, m_vEntries[ nStart ] );
831 if ( eCompare < 0 )
832 return false;
833 else if ( eCompare == 0 )
835 //Workaround. See i86963.
836 if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage)
837 return false;
839 if ( m_bInCheckMode )
840 m_vEntries[ nStart ]->m_bChecked = true;
841 return true;
843 else
845 nPos = nStart + 1;
846 return false;
850 const tools::Long nMid = nStart + ( ( nEnd - nStart ) / 2 );
851 eCompare = rEntry->CompareTo( &*m_oCollator, m_vEntries[ nMid ] );
853 if ( eCompare < 0 )
854 return FindEntryPos( rEntry, nStart, nMid-1, nPos );
855 else if ( eCompare > 0 )
856 return FindEntryPos( rEntry, nMid+1, nEnd, nPos );
857 else
859 //Workaround.See i86963.
860 if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage)
861 return false;
863 if ( m_bInCheckMode )
864 m_vEntries[ nMid ]->m_bChecked = true;
865 nPos = nMid;
866 return true;
870 void ExtensionBox_Impl::cleanVecListenerAdded()
872 std::erase_if(m_vListenerAdded,
873 [](const uno::WeakReference<deployment::XPackage>& rxListener) {
874 const uno::Reference<deployment::XPackage> hardRef(rxListener);
875 return !hardRef.is();
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: */