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 <dp_shared.hxx>
21 #include <strings.hrc>
22 #include "dp_gui.h"
23 #include "dp_gui_extlistbox.hxx"
24 #include "dp_gui_theextmgr.hxx"
25 #include <dp_dependencies.hxx>
26 #include <bitmaps.hlst>
27 
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>
38 #include <osl/diagnose.h>
39 #include <rtl/ustrbuf.hxx>
40 #include <vcl/event.hxx>
41 #include <vcl/ptrstyle.hxx>
42 #include <vcl/svapp.hxx>
43 #include <vcl/settings.hxx>
44 #include <algorithm>
45 
46 #define USER_PACKAGE_MANAGER "user"
47 #define SHARED_PACKAGE_MANAGER "shared"
48 
49 using namespace ::com::sun::star;
50 
51 namespace dp_gui {
52 
53 namespace {
54 
55 struct FindWeakRef
56 {
57  const uno::Reference<deployment::XPackage> m_extension;
58 
59  explicit FindWeakRef( uno::Reference<deployment::XPackage> const & ext): m_extension(ext) {}
60  bool operator () (uno::WeakReference< deployment::XPackage > const & ref);
61 };
62 
63 bool FindWeakRef::operator () (uno::WeakReference< deployment::XPackage > const & ref)
64 {
65  const uno::Reference<deployment::XPackage> ext(ref);
66  return ext == m_extension;
67 }
68 
69 } // end namespace
70 
71 // struct Entry_Impl
72 
73 Entry_Impl::Entry_Impl( const uno::Reference< deployment::XPackage > &xPackage,
74  const PackageState eState, const bool bReadOnly ) :
75  m_bActive( false ),
76  m_bLocked( bReadOnly ),
77  m_bHasOptions( false ),
78  m_bUser( false ),
79  m_bShared( false ),
80  m_bNew( false ),
81  m_bChecked( false ),
82  m_bMissingDeps( false ),
83  m_bHasButtons( false ),
84  m_bMissingLic( false ),
85  m_eState( eState ),
86  m_xPackage( xPackage )
87 {
88  try
89  {
90  m_sTitle = xPackage->getDisplayName();
91  m_sVersion = xPackage->getVersion();
92  m_sDescription = xPackage->getDescription();
93  m_sLicenseText = xPackage->getLicenseText();
94 
95  beans::StringPair aInfo( m_xPackage->getPublisherInfo() );
96  m_sPublisher = aInfo.First;
97  m_sPublisherURL = aInfo.Second;
98 
99  // get the icons for the package if there are any
100  uno::Reference< graphic::XGraphic > xGraphic = xPackage->getIcon( false );
101  if ( xGraphic.is() )
102  m_aIcon = Image( xGraphic );
103 
104  if ( eState == AMBIGUOUS )
105  m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
106  else if ( eState == NOT_REGISTERED )
108  }
109  catch (const deployment::ExtensionRemovedException &) {}
110  catch (const uno::RuntimeException &) {}
111 }
112 
113 
115 {}
116 
117 
118 sal_Int32 Entry_Impl::CompareTo( const CollatorWrapper *pCollator, const TEntry_Impl& rEntry ) const
119 {
120  sal_Int32 eCompare = pCollator->compareString( m_sTitle, rEntry->m_sTitle );
121  if ( eCompare == 0 )
122  {
123  eCompare = m_sVersion.compareTo( rEntry->m_sVersion );
124  if ( eCompare == 0 )
125  {
126  sal_Int32 nCompare = m_xPackage->getRepositoryName().compareTo( rEntry->m_xPackage->getRepositoryName() );
127  if ( nCompare < 0 )
128  eCompare = -1;
129  else if ( nCompare > 0 )
130  eCompare = 1;
131  }
132  }
133  return eCompare;
134 }
135 
136 
138 {
139  try {
140  m_xPackage->checkDependencies( uno::Reference< ucb::XCommandEnvironment >() );
141  }
142  catch ( const deployment::DeploymentException &e )
143  {
144  deployment::DependencyException depExc;
145  if ( e.Cause >>= depExc )
146  {
147  OUStringBuffer aMissingDep( DpResId( RID_STR_ERROR_MISSING_DEPENDENCIES ) );
148  for ( const auto& i : std::as_const(depExc.UnsatisfiedDependencies) )
149  {
150  aMissingDep.append("\n");
151  aMissingDep.append(dp_misc::Dependencies::getErrorText(i));
152  }
153  aMissingDep.append("\n");
154  m_sErrorText = aMissingDep.makeStringAndClear();
155  m_bMissingDeps = true;
156  }
157  }
158 }
159 
160 // ExtensionRemovedListener
161 
162 void ExtensionRemovedListener::disposing( lang::EventObject const & rEvt )
163 {
164  uno::Reference< deployment::XPackage > xPackage( rEvt.Source, uno::UNO_QUERY );
165 
166  if ( xPackage.is() )
167  {
168  m_pParent->removeEntry( xPackage );
169  }
170 }
171 
172 
174 {
175 }
176 
177 
178 // ExtensionBox_Impl
179 ExtensionBox_Impl::ExtensionBox_Impl(std::unique_ptr<weld::ScrolledWindow> xScroll)
180  : m_bHasScrollBar( false )
181  , m_bHasActive( false )
182  , m_bNeedsRecalc( true )
183  , m_bInCheckMode( false )
184  , m_bAdjustActive( false )
185  , m_bInDelete( false )
186  , m_nActive( 0 )
187  , m_nTopIndex( 0 )
188  , m_nStdHeight( 0 )
189  , m_nActiveHeight( 0 )
190  , m_aSharedImage(StockImage::Yes, RID_BMP_SHARED)
191  , m_aLockedImage(StockImage::Yes, RID_BMP_LOCKED)
192  , m_aWarningImage(StockImage::Yes, RID_BMP_WARNING)
193  , m_aDefaultImage(StockImage::Yes, RID_BMP_EXTENSION)
194  , m_pManager( nullptr )
195  , m_xScrollBar(std::move(xScroll))
196 {
197 }
198 
200 {
201  m_xScrollBar->set_user_managed_scrolling();
202  m_xScrollBar->connect_vadjustment_changed( LINK( this, ExtensionBox_Impl, ScrollHdl ) );
203 
204  auto nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
205  auto nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
206  if ( nIconHeight < nTitleHeight )
207  m_nStdHeight = nTitleHeight;
208  else
209  m_nStdHeight = nIconHeight;
211 
212  nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1;
213  if ( m_nStdHeight < nIconHeight )
214  m_nStdHeight = nIconHeight;
215 
217 
219 
220  m_pLocale.reset( new lang::Locale( Application::GetSettings().GetLanguageTag().getLocale() ) );
222  m_pCollator->loadDefaultCollator( *m_pLocale, i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
223 }
224 
226 {
227  if ( ! m_bInDelete )
228  DeleteRemoved();
229 
230  m_bInDelete = true;
231 
232  for (auto const& entry : m_vEntries)
233  {
234  entry->m_xPackage->removeEventListener( m_xRemoveListener.get() );
235  }
236 
237  m_vEntries.clear();
238 
239  m_xRemoveListener.clear();
240 
241  m_pLocale.reset();
242  m_pCollator.reset();
243 }
244 
246 {
247  return static_cast< sal_Int32 >( m_vEntries.size() );
248 }
249 
250 
252 {
253  if ( m_bHasActive )
254  {
255  OSL_ASSERT( m_nActive >= -1);
256  return static_cast< sal_Int32 >( m_nActive );
257  }
258  else
259  return ENTRY_NOTFOUND;
260 }
261 
262 
263 // Title + description
264 void ExtensionBox_Impl::CalcActiveHeight( const long nPos )
265 {
266  const ::osl::MutexGuard aGuard( m_entriesMutex );
267 
268  // get title height
269  long aTextHeight;
270  long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
271  long nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
272  if ( nIconHeight < nTitleHeight )
273  aTextHeight = nTitleHeight;
274  else
275  aTextHeight = nIconHeight;
276 
277  // calc description height
278  Size aSize = GetOutputSizePixel();
279 
280  aSize.AdjustWidth( -(ICON_OFFSET) );
281  aSize.setHeight( 10000 );
282 
283  OUString aText( m_vEntries[ nPos ]->m_sErrorText );
284  if ( !aText.isEmpty() )
285  aText += "\n";
286  aText += m_vEntries[ nPos ]->m_sDescription;
287 
289  DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
290  aTextHeight += aRect.GetHeight();
291 
292  if ( aTextHeight < m_nStdHeight )
293  aTextHeight = m_nStdHeight;
294 
295  m_nActiveHeight = aTextHeight;
296 
297  if ( m_vEntries[ nPos ]->m_bHasButtons )
298  m_nActiveHeight += 2;
299 }
300 
302 {
303  const ::osl::MutexGuard aGuard( m_entriesMutex );
304 
305  Size aSize( GetOutputSizePixel() );
306 
307  if ( m_vEntries[ nPos ]->m_bActive )
308  aSize.setHeight( m_nActiveHeight );
309  else
310  aSize.setHeight( m_nStdHeight );
311 
312  Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight );
313  if ( m_bHasActive && ( nPos < m_nActive ) )
314  aPos.AdjustY(m_nActiveHeight - m_nStdHeight );
315 
316  return tools::Rectangle( aPos, aSize );
317 }
318 
319 
321 {
322  const ::osl::MutexGuard aGuard( m_entriesMutex );
323 
324  m_bInDelete = true;
325 
326  m_vRemovedEntries.clear();
327 
328  m_bInDelete = false;
329 }
330 
331 
332 //This function may be called with nPos < 0
333 void ExtensionBox_Impl::selectEntry( const long nPos )
334 {
335  bool invalidate = false;
336  {
337  //ToDo we should not use the guard at such a big scope here.
338  //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be
339  //modified in this function.
340  //It would be probably best to always use a copy of m_vEntries
341  //and some other state variables from ExtensionBox_Impl for
342  //the whole painting operation. See issue i86993
343  ::osl::MutexGuard guard(m_entriesMutex);
344 
345  if ( m_bInCheckMode )
346  return;
347 
348  if ( m_bHasActive )
349  {
350  if ( nPos == m_nActive )
351  return;
352 
353  m_bHasActive = false;
354  m_vEntries[ m_nActive ]->m_bActive = false;
355  }
356 
357  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
358  {
359  m_bHasActive = true;
360  m_nActive = nPos;
361  m_vEntries[ nPos ]->m_bActive = true;
362 
363  if ( IsReallyVisible() )
364  {
365  m_bAdjustActive = true;
366  }
367  }
368 
369  if ( IsReallyVisible() )
370  {
371  m_bNeedsRecalc = true;
372  invalidate = true;
373  }
374  }
375 
376  if (invalidate)
377  {
378  SolarMutexGuard g;
379  Invalidate();
380  }
381 }
382 
383 
384 void ExtensionBox_Impl::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry)
385 {
386  const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
387 
388  if (rEntry->m_bActive)
389  rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
390  else if ((rEntry->m_eState != REGISTERED) && (rEntry->m_eState != NOT_AVAILABLE))
391  rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
392  else
393  rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
394 
395  if (rEntry->m_bActive)
396  {
397  rRenderContext.SetLineColor();
398  rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
399  rRenderContext.DrawRect(rRect);
400  }
401  else
402  {
403  rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
404  rRenderContext.SetTextFillColor();
405  rRenderContext.Erase(rRect);
406  }
407 
408  // Draw extension icon
409  Point aPos( rRect.TopLeft() );
410  aPos += Point(TOP_OFFSET, TOP_OFFSET);
411  Image aImage;
412  if (!rEntry->m_aIcon)
413  aImage = m_aDefaultImage;
414  else
415  aImage = rEntry->m_aIcon;
416  Size aImageSize = aImage.GetSizePixel();
417  if ((aImageSize.Width() <= ICON_WIDTH ) && ( aImageSize.Height() <= ICON_HEIGHT ) )
418  rRenderContext.DrawImage(Point(aPos.X() + ((ICON_WIDTH - aImageSize.Width()) / 2),
419  aPos.Y() + ((ICON_HEIGHT - aImageSize.Height()) / 2)),
420  aImage);
421  else
422  rRenderContext.DrawImage(aPos, Size(ICON_WIDTH, ICON_HEIGHT), aImage);
423 
424  // Setup fonts
425  // expand the point size of the desired font to the equivalent pixel size
426  if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice()))
427  pDefaultDevice->SetPointFont(rRenderContext, GetDrawingArea()->get_font());
428  vcl::Font aStdFont(rRenderContext.GetFont());
429  vcl::Font aBoldFont(aStdFont);
430  aBoldFont.SetWeight(WEIGHT_BOLD);
431  rRenderContext.SetFont(aBoldFont);
432  auto aTextHeight = rRenderContext.GetTextHeight();
433 
434  // Get max title width
435  auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET;
436  nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN);
437  rRenderContext.SetFont(aStdFont);
438  long nLinkWidth = 0;
439  if (!rEntry->m_sPublisher.isEmpty())
440  {
441  nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher);
442  nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN);
443  }
444  long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion);
445 
446  aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET);
447 
448  rRenderContext.SetFont(aBoldFont);
449  long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3);
450  if (aTitleWidth > nMaxTitleWidth - aVersionWidth)
451  {
452  aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3);
453  OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth);
454  rRenderContext.DrawText(aPos, aShortTitle);
455  aTitleWidth += (aTextHeight / 3);
456  }
457  else
458  rRenderContext.DrawText(aPos, rEntry->m_sTitle);
459 
460  rRenderContext.SetFont(aStdFont);
461  rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion);
462 
463  long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE;
464  long nTitleHeight = TOP_OFFSET + GetTextHeight();
465  if ( nIconHeight < nTitleHeight )
466  aTextHeight = nTitleHeight;
467  else
468  aTextHeight = nIconHeight;
469 
470  // draw description
471  OUString sDescription;
472  if (!rEntry->m_sErrorText.isEmpty())
473  {
474  if (rEntry->m_bActive)
475  sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription;
476  else
477  sDescription = rEntry->m_sErrorText;
478  }
479  else
480  sDescription = rEntry->m_sDescription;
481 
482  aPos.AdjustY(aTextHeight );
483  if (rEntry->m_bActive)
484  {
485  long nExtraHeight = 0;
486 
487  if (rEntry->m_bHasButtons)
488  nExtraHeight = 2;
489 
490  rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight),
491  sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
492  }
493  else
494  {
495  //replace LF to space, so words do not stick together in one line view
496  sDescription = sDescription.replace(0x000A, ' ');
497  const long nWidth = rRenderContext.GetTextWidth( sDescription );
498  if (nWidth > rRect.GetWidth() - aPos.X())
499  sDescription = rRenderContext.GetEllipsisString(sDescription, rRect.GetWidth() - aPos.X());
500  rRenderContext.DrawText(aPos, sDescription);
501  }
502 
503  // Draw publisher link
504  if (!rEntry->m_sPublisher.isEmpty())
505  {
506  aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET );
507 
508  rRenderContext.Push(PushFlags::FONT | PushFlags::TEXTCOLOR | PushFlags::TEXTFILLCOLOR);
509  rRenderContext.SetTextColor(rStyleSettings.GetLinkColor());
510  rRenderContext.SetTextFillColor(rStyleSettings.GetFieldColor());
511  vcl::Font aFont = rRenderContext.GetFont();
512  // to underline
514  rRenderContext.SetFont(aFont);
515  rRenderContext.DrawText(aPos, rEntry->m_sPublisher);
516  rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight));
517  rRenderContext.Pop();
518  }
519 
520  // Draw status icons
521  if (!rEntry->m_bUser)
522  {
523  aPos = rRect.TopRight() + Point( -(RIGHT_ICON_OFFSET + SMALL_ICON_SIZE), TOP_OFFSET );
524  if (rEntry->m_bLocked)
525  rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aLockedImage);
526  else
527  rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aSharedImage);
528  }
529  if ((rEntry->m_eState == AMBIGUOUS ) || rEntry->m_bMissingDeps || rEntry->m_bMissingLic)
530  {
531  aPos = rRect.TopRight() + Point(-(RIGHT_ICON_OFFSET + SPACE_BETWEEN + 2 * SMALL_ICON_SIZE), TOP_OFFSET);
532  rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aWarningImage);
533  }
534 
535  rRenderContext.SetLineColor(COL_LIGHTGRAY);
536  rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight());
537 }
538 
539 
541 {
542  if ( m_bHasActive )
544 
545  SetupScrollBar();
546 
547  if ( m_bHasActive )
548  {
549  tools::Rectangle aEntryRect = GetEntryRect( m_nActive );
550 
551  if ( m_bAdjustActive )
552  {
553  m_bAdjustActive = false;
554 
555  // If the top of the selected entry isn't visible, make it visible
556  if ( aEntryRect.Top() < 0 )
557  {
558  m_nTopIndex += aEntryRect.Top();
559  aEntryRect.Move( 0, -aEntryRect.Top() );
560  }
561 
562  // If the bottom of the selected entry isn't visible, make it visible even if now the top
563  // isn't visible any longer ( the buttons are more important )
564  Size aOutputSize = GetOutputSizePixel();
565  if ( aEntryRect.Bottom() > aOutputSize.Height() )
566  {
567  m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() );
568  aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) );
569  }
570 
571  // If there is unused space below the last entry but all entries don't fit into the box,
572  // move the content down to use the whole space
573  const long nTotalHeight = GetTotalHeight();
574  if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) )
575  {
576  long nOffset = m_nTopIndex;
577  m_nTopIndex = nTotalHeight - aOutputSize.Height();
578  nOffset -= m_nTopIndex;
579  aEntryRect.Move( 0, nOffset );
580  }
581 
582  if ( m_bHasScrollBar )
583  m_xScrollBar->vadjustment_set_value( m_nTopIndex );
584  }
585  }
586 
587  m_bNeedsRecalc = false;
588 }
589 
590 
591 bool ExtensionBox_Impl::HandleCursorKey( sal_uInt16 nKeyCode )
592 {
593  if ( m_vEntries.empty() )
594  return true;
595 
596  long nSelect = 0;
597 
598  if ( m_bHasActive )
599  {
600  long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight;
601  if ( nPageSize < 2 )
602  nPageSize = 2;
603 
604  if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) )
605  nSelect = m_nActive + 1;
606  else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) )
607  nSelect = m_nActive - 1;
608  else if ( nKeyCode == KEY_HOME )
609  nSelect = 0;
610  else if ( nKeyCode == KEY_END )
611  nSelect = m_vEntries.size() - 1;
612  else if ( nKeyCode == KEY_PAGEUP )
613  nSelect = m_nActive - nPageSize + 1;
614  else if ( nKeyCode == KEY_PAGEDOWN )
615  nSelect = m_nActive + nPageSize - 1;
616  }
617  else // when there is no selected entry, we will select the first or the last.
618  {
619  if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) )
620  nSelect = 0;
621  else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) )
622  nSelect = m_vEntries.size() - 1;
623  }
624 
625  if ( nSelect < 0 )
626  nSelect = 0;
627  if ( nSelect >= static_cast<long>(m_vEntries.size()) )
628  nSelect = m_vEntries.size() - 1;
629 
630  selectEntry( nSelect );
631 
632  return true;
633 }
634 
635 
636 void ExtensionBox_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/)
637 {
638  if ( !m_bInDelete )
639  DeleteRemoved();
640 
641  if ( m_bNeedsRecalc )
642  RecalcAll();
643 
644  Point aStart( 0, -m_nTopIndex );
645  Size aSize(GetOutputSizePixel());
646 
647  const ::osl::MutexGuard aGuard( m_entriesMutex );
648 
649  for (auto const& entry : m_vEntries)
650  {
651  aSize.setHeight( entry->m_bActive ? m_nActiveHeight : m_nStdHeight );
652  tools::Rectangle aEntryRect( aStart, aSize );
653  DrawRow(rRenderContext, aEntryRect, entry);
654  aStart.AdjustY(aSize.Height() );
655  }
656 }
657 
658 
660 {
661  long nHeight = m_vEntries.size() * m_nStdHeight;
662 
663  if ( m_bHasActive )
664  {
665  nHeight += m_nActiveHeight - m_nStdHeight;
666  }
667 
668  return nHeight;
669 }
670 
671 
673 {
674  const Size aSize = GetOutputSizePixel();
675  const auto nTotalHeight = GetTotalHeight();
676  const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() );
677 
678  if ( bNeedsScrollBar )
679  {
680  if ( m_nTopIndex + aSize.Height() > nTotalHeight )
681  m_nTopIndex = nTotalHeight - aSize.Height();
682 
683  m_xScrollBar->vadjustment_configure(m_nTopIndex, 0, nTotalHeight,
684  m_nStdHeight, ( aSize.Height() * 4 ) / 5,
685  aSize.Height());
686 
687  if (!m_bHasScrollBar)
688  m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS);
689  }
690  else if ( m_bHasScrollBar )
691  {
692  m_xScrollBar->set_vpolicy(VclPolicyType::NEVER);
693  m_nTopIndex = 0;
694  }
695 
696  m_bHasScrollBar = bNeedsScrollBar;
697 }
698 
699 
701 {
702  RecalcAll();
703  Invalidate();
704 }
705 
707 {
708  Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(250, 150), MapMode(MapUnit::MapAppFont));
709  pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
710  CustomWidgetController::SetDrawingArea(pDrawingArea);
711  SetOutputSizePixel(aSize);
712 
713  Init();
714 }
715 
716 long ExtensionBox_Impl::PointToPos( const Point& rPos )
717 {
718  long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight;
719 
720  if ( m_bHasActive && ( nPos > m_nActive ) )
721  {
722  if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight )
723  nPos = m_nActive;
724  else
725  nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight;
726  }
727 
728  return nPos;
729 }
730 
732 {
733  bool bOverHyperlink = false;
734 
735  auto nPos = PointToPos( rMEvt.GetPosPixel() );
736  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
737  {
738  const auto& rEntry = m_vEntries[nPos];
739  bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rMEvt.GetPosPixel());
740  }
741 
742  if (bOverHyperlink)
743  SetPointer(PointerStyle::RefHand);
744  else
745  SetPointer(PointerStyle::Arrow);
746 
747  return false;
748 }
749 
751 {
752  auto nPos = PointToPos( rRect.TopLeft() );
753  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
754  {
755  const auto& rEntry = m_vEntries[nPos];
756  bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rRect);
757  if (bOverHyperlink)
758  {
759  rRect = rEntry->m_aLinkRect;
760  return rEntry->m_sPublisherURL;
761  }
762  }
763 
764  return OUString();
765 }
766 
768 {
769  if ( rMEvt.IsLeft() )
770  {
771  if (rMEvt.IsMod1() && m_bHasActive)
772  selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND); // Selecting a not existing entry will deselect the current one
773  else
774  {
775  auto nPos = PointToPos( rMEvt.GetPosPixel() );
776 
777  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
778  {
779  const auto& rEntry = m_vEntries[nPos];
780  if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rMEvt.GetPosPixel()))
781  {
782  try
783  {
784  css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute(
786  //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
787  xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY);
788  }
789  catch (...)
790  {
791  }
792  return true;
793  }
794  }
795 
796  selectEntry( nPos );
797  }
798  return true;
799  }
800 
801  return false;
802 }
803 
805 {
806  if ( !m_bInDelete )
807  DeleteRemoved();
808 
809  vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
810  sal_uInt16 nKeyCode = aKeyCode.GetCode();
811 
812  bool bHandled = false;
813  if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR)
814  bHandled = HandleCursorKey(nKeyCode);
815 
816  return bHandled;
817 }
818 
819 bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const long nStart,
820  const long nEnd, long &nPos )
821 {
822  nPos = nStart;
823  if ( nStart > nEnd )
824  return false;
825 
826  sal_Int32 eCompare;
827 
828  if ( nStart == nEnd )
829  {
830  eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nStart ] );
831  if ( eCompare < 0 )
832  return false;
833  else if ( eCompare == 0 )
834  {
835  //Workaround. See i86963.
836  if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage)
837  return false;
838 
839  if ( m_bInCheckMode )
840  m_vEntries[ nStart ]->m_bChecked = true;
841  return true;
842  }
843  else
844  {
845  nPos = nStart + 1;
846  return false;
847  }
848  }
849 
850  const long nMid = nStart + ( ( nEnd - nStart ) / 2 );
851  eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nMid ] );
852 
853  if ( eCompare < 0 )
854  return FindEntryPos( rEntry, nStart, nMid-1, nPos );
855  else if ( eCompare > 0 )
856  return FindEntryPos( rEntry, nMid+1, nEnd, nPos );
857  else
858  {
859  //Workaround.See i86963.
860  if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage)
861  return false;
862 
863  if ( m_bInCheckMode )
864  m_vEntries[ nMid ]->m_bChecked = true;
865  nPos = nMid;
866  return true;
867  }
868 }
869 
871 {
872  m_vListenerAdded.erase(std::remove_if(m_vListenerAdded.begin(), m_vListenerAdded.end(),
873  [](const uno::WeakReference<deployment::XPackage>& rxListener) {
874  const uno::Reference<deployment::XPackage> hardRef(rxListener);
875  return !hardRef.is();
876  }),
877  m_vListenerAdded.end());
878 }
879 
881  uno::Reference<deployment::XPackage > const & extension)
882 {
883  //make sure to only add the listener once
885  if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(),
886  FindWeakRef(extension)) )
887  {
888  extension->addEventListener( m_xRemoveListener.get() );
889  m_vListenerAdded.emplace_back(extension);
890  }
891 }
892 
893 
894 void ExtensionBox_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage,
895  bool bLicenseMissing )
896 {
897  long nPos = 0;
899  bool bLocked = m_pManager->isReadOnly( xPackage );
900 
901  TEntry_Impl pEntry = std::make_shared<Entry_Impl>( xPackage, eState, bLocked );
902 
903  // Don't add empty entries
904  if ( pEntry->m_sTitle.isEmpty() )
905  return;
906 
907  {
908  osl::MutexGuard guard(m_entriesMutex);
909  if (m_vEntries.empty())
910  {
911  addEventListenerOnce(xPackage);
912  m_vEntries.push_back(pEntry);
913  }
914  else
915  {
916  if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos))
917  {
918  addEventListenerOnce(xPackage);
919  m_vEntries.insert(m_vEntries.begin() + nPos, pEntry);
920  }
921  else if (!m_bInCheckMode)
922  {
923  OSL_FAIL("ExtensionBox_Impl::addEntry(): Will not add duplicate entries");
924  }
925  }
926 
927  pEntry->m_bHasOptions = m_pManager->supportsOptions(xPackage);
928  pEntry->m_bUser = (xPackage->getRepositoryName() == USER_PACKAGE_MANAGER);
929  pEntry->m_bShared = (xPackage->getRepositoryName() == SHARED_PACKAGE_MANAGER);
930  pEntry->m_bNew = m_bInCheckMode;
931  pEntry->m_bMissingLic = bLicenseMissing;
932 
933  if (bLicenseMissing)
934  pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE);
935 
936  //access to m_nActive must be guarded
937  if (!m_bInCheckMode && m_bHasActive && (m_nActive >= nPos))
938  m_nActive += 1;
939  }
940 
941  if ( IsReallyVisible() )
942  Invalidate();
943 
944  m_bNeedsRecalc = true;
945 }
946 
947 void ExtensionBox_Impl::updateEntry( const uno::Reference< deployment::XPackage > &xPackage )
948 {
949  for (auto const& entry : m_vEntries)
950  {
951  if ( entry->m_xPackage == xPackage )
952  {
954  entry->m_bHasOptions = m_pManager->supportsOptions( xPackage );
955  entry->m_eState = eState;
956  entry->m_sTitle = xPackage->getDisplayName();
957  entry->m_sVersion = xPackage->getVersion();
958  entry->m_sDescription = xPackage->getDescription();
959 
960  if ( eState == REGISTERED )
961  entry->m_bMissingLic = false;
962 
963  if ( eState == AMBIGUOUS )
964  entry->m_sErrorText = DpResId( RID_STR_ERROR_UNKNOWN_STATUS );
965  else if ( ! entry->m_bMissingLic )
966  entry->m_sErrorText.clear();
967 
968  if ( IsReallyVisible() )
969  Invalidate();
970  break;
971  }
972  }
973 }
974 
975 //This function is also called as a result of removing an extension.
976 //see PackageManagerImpl::removePackage
977 //The gui is a registered as listener on the package. Removing it will cause the
978 //listeners to be notified and then this function is called. At this moment xPackage
979 //is in the disposing state and all calls on it may result in a DisposedException.
980 void ExtensionBox_Impl::removeEntry( const uno::Reference< deployment::XPackage > &xPackage )
981 {
982  if ( m_bInDelete )
983  return;
984 
985  bool invalidate = false;
986  {
987  ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
988 
989  auto iIndex = std::find_if(m_vEntries.begin(), m_vEntries.end(),
990  [&xPackage](const TEntry_Impl& rxEntry) { return rxEntry->m_xPackage == xPackage; });
991  if (iIndex != m_vEntries.end())
992  {
993  long nPos = iIndex - m_vEntries.begin();
994 
995  // Entries mustn't be removed here, because they contain a hyperlink control
996  // which can only be deleted when the thread has the solar mutex. Therefore
997  // the entry will be moved into the m_vRemovedEntries list which will be
998  // cleared on the next paint event
999  m_vRemovedEntries.push_back( *iIndex );
1000  (*iIndex)->m_xPackage->removeEventListener(m_xRemoveListener.get());
1001  m_vEntries.erase( iIndex );
1002 
1003  m_bNeedsRecalc = true;
1004 
1005  if ( IsReallyVisible() )
1006  invalidate = true;
1007 
1008  if ( m_bHasActive )
1009  {
1010  if ( nPos < m_nActive )
1011  m_nActive -= 1;
1012  else if ( ( nPos == m_nActive ) &&
1013  ( nPos == static_cast<long>(m_vEntries.size()) ) )
1014  m_nActive -= 1;
1015 
1016  m_bHasActive = false;
1017  //clear before calling out of this method
1018  aGuard.clear();
1020  }
1021  }
1022  }
1023 
1024  if (invalidate)
1025  {
1026  SolarMutexGuard g;
1027  Invalidate();
1028  }
1029 }
1030 
1031 
1033 {
1034  bool bAllRemoved = false;
1035 
1036  while ( ! bAllRemoved )
1037  {
1038  bAllRemoved = true;
1039 
1040  ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
1041 
1042  for (auto const& entry : m_vEntries)
1043  {
1044  if ( !entry->m_bLocked )
1045  {
1046  bAllRemoved = false;
1047  uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage;
1048  aGuard.clear();
1049  removeEntry( xPackage );
1050  break;
1051  }
1052  }
1053  }
1054 }
1055 
1056 
1058 {
1059  m_bInCheckMode = true;
1060  for (auto const& entry : m_vEntries)
1061  {
1062  entry->m_bChecked = false;
1063  entry->m_bNew = false;
1064  }
1065 }
1066 
1067 
1069 {
1070  long nNewPos = -1;
1071  long nChangedActivePos = -1;
1072  long nPos = 0;
1073  bool bNeedsUpdate = false;
1074 
1075  {
1076  osl::MutexGuard guard(m_entriesMutex);
1077  auto iIndex = m_vEntries.begin();
1078  while (iIndex != m_vEntries.end())
1079  {
1080  if (!(*iIndex)->m_bChecked)
1081  {
1082  (*iIndex)->m_bChecked = true;
1083  bNeedsUpdate = true;
1084  nPos = iIndex - m_vEntries.begin();
1085  if ((*iIndex)->m_bNew)
1086  { // add entry to list and correct active pos
1087  if (nNewPos == -1)
1088  nNewPos = nPos;
1089  if (nPos <= m_nActive)
1090  m_nActive += 1;
1091  ++iIndex;
1092  }
1093  else
1094  { // remove entry from list
1095  if (nPos < nNewPos)
1096  {
1097  --nNewPos;
1098  }
1099  if (nPos < nChangedActivePos)
1100  {
1101  --nChangedActivePos;
1102  }
1103  if (nPos < m_nActive)
1104  m_nActive -= 1;
1105  else if (nPos == m_nActive)
1106  {
1107  nChangedActivePos = nPos;
1108  m_nActive = -1;
1109  m_bHasActive = false;
1110  }
1111  m_vRemovedEntries.push_back(*iIndex);
1112  iIndex = m_vEntries.erase(iIndex);
1113  }
1114  }
1115  else
1116  ++iIndex;
1117  }
1118  }
1119 
1120  m_bInCheckMode = false;
1121 
1122  if ( nNewPos != - 1)
1123  selectEntry( nNewPos );
1124  else if (nChangedActivePos != -1) {
1125  selectEntry(nChangedActivePos);
1126  }
1127 
1128  if ( bNeedsUpdate )
1129  {
1130  m_bNeedsRecalc = true;
1131  if ( IsReallyVisible() )
1132  Invalidate();
1133  }
1134 }
1135 
1137 {
1138  m_nTopIndex = rScrBar.vadjustment_get_value();
1139  Invalidate();
1140 }
1141 
1142 } //namespace dp_gui
1143 
1144 /* 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
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)
const Color & GetHighlightTextColor() const
long Height() const
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)
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
constexpr sal_uInt16 KEY_UP
#define RIGHT_ICON_OFFSET
const Color & GetHighlightColor() const
Size const & GetOutputSizePixel() const
void Move(long nHorzMoveDelta, long nVertMoveDelta)
constexpr sal_uInt16 KEY_END
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
constexpr sal_uInt16 KEY_PAGEUP
#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
constexpr sal_uInt16 KEYGROUP_CURSOR
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
constexpr sal_uInt16 KEY_DOWN
Point BottomRight() const
void SetLineColor()
const Color & GetDisableColor() const
rtl::Reference< ExtensionRemovedListener > m_xRemoveListener
int i
virtual void SAL_CALL disposing(css::lang::EventObject const &evt) override
constexpr sal_uInt16 KEY_HOME
TheExtensionManager * m_pManager
bool isReadOnly(const css::uno::Reference< css::deployment::XPackage > &xPackage) const
void SetFillColor()
const Color & GetFieldColor() const
LINESTYLE_SINGLE
void SetTextColor(const Color &rColor)
long Bottom() const
#define SHARED_PACKAGE_MANAGER
constexpr sal_uInt16 KEY_PAGEDOWN
#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
sal_Int32 getSelIndex() const
constexpr sal_uInt16 KEY_RIGHT
Point LogicToPixel(const Point &rLogicPt) const
const LanguageTag & getLocale()
StockImage
const vcl::KeyCode & GetKeyCode() const
#define USER_PACKAGE_MANAGER
weld::DrawingArea * GetDrawingArea() const
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:25
uno::Reference< deployment::XPackage > m_xPackage
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
OUString DpResId(const char *pId)
Definition: dp_shared.hxx:37
constexpr sal_uInt16 KEY_LEFT
std::vector< css::uno::WeakReference< css::deployment::XPackage > > m_vListenerAdded
Definition: dp_gui.h:23
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)
Reference< XGraphic > xGraphic
Point TopRight() const
void removeEntry(const css::uno::Reference< css::deployment::XPackage > &xPackage)
sal_uInt16 nPos
constexpr sal_uInt16 KEY_TAB
std::shared_ptr< Entry_Impl > TEntry_Impl
bool IsMod1() const
IMPL_LINK(ExtMgrDialog, startProgress, void *, _bLockInterface, void)
void setHeight(long nHeight)