LibreOffice Module desktop (master)  1
dp_gui_extlistbox.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <svtools/controldims.hxx>
21 
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>
30 
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>
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>
47 
48 #define USER_PACKAGE_MANAGER "user"
49 #define SHARED_PACKAGE_MANAGER "shared"
50 
51 using namespace ::com::sun::star;
52 
53 namespace dp_gui {
54 
55 namespace {
56 
57 struct FindWeakRef
58 {
59  const uno::Reference<deployment::XPackage> m_extension;
60 
61  explicit FindWeakRef( uno::Reference<deployment::XPackage> const & ext): m_extension(ext) {}
62  bool operator () (uno::WeakReference< deployment::XPackage > const & ref);
63 };
64 
65 bool FindWeakRef::operator () (uno::WeakReference< deployment::XPackage > const & ref)
66 {
67  const uno::Reference<deployment::XPackage> ext(ref);
68  return ext == m_extension;
69 }
70 
71 } // end namespace
72 
73 // struct Entry_Impl
74 
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 )
89 {
90  try
91  {
92  m_sTitle = xPackage->getDisplayName();
93  m_sVersion = xPackage->getVersion();
94  m_sDescription = xPackage->getDescription();
95  m_sLicenseText = xPackage->getLicenseText();
96 
97  beans::StringPair aInfo( m_xPackage->getPublisherInfo() );
98  m_sPublisher = aInfo.First;
99  m_sPublisherURL = aInfo.Second;
100 
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 );
105 
106  if ( eState == AMBIGUOUS )
107  m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
108  else if ( eState == NOT_REGISTERED )
110  }
111  catch (const deployment::ExtensionRemovedException &) {}
112  catch (const uno::RuntimeException &) {}
113 }
114 
115 
117 {}
118 
119 
120 sal_Int32 Entry_Impl::CompareTo( const CollatorWrapper *pCollator, const TEntry_Impl& rEntry ) const
121 {
122  sal_Int32 eCompare = pCollator->compareString( m_sTitle, rEntry->m_sTitle );
123  if ( eCompare == 0 )
124  {
125  eCompare = m_sVersion.compareTo( rEntry->m_sVersion );
126  if ( eCompare == 0 )
127  {
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;
133  }
134  }
135  return eCompare;
136 }
137 
138 
140 {
141  try {
142  m_xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
143  }
144  catch ( const deployment::DeploymentException &e )
145  {
146  deployment::DependencyException depExc;
147  if ( e.Cause >>= depExc )
148  {
149  OUStringBuffer aMissingDep( DpResId( RID_STR_ERROR_MISSING_DEPENDENCIES ) );
150  for ( sal_Int32 i = 0; i < depExc.UnsatisfiedDependencies.getLength(); ++i )
151  {
152  aMissingDep.append("\n");
153  aMissingDep.append(dp_misc::Dependencies::getErrorText( depExc.UnsatisfiedDependencies[i]));
154  }
155  aMissingDep.append("\n");
156  m_sErrorText = aMissingDep.makeStringAndClear();
157  m_bMissingDeps = true;
158  }
159  }
160 }
161 
162 // ExtensionRemovedListener
163 
164 void ExtensionRemovedListener::disposing( lang::EventObject const & rEvt )
165 {
166  uno::Reference< deployment::XPackage > xPackage( rEvt.Source, uno::UNO_QUERY );
167 
168  if ( xPackage.is() )
169  {
170  m_pParent->removeEntry( xPackage );
171  }
172 }
173 
174 
176 {
177 }
178 
179 
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))
198 {
199 }
200 
202 {
203  m_xScrollBar->set_user_managed_scrolling();
204  m_xScrollBar->connect_vadjustment_changed( LINK( this, ExtensionBox_Impl, ScrollHdl ) );
205 
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;
213 
214  nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1;
215  if ( m_nStdHeight < nIconHeight )
216  m_nStdHeight = nIconHeight;
217 
219 
221 
222  m_pLocale.reset( new lang::Locale( Application::GetSettings().GetLanguageTag().getLocale() ) );
224  m_pCollator->loadDefaultCollator( *m_pLocale, i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
225 }
226 
228 {
229  if ( ! m_bInDelete )
230  DeleteRemoved();
231 
232  m_bInDelete = true;
233 
234  for (auto const& entry : m_vEntries)
235  {
236  entry->m_xPackage->removeEventListener( m_xRemoveListener.get() );
237  }
238 
239  m_vEntries.clear();
240 
241  m_xRemoveListener.clear();
242 
243  m_pLocale.reset();
244  m_pCollator.reset();
245 }
246 
248 {
249  return static_cast< sal_Int32 >( m_vEntries.size() );
250 }
251 
252 
254 {
255  if ( m_bHasActive )
256  {
257  OSL_ASSERT( m_nActive >= -1);
258  return static_cast< sal_Int32 >( m_nActive );
259  }
260  else
261  return ENTRY_NOTFOUND;
262 }
263 
264 
265 // Title + description
266 void ExtensionBox_Impl::CalcActiveHeight( const long nPos )
267 {
268  const ::osl::MutexGuard aGuard( m_entriesMutex );
269 
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;
278 
279  // calc description height
280  Size aSize = GetOutputSizePixel();
281 
282  aSize.AdjustWidth( -(ICON_OFFSET) );
283  aSize.setHeight( 10000 );
284 
285  OUString aText( m_vEntries[ nPos ]->m_sErrorText );
286  if ( !aText.isEmpty() )
287  aText += "\n";
288  aText += m_vEntries[ nPos ]->m_sDescription;
289 
291  DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
292  aTextHeight += aRect.GetHeight();
293 
294  if ( aTextHeight < m_nStdHeight )
295  aTextHeight = m_nStdHeight;
296 
297  m_nActiveHeight = aTextHeight;
298 
299  if ( m_vEntries[ nPos ]->m_bHasButtons )
300  m_nActiveHeight += 2;
301 }
302 
304 {
305  const ::osl::MutexGuard aGuard( m_entriesMutex );
306 
307  Size aSize( GetOutputSizePixel() );
308 
309  if ( m_vEntries[ nPos ]->m_bActive )
310  aSize.setHeight( m_nActiveHeight );
311  else
312  aSize.setHeight( m_nStdHeight );
313 
314  Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight );
315  if ( m_bHasActive && ( nPos < m_nActive ) )
316  aPos.AdjustY(m_nActiveHeight - m_nStdHeight );
317 
318  return tools::Rectangle( aPos, aSize );
319 }
320 
321 
323 {
324  const ::osl::MutexGuard aGuard( m_entriesMutex );
325 
326  m_bInDelete = true;
327 
328  m_vRemovedEntries.clear();
329 
330  m_bInDelete = false;
331 }
332 
333 
334 //This function may be called with nPos < 0
335 void ExtensionBox_Impl::selectEntry( const long nPos )
336 {
337  bool invalidate = false;
338  {
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);
346 
347  if ( m_bInCheckMode )
348  return;
349 
350  if ( m_bHasActive )
351  {
352  if ( nPos == m_nActive )
353  return;
354 
355  m_bHasActive = false;
356  m_vEntries[ m_nActive ]->m_bActive = false;
357  }
358 
359  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
360  {
361  m_bHasActive = true;
362  m_nActive = nPos;
363  m_vEntries[ nPos ]->m_bActive = true;
364 
365  if ( IsReallyVisible() )
366  {
367  m_bAdjustActive = true;
368  }
369  }
370 
371  if ( IsReallyVisible() )
372  {
373  m_bNeedsRecalc = true;
374  invalidate = true;
375  }
376  }
377 
378  if (invalidate)
379  {
380  SolarMutexGuard g;
381  Invalidate();
382  }
383 }
384 
385 
386 void ExtensionBox_Impl::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry)
387 {
388  const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
389 
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());
396 
397  if (rEntry->m_bActive)
398  {
399  rRenderContext.SetLineColor();
400  rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
401  rRenderContext.DrawRect(rRect);
402  }
403  else
404  {
405  rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
406  rRenderContext.SetTextFillColor();
407  rRenderContext.Erase(rRect);
408  }
409 
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);
425 
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();
435 
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())
442  {
443  nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher);
444  nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN);
445  }
446  long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion);
447 
448  aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET);
449 
450  rRenderContext.SetFont(aBoldFont);
451  long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3);
452  if (aTitleWidth > nMaxTitleWidth - aVersionWidth)
453  {
454  aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3);
455  OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth);
456  rRenderContext.DrawText(aPos, aShortTitle);
457  aTitleWidth += (aTextHeight / 3);
458  }
459  else
460  rRenderContext.DrawText(aPos, rEntry->m_sTitle);
461 
462  rRenderContext.SetFont(aStdFont);
463  rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion);
464 
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;
471 
472  // draw description
473  OUString sDescription;
474  if (!rEntry->m_sErrorText.isEmpty())
475  {
476  if (rEntry->m_bActive)
477  sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription;
478  else
479  sDescription = rEntry->m_sErrorText;
480  }
481  else
482  sDescription = rEntry->m_sDescription;
483 
484  aPos.AdjustY(aTextHeight );
485  if (rEntry->m_bActive)
486  {
487  long nExtraHeight = 0;
488 
489  if (rEntry->m_bHasButtons)
490  nExtraHeight = 2;
491 
492  rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight),
493  sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
494  }
495  else
496  {
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);
503  }
504 
505  // Draw publisher link
506  if (!rEntry->m_sPublisher.isEmpty())
507  {
508  aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET );
509 
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
516  rRenderContext.SetFont(aFont);
517  rRenderContext.DrawText(aPos, rEntry->m_sPublisher);
518  rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight));
519  rRenderContext.Pop();
520  }
521 
522  // Draw status icons
523  if (!rEntry->m_bUser)
524  {
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);
530  }
531  if ((rEntry->m_eState == AMBIGUOUS ) || rEntry->m_bMissingDeps || rEntry->m_bMissingLic)
532  {
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);
535  }
536 
537  rRenderContext.SetLineColor(COL_LIGHTGRAY);
538  rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight());
539 }
540 
541 
543 {
544  if ( m_bHasActive )
546 
547  SetupScrollBar();
548 
549  if ( m_bHasActive )
550  {
551  tools::Rectangle aEntryRect = GetEntryRect( m_nActive );
552 
553  if ( m_bAdjustActive )
554  {
555  m_bAdjustActive = false;
556 
557  // If the top of the selected entry isn't visible, make it visible
558  if ( aEntryRect.Top() < 0 )
559  {
560  m_nTopIndex += aEntryRect.Top();
561  aEntryRect.Move( 0, -aEntryRect.Top() );
562  }
563 
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() )
568  {
569  m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() );
570  aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) );
571  }
572 
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 ) )
577  {
578  long nOffset = m_nTopIndex;
579  m_nTopIndex = nTotalHeight - aOutputSize.Height();
580  nOffset -= m_nTopIndex;
581  aEntryRect.Move( 0, nOffset );
582  }
583 
584  if ( m_bHasScrollBar )
585  m_xScrollBar->vadjustment_set_value( m_nTopIndex );
586  }
587  }
588 
589  m_bNeedsRecalc = false;
590 }
591 
592 
593 bool ExtensionBox_Impl::HandleCursorKey( sal_uInt16 nKeyCode )
594 {
595  if ( m_vEntries.empty() )
596  return true;
597 
598  long nSelect = 0;
599 
600  if ( m_bHasActive )
601  {
602  long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight;
603  if ( nPageSize < 2 )
604  nPageSize = 2;
605 
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;
618  }
619  else // when there is no selected entry, we will select the first or the last.
620  {
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;
625  }
626 
627  if ( nSelect < 0 )
628  nSelect = 0;
629  if ( nSelect >= static_cast<long>(m_vEntries.size()) )
630  nSelect = m_vEntries.size() - 1;
631 
632  selectEntry( nSelect );
633 
634  return true;
635 }
636 
637 
638 void ExtensionBox_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/)
639 {
640  if ( !m_bInDelete )
641  DeleteRemoved();
642 
643  if ( m_bNeedsRecalc )
644  RecalcAll();
645 
646  Point aStart( 0, -m_nTopIndex );
647  Size aSize(GetOutputSizePixel());
648 
649  const ::osl::MutexGuard aGuard( m_entriesMutex );
650 
651  for (auto const& entry : m_vEntries)
652  {
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() );
657  }
658 }
659 
660 
662 {
663  long nHeight = m_vEntries.size() * m_nStdHeight;
664 
665  if ( m_bHasActive )
666  {
667  nHeight += m_nActiveHeight - m_nStdHeight;
668  }
669 
670  return nHeight;
671 }
672 
673 
675 {
676  const Size aSize = GetOutputSizePixel();
677  const auto nTotalHeight = GetTotalHeight();
678  const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() );
679 
680  if ( bNeedsScrollBar )
681  {
682  if ( m_nTopIndex + aSize.Height() > nTotalHeight )
683  m_nTopIndex = nTotalHeight - aSize.Height();
684 
685  m_xScrollBar->vadjustment_configure(m_nTopIndex, 0, nTotalHeight,
686  m_nStdHeight, ( aSize.Height() * 4 ) / 5,
687  aSize.Height());
688 
689  if (!m_bHasScrollBar)
690  m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS);
691  }
692  else if ( m_bHasScrollBar )
693  {
694  m_xScrollBar->set_vpolicy(VclPolicyType::NEVER);
695  m_nTopIndex = 0;
696  }
697 
698  m_bHasScrollBar = bNeedsScrollBar;
699 }
700 
701 
703 {
704  RecalcAll();
705  Invalidate();
706 }
707 
709 {
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);
714 
715  Init();
716 }
717 
719 {
720  long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight;
721 
722  if ( m_bHasActive && ( nPos > m_nActive ) )
723  {
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;
728  }
729 
730  return nPos;
731 }
732 
734 {
735  bool bOverHyperlink = false;
736 
737  auto nPos = PointToPos( rMEvt.GetPosPixel() );
738  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
739  {
740  const auto& rEntry = m_vEntries[nPos];
741  bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rMEvt.GetPosPixel());
742  }
743 
744  if (bOverHyperlink)
745  SetPointer(PointerStyle::RefHand);
746  else
747  SetPointer(PointerStyle::Arrow);
748 
749  return false;
750 }
751 
753 {
754  auto nPos = PointToPos( rRect.TopLeft() );
755  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
756  {
757  const auto& rEntry = m_vEntries[nPos];
758  bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rRect);
759  if (bOverHyperlink)
760  {
761  rRect = rEntry->m_aLinkRect;
762  return rEntry->m_sPublisherURL;
763  }
764  }
765 
766  return OUString();
767 }
768 
770 {
771  if ( rMEvt.IsLeft() )
772  {
773  if (rMEvt.IsMod1() && m_bHasActive)
774  selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND); // Selecting a not existing entry will deselect the current one
775  else
776  {
777  auto nPos = PointToPos( rMEvt.GetPosPixel() );
778 
779  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
780  {
781  const auto& rEntry = m_vEntries[nPos];
782  if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rMEvt.GetPosPixel()))
783  {
784  try
785  {
786  css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute(
788  //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
789  xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY);
790  }
791  catch (...)
792  {
793  }
794  return true;
795  }
796  }
797 
798  selectEntry( nPos );
799  }
800  return true;
801  }
802 
803  return false;
804 }
805 
807 {
808  if ( !m_bInDelete )
809  DeleteRemoved();
810 
811  vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
812  sal_uInt16 nKeyCode = aKeyCode.GetCode();
813 
814  bool bHandled = false;
815  if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR)
816  bHandled = HandleCursorKey(nKeyCode);
817 
818  return bHandled;
819 }
820 
821 bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const long nStart,
822  const long nEnd, long &nPos )
823 {
824  nPos = nStart;
825  if ( nStart > nEnd )
826  return false;
827 
828  sal_Int32 eCompare;
829 
830  if ( nStart == nEnd )
831  {
832  eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nStart ] );
833  if ( eCompare < 0 )
834  return false;
835  else if ( eCompare == 0 )
836  {
837  //Workaround. See i86963.
838  if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage)
839  return false;
840 
841  if ( m_bInCheckMode )
842  m_vEntries[ nStart ]->m_bChecked = true;
843  return true;
844  }
845  else
846  {
847  nPos = nStart + 1;
848  return false;
849  }
850  }
851 
852  const long nMid = nStart + ( ( nEnd - nStart ) / 2 );
853  eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nMid ] );
854 
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
860  {
861  //Workaround.See i86963.
862  if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage)
863  return false;
864 
865  if ( m_bInCheckMode )
866  m_vEntries[ nMid ]->m_bChecked = true;
867  nPos = nMid;
868  return true;
869  }
870 }
871 
873 {
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();
878  }),
879  m_vListenerAdded.end());
880 }
881 
883  uno::Reference<deployment::XPackage > const & extension)
884 {
885  //make sure to only add the listener once
887  if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(),
888  FindWeakRef(extension)) )
889  {
890  extension->addEventListener( m_xRemoveListener.get() );
891  m_vListenerAdded.emplace_back(extension);
892  }
893 }
894 
895 
896 void ExtensionBox_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage,
897  bool bLicenseMissing )
898 {
899  long nPos = 0;
901  bool bLocked = m_pManager->isReadOnly( xPackage );
902 
903  TEntry_Impl pEntry( new Entry_Impl( xPackage, eState, bLocked ) );
904 
905  // Don't add empty entries
906  if ( pEntry->m_sTitle.isEmpty() )
907  return;
908 
909  {
910  osl::MutexGuard guard(m_entriesMutex);
911  if (m_vEntries.empty())
912  {
913  addEventListenerOnce(xPackage);
914  m_vEntries.push_back(pEntry);
915  }
916  else
917  {
918  if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos))
919  {
920  addEventListenerOnce(xPackage);
921  m_vEntries.insert(m_vEntries.begin() + nPos, pEntry);
922  }
923  else if (!m_bInCheckMode)
924  {
925  OSL_FAIL("ExtensionBox_Impl::addEntry(): Will not add duplicate entries");
926  }
927  }
928 
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;
934 
935  if (bLicenseMissing)
936  pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE);
937 
938  //access to m_nActive must be guarded
939  if (!m_bInCheckMode && m_bHasActive && (m_nActive >= nPos))
940  m_nActive += 1;
941  }
942 
943  if ( IsReallyVisible() )
944  Invalidate();
945 
946  m_bNeedsRecalc = true;
947 }
948 
949 void ExtensionBox_Impl::updateEntry( const uno::Reference< deployment::XPackage > &xPackage )
950 {
951  for (auto const& entry : m_vEntries)
952  {
953  if ( entry->m_xPackage == xPackage )
954  {
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();
961 
962  if ( eState == REGISTERED )
963  entry->m_bMissingLic = false;
964 
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();
969 
970  if ( IsReallyVisible() )
971  Invalidate();
972  break;
973  }
974  }
975 }
976 
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 )
983 {
984  if ( ! m_bInDelete )
985  {
986  bool invalidate = false;
987  {
988  ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
989 
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())
993  {
994  long nPos = iIndex - m_vEntries.begin();
995 
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 );
1003 
1004  m_bNeedsRecalc = true;
1005 
1006  if ( IsReallyVisible() )
1007  invalidate = true;
1008 
1009  if ( m_bHasActive )
1010  {
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;
1016 
1017  m_bHasActive = false;
1018  //clear before calling out of this method
1019  aGuard.clear();
1021  }
1022  }
1023  }
1024 
1025  if (invalidate)
1026  {
1027  SolarMutexGuard g;
1028  Invalidate();
1029  }
1030  }
1031 }
1032 
1033 
1035 {
1036  bool bAllRemoved = false;
1037 
1038  while ( ! bAllRemoved )
1039  {
1040  bAllRemoved = true;
1041 
1042  ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
1043 
1044  for (auto const& entry : m_vEntries)
1045  {
1046  if ( !entry->m_bLocked )
1047  {
1048  bAllRemoved = false;
1049  uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage;
1050  aGuard.clear();
1051  removeEntry( xPackage );
1052  break;
1053  }
1054  }
1055  }
1056 }
1057 
1058 
1060 {
1061  m_bInCheckMode = true;
1062  for (auto const& entry : m_vEntries)
1063  {
1064  entry->m_bChecked = false;
1065  entry->m_bNew = false;
1066  }
1067 }
1068 
1069 
1071 {
1072  long nNewPos = -1;
1073  long nChangedActivePos = -1;
1074  long nPos = 0;
1075  bool bNeedsUpdate = false;
1076 
1077  {
1078  osl::MutexGuard guard(m_entriesMutex);
1079  auto iIndex = m_vEntries.begin();
1080  while (iIndex != m_vEntries.end())
1081  {
1082  if (!(*iIndex)->m_bChecked)
1083  {
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;
1094  }
1095  else
1096  { // remove entry from list
1097  if (nPos < nNewPos)
1098  {
1099  --nNewPos;
1100  }
1101  if (nPos < nChangedActivePos)
1102  {
1103  --nChangedActivePos;
1104  }
1105  if (nPos < m_nActive)
1106  m_nActive -= 1;
1107  else if (nPos == m_nActive)
1108  {
1109  nChangedActivePos = nPos;
1110  m_nActive = -1;
1111  m_bHasActive = false;
1112  }
1113  m_vRemovedEntries.push_back(*iIndex);
1114  iIndex = m_vEntries.erase(iIndex);
1115  }
1116  }
1117  else
1118  ++iIndex;
1119  }
1120  }
1121 
1122  m_bInCheckMode = false;
1123 
1124  if ( nNewPos != - 1)
1125  selectEntry( nNewPos );
1126  else if (nChangedActivePos != -1) {
1127  selectEntry(nChangedActivePos);
1128  }
1129 
1130  if ( bNeedsUpdate )
1131  {
1132  m_bNeedsRecalc = true;
1133  if ( IsReallyVisible() )
1134  Invalidate();
1135  }
1136 }
1137 
1139 {
1140  m_nTopIndex = rScrBar.vadjustment_get_value();
1141  Invalidate();
1142 }
1143 
1144 } //namespace dp_gui
1145 
1146 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Point TopLeft() const
long Width() const
Size GetSizePixel() const
void DrawText(const Point &rStartPt, const OUString &rStr, sal_Int32 nIndex=0, sal_Int32 nLen=-1, MetricVector *pVector=nullptr, OUString *pDisplayText=nullptr, const SalLayoutGlyphs *pLayoutCache=nullptr)
void addEntry(const css::uno::Reference< css::deployment::XPackage > &xPackage, bool bLicenseMissing=false)
OUString GetEllipsisString(const OUString &rStr, long nMaxWidth, DrawTextFlags nStyle=DrawTextFlags::EndEllipsis) const
long GetWidth() const
void updateEntry(const css::uno::Reference< css::deployment::XPackage > &xPackage)
#define ICON_WIDTH
#define KEYGROUP_CURSOR
long GetHeight() const
sal_Int32 compareString(const OUString &s1, const OUString &s2) const
void DrawImage(const Point &rPos, const Image &rImage, DrawImageFlags nStyle=DrawImageFlags::NONE)
virtual void Resize() override
bool HandleCursorKey(sal_uInt16 nKeyCode)
long AdjustWidth(long n)
void CalcActiveHeight(const long nPos)
#define KEY_TAB
#define KEY_PAGEDOWN
const Color & GetHighlightTextColor() const
long Height() const
#define KEY_PAGEUP
sal_Int32 CompareTo(const CollatorWrapper *pCollator, const TEntry_Impl &rEntry) const
Point BottomLeft() const
bool FindEntryPos(const TEntry_Impl &rEntry, long nStart, long nEnd, long &nFound)
const StyleSettings & GetStyleSettings() const
static const AllSettings & GetSettings()
Entry_Impl(const css::uno::Reference< css::deployment::XPackage > &xPackage, const PackageState eState, const bool bReadOnly)
virtual bool MouseButtonDown(const MouseEvent &rMEvt) override
#define TOP_OFFSET
virtual void Paint(vcl::RenderContext &rRenderContext, const tools::Rectangle &rPaintRect) override
sal_uInt16 GetGroup() const
void SetTextFillColor()
sal_uInt16 GetCode() const
virtual ~ExtensionRemovedListener() override
constexpr::Color COL_LIGHTGRAY(0xC0, 0xC0, 0xC0)
#define KEY_LEFT
css::uno::Reference< css::deployment::XPackageRegistry > create(css::uno::Reference< css::deployment::XPackageRegistry > const &xRootRegistry, OUString const &context, OUString const &cachePath, css::uno::Reference< css::uno::XComponentContext > const &xComponentContext)
const Color & GetFieldTextColor() const
#define RIGHT_ICON_OFFSET
const Color & GetHighlightColor() const
Size const & GetOutputSizePixel() const
void Move(long nHorzMoveDelta, long nVertMoveDelta)
static OutputDevice * GetDefaultDevice()
virtual bool MouseMove(const MouseEvent &rMEvt) override
WEIGHT_BOLD
tools::Rectangle GetTextRect(const tools::Rectangle &rRect, const OUString &rStr, DrawTextFlags nStyle=DrawTextFlags::WordBreak, TextRectInfo *pInfo=nullptr, const vcl::ITextLayout *_pTextLayout=nullptr) const
virtual void selectEntry(const long nPos)
void SetBackground()
long Right() const
const vcl::Font & GetFont() const
#define SMALL_ICON_SIZE
void DrawRow(vcl::RenderContext &rRenderContext, const tools::Rectangle &rRect, const TEntry_Impl &rEntry)
long PointToPos(const Point &rPos)
std::unique_ptr< weld::ScrolledWindow > m_xScrollBar
virtual ~ExtensionBox_Impl() override
long Top() const
void SetUnderline(FontLineStyle)
virtual OutputDevice & get_ref_device()=0
void DrawLine(const Point &rStartPt, const Point &rEndPt)
void DrawRect(const tools::Rectangle &rRect)
std::unique_ptr< css::lang::Locale > m_pLocale
virtual bool KeyInput(const KeyEvent &rKEvt) override
Point BottomRight() const
void SetLineColor()
const Color & GetDisableColor() const
rtl::Reference< ExtensionRemovedListener > m_xRemoveListener
long AdjustY(long nVertMove)
virtual void SAL_CALL disposing(css::lang::EventObject const &evt) override
TheExtensionManager * m_pManager
bool isReadOnly(const css::uno::Reference< css::deployment::XPackage > &xPackage) const
int i
void SetFillColor()
const Color & GetFieldColor() const
LINESTYLE_SINGLE
void SetTextColor(const Color &rColor)
long Bottom() const
#define SHARED_PACKAGE_MANAGER
#define ICON_OFFSET
const uno::Reference< deployment::XPackage > m_extension
css::uno::Reference< css::deployment::XPackage > m_xPackage
const AllSettings & GetSettings() const
bool supportsOptions(const css::uno::Reference< css::deployment::XPackage > &xPackage) const
long GetTextHeight() const
void SetOutputSizePixel(const Size &rSize)
#define SPACE_BETWEEN
std::vector< TEntry_Impl > m_vEntries
const Color & GetLinkColor() const
#define KEY_END
sal_Int32 getSelIndex() const
Point LogicToPixel(const Point &rLogicPt) const
StockImage
const vcl::KeyCode & GetKeyCode() const
#define USER_PACKAGE_MANAGER
weld::DrawingArea * GetDrawingArea() const
#define KEY_DOWN
tools::Rectangle GetEntryRect(const long nPos) const
sal_Int32 getItemCount() const
ExtensionBox_Impl(std::unique_ptr< weld::ScrolledWindow > xScroll)
std::vector< TEntry_Impl > m_vRemovedEntries
#define ICON_HEIGHT
virtual void SetDrawingArea(weld::DrawingArea *pDrawingArea) override
void SetFont(const vcl::Font &rNewFont)
bool IsLeft() const
PackageState
Definition: dp_gui.h:52
virtual OUString RequestHelp(tools::Rectangle &rRect) override
std::unique_ptr< CollatorWrapper > m_pCollator
Reference< XComponentContext > getProcessComponentContext()
static PackageState getPackageState(const css::uno::Reference< css::deployment::XPackage > &xPackage)
const Point & GetPosPixel() const
long GetTextWidth(const OUString &rStr, sal_Int32 nIndex=0, sal_Int32 nLen=-1, vcl::TextLayoutCache const *=nullptr, SalLayoutGlyphs const *const pLayoutCache=nullptr) const
void addEventListenerOnce(css::uno::Reference< css::deployment::XPackage > const &extension)
mutable::osl::Mutex m_entriesMutex
#define KEY_HOME
OUString DpResId(const char *pId)
Definition: dp_shared.hxx:38
std::vector< css::uno::WeakReference< css::deployment::XPackage > > m_vListenerAdded
Definition: dp_gui.h:50
DESKTOP_DEPLOYMENTMISC_DLLPUBLIC OUString getErrorText(css::uno::Reference< css::xml::dom::XElement > const &dependency)
Obtain the (human-readable) error message of a failed dependency.
virtual void set_size_request(int nWidth, int nHeight)=0
void Push(PushFlags nFlags=PushFlags::ALL)
void SetPointer(PointerStyle ePointerStyle)
sal_Int32 nPos
Point TopRight() const
#define KEY_RIGHT
void removeEntry(const css::uno::Reference< css::deployment::XPackage > &xPackage)
long Y() const
std::shared_ptr< Entry_Impl > TEntry_Impl
bool IsMod1() const
IMPL_LINK(ExtMgrDialog, startProgress, void *, _bLockInterface, void)
#define KEY_UP
void setHeight(long nHeight)