1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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>
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>
49 constexpr OUStringLiteral USER_PACKAGE_MANAGER
= u
"user";
50 constexpr OUStringLiteral SHARED_PACKAGE_MANAGER
= u
"shared";
52 using namespace ::com::sun::star
;
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
;
76 Entry_Impl::Entry_Impl( const uno::Reference
< deployment::XPackage
> &xPackage
,
77 const PackageState eState
, const bool bReadOnly
) :
79 m_bLocked( bReadOnly
),
80 m_bHasOptions( false ),
85 m_bMissingDeps( false ),
86 m_bHasButtons( false ),
87 m_bMissingLic( false ),
89 m_xPackage( xPackage
)
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 );
105 m_aIcon
= Image( xGraphic
);
107 if ( eState
== AMBIGUOUS
)
108 m_sErrorText
= DpResId( RID_STR_ERROR_UNKNOWN_STATUS
);
109 else if ( eState
== NOT_REGISTERED
)
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
);
126 eCompare
= m_sVersion
.compareTo( rEntry
->m_sVersion
);
129 sal_Int32 nCompare
= m_xPackage
->getRepositoryName().compareTo( rEntry
->m_xPackage
->getRepositoryName() );
132 else if ( nCompare
> 0 )
140 void Entry_Impl::checkDependencies()
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
);
171 m_pParent
->removeEntry( xPackage
);
176 ExtensionRemovedListener::~ExtensionRemovedListener()
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 )
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
;
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()
234 for (auto const& entry
: m_vEntries
)
236 entry
->m_xPackage
->removeEventListener( m_xRemoveListener
);
241 m_xRemoveListener
.clear();
247 sal_Int32
ExtensionBox_Impl::getItemCount() const
249 return static_cast< sal_Int32
>( m_vEntries
.size() );
253 sal_Int32
ExtensionBox_Impl::getSelIndex() const
257 OSL_ASSERT( m_nActive
>= -1);
258 return static_cast< sal_Int32
>( m_nActive
);
261 return ENTRY_NOTFOUND
;
265 // Title + description
266 void ExtensionBox_Impl::CalcActiveHeight( const tools::Long nPos
)
268 const ::osl::MutexGuard
aGuard( m_entriesMutex
);
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
;
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() )
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
);
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
);
328 m_vRemovedEntries
.clear();
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
)
352 if ( nPos
== m_nActive
)
355 m_bHasActive
= false;
356 m_vEntries
[ m_nActive
]->m_bActive
= false;
359 if ( ( nPos
>= 0 ) && ( o3tl::make_unsigned(nPos
) < m_vEntries
.size() ) )
363 m_vEntries
[ nPos
]->m_bActive
= true;
365 if ( IsReallyVisible() )
367 m_bAdjustActive
= true;
371 if ( IsReallyVisible() )
373 m_bNeedsRecalc
= true;
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());
395 rRenderContext
.SetTextColor(rStyleSettings
.GetFieldTextColor());
397 if (rEntry
->m_bActive
)
399 rRenderContext
.SetLineColor();
400 rRenderContext
.SetFillColor(rStyleSettings
.GetHighlightColor());
401 rRenderContext
.DrawRect(rRect
);
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
);
414 if (!rEntry
->m_aIcon
)
415 aImage
= m_aDefaultImage
;
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)),
424 rRenderContext
.DrawImage(aPos
, Size(ICON_WIDTH
, ICON_HEIGHT
), aImage
);
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);
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
;
470 aTextHeight
= nIconHeight
;
473 OUString sDescription
;
474 if (!rEntry
->m_sErrorText
.isEmpty())
476 if (rEntry
->m_bActive
)
477 sDescription
= rEntry
->m_sErrorText
+ "\n" + rEntry
->m_sDescription
;
479 sDescription
= rEntry
->m_sErrorText
;
482 sDescription
= rEntry
->m_sDescription
;
484 aPos
.AdjustY(aTextHeight
);
485 if (rEntry
->m_bActive
)
487 tools::Long nExtraHeight
= 0;
489 if (rEntry
->m_bHasButtons
)
492 rRenderContext
.DrawText(tools::Rectangle(aPos
.X(), aPos
.Y(), rRect
.Right(), rRect
.Bottom() - nExtraHeight
),
493 sDescription
, DrawTextFlags::MultiLine
| DrawTextFlags::WordBreak
);
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();
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();
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
);
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()
545 CalcActiveHeight( m_nActive
);
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() )
598 tools::Long nSelect
= 0;
602 tools::Long nPageSize
= GetOutputSizePixel().Height() / m_nStdHeight
;
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
)
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
) )
623 else if ( ( nKeyCode
== KEY_UP
) || ( nKeyCode
== KEY_PAGEUP
) || ( nKeyCode
== KEY_END
) )
624 nSelect
= m_vEntries
.size() - 1;
629 if ( o3tl::make_unsigned(nSelect
) >= m_vEntries
.size() )
630 nSelect
= m_vEntries
.size() - 1;
632 selectEntry( nSelect
);
638 void ExtensionBox_Impl::Paint(vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& /*rPaintRect*/)
643 if ( m_bNeedsRecalc
)
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
;
667 nHeight
+= m_nActiveHeight
- m_nStdHeight
;
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,
689 if (!m_bHasScrollBar
)
690 m_xScrollBar
->set_vpolicy(VclPolicyType::ALWAYS
);
692 else if ( m_bHasScrollBar
)
694 m_xScrollBar
->set_vpolicy(VclPolicyType::NEVER
);
698 m_bHasScrollBar
= bNeedsScrollBar
;
702 void ExtensionBox_Impl::Resize()
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
);
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
)
727 nPos
= ( rPos
.Y() + m_nTopIndex
- (m_nActiveHeight
- m_nStdHeight
) ) / m_nStdHeight
;
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());
745 SetPointer(PointerStyle::RefHand
);
747 SetPointer(PointerStyle::Arrow
);
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
);
761 rRect
= rEntry
->m_aLinkRect
;
762 return rEntry
->m_sPublisherURL
;
769 bool ExtensionBox_Impl::MouseButtonDown( const MouseEvent
& rMEvt
)
771 if ( !rMEvt
.IsLeft() )
774 if (rMEvt
.IsMod1() && m_bHasActive
)
775 selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND
); // Selecting a not existing entry will deselect the current one
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
);
804 bool ExtensionBox_Impl::KeyInput(const KeyEvent
& rKEvt
)
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
);
819 bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl
& rEntry
, const tools::Long nStart
,
820 const tools::Long nEnd
, tools::Long
&nPos
)
828 if ( nStart
== nEnd
)
830 eCompare
= rEntry
->CompareTo( &*m_oCollator
, m_vEntries
[ nStart
] );
833 else if ( eCompare
== 0 )
835 //Workaround. See i86963.
836 if (rEntry
->m_xPackage
!= m_vEntries
[nStart
]->m_xPackage
)
839 if ( m_bInCheckMode
)
840 m_vEntries
[ nStart
]->m_bChecked
= true;
850 const tools::Long nMid
= nStart
+ ( ( nEnd
- nStart
) / 2 );
851 eCompare
= rEntry
->CompareTo( &*m_oCollator
, m_vEntries
[ nMid
] );
854 return FindEntryPos( rEntry
, nStart
, nMid
-1, nPos
);
855 else if ( eCompare
> 0 )
856 return FindEntryPos( rEntry
, nMid
+1, nEnd
, nPos
);
859 //Workaround.See i86963.
860 if (rEntry
->m_xPackage
!= m_vEntries
[nMid
]->m_xPackage
)
863 if ( m_bInCheckMode
)
864 m_vEntries
[ nMid
]->m_bChecked
= 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() )
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
);
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
;
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
))
940 if ( IsReallyVisible() )
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() )
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
)
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() )
1009 if ( nPos
< m_nActive
)
1011 else if ( ( nPos
== m_nActive
) &&
1012 ( nPos
== static_cast<tools::Long
>(m_vEntries
.size()) ) )
1015 m_bHasActive
= false;
1016 //clear before calling out of this method
1018 selectEntry( m_nActive
);
1031 void ExtensionBox_Impl::RemoveUnlocked()
1033 bool bAllRemoved
= false;
1035 while ( ! bAllRemoved
)
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
;
1048 removeEntry( xPackage
);
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
1088 if (nPos
<= m_nActive
)
1093 { // remove entry from list
1098 if (nPos
< nChangedActivePos
)
1100 --nChangedActivePos
;
1102 if (nPos
< m_nActive
)
1104 else if (nPos
== m_nActive
)
1106 nChangedActivePos
= nPos
;
1108 m_bHasActive
= false;
1110 m_vRemovedEntries
.push_back(*iIndex
);
1111 (*iIndex
)->m_xPackage
->removeEventListener(m_xRemoveListener
);
1112 iIndex
= m_vEntries
.erase(iIndex
);
1120 m_bInCheckMode
= false;
1122 if ( nNewPos
!= - 1)
1123 selectEntry( nNewPos
);
1124 else if (nChangedActivePos
!= -1) {
1125 selectEntry(nChangedActivePos
);
1130 m_bNeedsRecalc
= true;
1131 if ( IsReallyVisible() )
1136 IMPL_LINK(ExtensionBox_Impl
, ScrollHdl
, weld::ScrolledWindow
&, rScrBar
, void)
1138 m_nTopIndex
= rScrBar
.vadjustment_get_value();
1142 } //namespace dp_gui
1144 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */