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->connect_vadjustment_changed( LINK( this, ExtensionBox_Impl, ScrollHdl ) );
202 
203  auto nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
204  auto nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
205  if ( nIconHeight < nTitleHeight )
206  m_nStdHeight = nTitleHeight;
207  else
208  m_nStdHeight = nIconHeight;
210 
211  nIconHeight = ICON_HEIGHT + 2*TOP_OFFSET + 1;
212  if ( m_nStdHeight < nIconHeight )
213  m_nStdHeight = nIconHeight;
214 
216 
218 
219  m_pLocale.reset( new lang::Locale( Application::GetSettings().GetLanguageTag().getLocale() ) );
221  m_pCollator->loadDefaultCollator( *m_pLocale, i18n::CollatorOptions::CollatorOptions_IGNORE_CASE );
222 }
223 
225 {
226  if ( ! m_bInDelete )
227  DeleteRemoved();
228 
229  m_bInDelete = true;
230 
231  for (auto const& entry : m_vEntries)
232  {
233  entry->m_xPackage->removeEventListener( m_xRemoveListener.get() );
234  }
235 
236  m_vEntries.clear();
237 
238  m_xRemoveListener.clear();
239 
240  m_pLocale.reset();
241  m_pCollator.reset();
242 }
243 
245 {
246  return static_cast< sal_Int32 >( m_vEntries.size() );
247 }
248 
249 
251 {
252  if ( m_bHasActive )
253  {
254  OSL_ASSERT( m_nActive >= -1);
255  return static_cast< sal_Int32 >( m_nActive );
256  }
257  else
258  return ENTRY_NOTFOUND;
259 }
260 
261 
262 // Title + description
263 void ExtensionBox_Impl::CalcActiveHeight( const long nPos )
264 {
265  const ::osl::MutexGuard aGuard( m_entriesMutex );
266 
267  // get title height
268  long aTextHeight;
269  long nIconHeight = 2*TOP_OFFSET + SMALL_ICON_SIZE;
270  long nTitleHeight = 2*TOP_OFFSET + GetTextHeight();
271  if ( nIconHeight < nTitleHeight )
272  aTextHeight = nTitleHeight;
273  else
274  aTextHeight = nIconHeight;
275 
276  // calc description height
277  Size aSize = GetOutputSizePixel();
278 
279  aSize.AdjustWidth( -(ICON_OFFSET) );
280  aSize.setHeight( 10000 );
281 
282  OUString aText( m_vEntries[ nPos ]->m_sErrorText );
283  if ( !aText.isEmpty() )
284  aText += "\n";
285  aText += m_vEntries[ nPos ]->m_sDescription;
286 
288  DrawTextFlags::MultiLine | DrawTextFlags::WordBreak);
289  aTextHeight += aRect.GetHeight();
290 
291  if ( aTextHeight < m_nStdHeight )
292  aTextHeight = m_nStdHeight;
293 
294  m_nActiveHeight = aTextHeight;
295 
296  if ( m_vEntries[ nPos ]->m_bHasButtons )
297  m_nActiveHeight += 2;
298 }
299 
301 {
302  const ::osl::MutexGuard aGuard( m_entriesMutex );
303 
304  Size aSize( GetOutputSizePixel() );
305 
306  if ( m_vEntries[ nPos ]->m_bActive )
307  aSize.setHeight( m_nActiveHeight );
308  else
309  aSize.setHeight( m_nStdHeight );
310 
311  Point aPos( 0, -m_nTopIndex + nPos * m_nStdHeight );
312  if ( m_bHasActive && ( nPos < m_nActive ) )
313  aPos.AdjustY(m_nActiveHeight - m_nStdHeight );
314 
315  return tools::Rectangle( aPos, aSize );
316 }
317 
318 
320 {
321  const ::osl::MutexGuard aGuard( m_entriesMutex );
322 
323  m_bInDelete = true;
324 
325  m_vRemovedEntries.clear();
326 
327  m_bInDelete = false;
328 }
329 
330 
331 //This function may be called with nPos < 0
332 void ExtensionBox_Impl::selectEntry( const long nPos )
333 {
334  bool invalidate = false;
335  {
336  //ToDo we should not use the guard at such a big scope here.
337  //Currently it is used to guard m_vEntries and m_nActive. m_nActive will be
338  //modified in this function.
339  //It would be probably best to always use a copy of m_vEntries
340  //and some other state variables from ExtensionBox_Impl for
341  //the whole painting operation. See issue i86993
342  ::osl::MutexGuard guard(m_entriesMutex);
343 
344  if ( m_bInCheckMode )
345  return;
346 
347  if ( m_bHasActive )
348  {
349  if ( nPos == m_nActive )
350  return;
351 
352  m_bHasActive = false;
353  m_vEntries[ m_nActive ]->m_bActive = false;
354  }
355 
356  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
357  {
358  m_bHasActive = true;
359  m_nActive = nPos;
360  m_vEntries[ nPos ]->m_bActive = true;
361 
362  if ( IsReallyVisible() )
363  {
364  m_bAdjustActive = true;
365  }
366  }
367 
368  if ( IsReallyVisible() )
369  {
370  m_bNeedsRecalc = true;
371  invalidate = true;
372  }
373  }
374 
375  if (invalidate)
376  {
377  SolarMutexGuard g;
378  Invalidate();
379  }
380 }
381 
382 
383 void ExtensionBox_Impl::DrawRow(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect, const TEntry_Impl& rEntry)
384 {
385  const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
386 
387  if (rEntry->m_bActive)
388  rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
389  else if ((rEntry->m_eState != REGISTERED) && (rEntry->m_eState != NOT_AVAILABLE))
390  rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
391  else
392  rRenderContext.SetTextColor(rStyleSettings.GetFieldTextColor());
393 
394  if (rEntry->m_bActive)
395  {
396  rRenderContext.SetLineColor();
397  rRenderContext.SetFillColor(rStyleSettings.GetHighlightColor());
398  rRenderContext.DrawRect(rRect);
399  }
400  else
401  {
402  rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
403  rRenderContext.SetTextFillColor();
404  rRenderContext.Erase(rRect);
405  }
406 
407  // Draw extension icon
408  Point aPos( rRect.TopLeft() );
409  aPos += Point(TOP_OFFSET, TOP_OFFSET);
410  Image aImage;
411  if (!rEntry->m_aIcon)
412  aImage = m_aDefaultImage;
413  else
414  aImage = rEntry->m_aIcon;
415  Size aImageSize = aImage.GetSizePixel();
416  if ((aImageSize.Width() <= ICON_WIDTH ) && ( aImageSize.Height() <= ICON_HEIGHT ) )
417  rRenderContext.DrawImage(Point(aPos.X() + ((ICON_WIDTH - aImageSize.Width()) / 2),
418  aPos.Y() + ((ICON_HEIGHT - aImageSize.Height()) / 2)),
419  aImage);
420  else
421  rRenderContext.DrawImage(aPos, Size(ICON_WIDTH, ICON_HEIGHT), aImage);
422 
423  // Setup fonts
424  // expand the point size of the desired font to the equivalent pixel size
425  if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice()))
426  pDefaultDevice->SetPointFont(rRenderContext, GetDrawingArea()->get_font());
427  vcl::Font aStdFont(rRenderContext.GetFont());
428  vcl::Font aBoldFont(aStdFont);
429  aBoldFont.SetWeight(WEIGHT_BOLD);
430  rRenderContext.SetFont(aBoldFont);
431  auto aTextHeight = rRenderContext.GetTextHeight();
432 
433  // Get max title width
434  auto nMaxTitleWidth = rRect.GetWidth() - ICON_OFFSET;
435  nMaxTitleWidth -= (2 * SMALL_ICON_SIZE) + (4 * SPACE_BETWEEN);
436  rRenderContext.SetFont(aStdFont);
437  long nLinkWidth = 0;
438  if (!rEntry->m_sPublisher.isEmpty())
439  {
440  nLinkWidth = rRenderContext.GetTextWidth(rEntry->m_sPublisher);
441  nMaxTitleWidth -= nLinkWidth + (2 * SPACE_BETWEEN);
442  }
443  long aVersionWidth = rRenderContext.GetTextWidth(rEntry->m_sVersion);
444 
445  aPos = rRect.TopLeft() + Point(ICON_OFFSET, TOP_OFFSET);
446 
447  rRenderContext.SetFont(aBoldFont);
448  long aTitleWidth = rRenderContext.GetTextWidth(rEntry->m_sTitle) + (aTextHeight / 3);
449  if (aTitleWidth > nMaxTitleWidth - aVersionWidth)
450  {
451  aTitleWidth = nMaxTitleWidth - aVersionWidth - (aTextHeight / 3);
452  OUString aShortTitle = rRenderContext.GetEllipsisString(rEntry->m_sTitle, aTitleWidth);
453  rRenderContext.DrawText(aPos, aShortTitle);
454  aTitleWidth += (aTextHeight / 3);
455  }
456  else
457  rRenderContext.DrawText(aPos, rEntry->m_sTitle);
458 
459  rRenderContext.SetFont(aStdFont);
460  rRenderContext.DrawText(Point(aPos.X() + aTitleWidth, aPos.Y()), rEntry->m_sVersion);
461 
462  long nIconHeight = TOP_OFFSET + SMALL_ICON_SIZE;
463  long nTitleHeight = TOP_OFFSET + GetTextHeight();
464  if ( nIconHeight < nTitleHeight )
465  aTextHeight = nTitleHeight;
466  else
467  aTextHeight = nIconHeight;
468 
469  // draw description
470  OUString sDescription;
471  if (!rEntry->m_sErrorText.isEmpty())
472  {
473  if (rEntry->m_bActive)
474  sDescription = rEntry->m_sErrorText + "\n" + rEntry->m_sDescription;
475  else
476  sDescription = rEntry->m_sErrorText;
477  }
478  else
479  sDescription = rEntry->m_sDescription;
480 
481  aPos.AdjustY(aTextHeight );
482  if (rEntry->m_bActive)
483  {
484  long nExtraHeight = 0;
485 
486  if (rEntry->m_bHasButtons)
487  nExtraHeight = 2;
488 
489  rRenderContext.DrawText(tools::Rectangle(aPos.X(), aPos.Y(), rRect.Right(), rRect.Bottom() - nExtraHeight),
490  sDescription, DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
491  }
492  else
493  {
494  //replace LF to space, so words do not stick together in one line view
495  sDescription = sDescription.replace(0x000A, ' ');
496  const long nWidth = rRenderContext.GetTextWidth( sDescription );
497  if (nWidth > rRect.GetWidth() - aPos.X())
498  sDescription = rRenderContext.GetEllipsisString(sDescription, rRect.GetWidth() - aPos.X());
499  rRenderContext.DrawText(aPos, sDescription);
500  }
501 
502  // Draw publisher link
503  if (!rEntry->m_sPublisher.isEmpty())
504  {
505  aPos = rRect.TopLeft() + Point( ICON_OFFSET + nMaxTitleWidth + (2*SPACE_BETWEEN), TOP_OFFSET );
506 
507  rRenderContext.Push(PushFlags::FONT | PushFlags::TEXTCOLOR | PushFlags::TEXTFILLCOLOR);
508  rRenderContext.SetTextColor(rStyleSettings.GetLinkColor());
509  rRenderContext.SetTextFillColor(rStyleSettings.GetFieldColor());
510  vcl::Font aFont = rRenderContext.GetFont();
511  // to underline
513  rRenderContext.SetFont(aFont);
514  rRenderContext.DrawText(aPos, rEntry->m_sPublisher);
515  rEntry->m_aLinkRect = tools::Rectangle(aPos, Size(nLinkWidth, aTextHeight));
516  rRenderContext.Pop();
517  }
518 
519  // Draw status icons
520  if (!rEntry->m_bUser)
521  {
522  aPos = rRect.TopRight() + Point( -(RIGHT_ICON_OFFSET + SMALL_ICON_SIZE), TOP_OFFSET );
523  if (rEntry->m_bLocked)
524  rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aLockedImage);
525  else
526  rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aSharedImage);
527  }
528  if ((rEntry->m_eState == AMBIGUOUS ) || rEntry->m_bMissingDeps || rEntry->m_bMissingLic)
529  {
530  aPos = rRect.TopRight() + Point(-(RIGHT_ICON_OFFSET + SPACE_BETWEEN + 2 * SMALL_ICON_SIZE), TOP_OFFSET);
531  rRenderContext.DrawImage(aPos, Size(SMALL_ICON_SIZE, SMALL_ICON_SIZE), m_aWarningImage);
532  }
533 
534  rRenderContext.SetLineColor(COL_LIGHTGRAY);
535  rRenderContext.DrawLine(rRect.BottomLeft(), rRect.BottomRight());
536 }
537 
538 
540 {
541  if ( m_bHasActive )
543 
544  SetupScrollBar();
545 
546  if ( m_bHasActive )
547  {
548  tools::Rectangle aEntryRect = GetEntryRect( m_nActive );
549 
550  if ( m_bAdjustActive )
551  {
552  m_bAdjustActive = false;
553 
554  // If the top of the selected entry isn't visible, make it visible
555  if ( aEntryRect.Top() < 0 )
556  {
557  m_nTopIndex += aEntryRect.Top();
558  aEntryRect.Move( 0, -aEntryRect.Top() );
559  }
560 
561  // If the bottom of the selected entry isn't visible, make it visible even if now the top
562  // isn't visible any longer ( the buttons are more important )
563  Size aOutputSize = GetOutputSizePixel();
564  if ( aEntryRect.Bottom() > aOutputSize.Height() )
565  {
566  m_nTopIndex += ( aEntryRect.Bottom() - aOutputSize.Height() );
567  aEntryRect.Move( 0, -( aEntryRect.Bottom() - aOutputSize.Height() ) );
568  }
569 
570  // If there is unused space below the last entry but all entries don't fit into the box,
571  // move the content down to use the whole space
572  const long nTotalHeight = GetTotalHeight();
573  if ( m_bHasScrollBar && ( aOutputSize.Height() + m_nTopIndex > nTotalHeight ) )
574  {
575  long nOffset = m_nTopIndex;
576  m_nTopIndex = nTotalHeight - aOutputSize.Height();
577  nOffset -= m_nTopIndex;
578  aEntryRect.Move( 0, nOffset );
579  }
580 
581  if ( m_bHasScrollBar )
582  m_xScrollBar->vadjustment_set_value( m_nTopIndex );
583  }
584  }
585 
586  m_bNeedsRecalc = false;
587 }
588 
589 
590 bool ExtensionBox_Impl::HandleCursorKey( sal_uInt16 nKeyCode )
591 {
592  if ( m_vEntries.empty() )
593  return true;
594 
595  long nSelect = 0;
596 
597  if ( m_bHasActive )
598  {
599  long nPageSize = GetOutputSizePixel().Height() / m_nStdHeight;
600  if ( nPageSize < 2 )
601  nPageSize = 2;
602 
603  if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_RIGHT ) )
604  nSelect = m_nActive + 1;
605  else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_LEFT ) )
606  nSelect = m_nActive - 1;
607  else if ( nKeyCode == KEY_HOME )
608  nSelect = 0;
609  else if ( nKeyCode == KEY_END )
610  nSelect = m_vEntries.size() - 1;
611  else if ( nKeyCode == KEY_PAGEUP )
612  nSelect = m_nActive - nPageSize + 1;
613  else if ( nKeyCode == KEY_PAGEDOWN )
614  nSelect = m_nActive + nPageSize - 1;
615  }
616  else // when there is no selected entry, we will select the first or the last.
617  {
618  if ( ( nKeyCode == KEY_DOWN ) || ( nKeyCode == KEY_PAGEDOWN ) || ( nKeyCode == KEY_HOME ) )
619  nSelect = 0;
620  else if ( ( nKeyCode == KEY_UP ) || ( nKeyCode == KEY_PAGEUP ) || ( nKeyCode == KEY_END ) )
621  nSelect = m_vEntries.size() - 1;
622  }
623 
624  if ( nSelect < 0 )
625  nSelect = 0;
626  if ( nSelect >= static_cast<long>(m_vEntries.size()) )
627  nSelect = m_vEntries.size() - 1;
628 
629  selectEntry( nSelect );
630 
631  return true;
632 }
633 
634 
635 void ExtensionBox_Impl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rPaintRect*/)
636 {
637  if ( !m_bInDelete )
638  DeleteRemoved();
639 
640  if ( m_bNeedsRecalc )
641  RecalcAll();
642 
643  Point aStart( 0, -m_nTopIndex );
644  Size aSize(GetOutputSizePixel());
645 
646  const ::osl::MutexGuard aGuard( m_entriesMutex );
647 
648  for (auto const& entry : m_vEntries)
649  {
650  aSize.setHeight( entry->m_bActive ? m_nActiveHeight : m_nStdHeight );
651  tools::Rectangle aEntryRect( aStart, aSize );
652  DrawRow(rRenderContext, aEntryRect, entry);
653  aStart.AdjustY(aSize.Height() );
654  }
655 }
656 
657 
659 {
660  long nHeight = m_vEntries.size() * m_nStdHeight;
661 
662  if ( m_bHasActive )
663  {
664  nHeight += m_nActiveHeight - m_nStdHeight;
665  }
666 
667  return nHeight;
668 }
669 
670 
672 {
673  const Size aSize = GetOutputSizePixel();
674  const auto nTotalHeight = GetTotalHeight();
675  const bool bNeedsScrollBar = ( nTotalHeight > aSize.Height() );
676 
677  if ( bNeedsScrollBar )
678  {
679  if ( m_nTopIndex + aSize.Height() > nTotalHeight )
680  m_nTopIndex = nTotalHeight - aSize.Height();
681 
682  m_xScrollBar->vadjustment_configure(m_nTopIndex, 0, nTotalHeight,
683  m_nStdHeight, ( aSize.Height() * 4 ) / 5,
684  aSize.Height());
685 
686  if (!m_bHasScrollBar)
687  m_xScrollBar->set_vpolicy(VclPolicyType::ALWAYS);
688  }
689  else if ( m_bHasScrollBar )
690  {
691  m_xScrollBar->set_vpolicy(VclPolicyType::NEVER);
692  m_nTopIndex = 0;
693  }
694 
695  m_bHasScrollBar = bNeedsScrollBar;
696 }
697 
698 
700 {
701  RecalcAll();
702  Invalidate();
703 }
704 
706 {
707  Size aSize = pDrawingArea->get_ref_device().LogicToPixel(Size(250, 150), MapMode(MapUnit::MapAppFont));
708  pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
709  CustomWidgetController::SetDrawingArea(pDrawingArea);
710  SetOutputSizePixel(aSize);
711 
712  Init();
713 }
714 
715 long ExtensionBox_Impl::PointToPos( const Point& rPos )
716 {
717  long nPos = ( rPos.Y() + m_nTopIndex ) / m_nStdHeight;
718 
719  if ( m_bHasActive && ( nPos > m_nActive ) )
720  {
721  if ( rPos.Y() + m_nTopIndex <= m_nActive*m_nStdHeight + m_nActiveHeight )
722  nPos = m_nActive;
723  else
724  nPos = ( rPos.Y() + m_nTopIndex - (m_nActiveHeight - m_nStdHeight) ) / m_nStdHeight;
725  }
726 
727  return nPos;
728 }
729 
731 {
732  bool bOverHyperlink = false;
733 
734  auto nPos = PointToPos( rMEvt.GetPosPixel() );
735  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
736  {
737  const auto& rEntry = m_vEntries[nPos];
738  bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rMEvt.GetPosPixel());
739  }
740 
741  if (bOverHyperlink)
742  SetPointer(PointerStyle::RefHand);
743  else
744  SetPointer(PointerStyle::Arrow);
745 
746  return false;
747 }
748 
750 {
751  auto nPos = PointToPos( rRect.TopLeft() );
752  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
753  {
754  const auto& rEntry = m_vEntries[nPos];
755  bool bOverHyperlink = !rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rRect);
756  if (bOverHyperlink)
757  {
758  rRect = rEntry->m_aLinkRect;
759  return rEntry->m_sPublisherURL;
760  }
761  }
762 
763  return OUString();
764 }
765 
767 {
768  if ( rMEvt.IsLeft() )
769  {
770  if (rMEvt.IsMod1() && m_bHasActive)
771  selectEntry(ExtensionBox_Impl::ENTRY_NOTFOUND); // Selecting a not existing entry will deselect the current one
772  else
773  {
774  auto nPos = PointToPos( rMEvt.GetPosPixel() );
775 
776  if ( ( nPos >= 0 ) && ( nPos < static_cast<long>(m_vEntries.size()) ) )
777  {
778  const auto& rEntry = m_vEntries[nPos];
779  if (!rEntry->m_sPublisher.isEmpty() && rEntry->m_aLinkRect.IsInside(rMEvt.GetPosPixel()))
780  {
781  try
782  {
783  css::uno::Reference<css::system::XSystemShellExecute> xSystemShellExecute(
785  //throws css::lang::IllegalArgumentException, css::system::SystemShellExecuteException
786  xSystemShellExecute->execute(rEntry->m_sPublisherURL, OUString(), css::system::SystemShellExecuteFlags::URIS_ONLY);
787  }
788  catch (...)
789  {
790  }
791  return true;
792  }
793  }
794 
795  selectEntry( nPos );
796  }
797  return true;
798  }
799 
800  return false;
801 }
802 
804 {
805  if ( !m_bInDelete )
806  DeleteRemoved();
807 
808  vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
809  sal_uInt16 nKeyCode = aKeyCode.GetCode();
810 
811  bool bHandled = false;
812  if (nKeyCode != KEY_TAB && aKeyCode.GetGroup() == KEYGROUP_CURSOR)
813  bHandled = HandleCursorKey(nKeyCode);
814 
815  return bHandled;
816 }
817 
818 bool ExtensionBox_Impl::FindEntryPos( const TEntry_Impl& rEntry, const long nStart,
819  const long nEnd, long &nPos )
820 {
821  nPos = nStart;
822  if ( nStart > nEnd )
823  return false;
824 
825  sal_Int32 eCompare;
826 
827  if ( nStart == nEnd )
828  {
829  eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nStart ] );
830  if ( eCompare < 0 )
831  return false;
832  else if ( eCompare == 0 )
833  {
834  //Workaround. See i86963.
835  if (rEntry->m_xPackage != m_vEntries[nStart]->m_xPackage)
836  return false;
837 
838  if ( m_bInCheckMode )
839  m_vEntries[ nStart ]->m_bChecked = true;
840  return true;
841  }
842  else
843  {
844  nPos = nStart + 1;
845  return false;
846  }
847  }
848 
849  const long nMid = nStart + ( ( nEnd - nStart ) / 2 );
850  eCompare = rEntry->CompareTo( m_pCollator.get(), m_vEntries[ nMid ] );
851 
852  if ( eCompare < 0 )
853  return FindEntryPos( rEntry, nStart, nMid-1, nPos );
854  else if ( eCompare > 0 )
855  return FindEntryPos( rEntry, nMid+1, nEnd, nPos );
856  else
857  {
858  //Workaround.See i86963.
859  if (rEntry->m_xPackage != m_vEntries[nMid]->m_xPackage)
860  return false;
861 
862  if ( m_bInCheckMode )
863  m_vEntries[ nMid ]->m_bChecked = true;
864  nPos = nMid;
865  return true;
866  }
867 }
868 
870 {
871  m_vListenerAdded.erase(std::remove_if(m_vListenerAdded.begin(), m_vListenerAdded.end(),
872  [](const uno::WeakReference<deployment::XPackage>& rxListener) {
873  const uno::Reference<deployment::XPackage> hardRef(rxListener);
874  return !hardRef.is();
875  }),
876  m_vListenerAdded.end());
877 }
878 
880  uno::Reference<deployment::XPackage > const & extension)
881 {
882  //make sure to only add the listener once
884  if ( std::none_of(m_vListenerAdded.begin(), m_vListenerAdded.end(),
885  FindWeakRef(extension)) )
886  {
887  extension->addEventListener( m_xRemoveListener.get() );
888  m_vListenerAdded.emplace_back(extension);
889  }
890 }
891 
892 
893 void ExtensionBox_Impl::addEntry( const uno::Reference< deployment::XPackage > &xPackage,
894  bool bLicenseMissing )
895 {
897  bool bLocked = m_pManager->isReadOnly( xPackage );
898 
899  TEntry_Impl pEntry = std::make_shared<Entry_Impl>( xPackage, eState, bLocked );
900 
901  // Don't add empty entries
902  if ( pEntry->m_sTitle.isEmpty() )
903  return;
904 
905  {
906  osl::MutexGuard guard(m_entriesMutex);
907  long nPos = 0;
908  if (m_vEntries.empty())
909  {
910  addEventListenerOnce(xPackage);
911  m_vEntries.push_back(pEntry);
912  }
913  else
914  {
915  if (!FindEntryPos(pEntry, 0, m_vEntries.size() - 1, nPos))
916  {
917  addEventListenerOnce(xPackage);
918  m_vEntries.insert(m_vEntries.begin() + nPos, pEntry);
919  }
920  else if (!m_bInCheckMode)
921  {
922  OSL_FAIL("ExtensionBox_Impl::addEntry(): Will not add duplicate entries");
923  }
924  }
925 
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;
931 
932  if (bLicenseMissing)
933  pEntry->m_sErrorText = DpResId(RID_STR_ERROR_MISSING_LICENSE);
934 
935  //access to m_nActive must be guarded
936  if (!m_bInCheckMode && m_bHasActive && (m_nActive >= nPos))
937  m_nActive += 1;
938  }
939 
940  if ( IsReallyVisible() )
941  Invalidate();
942 
943  m_bNeedsRecalc = true;
944 }
945 
946 void ExtensionBox_Impl::updateEntry( const uno::Reference< deployment::XPackage > &xPackage )
947 {
948  for (auto const& entry : m_vEntries)
949  {
950  if ( entry->m_xPackage == xPackage )
951  {
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();
958 
959  if ( eState == REGISTERED )
960  entry->m_bMissingLic = false;
961 
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();
966 
967  if ( IsReallyVisible() )
968  Invalidate();
969  break;
970  }
971  }
972 }
973 
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 )
980 {
981  if ( m_bInDelete )
982  return;
983 
984  bool invalidate = false;
985  {
986  ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
987 
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())
991  {
992  long nPos = iIndex - m_vEntries.begin();
993 
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.get());
1000  m_vEntries.erase( iIndex );
1001 
1002  m_bNeedsRecalc = true;
1003 
1004  if ( IsReallyVisible() )
1005  invalidate = true;
1006 
1007  if ( m_bHasActive )
1008  {
1009  if ( nPos < m_nActive )
1010  m_nActive -= 1;
1011  else if ( ( nPos == m_nActive ) &&
1012  ( nPos == static_cast<long>(m_vEntries.size()) ) )
1013  m_nActive -= 1;
1014 
1015  m_bHasActive = false;
1016  //clear before calling out of this method
1017  aGuard.clear();
1019  }
1020  }
1021  }
1022 
1023  if (invalidate)
1024  {
1025  SolarMutexGuard g;
1026  Invalidate();
1027  }
1028 }
1029 
1030 
1032 {
1033  bool bAllRemoved = false;
1034 
1035  while ( ! bAllRemoved )
1036  {
1037  bAllRemoved = true;
1038 
1039  ::osl::ClearableMutexGuard aGuard( m_entriesMutex );
1040 
1041  for (auto const& entry : m_vEntries)
1042  {
1043  if ( !entry->m_bLocked )
1044  {
1045  bAllRemoved = false;
1046  uno::Reference< deployment::XPackage> xPackage = entry->m_xPackage;
1047  aGuard.clear();
1048  removeEntry( xPackage );
1049  break;
1050  }
1051  }
1052  }
1053 }
1054 
1055 
1057 {
1058  m_bInCheckMode = true;
1059  for (auto const& entry : m_vEntries)
1060  {
1061  entry->m_bChecked = false;
1062  entry->m_bNew = false;
1063  }
1064 }
1065 
1066 
1068 {
1069  long nNewPos = -1;
1070  long nChangedActivePos = -1;
1071  long nPos = 0;
1072  bool bNeedsUpdate = false;
1073 
1074  {
1075  osl::MutexGuard guard(m_entriesMutex);
1076  auto iIndex = m_vEntries.begin();
1077  while (iIndex != m_vEntries.end())
1078  {
1079  if (!(*iIndex)->m_bChecked)
1080  {
1081  (*iIndex)->m_bChecked = true;
1082  bNeedsUpdate = true;
1083  nPos = iIndex - m_vEntries.begin();
1084  if ((*iIndex)->m_bNew)
1085  { // add entry to list and correct active pos
1086  if (nNewPos == -1)
1087  nNewPos = nPos;
1088  if (nPos <= m_nActive)
1089  m_nActive += 1;
1090  ++iIndex;
1091  }
1092  else
1093  { // remove entry from list
1094  if (nPos < nNewPos)
1095  {
1096  --nNewPos;
1097  }
1098  if (nPos < nChangedActivePos)
1099  {
1100  --nChangedActivePos;
1101  }
1102  if (nPos < m_nActive)
1103  m_nActive -= 1;
1104  else if (nPos == m_nActive)
1105  {
1106  nChangedActivePos = nPos;
1107  m_nActive = -1;
1108  m_bHasActive = false;
1109  }
1110  m_vRemovedEntries.push_back(*iIndex);
1111  iIndex = m_vEntries.erase(iIndex);
1112  }
1113  }
1114  else
1115  ++iIndex;
1116  }
1117  }
1118 
1119  m_bInCheckMode = false;
1120 
1121  if ( nNewPos != - 1)
1122  selectEntry( nNewPos );
1123  else if (nChangedActivePos != -1) {
1124  selectEntry(nChangedActivePos);
1125  }
1126 
1127  if ( bNeedsUpdate )
1128  {
1129  m_bNeedsRecalc = true;
1130  if ( IsReallyVisible() )
1131  Invalidate();
1132  }
1133 }
1134 
1136 {
1137  m_nTopIndex = rScrBar.vadjustment_get_value();
1138  Invalidate();
1139 }
1140 
1141 } //namespace dp_gui
1142 
1143 /* 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:24
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:36
constexpr sal_uInt16 KEY_LEFT
std::vector< css::uno::WeakReference< css::deployment::XPackage > > m_vListenerAdded
Definition: dp_gui.h:22
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)