1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "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>
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
;
69 struct ControlEvent
: public ::comphelper::AnyEvent
71 Reference
< XPropertyControl
> xControl
;
72 ControlEventType eType
;
74 ControlEvent( const Reference
< XPropertyControl
>& _rxControl
, ControlEventType _eType
)
75 :xControl( _rxControl
)
84 static ::osl::Mutex
& getMutex();
85 static ::rtl::Reference
< ::comphelper::AsyncEventNotifier
> s_pNotifier
;
88 SharedNotifier(const SharedNotifier
&) = delete;
89 SharedNotifier
& operator=(const SharedNotifier
&) = delete;
90 static const ::rtl::Reference
< ::comphelper::AsyncEventNotifier
>&
95 ::rtl::Reference
< ::comphelper::AsyncEventNotifier
> SharedNotifier::s_pNotifier
;
98 ::osl::Mutex
& SharedNotifier::getMutex()
100 static ::osl::Mutex s_aMutex
;
105 const ::rtl::Reference
< ::comphelper::AsyncEventNotifier
>& SharedNotifier::getNotifier()
107 ::osl::MutexGuard
aGuard( getMutex() );
108 if ( !s_pNotifier
.is() )
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
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
130 enum NotificationMode
137 VclPtr
<OBrowserListBox
> m_pContext
;
138 NotificationMode m_eMode
;
141 /** creates an instance
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>.
155 /** sets the notification mode, so that notifications received from the controls are
156 forwarded to our OBrowserListBox either synchronously or asynchronously
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
;
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
;
175 virtual void processEvent( const ::comphelper::AnyEvent
& _rEvent
) override
;
178 /** processes the given event, i.e. notifies it to our OBrowserListBox
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
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() )
213 void PropertyControlContext_Impl::dispose()
215 SolarMutexGuard aGuard
;
216 if ( impl_isDisposed_nothrow() )
219 SharedNotifier::getNotifier()->removeEventsForProcessor( this );
220 m_pContext
= nullptr;
224 void PropertyControlContext_Impl::setNotificationMode( NotificationMode _eMode
)
226 SolarMutexGuard aGuard
;
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
);
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() )
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
)
307 m_pContext
->focusGained( rControlEvent
.xControl
);
310 m_pContext
->valueChanged( rControlEvent
.xControl
);
313 m_pContext
->activateNextControl( rControlEvent
.xControl
);
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 )
326 ,m_nCurrentPreferredHelpHeight(0)
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());
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();
344 m_aVScroll
->SetScrollHdl(LINK(this, OBrowserListBox
, ScrollHdl
));
347 OBrowserListBox::~OBrowserListBox()
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();
364 m_aLinesPlayground
.disposeAndClear();
365 m_aVScroll
.disposeAndClear();
366 m_pHelpWindow
.disposeAndClear();
371 bool OBrowserListBox::IsModified( ) const
373 bool bModified
= false;
375 if ( m_bIsActive
&& m_xActiveControl
.is() )
376 bModified
= m_xActiveControl
->isModified();
382 void OBrowserListBox::CommitModified( )
384 if ( IsModified() && m_xActiveControl
.is() )
386 // for the time of this commit, notify all events synchronously
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
;
407 // TODO: what's the sense of this?
408 m_aVScroll
->SetThumbPos(100);
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() );
439 bool bNeedScrollbar
= m_aLines
.size() > static_cast<sal_uInt32
>(CalcVisibleLines());
440 if ( !bNeedScrollbar
)
442 if ( m_aVScroll
->IsVisible() )
446 m_aVScroll
->SetThumbPos( 0 );
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
);
470 // show the scrollbar
471 if ( bNeedScrollbar
)
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
);
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() )
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
;
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();
544 m_aVScroll
->SetRange(Range(0,nCount
-1));
545 m_nYOffset
= -m_aVScroll
->GetThumbPos()*m_nRowHeight
;
549 m_aVScroll
->SetRange(Range(0,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() )
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
);
608 void OBrowserListBox::DisableUpdate()
614 void OBrowserListBox::EnableUpdate()
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!" );
633 xControl
->setValue( Any() );
636 impl_setControlAsPropertyValue( *line
, _rValue
);
641 sal_uInt16
OBrowserListBox::GetPropertyPos( const OUString
& _rEntryName
) const
644 for (auto const& line
: m_aLines
)
646 if ( line
.aName
== _rEntryName
)
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
;
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();
695 void OBrowserListBox::InsertEntry(const OLineDescriptor
& _rPropertyData
, sal_uInt16 _nPos
)
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!
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
);
718 m_aLines
.insert( m_aLines
.begin() + _nPos
, aNewLine
);
720 pBrowserLine
->SetTitleWidth(m_nTheNameSize
);
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
++ );
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();
761 void OBrowserListBox::ShowEntry(sal_uInt16 _nPos
)
763 if ( _nPos
< m_aLines
.size() )
765 sal_Int32 nThumbPos
= m_aVScroll
->GetThumbPos();
767 if (_nPos
< nThumbPos
)
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
);
799 // TODO: what's the sense of this two PositionLines? Why not just one call?
800 PositionLine(nEnd
-1);
803 else if (-1 == nDelta
)
805 PositionLine(nThumbPos
);
807 else if (0 != nDelta
)
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
);
835 PositionLine(nEnd
-1);
840 PositionLine(nThumbPos
);
842 else if (nDelta
!=0 || m_aVScroll
->GetType() == ScrollType::DontKnow
)
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
);
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() );
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() );
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
915 for (auto const& search
: m_aLines
)
917 if ( search
.pLine
->getControl().get() == _rxControl
.get() )
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() )
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() )
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
972 while ( static_cast< size_t >( nLine
) < m_aLines
.size() )
974 if ( m_aLines
[nLine
].pLine
->GrabFocus() )
980 if ( ( static_cast< size_t >( nLine
) >= m_aLines
.size() ) && ( !m_aLines
.empty() ) )
981 m_aLines
[0].pLine
->GrabFocus();
988 void lcl_implDisposeControl_nothrow( const Reference
< XPropertyControl
>& _rxControl
)
990 if ( !_rxControl
.is() )
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
)
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() )
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
++ );
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() )
1048 if ( nPos
== EDITOR_LIST_REPLACE_EXISTING
)
1049 nPos
= GetPropertyPos( _rPropertyData
.sName
);
1051 if ( nPos
< m_aLines
.size() )
1053 vcl::Window
* pRefWindow
= nullptr;
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();
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() );
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 );
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 );
1106 rLine
.pLine
->ShowBrowseButton( false );
1109 rLine
.pLine
->HideBrowseButton( false );
1111 rLine
.pLine
->SetClickListener( this );
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 );
1124 rLine
.pLine
->SetTabOrder( pRefWindow
, ZOrderFlags::Behind
);
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
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();
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
)
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();
1200 OSL_FAIL( "OBrowserListBox::PreNotify: internal error, invalid focus control position!" );
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
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
);
1239 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */