Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / desktop / source / deployment / gui / dp_gui_extlistbox.cxx
blob1f74612e1729524892375fa9cb6ef86605ad2739
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 <svtools/controldims.hxx>
22 #include <dp_shared.hxx>
23 #include <strings.hrc>
24 #include "dp_gui.h"
25 #include "dp_gui_extlistbox.hxx"
26 #include "dp_gui_theextmgr.hxx"
27 #include "dp_gui_dialog2.hxx"
28 #include <dp_dependencies.hxx>
29 #include <bitmaps.hlst>
31 #include <comphelper/processfactory.hxx>
32 #include <com/sun/star/i18n/CollatorOptions.hpp>
33 #include <com/sun/star/deployment/DependencyException.hpp>
34 #include <com/sun/star/deployment/DeploymentException.hpp>
35 #include <com/sun/star/deployment/ExtensionRemovedException.hpp>
36 #include <com/sun/star/system/XSystemShellExecute.hpp>
37 #include <com/sun/star/system/SystemShellExecuteFlags.hpp>
38 #include <com/sun/star/system/SystemShellExecute.hpp>
39 #include <cppuhelper/weakref.hxx>
40 #include <i18nlangtag/languagetag.hxx>
41 #include <vcl/commandevent.hxx>
42 #include <vcl/event.hxx>
43 #include <vcl/ptrstyle.hxx>
44 #include <vcl/svapp.hxx>
45 #include <vcl/settings.hxx>
46 #include <algorithm>
48 #define USER_PACKAGE_MANAGER "user"
49 #define SHARED_PACKAGE_MANAGER "shared"
51 using namespace ::com::sun::star;
53 namespace dp_gui {
55 namespace {
57 struct FindWeakRef
59 const uno::Reference<deployment::XPackage> m_extension;
61 explicit FindWeakRef( uno::Reference<deployment::XPackage> const & ext): m_extension(ext) {}
62 bool operator () (uno::WeakReference< deployment::XPackage > const & ref);
65 bool FindWeakRef::operator () (uno::WeakReference< deployment::XPackage > const & ref)
67 const uno::Reference<deployment::XPackage> ext(ref);
68 return ext == m_extension;
71 } // end namespace
73 // struct Entry_Impl
75 Entry_Impl::Entry_Impl( const uno::Reference< deployment::XPackage > &xPackage,
76 const PackageState eState, const bool bReadOnly ) :
77 m_bActive( false ),
78 m_bLocked( bReadOnly ),
79 m_bHasOptions( false ),
80 m_bUser( false ),
81 m_bShared( false ),
82 m_bNew( false ),
83 m_bChecked( false ),
84 m_bMissingDeps( false ),
85 m_bHasButtons( false ),
86 m_bMissingLic( false ),
87 m_eState( eState ),
88 m_xPackage( xPackage )
90 try
92 m_sTitle = xPackage->getDisplayName();
93 m_sVersion = xPackage->getVersion();
94 m_sDescription = xPackage->getDescription();
95 m_sLicenseText = xPackage->getLicenseText();
97 beans::StringPair aInfo( m_xPackage->getPublisherInfo() );
98 m_sPublisher = aInfo.First;
99 m_sPublisherURL = aInfo.Second;
101 // get the icons for the package if there are any
102 uno::Reference< graphic::XGraphic > xGraphic = xPackage->getIcon( false );
103 if ( xGraphic.is() )
104 m_aIcon = Image( xGraphic );
106 if ( eState == AMBIGUOUS )
107 m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
108 else if ( eState == NOT_REGISTERED )
109 checkDependencies();
111 catch (const deployment::ExtensionRemovedException &) {}
112 catch (const uno::RuntimeException &) {}
116 Entry_Impl::~Entry_Impl()
120 sal_Int32 Entry_Impl::CompareTo( const CollatorWrapper *pCollator, const TEntry_Impl& rEntry ) const
122 sal_Int32 eCompare = pCollator->compareString( m_sTitle, rEntry->m_sTitle );
123 if ( eCompare == 0 )
125 eCompare = m_sVersion.compareTo( rEntry->m_sVersion );
126 if ( eCompare == 0 )
128 sal_Int32 nCompare = m_xPackage->getRepositoryName().compareTo( rEntry->m_xPackage->getRepositoryName() );
129 if ( nCompare < 0 )
130 eCompare = -1;
131 else if ( nCompare > 0 )
132 eCompare = 1;
135 return eCompare;
139 void Entry_Impl::checkDependencies()
141 try {
142 m_xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
144 catch ( const deployment::DeploymentException &e )
146 deployment::DependencyException depExc;
147 if ( e.Cause >>= depExc )
149 OUStringBuffer aMissingDep( DpResId( RID_STR_ERROR_MISSING_DEPENDENCIES ) );
150 for ( sal_Int32 i = 0; i < depExc.UnsatisfiedDependencies.getLength(); ++i )
152 aMissingDep.append("\n");
153 aMissingDep.append(dp_misc::Dependencies::getErrorText( depExc.UnsatisfiedDependencies[i]));
155 aMissingDep.append("\n");
156 m_sErrorText = aMissingDep.makeStringAndClear();
157 m_bMissingDeps = true;
162 // ExtensionRemovedListener
164 void ExtensionRemovedListener::disposing( lang::EventObject const & rEvt )
166 uno::Reference< deployment::XPackage > xPackage( rEvt.Source, uno::UNO_QUERY );
168 if ( xPackage.is() )
170 m_pParent->removeEntry( xPackage );
175 ExtensionRemovedListener::~ExtensionRemovedListener()
180 // ExtensionBox_Impl
181 ExtensionBox_Impl::ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll)
182 : m_bHasScrollBar( false )
183 , m_bHasActive( false )
184 , m_bNeedsRecalc( true )
185 , m_bInCheckMode( false )
186 , m_bAdjustActive( false )
187 , m_bInDelete( false )
188 , m_nActive( 0 )
189 , m_nTopIndex( 0 )
190 , m_nStdHeight( 0 )
191 , m_nActiveHeight( 0 )
192 , m_aSharedImage(StockImage::Yes, RID_BMP_SHARED)
193 , m_aLockedImage(StockImage::Yes, RID_BMP_LOCKED)
194 , m_aWarningImage(StockImage::Yes, RID_BMP_WARNING)
195 , m_aDefaultImage(StockImage::Yes, RID_BMP_EXTENSION)
196 , m_pManager( nullptr )
197 , m_xScrollBar(std::move(xScroll))
201 void ExtensionBox_Impl::Init()
203 m_xScrollBar->set_user_managed_scrolling();
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_pCollator.reset( new CollatorWrapper( ::comphelper::getProcessComponentContext() ) );
224 m_pCollator->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.get() );
239 m_vEntries.clear();
241 m_xRemoveListener.clear();
243 m_pLocale.reset();
244 m_pCollator.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 long nPos )
268 const ::osl::MutexGuard aGuard( m_entriesMutex );
270 // get title height
271 long aTextHeight;
272 long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
273 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 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 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 ) && ( nPos < static_cast<long>(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 if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice()))
429 pDefaultDevice->SetPointFont(rRenderContext, GetDrawingArea()->get_font());
430 vcl::Font aStdFont(rRenderContext.GetFont());
431 vcl::Font aBoldFont(aStdFont);
432 aBoldFont.SetWeight(WEIGHT_BOLD);
433 rRenderContext.SetFont(aBoldFont);
434 auto aTextHeight = rRenderContext.GetTextHeight();
436 // Get max title width
437 auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET;
438 nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN);
439 rRenderContext.SetFont(aStdFont);
440 long nLinkWidth = 0;
441 if (!rEntry->m_sPublisher.isEmpty())
443 nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher);
444 nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN);
446 long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion);
448 aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET);
450 rRenderContext.SetFont(aBoldFont);
451 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 long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE;
466 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 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 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(PushFlags::FONT | PushFlags::TEXTCOLOR | 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 long nTotalHeight = GetTotalHeight();
576 if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) )
578 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 long nSelect = 0;
600 if ( m_bHasActive )
602 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 ( nSelect >= static_cast<long>(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 long ExtensionBox_Impl::GetTotalHeight() const
663 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 long ExtensionBox_Impl::PointToPos( const Point& rPos )
720 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 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
740 const auto& rEntry = m_vEntries[nPos];
741 bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(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 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
757 const auto& rEntry = m_vEntries[nPos];
758 bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(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() )
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 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
781 const auto& rEntry = m_vEntries[nPos];
782 if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(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 return false;
806 bool ExtensionBox_Impl::KeyInput(const KeyEvent& rKEvt)
808 if ( !m_bInDelete )
809 DeleteRemoved();
811 vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
812 sal_uInt16 nKeyCode = aKeyCode.GetCode();
814 bool bHandled = false;
815 if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR)
816 bHandled = HandleCursorKey(nKeyCode);
818 return bHandled;
821 bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const long nStart,
822 const long nEnd, long &nPos )
824 nPos = nStart;
825 if ( nStart > nEnd )
826 return false;
828 sal_Int32 eCompare;
830 if ( nStart == nEnd )
832 eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nStart ] );
833 if ( eCompare < 0 )
834 return false;
835 else if ( eCompare == 0 )
837 //Workaround. See i86963.
838 if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage)
839 return false;
841 if ( m_bInCheckMode )
842 m_vEntries[ nStart ]->m_bChecked = true;
843 return true;
845 else
847 nPos = nStart + 1;
848 return false;
852 const long nMid = nStart + ( ( nEnd - nStart ) / 2 );
853 eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nMid ] );
855 if ( eCompare < 0 )
856 return FindEntryPos( rEntry, nStart, nMid-1, nPos );
857 else if ( eCompare > 0 )
858 return FindEntryPos( rEntry, nMid+1, nEnd, nPos );
859 else
861 //Workaround.See i86963.
862 if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage)
863 return false;
865 if ( m_bInCheckMode )
866 m_vEntries[ nMid ]->m_bChecked = true;
867 nPos = nMid;
868 return true;
872 void ExtensionBox_Impl::cleanVecListenerAdded()
874 m_vListenerAdded.erase(std::remove_if(m_vListenerAdded.begin(), m_vListenerAdded.end(),
875 [](const uno::WeakReference<deployment::XPackage>& rxListener) {
876 const uno::Reference<deployment::XPackage> hardRef(rxListener);
877 return !hardRef.is();
879 m_vListenerAdded.end());
882 void ExtensionBox_Impl::addEventListenerOnce(
883 uno::Reference<deployment::XPackage > const & extension)
885 //make sure to only add the listener once
886 cleanVecListenerAdded();
887 if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(),
888 FindWeakRef(extension)) )
890 extension->addEventListener( m_xRemoveListener.get() );
891 m_vListenerAdded.emplace_back(extension);
896 void ExtensionBox_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage,
897 bool bLicenseMissing )
899 long nPos = 0;
900 PackageState eState = TheExtensionManager::getPackageState( xPackage );
901 bool bLocked = m_pManager->isReadOnly( xPackage );
903 TEntry_Impl pEntry( new Entry_Impl( xPackage, eState, bLocked ) );
905 // Don't add empty entries
906 if ( pEntry->m_sTitle.isEmpty() )
907 return;
910 osl::MutexGuard guard(m_entriesMutex);
911 if (m_vEntries.empty())
913 addEventListenerOnce(xPackage);
914 m_vEntries.push_back(pEntry);
916 else
918 if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos))
920 addEventListenerOnce(xPackage);
921 m_vEntries.insert(m_vEntries.begin() + nPos, pEntry);
923 else if (!m_bInCheckMode)
925 OSL_FAIL("ExtensionBox_Impl::addEntry(): Will not add duplicate entries");
929 pEntry->m_bHasOptions = m_pManager->supportsOptions(xPackage);
930 pEntry->m_bUser = (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER);
931 pEntry->m_bShared = (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER);
932 pEntry->m_bNew = m_bInCheckMode;
933 pEntry->m_bMissingLic = bLicenseMissing;
935 if (bLicenseMissing)
936 pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE);
938 //access to m_nActive must be guarded
939 if (!m_bInCheckMode && m_bHasActive && (m_nActive >= nPos))
940 m_nActive += 1;
943 if ( IsReallyVisible() )
944 Invalidate();
946 m_bNeedsRecalc = true;
949 void ExtensionBox_Impl::updateEntry( const uno::Reference< deployment::XPackage > &xPackage )
951 for (auto const& entry : m_vEntries)
953 if ( entry->m_xPackage == xPackage )
955 PackageState eState = TheExtensionManager::getPackageState( xPackage );
956 entry->m_bHasOptions = m_pManager->supportsOptions( xPackage );
957 entry->m_eState = eState;
958 entry->m_sTitle = xPackage->getDisplayName();
959 entry->m_sVersion = xPackage->getVersion();
960 entry->m_sDescription = xPackage->getDescription();
962 if ( eState == REGISTERED )
963 entry->m_bMissingLic = false;
965 if ( eState == AMBIGUOUS )
966 entry->m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
967 else if ( ! entry->m_bMissingLic )
968 entry->m_sErrorText.clear();
970 if ( IsReallyVisible() )
971 Invalidate();
972 break;
977 //This function is also called as a result of removing an extension.
978 //see PackageManagerImpl::removePackage
979 //The gui is a registered as listener on the package. Removing it will cause the
980 //listeners to be notified and then this function is called. At this moment xPackage
981 //is in the disposing state and all calls on it may result in a DisposedException.
982 void ExtensionBox_Impl::removeEntry( const uno::Reference< deployment::XPackage > &xPackage )
984 if ( ! m_bInDelete )
986 bool invalidate = false;
988 ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
990 auto iIndex = std::find_if(m_vEntries.begin(), m_vEntries.end(),
991 [&xPackage](const TEntry_Impl& rxEntry) { return rxEntry->m_xPackage == xPackage; });
992 if (iIndex != m_vEntries.end())
994 long nPos = iIndex - m_vEntries.begin();
996 // Entries mustn't be removed here, because they contain a hyperlink control
997 // which can only be deleted when the thread has the solar mutex. Therefore
998 // the entry will be moved into the m_vRemovedEntries list which will be
999 // cleared on the next paint event
1000 m_vRemovedEntries.push_back( *iIndex );
1001 (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener.get());
1002 m_vEntries.erase( iIndex );
1004 m_bNeedsRecalc = true;
1006 if ( IsReallyVisible() )
1007 invalidate = true;
1009 if ( m_bHasActive )
1011 if ( nPos < m_nActive )
1012 m_nActive -= 1;
1013 else if ( ( nPos == m_nActive ) &&
1014 ( nPos == static_cast<long>(m_vEntries.size()) ) )
1015 m_nActive -= 1;
1017 m_bHasActive = false;
1018 //clear before calling out of this method
1019 aGuard.clear();
1020 selectEntry( m_nActive );
1025 if (invalidate)
1027 SolarMutexGuard g;
1028 Invalidate();
1034 void ExtensionBox_Impl::RemoveUnlocked()
1036 bool bAllRemoved = false;
1038 while ( ! bAllRemoved )
1040 bAllRemoved = true;
1042 ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
1044 for (auto const& entry : m_vEntries)
1046 if ( !entry->m_bLocked )
1048 bAllRemoved = false;
1049 uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage;
1050 aGuard.clear();
1051 removeEntry( xPackage );
1052 break;
1059 void ExtensionBox_Impl::prepareChecking()
1061 m_bInCheckMode = true;
1062 for (auto const& entry : m_vEntries)
1064 entry->m_bChecked = false;
1065 entry->m_bNew = false;
1070 void ExtensionBox_Impl::checkEntries()
1072 long nNewPos = -1;
1073 long nChangedActivePos = -1;
1074 long nPos = 0;
1075 bool bNeedsUpdate = false;
1078 osl::MutexGuard guard(m_entriesMutex);
1079 auto iIndex = m_vEntries.begin();
1080 while (iIndex != m_vEntries.end())
1082 if (!(*iIndex)->m_bChecked)
1084 (*iIndex)->m_bChecked = true;
1085 bNeedsUpdate = true;
1086 nPos = iIndex - m_vEntries.begin();
1087 if ((*iIndex)->m_bNew)
1088 { // add entry to list and correct active pos
1089 if (nNewPos == -1)
1090 nNewPos = nPos;
1091 if (nPos <= m_nActive)
1092 m_nActive += 1;
1093 ++iIndex;
1095 else
1096 { // remove entry from list
1097 if (nPos < nNewPos)
1099 --nNewPos;
1101 if (nPos < nChangedActivePos)
1103 --nChangedActivePos;
1105 if (nPos < m_nActive)
1106 m_nActive -= 1;
1107 else if (nPos == m_nActive)
1109 nChangedActivePos = nPos;
1110 m_nActive = -1;
1111 m_bHasActive = false;
1113 m_vRemovedEntries.push_back(*iIndex);
1114 iIndex = m_vEntries.erase(iIndex);
1117 else
1118 ++iIndex;
1122 m_bInCheckMode = false;
1124 if ( nNewPos != - 1)
1125 selectEntry( nNewPos );
1126 else if (nChangedActivePos != -1) {
1127 selectEntry(nChangedActivePos);
1130 if ( bNeedsUpdate )
1132 m_bNeedsRecalc = true;
1133 if ( IsReallyVisible() )
1134 Invalidate();
1138 IMPL_LINK(ExtensionBox_Impl, ScrollHdl, weld::ScrolledWindow&, rScrBar, void)
1140 m_nTopIndex = rScrBar.vadjustment_get_value();
1141 Invalidate();
1144 } //namespace dp_gui
1146 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */