bump product version to 6.3.0.0.beta1
[LibreOffice.git] / extensions / source / propctrlr / browserlistbox.cxx
blob3bfeee430d88a5888c3ebfd9f1df56a02c4b80e2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "browserlistbox.hxx"
21 #include <strings.hrc>
22 #include "proplinelistener.hxx"
23 #include "propcontrolobserver.hxx"
24 #include "linedescriptor.hxx"
25 #include "inspectorhelpwindow.hxx"
27 #include <sal/log.hxx>
28 #include <com/sun/star/lang/DisposedException.hpp>
29 #include <com/sun/star/lang/XComponent.hpp>
30 #include <com/sun/star/inspection/PropertyControlType.hpp>
31 #include <tools/debug.hxx>
32 #include <tools/diagnose_ex.h>
33 #include <comphelper/asyncnotification.hxx>
34 #include <cppuhelper/implbase.hxx>
35 #include <vcl/commandevent.hxx>
36 #include <vcl/event.hxx>
37 #include <vcl/lstbox.hxx>
38 #include <vcl/svapp.hxx>
39 #include <osl/mutex.hxx>
42 namespace pcr
46 #define FRAME_OFFSET 4
47 // TODO: find out what this is really for ... and check if it does make sense in the new
48 // browser environment
49 #define LAYOUT_HELP_WINDOW_DISTANCE_APPFONT 3
51 using ::com::sun::star::uno::Any;
52 using ::com::sun::star::uno::Exception;
53 using ::com::sun::star::inspection::XPropertyControlContext;
54 using ::com::sun::star::uno::Reference;
55 using ::com::sun::star::inspection::XPropertyControl;
56 using ::com::sun::star::lang::DisposedException;
57 using ::com::sun::star::lang::XComponent;
58 using ::com::sun::star::uno::UNO_QUERY;
60 namespace PropertyControlType = ::com::sun::star::inspection::PropertyControlType;
62 enum ControlEventType
64 FOCUS_GAINED,
65 VALUE_CHANGED,
66 ACTIVATE_NEXT
69 struct ControlEvent : public ::comphelper::AnyEvent
71 Reference< XPropertyControl > xControl;
72 ControlEventType eType;
74 ControlEvent( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType )
75 :xControl( _rxControl )
76 ,eType( _eType )
81 class SharedNotifier
83 private:
84 static ::osl::Mutex& getMutex();
85 static ::rtl::Reference< ::comphelper::AsyncEventNotifier > s_pNotifier;
87 public:
88 SharedNotifier(const SharedNotifier&) = delete;
89 SharedNotifier& operator=(const SharedNotifier&) = delete;
90 static const ::rtl::Reference< ::comphelper::AsyncEventNotifier >&
91 getNotifier();
95 ::rtl::Reference< ::comphelper::AsyncEventNotifier > SharedNotifier::s_pNotifier;
98 ::osl::Mutex& SharedNotifier::getMutex()
100 static ::osl::Mutex s_aMutex;
101 return s_aMutex;
105 const ::rtl::Reference< ::comphelper::AsyncEventNotifier >& SharedNotifier::getNotifier()
107 ::osl::MutexGuard aGuard( getMutex() );
108 if ( !s_pNotifier.is() )
110 s_pNotifier.set(
111 new ::comphelper::AsyncEventNotifier("browserlistbox"));
112 s_pNotifier->launch();
113 //TODO: a protocol is missing how to join with the launched
114 // thread before exit(3), to ensure the thread is no longer
115 // relying on any infrastructure while that infrastructure is
116 // being shut down in atexit handlers
118 return s_pNotifier;
122 /** implementation for of <type scope="css::inspection">XPropertyControlContext</type>
123 which forwards all events to a non-UNO version of this interface
125 typedef ::cppu::WeakImplHelper< XPropertyControlContext > PropertyControlContext_Impl_Base;
126 class PropertyControlContext_Impl :public PropertyControlContext_Impl_Base
127 ,public ::comphelper::IEventProcessor
129 public:
130 enum NotificationMode
132 eSynchronously,
133 eAsynchronously
136 private:
137 VclPtr<OBrowserListBox> m_pContext;
138 NotificationMode m_eMode;
140 public:
141 /** creates an instance
142 @param _rContextImpl
143 the instance to delegate events to
145 explicit PropertyControlContext_Impl( OBrowserListBox& _rContextImpl );
147 /** disposes the context.
149 When you call this method, all subsequent callbacks to the
150 <type scope="css::inspection">XPropertyControlContext</type> methods
151 will throw a <type scope="css::lang">DisposedException</type>.
153 void dispose();
155 /** sets the notification mode, so that notifications received from the controls are
156 forwarded to our OBrowserListBox either synchronously or asynchronously
157 @param _eMode
158 the new notification mode
160 void setNotificationMode( NotificationMode _eMode );
162 virtual void SAL_CALL acquire() throw() override;
163 virtual void SAL_CALL release() throw() override;
165 protected:
166 virtual ~PropertyControlContext_Impl() override;
168 // XPropertyControlObserver
169 virtual void SAL_CALL focusGained( const Reference< XPropertyControl >& Control ) override;
170 virtual void SAL_CALL valueChanged( const Reference< XPropertyControl >& Control ) override;
171 // XPropertyControlContext
172 virtual void SAL_CALL activateNextControl( const Reference< XPropertyControl >& CurrentControl ) override;
174 // IEventProcessor
175 virtual void processEvent( const ::comphelper::AnyEvent& _rEvent ) override;
177 private:
178 /** processes the given event, i.e. notifies it to our OBrowserListBox
179 @param _rEvent
180 the event no notify
181 @precond
182 our mutex (well, the SolarMutex) is locked
184 void impl_processEvent_throw( const ::comphelper::AnyEvent& _rEvent );
186 /** checks whether the instance is already disposed
188 bool impl_isDisposed_nothrow() const { return m_pContext.get() == nullptr; }
190 /** notifies the given event originating from the given control
191 @throws DisposedException
192 @param _rxControl
193 @param _eType
195 void impl_notify_throw( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType );
199 PropertyControlContext_Impl::PropertyControlContext_Impl( OBrowserListBox& _rContextImpl )
200 :m_pContext( &_rContextImpl )
201 ,m_eMode( eAsynchronously )
206 PropertyControlContext_Impl::~PropertyControlContext_Impl()
208 if ( !impl_isDisposed_nothrow() )
209 dispose();
213 void PropertyControlContext_Impl::dispose()
215 SolarMutexGuard aGuard;
216 if ( impl_isDisposed_nothrow() )
217 return;
219 SharedNotifier::getNotifier()->removeEventsForProcessor( this );
220 m_pContext = nullptr;
224 void PropertyControlContext_Impl::setNotificationMode( NotificationMode _eMode )
226 SolarMutexGuard aGuard;
227 m_eMode = _eMode;
231 void PropertyControlContext_Impl::impl_notify_throw( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType )
233 ::comphelper::AnyEventRef pEvent;
236 SolarMutexGuard aGuard;
237 if ( impl_isDisposed_nothrow() )
238 throw DisposedException( OUString(), *this );
239 pEvent = new ControlEvent( _rxControl, _eType );
241 if ( m_eMode == eSynchronously )
243 impl_processEvent_throw( *pEvent );
244 return;
248 SharedNotifier::getNotifier()->addEvent( pEvent, this );
252 void SAL_CALL PropertyControlContext_Impl::focusGained( const Reference< XPropertyControl >& Control )
254 impl_notify_throw( Control, FOCUS_GAINED );
258 void SAL_CALL PropertyControlContext_Impl::valueChanged( const Reference< XPropertyControl >& Control )
260 impl_notify_throw( Control, VALUE_CHANGED );
264 void SAL_CALL PropertyControlContext_Impl::activateNextControl( const Reference< XPropertyControl >& CurrentControl )
266 impl_notify_throw( CurrentControl, ACTIVATE_NEXT );
270 void SAL_CALL PropertyControlContext_Impl::acquire() throw()
272 PropertyControlContext_Impl_Base::acquire();
276 void SAL_CALL PropertyControlContext_Impl::release() throw()
278 PropertyControlContext_Impl_Base::release();
282 void PropertyControlContext_Impl::processEvent( const ::comphelper::AnyEvent& _rEvent )
284 SolarMutexGuard aGuard;
285 if ( impl_isDisposed_nothrow() )
286 return;
290 impl_processEvent_throw( _rEvent );
292 catch( const Exception& )
294 // can't handle otherwise, since our caller (the notification thread) does not allow
295 // for exceptions (it could itself abort only)
296 DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
301 void PropertyControlContext_Impl::impl_processEvent_throw( const ::comphelper::AnyEvent& _rEvent )
303 const ControlEvent& rControlEvent = static_cast< const ControlEvent& >( _rEvent );
304 switch ( rControlEvent.eType )
306 case FOCUS_GAINED:
307 m_pContext->focusGained( rControlEvent.xControl );
308 break;
309 case VALUE_CHANGED:
310 m_pContext->valueChanged( rControlEvent.xControl );
311 break;
312 case ACTIVATE_NEXT:
313 m_pContext->activateNextControl( rControlEvent.xControl );
314 break;
318 OBrowserListBox::OBrowserListBox( vcl::Window* pParent)
319 :Control(pParent, WB_DIALOGCONTROL | WB_CLIPCHILDREN)
320 ,m_aLinesPlayground(VclPtr<vcl::Window>::Create(this,WB_DIALOGCONTROL | WB_CLIPCHILDREN))
321 ,m_aVScroll(VclPtr<ScrollBar>::Create(this,WB_VSCROLL|WB_REPEAT|WB_DRAG))
322 ,m_pHelpWindow( VclPtr<InspectorHelpWindow>::Create( this ) )
323 ,m_pLineListener(nullptr)
324 ,m_pControlObserver( nullptr )
325 ,m_nYOffset(0)
326 ,m_nCurrentPreferredHelpHeight(0)
327 ,m_nTheNameSize(0)
328 ,m_bIsActive(false)
329 ,m_bUpdate(true)
330 ,m_pControlContextImpl( new PropertyControlContext_Impl( *this ) )
332 ScopedVclPtrInstance<ListBox> aListBox(this, WB_DROPDOWN);
333 ScopedVclPtrInstance<Edit> aEditBox(this);
334 m_nRowHeight = std::max(aListBox->get_preferred_size().Height(),
335 aEditBox->get_preferred_size().Height());
336 m_nRowHeight += 2;
337 SetBackground( pParent->GetBackground() );
338 m_aLinesPlayground->SetBackground( GetBackground() );
340 m_aLinesPlayground->SetPosPixel(Point(0,0));
341 m_aLinesPlayground->SetPaintTransparent(true);
342 m_aLinesPlayground->Show();
343 m_aVScroll->Hide();
344 m_aVScroll->SetScrollHdl(LINK(this, OBrowserListBox, ScrollHdl));
347 OBrowserListBox::~OBrowserListBox()
349 disposeOnce();
352 void OBrowserListBox::dispose()
354 OSL_ENSURE( !IsModified(), "OBrowserListBox::~OBrowserListBox: still modified - should have been committed before!" );
355 // doing the commit here, while we, as well as our owner, as well as some other components,
356 // are already "half dead" (means within their dtor) is potentially dangerous.
357 // By definition, CommitModified has to be called (if necessary) before destruction
359 m_pControlContextImpl->dispose();
360 m_pControlContextImpl.clear();
362 Hide();
363 Clear();
364 m_aLinesPlayground.disposeAndClear();
365 m_aVScroll.disposeAndClear();
366 m_pHelpWindow.disposeAndClear();
367 Control::dispose();
371 bool OBrowserListBox::IsModified( ) const
373 bool bModified = false;
375 if ( m_bIsActive && m_xActiveControl.is() )
376 bModified = m_xActiveControl->isModified();
378 return bModified;
382 void OBrowserListBox::CommitModified( )
384 if ( IsModified() && m_xActiveControl.is() )
386 // for the time of this commit, notify all events synchronously
387 // #i63814#
388 m_pControlContextImpl->setNotificationMode( PropertyControlContext_Impl::eSynchronously );
391 m_xActiveControl->notifyModifiedValue();
393 catch( const Exception& )
395 DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
397 m_pControlContextImpl->setNotificationMode( PropertyControlContext_Impl::eAsynchronously );
402 void OBrowserListBox::ActivateListBox(bool _bActive)
404 m_bIsActive = _bActive;
405 if (m_bIsActive)
407 // TODO: what's the sense of this?
408 m_aVScroll->SetThumbPos(100);
409 MoveThumbTo(0);
410 Resize();
415 long OBrowserListBox::impl_getPrefererredHelpHeight()
417 return HasHelpSection() ? m_pHelpWindow->GetOptimalHeightPixel() : 0;
421 void OBrowserListBox::Resize()
423 tools::Rectangle aPlayground( Point( 0, 0 ), GetOutputSizePixel() );
424 Size aHelpWindowDistance( LogicToPixel(Size(0, LAYOUT_HELP_WINDOW_DISTANCE_APPFONT), MapMode(MapUnit::MapAppFont)) );
426 long nHelpWindowHeight = m_nCurrentPreferredHelpHeight = impl_getPrefererredHelpHeight();
427 bool bPositionHelpWindow = ( nHelpWindowHeight != 0 );
429 tools::Rectangle aLinesArea( aPlayground );
430 if ( bPositionHelpWindow )
432 aLinesArea.AdjustBottom( -nHelpWindowHeight );
433 aLinesArea.AdjustBottom( -(aHelpWindowDistance.Height()) );
435 m_aLinesPlayground->SetPosSizePixel( aLinesArea.TopLeft(), aLinesArea.GetSize() );
437 UpdateVScroll();
439 bool bNeedScrollbar = m_aLines.size() > static_cast<sal_uInt32>(CalcVisibleLines());
440 if ( !bNeedScrollbar )
442 if ( m_aVScroll->IsVisible() )
443 m_aVScroll->Hide();
444 // scroll to top
445 m_nYOffset = 0;
446 m_aVScroll->SetThumbPos( 0 );
448 else
450 Size aVScrollSize( m_aVScroll->GetSizePixel() );
452 // adjust the playground's width
453 aLinesArea.AdjustRight( -(aVScrollSize.Width()) );
454 m_aLinesPlayground->SetPosSizePixel( aLinesArea.TopLeft(), aLinesArea.GetSize() );
456 // position the scrollbar
457 aVScrollSize.setHeight( aLinesArea.GetHeight() );
458 Point aVScrollPos( aLinesArea.GetWidth(), 0 );
459 m_aVScroll->SetPosSizePixel( aVScrollPos, aVScrollSize );
462 for ( ListBoxLines::size_type i = 0; i < m_aLines.size(); ++i )
463 m_aOutOfDateLines.insert( i );
465 // repaint
466 EnablePaint(false);
467 UpdatePlayGround();
468 EnablePaint(true);
470 // show the scrollbar
471 if ( bNeedScrollbar )
472 m_aVScroll->Show();
474 // position the help window
475 if ( bPositionHelpWindow )
477 tools::Rectangle aHelpArea( aPlayground );
478 aHelpArea.SetTop( aLinesArea.Bottom() + aHelpWindowDistance.Height() );
479 m_pHelpWindow->SetPosSizePixel( aHelpArea.TopLeft(), aHelpArea.GetSize() );
484 void OBrowserListBox::SetListener( IPropertyLineListener* _pListener )
486 m_pLineListener = _pListener;
490 void OBrowserListBox::SetObserver( IPropertyControlObserver* _pObserver )
492 m_pControlObserver = _pObserver;
496 void OBrowserListBox::EnableHelpSection( bool _bEnable )
498 m_pHelpWindow->Show( _bEnable );
499 Resize();
503 bool OBrowserListBox::HasHelpSection() const
505 return m_pHelpWindow->IsVisible();
509 void OBrowserListBox::SetHelpText( const OUString& _rHelpText )
511 OSL_ENSURE( HasHelpSection(), "OBrowserListBox::SetHelpText: help section not visible!" );
512 m_pHelpWindow->SetText( _rHelpText );
513 if ( m_nCurrentPreferredHelpHeight != impl_getPrefererredHelpHeight() )
514 Resize();
518 void OBrowserListBox::SetHelpLineLimites( sal_Int32 _nMinLines, sal_Int32 _nMaxLines )
520 m_pHelpWindow->SetLimits( _nMinLines, _nMaxLines );
524 sal_uInt16 OBrowserListBox::CalcVisibleLines()
526 Size aSize(m_aLinesPlayground->GetOutputSizePixel());
527 sal_uInt16 nResult = 0;
528 if (0 != m_nRowHeight)
529 nResult = static_cast<sal_uInt16>(aSize.Height())/m_nRowHeight;
531 return nResult;
535 void OBrowserListBox::UpdateVScroll()
537 sal_uInt16 nLines = CalcVisibleLines();
538 m_aVScroll->SetPageSize(nLines-1);
539 m_aVScroll->SetVisibleSize(nLines-1);
541 size_t nCount = m_aLines.size();
542 if (nCount>0)
544 m_aVScroll->SetRange(Range(0,nCount-1));
545 m_nYOffset = -m_aVScroll->GetThumbPos()*m_nRowHeight;
547 else
549 m_aVScroll->SetRange(Range(0,0));
550 m_nYOffset = 0;
555 void OBrowserListBox::PositionLine( ListBoxLines::size_type _nIndex )
557 Size aSize(m_aLinesPlayground->GetOutputSizePixel());
558 Point aPos(0, m_nYOffset);
560 aSize.setHeight( m_nRowHeight );
562 aPos.AdjustY(_nIndex * m_nRowHeight );
564 if ( _nIndex < m_aLines.size() )
566 BrowserLinePointer pLine = m_aLines[ _nIndex ].pLine;
568 pLine->SetPosSizePixel( aPos, aSize );
569 pLine->SetTitleWidth( m_nTheNameSize + 2 * FRAME_OFFSET );
571 // show the line if necessary
572 if ( !pLine->IsVisible() )
573 pLine->Show();
578 void OBrowserListBox::UpdatePosNSize()
580 for ( auto const & aLoop: m_aOutOfDateLines )
582 DBG_ASSERT( aLoop < m_aLines.size(), "OBrowserListBox::UpdatePosNSize: invalid line index!" );
583 if ( aLoop < m_aLines.size() )
584 PositionLine( aLoop );
586 m_aOutOfDateLines.clear();
590 void OBrowserListBox::UpdatePlayGround()
592 sal_Int32 nThumbPos = m_aVScroll->GetThumbPos();
593 sal_Int32 nLines = CalcVisibleLines();
595 ListBoxLines::size_type nEnd = nThumbPos + nLines;
596 if (nEnd >= m_aLines.size())
597 nEnd = m_aLines.size()-1;
599 if ( !m_aLines.empty() )
601 for ( ListBoxLines::size_type i = nThumbPos; i <= nEnd; ++i )
602 m_aOutOfDateLines.insert( i );
603 UpdatePosNSize();
608 void OBrowserListBox::DisableUpdate()
610 m_bUpdate = false;
614 void OBrowserListBox::EnableUpdate()
616 m_bUpdate = true;
617 Resize();
621 void OBrowserListBox::SetPropertyValue(const OUString& _rEntryName, const Any& _rValue, bool _bUnknownValue )
623 ListBoxLines::iterator line = std::find_if(m_aLines.begin(), m_aLines.end(),
624 [&_rEntryName](const ListBoxLine& rLine) { return rLine.aName == _rEntryName; });
626 if ( line != m_aLines.end() )
628 if ( _bUnknownValue )
630 Reference< XPropertyControl > xControl( line->pLine->getControl() );
631 OSL_ENSURE( xControl.is(), "OBrowserListBox::SetPropertyValue: illegal control!" );
632 if ( xControl.is() )
633 xControl->setValue( Any() );
635 else
636 impl_setControlAsPropertyValue( *line, _rValue );
641 sal_uInt16 OBrowserListBox::GetPropertyPos( const OUString& _rEntryName ) const
643 sal_uInt16 nPos = 0;
644 for (auto const& line : m_aLines)
646 if ( line.aName == _rEntryName )
648 return nPos;
650 ++nPos;
653 return EDITOR_LIST_ENTRY_NOTFOUND;
657 bool OBrowserListBox::impl_getBrowserLineForName( const OUString& _rEntryName, BrowserLinePointer& _out_rpLine ) const
659 ListBoxLines::const_iterator line = std::find_if(m_aLines.begin(), m_aLines.end(),
660 [&_rEntryName](const ListBoxLine& rLine) { return rLine.aName == _rEntryName; });
662 if ( line != m_aLines.end() )
663 _out_rpLine = line->pLine;
664 else
665 _out_rpLine.reset();
666 return ( nullptr != _out_rpLine.get() );
670 void OBrowserListBox::EnablePropertyControls( const OUString& _rEntryName, sal_Int16 _nControls, bool _bEnable )
672 BrowserLinePointer pLine;
673 if ( impl_getBrowserLineForName( _rEntryName, pLine ) )
674 pLine->EnablePropertyControls( _nControls, _bEnable );
678 void OBrowserListBox::EnablePropertyLine( const OUString& _rEntryName, bool _bEnable )
680 BrowserLinePointer pLine;
681 if ( impl_getBrowserLineForName( _rEntryName, pLine ) )
682 pLine->EnablePropertyLine( _bEnable );
686 Reference< XPropertyControl > OBrowserListBox::GetPropertyControl( const OUString& _rEntryName )
688 BrowserLinePointer pLine;
689 if ( impl_getBrowserLineForName( _rEntryName, pLine ) )
690 return pLine->getControl();
691 return nullptr;
695 void OBrowserListBox::InsertEntry(const OLineDescriptor& _rPropertyData, sal_uInt16 _nPos)
697 // create a new line
698 BrowserLinePointer pBrowserLine( new OBrowserLine( _rPropertyData.sName, m_aLinesPlayground.get() ) );
700 // check that the name is unique
701 for (auto const& line : m_aLines)
703 if (line.aName == _rPropertyData.sName)
705 // already have another line for this name!
706 assert(false);
710 ListBoxLine aNewLine( _rPropertyData.sName, pBrowserLine, _rPropertyData.xPropertyHandler );
711 ListBoxLines::size_type nInsertPos = _nPos;
712 if ( _nPos >= m_aLines.size() )
714 nInsertPos = m_aLines.size();
715 m_aLines.push_back( aNewLine );
717 else
718 m_aLines.insert( m_aLines.begin() + _nPos, aNewLine );
720 pBrowserLine->SetTitleWidth(m_nTheNameSize);
721 if (m_bUpdate)
723 UpdateVScroll();
724 Invalidate();
727 // initialize the entry
728 ChangeEntry(_rPropertyData, nInsertPos);
730 // update the positions of possibly affected lines
731 ListBoxLines::size_type nUpdatePos = nInsertPos;
732 while ( nUpdatePos < m_aLines.size() )
733 m_aOutOfDateLines.insert( nUpdatePos++ );
734 UpdatePosNSize( );
738 sal_Int32 OBrowserListBox::GetMinimumWidth()
740 return m_nTheNameSize + 2 * FRAME_OFFSET + (m_nRowHeight - 4) * 8;
744 sal_Int32 OBrowserListBox::GetMinimumHeight()
746 // assume that we want to display 5 rows, at least
747 sal_Int32 nMinHeight = m_nRowHeight * 5;
749 if ( HasHelpSection() )
751 Size aHelpWindowDistance( LogicToPixel(Size(0, LAYOUT_HELP_WINDOW_DISTANCE_APPFONT), MapMode(MapUnit::MapAppFont)) );
752 nMinHeight += aHelpWindowDistance.Height();
754 nMinHeight += m_pHelpWindow->GetMinimalHeightPixel();
757 return nMinHeight;
761 void OBrowserListBox::ShowEntry(sal_uInt16 _nPos)
763 if ( _nPos < m_aLines.size() )
765 sal_Int32 nThumbPos = m_aVScroll->GetThumbPos();
767 if (_nPos < nThumbPos)
768 MoveThumbTo(_nPos);
769 else
771 sal_Int32 nLines = CalcVisibleLines();
772 if (_nPos >= nThumbPos + nLines)
773 MoveThumbTo(_nPos - nLines + 1);
780 void OBrowserListBox::MoveThumbTo(sal_Int32 _nNewThumbPos)
782 // disable painting to prevent flicker
783 m_aLinesPlayground->EnablePaint(false);
785 sal_Int32 nDelta = _nNewThumbPos - m_aVScroll->GetThumbPos();
786 // adjust the scrollbar
787 m_aVScroll->SetThumbPos(_nNewThumbPos);
788 sal_Int32 nThumbPos = _nNewThumbPos;
790 m_nYOffset = -m_aVScroll->GetThumbPos() * m_nRowHeight;
792 sal_Int32 nLines = CalcVisibleLines();
793 ListBoxLines::size_type nEnd = nThumbPos + nLines;
795 m_aLinesPlayground->Scroll(0, -nDelta * m_nRowHeight, ScrollFlags::Children);
797 if (1 == nDelta)
799 // TODO: what's the sense of this two PositionLines? Why not just one call?
800 PositionLine(nEnd-1);
801 PositionLine(nEnd);
803 else if (-1 == nDelta)
805 PositionLine(nThumbPos);
807 else if (0 != nDelta)
809 UpdatePlayGround();
812 m_aLinesPlayground->EnablePaint(true);
813 m_aLinesPlayground->Invalidate(InvalidateFlags::Children);
817 IMPL_LINK(OBrowserListBox, ScrollHdl, ScrollBar*, _pScrollBar, void )
819 DBG_ASSERT(_pScrollBar == m_aVScroll.get(), "OBrowserListBox::ScrollHdl: where does this come from?");
821 // disable painting to prevent flicker
822 m_aLinesPlayground->EnablePaint(false);
824 sal_Int32 nThumbPos = m_aVScroll->GetThumbPos();
826 sal_Int32 nDelta = m_aVScroll->GetDelta();
827 m_nYOffset = -nThumbPos * m_nRowHeight;
829 ListBoxLines::size_type nEnd = nThumbPos + CalcVisibleLines();
831 m_aLinesPlayground->Scroll(0, -nDelta * m_nRowHeight, ScrollFlags::Children);
833 if (1 == nDelta)
835 PositionLine(nEnd-1);
836 PositionLine(nEnd);
838 else if (nDelta==-1)
840 PositionLine(nThumbPos);
842 else if (nDelta!=0 || m_aVScroll->GetType() == ScrollType::DontKnow)
844 UpdatePlayGround();
847 m_aLinesPlayground->EnablePaint(true);
851 void OBrowserListBox::buttonClicked( OBrowserLine* _pLine, bool _bPrimary )
853 DBG_ASSERT( _pLine, "OBrowserListBox::buttonClicked: invalid browser line!" );
854 if ( _pLine && m_pLineListener )
856 m_pLineListener->Clicked( _pLine->GetEntryName(), _bPrimary );
861 void OBrowserListBox::impl_setControlAsPropertyValue( const ListBoxLine& _rLine, const Any& _rPropertyValue )
863 Reference< XPropertyControl > xControl( _rLine.pLine->getControl() );
866 if ( _rPropertyValue.getValueType().equals( _rLine.pLine->getControl()->getValueType() ) )
868 xControl->setValue( _rPropertyValue );
870 else
872 SAL_WARN_IF( !_rLine.xHandler.is(), "extensions.propctrlr",
873 "OBrowserListBox::impl_setControlAsPropertyValue: no handler -> no conversion (property: '"
874 << _rLine.pLine->GetEntryName() << "')!" );
875 if ( _rLine.xHandler.is() )
877 Any aControlValue = _rLine.xHandler->convertToControlValue(
878 _rLine.pLine->GetEntryName(), _rPropertyValue, xControl->getValueType() );
879 xControl->setValue( aControlValue );
883 catch( const Exception& )
885 DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
890 Any OBrowserListBox::impl_getControlAsPropertyValue( const ListBoxLine& _rLine )
892 Reference< XPropertyControl > xControl( _rLine.pLine->getControl() );
893 Any aPropertyValue;
896 SAL_WARN_IF( !_rLine.xHandler.is(), "extensions.propctrlr",
897 "OBrowserListBox::impl_getControlAsPropertyValue: no handler -> no conversion (property: '"
898 << _rLine.pLine->GetEntryName() << "')!" );
899 if ( _rLine.xHandler.is() )
900 aPropertyValue = _rLine.xHandler->convertToPropertyValue( _rLine.pLine->GetEntryName(), xControl->getValue() );
901 else
902 aPropertyValue = xControl->getValue();
904 catch( const Exception& )
906 DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
908 return aPropertyValue;
912 sal_uInt16 OBrowserListBox::impl_getControlPos( const Reference< XPropertyControl >& _rxControl ) const
914 sal_uInt16 nPos = 0;
915 for (auto const& search : m_aLines)
917 if ( search.pLine->getControl().get() == _rxControl.get() )
918 return nPos;
919 ++nPos;
921 OSL_FAIL( "OBrowserListBox::impl_getControlPos: invalid control - not part of any of our lines!" );
922 return sal_uInt16(-1);
926 void OBrowserListBox::focusGained( const Reference< XPropertyControl >& _rxControl )
928 DBG_TESTSOLARMUTEX();
930 DBG_ASSERT( _rxControl.is(), "OBrowserListBox::focusGained: invalid event source!" );
931 if ( !_rxControl.is() )
932 return;
934 if ( m_pControlObserver )
935 m_pControlObserver->focusGained( _rxControl );
937 m_xActiveControl = _rxControl;
938 ShowEntry( impl_getControlPos( m_xActiveControl ) );
942 void OBrowserListBox::valueChanged( const Reference< XPropertyControl >& _rxControl )
944 DBG_TESTSOLARMUTEX();
946 DBG_ASSERT( _rxControl.is(), "OBrowserListBox::valueChanged: invalid event source!" );
947 if ( !_rxControl.is() )
948 return;
950 if ( m_pControlObserver )
951 m_pControlObserver->valueChanged( _rxControl );
953 if ( m_pLineListener )
955 const ListBoxLine& rLine = m_aLines[ impl_getControlPos( _rxControl ) ];
956 m_pLineListener->Commit(
957 rLine.pLine->GetEntryName(),
958 impl_getControlAsPropertyValue( rLine )
964 void OBrowserListBox::activateNextControl( const Reference< XPropertyControl >& _rxCurrentControl )
966 DBG_TESTSOLARMUTEX();
968 sal_uInt16 nLine = impl_getControlPos( _rxCurrentControl );
970 // cycle forwards, 'til we've the next control which can grab the focus
971 ++nLine;
972 while ( static_cast< size_t >( nLine ) < m_aLines.size() )
974 if ( m_aLines[nLine].pLine->GrabFocus() )
975 break;
976 ++nLine;
979 // wrap around?
980 if ( ( static_cast< size_t >( nLine ) >= m_aLines.size() ) && ( !m_aLines.empty() ) )
981 m_aLines[0].pLine->GrabFocus();
985 namespace
988 void lcl_implDisposeControl_nothrow( const Reference< XPropertyControl >& _rxControl )
990 if ( !_rxControl.is() )
991 return;
994 _rxControl->setControlContext( nullptr );
995 Reference< XComponent > xControlComponent( _rxControl, UNO_QUERY );
996 if ( xControlComponent.is() )
997 xControlComponent->dispose();
999 catch( const Exception& )
1001 DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
1007 void OBrowserListBox::Clear()
1009 for (auto const& line : m_aLines)
1011 // hide the line
1012 line.pLine->Hide();
1013 // reset the listener
1014 lcl_implDisposeControl_nothrow( line.pLine->getControl() );
1017 clearContainer( m_aLines );
1021 bool OBrowserListBox::RemoveEntry( const OUString& _rName )
1023 ListBoxLines::iterator it = std::find_if(m_aLines.begin(), m_aLines.end(),
1024 [&_rName](const ListBoxLine& rLine) { return rLine.aName == _rName; });
1026 if ( it == m_aLines.end() )
1027 return false;
1029 ListBoxLines::size_type nPos = static_cast<ListBoxLines::size_type>(std::distance(m_aLines.begin(), it));
1030 m_aLines.erase( it );
1031 m_aOutOfDateLines.erase( m_aLines.size() );
1033 // update the positions of possibly affected lines
1034 while ( nPos < m_aLines.size() )
1035 m_aOutOfDateLines.insert( nPos++ );
1036 UpdatePosNSize( );
1038 return true;
1042 void OBrowserListBox::ChangeEntry( const OLineDescriptor& _rPropertyData, ListBoxLines::size_type nPos )
1044 OSL_PRECOND( _rPropertyData.Control.is(), "OBrowserListBox::ChangeEntry: invalid control!" );
1045 if ( !_rPropertyData.Control.is() )
1046 return;
1048 if ( nPos == EDITOR_LIST_REPLACE_EXISTING )
1049 nPos = GetPropertyPos( _rPropertyData.sName );
1051 if ( nPos < m_aLines.size() )
1053 vcl::Window* pRefWindow = nullptr;
1054 if ( nPos > 0 )
1055 pRefWindow = m_aLines[nPos-1].pLine->GetRefWindow();
1057 // the current line and control
1058 ListBoxLine& rLine = m_aLines[nPos];
1060 // the old control and some data about it
1061 Reference< XPropertyControl > xControl = rLine.pLine->getControl();
1062 vcl::Window* pControlWindow = rLine.pLine->getControlWindow();
1063 Point aControlPos;
1064 if ( pControlWindow )
1065 aControlPos = pControlWindow->GetPosPixel();
1067 // clean up the old control
1068 lcl_implDisposeControl_nothrow( xControl );
1070 // set the new control at the line
1071 rLine.pLine->setControl( _rPropertyData.Control );
1072 xControl = rLine.pLine->getControl();
1074 if ( xControl.is() )
1075 xControl->setControlContext( m_pControlContextImpl.get() );
1077 // the initial property value
1078 if ( _rPropertyData.bUnknownValue )
1079 xControl->setValue( Any() );
1080 else
1081 impl_setControlAsPropertyValue( rLine, _rPropertyData.aValue );
1083 rLine.pLine->SetTitle(_rPropertyData.DisplayName);
1084 rLine.xHandler = _rPropertyData.xPropertyHandler;
1086 sal_uInt16 nTextWidth = static_cast<sal_uInt16>(m_aLinesPlayground->GetTextWidth(_rPropertyData.DisplayName));
1087 if (m_nTheNameSize< nTextWidth)
1088 m_nTheNameSize = nTextWidth;
1090 if ( _rPropertyData.HasPrimaryButton )
1092 if ( !_rPropertyData.PrimaryButtonImageURL.isEmpty() )
1093 rLine.pLine->ShowBrowseButton( _rPropertyData.PrimaryButtonImageURL, true );
1094 else if ( _rPropertyData.PrimaryButtonImage.is() )
1095 rLine.pLine->ShowBrowseButton( Image( _rPropertyData.PrimaryButtonImage ), true );
1096 else
1097 rLine.pLine->ShowBrowseButton( true );
1099 if ( _rPropertyData.HasSecondaryButton )
1101 if ( !_rPropertyData.SecondaryButtonImageURL.isEmpty() )
1102 rLine.pLine->ShowBrowseButton( _rPropertyData.SecondaryButtonImageURL, false );
1103 else if ( _rPropertyData.SecondaryButtonImage.is() )
1104 rLine.pLine->ShowBrowseButton( Image( _rPropertyData.SecondaryButtonImage ), false );
1105 else
1106 rLine.pLine->ShowBrowseButton( false );
1108 else
1109 rLine.pLine->HideBrowseButton( false );
1111 rLine.pLine->SetClickListener( this );
1113 else
1115 rLine.pLine->HideBrowseButton( true );
1116 rLine.pLine->HideBrowseButton( false );
1119 DBG_ASSERT( ( _rPropertyData.IndentLevel == 0 ) || ( _rPropertyData.IndentLevel == 1 ),
1120 "OBrowserListBox::ChangeEntry: unsupported indent level!" );
1121 rLine.pLine->IndentTitle( _rPropertyData.IndentLevel > 0 );
1123 if ( nPos > 0 )
1124 rLine.pLine->SetTabOrder( pRefWindow, ZOrderFlags::Behind );
1125 else
1126 rLine.pLine->SetTabOrder( pRefWindow, ZOrderFlags::First );
1128 m_aOutOfDateLines.insert( nPos );
1129 rLine.pLine->SetComponentHelpIds(
1130 HelpIdUrl::getHelpId( _rPropertyData.HelpURL )
1133 if ( _rPropertyData.bReadOnly )
1135 rLine.pLine->SetReadOnly( true );
1137 // user controls (i.e. the ones not provided by the usual
1138 // XPropertyControlFactory) have no chance to know that they should be read-only,
1139 // since XPropertyHandler::describePropertyLine does not transport this
1140 // information.
1141 // So, we manually switch this to read-only.
1142 if ( xControl.is() && ( xControl->getControlType() == PropertyControlType::Unknown ) )
1144 vcl::Window *pWindow = rLine.pLine->getControlWindow();
1145 Edit* pControlWindowAsEdit = dynamic_cast<Edit*>(pWindow);
1146 if (pControlWindowAsEdit)
1147 pControlWindowAsEdit->SetReadOnly();
1148 else
1149 pWindow->Enable(false);
1156 bool OBrowserListBox::PreNotify( NotifyEvent& _rNEvt )
1158 switch ( _rNEvt.GetType() )
1160 case MouseNotifyEvent::KEYINPUT:
1162 const KeyEvent* pKeyEvent = _rNEvt.GetKeyEvent();
1163 if ( ( pKeyEvent->GetKeyCode().GetModifier() != 0 )
1164 || ( ( pKeyEvent->GetKeyCode().GetCode() != KEY_PAGEUP )
1165 && ( pKeyEvent->GetKeyCode().GetCode() != KEY_PAGEDOWN )
1168 break;
1170 long nScrollOffset = 0;
1171 if ( m_aVScroll->IsVisible() )
1173 if ( pKeyEvent->GetKeyCode().GetCode() == KEY_PAGEUP )
1174 nScrollOffset = -m_aVScroll->GetPageSize();
1175 else if ( pKeyEvent->GetKeyCode().GetCode() == KEY_PAGEDOWN )
1176 nScrollOffset = m_aVScroll->GetPageSize();
1179 if ( nScrollOffset )
1181 long nNewThumbPos = m_aVScroll->GetThumbPos() + nScrollOffset;
1182 nNewThumbPos = std::max( nNewThumbPos, m_aVScroll->GetRangeMin() );
1183 nNewThumbPos = std::min( nNewThumbPos, m_aVScroll->GetRangeMax() );
1184 m_aVScroll->DoScroll( nNewThumbPos );
1185 nNewThumbPos = m_aVScroll->GetThumbPos();
1187 sal_uInt16 nFocusControlPos = 0;
1188 sal_uInt16 nActiveControlPos = impl_getControlPos( m_xActiveControl );
1189 if ( nActiveControlPos < nNewThumbPos )
1190 nFocusControlPos = static_cast<sal_uInt16>(nNewThumbPos);
1191 else if ( nActiveControlPos >= nNewThumbPos + CalcVisibleLines() )
1192 nFocusControlPos = static_cast<sal_uInt16>(nNewThumbPos) + CalcVisibleLines() - 1;
1193 if ( nFocusControlPos )
1195 if ( nFocusControlPos < m_aLines.size() )
1197 m_aLines[ nFocusControlPos ].pLine->GrabFocus();
1199 else
1200 OSL_FAIL( "OBrowserListBox::PreNotify: internal error, invalid focus control position!" );
1204 return true;
1205 // handled this. In particular, we also consume PageUp/Down events if we do not use them for scrolling,
1206 // otherwise they would be used to scroll the document view, which does not sound like it is desired by
1207 // the user.
1209 default:
1210 break;
1212 return Control::PreNotify( _rNEvt );
1215 bool OBrowserListBox::EventNotify( NotifyEvent& _rNEvt )
1217 if ( _rNEvt.GetType() == MouseNotifyEvent::COMMAND)
1219 const CommandEvent* pCommand = _rNEvt.GetCommandEvent();
1220 if ( ( CommandEventId::Wheel == pCommand->GetCommand() )
1221 || ( CommandEventId::StartAutoScroll == pCommand->GetCommand() )
1222 || ( CommandEventId::AutoScroll == pCommand->GetCommand() )
1225 // interested in scroll events if we have a scrollbar
1226 if ( m_aVScroll->IsVisible() )
1228 HandleScrollCommand( *pCommand, nullptr, m_aVScroll.get() );
1232 return Control::EventNotify(_rNEvt);
1236 } // namespace pcr
1239 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */