Files
libreoffice/extensions/source/propctrlr/browserlistbox.cxx
Stephan Bergmann 314f15bff0 Extend loplugin:external to warn about enums
To mitigate the dangers of silently breaking ADL when moving enums into unnamed
namespaces (see the commit message of 206b5b2661
"New loplugin:external"), note all functions that are affected.  (The plan is to
extend loplugin:external further to also warn about classes and class templates,
and the code to identify affected functions already takes that into account, so
some parts of that code are not actually relevant for enums.)

But it appears that none of the functions that are actually affected by the
changes in this commit relied on being found through ADL, so no adaptions were
necessary for them.

(clang::DeclContext::collectAllContexts is non-const, which recursively means
that External's Visit... functions must take non-const Decl*.  Which required
compilerplugins/clang/sharedvisitor/analyzer.cxx to be generalized to support
such Visit... functions with non-const Decl* parameters.)

Change-Id: Ia215291402bf850d43defdab3cff4db5b270d1bd
Reviewed-on: https://gerrit.libreoffice.org/83001
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
2019-11-17 00:28:17 +01:00

1240 lines
40 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include "browserlistbox.hxx"
#include <strings.hrc>
#include "proplinelistener.hxx"
#include "propcontrolobserver.hxx"
#include "linedescriptor.hxx"
#include "inspectorhelpwindow.hxx"
#include <sal/log.hxx>
#include <com/sun/star/lang/DisposedException.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/inspection/PropertyControlType.hpp>
#include <tools/debug.hxx>
#include <tools/diagnose_ex.h>
#include <comphelper/asyncnotification.hxx>
#include <cppuhelper/implbase.hxx>
#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/lstbox.hxx>
#include <vcl/svapp.hxx>
#include <osl/mutex.hxx>
namespace pcr
{
#define FRAME_OFFSET 4
// TODO: find out what this is really for ... and check if it does make sense in the new
// browser environment
#define LAYOUT_HELP_WINDOW_DISTANCE_APPFONT 3
using ::com::sun::star::uno::Any;
using ::com::sun::star::uno::Exception;
using ::com::sun::star::inspection::XPropertyControlContext;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::inspection::XPropertyControl;
using ::com::sun::star::lang::DisposedException;
using ::com::sun::star::lang::XComponent;
using ::com::sun::star::uno::UNO_QUERY;
namespace PropertyControlType = ::com::sun::star::inspection::PropertyControlType;
namespace {
enum ControlEventType
{
FOCUS_GAINED,
VALUE_CHANGED,
ACTIVATE_NEXT
};
}
struct ControlEvent : public ::comphelper::AnyEvent
{
Reference< XPropertyControl > xControl;
ControlEventType eType;
ControlEvent( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType )
:xControl( _rxControl )
,eType( _eType )
{
}
};
class SharedNotifier
{
private:
static ::osl::Mutex& getMutex();
static ::rtl::Reference< ::comphelper::AsyncEventNotifier > s_pNotifier;
public:
SharedNotifier(const SharedNotifier&) = delete;
SharedNotifier& operator=(const SharedNotifier&) = delete;
static const ::rtl::Reference< ::comphelper::AsyncEventNotifier >&
getNotifier();
};
::rtl::Reference< ::comphelper::AsyncEventNotifier > SharedNotifier::s_pNotifier;
::osl::Mutex& SharedNotifier::getMutex()
{
static ::osl::Mutex s_aMutex;
return s_aMutex;
}
const ::rtl::Reference< ::comphelper::AsyncEventNotifier >& SharedNotifier::getNotifier()
{
::osl::MutexGuard aGuard( getMutex() );
if ( !s_pNotifier.is() )
{
s_pNotifier.set(
new ::comphelper::AsyncEventNotifier("browserlistbox"));
s_pNotifier->launch();
//TODO: a protocol is missing how to join with the launched
// thread before exit(3), to ensure the thread is no longer
// relying on any infrastructure while that infrastructure is
// being shut down in atexit handlers
}
return s_pNotifier;
}
/** implementation for of <type scope="css::inspection">XPropertyControlContext</type>
which forwards all events to a non-UNO version of this interface
*/
typedef ::cppu::WeakImplHelper< XPropertyControlContext > PropertyControlContext_Impl_Base;
class PropertyControlContext_Impl :public PropertyControlContext_Impl_Base
,public ::comphelper::IEventProcessor
{
public:
enum NotificationMode
{
eSynchronously,
eAsynchronously
};
private:
VclPtr<OBrowserListBox> m_pContext;
NotificationMode m_eMode;
public:
/** creates an instance
@param _rContextImpl
the instance to delegate events to
*/
explicit PropertyControlContext_Impl( OBrowserListBox& _rContextImpl );
/** disposes the context.
When you call this method, all subsequent callbacks to the
<type scope="css::inspection">XPropertyControlContext</type> methods
will throw a <type scope="css::lang">DisposedException</type>.
*/
void dispose();
/** sets the notification mode, so that notifications received from the controls are
forwarded to our OBrowserListBox either synchronously or asynchronously
@param _eMode
the new notification mode
*/
void setNotificationMode( NotificationMode _eMode );
virtual void SAL_CALL acquire() throw() override;
virtual void SAL_CALL release() throw() override;
protected:
virtual ~PropertyControlContext_Impl() override;
// XPropertyControlObserver
virtual void SAL_CALL focusGained( const Reference< XPropertyControl >& Control ) override;
virtual void SAL_CALL valueChanged( const Reference< XPropertyControl >& Control ) override;
// XPropertyControlContext
virtual void SAL_CALL activateNextControl( const Reference< XPropertyControl >& CurrentControl ) override;
// IEventProcessor
virtual void processEvent( const ::comphelper::AnyEvent& _rEvent ) override;
private:
/** processes the given event, i.e. notifies it to our OBrowserListBox
@param _rEvent
the event no notify
@precond
our mutex (well, the SolarMutex) is locked
*/
void impl_processEvent_throw( const ::comphelper::AnyEvent& _rEvent );
/** checks whether the instance is already disposed
*/
bool impl_isDisposed_nothrow() const { return m_pContext.get() == nullptr; }
/** notifies the given event originating from the given control
@throws DisposedException
@param _rxControl
@param _eType
*/
void impl_notify_throw( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType );
};
PropertyControlContext_Impl::PropertyControlContext_Impl( OBrowserListBox& _rContextImpl )
:m_pContext( &_rContextImpl )
,m_eMode( eAsynchronously )
{
}
PropertyControlContext_Impl::~PropertyControlContext_Impl()
{
if ( !impl_isDisposed_nothrow() )
dispose();
}
void PropertyControlContext_Impl::dispose()
{
SolarMutexGuard aGuard;
if ( impl_isDisposed_nothrow() )
return;
SharedNotifier::getNotifier()->removeEventsForProcessor( this );
m_pContext = nullptr;
}
void PropertyControlContext_Impl::setNotificationMode( NotificationMode _eMode )
{
SolarMutexGuard aGuard;
m_eMode = _eMode;
}
void PropertyControlContext_Impl::impl_notify_throw( const Reference< XPropertyControl >& _rxControl, ControlEventType _eType )
{
::comphelper::AnyEventRef pEvent;
{
SolarMutexGuard aGuard;
if ( impl_isDisposed_nothrow() )
throw DisposedException( OUString(), *this );
pEvent = new ControlEvent( _rxControl, _eType );
if ( m_eMode == eSynchronously )
{
impl_processEvent_throw( *pEvent );
return;
}
}
SharedNotifier::getNotifier()->addEvent( pEvent, this );
}
void SAL_CALL PropertyControlContext_Impl::focusGained( const Reference< XPropertyControl >& Control )
{
impl_notify_throw( Control, FOCUS_GAINED );
}
void SAL_CALL PropertyControlContext_Impl::valueChanged( const Reference< XPropertyControl >& Control )
{
impl_notify_throw( Control, VALUE_CHANGED );
}
void SAL_CALL PropertyControlContext_Impl::activateNextControl( const Reference< XPropertyControl >& CurrentControl )
{
impl_notify_throw( CurrentControl, ACTIVATE_NEXT );
}
void SAL_CALL PropertyControlContext_Impl::acquire() throw()
{
PropertyControlContext_Impl_Base::acquire();
}
void SAL_CALL PropertyControlContext_Impl::release() throw()
{
PropertyControlContext_Impl_Base::release();
}
void PropertyControlContext_Impl::processEvent( const ::comphelper::AnyEvent& _rEvent )
{
SolarMutexGuard aGuard;
if ( impl_isDisposed_nothrow() )
return;
try
{
impl_processEvent_throw( _rEvent );
}
catch( const Exception& )
{
// can't handle otherwise, since our caller (the notification thread) does not allow
// for exceptions (it could itself abort only)
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
}
}
void PropertyControlContext_Impl::impl_processEvent_throw( const ::comphelper::AnyEvent& _rEvent )
{
const ControlEvent& rControlEvent = static_cast< const ControlEvent& >( _rEvent );
switch ( rControlEvent.eType )
{
case FOCUS_GAINED:
m_pContext->focusGained( rControlEvent.xControl );
break;
case VALUE_CHANGED:
m_pContext->valueChanged( rControlEvent.xControl );
break;
case ACTIVATE_NEXT:
m_pContext->activateNextControl( rControlEvent.xControl );
break;
}
}
OBrowserListBox::OBrowserListBox( vcl::Window* pParent)
:Control(pParent, WB_DIALOGCONTROL | WB_CLIPCHILDREN)
,m_aLinesPlayground(VclPtr<vcl::Window>::Create(this,WB_DIALOGCONTROL | WB_CLIPCHILDREN))
,m_aVScroll(VclPtr<ScrollBar>::Create(this,WB_VSCROLL|WB_REPEAT|WB_DRAG))
,m_pHelpWindow( VclPtr<InspectorHelpWindow>::Create( this ) )
,m_pLineListener(nullptr)
,m_pControlObserver( nullptr )
,m_nYOffset(0)
,m_nCurrentPreferredHelpHeight(0)
,m_nTheNameSize(0)
,m_bIsActive(false)
,m_bUpdate(true)
,m_pControlContextImpl( new PropertyControlContext_Impl( *this ) )
{
ScopedVclPtrInstance<ListBox> aListBox(this, WB_DROPDOWN);
ScopedVclPtrInstance<Edit> aEditBox(this);
m_nRowHeight = std::max(aListBox->get_preferred_size().Height(),
aEditBox->get_preferred_size().Height());
m_nRowHeight += 2;
SetBackground( pParent->GetBackground() );
m_aLinesPlayground->SetBackground( GetBackground() );
m_aLinesPlayground->SetPosPixel(Point(0,0));
m_aLinesPlayground->SetPaintTransparent(true);
m_aLinesPlayground->Show();
m_aVScroll->Hide();
m_aVScroll->SetScrollHdl(LINK(this, OBrowserListBox, ScrollHdl));
}
OBrowserListBox::~OBrowserListBox()
{
disposeOnce();
}
void OBrowserListBox::dispose()
{
OSL_ENSURE( !IsModified(), "OBrowserListBox::~OBrowserListBox: still modified - should have been committed before!" );
// doing the commit here, while we, as well as our owner, as well as some other components,
// are already "half dead" (means within their dtor) is potentially dangerous.
// By definition, CommitModified has to be called (if necessary) before destruction
m_pControlContextImpl->dispose();
m_pControlContextImpl.clear();
Hide();
Clear();
m_aLinesPlayground.disposeAndClear();
m_aVScroll.disposeAndClear();
m_pHelpWindow.disposeAndClear();
Control::dispose();
}
bool OBrowserListBox::IsModified( ) const
{
bool bModified = false;
if ( m_bIsActive && m_xActiveControl.is() )
bModified = m_xActiveControl->isModified();
return bModified;
}
void OBrowserListBox::CommitModified( )
{
if ( IsModified() && m_xActiveControl.is() )
{
// for the time of this commit, notify all events synchronously
// #i63814#
m_pControlContextImpl->setNotificationMode( PropertyControlContext_Impl::eSynchronously );
try
{
m_xActiveControl->notifyModifiedValue();
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
}
m_pControlContextImpl->setNotificationMode( PropertyControlContext_Impl::eAsynchronously );
}
}
void OBrowserListBox::ActivateListBox(bool _bActive)
{
m_bIsActive = _bActive;
if (m_bIsActive)
{
// TODO: what's the sense of this?
m_aVScroll->SetThumbPos(100);
MoveThumbTo(0);
Resize();
}
}
long OBrowserListBox::impl_getPrefererredHelpHeight()
{
return HasHelpSection() ? m_pHelpWindow->GetOptimalHeightPixel() : 0;
}
void OBrowserListBox::Resize()
{
tools::Rectangle aPlayground( Point( 0, 0 ), GetOutputSizePixel() );
Size aHelpWindowDistance( LogicToPixel(Size(0, LAYOUT_HELP_WINDOW_DISTANCE_APPFONT), MapMode(MapUnit::MapAppFont)) );
long nHelpWindowHeight = m_nCurrentPreferredHelpHeight = impl_getPrefererredHelpHeight();
bool bPositionHelpWindow = ( nHelpWindowHeight != 0 );
tools::Rectangle aLinesArea( aPlayground );
if ( bPositionHelpWindow )
{
aLinesArea.AdjustBottom( -nHelpWindowHeight );
aLinesArea.AdjustBottom( -(aHelpWindowDistance.Height()) );
}
m_aLinesPlayground->SetPosSizePixel( aLinesArea.TopLeft(), aLinesArea.GetSize() );
UpdateVScroll();
bool bNeedScrollbar = m_aLines.size() > static_cast<sal_uInt32>(CalcVisibleLines());
if ( !bNeedScrollbar )
{
if ( m_aVScroll->IsVisible() )
m_aVScroll->Hide();
// scroll to top
m_nYOffset = 0;
m_aVScroll->SetThumbPos( 0 );
}
else
{
Size aVScrollSize( m_aVScroll->GetSizePixel() );
// adjust the playground's width
aLinesArea.AdjustRight( -(aVScrollSize.Width()) );
m_aLinesPlayground->SetPosSizePixel( aLinesArea.TopLeft(), aLinesArea.GetSize() );
// position the scrollbar
aVScrollSize.setHeight( aLinesArea.GetHeight() );
Point aVScrollPos( aLinesArea.GetWidth(), 0 );
m_aVScroll->SetPosSizePixel( aVScrollPos, aVScrollSize );
}
for ( ListBoxLines::size_type i = 0; i < m_aLines.size(); ++i )
m_aOutOfDateLines.insert( i );
// repaint
EnablePaint(false);
UpdatePlayGround();
EnablePaint(true);
// show the scrollbar
if ( bNeedScrollbar )
m_aVScroll->Show();
// position the help window
if ( bPositionHelpWindow )
{
tools::Rectangle aHelpArea( aPlayground );
aHelpArea.SetTop( aLinesArea.Bottom() + aHelpWindowDistance.Height() );
m_pHelpWindow->SetPosSizePixel( aHelpArea.TopLeft(), aHelpArea.GetSize() );
}
}
void OBrowserListBox::SetListener( IPropertyLineListener* _pListener )
{
m_pLineListener = _pListener;
}
void OBrowserListBox::SetObserver( IPropertyControlObserver* _pObserver )
{
m_pControlObserver = _pObserver;
}
void OBrowserListBox::EnableHelpSection( bool _bEnable )
{
m_pHelpWindow->Show( _bEnable );
Resize();
}
bool OBrowserListBox::HasHelpSection() const
{
return m_pHelpWindow->IsVisible();
}
void OBrowserListBox::SetHelpText( const OUString& _rHelpText )
{
OSL_ENSURE( HasHelpSection(), "OBrowserListBox::SetHelpText: help section not visible!" );
m_pHelpWindow->SetText( _rHelpText );
if ( m_nCurrentPreferredHelpHeight != impl_getPrefererredHelpHeight() )
Resize();
}
void OBrowserListBox::SetHelpLineLimites( sal_Int32 _nMinLines, sal_Int32 _nMaxLines )
{
m_pHelpWindow->SetLimits( _nMinLines, _nMaxLines );
}
sal_uInt16 OBrowserListBox::CalcVisibleLines()
{
Size aSize(m_aLinesPlayground->GetOutputSizePixel());
sal_uInt16 nResult = 0;
if (0 != m_nRowHeight)
nResult = static_cast<sal_uInt16>(aSize.Height())/m_nRowHeight;
return nResult;
}
void OBrowserListBox::UpdateVScroll()
{
sal_uInt16 nLines = CalcVisibleLines();
m_aVScroll->SetPageSize(nLines-1);
m_aVScroll->SetVisibleSize(nLines-1);
size_t nCount = m_aLines.size();
if (nCount>0)
{
m_aVScroll->SetRange(Range(0,nCount-1));
m_nYOffset = -m_aVScroll->GetThumbPos()*m_nRowHeight;
}
else
{
m_aVScroll->SetRange(Range(0,0));
m_nYOffset = 0;
}
}
void OBrowserListBox::PositionLine( ListBoxLines::size_type _nIndex )
{
Size aSize(m_aLinesPlayground->GetOutputSizePixel());
Point aPos(0, m_nYOffset);
aSize.setHeight( m_nRowHeight );
aPos.AdjustY(_nIndex * m_nRowHeight );
if ( _nIndex < m_aLines.size() )
{
BrowserLinePointer pLine = m_aLines[ _nIndex ].pLine;
pLine->SetPosSizePixel( aPos, aSize );
pLine->SetTitleWidth( m_nTheNameSize + 2 * FRAME_OFFSET );
// show the line if necessary
if ( !pLine->IsVisible() )
pLine->Show();
}
}
void OBrowserListBox::UpdatePosNSize()
{
for ( auto const & aLoop: m_aOutOfDateLines )
{
DBG_ASSERT( aLoop < m_aLines.size(), "OBrowserListBox::UpdatePosNSize: invalid line index!" );
if ( aLoop < m_aLines.size() )
PositionLine( aLoop );
}
m_aOutOfDateLines.clear();
}
void OBrowserListBox::UpdatePlayGround()
{
sal_Int32 nThumbPos = m_aVScroll->GetThumbPos();
sal_Int32 nLines = CalcVisibleLines();
ListBoxLines::size_type nEnd = nThumbPos + nLines;
if (nEnd >= m_aLines.size())
nEnd = m_aLines.size()-1;
if ( !m_aLines.empty() )
{
for ( ListBoxLines::size_type i = nThumbPos; i <= nEnd; ++i )
m_aOutOfDateLines.insert( i );
UpdatePosNSize();
}
}
void OBrowserListBox::DisableUpdate()
{
m_bUpdate = false;
}
void OBrowserListBox::EnableUpdate()
{
m_bUpdate = true;
Resize();
}
void OBrowserListBox::SetPropertyValue(const OUString& _rEntryName, const Any& _rValue, bool _bUnknownValue )
{
ListBoxLines::iterator line = std::find_if(m_aLines.begin(), m_aLines.end(),
[&_rEntryName](const ListBoxLine& rLine) { return rLine.aName == _rEntryName; });
if ( line != m_aLines.end() )
{
if ( _bUnknownValue )
{
Reference< XPropertyControl > xControl( line->pLine->getControl() );
OSL_ENSURE( xControl.is(), "OBrowserListBox::SetPropertyValue: illegal control!" );
if ( xControl.is() )
xControl->setValue( Any() );
}
else
impl_setControlAsPropertyValue( *line, _rValue );
}
}
sal_uInt16 OBrowserListBox::GetPropertyPos( const OUString& _rEntryName ) const
{
sal_uInt16 nPos = 0;
for (auto const& line : m_aLines)
{
if ( line.aName == _rEntryName )
{
return nPos;
}
++nPos;
}
return EDITOR_LIST_ENTRY_NOTFOUND;
}
bool OBrowserListBox::impl_getBrowserLineForName( const OUString& _rEntryName, BrowserLinePointer& _out_rpLine ) const
{
ListBoxLines::const_iterator line = std::find_if(m_aLines.begin(), m_aLines.end(),
[&_rEntryName](const ListBoxLine& rLine) { return rLine.aName == _rEntryName; });
if ( line != m_aLines.end() )
_out_rpLine = line->pLine;
else
_out_rpLine.reset();
return ( nullptr != _out_rpLine.get() );
}
void OBrowserListBox::EnablePropertyControls( const OUString& _rEntryName, sal_Int16 _nControls, bool _bEnable )
{
BrowserLinePointer pLine;
if ( impl_getBrowserLineForName( _rEntryName, pLine ) )
pLine->EnablePropertyControls( _nControls, _bEnable );
}
void OBrowserListBox::EnablePropertyLine( const OUString& _rEntryName, bool _bEnable )
{
BrowserLinePointer pLine;
if ( impl_getBrowserLineForName( _rEntryName, pLine ) )
pLine->EnablePropertyLine( _bEnable );
}
Reference< XPropertyControl > OBrowserListBox::GetPropertyControl( const OUString& _rEntryName )
{
BrowserLinePointer pLine;
if ( impl_getBrowserLineForName( _rEntryName, pLine ) )
return pLine->getControl();
return nullptr;
}
void OBrowserListBox::InsertEntry(const OLineDescriptor& _rPropertyData, sal_uInt16 _nPos)
{
// create a new line
BrowserLinePointer pBrowserLine( new OBrowserLine( _rPropertyData.sName, m_aLinesPlayground.get() ) );
// check that the name is unique
for (auto const& line : m_aLines)
{
if (line.aName == _rPropertyData.sName)
{
// already have another line for this name!
assert(false);
}
}
ListBoxLine aNewLine( _rPropertyData.sName, pBrowserLine, _rPropertyData.xPropertyHandler );
ListBoxLines::size_type nInsertPos = _nPos;
if ( _nPos >= m_aLines.size() )
{
nInsertPos = m_aLines.size();
m_aLines.push_back( aNewLine );
}
else
m_aLines.insert( m_aLines.begin() + _nPos, aNewLine );
pBrowserLine->SetTitleWidth(m_nTheNameSize);
if (m_bUpdate)
{
UpdateVScroll();
Invalidate();
}
// initialize the entry
ChangeEntry(_rPropertyData, nInsertPos);
// update the positions of possibly affected lines
ListBoxLines::size_type nUpdatePos = nInsertPos;
while ( nUpdatePos < m_aLines.size() )
m_aOutOfDateLines.insert( nUpdatePos++ );
UpdatePosNSize( );
}
sal_Int32 OBrowserListBox::GetMinimumWidth() const
{
return m_nTheNameSize + 2 * FRAME_OFFSET + (m_nRowHeight - 4) * 8;
}
sal_Int32 OBrowserListBox::GetMinimumHeight()
{
// assume that we want to display 5 rows, at least
sal_Int32 nMinHeight = m_nRowHeight * 5;
if ( HasHelpSection() )
{
Size aHelpWindowDistance( LogicToPixel(Size(0, LAYOUT_HELP_WINDOW_DISTANCE_APPFONT), MapMode(MapUnit::MapAppFont)) );
nMinHeight += aHelpWindowDistance.Height();
nMinHeight += m_pHelpWindow->GetMinimalHeightPixel();
}
return nMinHeight;
}
void OBrowserListBox::ShowEntry(sal_uInt16 _nPos)
{
if ( _nPos < m_aLines.size() )
{
sal_Int32 nThumbPos = m_aVScroll->GetThumbPos();
if (_nPos < nThumbPos)
MoveThumbTo(_nPos);
else
{
sal_Int32 nLines = CalcVisibleLines();
if (_nPos >= nThumbPos + nLines)
MoveThumbTo(_nPos - nLines + 1);
}
}
}
void OBrowserListBox::MoveThumbTo(sal_Int32 _nNewThumbPos)
{
// disable painting to prevent flicker
m_aLinesPlayground->EnablePaint(false);
sal_Int32 nDelta = _nNewThumbPos - m_aVScroll->GetThumbPos();
// adjust the scrollbar
m_aVScroll->SetThumbPos(_nNewThumbPos);
sal_Int32 nThumbPos = _nNewThumbPos;
m_nYOffset = -m_aVScroll->GetThumbPos() * m_nRowHeight;
sal_Int32 nLines = CalcVisibleLines();
ListBoxLines::size_type nEnd = nThumbPos + nLines;
m_aLinesPlayground->Scroll(0, -nDelta * m_nRowHeight, ScrollFlags::Children);
if (1 == nDelta)
{
// TODO: what's the sense of this two PositionLines? Why not just one call?
PositionLine(nEnd-1);
PositionLine(nEnd);
}
else if (-1 == nDelta)
{
PositionLine(nThumbPos);
}
else if (0 != nDelta)
{
UpdatePlayGround();
}
m_aLinesPlayground->EnablePaint(true);
m_aLinesPlayground->Invalidate(InvalidateFlags::Children);
}
IMPL_LINK(OBrowserListBox, ScrollHdl, ScrollBar*, _pScrollBar, void )
{
DBG_ASSERT(_pScrollBar == m_aVScroll.get(), "OBrowserListBox::ScrollHdl: where does this come from?");
// disable painting to prevent flicker
m_aLinesPlayground->EnablePaint(false);
sal_Int32 nThumbPos = m_aVScroll->GetThumbPos();
sal_Int32 nDelta = m_aVScroll->GetDelta();
m_nYOffset = -nThumbPos * m_nRowHeight;
ListBoxLines::size_type nEnd = nThumbPos + CalcVisibleLines();
m_aLinesPlayground->Scroll(0, -nDelta * m_nRowHeight, ScrollFlags::Children);
if (1 == nDelta)
{
PositionLine(nEnd-1);
PositionLine(nEnd);
}
else if (nDelta==-1)
{
PositionLine(nThumbPos);
}
else if (nDelta!=0 || m_aVScroll->GetType() == ScrollType::DontKnow)
{
UpdatePlayGround();
}
m_aLinesPlayground->EnablePaint(true);
}
void OBrowserListBox::buttonClicked( OBrowserLine* _pLine, bool _bPrimary )
{
DBG_ASSERT( _pLine, "OBrowserListBox::buttonClicked: invalid browser line!" );
if ( _pLine && m_pLineListener )
{
m_pLineListener->Clicked( _pLine->GetEntryName(), _bPrimary );
}
}
void OBrowserListBox::impl_setControlAsPropertyValue( const ListBoxLine& _rLine, const Any& _rPropertyValue )
{
Reference< XPropertyControl > xControl( _rLine.pLine->getControl() );
try
{
if ( _rPropertyValue.getValueType().equals( _rLine.pLine->getControl()->getValueType() ) )
{
xControl->setValue( _rPropertyValue );
}
else
{
SAL_WARN_IF( !_rLine.xHandler.is(), "extensions.propctrlr",
"OBrowserListBox::impl_setControlAsPropertyValue: no handler -> no conversion (property: '"
<< _rLine.pLine->GetEntryName() << "')!" );
if ( _rLine.xHandler.is() )
{
Any aControlValue = _rLine.xHandler->convertToControlValue(
_rLine.pLine->GetEntryName(), _rPropertyValue, xControl->getValueType() );
xControl->setValue( aControlValue );
}
}
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
}
}
Any OBrowserListBox::impl_getControlAsPropertyValue( const ListBoxLine& _rLine )
{
Reference< XPropertyControl > xControl( _rLine.pLine->getControl() );
Any aPropertyValue;
try
{
SAL_WARN_IF( !_rLine.xHandler.is(), "extensions.propctrlr",
"OBrowserListBox::impl_getControlAsPropertyValue: no handler -> no conversion (property: '"
<< _rLine.pLine->GetEntryName() << "')!" );
if ( _rLine.xHandler.is() )
aPropertyValue = _rLine.xHandler->convertToPropertyValue( _rLine.pLine->GetEntryName(), xControl->getValue() );
else
aPropertyValue = xControl->getValue();
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
}
return aPropertyValue;
}
sal_uInt16 OBrowserListBox::impl_getControlPos( const Reference< XPropertyControl >& _rxControl ) const
{
sal_uInt16 nPos = 0;
for (auto const& search : m_aLines)
{
if ( search.pLine->getControl().get() == _rxControl.get() )
return nPos;
++nPos;
}
OSL_FAIL( "OBrowserListBox::impl_getControlPos: invalid control - not part of any of our lines!" );
return sal_uInt16(-1);
}
void OBrowserListBox::focusGained( const Reference< XPropertyControl >& _rxControl )
{
DBG_TESTSOLARMUTEX();
DBG_ASSERT( _rxControl.is(), "OBrowserListBox::focusGained: invalid event source!" );
if ( !_rxControl.is() )
return;
if ( m_pControlObserver )
m_pControlObserver->focusGained( _rxControl );
m_xActiveControl = _rxControl;
ShowEntry( impl_getControlPos( m_xActiveControl ) );
}
void OBrowserListBox::valueChanged( const Reference< XPropertyControl >& _rxControl )
{
DBG_TESTSOLARMUTEX();
DBG_ASSERT( _rxControl.is(), "OBrowserListBox::valueChanged: invalid event source!" );
if ( !_rxControl.is() )
return;
if ( m_pControlObserver )
m_pControlObserver->valueChanged( _rxControl );
if ( m_pLineListener )
{
const ListBoxLine& rLine = m_aLines[ impl_getControlPos( _rxControl ) ];
m_pLineListener->Commit(
rLine.pLine->GetEntryName(),
impl_getControlAsPropertyValue( rLine )
);
}
}
void OBrowserListBox::activateNextControl( const Reference< XPropertyControl >& _rxCurrentControl )
{
DBG_TESTSOLARMUTEX();
sal_uInt16 nLine = impl_getControlPos( _rxCurrentControl );
// cycle forwards, 'til we've the next control which can grab the focus
++nLine;
while ( static_cast< size_t >( nLine ) < m_aLines.size() )
{
if ( m_aLines[nLine].pLine->GrabFocus() )
break;
++nLine;
}
// wrap around?
if ( ( static_cast< size_t >( nLine ) >= m_aLines.size() ) && ( !m_aLines.empty() ) )
m_aLines[0].pLine->GrabFocus();
}
namespace
{
void lcl_implDisposeControl_nothrow( const Reference< XPropertyControl >& _rxControl )
{
if ( !_rxControl.is() )
return;
try
{
_rxControl->setControlContext( nullptr );
Reference< XComponent > xControlComponent( _rxControl, UNO_QUERY );
if ( xControlComponent.is() )
xControlComponent->dispose();
}
catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("extensions.propctrlr");
}
}
}
void OBrowserListBox::Clear()
{
for (auto const& line : m_aLines)
{
// hide the line
line.pLine->Hide();
// reset the listener
lcl_implDisposeControl_nothrow( line.pLine->getControl() );
}
clearContainer( m_aLines );
}
bool OBrowserListBox::RemoveEntry( const OUString& _rName )
{
ListBoxLines::iterator it = std::find_if(m_aLines.begin(), m_aLines.end(),
[&_rName](const ListBoxLine& rLine) { return rLine.aName == _rName; });
if ( it == m_aLines.end() )
return false;
ListBoxLines::size_type nPos = static_cast<ListBoxLines::size_type>(std::distance(m_aLines.begin(), it));
m_aLines.erase( it );
m_aOutOfDateLines.erase( m_aLines.size() );
// update the positions of possibly affected lines
while ( nPos < m_aLines.size() )
m_aOutOfDateLines.insert( nPos++ );
UpdatePosNSize( );
return true;
}
void OBrowserListBox::ChangeEntry( const OLineDescriptor& _rPropertyData, ListBoxLines::size_type nPos )
{
OSL_PRECOND( _rPropertyData.Control.is(), "OBrowserListBox::ChangeEntry: invalid control!" );
if ( !_rPropertyData.Control.is() )
return;
if ( nPos == EDITOR_LIST_REPLACE_EXISTING )
nPos = GetPropertyPos( _rPropertyData.sName );
if ( nPos < m_aLines.size() )
{
vcl::Window* pRefWindow = nullptr;
if ( nPos > 0 )
pRefWindow = m_aLines[nPos-1].pLine->GetRefWindow();
// the current line and control
ListBoxLine& rLine = m_aLines[nPos];
// the old control and some data about it
Reference< XPropertyControl > xControl = rLine.pLine->getControl();
// clean up the old control
lcl_implDisposeControl_nothrow( xControl );
// set the new control at the line
rLine.pLine->setControl( _rPropertyData.Control );
xControl = rLine.pLine->getControl();
if ( xControl.is() )
xControl->setControlContext( m_pControlContextImpl.get() );
// the initial property value
if ( _rPropertyData.bUnknownValue )
xControl->setValue( Any() );
else
impl_setControlAsPropertyValue( rLine, _rPropertyData.aValue );
rLine.pLine->SetTitle(_rPropertyData.DisplayName);
rLine.xHandler = _rPropertyData.xPropertyHandler;
sal_uInt16 nTextWidth = static_cast<sal_uInt16>(m_aLinesPlayground->GetTextWidth(_rPropertyData.DisplayName));
if (m_nTheNameSize< nTextWidth)
m_nTheNameSize = nTextWidth;
if ( _rPropertyData.HasPrimaryButton )
{
if ( !_rPropertyData.PrimaryButtonImageURL.isEmpty() )
rLine.pLine->ShowBrowseButton( _rPropertyData.PrimaryButtonImageURL, true );
else if ( _rPropertyData.PrimaryButtonImage.is() )
rLine.pLine->ShowBrowseButton( Image( _rPropertyData.PrimaryButtonImage ), true );
else
rLine.pLine->ShowBrowseButton( true );
if ( _rPropertyData.HasSecondaryButton )
{
if ( !_rPropertyData.SecondaryButtonImageURL.isEmpty() )
rLine.pLine->ShowBrowseButton( _rPropertyData.SecondaryButtonImageURL, false );
else if ( _rPropertyData.SecondaryButtonImage.is() )
rLine.pLine->ShowBrowseButton( Image( _rPropertyData.SecondaryButtonImage ), false );
else
rLine.pLine->ShowBrowseButton( false );
}
else
rLine.pLine->HideBrowseButton( false );
rLine.pLine->SetClickListener( this );
}
else
{
rLine.pLine->HideBrowseButton( true );
rLine.pLine->HideBrowseButton( false );
}
DBG_ASSERT( ( _rPropertyData.IndentLevel == 0 ) || ( _rPropertyData.IndentLevel == 1 ),
"OBrowserListBox::ChangeEntry: unsupported indent level!" );
rLine.pLine->IndentTitle( _rPropertyData.IndentLevel > 0 );
if ( nPos > 0 )
rLine.pLine->SetTabOrder( pRefWindow, ZOrderFlags::Behind );
else
rLine.pLine->SetTabOrder( pRefWindow, ZOrderFlags::First );
m_aOutOfDateLines.insert( nPos );
rLine.pLine->SetComponentHelpIds(
HelpIdUrl::getHelpId( _rPropertyData.HelpURL )
);
if ( _rPropertyData.bReadOnly )
{
rLine.pLine->SetReadOnly( true );
// user controls (i.e. the ones not provided by the usual
// XPropertyControlFactory) have no chance to know that they should be read-only,
// since XPropertyHandler::describePropertyLine does not transport this
// information.
// So, we manually switch this to read-only.
if ( xControl.is() && ( xControl->getControlType() == PropertyControlType::Unknown ) )
{
vcl::Window *pWindow = rLine.pLine->getControlWindow();
Edit* pControlWindowAsEdit = dynamic_cast<Edit*>(pWindow);
if (pControlWindowAsEdit)
pControlWindowAsEdit->SetReadOnly();
else
pWindow->Enable(false);
}
}
}
}
bool OBrowserListBox::PreNotify( NotifyEvent& _rNEvt )
{
switch ( _rNEvt.GetType() )
{
case MouseNotifyEvent::KEYINPUT:
{
const KeyEvent* pKeyEvent = _rNEvt.GetKeyEvent();
if ( ( pKeyEvent->GetKeyCode().GetModifier() != 0 )
|| ( ( pKeyEvent->GetKeyCode().GetCode() != KEY_PAGEUP )
&& ( pKeyEvent->GetKeyCode().GetCode() != KEY_PAGEDOWN )
)
)
break;
long nScrollOffset = 0;
if ( m_aVScroll->IsVisible() )
{
if ( pKeyEvent->GetKeyCode().GetCode() == KEY_PAGEUP )
nScrollOffset = -m_aVScroll->GetPageSize();
else if ( pKeyEvent->GetKeyCode().GetCode() == KEY_PAGEDOWN )
nScrollOffset = m_aVScroll->GetPageSize();
}
if ( nScrollOffset )
{
long nNewThumbPos = m_aVScroll->GetThumbPos() + nScrollOffset;
nNewThumbPos = std::max( nNewThumbPos, m_aVScroll->GetRangeMin() );
nNewThumbPos = std::min( nNewThumbPos, m_aVScroll->GetRangeMax() );
m_aVScroll->DoScroll( nNewThumbPos );
nNewThumbPos = m_aVScroll->GetThumbPos();
sal_uInt16 nFocusControlPos = 0;
sal_uInt16 nActiveControlPos = impl_getControlPos( m_xActiveControl );
if ( nActiveControlPos < nNewThumbPos )
nFocusControlPos = static_cast<sal_uInt16>(nNewThumbPos);
else if ( nActiveControlPos >= nNewThumbPos + CalcVisibleLines() )
nFocusControlPos = static_cast<sal_uInt16>(nNewThumbPos) + CalcVisibleLines() - 1;
if ( nFocusControlPos )
{
if ( nFocusControlPos < m_aLines.size() )
{
m_aLines[ nFocusControlPos ].pLine->GrabFocus();
}
else
OSL_FAIL( "OBrowserListBox::PreNotify: internal error, invalid focus control position!" );
}
}
return true;
// handled this. In particular, we also consume PageUp/Down events if we do not use them for scrolling,
// otherwise they would be used to scroll the document view, which does not sound like it is desired by
// the user.
}
default:
break;
}
return Control::PreNotify( _rNEvt );
}
bool OBrowserListBox::EventNotify( NotifyEvent& _rNEvt )
{
if ( _rNEvt.GetType() == MouseNotifyEvent::COMMAND)
{
const CommandEvent* pCommand = _rNEvt.GetCommandEvent();
if ( ( CommandEventId::Wheel == pCommand->GetCommand() )
|| ( CommandEventId::StartAutoScroll == pCommand->GetCommand() )
|| ( CommandEventId::AutoScroll == pCommand->GetCommand() )
)
{
// interested in scroll events if we have a scrollbar
if ( m_aVScroll->IsVisible() )
{
HandleScrollCommand( *pCommand, nullptr, m_aVScroll.get() );
}
}
}
return Control::EventNotify(_rNEvt);
}
} // namespace pcr
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */