diff options
Diffstat (limited to 'svtools/source/control')
-rw-r--r-- | svtools/source/control/accessibleruler.cxx | 362 | ||||
-rw-r--r-- | svtools/source/control/accessibleruler.hxx | 188 | ||||
-rw-r--r-- | svtools/source/control/asynclink.cxx | 100 | ||||
-rw-r--r-- | svtools/source/control/calendar.cxx | 346 | ||||
-rw-r--r-- | svtools/source/control/collatorres.cxx | 66 | ||||
-rw-r--r-- | svtools/source/control/ctrlbox.cxx | 1663 | ||||
-rw-r--r-- | svtools/source/control/ctrltool.cxx | 890 | ||||
-rw-r--r-- | svtools/source/control/indexentryres.cxx | 58 | ||||
-rw-r--r-- | svtools/source/control/inettbc.cxx | 1144 | ||||
-rw-r--r-- | svtools/source/control/managedmenubutton.cxx | 122 | ||||
-rw-r--r-- | svtools/source/control/ruler.cxx | 2774 | ||||
-rw-r--r-- | svtools/source/control/scriptedtext.cxx | 317 | ||||
-rw-r--r-- | svtools/source/control/scrwin.cxx | 345 | ||||
-rw-r--r-- | svtools/source/control/tabbar.cxx | 2525 | ||||
-rw-r--r-- | svtools/source/control/toolbarmenu.cxx | 267 | ||||
-rw-r--r-- | svtools/source/control/valueacc.cxx | 1020 | ||||
-rw-r--r-- | svtools/source/control/valueimp.hxx | 255 | ||||
-rw-r--r-- | svtools/source/control/valueset.cxx | 1942 |
18 files changed, 14384 insertions, 0 deletions
diff --git a/svtools/source/control/accessibleruler.cxx b/svtools/source/control/accessibleruler.cxx new file mode 100644 index 000000000..cfd54364e --- /dev/null +++ b/svtools/source/control/accessibleruler.cxx @@ -0,0 +1,362 @@ +/* -*- 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 <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/IllegalAccessibleComponentStateException.hpp> +#include <unotools/accessiblestatesethelper.hxx> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <comphelper/accessibleeventnotifier.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <toolkit/helper/convert.hxx> +#include <vcl/svapp.hxx> +#include <osl/mutex.hxx> +#include <tools/gen.hxx> + +#include <svtools/ruler.hxx> +#include "accessibleruler.hxx" + +using namespace ::cppu; +using namespace ::osl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::accessibility; + + +//===== internal ============================================================ + +SvtRulerAccessible::SvtRulerAccessible( + const uno::Reference< XAccessible >& rxParent, Ruler& rRepr, const OUString& rName ) : + + SvtRulerAccessible_Base( m_aMutex ), + msName( rName ), + mxParent( rxParent ), + mpRepr( &rRepr ), + mnClientId( 0 ) +{ +} + +SvtRulerAccessible::~SvtRulerAccessible() +{ + + if( IsAlive() ) + { + osl_atomic_increment( &m_refCount ); + dispose(); // set mpRepr = NULL & release all children + } +} + +//===== XAccessible ========================================================= + +uno::Reference< XAccessibleContext > SAL_CALL SvtRulerAccessible::getAccessibleContext() +{ + return this; +} + +//===== XAccessibleComponent ================================================ + +sal_Bool SAL_CALL SvtRulerAccessible::containsPoint( const awt::Point& rPoint ) +{ + // no guard -> done in getBounds() +// return GetBoundingBox().IsInside( VCLPoint( rPoint ) ); + return tools::Rectangle( Point( 0, 0 ), GetBoundingBox().GetSize() ).IsInside( VCLPoint( rPoint ) ); +} + +uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleAtPoint( const awt::Point& ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + uno::Reference< XAccessible > xRet; + + + return xRet; +} + +awt::Rectangle SAL_CALL SvtRulerAccessible::getBounds() +{ + // no guard -> done in GetBoundingBox() + return AWTRectangle( GetBoundingBox() ); +} + +awt::Point SAL_CALL SvtRulerAccessible::getLocation() +{ + // no guard -> done in GetBoundingBox() + return AWTPoint( GetBoundingBox().TopLeft() ); +} + +awt::Point SAL_CALL SvtRulerAccessible::getLocationOnScreen() +{ + // no guard -> done in GetBoundingBoxOnScreen() + return AWTPoint( GetBoundingBoxOnScreen().TopLeft() ); +} + +awt::Size SAL_CALL SvtRulerAccessible::getSize() +{ + // no guard -> done in GetBoundingBox() + return AWTSize( GetBoundingBox().GetSize() ); +} + +bool SvtRulerAccessible::isVisible() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + return mpRepr->IsVisible(); +} + +//===== XAccessibleContext ================================================== +sal_Int32 SAL_CALL SvtRulerAccessible::getAccessibleChildCount() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + return 0; +} + +uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleChild( sal_Int32 ) +{ + uno::Reference< XAccessible > xChild ; + + return xChild; +} + +uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleParent() +{ + return mxParent; +} + +sal_Int32 SAL_CALL SvtRulerAccessible::getAccessibleIndexInParent() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + // Use a simple but slow solution for now. Optimize later. + + // Iterate over all the parent's children and search for this object. + if( mxParent.is() ) + { + uno::Reference< XAccessibleContext > xParentContext( mxParent->getAccessibleContext() ); + if( xParentContext.is() ) + { + sal_Int32 nChildCount = xParentContext->getAccessibleChildCount(); + for( sal_Int32 i = 0 ; i < nChildCount ; ++i ) + { + uno::Reference< XAccessible > xChild( xParentContext->getAccessibleChild( i ) ); + if( xChild.get() == static_cast<XAccessible*>(this) ) + return i; + } + } + } + + // Return -1 to indicate that this object's parent does not know about the + // object. + return -1; +} + +sal_Int16 SAL_CALL SvtRulerAccessible::getAccessibleRole() +{ + return AccessibleRole::RULER; +} + +OUString SAL_CALL SvtRulerAccessible::getAccessibleDescription() +{ + return OUString(); +} + +OUString SAL_CALL SvtRulerAccessible::getAccessibleName() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + return msName; +} + +/** Return empty uno::Reference to indicate that the relation set is not + supported. +*/ +uno::Reference< XAccessibleRelationSet > SAL_CALL SvtRulerAccessible::getAccessibleRelationSet() +{ + return uno::Reference< XAccessibleRelationSet >(); +} + + +uno::Reference< XAccessibleStateSet > SAL_CALL SvtRulerAccessible::getAccessibleStateSet() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + utl::AccessibleStateSetHelper* pStateSetHelper = new utl::AccessibleStateSetHelper; + + if( IsAlive() ) + { + pStateSetHelper->AddState( AccessibleStateType::ENABLED ); + + pStateSetHelper->AddState( AccessibleStateType::SHOWING ); + + if( isVisible() ) + pStateSetHelper->AddState( AccessibleStateType::VISIBLE ); + + if ( mpRepr->GetStyle() & WB_HORZ ) + pStateSetHelper->AddState( AccessibleStateType::HORIZONTAL ); + else + pStateSetHelper->AddState( AccessibleStateType::VERTICAL ); + + if(pStateSetHelper->contains(AccessibleStateType::FOCUSABLE)) + { + pStateSetHelper->RemoveState( AccessibleStateType::FOCUSABLE ); + } + + } + + + return pStateSetHelper; +} + +lang::Locale SAL_CALL SvtRulerAccessible::getLocale() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if( mxParent.is() ) + { + uno::Reference< XAccessibleContext > xParentContext( mxParent->getAccessibleContext() ); + if( xParentContext.is() ) + return xParentContext->getLocale(); + } + + // No parent. Therefore throw exception to indicate this cluelessness. + throw IllegalAccessibleComponentStateException(); +} + +void SAL_CALL SvtRulerAccessible::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (xListener.is()) + { + ::osl::MutexGuard aGuard( m_aMutex ); + if (!mnClientId) + mnClientId = comphelper::AccessibleEventNotifier::registerClient( ); + comphelper::AccessibleEventNotifier::addEventListener( mnClientId, xListener ); + } +} + +void SAL_CALL SvtRulerAccessible::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) +{ + if (!(xListener.is() && mnClientId)) + return; + + ::osl::MutexGuard aGuard( m_aMutex ); + + sal_Int32 nListenerCount = comphelper::AccessibleEventNotifier::removeEventListener( mnClientId, xListener ); + if ( !nListenerCount ) + { + // no listeners anymore + // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), + // and at least to us not firing any events anymore, in case somebody calls + // NotifyAccessibleEvent, again + comphelper::AccessibleEventNotifier::revokeClient( mnClientId ); + mnClientId = 0; + } +} + +void SAL_CALL SvtRulerAccessible::grabFocus() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + mpRepr->GrabFocus(); +} + +sal_Int32 SvtRulerAccessible::getForeground( ) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + ThrowExceptionIfNotAlive(); + + return sal_Int32(mpRepr->GetControlForeground()); +} +sal_Int32 SvtRulerAccessible::getBackground( ) +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + ThrowExceptionIfNotAlive(); + + return sal_Int32(mpRepr->GetControlBackground()); +} + +// XServiceInfo +OUString SAL_CALL SvtRulerAccessible::getImplementationName() +{ + return "com.sun.star.comp.ui.SvtRulerAccessible"; +} + +sal_Bool SAL_CALL SvtRulerAccessible::supportsService( const OUString& sServiceName ) +{ + return cppu::supportsService( this, sServiceName ); +} + +Sequence< OUString > SAL_CALL SvtRulerAccessible::getSupportedServiceNames() +{ + return { "com.sun.star.accessibility.AccessibleContext" }; +} + +//===== XTypeProvider ======================================================= +Sequence< sal_Int8 > SAL_CALL SvtRulerAccessible::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +void SAL_CALL SvtRulerAccessible::disposing() +{ + if( rBHelper.bDisposed ) + return; + + ::osl::MutexGuard aGuard( m_aMutex ); + mpRepr = nullptr; // object dies with representation + + // Send a disposing to all listeners. + if ( mnClientId ) + { + comphelper::AccessibleEventNotifier::revokeClientNotifyDisposing( mnClientId, *this ); + mnClientId = 0; + } + mxParent.clear(); +} + +tools::Rectangle SvtRulerAccessible::GetBoundingBoxOnScreen() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + return tools::Rectangle( mpRepr->GetParent()->OutputToAbsoluteScreenPixel( mpRepr->GetPosPixel() ), mpRepr->GetSizePixel() ); +} + +tools::Rectangle SvtRulerAccessible::GetBoundingBox() +{ + SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard( m_aMutex ); + + ThrowExceptionIfNotAlive(); + + return tools::Rectangle( mpRepr->GetPosPixel(), mpRepr->GetSizePixel() ); +} + +void SvtRulerAccessible::ThrowExceptionIfNotAlive() +{ + if( rBHelper.bDisposed || rBHelper.bInDispose ) + throw lang::DisposedException(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/accessibleruler.hxx b/svtools/source/control/accessibleruler.hxx new file mode 100644 index 000000000..bd7d46123 --- /dev/null +++ b/svtools/source/control/accessibleruler.hxx @@ -0,0 +1,188 @@ +/* -*- 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 . + */ +#ifndef INCLUDED_SVTOOLS_ACCESSIBLERULER_HXX +#define INCLUDED_SVTOOLS_ACCESSIBLERULER_HXX + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/interfacecontainer.h> +#include <cppuhelper/compbase5.hxx> +#include <cppuhelper/basemutex.hxx> +#include <vcl/vclptr.hxx> + +namespace tools { class Rectangle; } +class Ruler; + + +typedef ::cppu::WeakAggComponentImplHelper5< + css::accessibility::XAccessible, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleEventBroadcaster, + css::lang::XServiceInfo > + SvtRulerAccessible_Base; + +class SvtRulerAccessible final : public ::cppu::BaseMutex, public SvtRulerAccessible_Base +{ +public: + //===== internal ======================================================== + SvtRulerAccessible( + const css::uno::Reference< css::accessibility::XAccessible>& rxParent, Ruler& rRepresentation, const OUString& rName ); + + /// @throws css::uno::RuntimeException + bool + isVisible(); + + //===== XAccessible ===================================================== + + virtual css::uno::Reference< css::accessibility::XAccessibleContext> SAL_CALL + getAccessibleContext() override; + + //===== XAccessibleComponent ============================================ + + virtual sal_Bool SAL_CALL + containsPoint( const css::awt::Point& rPoint ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL + getAccessibleAtPoint( const css::awt::Point& rPoint ) override; + + virtual css::awt::Rectangle SAL_CALL + getBounds() override; + + virtual css::awt::Point SAL_CALL + getLocation() override; + + virtual css::awt::Point SAL_CALL + getLocationOnScreen() override; + + virtual css::awt::Size SAL_CALL + getSize() override; + + virtual void SAL_CALL + grabFocus() override; + + virtual sal_Int32 SAL_CALL + getForeground( ) override; + virtual sal_Int32 SAL_CALL + getBackground( ) override; + + //===== XAccessibleContext ============================================== + + virtual sal_Int32 SAL_CALL + getAccessibleChildCount() override; + + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild( sal_Int32 nIndex ) override; + + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleParent() override; + + virtual sal_Int32 SAL_CALL + getAccessibleIndexInParent() override; + + virtual sal_Int16 SAL_CALL + getAccessibleRole() override; + + virtual OUString SAL_CALL + getAccessibleDescription() override; + + virtual OUString SAL_CALL + getAccessibleName() override; + + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL + getAccessibleRelationSet() override; + + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL + getAccessibleStateSet() override; + + virtual css::lang::Locale SAL_CALL + getLocale() override; + //===== XAccessibleEventBroadcaster ===================================== + + virtual void SAL_CALL + addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + virtual void SAL_CALL + removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + //===== XServiceInfo ==================================================== + + virtual OUString SAL_CALL + getImplementationName() override; + + virtual sal_Bool SAL_CALL + supportsService( const OUString& sServiceName ) override; + + virtual css::uno::Sequence< OUString> SAL_CALL + getSupportedServiceNames() override; + + //===== XTypeProvider =================================================== + + virtual css::uno::Sequence<sal_Int8> SAL_CALL + getImplementationId() override; + +private: + + virtual ~SvtRulerAccessible() override; + + virtual void SAL_CALL disposing() override; + + /// @returns true if it's disposed or in disposing + inline bool IsAlive() const; + + /// @throws DisposedException if it's not alive + void ThrowExceptionIfNotAlive(); + + /// @Return the object's current bounding box relative to the desktop. + /// + /// @throws css::uno::RuntimeException + tools::Rectangle GetBoundingBoxOnScreen(); + + /// @Return the object's current bounding box relative to the parent object. + /// + /// @throws css::uno::RuntimeException + tools::Rectangle GetBoundingBox(); + + /// Name of this object. + OUString msName; + + /// Reference to the parent object. + css::uno::Reference< css::accessibility::XAccessible > + mxParent; + + /// pointer to internal representation + VclPtr<Ruler> mpRepr; + + /// client id in the AccessibleEventNotifier queue + sal_uInt32 mnClientId; +}; + +inline bool SvtRulerAccessible::IsAlive() const +{ + return !rBHelper.bDisposed && !rBHelper.bInDispose; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/asynclink.cxx b/svtools/source/control/asynclink.cxx new file mode 100644 index 000000000..31a7b28e7 --- /dev/null +++ b/svtools/source/control/asynclink.cxx @@ -0,0 +1,100 @@ +/* -*- 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 <svtools/asynclink.hxx> +#include <osl/mutex.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <vcl/timer.hxx> +#include <vcl/idle.hxx> +#include <vcl/svapp.hxx> + + +namespace svtools { + +void AsynchronLink::CreateMutex() +{ + if( !_pMutex ) _pMutex.reset( new osl::Mutex ); +} + +void AsynchronLink::Call( void* pObj, bool bAllowDoubles ) +{ + SAL_INFO_IF( !_bInCall, "svtools", "Recursives Call. Eher ueber Timer. TLX Fragen" ); // Do NOT translate. This is a valuable historical artefact. + if( _aLink.IsSet() ) + { + _pArg = pObj; + DBG_ASSERT( bAllowDoubles || !_nEventId, "Already made a call" ); + ClearPendingCall(); + if( _pMutex ) _pMutex->acquire(); + _nEventId = Application::PostUserEvent( LINK( this, AsynchronLink, HandleCall_PostUserEvent) ); + if( _pMutex ) _pMutex->release(); + } +} + +AsynchronLink::~AsynchronLink() +{ + if( _nEventId ) + { + Application::RemoveUserEvent( _nEventId ); + } + if( _pDeleted ) *_pDeleted = true; + _pMutex.reset(); +} + +IMPL_LINK_NOARG( AsynchronLink, HandleCall_Idle, Timer*, void ) +{ + if( _pMutex ) _pMutex->acquire(); + _nEventId = nullptr; + if( _pMutex ) _pMutex->release(); + Call_Impl( _pArg ); +} + +IMPL_LINK_NOARG( AsynchronLink, HandleCall_PostUserEvent, void*, void ) +{ + HandleCall_Idle(nullptr); +} + +void AsynchronLink::ClearPendingCall() +{ + if( _pMutex ) _pMutex->acquire(); + if( _nEventId ) + { + Application::RemoveUserEvent( _nEventId ); + _nEventId = nullptr; + } + if( _pMutex ) _pMutex->release(); +} + +void AsynchronLink::Call_Impl( void* pArg ) +{ + _bInCall = true; + bool bDeleted = false; + _pDeleted = &bDeleted; + _aLink.Call( pArg ); + if( !bDeleted ) + { + _bInCall = false; + _pDeleted = nullptr; + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/calendar.cxx b/svtools/source/control/calendar.cxx new file mode 100644 index 000000000..d1b7f5d11 --- /dev/null +++ b/svtools/source/control/calendar.cxx @@ -0,0 +1,346 @@ +/* -*- 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 <vcl/floatwin.hxx> +#include <vcl/button.hxx> +#include <vcl/fixed.hxx> +#include <vcl/event.hxx> + +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <svtools/calendar.hxx> + +#define CALFIELD_EXTRA_BUTTON_WIDTH 14 +#define CALFIELD_EXTRA_BUTTON_HEIGHT 8 +#define CALFIELD_SEP_X 6 +#define CALFIELD_BORDERLINE_X 5 +#define CALFIELD_BORDER_YTOP 4 +#define CALFIELD_BORDER_Y 5 + +class ImplCFieldFloatWin : public FloatingWindow +{ +private: + VclPtr<Calendar> mpCalendar; + VclPtr<PushButton> mpTodayBtn; + VclPtr<PushButton> mpNoneBtn; + VclPtr<FixedLine> mpFixedLine; + +public: + explicit ImplCFieldFloatWin( vcl::Window* pParent ); + virtual ~ImplCFieldFloatWin() override; + virtual void dispose() override; + + void SetCalendar( Calendar* pCalendar ) + { mpCalendar = pCalendar; } + + PushButton* EnableTodayBtn( bool bEnable ); + PushButton* EnableNoneBtn( bool bEnable ); + void ArrangeButtons(); + + virtual bool EventNotify( NotifyEvent& rNEvt ) override; +}; + +ImplCFieldFloatWin::ImplCFieldFloatWin( vcl::Window* pParent ) : + FloatingWindow( pParent, WB_BORDER | WB_SYSTEMWINDOW | WB_NOSHADOW ), + mpCalendar(nullptr), mpTodayBtn(nullptr), mpNoneBtn(nullptr), mpFixedLine(nullptr) +{ +} + +ImplCFieldFloatWin::~ImplCFieldFloatWin() +{ + disposeOnce(); +} + +void ImplCFieldFloatWin::dispose() +{ + mpTodayBtn.disposeAndClear(); + mpNoneBtn.disposeAndClear(); + mpFixedLine.disposeAndClear(); + mpCalendar.clear(); + FloatingWindow::dispose(); +} + +PushButton* ImplCFieldFloatWin::EnableTodayBtn( bool bEnable ) +{ + if ( bEnable ) + { + if ( !mpTodayBtn ) + { + mpTodayBtn = VclPtr<PushButton>::Create( this, WB_NOPOINTERFOCUS ); + OUString aTodayText(SvtResId(STR_SVT_CALENDAR_TODAY)); + mpTodayBtn->SetText( aTodayText ); + Size aSize; + aSize.setWidth( mpTodayBtn->GetCtrlTextWidth( mpTodayBtn->GetText() ) ); + aSize.setHeight( mpTodayBtn->GetTextHeight() ); + aSize.AdjustWidth(CALFIELD_EXTRA_BUTTON_WIDTH ); + aSize.AdjustHeight(CALFIELD_EXTRA_BUTTON_HEIGHT ); + mpTodayBtn->SetSizePixel( aSize ); + mpTodayBtn->Show(); + } + } + else + { + mpTodayBtn.disposeAndClear(); + } + + return mpTodayBtn; +} + +PushButton* ImplCFieldFloatWin::EnableNoneBtn( bool bEnable ) +{ + if ( bEnable ) + { + if ( !mpNoneBtn ) + { + mpNoneBtn = VclPtr<PushButton>::Create( this, WB_NOPOINTERFOCUS ); + OUString aNoneText(SvtResId(STR_SVT_CALENDAR_NONE)); + mpNoneBtn->SetText( aNoneText ); + Size aSize; + aSize.setWidth( mpNoneBtn->GetCtrlTextWidth( mpNoneBtn->GetText() ) ); + aSize.setHeight( mpNoneBtn->GetTextHeight() ); + aSize.AdjustWidth(CALFIELD_EXTRA_BUTTON_WIDTH ); + aSize.AdjustHeight(CALFIELD_EXTRA_BUTTON_HEIGHT ); + mpNoneBtn->SetSizePixel( aSize ); + mpNoneBtn->Show(); + } + } + else + { + mpNoneBtn.disposeAndClear(); + } + + return mpNoneBtn; +} + +void ImplCFieldFloatWin::ArrangeButtons() +{ + long nBtnHeight = 0; + long nBtnWidth = 0; + Size aOutSize = GetOutputSizePixel(); + if ( mpTodayBtn && mpNoneBtn ) + { + Size aTodayBtnSize = mpTodayBtn->GetSizePixel(); + Size aNoneBtnSize = mpNoneBtn->GetSizePixel(); + if ( aTodayBtnSize.Width() < aNoneBtnSize.Width() ) + aTodayBtnSize.setWidth( aNoneBtnSize.Width() ); + else + aNoneBtnSize.setWidth( aTodayBtnSize.Width() ); + if ( aTodayBtnSize.Height() < aNoneBtnSize.Height() ) + aTodayBtnSize.setHeight( aNoneBtnSize.Height() ); + else + aNoneBtnSize.setHeight( aTodayBtnSize.Height() ); + + nBtnWidth = aTodayBtnSize.Width() + aNoneBtnSize.Width() + CALFIELD_SEP_X; + nBtnHeight = aTodayBtnSize.Height(); + long nX = (aOutSize.Width()-nBtnWidth)/2; + long nY = aOutSize.Height()+CALFIELD_BORDER_Y+CALFIELD_BORDER_YTOP; + mpTodayBtn->SetPosSizePixel( Point( nX, nY ), aTodayBtnSize ); + nX += aTodayBtnSize.Width() + CALFIELD_SEP_X; + mpNoneBtn->SetPosSizePixel( Point( nX, nY ), aNoneBtnSize ); + } + else if ( mpTodayBtn ) + { + Size aTodayBtnSize = mpTodayBtn->GetSizePixel(); + nBtnWidth = aTodayBtnSize.Width(); + nBtnHeight = aTodayBtnSize.Height(); + mpTodayBtn->SetPosPixel( Point( (aOutSize.Width()-nBtnWidth)/2, aOutSize.Height()+CALFIELD_BORDER_Y+CALFIELD_BORDER_YTOP ) ); + } + else if ( mpNoneBtn ) + { + Size aNoneBtnSize = mpNoneBtn->GetSizePixel(); + nBtnWidth = aNoneBtnSize.Width(); + nBtnHeight = aNoneBtnSize.Height(); + mpNoneBtn->SetPosPixel( Point( (aOutSize.Width()-nBtnWidth)/2, aOutSize.Height()+CALFIELD_BORDER_Y+CALFIELD_BORDER_YTOP ) ); + } + + if ( nBtnHeight ) + { + if ( !mpFixedLine ) + { + mpFixedLine = VclPtr<FixedLine>::Create( this ); + mpFixedLine->Show(); + } + long nLineWidth = aOutSize.Width()-(CALFIELD_BORDERLINE_X*2); + mpFixedLine->setPosSizePixel( (aOutSize.Width()-nLineWidth)/2, aOutSize.Height()+((CALFIELD_BORDER_YTOP-2)/2), + nLineWidth, 2 ); + aOutSize.AdjustHeight(nBtnHeight + (CALFIELD_BORDER_Y*2) + CALFIELD_BORDER_YTOP ); + SetOutputSizePixel( aOutSize ); + } + else + { + mpFixedLine.disposeAndClear(); + } +} + +bool ImplCFieldFloatWin::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + const KeyEvent* pKEvt = rNEvt.GetKeyEvent(); + if ( pKEvt->GetKeyCode().GetCode() == KEY_RETURN ) + mpCalendar->Select(); + } + + return FloatingWindow::EventNotify( rNEvt ); +} + +CalendarField::CalendarField(vcl::Window* pParent, WinBits nWinStyle) + : DateField(pParent, nWinStyle) + , mpFloatWin(nullptr) + , mpCalendar(nullptr) + , mpTodayBtn(nullptr) + , mpNoneBtn(nullptr) + , mbToday(false) + , mbNone(false) +{ +} + +CalendarField::~CalendarField() +{ + disposeOnce(); +} + +void CalendarField::dispose() +{ + mpCalendar.disposeAndClear(); + mpFloatWin.disposeAndClear(); + mpTodayBtn.clear(); + mpNoneBtn.clear(); + DateField::dispose(); +} + +IMPL_LINK( CalendarField, ImplSelectHdl, Calendar*, pCalendar, void ) +{ + if ( pCalendar->IsTravelSelect() ) + return; + + mpFloatWin->EndPopupMode(); + EndDropDown(); + GrabFocus(); + Date aNewDate = mpCalendar->GetFirstSelectedDate(); + if ( IsEmptyDate() || ( aNewDate != GetDate() ) ) + { + SetDate( aNewDate ); + SetModifyFlag(); + Modify(); + } +} + +IMPL_LINK( CalendarField, ImplClickHdl, Button*, pButton, void ) +{ + PushButton* pBtn = static_cast<PushButton*>(pButton); + mpFloatWin->EndPopupMode(); + EndDropDown(); + GrabFocus(); + + if ( pBtn == mpTodayBtn ) + { + Date aToday( Date::SYSTEM ); + if ( (aToday != GetDate()) || IsEmptyDate() ) + { + SetDate( aToday ); + SetModifyFlag(); + Modify(); + } + } + else if ( pBtn == mpNoneBtn ) + { + if ( !IsEmptyDate() ) + { + SetEmptyDate(); + SetModifyFlag(); + Modify(); + } + } +} + +IMPL_LINK_NOARG(CalendarField, ImplPopupModeEndHdl, FloatingWindow*, void) +{ + EndDropDown(); + GrabFocus(); + mpCalendar->EndSelection(); +} + +bool CalendarField::ShowDropDown( bool bShow ) +{ + if ( bShow ) + { + Calendar* pCalendar = GetCalendar(); + + Date aDate = GetDate(); + if ( IsEmptyDate() || !aDate.IsValidAndGregorian() ) + { + aDate = Date( Date::SYSTEM ); + } + pCalendar->SetCurDate( aDate ); + Point aPos( GetParent()->OutputToScreenPixel( GetPosPixel() ) ); + tools::Rectangle aRect( aPos, GetSizePixel() ); + aRect.AdjustBottom( -1 ); + mpCalendar->SetOutputSizePixel( mpCalendar->CalcWindowSizePixel() ); + mpFloatWin->SetOutputSizePixel( mpCalendar->GetSizePixel() ); + mpFloatWin->SetCalendar( mpCalendar ); + mpTodayBtn = mpFloatWin->EnableTodayBtn( mbToday ); + mpNoneBtn = mpFloatWin->EnableNoneBtn( mbNone ); + if ( mpTodayBtn ) + mpTodayBtn->SetClickHdl( LINK( this, CalendarField, ImplClickHdl ) ); + if ( mpNoneBtn ) + mpNoneBtn->SetClickHdl( LINK( this, CalendarField, ImplClickHdl ) ); + mpFloatWin->ArrangeButtons(); + mpCalendar->EnableCallEverySelect(); + mpCalendar->StartSelection(); + mpCalendar->GrabFocus(); + mpCalendar->Show(); + mpFloatWin->StartPopupMode( aRect, FloatWinPopupFlags::Down ); + } + else + { + mpFloatWin->EndPopupMode( FloatWinPopupEndFlags::Cancel ); + mpCalendar->EndSelection(); + EndDropDown(); + } + return true; +} + +Calendar* CalendarField::GetCalendar() +{ + if ( !mpFloatWin ) + { + mpFloatWin = VclPtr<ImplCFieldFloatWin>::Create( this ); + mpFloatWin->SetPopupModeEndHdl( LINK( this, CalendarField, ImplPopupModeEndHdl ) ); + mpCalendar = VclPtr<Calendar>::Create( mpFloatWin, WB_TABSTOP ); + mpCalendar->SetPosPixel( Point() ); + mpCalendar->SetSelectHdl( LINK( this, CalendarField, ImplSelectHdl ) ); + } + + return mpCalendar; +} + +void CalendarField::StateChanged( StateChangedType nStateChange ) +{ + DateField::StateChanged( nStateChange ); + + if ( ( nStateChange == StateChangedType::Style ) && GetSubEdit() ) + { + WinBits nAllAlignmentBits = ( WB_LEFT | WB_CENTER | WB_RIGHT | WB_TOP | WB_VCENTER | WB_BOTTOM ); + WinBits nMyAlignment = GetStyle() & nAllAlignmentBits; + GetSubEdit()->SetStyle( ( GetSubEdit()->GetStyle() & ~nAllAlignmentBits ) | nMyAlignment ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/collatorres.cxx b/svtools/source/control/collatorres.cxx new file mode 100644 index 000000000..c2db6b4e9 --- /dev/null +++ b/svtools/source/control/collatorres.cxx @@ -0,0 +1,66 @@ +/* -*- 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 <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/collatorres.hxx> + +// implementation of the collator-algorithm-name translation +CollatorResource::CollatorResource() +{ + m_aData.emplace_back("alphanumeric", SvtResId(STR_SVT_COLLATE_ALPHANUMERIC)); + m_aData.emplace_back("charset", SvtResId(STR_SVT_COLLATE_CHARSET)); + m_aData.emplace_back("dict", SvtResId(STR_SVT_COLLATE_DICTIONARY)); + m_aData.emplace_back("normal", SvtResId(STR_SVT_COLLATE_NORMAL)); + m_aData.emplace_back("pinyin", SvtResId(STR_SVT_COLLATE_PINYIN)); + m_aData.emplace_back("radical", SvtResId(STR_SVT_COLLATE_RADICAL)); + m_aData.emplace_back("stroke", SvtResId(STR_SVT_COLLATE_STROKE)); + m_aData.emplace_back("unicode", SvtResId(STR_SVT_COLLATE_UNICODE)); + m_aData.emplace_back("zhuyin", SvtResId(STR_SVT_COLLATE_ZHUYIN)); + m_aData.emplace_back("phonebook", SvtResId(STR_SVT_COLLATE_PHONEBOOK)); + m_aData.emplace_back("phonetic (alphanumeric first)", SvtResId(STR_SVT_COLLATE_PHONETIC_F)); + m_aData.emplace_back("phonetic (alphanumeric last)", SvtResId(STR_SVT_COLLATE_PHONETIC_L)); +} + +const OUString& +CollatorResource::GetTranslation(const OUString &r_Algorithm) +{ + sal_Int32 nIndex = r_Algorithm.indexOf('.'); + OUString aLocaleFreeAlgorithm; + + if (nIndex == -1) + { + aLocaleFreeAlgorithm = r_Algorithm; + } + else + { + nIndex += 1; + aLocaleFreeAlgorithm = r_Algorithm.copy(nIndex); + } + + for (size_t i = 0; i < m_aData.size(); ++i) + { + if (aLocaleFreeAlgorithm == m_aData[i].GetAlgorithm()) + return m_aData[i].GetTranslation(); + } + + return r_Algorithm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/ctrlbox.cxx b/svtools/source/control/ctrlbox.cxx new file mode 100644 index 000000000..4a57f2e5b --- /dev/null +++ b/svtools/source/control/ctrlbox.cxx @@ -0,0 +1,1663 @@ +/* -*- 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 <config_folders.h> + +#include <comphelper/lok.hxx> +#include <i18nutil/unicode.hxx> +#include <tools/stream.hxx> +#include <vcl/builder.hxx> +#include <vcl/customweld.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <vcl/fieldvalues.hxx> +#include <vcl/settings.hxx> +#include <vcl/image.hxx> +#include <vcl/virdev.hxx> +#include <rtl/math.hxx> +#include <sal/macros.h> +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <unotools/charclass.hxx> +#include <unotools/fontoptions.hxx> +#include <unotools/localedatawrapper.hxx> + +#include <svtools/borderline.hxx> +#include <svtools/sampletext.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/ctrlbox.hxx> +#include <svtools/ctrltool.hxx> +#include <svtools/borderhelper.hxx> +#include <svtools/valueset.hxx> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <editeng/borderline.hxx> + +#include <rtl/bootstrap.hxx> + +#include <boost/property_tree/ptree.hpp> + +#include <borderline.hrc> + +#include <stdio.h> + +#define IMGOUTERTEXTSPACE 5 +#define EXTRAFONTSIZE 5 +#define GAPTOEXTRAPREVIEW 10 +#define MINGAPWIDTH 2 + +#define FONTNAMEBOXMRUENTRIESFILE "/user/config/fontnameboxmruentries" + + +BorderWidthImpl::BorderWidthImpl( BorderWidthImplFlags nFlags, double nRate1, double nRate2, double nRateGap ): + m_nFlags( nFlags ), + m_nRate1( nRate1 ), + m_nRate2( nRate2 ), + m_nRateGap( nRateGap ) +{ +} + +bool BorderWidthImpl::operator== ( const BorderWidthImpl& r ) const +{ + return ( m_nFlags == r.m_nFlags ) && + ( m_nRate1 == r.m_nRate1 ) && + ( m_nRate2 == r.m_nRate2 ) && + ( m_nRateGap == r.m_nRateGap ); +} + +long BorderWidthImpl::GetLine1( long nWidth ) const +{ + long result = static_cast<long>(m_nRate1); + if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 ) + { + long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2; + long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap; + result = std::max<long>(0, + static_cast<long>((m_nRate1 * nWidth) + 0.5) + - (nConstant2 + nConstantD)); + if (result == 0 && m_nRate1 > 0.0 && nWidth > 0) + { // fdo#51777: hack to essentially treat 1 twip DOUBLE border + result = 1; // as 1 twip SINGLE border + } + } + return result; +} + +long BorderWidthImpl::GetLine2( long nWidth ) const +{ + long result = static_cast<long>(m_nRate2); + if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) + { + long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1; + long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap; + result = std::max<long>(0, + static_cast<long>((m_nRate2 * nWidth) + 0.5) + - (nConstant1 + nConstantD)); + } + return result; +} + +long BorderWidthImpl::GetGap( long nWidth ) const +{ + long result = static_cast<long>(m_nRateGap); + if ( m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) + { + long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1; + long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2; + result = std::max<long>(0, + static_cast<long>((m_nRateGap * nWidth) + 0.5) + - (nConstant1 + nConstant2)); + } + + // Avoid having too small distances (less than 0.1pt) + if ( result < MINGAPWIDTH && m_nRate1 > 0 && m_nRate2 > 0 ) + result = MINGAPWIDTH; + + return result; +} + +static double lcl_getGuessedWidth( long nTested, double nRate, bool bChanging ) +{ + double nWidth = -1.0; + if ( bChanging ) + nWidth = double( nTested ) / nRate; + else + { + if ( rtl::math::approxEqual(double( nTested ), nRate) ) + nWidth = nRate; + } + + return nWidth; +} + +long BorderWidthImpl::GuessWidth( long nLine1, long nLine2, long nGap ) +{ + std::vector< double > aToCompare; + bool bInvalid = false; + + bool bLine1Change = bool( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 ); + double nWidth1 = lcl_getGuessedWidth( nLine1, m_nRate1, bLine1Change ); + if ( bLine1Change ) + aToCompare.push_back( nWidth1 ); + else if (nWidth1 < 0) + bInvalid = true; + + bool bLine2Change = bool( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2 ); + double nWidth2 = lcl_getGuessedWidth( nLine2, m_nRate2, bLine2Change ); + if ( bLine2Change ) + aToCompare.push_back( nWidth2 ); + else if (nWidth2 < 0) + bInvalid = true; + + bool bGapChange = bool( m_nFlags & BorderWidthImplFlags::CHANGE_DIST ); + double nWidthGap = lcl_getGuessedWidth( nGap, m_nRateGap, bGapChange ); + if ( bGapChange && nGap >= MINGAPWIDTH ) + aToCompare.push_back( nWidthGap ); + else if ( !bGapChange && nWidthGap < 0 ) + bInvalid = true; + + // non-constant line width factors must sum to 1 + assert((((bLine1Change) ? m_nRate1 : 0) + + ((bLine2Change) ? m_nRate2 : 0) + + ((bGapChange) ? m_nRateGap : 0)) - 1.0 < 0.00001 ); + + double nWidth = 0.0; + if ( (!bInvalid) && (!aToCompare.empty()) ) + { + nWidth = *aToCompare.begin(); + for (auto const& elem : aToCompare) + { + bInvalid = ( nWidth != elem ); + if (bInvalid) + break; + } + nWidth = bInvalid ? 0.0 : nLine1 + nLine2 + nGap; + } + + return nWidth; +} + +static void lclDrawPolygon( OutputDevice& rDev, const basegfx::B2DPolygon& rPolygon, long nWidth, SvxBorderLineStyle nDashing ) +{ + AntialiasingFlags nOldAA = rDev.GetAntialiasing(); + rDev.SetAntialiasing( nOldAA & ~AntialiasingFlags::EnableB2dDraw ); + + long nPix = rDev.PixelToLogic(Size(1, 1)).Width(); + basegfx::B2DPolyPolygon aPolygons = svtools::ApplyLineDashing(rPolygon, nDashing, nPix); + + // Handle problems of width 1px in Pixel mode: 0.5px gives a 1px line + if (rDev.GetMapMode().GetMapUnit() == MapUnit::MapPixel && nWidth == nPix) + nWidth = 0; + + for ( sal_uInt32 i = 0; i < aPolygons.count( ); i++ ) + { + const basegfx::B2DPolygon& aDash = aPolygons.getB2DPolygon( i ); + basegfx::B2DPoint aStart = aDash.getB2DPoint( 0 ); + basegfx::B2DPoint aEnd = aDash.getB2DPoint( aDash.count() - 1 ); + + basegfx::B2DVector aVector( aEnd - aStart ); + aVector.normalize( ); + const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector)); + + const basegfx::B2DVector aWidthOffset( double( nWidth ) / 2 * aPerpendicular); + basegfx::B2DPolygon aDashPolygon; + aDashPolygon.append( aStart + aWidthOffset ); + aDashPolygon.append( aEnd + aWidthOffset ); + aDashPolygon.append( aEnd - aWidthOffset ); + aDashPolygon.append( aStart - aWidthOffset ); + aDashPolygon.setClosed( true ); + + rDev.DrawPolygon( aDashPolygon ); + } + + rDev.SetAntialiasing( nOldAA ); +} + +namespace svtools { + +/** + * Dashing array must start with a line width and end with a blank width. + */ +static std::vector<double> GetDashing( SvxBorderLineStyle nDashing ) +{ + std::vector<double> aPattern; + switch (nDashing) + { + case SvxBorderLineStyle::DOTTED: + aPattern.push_back( 1.0 ); // line + aPattern.push_back( 2.0 ); // blank + break; + case SvxBorderLineStyle::DASHED: + aPattern.push_back( 16.0 ); // line + aPattern.push_back( 5.0 ); // blank + break; + case SvxBorderLineStyle::FINE_DASHED: + aPattern.push_back( 6.0 ); // line + aPattern.push_back( 2.0 ); // blank + break; + case SvxBorderLineStyle::DASH_DOT: + aPattern.push_back( 16.0 ); // line + aPattern.push_back( 5.0 ); // blank + aPattern.push_back( 5.0 ); // line + aPattern.push_back( 5.0 ); // blank + break; + case SvxBorderLineStyle::DASH_DOT_DOT: + aPattern.push_back( 16.0 ); // line + aPattern.push_back( 5.0 ); // blank + aPattern.push_back( 5.0 ); // line + aPattern.push_back( 5.0 ); // blank + aPattern.push_back( 5.0 ); // line + aPattern.push_back( 5.0 ); // blank + break; + default: + ; + } + + return aPattern; +} + +namespace { + +class ApplyScale +{ + double mfScale; +public: + explicit ApplyScale( double fScale ) : mfScale(fScale) {} + void operator() ( double& rVal ) + { + rVal *= mfScale; + } +}; + +} + +std::vector<double> GetLineDashing( SvxBorderLineStyle nDashing, double fScale ) +{ + std::vector<double> aPattern = GetDashing(nDashing); + std::for_each(aPattern.begin(), aPattern.end(), ApplyScale(fScale)); + return aPattern; +} + +basegfx::B2DPolyPolygon ApplyLineDashing( const basegfx::B2DPolygon& rPolygon, SvxBorderLineStyle nDashing, double fScale ) +{ + std::vector<double> aPattern = GetDashing(nDashing); + std::for_each(aPattern.begin(), aPattern.end(), ApplyScale(fScale)); + + basegfx::B2DPolyPolygon aPolygons; + + if (aPattern.empty()) + aPolygons.append(rPolygon); + else + basegfx::utils::applyLineDashing(rPolygon, aPattern, &aPolygons); + + return aPolygons; +} + +void DrawLine( OutputDevice& rDev, const Point& rP1, const Point& rP2, + sal_uInt32 nWidth, SvxBorderLineStyle nDashing ) +{ + DrawLine( rDev, basegfx::B2DPoint( rP1.X(), rP1.Y() ), + basegfx::B2DPoint( rP2.X(), rP2.Y( ) ), nWidth, nDashing ); +} + +void DrawLine( OutputDevice& rDev, const basegfx::B2DPoint& rP1, const basegfx::B2DPoint& rP2, + sal_uInt32 nWidth, SvxBorderLineStyle nDashing ) +{ + basegfx::B2DPolygon aPolygon; + aPolygon.append( rP1 ); + aPolygon.append( rP2 ); + lclDrawPolygon( rDev, aPolygon, nWidth, nDashing ); +} + +} + +static Size gUserItemSz; +static int gFontNameBoxes; +static size_t gPreviewsPerDevice; +static std::vector<VclPtr<VirtualDevice>> gFontPreviewVirDevs; +static std::vector<OUString> gRenderedFontNames; + +FontNameBox::FontNameBox(std::unique_ptr<weld::ComboBox> p) + : m_xComboBox(std::move(p)) + , mnPreviewProgress(0) + , mbWYSIWYG(false) + , maUpdateIdle("FontNameBox Preview Update") +{ + ++gFontNameBoxes; + InitFontMRUEntriesFile(); + + maUpdateIdle.SetPriority(TaskPriority::LOWEST); + maUpdateIdle.SetInvokeHandler(LINK(this, FontNameBox, UpdateHdl)); +} + +FontNameBox::~FontNameBox() +{ + if (mpFontList) + { + SaveMRUEntries (maFontMRUEntriesFile); + ImplDestroyFontList(); + } + --gFontNameBoxes; + if (!gFontNameBoxes) + { + for (auto &rDev : gFontPreviewVirDevs) + rDev.disposeAndClear(); + gFontPreviewVirDevs.clear(); + gRenderedFontNames.clear(); + } +} + +void FontNameBox::SaveMRUEntries(const OUString& aFontMRUEntriesFile) const +{ + OString aEntries(OUStringToOString(m_xComboBox->get_mru_entries(), + RTL_TEXTENCODING_UTF8)); + + if (aEntries.isEmpty() || aFontMRUEntriesFile.isEmpty()) + return; + + SvFileStream aStream; + aStream.Open( aFontMRUEntriesFile, StreamMode::WRITE | StreamMode::TRUNC ); + if( ! (aStream.IsOpen() && aStream.IsWritable()) ) + { + SAL_INFO("svtools.control", "FontNameBox::SaveMRUEntries: opening mru entries file " << aFontMRUEntriesFile << " failed"); + return; + } + + aStream.SetLineDelimiter( LINEEND_LF ); + aStream.WriteLine( aEntries ); + aStream.WriteLine( OString() ); +} + +void FontNameBox::LoadMRUEntries( const OUString& aFontMRUEntriesFile ) +{ + if (aFontMRUEntriesFile.isEmpty()) + return; + + SvtFontOptions aFontOpt; + if (!aFontOpt.IsFontHistoryEnabled()) + return; + + SvFileStream aStream( aFontMRUEntriesFile, StreamMode::READ ); + if( ! aStream.IsOpen() ) + { + SAL_INFO("svtools.control", "FontNameBox::LoadMRUEntries: opening mru entries file " << aFontMRUEntriesFile << " failed"); + return; + } + + OString aLine; + aStream.ReadLine( aLine ); + OUString aEntries = OStringToOUString(aLine, + RTL_TEXTENCODING_UTF8); + m_xComboBox->set_mru_entries(aEntries); +} + +void FontNameBox::InitFontMRUEntriesFile() +{ + OUString sUserConfigDir("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"); + rtl::Bootstrap::expandMacros(sUserConfigDir); + + maFontMRUEntriesFile = sUserConfigDir; + if( !maFontMRUEntriesFile.isEmpty() ) + { + maFontMRUEntriesFile += FONTNAMEBOXMRUENTRIESFILE; + } +} + +void FontNameBox::ImplDestroyFontList() +{ + mpFontList.reset(); + mnPreviewProgress = 0; + maUpdateIdle.Stop(); +} + +void FontNameBox::Fill( const FontList* pList ) +{ + // store old text and clear box + OUString aOldText = m_xComboBox->get_active_text(); + OUString rEntries = m_xComboBox->get_mru_entries(); + bool bLoadFromFile = rEntries.isEmpty(); + m_xComboBox->freeze(); + m_xComboBox->clear(); + + ImplDestroyFontList(); + mpFontList.reset(new ImplFontList); + + // insert fonts + size_t nFontCount = pList->GetFontNameCount(); + for (size_t i = 0; i < nFontCount; ++i) + { + const FontMetric& rFontMetric = pList->GetFontName(i); + m_xComboBox->append(OUString::number(i), rFontMetric.GetFamilyName()); + mpFontList->push_back(rFontMetric); + } + + if (bLoadFromFile) + LoadMRUEntries(maFontMRUEntriesFile); + else + m_xComboBox->set_mru_entries(rEntries); + + m_xComboBox->thaw(); + + if (mbWYSIWYG && nFontCount) + { + assert(mnPreviewProgress == 0 && "ImplDestroyFontList wasn't called"); + maUpdateIdle.Start(); + } + + // restore text + if (!aOldText.isEmpty()) + set_active_or_entry_text(aOldText); +} + +void FontNameBox::EnableWYSIWYG(bool bEnable) +{ + if (comphelper::LibreOfficeKit::isActive()) + return; + if (mbWYSIWYG == bEnable) + return; + mbWYSIWYG = bEnable; + + static bool bGlobalsInited; + if (mbWYSIWYG && !bGlobalsInited) + { + gUserItemSz = Size(m_xComboBox->get_approximate_digit_width() * 52, m_xComboBox->get_text_height()); + gUserItemSz.setHeight(gUserItemSz.Height() * 16); + gUserItemSz.setHeight(gUserItemSz.Height() / 10); + + size_t nMaxDeviceHeight = SAL_MAX_INT16 / 2; // see limitXCreatePixmap + gPreviewsPerDevice = nMaxDeviceHeight / gUserItemSz.Height(); + + bGlobalsInited = true; + } + + if (mbWYSIWYG) + { + m_xComboBox->connect_custom_get_size(LINK(this, FontNameBox, CustomGetSizeHdl)); + m_xComboBox->connect_custom_render(LINK(this, FontNameBox, CustomRenderHdl)); + } + else + { + m_xComboBox->connect_custom_get_size(Link<OutputDevice&, Size>()); + m_xComboBox->connect_custom_render(Link<weld::ComboBox::render_args, void>()); + } + m_xComboBox->set_custom_renderer(mbWYSIWYG); +} + +IMPL_STATIC_LINK_NOARG(FontNameBox, CustomGetSizeHdl, OutputDevice&, Size) +{ + return gUserItemSz; +} + +namespace +{ + long shrinkFontToFit(OUString const &rSampleText, long nH, vcl::Font &rFont, OutputDevice &rDevice, tools::Rectangle &rTextRect) + { + long nWidth = 0; + + Size aSize( rFont.GetFontSize() ); + + //Make sure it fits in the available height + while (aSize.Height() > 0) + { + if (!rDevice.GetTextBoundRect(rTextRect, rSampleText)) + break; + if (rTextRect.GetHeight() <= nH) + { + nWidth = rTextRect.GetWidth(); + break; + } + + aSize.AdjustHeight( -(EXTRAFONTSIZE) ); + rFont.SetFontSize(aSize); + rDevice.SetFont(rFont); + } + + return nWidth; + } +} + +IMPL_LINK_NOARG(FontNameBox, UpdateHdl, Timer*, void) +{ + CachePreview(mnPreviewProgress++, nullptr); + // tdf#132536 limit to ~25 pre-rendered for now. The font caches look + // b0rked, the massive charmaps are ~never swapped out, and don't count + // towards the size of a font in the font cache and if the freetype font + // cache size is set experimentally very low then we crash, so there's an + // awful lot to consider there. + if (mnPreviewProgress < std::min<size_t>(25, mpFontList->size())) + maUpdateIdle.Start(); +} + +static void DrawPreview(const FontMetric& rFontMetric, const Point& rTopLeft, OutputDevice& rDevice, bool bSelected) +{ + rDevice.Push(PushFlags::TEXTCOLOR); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + if (bSelected) + rDevice.SetTextColor(rStyleSettings.GetHighlightTextColor()); + else + rDevice.SetTextColor(rStyleSettings.GetDialogTextColor()); + + long nX = rTopLeft.X(); + long nH = gUserItemSz.Height(); + + nX += IMGOUTERTEXTSPACE; + + const bool bSymbolFont = isSymbolFont(rFontMetric); + + vcl::Font aOldFont(rDevice.GetFont()); + Size aSize( aOldFont.GetFontSize() ); + aSize.AdjustHeight(EXTRAFONTSIZE ); + vcl::Font aFont( rFontMetric ); + aFont.SetFontSize( aSize ); + rDevice.SetFont(aFont); + + bool bUsingCorrectFont = true; + tools::Rectangle aTextRect; + + // Preview the font name + const OUString& sFontName = rFontMetric.GetFamilyName(); + + //If it shouldn't or can't draw its own name because it doesn't have the glyphs + if (!canRenderNameOfSelectedFont(rDevice)) + bUsingCorrectFont = false; + else + { + //Make sure it fits in the available height, shrinking the font if necessary + bUsingCorrectFont = shrinkFontToFit(sFontName, nH, aFont, rDevice, aTextRect) != 0; + } + + if (!bUsingCorrectFont) + { + rDevice.SetFont(aOldFont); + rDevice.GetTextBoundRect(aTextRect, sFontName); + } + + long nTextHeight = aTextRect.GetHeight(); + long nDesiredGap = (nH-nTextHeight)/2; + long nVertAdjust = nDesiredGap - aTextRect.Top(); + Point aPos( nX, rTopLeft.Y() + nVertAdjust ); + rDevice.DrawText(aPos, sFontName); + long nTextX = aPos.X() + aTextRect.GetWidth() + GAPTOEXTRAPREVIEW; + + if (!bUsingCorrectFont) + rDevice.SetFont(aFont); + + OUString sSampleText; + + if (!bSymbolFont) + { + const bool bNameBeginsWithLatinText = rFontMetric.GetFamilyName()[0] <= 'z'; + + if (bNameBeginsWithLatinText || !bUsingCorrectFont) + sSampleText = makeShortRepresentativeTextForSelectedFont(rDevice); + } + + //If we're not a symbol font, but could neither render our own name and + //we can't determine what script it would like to render, then try a + //few well known scripts + if (sSampleText.isEmpty() && !bUsingCorrectFont) + { + static const UScriptCode aScripts[] = + { + USCRIPT_ARABIC, + USCRIPT_HEBREW, + + USCRIPT_BENGALI, + USCRIPT_GURMUKHI, + USCRIPT_GUJARATI, + USCRIPT_ORIYA, + USCRIPT_TAMIL, + USCRIPT_TELUGU, + USCRIPT_KANNADA, + USCRIPT_MALAYALAM, + USCRIPT_SINHALA, + USCRIPT_DEVANAGARI, + + USCRIPT_THAI, + USCRIPT_LAO, + USCRIPT_GEORGIAN, + USCRIPT_TIBETAN, + USCRIPT_SYRIAC, + USCRIPT_MYANMAR, + USCRIPT_ETHIOPIC, + USCRIPT_KHMER, + USCRIPT_MONGOLIAN, + + USCRIPT_KOREAN, + USCRIPT_JAPANESE, + USCRIPT_HAN, + USCRIPT_SIMPLIFIED_HAN, + USCRIPT_TRADITIONAL_HAN, + + USCRIPT_GREEK + }; + + for (const UScriptCode& rScript : aScripts) + { + OUString sText = makeShortRepresentativeTextForScript(rScript); + if (!sText.isEmpty()) + { + bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(aFont, sText)); + if (bHasSampleTextGlyphs) + { + sSampleText = sText; + break; + } + } + } + + static const UScriptCode aMinimalScripts[] = + { + USCRIPT_HEBREW, //e.g. biblical hebrew + USCRIPT_GREEK + }; + + for (const UScriptCode& rMinimalScript : aMinimalScripts) + { + OUString sText = makeShortMinimalTextForScript(rMinimalScript); + if (!sText.isEmpty()) + { + bool bHasSampleTextGlyphs = (-1 == rDevice.HasGlyphs(aFont, sText)); + if (bHasSampleTextGlyphs) + { + sSampleText = sText; + break; + } + } + } + } + + //If we're a symbol font, or for some reason the font still couldn't + //render something representative of what it would like to render then + //make up some semi-random text that it *can* display + if (bSymbolFont || (!bUsingCorrectFont && sSampleText.isEmpty())) + sSampleText = makeShortRepresentativeSymbolTextForSelectedFont(rDevice); + + if (!sSampleText.isEmpty()) + { + const Size &rItemSize = gUserItemSz; + + //leave a little border at the edge + long nSpace = rItemSize.Width() - nTextX - IMGOUTERTEXTSPACE; + if (nSpace >= 0) + { + //Make sure it fits in the available height, and get how wide that would be + long nWidth = shrinkFontToFit(sSampleText, nH, aFont, rDevice, aTextRect); + //Chop letters off until it fits in the available width + while (nWidth > nSpace || nWidth > gUserItemSz.Width()) + { + sSampleText = sSampleText.copy(0, sSampleText.getLength()-1); + nWidth = rDevice.GetTextBoundRect(aTextRect, sSampleText) ? + aTextRect.GetWidth() : 0; + } + + //center the text on the line + if (!sSampleText.isEmpty() && nWidth) + { + nTextHeight = aTextRect.GetHeight(); + nDesiredGap = (nH-nTextHeight)/2; + nVertAdjust = nDesiredGap - aTextRect.Top(); + aPos = Point(nTextX + nSpace - nWidth, rTopLeft.Y() + nVertAdjust); + rDevice.DrawText(aPos, sSampleText); + } + } + } + + rDevice.SetFont(aOldFont); + rDevice.Pop(); +} + +OutputDevice& FontNameBox::CachePreview(size_t nIndex, Point* pTopLeft) +{ + SolarMutexGuard aGuard; + const FontMetric& rFontMetric = (*mpFontList)[nIndex]; + const OUString& rFontName = rFontMetric.GetFamilyName(); + + size_t nPreviewIndex; + auto xFind = std::find(gRenderedFontNames.begin(), gRenderedFontNames.end(), rFontName); + bool bPreviewAvailable = xFind != gRenderedFontNames.end(); + if (!bPreviewAvailable) + { + nPreviewIndex = gRenderedFontNames.size(); + gRenderedFontNames.push_back(rFontName); + } + else + nPreviewIndex = std::distance(gRenderedFontNames.begin(), xFind); + + size_t nPage = nPreviewIndex / gPreviewsPerDevice; + size_t nIndexInPage = nPreviewIndex - (nPage * gPreviewsPerDevice); + + Point aTopLeft(0, gUserItemSz.Height() * nIndexInPage); + + if (!bPreviewAvailable) + { + if (nPage >= gFontPreviewVirDevs.size()) + { + gFontPreviewVirDevs.emplace_back(m_xComboBox->create_render_virtual_device()); + VirtualDevice& rDevice = *gFontPreviewVirDevs.back(); + rDevice.SetOutputSizePixel(Size(gUserItemSz.Width(), gUserItemSz.Height() * gPreviewsPerDevice)); + if (vcl::Window* pDefaultDevice = dynamic_cast<vcl::Window*>(Application::GetDefaultDevice())) + pDefaultDevice->SetPointFont(rDevice, m_xComboBox->get_font()); + assert(gFontPreviewVirDevs.size() == nPage + 1); + } + + DrawPreview(rFontMetric, aTopLeft, *gFontPreviewVirDevs.back(), false); + } + + if (pTopLeft) + *pTopLeft = aTopLeft; + + return *gFontPreviewVirDevs[nPage]; +} + +IMPL_LINK(FontNameBox, CustomRenderHdl, weld::ComboBox::render_args, aPayload, void) +{ + vcl::RenderContext& rRenderContext = std::get<0>(aPayload); + const ::tools::Rectangle& rRect = std::get<1>(aPayload); + bool bSelected = std::get<2>(aPayload); + const OUString& rId = std::get<3>(aPayload); + + sal_uInt32 nIndex = rId.toUInt32(); + + Point aDestPoint(rRect.TopLeft()); + auto nMargin = (rRect.GetHeight() - gUserItemSz.Height()) / 2; + aDestPoint.AdjustY(nMargin); + + if (bSelected) + { + const FontMetric& rFontMetric = (*mpFontList)[nIndex]; + DrawPreview(rFontMetric, aDestPoint, rRenderContext, true); + } + else + { + // use cache of unselected entries + Point aTopLeft; + OutputDevice& rDevice = CachePreview(nIndex, &aTopLeft); + + rRenderContext.DrawOutDev(aDestPoint, gUserItemSz, + aTopLeft, gUserItemSz, + rDevice); + } +} + +void FontNameBox::set_active_or_entry_text(const OUString& rText) +{ + const int nFound = m_xComboBox->find_text(rText); + if (nFound != -1) + m_xComboBox->set_active(nFound); + m_xComboBox->set_entry_text(rText); +} + +FontStyleBox::FontStyleBox(std::unique_ptr<weld::ComboBox> p) + : m_xComboBox(std::move(p)) +{ + //Use the standard texts to get an optimal size and stick to that size. + //That should stop the character dialog dancing around. + auto nMaxLen = m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_LIGHT)).Width(); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_LIGHT_ITALIC)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_NORMAL)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_NORMAL_ITALIC)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BOLD)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BOLD_ITALIC)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BLACK)).Width()); + nMaxLen = std::max(nMaxLen, m_xComboBox->get_pixel_size(SvtResId(STR_SVT_STYLE_BLACK_ITALIC)).Width()); + m_xComboBox->set_entry_width_chars(std::ceil(nMaxLen / m_xComboBox->get_approximate_digit_width())); +} + +void FontStyleBox::Fill( const OUString& rName, const FontList* pList ) +{ + m_xComboBox->freeze(); + OUString aOldText = m_xComboBox->get_active_text(); + int nPos = m_xComboBox->get_active(); + m_xComboBox->clear(); + + // does a font with this name already exist? + sal_Handle hFontMetric = pList->GetFirstFontMetric( rName ); + if ( hFontMetric ) + { + OUString aStyleText; + FontWeight eLastWeight = WEIGHT_DONTKNOW; + FontItalic eLastItalic = ITALIC_NONE; + FontWidth eLastWidth = WIDTH_DONTKNOW; + bool bNormal = false; + bool bItalic = false; + bool bBold = false; + bool bBoldItalic = false; + bool bInsert = false; + FontMetric aFontMetric; + while ( hFontMetric ) + { + aFontMetric = FontList::GetFontMetric( hFontMetric ); + + FontWeight eWeight = aFontMetric.GetWeight(); + FontItalic eItalic = aFontMetric.GetItalic(); + FontWidth eWidth = aFontMetric.GetWidthType(); + // Only if the attributes are different, we insert the + // Font to avoid double Entries in different languages + if ( (eWeight != eLastWeight) || (eItalic != eLastItalic) || + (eWidth != eLastWidth) ) + { + if ( bInsert ) + m_xComboBox->append_text(aStyleText); + + if ( eWeight <= WEIGHT_NORMAL ) + { + if ( eItalic != ITALIC_NONE ) + bItalic = true; + else + bNormal = true; + } + else + { + if ( eItalic != ITALIC_NONE ) + bBoldItalic = true; + else + bBold = true; + } + + // For wrong StyleNames we replace this with the correct once + aStyleText = pList->GetStyleName( aFontMetric ); + bInsert = m_xComboBox->find_text(aStyleText) == -1; + if ( !bInsert ) + { + aStyleText = pList->GetStyleName( eWeight, eItalic ); + bInsert = m_xComboBox->find_text(aStyleText) == -1; + } + + eLastWeight = eWeight; + eLastItalic = eItalic; + eLastWidth = eWidth; + } + else + { + if ( bInsert ) + { + // If we have two names for the same attributes + // we prefer the translated standard names + const OUString& rAttrStyleText = pList->GetStyleName( eWeight, eItalic ); + if (rAttrStyleText != aStyleText) + { + OUString aTempStyleText = pList->GetStyleName( aFontMetric ); + if (rAttrStyleText == aTempStyleText) + aStyleText = rAttrStyleText; + bInsert = m_xComboBox->find_text(aStyleText) == -1; + } + } + } + + if ( !bItalic && (aStyleText == pList->GetItalicStr()) ) + bItalic = true; + else if ( !bBold && (aStyleText == pList->GetBoldStr()) ) + bBold = true; + else if ( !bBoldItalic && (aStyleText == pList->GetBoldItalicStr()) ) + bBoldItalic = true; + + hFontMetric = FontList::GetNextFontMetric( hFontMetric ); + } + + if ( bInsert ) + m_xComboBox->append_text(aStyleText); + + // certain style as copy + if ( bNormal ) + { + if ( !bItalic ) + m_xComboBox->append_text(pList->GetItalicStr()); + if ( !bBold ) + m_xComboBox->append_text(pList->GetBoldStr()); + } + if ( !bBoldItalic ) + { + if ( bNormal || bItalic || bBold ) + m_xComboBox->append_text(pList->GetBoldItalicStr()); + } + if (!aOldText.isEmpty()) + { + int nFound = m_xComboBox->find_text(aOldText); + if (nFound != -1) + m_xComboBox->set_active(nFound); + else + { + if (nPos >= m_xComboBox->get_count()) + m_xComboBox->set_active(0); + else + m_xComboBox->set_active(nPos); + } + } + } + else + { + // insert standard styles if no font + m_xComboBox->append_text(pList->GetNormalStr()); + m_xComboBox->append_text(pList->GetItalicStr()); + m_xComboBox->append_text(pList->GetBoldStr()); + m_xComboBox->append_text(pList->GetBoldItalicStr()); + if (!aOldText.isEmpty()) + { + if (nPos >= m_xComboBox->get_count()) + m_xComboBox->set_active(0); + else + m_xComboBox->set_active(nPos); + } + } + m_xComboBox->thaw(); +} + +FontSizeBox::FontSizeBox(std::unique_ptr<weld::ComboBox> p) + : pFontList(nullptr) + , nSavedValue(0) + , nMin(20) + , nMax(9999) + , eUnit(FieldUnit::POINT) + , nDecimalDigits(1) + , nRelMin(0) + , nRelMax(0) + , nRelStep(0) + , nPtRelMin(0) + , nPtRelMax(0) + , nPtRelStep(0) + , bRelativeMode(false) + , bRelative(false) + , bPtRelative(false) + , bStdSize(false) + , m_xComboBox(std::move(p)) +{ + m_xComboBox->set_entry_width_chars(std::ceil(m_xComboBox->get_pixel_size(format_number(105)).Width() / + m_xComboBox->get_approximate_digit_width())); + m_xComboBox->connect_focus_out(LINK(this, FontSizeBox, ReformatHdl)); + m_xComboBox->connect_changed(LINK(this, FontSizeBox, ModifyHdl)); +} + +void FontSizeBox::set_active_or_entry_text(const OUString& rText) +{ + const int nFound = m_xComboBox->find_text(rText); + if (nFound != -1) + m_xComboBox->set_active(nFound); + m_xComboBox->set_entry_text(rText); +} + +IMPL_LINK(FontSizeBox, ReformatHdl, weld::Widget&, rWidget, void) +{ + FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType()); + if (!bRelativeMode || !aFontSizeNames.IsEmpty()) + { + if (aFontSizeNames.Name2Size(m_xComboBox->get_active_text()) != 0) + return; + } + + set_value(get_value()); + + m_aFocusOutHdl.Call(rWidget); +} + +IMPL_LINK(FontSizeBox, ModifyHdl, weld::ComboBox&, rBox, void) +{ + if (bRelativeMode) + { + OUString aStr = comphelper::string::stripStart(rBox.get_active_text(), ' '); + + bool bNewMode = bRelative; + bool bOldPtRelMode = bPtRelative; + + if ( bRelative ) + { + bPtRelative = false; + const sal_Unicode* pStr = aStr.getStr(); + while ( *pStr ) + { + if ( ((*pStr < '0') || (*pStr > '9')) && (*pStr != '%') && !unicode::isSpace(*pStr) ) + { + if ( ('-' == *pStr || '+' == *pStr) && !bPtRelative ) + bPtRelative = true; + else if ( bPtRelative && 'p' == *pStr && 't' == *++pStr ) + ; + else + { + bNewMode = false; + break; + } + } + pStr++; + } + } + else if (!aStr.isEmpty()) + { + if ( -1 != aStr.indexOf('%') ) + { + bNewMode = true; + bPtRelative = false; + } + + if ( '-' == aStr[0] || '+' == aStr[0] ) + { + bNewMode = true; + bPtRelative = true; + } + } + + if ( bNewMode != bRelative || bPtRelative != bOldPtRelMode ) + SetRelative( bNewMode ); + } + m_aChangeHdl.Call(rBox); +} + +void FontSizeBox::Fill( const FontMetric* pFontMetric, const FontList* pList ) +{ + // remember for relative mode + pFontList = pList; + + // no font sizes need to be set for relative mode + if ( bRelative ) + return; + + // query font sizes + const sal_IntPtr* pTempAry; + const sal_IntPtr* pAry = nullptr; + + if( pFontMetric ) + { + aFontMetric = *pFontMetric; + pAry = pList->GetSizeAry( *pFontMetric ); + } + else + { + pAry = FontList::GetStdSizeAry(); + } + + // first insert font size names (for simplified/traditional chinese) + FontSizeNames aFontSizeNames( Application::GetSettings().GetUILanguageTag().getLanguageType() ); + if ( pAry == FontList::GetStdSizeAry() ) + { + // for standard sizes we don't need to bother + if (bStdSize && m_xComboBox->get_count() && aFontSizeNames.IsEmpty()) + return; + bStdSize = true; + } + else + bStdSize = false; + + int nSelectionStart, nSelectionEnd; + m_xComboBox->get_entry_selection_bounds(nSelectionStart, nSelectionEnd); + OUString aStr = m_xComboBox->get_active_text(); + + m_xComboBox->freeze(); + m_xComboBox->clear(); + int nPos = 0; + + if ( !aFontSizeNames.IsEmpty() ) + { + if ( pAry == FontList::GetStdSizeAry() ) + { + // for scalable fonts all font size names + sal_uLong nCount = aFontSizeNames.Count(); + for( sal_uLong i = 0; i < nCount; i++ ) + { + OUString aSizeName = aFontSizeNames.GetIndexName( i ); + sal_IntPtr nSize = aFontSizeNames.GetIndexSize( i ); + OUString sId(OUString::number(-nSize)); // mark as special + m_xComboBox->insert(nPos, aSizeName, &sId, nullptr, nullptr); + nPos++; + } + } + else + { + // for fixed size fonts only selectable font size names + pTempAry = pAry; + while ( *pTempAry ) + { + OUString aSizeName = aFontSizeNames.Size2Name( *pTempAry ); + if ( !aSizeName.isEmpty() ) + { + OUString sId(OUString::number(-(*pTempAry))); // mark as special + m_xComboBox->insert(nPos, aSizeName, &sId, nullptr, nullptr); + nPos++; + } + pTempAry++; + } + } + } + + // then insert numerical font size values + pTempAry = pAry; + while (*pTempAry) + { + InsertValue(*pTempAry); + ++pTempAry; + } + + set_active_or_entry_text(aStr); + m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd); + m_xComboBox->thaw(); +} + +void FontSizeBox::EnableRelativeMode( sal_uInt16 nNewMin, sal_uInt16 nNewMax, sal_uInt16 nStep ) +{ + bRelativeMode = true; + nRelMin = nNewMin; + nRelMax = nNewMax; + nRelStep = nStep; + SetUnit(FieldUnit::POINT); +} + +void FontSizeBox::EnablePtRelativeMode( short nNewMin, short nNewMax, short nStep ) +{ + bRelativeMode = true; + nPtRelMin = nNewMin; + nPtRelMax = nNewMax; + nPtRelStep = nStep; + SetUnit(FieldUnit::POINT); +} + +void FontSizeBox::InsertValue(int i) +{ + OUString sNumber(OUString::number(i)); + m_xComboBox->append(sNumber, format_number(i)); +} + +void FontSizeBox::SetRelative( bool bNewRelative ) +{ + if ( !bRelativeMode ) + return; + + int nSelectionStart, nSelectionEnd; + m_xComboBox->get_entry_selection_bounds(nSelectionStart, nSelectionEnd); + OUString aStr = comphelper::string::stripStart(m_xComboBox->get_active_text(), ' '); + + if (bNewRelative) + { + bRelative = true; + bStdSize = false; + + m_xComboBox->clear(); + + if (bPtRelative) + { + SetDecimalDigits( 1 ); + SetRange(nPtRelMin, nPtRelMax); + SetUnit(FieldUnit::POINT); + + short i = nPtRelMin, n = 0; + // JP 30.06.98: more than 100 values are not useful + while ( i <= nPtRelMax && n++ < 100 ) + { + InsertValue( i ); + i = i + nPtRelStep; + } + } + else + { + SetDecimalDigits(0); + SetRange(nRelMin, nRelMax); + SetUnit(FieldUnit::PERCENT); + + sal_uInt16 i = nRelMin; + while ( i <= nRelMax ) + { + InsertValue( i ); + i = i + nRelStep; + } + } + } + else + { + if (pFontList) + m_xComboBox->clear(); + bRelative = bPtRelative = false; + SetDecimalDigits(1); + SetRange(20, 9999); + SetUnit(FieldUnit::POINT); + if ( pFontList) + Fill( &aFontMetric, pFontList ); + } + + set_active_or_entry_text(aStr); + m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd); +} + +OUString FontSizeBox::format_number(int nValue) const +{ + OUString sRet; + + //pawn percent off to icu to decide whether percent is separated from its number for this locale + if (eUnit == FieldUnit::PERCENT) + { + double fValue = nValue; + fValue /= weld::SpinButton::Power10(nDecimalDigits); + sRet = unicode::formatPercent(fValue, Application::GetSettings().GetUILanguageTag()); + } + else + { + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData(); + sRet = rLocaleData.getNum(nValue, nDecimalDigits, true, false); + if (eUnit != FieldUnit::NONE && eUnit != FieldUnit::DEGREE) + sRet += " "; + assert(eUnit != FieldUnit::PERCENT); + sRet += weld::MetricSpinButton::MetricToString(eUnit); + } + + if (bRelativeMode && bPtRelative && (0 <= nValue) && !sRet.isEmpty()) + sRet = "+" + sRet; + + return sRet; +} + +void FontSizeBox::SetValue(int nNewValue, FieldUnit eInUnit) +{ + auto nTempValue = vcl::ConvertValue(nNewValue, 0, GetDecimalDigits(), eInUnit, GetUnit()); + if (nTempValue < nMin) + nTempValue = nMin; + else if (nTempValue > nMax) + nTempValue = nMax; + if (!bRelative) + { + FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType()); + // conversion loses precision; however font sizes should + // never have a problem with that + OUString aName = aFontSizeNames.Size2Name(nTempValue); + if (!aName.isEmpty() && m_xComboBox->find_text(aName) != -1) + { + m_xComboBox->set_active_text(aName); + return; + } + } + OUString aResult = format_number(nTempValue); + set_active_or_entry_text(aResult); +} + +void FontSizeBox::set_value(int nNewValue) +{ + SetValue(nNewValue, eUnit); +} + +int FontSizeBox::get_value() const +{ + OUString aStr = m_xComboBox->get_active_text(); + if (!bRelative) + { + FontSizeNames aFontSizeNames(Application::GetSettings().GetUILanguageTag().getLanguageType()); + auto nValue = aFontSizeNames.Name2Size(aStr); + if (nValue) + return vcl::ConvertValue(nValue, 0, GetDecimalDigits(), GetUnit(), GetUnit()); + } + + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocaleData = aSysLocale.GetLocaleData(); + double fResult(0.0); + (void)vcl::TextToValue(aStr, fResult, 0, GetDecimalDigits(), rLocaleData, GetUnit()); + if (!aStr.isEmpty()) + { + if (fResult < nMin) + fResult = nMin; + else if (fResult > nMax) + fResult = nMax; + } + return fResult; +} + +SvxBorderLineStyle SvtLineListBox::GetSelectEntryStyle() const +{ + if (m_xLineSet->IsNoSelection()) + return SvxBorderLineStyle::NONE; + auto nId = m_xLineSet->GetSelectedItemId(); + return static_cast<SvxBorderLineStyle>(nId - 1); +} + +namespace +{ + Size getPreviewSize(const weld::Widget& rControl) + { + return Size(rControl.get_approximate_digit_width() * 15, rControl.get_text_height()); + } +} + +void SvtLineListBox::ImpGetLine( long nLine1, long nLine2, long nDistance, + Color aColor1, Color aColor2, Color aColorDist, + SvxBorderLineStyle nStyle, BitmapEx& rBmp ) +{ + Size aSize(getPreviewSize(*m_xControl)); + + // SourceUnit to Twips + if ( eSourceUnit == FieldUnit::POINT ) + { + nLine1 /= 5; + nLine2 /= 5; + nDistance /= 5; + } + + // Paint the lines + aSize = aVirDev->PixelToLogic( aSize ); + long nPix = aVirDev->PixelToLogic( Size( 0, 1 ) ).Height(); + sal_uInt32 n1 = nLine1; + sal_uInt32 n2 = nLine2; + long nDist = nDistance; + n1 += nPix-1; + n1 -= n1%nPix; + if ( n2 ) + { + nDist += nPix-1; + nDist -= nDist%nPix; + n2 += nPix-1; + n2 -= n2%nPix; + } + long nVirHeight = n1+nDist+n2; + if ( nVirHeight > aSize.Height() ) + aSize.setHeight( nVirHeight ); + // negative width should not be drawn + if ( aSize.Width() <= 0 ) + return; + + Size aVirSize = aVirDev->LogicToPixel( aSize ); + if ( aVirDev->GetOutputSizePixel() != aVirSize ) + aVirDev->SetOutputSizePixel( aVirSize ); + aVirDev->SetFillColor( aColorDist ); + aVirDev->DrawRect( tools::Rectangle( Point(), aSize ) ); + + aVirDev->SetFillColor( aColor1 ); + + double y1 = double( n1 ) / 2; + svtools::DrawLine( *aVirDev, basegfx::B2DPoint( 0, y1 ), basegfx::B2DPoint( aSize.Width( ), y1 ), n1, nStyle ); + + if ( n2 ) + { + double y2 = n1 + nDist + double( n2 ) / 2; + aVirDev->SetFillColor( aColor2 ); + svtools::DrawLine( *aVirDev, basegfx::B2DPoint( 0, y2 ), basegfx::B2DPoint( aSize.Width(), y2 ), n2, SvxBorderLineStyle::SOLID ); + } + rBmp = aVirDev->GetBitmapEx( Point(), Size( aSize.Width(), n1+nDist+n2 ) ); +} + +namespace +{ + OUString GetLineStyleName(SvxBorderLineStyle eStyle) + { + OUString sRet; + for (sal_uInt32 i = 0; i < SAL_N_ELEMENTS(RID_SVXSTR_BORDERLINE); ++i) + { + if (eStyle == RID_SVXSTR_BORDERLINE[i].second) + { + sRet = SvtResId(RID_SVXSTR_BORDERLINE[i].first); + break; + } + } + return sRet; + } +} + +SvtLineListBox::SvtLineListBox(std::unique_ptr<weld::MenuButton> pControl) + : m_xControl(std::move(pControl)) + , m_xBuilder(Application::CreateBuilder(m_xControl.get(), "svt/ui/linewindow.ui")) + , m_xTopLevel(m_xBuilder->weld_widget("line_popup_window")) + , m_xNoneButton(m_xBuilder->weld_button("none_line_button")) + , m_xLineSet(new ValueSet(nullptr)) + , m_xLineSetWin(new weld::CustomWeld(*m_xBuilder, "lineset", *m_xLineSet)) + , m_nWidth( 5 ) + , aVirDev(VclPtr<VirtualDevice>::Create()) + , aColor(COL_BLACK) + , maPaintCol(COL_BLACK) +{ + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + m_xLineSet->SetStyle(WinBits(WB_FLATVALUESET | WB_NO_DIRECTSELECT | WB_TABSTOP)); + m_xLineSet->SetItemHeight(rStyleSettings.GetListBoxPreviewDefaultPixelSize().Height() + 1); + m_xLineSet->SetColCount(1); + m_xLineSet->SetSelectHdl(LINK(this, SvtLineListBox, ValueSelectHdl)); + + m_xNoneButton->connect_clicked(LINK(this, SvtLineListBox, NoneHdl)); + + m_xTopLevel->connect_focus_in(LINK(this, SvtLineListBox, FocusHdl)); + m_xControl->set_popover(m_xTopLevel.get()); + m_xControl->connect_toggled(LINK(this, SvtLineListBox, ToggleHdl)); + + // lock size to these maxes height/width so it doesn't jump around in size + m_xControl->set_label(GetLineStyleName(SvxBorderLineStyle::NONE)); + Size aNonePrefSize = m_xControl->get_preferred_size(); + m_xControl->set_label(""); + aVirDev->SetOutputSizePixel(getPreviewSize(*m_xControl)); + m_xControl->set_image(aVirDev); + Size aSolidPrefSize = m_xControl->get_preferred_size(); + m_xControl->set_size_request(std::max(aNonePrefSize.Width(), aSolidPrefSize.Width()), + std::max(aNonePrefSize.Height(), aSolidPrefSize.Height())); + + eSourceUnit = FieldUnit::POINT; + + aVirDev->SetLineColor(); + aVirDev->SetMapMode(MapMode(MapUnit::MapTwip)); + + UpdatePaintLineColor(); +} + +IMPL_LINK_NOARG(SvtLineListBox, FocusHdl, weld::Widget&, void) +{ + if (GetSelectEntryStyle() == SvxBorderLineStyle::NONE) + m_xNoneButton->grab_focus(); + else + m_xLineSet->GrabFocus(); +} + +IMPL_LINK(SvtLineListBox, ToggleHdl, weld::ToggleButton&, rButton, void) +{ + if (rButton.get_active()) + FocusHdl(*m_xTopLevel); +} + +IMPL_LINK_NOARG(SvtLineListBox, NoneHdl, weld::Button&, void) +{ + SelectEntry(SvxBorderLineStyle::NONE); + ValueSelectHdl(nullptr); +} + +SvtLineListBox::~SvtLineListBox() +{ +} + +sal_Int32 SvtLineListBox::GetStylePos( sal_Int32 nListPos ) +{ + sal_Int32 nPos = -1; + --nListPos; + + sal_Int32 n = 0; + size_t i = 0; + size_t nCount = m_vLineList.size(); + while ( nPos == -1 && i < nCount ) + { + if ( nListPos == n ) + nPos = static_cast<sal_Int32>(i); + n++; + i++; + } + + return nPos; +} + +void SvtLineListBox::SelectEntry(SvxBorderLineStyle nStyle) +{ + if (nStyle == SvxBorderLineStyle::NONE) + m_xLineSet->SetNoSelection(); + else + m_xLineSet->SelectItem(static_cast<sal_Int16>(nStyle) + 1); + UpdatePreview(); +} + +void SvtLineListBox::InsertEntry( + const BorderWidthImpl& rWidthImpl, SvxBorderLineStyle nStyle, long nMinWidth, + ColorFunc pColor1Fn, ColorFunc pColor2Fn, ColorDistFunc pColorDistFn ) +{ + m_vLineList.emplace_back(new ImpLineListData( + rWidthImpl, nStyle, nMinWidth, pColor1Fn, pColor2Fn, pColorDistFn)); +} + +void SvtLineListBox::UpdatePaintLineColor() +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + Color aNewCol(rSettings.GetWindowColor().IsDark() ? rSettings.GetLabelTextColor() : aColor); + + bool bRet = aNewCol != maPaintCol; + + if( bRet ) + maPaintCol = aNewCol; +} + +void SvtLineListBox::UpdateEntries() +{ + UpdatePaintLineColor( ); + + SvxBorderLineStyle eSelected = GetSelectEntryStyle(); + + // Remove the old entries + m_xLineSet->Clear(); + + // Add the new entries based on the defined width + + sal_uInt16 n = 0; + sal_uInt16 nCount = m_vLineList.size( ); + while ( n < nCount ) + { + auto& pData = m_vLineList[ n ]; + BitmapEx aBmp; + ImpGetLine( pData->GetLine1ForWidth( m_nWidth ), + pData->GetLine2ForWidth( m_nWidth ), + pData->GetDistForWidth( m_nWidth ), + GetColorLine1(m_xLineSet->GetItemCount()), + GetColorLine2(m_xLineSet->GetItemCount()), + GetColorDist(m_xLineSet->GetItemCount()), + pData->GetStyle(), aBmp ); + sal_Int16 nItemId = static_cast<sal_Int16>(pData->GetStyle()) + 1; + m_xLineSet->InsertItem(nItemId, Image(aBmp), GetLineStyleName(pData->GetStyle())); + if (pData->GetStyle() == eSelected) + m_xLineSet->SelectItem(nItemId); + n++; + } + + m_xLineSet->SetOptimalSize(); +} + +Color SvtLineListBox::GetColorLine1( sal_Int32 nPos ) +{ + sal_Int32 nStyle = GetStylePos( nPos ); + if (nStyle == -1) + return GetPaintColor( ); + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorLine1( GetColor( ) ); +} + +Color SvtLineListBox::GetColorLine2( sal_Int32 nPos ) +{ + sal_Int32 nStyle = GetStylePos(nPos); + if (nStyle == -1) + return GetPaintColor( ); + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorLine2( GetColor( ) ); +} + +Color SvtLineListBox::GetColorDist( sal_Int32 nPos ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + Color rResult = rSettings.GetFieldColor(); + + sal_Int32 nStyle = GetStylePos( nPos ); + if (nStyle == -1) + return rResult; + auto& pData = m_vLineList[ nStyle ]; + return pData->GetColorDist( GetColor( ), rResult ); +} + +IMPL_LINK_NOARG(SvtLineListBox, ValueSelectHdl, ValueSet*, void) +{ + maSelectHdl.Call(*this); + UpdatePreview(); + if (m_xControl->get_active()) + m_xControl->set_active(false); +} + +void SvtLineListBox::UpdatePreview() +{ + SvxBorderLineStyle eStyle = GetSelectEntryStyle(); + for (sal_uInt32 i = 0; i < SAL_N_ELEMENTS(RID_SVXSTR_BORDERLINE); ++i) + { + if (eStyle == RID_SVXSTR_BORDERLINE[i].second) + { + m_xControl->set_label(SvtResId(RID_SVXSTR_BORDERLINE[i].first)); + break; + } + } + + if (eStyle == SvxBorderLineStyle::NONE) + { + m_xControl->set_image(nullptr); + m_xControl->set_label(GetLineStyleName(SvxBorderLineStyle::NONE)); + } + else + { + Image aImage(m_xLineSet->GetItemImage(m_xLineSet->GetSelectedItemId())); + m_xControl->set_label(""); + const auto nPos = (aVirDev->GetOutputSizePixel().Height() - aImage.GetSizePixel().Height()) / 2; + aVirDev->Push(PushFlags::MAPMODE); + aVirDev->SetMapMode(MapMode(MapUnit::MapPixel)); + aVirDev->Erase(); + aVirDev->DrawImage(Point(0, nPos), aImage); + m_xControl->set_image(aVirDev.get()); + aVirDev->Pop(); + } +} + +SvtCalendarBox::SvtCalendarBox(std::unique_ptr<weld::MenuButton> pControl) + : m_xControl(std::move(pControl)) + , m_xBuilder(Application::CreateBuilder(m_xControl.get(), "svt/ui/datewindow.ui")) + , m_xTopLevel(m_xBuilder->weld_widget("date_popup_window")) + , m_xCalendar(m_xBuilder->weld_calendar("date")) +{ + m_xControl->set_popover(m_xTopLevel.get()); + m_xCalendar->connect_selected(LINK(this, SvtCalendarBox, SelectHdl)); + m_xCalendar->connect_activated(LINK(this, SvtCalendarBox, ActivateHdl)); +} + +void SvtCalendarBox::set_date(const Date& rDate) +{ + m_xCalendar->set_date(rDate); + set_label_from_date(); +} + +void SvtCalendarBox::set_label_from_date() +{ + const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper(); + m_xControl->set_label(rLocaleData.getDate(m_xCalendar->get_date())); +} + +IMPL_LINK_NOARG(SvtCalendarBox, SelectHdl, weld::Calendar&, void) +{ + set_label_from_date(); + m_aSelectHdl.Call(*this); +} + +IMPL_LINK_NOARG(SvtCalendarBox, ActivateHdl, weld::Calendar&, void) +{ + if (m_xControl->get_active()) + m_xControl->set_active(false); + m_aActivatedHdl.Call(*this); +} + +SvtCalendarBox::~SvtCalendarBox() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/ctrltool.cxx b/svtools/source/control/ctrltool.cxx new file mode 100644 index 000000000..cdd1aed8a --- /dev/null +++ b/svtools/source/control/ctrltool.cxx @@ -0,0 +1,890 @@ +/* -*- 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 <memory> +#include <string.h> + +#include <tools/debug.hxx> +#include <tools/fract.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sal/macros.h> +#include <svtools/strings.hrc> +#include <svtools/svtresid.hxx> +#include <svtools/ctrltool.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <comphelper/lok.hxx> + +// Standard fontsizes for scalable Fonts +const sal_IntPtr FontList::aStdSizeAry[] = +{ + 60, + 70, + 80, + 90, + 100, + 105, + 110, + 120, + 130, + 140, + 150, + 160, + 180, + 200, + 220, + 240, + 260, + 280, + 320, + 360, + 400, + 440, + 480, + 540, + 600, + 660, + 720, + 800, + 880, + 960, + 0 +}; + +namespace { + +class ImplFontListFontMetric : public FontMetric +{ + friend FontList; + +private: + VclPtr<OutputDevice> mpDevice; + ImplFontListFontMetric* mpNext; + +public: + ImplFontListFontMetric( const FontMetric& rInfo, + OutputDevice* pDev ) : + FontMetric( rInfo ), mpDevice(pDev), mpNext(nullptr) + { + } + + OutputDevice* GetDevice() const { return mpDevice; } +}; + +enum class FontListFontNameType +{ + NONE = 0x00, + PRINTER = 0x01, + SCREEN = 0x02, +}; + +} + +namespace o3tl +{ + template<> struct typed_flags<FontListFontNameType> : is_typed_flags<FontListFontNameType, 0x3> {}; +} + +class ImplFontListNameInfo +{ + friend class FontList; + +private: + OUString maSearchName; + ImplFontListFontMetric* mpFirst; + FontListFontNameType mnType; + + explicit ImplFontListNameInfo(const OUString& rSearchName) + : maSearchName(rSearchName) + , mpFirst(nullptr) + , mnType(FontListFontNameType::NONE) + { + } +}; + +//sort normal to the start +static int sortWeightValue(FontWeight eWeight) +{ + if (eWeight < WEIGHT_NORMAL) + return eWeight + 1; + if (eWeight > WEIGHT_NORMAL) + return eWeight - 1; + return 0; // eWeight == WEIGHT_NORMAL +} + +static sal_Int32 ImplCompareFontMetric( ImplFontListFontMetric* pInfo1, + ImplFontListFontMetric* pInfo2 ) +{ + //Sort non italic before italics + if ( pInfo1->GetItalic() < pInfo2->GetItalic() ) + return -1; + else if ( pInfo1->GetItalic() > pInfo2->GetItalic() ) + return 1; + + //Sort normal weight to the start, followed by lightest to heaviest weights + int nWeight1 = sortWeightValue(pInfo1->GetWeight()); + int nWeight2 = sortWeightValue(pInfo2->GetWeight()); + + if ( nWeight1 < nWeight2 ) + return -1; + else if ( nWeight1 > nWeight2 ) + return 1; + + return pInfo1->GetStyleName().compareTo( pInfo2->GetStyleName() ); +} + +static OUString ImplMakeSearchString(const OUString& rStr) +{ + return rStr.toAsciiLowerCase(); +} + +static OUString ImplMakeSearchStringFromName(const OUString& rStr) +{ + // check for features before alternate font separator + sal_Int32 nColon = rStr.indexOf(':'); + sal_Int32 nSemiColon = rStr.indexOf(';'); + if (nColon != -1 && (nSemiColon == -1 || nColon < nSemiColon)) + return ImplMakeSearchString(rStr.getToken( 0, ':' )); + return ImplMakeSearchString(rStr.getToken( 0, ';' )); +} + +ImplFontListNameInfo* FontList::ImplFind(const OUString& rSearchName, sal_uInt32* pIndex) const +{ + // Append if there is no entry in the list or if the entry is larger + // then the last one. We only compare to the last entry as the list of VCL + // is returned sorted, which increases the probability that appending + // is more likely + if (m_Entries.empty()) + { + if ( pIndex ) + *pIndex = SAL_MAX_UINT32; + return nullptr; + } + else + { + const ImplFontListNameInfo* pCmpData = m_Entries.back().get(); + sal_Int32 nComp = rSearchName.compareTo( pCmpData->maSearchName ); + if (nComp > 0) + { + if ( pIndex ) + *pIndex = SAL_MAX_UINT32; + return nullptr; + } + else if (nComp == 0) + return const_cast<ImplFontListNameInfo*>(pCmpData); + } + + // search fonts in the list + const ImplFontListNameInfo* pCompareData; + const ImplFontListNameInfo* pFoundData = nullptr; + size_t nLow = 0; + size_t nHigh = m_Entries.size() - 1; + size_t nMid; + + do + { + nMid = (nLow + nHigh) / 2; + pCompareData = m_Entries[nMid].get(); + sal_Int32 nComp = rSearchName.compareTo(pCompareData->maSearchName); + if (nComp < 0) + { + if ( !nMid ) + break; + nHigh = nMid-1; + } + else + { + if (nComp > 0) + nLow = nMid + 1; + else + { + pFoundData = pCompareData; + break; + } + } + } + while ( nLow <= nHigh ); + + if ( pIndex ) + { + sal_Int32 nComp = rSearchName.compareTo(pCompareData->maSearchName); + if (nComp > 0) + *pIndex = (nMid+1); + else + *pIndex = nMid; + } + + return const_cast<ImplFontListNameInfo*>(pFoundData); +} + +ImplFontListNameInfo* FontList::ImplFindByName(const OUString& rStr) const +{ + OUString aSearchName = ImplMakeSearchStringFromName(rStr); + return ImplFind( aSearchName, nullptr ); +} + +void FontList::ImplInsertFonts(OutputDevice* pDevice, bool bInsertData) +{ + rtl_TextEncoding eSystemEncoding = osl_getThreadTextEncoding(); + + FontListFontNameType nType; + if ( pDevice->GetOutDevType() != OUTDEV_PRINTER ) + nType = FontListFontNameType::SCREEN; + else + nType = FontListFontNameType::PRINTER; + + // inquire all fonts from the device + int n = pDevice->GetDevFontCount(); + if (n == 0 && comphelper::LibreOfficeKit::isActive()) + { + pDevice->RefreshFontData(true); + n = pDevice->GetDevFontCount(); + } + + for (int i = 0; i < n; ++i) + { + FontMetric aFontMetric = pDevice->GetDevFont( i ); + OUString aSearchName(aFontMetric.GetFamilyName()); + ImplFontListNameInfo* pData; + sal_uInt32 nIndex; + aSearchName = ImplMakeSearchString(aSearchName); + pData = ImplFind( aSearchName, &nIndex ); + + if ( !pData ) + { + if ( bInsertData ) + { + ImplFontListFontMetric* pNewInfo = new ImplFontListFontMetric( aFontMetric, pDevice ); + pData = new ImplFontListNameInfo( aSearchName ); + pData->mpFirst = pNewInfo; + pNewInfo->mpNext = nullptr; + + if (nIndex < static_cast<sal_uInt32>(m_Entries.size())) + m_Entries.insert(m_Entries.begin()+nIndex, + std::unique_ptr<ImplFontListNameInfo>(pData)); + else + m_Entries.push_back(std::unique_ptr<ImplFontListNameInfo>(pData)); + } + } + else + { + if ( bInsertData ) + { + bool bInsert = true; + ImplFontListFontMetric* pPrev = nullptr; + ImplFontListFontMetric* pTemp = pData->mpFirst; + ImplFontListFontMetric* pNewInfo = new ImplFontListFontMetric( aFontMetric, pDevice ); + while ( pTemp ) + { + sal_Int32 eComp = ImplCompareFontMetric( pNewInfo, pTemp ); + if ( eComp <= 0 ) + { + if ( eComp == 0 ) + { + // Overwrite charset, because charset should match + // with the system charset + if ( (pTemp->GetCharSet() != eSystemEncoding) && + (pNewInfo->GetCharSet() == eSystemEncoding) ) + { + ImplFontListFontMetric* pTemp2 = pTemp->mpNext; + *static_cast<FontMetric*>(pTemp) = *static_cast<FontMetric*>(pNewInfo); + pTemp->mpNext = pTemp2; + } + delete pNewInfo; + bInsert = false; + } + + break; + } + + pPrev = pTemp; + pTemp = pTemp->mpNext; + } + + if ( bInsert ) + { + pNewInfo->mpNext = pTemp; + if ( pPrev ) + pPrev->mpNext = pNewInfo; + else + pData->mpFirst = pNewInfo; + } + } + } + + if ( pData ) + pData->mnType |= nType; + } +} + +FontList::FontList(OutputDevice* pDevice, OutputDevice* pDevice2) +{ + // initialise variables + mpDev = pDevice; + mpDev2 = pDevice2; + + // store style names + maLight = SvtResId(STR_SVT_STYLE_LIGHT); + maLightItalic = SvtResId(STR_SVT_STYLE_LIGHT_ITALIC); + maNormal = SvtResId(STR_SVT_STYLE_NORMAL); + maNormalItalic = SvtResId(STR_SVT_STYLE_NORMAL_ITALIC); + maBold = SvtResId(STR_SVT_STYLE_BOLD); + maBoldItalic = SvtResId(STR_SVT_STYLE_BOLD_ITALIC); + maBlack = SvtResId(STR_SVT_STYLE_BLACK); + maBlackItalic = SvtResId(STR_SVT_STYLE_BLACK_ITALIC); + + ImplInsertFonts(pDevice, true); + + // if required compare to the screen fonts + // in order to map the duplicates to Equal + bool bCompareWindow = false; + if ( !pDevice2 && (pDevice->GetOutDevType() == OUTDEV_PRINTER) ) + { + bCompareWindow = true; + pDevice2 = Application::GetDefaultDevice(); + } + + if ( pDevice2 && + (pDevice2->GetOutDevType() != pDevice->GetOutDevType()) ) + ImplInsertFonts(pDevice2, !bCompareWindow); +} + +FontList::~FontList() +{ + // delete FontMetrics + ImplFontListFontMetric *pTemp, *pInfo; + for (auto const& it : m_Entries) + { + pInfo = it->mpFirst; + while ( pInfo ) + { + pTemp = pInfo->mpNext; + delete pInfo; + pInfo = pTemp; + } + } +} + +std::unique_ptr<FontList> FontList::Clone() const +{ + return std::unique_ptr<FontList>(new FontList(mpDev, mpDev2)); +} + +const OUString& FontList::GetStyleName(FontWeight eWeight, FontItalic eItalic) const +{ + if ( eWeight > WEIGHT_BOLD ) + { + if ( eItalic > ITALIC_NONE ) + return maBlackItalic; + else + return maBlack; + } + else if ( eWeight > WEIGHT_MEDIUM ) + { + if ( eItalic > ITALIC_NONE ) + return maBoldItalic; + else + return maBold; + } + else if ( eWeight > WEIGHT_LIGHT ) + { + if ( eItalic > ITALIC_NONE ) + return maNormalItalic; + else + return maNormal; + } + else if ( eWeight != WEIGHT_DONTKNOW ) + { + if ( eItalic > ITALIC_NONE ) + return maLightItalic; + else + return maLight; + } + else + { + if ( eItalic > ITALIC_NONE ) + return maNormalItalic; + else + return maNormal; + } +} + +OUString FontList::GetStyleName(const FontMetric& rInfo) const +{ + OUString aStyleName = rInfo.GetStyleName(); + FontWeight eWeight = rInfo.GetWeight(); + FontItalic eItalic = rInfo.GetItalic(); + + // return synthetic Name if no StyleName was set + if (aStyleName.isEmpty()) + aStyleName = GetStyleName(eWeight, eItalic); + else + { + // Translate StyleName to localized name + OUString aCompareStyleName = aStyleName.toAsciiLowerCase().replaceAll(" ", ""); + if (aCompareStyleName == "bold") + aStyleName = maBold; + else if (aCompareStyleName == "bolditalic") + aStyleName = maBoldItalic; + else if (aCompareStyleName == "italic") + aStyleName = maNormalItalic; + else if (aCompareStyleName == "standard") + aStyleName = maNormal; + else if (aCompareStyleName == "regular") + aStyleName = maNormal; + else if (aCompareStyleName == "medium") + aStyleName = maNormal; + else if (aCompareStyleName == "light") + aStyleName = maLight; + else if (aCompareStyleName == "lightitalic") + aStyleName = maLightItalic; + else if (aCompareStyleName == "black") + aStyleName = maBlack; + else if (aCompareStyleName == "blackitalic") + aStyleName = maBlackItalic; + /* tdf#107700 support some less common style names with localization */ + else if (aCompareStyleName == "book") + aStyleName = SvtResId(STR_SVT_STYLE_BOOK); + else if (aCompareStyleName == "boldoblique") + aStyleName = SvtResId(STR_SVT_STYLE_BOLD_OBLIQUE); + else if (aCompareStyleName == "condensed") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED); + else if (aCompareStyleName == "condensedbold") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_BOLD); + else if (aCompareStyleName == "condensedbolditalic") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_BOLD_ITALIC); + else if (aCompareStyleName == "condensedboldoblique") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_BOLD_OBLIQUE); + else if (aCompareStyleName == "condenseditalic") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_ITALIC); + else if (aCompareStyleName == "condensedoblique") + aStyleName = SvtResId(STR_SVT_STYLE_CONDENSED_OBLIQUE); + else if (aCompareStyleName == "extralight") + aStyleName = SvtResId(STR_SVT_STYLE_EXTRALIGHT); + else if (aCompareStyleName == "extralightitalic") + aStyleName = SvtResId(STR_SVT_STYLE_EXTRALIGHT_ITALIC); + /* Medium is synonym with Normal */ + else if (aCompareStyleName == "mediumitalic") + aStyleName = maNormalItalic; + else if (aCompareStyleName == "oblique") + aStyleName = SvtResId(STR_SVT_STYLE_OBLIQUE); + else if (aCompareStyleName == "semibold") + aStyleName = SvtResId(STR_SVT_STYLE_SEMIBOLD); + else if (aCompareStyleName == "semibolditalic") + aStyleName = SvtResId(STR_SVT_STYLE_SEMIBOLD_ITALIC); + + // fix up StyleName, because the PS Printer driver from + // W2000 returns wrong StyleNames (e.g. Bold instead of Bold Italic + // for Helvetica) + if ( eItalic > ITALIC_NONE ) + { + if ( (aStyleName == maNormal) || + (aStyleName == maBold) || + (aStyleName == maLight) || + (aStyleName == maBlack) ) + aStyleName = GetStyleName( eWeight, eItalic ); + } + } + + return aStyleName; +} + +OUString FontList::GetFontMapText( const FontMetric& rInfo ) const +{ + if ( rInfo.GetFamilyName().isEmpty() ) + { + return OUString(); + } + + // Search Fontname + ImplFontListNameInfo* pData = ImplFindByName( rInfo.GetFamilyName() ); + if ( !pData ) + { + if (maMapNotAvailable.isEmpty()) + maMapNotAvailable = SvtResId(STR_SVT_FONTMAP_NOTAVAILABLE); + return maMapNotAvailable; + } + + // search for synthetic style + FontListFontNameType nType = pData->mnType; + const OUString& rStyleName = rInfo.GetStyleName(); + if (!rStyleName.isEmpty()) + { + bool bNotSynthetic = false; + FontWeight eWeight = rInfo.GetWeight(); + FontItalic eItalic = rInfo.GetItalic(); + ImplFontListFontMetric* pFontMetric = pData->mpFirst; + while ( pFontMetric ) + { + if ( (eWeight == pFontMetric->GetWeight()) && + (eItalic == pFontMetric->GetItalic()) ) + { + bNotSynthetic = true; + break; + } + + pFontMetric = pFontMetric->mpNext; + } + + if ( !bNotSynthetic ) + { + if (maMapStyleNotAvailable.isEmpty()) + const_cast<FontList*>(this)->maMapStyleNotAvailable = SvtResId(STR_SVT_FONTMAP_STYLENOTAVAILABLE); + return maMapStyleNotAvailable; + } + } + + // Only Printer-Font? + if ( nType == FontListFontNameType::PRINTER ) + { + if (maMapPrinterOnly.isEmpty()) + const_cast<FontList*>(this)->maMapPrinterOnly = SvtResId(STR_SVT_FONTMAP_PRINTERONLY); + return maMapPrinterOnly; + } + else + { + if (maMapBoth.isEmpty()) + const_cast<FontList*>(this)->maMapBoth = SvtResId(STR_SVT_FONTMAP_BOTH); + return maMapBoth; + } +} + +namespace +{ + FontMetric makeMissing(ImplFontListFontMetric const * pFontNameInfo, const OUString &rName, + FontWeight eWeight, FontItalic eItalic) + { + FontMetric aInfo; + // if the fontname matches, we copy as much as possible + if (pFontNameInfo) + { + aInfo = *pFontNameInfo; + aInfo.SetStyleName(OUString()); + } + + aInfo.SetWeight(eWeight); + aInfo.SetItalic(eItalic); + + //If this is a known but uninstalled symbol font which we can remap to + //OpenSymbol then toggle its charset to be a symbol font + if (ConvertChar::GetRecodeData(rName, "OpenSymbol")) + aInfo.SetCharSet(RTL_TEXTENCODING_SYMBOL); + + return aInfo; + } +} + +FontMetric FontList::Get(const OUString& rName, const OUString& rStyleName) const +{ + ImplFontListNameInfo* pData = ImplFindByName( rName ); + ImplFontListFontMetric* pFontMetric = nullptr; + ImplFontListFontMetric* pFontNameInfo = nullptr; + if ( pData ) + { + ImplFontListFontMetric* pSearchInfo = pData->mpFirst; + pFontNameInfo = pSearchInfo; + pSearchInfo = pData->mpFirst; + while ( pSearchInfo ) + { + if (rStyleName.equalsIgnoreAsciiCase(GetStyleName(*pSearchInfo))) + { + pFontMetric = pSearchInfo; + break; + } + + pSearchInfo = pSearchInfo->mpNext; + } + } + + // reproduce attributes if data could not be found + FontMetric aInfo; + if ( !pFontMetric ) + { + FontWeight eWeight = WEIGHT_DONTKNOW; + FontItalic eItalic = ITALIC_NONE; + + if ( rStyleName == maNormal ) + { + eItalic = ITALIC_NONE; + eWeight = WEIGHT_NORMAL; + } + else if ( rStyleName == maNormalItalic ) + { + eItalic = ITALIC_NORMAL; + eWeight = WEIGHT_NORMAL; + } + else if ( rStyleName == maBold ) + { + eItalic = ITALIC_NONE; + eWeight = WEIGHT_BOLD; + } + else if ( rStyleName == maBoldItalic ) + { + eItalic = ITALIC_NORMAL; + eWeight = WEIGHT_BOLD; + } + else if ( rStyleName == maLight ) + { + eItalic = ITALIC_NONE; + eWeight = WEIGHT_LIGHT; + } + else if ( rStyleName == maLightItalic ) + { + eItalic = ITALIC_NORMAL; + eWeight = WEIGHT_LIGHT; + } + else if ( rStyleName == maBlack ) + { + eItalic = ITALIC_NONE; + eWeight = WEIGHT_BLACK; + } + else if ( rStyleName == maBlackItalic ) + { + eItalic = ITALIC_NORMAL; + eWeight = WEIGHT_BLACK; + } + aInfo = makeMissing(pFontNameInfo, rName, eWeight, eItalic); + } + else + aInfo = *pFontMetric; + + // set Fontname to keep FontAlias + aInfo.SetFamilyName( rName ); + aInfo.SetStyleName( rStyleName ); + + return aInfo; +} + +FontMetric FontList::Get(const OUString& rName, + FontWeight eWeight, FontItalic eItalic) const +{ + ImplFontListNameInfo* pData = ImplFindByName( rName ); + ImplFontListFontMetric* pFontMetric = nullptr; + ImplFontListFontMetric* pFontNameInfo = nullptr; + if ( pData ) + { + ImplFontListFontMetric* pSearchInfo = pData->mpFirst; + pFontNameInfo = pSearchInfo; + while ( pSearchInfo ) + { + if ( (eWeight == pSearchInfo->GetWeight()) && + (eItalic == pSearchInfo->GetItalic()) ) + { + pFontMetric = pSearchInfo; + break; + } + + pSearchInfo = pSearchInfo->mpNext; + } + } + + // reproduce attributes if data could not be found + FontMetric aInfo; + if ( !pFontMetric ) + aInfo = makeMissing(pFontNameInfo, rName, eWeight, eItalic); + else + aInfo = *pFontMetric; + + // set Fontname to keep FontAlias + aInfo.SetFamilyName( rName ); + + return aInfo; +} + +bool FontList::IsAvailable(const OUString& rName) const +{ + return (ImplFindByName( rName ) != nullptr); +} + +const FontMetric& FontList::GetFontName(size_t const nFont) const +{ + DBG_ASSERT( nFont < GetFontNameCount(), "FontList::GetFontName(): nFont >= Count" ); + + return *(m_Entries[nFont]->mpFirst); +} + +sal_Handle FontList::GetFirstFontMetric(const OUString& rName) const +{ + ImplFontListNameInfo* pData = ImplFindByName( rName ); + if ( !pData ) + return nullptr; + else + return static_cast<sal_Handle>(pData->mpFirst); +} + +sal_Handle FontList::GetNextFontMetric( sal_Handle hFontMetric ) +{ + ImplFontListFontMetric* pInfo = static_cast<ImplFontListFontMetric*>(hFontMetric); + return static_cast<sal_Handle>(pInfo->mpNext); +} + +const FontMetric& FontList::GetFontMetric( sal_Handle hFontMetric ) +{ + ImplFontListFontMetric* pInfo = static_cast<ImplFontListFontMetric*>(hFontMetric); + return *pInfo; +} + +const sal_IntPtr* FontList::GetSizeAry( const FontMetric& rInfo ) const +{ + // first delete Size-Array + mpSizeAry.reset(); + + // use standard sizes if no name + if ( rInfo.GetFamilyName().isEmpty() ) + return aStdSizeAry; + + // first search fontname in order to use device from the matching font + OutputDevice* pDevice = mpDev; + ImplFontListNameInfo* pData = ImplFindByName( rInfo.GetFamilyName() ); + if ( pData ) + pDevice = pData->mpFirst->GetDevice(); + + int nDevSizeCount = pDevice->GetDevFontSizeCount( rInfo ); + if ( !nDevSizeCount || + (pDevice->GetDevFontSize( rInfo, 0 ).Height() == 0) ) + return aStdSizeAry; + + MapMode aOldMapMode = pDevice->GetMapMode(); + MapMode aMap( MapUnit::Map10thInch, Point(), Fraction( 1, 72 ), Fraction( 1, 72 ) ); + pDevice->SetMapMode( aMap ); + + int nRealCount = 0; + long nOldHeight = 0; + mpSizeAry.reset(new sal_IntPtr[nDevSizeCount+1] ); + for (int i = 0; i < nDevSizeCount; ++i) + { + Size aSize = pDevice->GetDevFontSize( rInfo, i ); + if ( aSize.Height() != nOldHeight ) + { + nOldHeight = aSize.Height(); + mpSizeAry[nRealCount] = nOldHeight; + nRealCount++; + } + } + mpSizeAry[nRealCount] = 0; + + pDevice->SetMapMode( aOldMapMode ); + return mpSizeAry.get(); +} + +struct ImplFSNameItem +{ + sal_Int32 mnSize; + const char* mszUtf8Name; +}; + +static const ImplFSNameItem aImplSimplifiedChinese[] = +{ + { 50, "\xe5\x85\xab\xe5\x8f\xb7" }, + { 55, "\xe4\xb8\x83\xe5\x8f\xb7" }, + { 65, "\xe5\xb0\x8f\xe5\x85\xad" }, + { 75, "\xe5\x85\xad\xe5\x8f\xb7" }, + { 90, "\xe5\xb0\x8f\xe4\xba\x94" }, + { 105, "\xe4\xba\x94\xe5\x8f\xb7" }, + { 120, "\xe5\xb0\x8f\xe5\x9b\x9b" }, + { 140, "\xe5\x9b\x9b\xe5\x8f\xb7" }, + { 150, "\xe5\xb0\x8f\xe4\xb8\x89" }, + { 160, "\xe4\xb8\x89\xe5\x8f\xb7" }, + { 180, "\xe5\xb0\x8f\xe4\xba\x8c" }, + { 220, "\xe4\xba\x8c\xe5\x8f\xb7" }, + { 240, "\xe5\xb0\x8f\xe4\xb8\x80" }, + { 260, "\xe4\xb8\x80\xe5\x8f\xb7" }, + { 360, "\xe5\xb0\x8f\xe5\x88\x9d" }, + { 420, "\xe5\x88\x9d\xe5\x8f\xb7" } +}; + +FontSizeNames::FontSizeNames( LanguageType eLanguage ) +{ + if ( eLanguage == LANGUAGE_DONTKNOW ) + eLanguage = Application::GetSettings().GetUILanguageTag().getLanguageType(); + if ( eLanguage == LANGUAGE_SYSTEM ) + eLanguage = MsLangId::getSystemUILanguage(); + + if (MsLangId::isSimplifiedChinese(eLanguage)) + { + // equivalent for traditional chinese disabled by popular request, #i89077# + mpArray = aImplSimplifiedChinese; + mnElem = SAL_N_ELEMENTS(aImplSimplifiedChinese); + } + else + { + mpArray = nullptr; + mnElem = 0; + } +} + +sal_Int32 FontSizeNames::Name2Size( const OUString& rName ) const +{ + if ( mnElem ) + { + OString aName(OUStringToOString(rName, + RTL_TEXTENCODING_UTF8)); + + // linear search is sufficient for this rare case + for( long i = mnElem; --i >= 0; ) + if ( aName == mpArray[i].mszUtf8Name ) + return mpArray[i].mnSize; + } + + return 0; +} + +OUString FontSizeNames::Size2Name( sal_Int32 nValue ) const +{ + OUString aStr; + + // binary search + for( long lower = 0, upper = mnElem - 1; lower <= upper; ) + { + long mid = (upper + lower) >> 1; + if ( nValue == mpArray[mid].mnSize ) + { + aStr = OUString( mpArray[mid].mszUtf8Name, strlen(mpArray[mid].mszUtf8Name), RTL_TEXTENCODING_UTF8 ); + break; + } + else if ( nValue < mpArray[mid].mnSize ) + upper = mid - 1; + else /* ( nValue > mpArray[mid].mnSize ) */ + lower = mid + 1; + } + + return aStr; +} + +OUString FontSizeNames::GetIndexName( sal_Int32 nIndex ) const +{ + OUString aStr; + + if ( nIndex < mnElem ) + aStr = OUString( mpArray[nIndex].mszUtf8Name, strlen(mpArray[nIndex].mszUtf8Name), RTL_TEXTENCODING_UTF8 ); + + return aStr; +} + +sal_Int32 FontSizeNames::GetIndexSize( sal_Int32 nIndex ) const +{ + if ( nIndex >= mnElem ) + return 0; + return mpArray[nIndex].mnSize; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/indexentryres.cxx b/svtools/source/control/indexentryres.cxx new file mode 100644 index 000000000..9420a37b2 --- /dev/null +++ b/svtools/source/control/indexentryres.cxx @@ -0,0 +1,58 @@ +/* -*- 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 <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/indexentryres.hxx> + +// implementation of the indexentry-algorithm-name translation +IndexEntryResource::IndexEntryResource() +{ + m_aData.emplace_back("alphanumeric", SvtResId(STR_SVT_INDEXENTRY_ALPHANUMERIC)); + m_aData.emplace_back("dict", SvtResId(STR_SVT_INDEXENTRY_DICTIONARY)); + m_aData.emplace_back("pinyin", SvtResId(STR_SVT_INDEXENTRY_PINYIN)); + m_aData.emplace_back("radical", SvtResId(STR_SVT_INDEXENTRY_RADICAL)); + m_aData.emplace_back("stroke", SvtResId(STR_SVT_INDEXENTRY_STROKE)); + m_aData.emplace_back("zhuyin", SvtResId(STR_SVT_INDEXENTRY_ZHUYIN)); + m_aData.emplace_back("phonetic (alphanumeric first) (grouped by syllable)", SvtResId(STR_SVT_INDEXENTRY_PHONETIC_FS)); + m_aData.emplace_back("phonetic (alphanumeric first) (grouped by consonant)", SvtResId(STR_SVT_INDEXENTRY_PHONETIC_FC)); + m_aData.emplace_back("phonetic (alphanumeric last) (grouped by syllable)", SvtResId(STR_SVT_INDEXENTRY_PHONETIC_LS)); + m_aData.emplace_back("phonetic (alphanumeric last) (grouped by consonant)", SvtResId(STR_SVT_INDEXENTRY_PHONETIC_LC)); +} + +const OUString& IndexEntryResource::GetTranslation(const OUString &r_Algorithm) +{ + sal_Int32 nIndex = r_Algorithm.indexOf('.'); + OUString aLocaleFreeAlgorithm; + + if (nIndex == -1) + aLocaleFreeAlgorithm = r_Algorithm; + else { + nIndex += 1; + aLocaleFreeAlgorithm = r_Algorithm.copy(nIndex); + } + + for (size_t i = 0; i < m_aData.size(); ++i) + if (aLocaleFreeAlgorithm == m_aData[i].GetAlgorithm()) + return m_aData[i].GetTranslation(); + return r_Algorithm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/inettbc.cxx b/svtools/source/control/inettbc.cxx new file mode 100644 index 000000000..9fcb473cc --- /dev/null +++ b/svtools/source/control/inettbc.cxx @@ -0,0 +1,1144 @@ +/* -*- 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 . + */ + +#ifdef UNX +#include <pwd.h> +#endif + +#include <svtools/inettbc.hxx> +#include <tools/diagnose_ex.h> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/NumberedSortingInfo.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/ucb/XAnyCompareFactory.hpp> +#include <com/sun/star/ucb/XCommandProcessor2.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/SortedDynamicResultSetFactory.hpp> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <rtl/instance.hxx> +#include <salhelper/thread.hxx> +#include <tools/debug.hxx> +#include <osl/file.hxx> +#include <osl/mutex.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> +#include <unotools/historyoptions.hxx> +#include <unotools/pathoptions.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/ucbhelper.hxx> +#include <svtools/asynclink.hxx> +#include <svtools/urlfilter.hxx> + +#include <vector> +#include <algorithm> + +using namespace ::ucbhelper; +using namespace ::utl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::uno; + +class SvtURLBox_Impl +{ +public: + std::vector<OUString> aURLs; + std::vector<OUString> aCompletions; + std::vector<WildCard> m_aFilters; + + static bool TildeParsing( OUString& aText, OUString& aBaseUrl ); + + SvtURLBox_Impl( ) + { + FilterMatch::createWildCardFilterList(OUString(),m_aFilters); + } +}; + +class SvtMatchContext_Impl: public salhelper::Thread +{ + static ::osl::Mutex* pDirMutex; + + std::vector<OUString> aPickList; + std::vector<OUString> aCompletions; + std::vector<OUString> aURLs; + svtools::AsynchronLink aLink; + OUString aText; + SvtURLBox* pBox; + bool bOnlyDirectories; + bool bNoSelection; + + osl::Mutex mutex_; + bool stopped_; + css::uno::Reference< css::ucb::XCommandProcessor > processor_; + sal_Int32 commandId_; + + DECL_LINK( Select_Impl, void*, void ); + + virtual ~SvtMatchContext_Impl() override; + virtual void execute() override; + void doExecute(); + void Insert( const OUString& rCompletion, const OUString& rURL, bool bForce = false); + void ReadFolder( const OUString& rURL, const OUString& rMatch, bool bSmart ); + static void FillPicklist(std::vector<OUString>& rPickList); + +public: + SvtMatchContext_Impl( SvtURLBox* pBoxP, const OUString& rText ); + void Stop(); +}; + + +namespace +{ + struct theSvtMatchContextMutex + : public rtl::Static< ::osl::Mutex, theSvtMatchContextMutex > {}; +} + +SvtMatchContext_Impl::SvtMatchContext_Impl(SvtURLBox* pBoxP, const OUString& rText) + : Thread( "MatchContext_Impl" ) + , aLink( LINK( this, SvtMatchContext_Impl, Select_Impl ) ) + , aText( rText ) + , pBox( pBoxP ) + , bOnlyDirectories( pBoxP->bOnlyDirectories ) + , bNoSelection( pBoxP->bNoSelection ) + , stopped_(false) + , commandId_(0) +{ + aLink.CreateMutex(); + + FillPicklist( aPickList ); +} + +SvtMatchContext_Impl::~SvtMatchContext_Impl() +{ + aLink.ClearPendingCall(); +} + +void SvtMatchContext_Impl::FillPicklist(std::vector<OUString>& rPickList) +{ + // Read the history of picks + Sequence< Sequence< PropertyValue > > seqPicklist = SvtHistoryOptions().GetList( ePICKLIST ); + sal_uInt32 nCount = seqPicklist.getLength(); + + for( sal_uInt32 nItem=0; nItem < nCount; nItem++ ) + { + Sequence< PropertyValue > seqPropertySet = seqPicklist[ nItem ]; + + auto pProperty = std::find_if(seqPropertySet.begin(), seqPropertySet.end(), + [](const PropertyValue& rProperty) { return rProperty.Name == HISTORY_PROPERTYNAME_TITLE; }); + if (pProperty != seqPropertySet.end()) + { + OUString sTitle; + INetURLObject aURL; + + pProperty->Value >>= sTitle; + aURL.SetURL( sTitle ); + rPickList.insert(rPickList.begin() + nItem, aURL.GetMainURL(INetURLObject::DecodeMechanism::WithCharset)); + } + } +} + +void SvtMatchContext_Impl::Stop() +{ + css::uno::Reference< css::ucb::XCommandProcessor > proc; + sal_Int32 id(0); + { + osl::MutexGuard g(mutex_); + if (!stopped_) { + stopped_ = true; + proc = processor_; + id = commandId_; + } + } + if (proc.is()) { + proc->abort(id); + } + terminate(); +} + +void SvtMatchContext_Impl::execute( ) +{ + doExecute(); + aLink.Call( this ); +} + + +// This method is called via AsynchronLink, so it has the SolarMutex and +// calling solar code ( VCL ... ) is safe. It is called when the thread is +// terminated ( finished work or stopped ). Cancelling the thread via +// Cancellable does not discard the information gained so far, it +// inserts all collected completions into the listbox. + +IMPL_LINK_NOARG( SvtMatchContext_Impl, Select_Impl, void*, void ) +{ + // avoid recursion through cancel button + { + osl::MutexGuard g(mutex_); + if (stopped_) { + // Completion was stopped, no display: + return; + } + } + + // insert all completed strings into the listbox + pBox->clear(); + + for (auto const& completion : aCompletions) + { + // convert the file into a URL + OUString sURL; + osl::FileBase::getFileURLFromSystemPath(completion, sURL); + // note: if this doesn't work, we're not interested in: we're checking the + // untouched sCompletion then + + if ( !sURL.isEmpty() && !sURL.endsWith("/") ) + { + OUString sUpperURL( sURL.toAsciiUpperCase() ); + + if ( ::std::none_of( pBox->pImpl->m_aFilters.begin(), + pBox->pImpl->m_aFilters.end(), + FilterMatch( sUpperURL ) ) ) + { // this URL is not allowed + continue; + } + } + + pBox->append_text(completion); + } + + pBox->EnableAutocomplete(!bNoSelection); + + // transfer string lists to listbox and forget them + pBox->pImpl->aURLs = aURLs; + pBox->pImpl->aCompletions = aCompletions; + aURLs.clear(); + aCompletions.clear(); + + // the box has this control as a member so we have to set that member + // to zero before deleting ourself. + pBox->pCtx.clear(); +} + +void SvtMatchContext_Impl::Insert( const OUString& rCompletion, + const OUString& rURL, + bool bForce ) +{ + if( !bForce ) + { + // avoid doubles + if(find(aCompletions.begin(), aCompletions.end(), rCompletion) != aCompletions.end()) + return; + } + + aCompletions.push_back(rCompletion); + aURLs.push_back(rURL); +} + + +void SvtMatchContext_Impl::ReadFolder( const OUString& rURL, + const OUString& rMatch, + bool bSmart ) +{ + // check folder to scan + if( !UCBContentHelper::IsFolder( rURL ) ) + return; + + bool bPureHomePath = false; +#ifdef UNX + bPureHomePath = aText.startsWith( "~" ) && aText.indexOf( '/' ) == -1; +#endif + + bool bExectMatch = bPureHomePath + || aText == "." + || aText.endsWith("/.") + || aText.endsWith("/.."); + + // for pure home paths ( ~username ) the '.' at the end of rMatch + // means that it points to root catalog + // this is done only for file contents since home paths parsing is useful only for them + if ( bPureHomePath && rMatch == "file:///." ) + { + // a home that refers to / + + OUString aNewText = aText + "/"; + Insert( aNewText, rURL, true ); + + return; + } + + // string to match with + INetURLObject aMatchObj( rMatch ); + OUString aMatchName; + + if ( rURL != aMatchObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) + { + aMatchName = aMatchObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + + // matching is always done case insensitive, but completion will be case sensitive and case preserving + aMatchName = aMatchName.toAsciiLowerCase(); + + // if the matchstring ends with a slash, we must search for this also + if ( rMatch.endsWith("/") ) + aMatchName += "/"; + } + + sal_Int32 nMatchLen = aMatchName.getLength(); + + INetURLObject aFolderObj( rURL ); + DBG_ASSERT( aFolderObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" ); + + try + { + Content aCnt( aFolderObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + new ::ucbhelper::CommandEnvironment( uno::Reference< XInteractionHandler >(), + uno::Reference< XProgressHandler >() ), + comphelper::getProcessComponentContext() ); + uno::Reference< XResultSet > xResultSet; + Sequence< OUString > aProps(2); + OUString* pProps = aProps.getArray(); + pProps[0] = "Title"; + pProps[1] = "IsFolder"; + + try + { + ResultSetInclude eInclude = INCLUDE_FOLDERS_AND_DOCUMENTS; + if ( bOnlyDirectories ) + eInclude = INCLUDE_FOLDERS_ONLY; + uno::Reference< XDynamicResultSet > xDynResultSet = aCnt.createDynamicCursor( aProps, eInclude ); + + uno::Reference < XAnyCompareFactory > xCompare; + uno::Reference < XSortedDynamicResultSetFactory > xSRSFac = + SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() ); + + Sequence< NumberedSortingInfo > aSortInfo( 2 ); + NumberedSortingInfo* pInfo = aSortInfo.getArray(); + pInfo[ 0 ].ColumnIndex = 2; + pInfo[ 0 ].Ascending = false; + pInfo[ 1 ].ColumnIndex = 1; + pInfo[ 1 ].Ascending = true; + + uno::Reference< XDynamicResultSet > xDynamicResultSet = + xSRSFac->createSortedDynamicResultSet( xDynResultSet, aSortInfo, xCompare ); + + if ( xDynamicResultSet.is() ) + { + xResultSet = xDynamicResultSet->getStaticResultSet(); + } + } + catch( css::uno::Exception& ) {} + + if ( xResultSet.is() ) + { + uno::Reference< XRow > xRow( xResultSet, UNO_QUERY ); + uno::Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY ); + + try + { + while ( schedule() && xResultSet->next() ) + { + OUString aURL = xContentAccess->queryContentIdentifierString(); + OUString aTitle = xRow->getString(1); + bool bIsFolder = xRow->getBoolean(2); + + // matching is always done case insensitive, but completion will be case sensitive and case preserving + aTitle = aTitle.toAsciiLowerCase(); + + if ( + !nMatchLen || + (bExectMatch && aMatchName == aTitle) || + (!bExectMatch && aTitle.startsWith(aMatchName)) + ) + { + // all names fit if matchstring is empty + INetURLObject aObj( aURL ); + sal_Unicode aDelimiter = '/'; + if ( bSmart ) + // when parsing is done "smart", the delimiter must be "guessed" + aObj.getFSysPath( static_cast<FSysStyle>(FSysStyle::Detect & ~FSysStyle::Vos), &aDelimiter ); + + if ( bIsFolder ) + aObj.setFinalSlash(); + + // get the last name of the URL + OUString aMatch = aObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + OUString aInput( aText ); + if ( nMatchLen ) + { + if (aText.endsWith(".") || bPureHomePath) + { + // if a "special folder" URL was typed, don't touch the user input + aMatch = aMatch.copy( nMatchLen ); + } + else + { + // make the user input case preserving + DBG_ASSERT( aInput.getLength() >= nMatchLen, "Suspicious Matching!" ); + aInput = aInput.copy( 0, aInput.getLength() - nMatchLen ); + } + } + + aInput += aMatch; + + // folders should get a final slash automatically + if ( bIsFolder ) + aInput += OUStringChar(aDelimiter); + + Insert( aInput, aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), true ); + } + } + } + catch( css::uno::Exception& ) + { + } + } + } + catch( css::uno::Exception& ) + { + } +} + +void SvtMatchContext_Impl::doExecute() +{ + ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() ); + { + // have we been stopped while we were waiting for the mutex? + osl::MutexGuard g(mutex_); + if (stopped_) { + return; + } + } + + // Reset match lists + aCompletions.clear(); + aURLs.clear(); + + // check for input + if ( aText.isEmpty() ) + return; + + if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 ) + // no autocompletion for wildcards + return; + + OUString aMatch; + INetProtocol eProt = INetURLObject::CompareProtocolScheme( aText ); + INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( pBox->aBaseURL ); + if ( pBox->aBaseURL.isEmpty() ) + eBaseProt = INetURLObject::CompareProtocolScheme( SvtPathOptions().GetWorkPath() ); + INetProtocol eSmartProt = pBox->GetSmartProtocol(); + + // if the user input is a valid URL, go on with it + // otherwise it could be parsed smart with a predefined smart protocol + // ( or if this is not set with the protocol of a predefined base URL ) + if( eProt == INetProtocol::NotValid || eProt == eSmartProt || (eSmartProt == INetProtocol::NotValid && eProt == eBaseProt) ) + { + // not stopped yet ? + if( schedule() ) + { + if ( eProt == INetProtocol::NotValid ) + aMatch = SvtURLBox::ParseSmart( aText, pBox->aBaseURL ); + else + aMatch = aText; + if ( !aMatch.isEmpty() ) + { + INetURLObject aURLObject( aMatch ); + OUString aMainURL( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + // Disable autocompletion for anything but the (local) file + // system (for which access is hopefully fast), as the logic of + // how SvtMatchContext_Impl is used requires this code to run to + // completion before further user input is processed, and even + // SvtMatchContext_Impl::Stop does not guarantee a speedy + // return: + if ( !aMainURL.isEmpty() + && aURLObject.GetProtocol() == INetProtocol::File ) + { + // if text input is a directory, it must be part of the match list! Until then it is scanned + bool folder = false; + if (aURLObject.hasFinalSlash()) { + try { + css::uno::Reference< css::uno::XComponentContext > + ctx(comphelper::getProcessComponentContext()); + css::uno::Reference< + css::ucb::XUniversalContentBroker > ucb( + css::ucb::UniversalContentBroker::create( + ctx)); + css::uno::Sequence< css::beans::Property > prop(1); + prop[0].Name = "IsFolder"; + prop[0].Handle = -1; + prop[0].Type = cppu::UnoType< bool >::get(); + css::uno::Any res; + css::uno::Reference< css::ucb::XCommandProcessor > + proc( + ucb->queryContent( + ucb->createContentIdentifier(aMainURL)), + css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::ucb::XCommandProcessor2 > + proc2(proc, css::uno::UNO_QUERY); + sal_Int32 id = proc->createCommandIdentifier(); + try { + { + osl::MutexGuard g(mutex_); + processor_ = proc; + commandId_ = id; + } + res = proc->execute( + css::ucb::Command( + "getPropertyValues", -1, + css::uno::makeAny(prop)), + id, + css::uno::Reference< + css::ucb::XCommandEnvironment >()); + } catch (...) { + if (proc2.is()) { + try { + proc2->releaseCommandIdentifier(id); + } catch (css::uno::RuntimeException &) { + TOOLS_WARN_EXCEPTION("svtools.control", "ignoring"); + } + } + throw; + } + if (proc2.is()) { + proc2->releaseCommandIdentifier(id); + } + { + osl::MutexGuard g(mutex_); + processor_.clear(); + // At least the neon-based WebDAV UCP does not + // properly support aborting commands, so return + // anyway now if an abort request had been + // ignored and the command execution only + // returned "successfully" after some timeout: + if (stopped_) { + return; + } + } + css::uno::Reference< css::sdbc::XRow > row( + res, css::uno::UNO_QUERY_THROW); + folder = row->getBoolean(1) && !row->wasNull(); + } catch (css::uno::Exception &) { + TOOLS_WARN_EXCEPTION("svtools.control", "ignoring"); + return; + } + } + if (folder) + Insert( aText, aMatch ); + else + // otherwise the parent folder will be taken + aURLObject.removeSegment(); + + // scan directory and insert all matches + ReadFolder( aURLObject.GetMainURL( INetURLObject::DecodeMechanism::NONE ), aMatch, eProt == INetProtocol::NotValid ); + } + } + } + } + + if ( bOnlyDirectories ) + // don't scan history picklist if only directories are allowed, picklist contains only files + return; + + bool bFull = false; + + INetURLObject aCurObj; + OUString aCurString, aCurMainURL; + INetURLObject aObj; + aObj.SetSmartProtocol( eSmartProt == INetProtocol::NotValid ? INetProtocol::Http : eSmartProt ); + for( ;; ) + { + for(const auto& rPick : aPickList) + { + if (!schedule()) + break; + + aCurObj.SetURL(rPick); + aCurObj.SetSmartURL( aCurObj.GetURLNoPass()); + aCurMainURL = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + if( eProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eProt ) + continue; + + if( eSmartProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eSmartProt ) + continue; + + switch( aCurObj.GetProtocol() ) + { + case INetProtocol::Http: + case INetProtocol::Https: + case INetProtocol::Ftp: + { + if( eProt == INetProtocol::NotValid && !bFull ) + { + aObj.SetSmartURL( aText ); + if( aObj.GetURLPath().getLength() > 1 ) + continue; + } + + aCurString = aCurMainURL; + if( eProt == INetProtocol::NotValid ) + { + // try if text matches the scheme + OUString aScheme( INetURLObject::GetScheme( aCurObj.GetProtocol() ) ); + if ( aScheme.startsWithIgnoreAsciiCase( aText ) && aText.getLength() < aScheme.getLength() ) + { + if( bFull ) + aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + else + { + aCurObj.SetMark( "" ); + aCurObj.SetParam( "" ); + aCurObj.SetURLPath( "" ); + aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + Insert( aMatch, aMatch ); + } + + // now try smart matching + aCurString = aCurString.copy( aScheme.getLength() ); + } + + if( aCurString.startsWithIgnoreAsciiCase( aText ) ) + { + if( bFull ) + aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + else + { + aCurObj.SetMark( "" ); + aCurObj.SetParam( "" ); + aCurObj.SetURLPath( "" ); + aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + + OUString aURL( aMatch ); + if( eProt == INetProtocol::NotValid ) + aMatch = aMatch.copy( INetURLObject::GetScheme( aCurObj.GetProtocol() ).getLength() ); + + if( aText.getLength() < aMatch.getLength() ) + Insert( aMatch, aURL ); + + continue; + } + break; + } + default: + { + if( bFull ) + continue; + + if( aCurMainURL.startsWith(aText) ) + { + if( aText.getLength() < aCurMainURL.getLength() ) + Insert( aCurMainURL, aCurMainURL ); + + continue; + } + break; + } + } + } + + if( !bFull ) + bFull = true; + else + break; + } +} + +/** Parse leading ~ for Unix systems, + does nothing for Windows + */ +bool SvtURLBox_Impl::TildeParsing( + OUString& +#ifdef UNX + aText +#endif + , OUString& +#ifdef UNX + aBaseURL +#endif +) +{ +#ifdef UNX + if( aText.startsWith( "~" ) ) + { + OUString aParseTilde; + bool bTrailingSlash = true; // use trailing slash + + if( aText.getLength() == 1 || aText[ 1 ] == '/' ) + { + // covers "~" or "~/..." cases + const char* aHomeLocation = getenv( "HOME" ); + if( !aHomeLocation ) + aHomeLocation = ""; + + aParseTilde = OUString::createFromAscii(aHomeLocation); + + // in case the whole path is just "~" then there should + // be no trailing slash at the end + if( aText.getLength() == 1 ) + bTrailingSlash = false; + } + else + { + // covers "~username" and "~username/..." cases + sal_Int32 nNameEnd = aText.indexOf( '/' ); + OUString aUserName = aText.copy( 1, ( nNameEnd != -1 ) ? nNameEnd : ( aText.getLength() - 1 ) ); + + struct passwd* pPasswd = nullptr; +#ifdef __sun + Sequence< sal_Int8 > sBuf( 1024 ); + struct passwd aTmp; + sal_Int32 nRes = getpwnam_r( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr(), + &aTmp, + (char*)sBuf.getArray(), + 1024, + &pPasswd ); + if( !nRes && pPasswd ) + aParseTilde = OUString::createFromAscii(pPasswd->pw_dir); + else + return false; // no such user +#else + pPasswd = getpwnam( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr() ); + if( pPasswd ) + aParseTilde = OUString::createFromAscii(pPasswd->pw_dir); + else + return false; // no such user +#endif + + // in case the path is "~username" then there should + // be no trailing slash at the end + if( nNameEnd == -1 ) + bTrailingSlash = false; + } + + if( !bTrailingSlash ) + { + if( aParseTilde.isEmpty() || aParseTilde == "/" ) + { + // "/" path should be converted to "/." + aParseTilde = "/."; + } + else + { + // "blabla/" path should be converted to "blabla" + aParseTilde = comphelper::string::stripEnd(aParseTilde, '/'); + } + } + else + { + if( !aParseTilde.endsWith("/") ) + aParseTilde += "/"; + if( aText.getLength() > 2 ) + aParseTilde += aText.copy( 2 ); + } + + aText = aParseTilde; + aBaseURL.clear(); // tilde provide absolute path + } +#endif + + return true; +} + +//-- + +OUString SvtURLBox::ParseSmart( const OUString& _aText, const OUString& _aBaseURL ) +{ + OUString aMatch; + OUString aText = _aText; + OUString aBaseURL = _aBaseURL; + + // parse ~ for Unix systems + // does nothing for Windows + if( !SvtURLBox_Impl::TildeParsing( aText, aBaseURL ) ) + return OUString(); + + if( !aBaseURL.isEmpty() ) + { + INetProtocol eBaseProt = INetURLObject::CompareProtocolScheme( aBaseURL ); + + // if a base URL is set the string may be parsed relative + if( aText.startsWith( "/" ) ) + { + // text starting with slashes means absolute file URLs + OUString aTemp = INetURLObject::GetScheme( eBaseProt ); + + // file URL must be correctly encoded! + OUString aTextURL = INetURLObject::encode( aText, INetURLObject::PART_FPATH, + INetURLObject::EncodeMechanism::All ); + aTemp += aTextURL; + + INetURLObject aTmp( aTemp ); + if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid ) + aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + else + { + OUString aSmart( aText ); + INetURLObject aObj( aBaseURL ); + + // HRO: I suppose this hack should only be done for Windows !!!??? +#ifdef _WIN32 + // HRO: INetURLObject::smatRel2Abs does not recognize '\\' as a relative path + // but in case of "\\\\" INetURLObject is right - this is an absolute path ! + + if( aText.startsWith("\\") && (aText.getLength() < 2 || aText[ 1 ] != '\\') ) + { + // cut to first segment + OUString aTmp = INetURLObject::GetScheme( eBaseProt ) + "/"; + aTmp += aObj.getName( 0, true, INetURLObject::DecodeMechanism::WithCharset ); + aObj.SetURL( aTmp ); + + aSmart = aSmart.copy(1); + } +#endif + // base URL must be a directory ! + aObj.setFinalSlash(); + + // take base URL and append current input + bool bWasAbsolute = false; +#ifdef UNX + // encode file URL correctly + aSmart = INetURLObject::encode( aSmart, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All ); +#endif + INetURLObject aTmp( aObj.smartRel2Abs( aSmart, bWasAbsolute ) ); + + if ( aText.endsWith(".") ) + // INetURLObject appends a final slash for the directories "." and "..", this is a bug! + // Remove it as a workaround + aTmp.removeFinalSlash(); + if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid ) + aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + } + } + else + { + OUString aTmpMatch; + osl::FileBase::getFileURLFromSystemPath( aText, aTmpMatch ); + aMatch = aTmpMatch; + } + + return aMatch; +} + +IMPL_LINK_NOARG(SvtURLBox, TryAutoComplete, Timer *, void) +{ + OUString aCurText = m_xWidget->get_active_text(); + int nStartPos, nEndPos; + m_xWidget->get_entry_selection_bounds(nStartPos, nEndPos); + if (std::max(nStartPos, nEndPos) != aCurText.getLength()) + return; + + auto nLen = std::min(nStartPos, nEndPos); + aCurText = aCurText.copy( 0, nLen ); + if (!aCurText.isEmpty()) + { + if (pCtx.is()) + { + pCtx->Stop(); + pCtx->join(); + pCtx.clear(); + } + pCtx = new SvtMatchContext_Impl(this, aCurText); + pCtx->launch(); + } + else + m_xWidget->clear(); +} + +SvtURLBox::SvtURLBox(std::unique_ptr<weld::ComboBox> pWidget) + : eSmartProtocol(INetProtocol::NotValid) + , bOnlyDirectories( false ) + , bHistoryDisabled( false ) + , bNoSelection( false ) + , m_xWidget(std::move(pWidget)) +{ + //don't grow to fix mega-long urls + Size aSize(m_xWidget->get_preferred_size()); + m_xWidget->set_size_request(aSize.Width(), -1); + + Init(); + + m_xWidget->connect_focus_in(LINK(this, SvtURLBox, FocusInHdl)); + m_xWidget->connect_focus_out(LINK(this, SvtURLBox, FocusOutHdl)); + m_xWidget->connect_changed(LINK(this, SvtURLBox, ChangedHdl)); + + aChangedIdle.SetInvokeHandler(LINK(this, SvtURLBox, TryAutoComplete)); + aChangedIdle.SetDebugName("svtools::URLBox aChangedIdle"); +} + +void SvtURLBox::Init() +{ + pImpl.reset( new SvtURLBox_Impl ); + + m_xWidget->set_entry_completion(false); + + UpdatePicklistForSmartProtocol_Impl(); +} + +SvtURLBox::~SvtURLBox() +{ + if (pCtx.is()) + { + pCtx->Stop(); + pCtx->join(); + } +} + +void SvtURLBox::SetSmartProtocol(INetProtocol eProt) +{ + if ( eSmartProtocol != eProt ) + { + eSmartProtocol = eProt; + UpdatePicklistForSmartProtocol_Impl(); + } +} + +void SvtURLBox::UpdatePicklistForSmartProtocol_Impl() +{ + m_xWidget->clear(); + if ( bHistoryDisabled ) + return; + + if (bHistoryDisabled) + return; + + // read history pick list + const Sequence< Sequence< PropertyValue > > seqPicklist = SvtHistoryOptions().GetList( ePICKLIST ); + INetURLObject aCurObj; + + for( const Sequence< PropertyValue >& rPropertySet : seqPicklist ) + { + auto pProperty = std::find_if(rPropertySet.begin(), rPropertySet.end(), + [](const PropertyValue& rProperty) { return rProperty.Name == HISTORY_PROPERTYNAME_URL; }); + if (pProperty != rPropertySet.end()) + { + OUString sURL; + + pProperty->Value >>= sURL; + aCurObj.SetURL( sURL ); + + if ( !sURL.isEmpty() && ( eSmartProtocol != INetProtocol::NotValid ) ) + { + if( aCurObj.GetProtocol() != eSmartProtocol ) + continue; + } + + OUString aURL( aCurObj.GetMainURL( INetURLObject::DecodeMechanism::WithCharset ) ); + + if ( !aURL.isEmpty() ) + { + bool bFound = aURL.endsWith("/"); + if ( !bFound ) + { + OUString aUpperURL = aURL.toAsciiUpperCase(); + + bFound = ::std::any_of(pImpl->m_aFilters.begin(), + pImpl->m_aFilters.end(), + FilterMatch( aUpperURL ) ); + } + if ( bFound ) + { + OUString aFile; + if (osl::FileBase::getSystemPathFromFileURL(aURL, aFile) == osl::FileBase::E_None) + m_xWidget->append_text(aFile); + else + m_xWidget->append_text(aURL); + } + } + } + } +} + +IMPL_LINK_NOARG(SvtURLBox, ChangedHdl, weld::ComboBox&, void) +{ + aChangeHdl.Call(*m_xWidget); + aChangedIdle.Start(); //launch this to happen on idle after cursor position will have been set +} + +IMPL_LINK_NOARG(SvtURLBox, FocusInHdl, weld::Widget&, void) +{ +#ifndef UNX + // pb: don't select automatically on unix #93251# + m_xWidget->select_entry_region(0, -1); +#endif + aFocusInHdl.Call(*m_xWidget); +} + +IMPL_LINK_NOARG(SvtURLBox, FocusOutHdl, weld::Widget&, void) +{ + if (pCtx.is()) + { + pCtx->Stop(); + pCtx->join(); + pCtx.clear(); + } + aFocusOutHdl.Call(*m_xWidget); +} + +void SvtURLBox::SetOnlyDirectories( bool bDir ) +{ + bOnlyDirectories = bDir; + if ( bOnlyDirectories ) + m_xWidget->clear(); +} + +void SvtURLBox::SetNoURLSelection( bool bSet ) +{ + bNoSelection = bSet; +} + +OUString SvtURLBox::GetURL() +{ + // wait for end of autocompletion + ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() ); + + OUString aText(m_xWidget->get_active_text()); + if (MatchesPlaceHolder(aText)) + return aPlaceHolder; + + // try to get the right case preserving URL from the list of URLs + for(std::vector<OUString>::iterator i = pImpl->aCompletions.begin(), j = pImpl->aURLs.begin(); i != pImpl->aCompletions.end() && j != pImpl->aURLs.end(); ++i, ++j) + { + if((*i) == aText) + return *j; + } + +#ifdef _WIN32 + // erase trailing spaces on Windows since they are invalid on this OS and + // most of the time they are inserted by accident via copy / paste + aText = comphelper::string::stripEnd(aText, ' '); + if ( aText.isEmpty() ) + return aText; + // #i9739# +#endif + + INetURLObject aObj( aText ); + if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 ) + { + // no autocompletion for wildcards + INetURLObject aTempObj; + if ( eSmartProtocol != INetProtocol::NotValid ) + aTempObj.SetSmartProtocol( eSmartProtocol ); + if ( aTempObj.SetSmartURL( aText ) ) + return aTempObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + else + return aText; + } + + if ( aObj.GetProtocol() == INetProtocol::NotValid ) + { + OUString aName = ParseSmart( aText, aBaseURL ); + aObj.SetURL(aName); + OUString aURL( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + if ( aURL.isEmpty() ) + // aText itself is invalid, and even together with aBaseURL, it could not + // made valid -> no chance + return aText; + + bool bSlash = aObj.hasFinalSlash(); + { + const OUString aPropName("CasePreservingURL"); + + OUString aFileURL; + + Any aAny = UCBContentHelper::GetProperty(aURL, aPropName); + bool success = (aAny >>= aFileURL); + OUString aTitle; + if(success) + aTitle = INetURLObject(aFileURL).getName( + INetURLObject::LAST_SEGMENT, + true, + INetURLObject::DecodeMechanism::WithCharset ); + else + success = + UCBContentHelper::GetTitle(aURL,&aTitle); + + if( success && aTitle != "/" && aTitle != "." ) + { + aObj.setName( aTitle ); + if ( bSlash ) + aObj.setFinalSlash(); + } + } + } + + return aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); +} + +void SvtURLBox::SetBaseURL( const OUString& rURL ) +{ + ::osl::MutexGuard aGuard( theSvtMatchContextMutex::get() ); + + // Reset match lists + pImpl->aCompletions.clear(); + pImpl->aURLs.clear(); + + aBaseURL = rURL; +} + +void SvtURLBox::DisableHistory() +{ + bHistoryDisabled = true; + UpdatePicklistForSmartProtocol_Impl(); +} + +void SvtURLBox::SetFilter(const OUString& _sFilter) +{ + pImpl->m_aFilters.clear(); + FilterMatch::createWildCardFilterList(_sFilter,pImpl->m_aFilters); +} + +void FilterMatch::createWildCardFilterList(const OUString& _rFilterList,::std::vector< WildCard >& _rFilters) +{ + if( _rFilterList.getLength() ) + { + // filter is given + sal_Int32 nIndex = 0; + OUString sToken; + do + { + sToken = _rFilterList.getToken( 0, ';', nIndex ); + if ( !sToken.isEmpty() ) + { + _rFilters.emplace_back( sToken.toAsciiUpperCase() ); + } + } + while ( nIndex >= 0 ); + } + else + { + // no filter is given -> match all + _rFilters.emplace_back("*" ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/managedmenubutton.cxx b/svtools/source/control/managedmenubutton.cxx new file mode 100644 index 000000000..45e9d98ca --- /dev/null +++ b/svtools/source/control/managedmenubutton.cxx @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <toolkit/awt/vclxmenu.hxx> +#include <vcl/builderfactory.hxx> +#include <vcl/menu.hxx> +#include <vcl/menubtn.hxx> + +#include <com/sun/star/frame/theDesktop.hpp> +#include <com/sun/star/frame/ModuleManager.hpp> +#include <com/sun/star/frame/thePopupMenuControllerFactory.hpp> +#include <com/sun/star/frame/XPopupMenuController.hpp> + +namespace { + +class ManagedMenuButton : public MenuButton +{ +public: + ManagedMenuButton(vcl::Window* pParent, WinBits nStyle); + ~ManagedMenuButton() override; + + void Activate() override; + void dispose() override; + +private: + rtl::Reference<VCLXPopupMenu> m_xPopupMenu; + css::uno::Reference<css::frame::XPopupMenuController> m_xPopupController; +}; + +ManagedMenuButton::ManagedMenuButton(vcl::Window* pParent, WinBits nStyle) + : MenuButton(pParent, nStyle) +{ + SetImageAlign(ImageAlign::Left); +} + +ManagedMenuButton::~ManagedMenuButton() +{ + disposeOnce(); +} + +void ManagedMenuButton::dispose() +{ + css::uno::Reference<css::lang::XComponent> xComponent(m_xPopupController, css::uno::UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + + m_xPopupMenu.clear(); + m_xPopupController.clear(); + MenuButton::dispose(); +} + +void ManagedMenuButton::Activate() +{ + if (!GetPopupMenu()) + SetPopupMenu(VclPtr<PopupMenu>::Create()); + + MenuButton::Activate(); + + if (m_xPopupController.is()) + { + m_xPopupController->updatePopupMenu(); + return; + } + + if (!m_xPopupMenu.is()) + m_xPopupMenu.set(new VCLXPopupMenu(GetPopupMenu())); + + // FIXME: get the frame from the parent VclBuilder. + css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + css::uno::Reference<css::frame::XDesktop2> xDesktop(css::frame::theDesktop::get(xContext)); + css::uno::Reference<css::frame::XFrame> xFrame(xDesktop->getActiveFrame()); + if (!xFrame.is()) + return; + + OUString aModuleName; + try + { + css::uno::Reference<css::frame::XModuleManager> xModuleManager(css::frame::ModuleManager::create(xContext)); + aModuleName = xModuleManager->identify(xFrame); + } + catch( const css::uno::Exception& ) + {} + + css::uno::Sequence<css::uno::Any> aArgs { + css::uno::makeAny(comphelper::makePropertyValue("ModuleIdentifier", aModuleName)), + css::uno::makeAny(comphelper::makePropertyValue("Frame", css::uno::makeAny(xFrame))), + css::uno::makeAny(comphelper::makePropertyValue("InToolbar", css::uno::makeAny(true))) + }; + + const OUString aCommand(GetCommand()); + if (!aCommand.isEmpty() && GetPopupMenu()->GetItemCount() == 0) + { + css::uno::Reference<css::frame::XUIControllerFactory> xPopupMenuControllerFactory = + css::frame::thePopupMenuControllerFactory::get(xContext); + + if (xPopupMenuControllerFactory->hasController(aCommand, aModuleName)) + m_xPopupController.set(xPopupMenuControllerFactory->createInstanceWithArgumentsAndContext( + aCommand, aArgs, xContext), css::uno::UNO_QUERY); + } + + // No registered controller found, use one the can handle arbitrary menus (e.g. defined in .ui file). + if (!m_xPopupController.is()) + m_xPopupController.set(xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.framework.ResourceMenuController", aArgs, xContext), css::uno::UNO_QUERY); + + if (m_xPopupController.is()) + m_xPopupController->setPopupMenu(m_xPopupMenu.get()); +} + +} + +VCL_BUILDER_FACTORY_ARGS(ManagedMenuButton, WB_CLIPCHILDREN|WB_CENTER|WB_VCENTER|WB_FLATBUTTON) + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/svtools/source/control/ruler.cxx b/svtools/source/control/ruler.cxx new file mode 100644 index 000000000..8608adc3b --- /dev/null +++ b/svtools/source/control/ruler.cxx @@ -0,0 +1,2774 @@ +/* -*- 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 <tools/debug.hxx> +#include <tools/poly.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/vcllayout.hxx> +#include <vcl/virdev.hxx> +#include <vcl/ptrstyle.hxx> +#include <sal/log.hxx> + +#include <svtools/ruler.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <svtools/colorcfg.hxx> +#include "accessibleruler.hxx" + +#include <memory> +#include <vector> + +using namespace std; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::accessibility; + +#define RULER_OFF 3 +#define RULER_RESIZE_OFF 4 +#define RULER_MIN_SIZE 3 + +#define RULER_VAR_SIZE 8 + +#define RULER_UPDATE_LINES 0x01 + +#define RULER_CLIP 150 + +#define RULER_UNIT_MM 0 +#define RULER_UNIT_CM 1 +#define RULER_UNIT_M 2 +#define RULER_UNIT_KM 3 +#define RULER_UNIT_INCH 4 +#define RULER_UNIT_FOOT 5 +#define RULER_UNIT_MILE 6 +#define RULER_UNIT_POINT 7 +#define RULER_UNIT_PICA 8 +#define RULER_UNIT_CHAR 9 +#define RULER_UNIT_LINE 10 +#define RULER_UNIT_COUNT 11 + +namespace +{ +/** + * Pre-calculates glyph items for rText on rRenderContext. Subsequent calls + * avoid the calculation and just return a pointer to rTextGlyphs. + */ +SalLayoutGlyphs* lcl_GetRulerTextGlyphs(const vcl::RenderContext& rRenderContext, const OUString& rText, + SalLayoutGlyphs& rTextGlyphs) +{ + if (rTextGlyphs.IsValid()) + // Use pre-calculated result. + return &rTextGlyphs; + + // Calculate glyph items. + + std::unique_ptr<SalLayout> pLayout = rRenderContext.ImplLayout( + rText, 0, rText.getLength(), Point(0, 0), 0, nullptr, SalLayoutFlags::GlyphItemsOnly); + if (!pLayout) + return nullptr; + + const SalLayoutGlyphs* pGlyphs = pLayout->GetGlyphs(); + if (!pGlyphs) + return nullptr; + + // Remember the calculation result. + rTextGlyphs = *pGlyphs; + + return &rTextGlyphs; +} +} + +class ImplRulerData +{ + friend class Ruler; + +private: + vector<RulerLine> pLines; + vector<RulerBorder> pBorders; + vector<RulerIndent> pIndents; + vector<RulerTab> pTabs; + + long nNullVirOff; + long nRulVirOff; + long nRulWidth; + long nPageOff; + long nPageWidth; + long nNullOff; + long nMargin1; + long nMargin2; + // In this context, "frame margin" means paragraph margins (indents) + long nLeftFrameMargin; + long nRightFrameMargin; + RulerMarginStyle nMargin1Style; + RulerMarginStyle nMargin2Style; + bool bAutoPageWidth; + bool bTextRTL; + +public: + ImplRulerData(); +}; + +ImplRulerData::ImplRulerData() : + nNullVirOff (0), + nRulVirOff (0), + nRulWidth (0), + nPageOff (0), + nPageWidth (0), + nNullOff (0), + nMargin1 (0), + nMargin2 (0), + nLeftFrameMargin (0), + nRightFrameMargin (0), + nMargin1Style (RulerMarginStyle::NONE), + nMargin2Style (RulerMarginStyle::NONE), + bAutoPageWidth (true), // Page width == EditWin width + bTextRTL (false) +{ +} + +static const RulerUnitData aImplRulerUnitTab[RULER_UNIT_COUNT] = +{ +{ MapUnit::Map100thMM, 100, 25.0, 25.0, 50.0, 100.0, " mm" }, // MM +{ MapUnit::Map100thMM, 1000, 100.0, 500.0, 1000.0, 1000.0, " cm" }, // CM +{ MapUnit::MapMM, 1000, 10.0, 250.0, 500.0, 1000.0, " m" }, // M +{ MapUnit::MapCM, 100000, 12500.0, 25000.0, 50000.0, 100000.0, " km" }, // KM +{ MapUnit::Map1000thInch, 1000, 62.5, 125.0, 500.0, 1000.0, "\"" }, // INCH +{ MapUnit::Map100thInch, 1200, 120.0, 120.0, 600.0, 1200.0, "'" }, // FOOT +{ MapUnit::Map10thInch, 633600, 63360.0, 63360.0, 316800.0, 633600.0, " miles" }, // MILE +{ MapUnit::MapPoint, 1, 12.0, 12.0, 12.0, 36.0, " pt" }, // POINT +{ MapUnit::Map100thMM, 423, 423.0, 423.0, 423.0, 846.0, " pc" }, // PICA +{ MapUnit::Map100thMM, 371, 371.0, 371.0, 371.0, 743.0, " ch" }, // CHAR +{ MapUnit::Map100thMM, 551, 551.0, 551.0, 551.0, 1102.0, " li" } // LINE +}; + +static RulerTabData ruler_tab = +{ + 0, // DPIScaleFactor to be set + 7, // ruler_tab_width + 6, // ruler_tab_height + 2, // ruler_tab_height2 + 2, // ruler_tab_width2 + 8, // ruler_tab_cwidth + 4, // ruler_tab_cwidth2 + 4, // ruler_tab_cwidth3 + 2, // ruler_tab_cwidth4 + 4, // ruler_tab_dheight + 1, // ruler_tab_dheight2 + 5, // ruler_tab_dwidth + 3, // ruler_tab_dwidth2 + 3, // ruler_tab_dwidth3 + 1, // ruler_tab_dwidth4 + 5 // ruler_tab_textoff +}; + +void Ruler::ImplInit( WinBits nWinBits ) +{ + // Set default WinBits + if ( !(nWinBits & WB_VERT) ) + { + nWinBits |= WB_HORZ; + + // RTL: no UI mirroring for horizontal rulers, because + // the document is also not mirrored + EnableRTL( false ); + } + + // Initialize variables + mnWinStyle = nWinBits; // Window-Style + mnBorderOff = 0; // Border-Offset + mnWinOff = 0; // EditWinOffset + mnWinWidth = 0; // EditWinWidth + mnWidth = 0; // Window width + mnHeight = 0; // Window height + mnVirOff = 0; // Offset of VirtualDevice from top-left corner + mnVirWidth = 0; // width or height from VirtualDevice + mnVirHeight = 0; // height of width from VirtualDevice + mnDragPos = 0; // Drag-Position (Null point) + mnDragAryPos = 0; // Drag-Array-Index + mnDragSize = RulerDragSize::Move; // Did size change at dragging + mnDragModifier = 0; // Modifier key at dragging + mnExtraStyle = 0; // Style of Extra field + mnCharWidth = 371; + mnLineHeight = 551; + mbCalc = true; // Should recalculate page width + mbFormat = true; // Should redraw + mbDrag = false; // Currently at dragging + mbDragDelete = false; // Has mouse left the dragging area + mbDragCanceled = false; // Dragging cancelled? + mbAutoWinWidth = true; // EditWinWidth == RulerWidth + mbActive = true; // Is ruler active + mnUpdateFlags = 0; // What needs to be updated + mpData = mpSaveData.get(); // Pointer to normal data + meExtraType = RulerExtra::DontKnow; // What is in extra field + meDragType = RulerType::DontKnow; // Which element is dragged + + // Initialize Units + mnUnitIndex = RULER_UNIT_CM; + meUnit = FieldUnit::CM; + maZoom = Fraction( 1, 1 ); + + // Recalculate border widths + if ( nWinBits & WB_BORDER ) + mnBorderWidth = 1; + else + mnBorderWidth = 0; + + // Settings + ImplInitSettings( true, true, true ); + + // Setup the default size + tools::Rectangle aRect; + GetTextBoundRect( aRect, "0123456789" ); + long nDefHeight = aRect.GetHeight() + RULER_OFF * 2 + ruler_tab.textoff * 2 + mnBorderWidth; + + Size aDefSize; + if ( nWinBits & WB_HORZ ) + aDefSize.setHeight( nDefHeight ); + else + aDefSize.setWidth( nDefHeight ); + SetOutputSizePixel( aDefSize ); + SetType(WindowType::RULER); +} + +Ruler::Ruler( vcl::Window* pParent, WinBits nWinStyle ) : + Window( pParent, nWinStyle & WB_3DLOOK ), + maVirDev( VclPtr<VirtualDevice>::Create(*this) ), + maMapMode( MapUnit::Map100thMM ), + mpSaveData(new ImplRulerData), + mpData(nullptr), + mpDragData(new ImplRulerData) +{ + // Check to see if the ruler constructor has + // already been called before otherwise + // we end up with over-scaled elements + if (ruler_tab.DPIScaleFactor == 0) + { + ruler_tab.DPIScaleFactor = GetDPIScaleFactor(); + ruler_tab.width *= ruler_tab.DPIScaleFactor; + ruler_tab.height *= ruler_tab.DPIScaleFactor; + ruler_tab.height2 *= ruler_tab.DPIScaleFactor; + ruler_tab.width2 *= ruler_tab.DPIScaleFactor; + ruler_tab.cwidth *= ruler_tab.DPIScaleFactor; + ruler_tab.cwidth2 *= ruler_tab.DPIScaleFactor; + ruler_tab.cwidth3 *= ruler_tab.DPIScaleFactor; + ruler_tab.cwidth4 *= ruler_tab.DPIScaleFactor; + ruler_tab.dheight *= ruler_tab.DPIScaleFactor; + ruler_tab.dheight2 *= ruler_tab.DPIScaleFactor; + ruler_tab.dwidth *= ruler_tab.DPIScaleFactor; + ruler_tab.dwidth2 *= ruler_tab.DPIScaleFactor; + ruler_tab.dwidth3 *= ruler_tab.DPIScaleFactor; + ruler_tab.dwidth4 *= ruler_tab.DPIScaleFactor; + ruler_tab.textoff *= ruler_tab.DPIScaleFactor; + } + + + ImplInit( nWinStyle ); +} + +Ruler::~Ruler() +{ + disposeOnce(); +} + +void Ruler::dispose() +{ + mpSaveData.reset(); + mpDragData.reset(); + mxAccContext.clear(); + Window::dispose(); +} + +void Ruler::ImplVDrawLine(vcl::RenderContext& rRenderContext, long nX1, long nY1, long nX2, long nY2) +{ + if ( nX1 < -RULER_CLIP ) + { + nX1 = -RULER_CLIP; + if ( nX2 < -RULER_CLIP ) + return; + } + long nClip = mnVirWidth + RULER_CLIP; + if ( nX2 > nClip ) + { + nX2 = nClip; + if ( nX1 > nClip ) + return; + } + + if ( mnWinStyle & WB_HORZ ) + rRenderContext.DrawLine( Point( nX1, nY1 ), Point( nX2, nY2 ) ); + else + rRenderContext.DrawLine( Point( nY1, nX1 ), Point( nY2, nX2 ) ); +} + +void Ruler::ImplVDrawRect(vcl::RenderContext& rRenderContext, long nX1, long nY1, long nX2, long nY2) +{ + if ( nX1 < -RULER_CLIP ) + { + nX1 = -RULER_CLIP; + if ( nX2 < -RULER_CLIP ) + return; + } + long nClip = mnVirWidth + RULER_CLIP; + if ( nX2 > nClip ) + { + nX2 = nClip; + if ( nX1 > nClip ) + return; + } + + if ( mnWinStyle & WB_HORZ ) + rRenderContext.DrawRect(tools::Rectangle(nX1, nY1, nX2, nY2)); + else + rRenderContext.DrawRect(tools::Rectangle(nY1, nX1, nY2, nX2)); +} + +void Ruler::ImplVDrawText(vcl::RenderContext& rRenderContext, long nX, long nY, const OUString& rText, long nMin, long nMax) +{ + tools::Rectangle aRect; + SalLayoutGlyphs* pTextLayout + = lcl_GetRulerTextGlyphs(rRenderContext, rText, maTextGlyphs[rText]); + rRenderContext.GetTextBoundRect(aRect, rText, 0, 0, -1, 0, nullptr, pTextLayout); + + long nShiftX = ( aRect.GetWidth() / 2 ) + aRect.Left(); + long nShiftY = ( aRect.GetHeight() / 2 ) + aRect.Top(); + + if ( (nX > -RULER_CLIP) && (nX < mnVirWidth + RULER_CLIP) && ( nX < nMax - nShiftX ) && ( nX > nMin + nShiftX ) ) + { + if ( mnWinStyle & WB_HORZ ) + rRenderContext.DrawText(Point(nX - nShiftX, nY - nShiftY), rText, 0, -1, nullptr, + nullptr, pTextLayout); + else + rRenderContext.DrawText(Point(nY - nShiftX, nX - nShiftY), rText, 0, -1, nullptr, + nullptr, pTextLayout); + } +} + +void Ruler::ImplInvertLines(vcl::RenderContext& rRenderContext) +{ + // Position lines + if (!(!mpData->pLines.empty() && mbActive && !mbDrag && !mbFormat && !(mnUpdateFlags & RULER_UPDATE_LINES)) ) + return; + + long nNullWinOff = mpData->nNullVirOff + mnVirOff; + long nRulX1 = mpData->nRulVirOff + mnVirOff; + long nRulX2 = nRulX1 + mpData->nRulWidth; + long nY = (RULER_OFF * 2) + mnVirHeight - 1; + + // Calculate rectangle + tools::Rectangle aRect; + if (mnWinStyle & WB_HORZ) + aRect.SetBottom( nY ); + else + aRect.SetRight( nY ); + + // Draw lines + for (const RulerLine & rLine : mpData->pLines) + { + const long n = rLine.nPos + nNullWinOff; + if ((n >= nRulX1) && (n < nRulX2)) + { + if (mnWinStyle & WB_HORZ ) + { + aRect.SetLeft( n ); + aRect.SetRight( n ); + } + else + { + aRect.SetTop( n ); + aRect.SetBottom( n ); + } + tools::Rectangle aTempRect = aRect; + + if (mnWinStyle & WB_HORZ) + aTempRect.SetBottom( RULER_OFF - 1 ); + else + aTempRect.SetRight( RULER_OFF - 1 ); + + rRenderContext.Erase(aTempRect); + + if (mnWinStyle & WB_HORZ) + { + aTempRect.SetBottom( aRect.Bottom() ); + aTempRect.SetTop( aTempRect.Bottom() - RULER_OFF + 1 ); + } + else + { + aTempRect.SetRight( aRect.Right() ); + aTempRect.SetLeft( aTempRect.Right() - RULER_OFF + 1 ); + } + rRenderContext.Erase(aTempRect); + Invert(aRect); + } + } + mnUpdateFlags = 0; +} + +void Ruler::ImplDrawTicks(vcl::RenderContext& rRenderContext, long nMin, long nMax, long nStart, long nTop, long nBottom) +{ + double nCenter = nTop + ((nBottom - nTop) / 2); + + long nTickLength3 = (nBottom - nTop) * 0.5; + long nTickLength2 = nTickLength3 * 0.66; + long nTickLength1 = nTickLength2 * 0.66; + + long nScale = ruler_tab.DPIScaleFactor; + long DPIOffset = nScale - 1; + + double nTick4 = aImplRulerUnitTab[mnUnitIndex].nTick4; + double nTick2 = 0; + double nTickCount = aImplRulerUnitTab[mnUnitIndex].nTick1 / nScale; + double nTickUnit = 0; + long nTickWidth; + bool bNoTicks = false; + + Size aPixSize = rRenderContext.LogicToPixel(Size(nTick4, nTick4), maMapMode); + + if (mnUnitIndex == RULER_UNIT_CHAR) + { + if (mnCharWidth == 0) + mnCharWidth = 371; + nTick4 = mnCharWidth * 2; + nTick2 = mnCharWidth; + nTickCount = mnCharWidth; + nTickUnit = mnCharWidth; + } + else if (mnUnitIndex == RULER_UNIT_LINE) + { + if (mnLineHeight == 0) + mnLineHeight = 551; + nTick4 = mnLineHeight * 2; + nTick2 = mnLineHeight; + nTickUnit = mnLineHeight; + nTickCount = mnLineHeight; + } + + if (mnWinStyle & WB_HORZ) + { + nTickWidth = aPixSize.Width(); + } + else + { + vcl::Font aFont = rRenderContext.GetFont(); + if (mnWinStyle & WB_RIGHT_ALIGNED) + aFont.SetOrientation(2700); + else + aFont.SetOrientation(900); + rRenderContext.SetFont(aFont); + nTickWidth = aPixSize.Height(); + } + + long nMaxWidth = rRenderContext.PixelToLogic(Size(mpData->nPageWidth, 0), maMapMode).Width(); + if (nMaxWidth < 0) + nMaxWidth = -nMaxWidth; + + if ((mnUnitIndex == RULER_UNIT_CHAR) || (mnUnitIndex == RULER_UNIT_LINE)) + nMaxWidth /= nTickUnit; + else + nMaxWidth /= aImplRulerUnitTab[mnUnitIndex].nTickUnit; + + OUString aNumString = OUString::number(nMaxWidth); + long nTxtWidth = rRenderContext.GetTextWidth( aNumString ); + const long nTextOff = 4; + + // Determine the number divider for ruler drawn numbers - means which numbers + // should be shown on the ruler and which should be skipped because the ruler + // is not big enough to draw them + if (nTickWidth < nTxtWidth + nTextOff) + { + // Calculate the scale of the ruler + long nMulti = 1; + long nOrgTick4 = nTick4; + + while (nTickWidth < nTxtWidth + nTextOff) + { + long nOldMulti = nMulti; + if (nTickWidth == 0) + nMulti *= 10; + else if (nMulti < 10) + nMulti++; + else if (nMulti < 100) + nMulti += 10; + else if (nMulti < 1000) + nMulti += 100; + else + nMulti += 1000; + + // Overflow - in this case don't draw ticks and exit + if (nMulti < nOldMulti) + { + bNoTicks = true; + break; + } + + nTick4 = nOrgTick4 * nMulti; + aPixSize = rRenderContext.LogicToPixel(Size(nTick4, nTick4), maMapMode); + if (mnWinStyle & WB_HORZ) + nTickWidth = aPixSize.Width(); + else + nTickWidth = aPixSize.Height(); + } + nTickCount = nTick4; + } + else + { + rRenderContext.SetLineColor(rRenderContext.GetSettings().GetStyleSettings().GetShadowColor()); + } + + if (bNoTicks) + return; + + long n = 0; + double nTick = 0.0; + double nTick3 = 0; + + if ((mnUnitIndex != RULER_UNIT_CHAR) && (mnUnitIndex != RULER_UNIT_LINE)) + { + nTick2 = aImplRulerUnitTab[mnUnitIndex].nTick2; + nTick3 = aImplRulerUnitTab[mnUnitIndex].nTick3; + } + + Size nTickGapSize; + + nTickGapSize = rRenderContext.LogicToPixel(Size(nTickCount, nTickCount), maMapMode); + long nTickGap1 = mnWinStyle & WB_HORZ ? nTickGapSize.Width() : nTickGapSize.Height(); + nTickGapSize = rRenderContext.LogicToPixel(Size(nTick2, nTick2), maMapMode); + long nTickGap2 = mnWinStyle & WB_HORZ ? nTickGapSize.Width() : nTickGapSize.Height(); + nTickGapSize = rRenderContext.LogicToPixel(Size(nTick3, nTick3), maMapMode); + long nTickGap3 = mnWinStyle & WB_HORZ ? nTickGapSize.Width() : nTickGapSize.Height(); + + while (((nStart - n) >= nMin) || ((nStart + n) <= nMax)) + { + // Null point + if (nTick == 0.0) + { + if (nStart > nMin) + { + // 0 is only painted when Margin1 is not equal to zero + if ((mpData->nMargin1Style & RulerMarginStyle::Invisible) || (mpData->nMargin1 != 0)) + { + aNumString = "0"; + ImplVDrawText(rRenderContext, nStart, nCenter, aNumString); + } + } + } + else + { + aPixSize = rRenderContext.LogicToPixel(Size(nTick, nTick), maMapMode); + + if (mnWinStyle & WB_HORZ) + n = aPixSize.Width(); + else + n = aPixSize.Height(); + + // Tick4 - Output (Text) + double aStep = nTick / nTick4; + double aRest = std::abs(aStep - std::floor(aStep)); + double nAcceptanceDelta = 0.0001; + + if (aRest < nAcceptanceDelta) + { + if ((mnUnitIndex == RULER_UNIT_CHAR) || (mnUnitIndex == RULER_UNIT_LINE)) + aNumString = OUString::number(nTick / nTickUnit); + else + aNumString = OUString::number(nTick / aImplRulerUnitTab[mnUnitIndex].nTickUnit); + + long nHorizontalLocation = nStart + n; + ImplVDrawText(rRenderContext, nHorizontalLocation, nCenter, aNumString, nMin, nMax); + + if (nMin < nHorizontalLocation && nHorizontalLocation < nMax) + { + ImplVDrawRect(rRenderContext, nHorizontalLocation, nBottom - 1 * nScale, nHorizontalLocation + DPIOffset, nBottom); + ImplVDrawRect(rRenderContext, nHorizontalLocation, nTop, nHorizontalLocation + DPIOffset, nTop + 1 * nScale); + } + + nHorizontalLocation = nStart - n; + ImplVDrawText(rRenderContext, nHorizontalLocation, nCenter, aNumString, nMin, nMax); + + if (nMin < nHorizontalLocation && nHorizontalLocation < nMax) + { + ImplVDrawRect(rRenderContext, nHorizontalLocation, nBottom, + nHorizontalLocation + DPIOffset, nBottom - 1 * nScale); + ImplVDrawRect(rRenderContext, nHorizontalLocation, nTop, + nHorizontalLocation + DPIOffset, nTop + 1 * nScale); + } + } + // Tick/Tick2 - Output (Strokes) + else + { + long nTickLength = nTickLength1; + + aStep = (nTick / nTick2); + aRest = std::abs(aStep - std::floor(aStep)); + if (aRest < nAcceptanceDelta) + nTickLength = nTickLength2; + + aStep = (nTick / nTick3); + aRest = std::abs(aStep - std::floor(aStep)); + if (aRest < nAcceptanceDelta ) + nTickLength = nTickLength3; + + if ((nTickLength == nTickLength1 && nTickGap1 > 6) || + (nTickLength == nTickLength2 && nTickGap2 > 6) || + (nTickLength == nTickLength3 && nTickGap3 > 6)) + { + long nT1 = nCenter - (nTickLength / 2.0); + long nT2 = nT1 + nTickLength - 1; + long nT; + + nT = nStart + n; + + if (nT < nMax) + ImplVDrawRect(rRenderContext, nT, nT1, nT + DPIOffset, nT2); + nT = nStart - n; + if (nT > nMin) + ImplVDrawRect(rRenderContext, nT, nT1, nT + DPIOffset, nT2); + } + } + } + nTick += nTickCount; + } +} + +void Ruler::ImplDrawBorders(vcl::RenderContext& rRenderContext, long nMin, long nMax, long nVirTop, long nVirBottom) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + long n; + long n1; + long n2; + long nTemp1; + long nTemp2; + + for (std::vector<RulerBorder>::size_type i = 0; i < mpData->pBorders.size(); i++) + { + if (mpData->pBorders[i].nStyle & RulerBorderStyle::Invisible) + continue; + + n1 = mpData->pBorders[i].nPos + mpData->nNullVirOff; + n2 = n1 + mpData->pBorders[i].nWidth; + + if (((n1 >= nMin) && (n1 <= nMax)) || ((n2 >= nMin) && (n2 <= nMax))) + { + if ((n2 - n1) > 3) + { + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetFaceColor()); + ImplVDrawRect(rRenderContext, n1, nVirTop, n2, nVirBottom); + + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + ImplVDrawLine(rRenderContext, n1 + 1, nVirTop, n1 + 1, nVirBottom); + ImplVDrawLine(rRenderContext, n1, nVirTop, n2, nVirTop); + + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + ImplVDrawLine(rRenderContext, n1, nVirTop, n1, nVirBottom); + ImplVDrawLine(rRenderContext, n1, nVirBottom, n2, nVirBottom); + ImplVDrawLine(rRenderContext, n2 - 1, nVirTop, n2 - 1, nVirBottom); + + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + ImplVDrawLine(rRenderContext, n2, nVirTop, n2, nVirBottom); + + if (mpData->pBorders[i].nStyle & RulerBorderStyle::Variable) + { + if (n2 - n1 > RULER_VAR_SIZE + 4) + { + nTemp1 = n1 + (((n2 - n1 + 1) - RULER_VAR_SIZE) / 2); + nTemp2 = nVirTop + (((nVirBottom - nVirTop + 1) - RULER_VAR_SIZE) / 2); + long nTemp3 = nTemp1 + RULER_VAR_SIZE - 1; + long nTemp4 = nTemp2 + RULER_VAR_SIZE - 1; + long nTempY = nTemp2; + + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + while (nTempY <= nTemp4) + { + ImplVDrawLine(rRenderContext, nTemp1, nTempY, nTemp3, nTempY); + nTempY += 2; + } + + nTempY = nTemp2 + 1; + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + while (nTempY <= nTemp4) + { + ImplVDrawLine(rRenderContext, nTemp1, nTempY, nTemp3, nTempY); + nTempY += 2; + } + } + } + + if (mpData->pBorders[i].nStyle & RulerBorderStyle::Sizeable) + { + if (n2 - n1 > RULER_VAR_SIZE + 10) + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + ImplVDrawLine(rRenderContext, n1 + 4, nVirTop + 3, n1 + 4, nVirBottom - 3); + ImplVDrawLine(rRenderContext, n2 - 5, nVirTop + 3, n2 - 5, nVirBottom - 3); + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + ImplVDrawLine(rRenderContext, n1 + 5, nVirTop + 3, n1 + 5, nVirBottom - 3); + ImplVDrawLine(rRenderContext, n2 - 4, nVirTop + 3, n2 - 4, nVirBottom - 3); + } + } + } + else + { + n = n1 + ((n2 - n1) / 2); + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + + ImplVDrawLine(rRenderContext, n - 1, nVirTop, n - 1, nVirBottom); + ImplVDrawLine(rRenderContext, n + 1, nVirTop, n + 1, nVirBottom); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetWindowColor()); + ImplVDrawRect(rRenderContext, n, nVirTop, n, nVirBottom); + } + } + } +} + +void Ruler::ImplDrawIndent(vcl::RenderContext& rRenderContext, const tools::Polygon& rPoly, bool bIsHit) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + rRenderContext.SetFillColor(bIsHit ? rStyleSettings.GetDarkShadowColor() : rStyleSettings.GetWorkspaceColor()); + tools::Polygon aPolygon(rPoly); + aPolygon.Optimize(PolyOptimizeFlags::CLOSE); + rRenderContext.DrawPolygon(aPolygon); +} + +void Ruler::ImplDrawIndents(vcl::RenderContext& rRenderContext, long nMin, long nMax, long nVirTop, long nVirBottom) +{ + long n; + long nIndentHeight = (mnVirHeight / 2) - 1; + long nIndentWidth2 = nIndentHeight-3; + + tools::Polygon aPoly(5); + + for (std::vector<RulerIndent>::size_type j = 0; j < mpData->pIndents.size(); j++) + { + if (mpData->pIndents[j].bInvisible) + continue; + + RulerIndentStyle nIndentStyle = mpData->pIndents[j].nStyle; + + n = mpData->pIndents[j].nPos+mpData->nNullVirOff; + + if ((n >= nMin) && (n <= nMax)) + { + if (nIndentStyle == RulerIndentStyle::Bottom) + { + aPoly.SetPoint(Point(n + 0, nVirBottom - nIndentHeight), 0); + aPoly.SetPoint(Point(n - nIndentWidth2, nVirBottom - 3), 1); + aPoly.SetPoint(Point(n - nIndentWidth2, nVirBottom), 2); + aPoly.SetPoint(Point(n + nIndentWidth2, nVirBottom), 3); + aPoly.SetPoint(Point(n + nIndentWidth2, nVirBottom - 3), 4); + } + else + { + aPoly.SetPoint(Point(n + 0, nVirTop + nIndentHeight), 0); + aPoly.SetPoint(Point(n - nIndentWidth2, nVirTop + 3), 1); + aPoly.SetPoint(Point(n - nIndentWidth2, nVirTop), 2); + aPoly.SetPoint(Point(n + nIndentWidth2, nVirTop), 3); + aPoly.SetPoint(Point(n + nIndentWidth2, nVirTop + 3), 4); + } + + if (0 == (mnWinStyle & WB_HORZ)) + { + Point aTmp; + for (sal_uInt16 i = 0; i < 5; i++) + { + aTmp = aPoly[i]; + Point aSet(nVirBottom - aTmp.Y(), aTmp.X()); + aPoly[i] = aSet; + } + } + bool bIsHit = false; + if (mxCurrentHitTest != nullptr && mxCurrentHitTest->eType == RulerType::Indent) + { + bIsHit = mxCurrentHitTest->nAryPos == j; + } + else if(mbDrag && meDragType == RulerType::Indent) + { + bIsHit = mnDragAryPos == j; + } + ImplDrawIndent(rRenderContext, aPoly, bIsHit); + } + } +} + +static void ImplCenterTabPos(Point& rPos, sal_uInt16 nTabStyle) +{ + bool bRTL = 0 != (nTabStyle & RULER_TAB_RTL); + nTabStyle &= RULER_TAB_STYLE; + rPos.AdjustY(ruler_tab.height/2 ); + + if ( (!bRTL && nTabStyle == RULER_TAB_LEFT) || + ( bRTL && nTabStyle == RULER_TAB_RIGHT) ) + { + rPos.AdjustX( -(ruler_tab.width / 2) ); + } + else if ( (!bRTL && nTabStyle == RULER_TAB_RIGHT) || + ( bRTL && nTabStyle == RULER_TAB_LEFT) ) + { + rPos.AdjustX(ruler_tab.width / 2 ); + } +} + +static void lcl_RotateRect_Impl(tools::Rectangle& rRect, const long nReference, bool bRightAligned) +{ + if (rRect.IsEmpty()) + return; + + tools::Rectangle aTmp(rRect); + rRect.SetTop( aTmp.Left() ); + rRect.SetBottom( aTmp.Right() ); + rRect.SetLeft( aTmp.Top() ); + rRect.SetRight( aTmp.Bottom() ); + + if (bRightAligned) + { + long nRef = 2 * nReference; + rRect.SetLeft( nRef - rRect.Left() ); + rRect.SetRight( nRef - rRect.Right() ); + } +} + +static void ImplDrawRulerTab(vcl::RenderContext& rRenderContext, const Point& rPos, + sal_uInt16 nStyle, WinBits nWinBits) +{ + if (nStyle & RULER_STYLE_INVISIBLE) + return; + + sal_uInt16 nTabStyle = nStyle & RULER_TAB_STYLE; + bool bRTL = 0 != (nStyle & RULER_TAB_RTL); + + // Scale by the screen DPI scaling factor + // However when doing this some of the rectangles + // drawn become asymmetric due to the +1 offsets + sal_uInt16 DPIOffset = rRenderContext.GetDPIScaleFactor() - 1; + + tools::Rectangle aRect1; + tools::Rectangle aRect2; + tools::Rectangle aRect3; + + aRect3.SetEmpty(); + + if (nTabStyle == RULER_TAB_DEFAULT) + { + aRect1.SetLeft( rPos.X() - ruler_tab.dwidth2 + 1 ); + aRect1.SetTop( rPos.Y() - ruler_tab.dheight2 + 1 ); + aRect1.SetRight( rPos.X() - ruler_tab.dwidth2 + ruler_tab.dwidth + DPIOffset ); + aRect1.SetBottom( rPos.Y() ); + + aRect2.SetLeft( rPos.X() - ruler_tab.dwidth2 + ruler_tab.dwidth3 ); + aRect2.SetTop( rPos.Y() - ruler_tab.dheight + 1 ); + aRect2.SetRight( rPos.X() - ruler_tab.dwidth2 + ruler_tab.dwidth3 + ruler_tab.dwidth4 - 1 ); + aRect2.SetBottom( rPos.Y() ); + + } + else if ((!bRTL && nTabStyle == RULER_TAB_LEFT) || (bRTL && nTabStyle == RULER_TAB_RIGHT)) + { + aRect1.SetLeft( rPos.X() ); + aRect1.SetTop( rPos.Y() - ruler_tab.height2 + 1 ); + aRect1.SetRight( rPos.X() + ruler_tab.width - 1 ); + aRect1.SetBottom( rPos.Y() ); + + aRect2.SetLeft( rPos.X() ); + aRect2.SetTop( rPos.Y() - ruler_tab.height + 1 ); + aRect2.SetRight( rPos.X() + ruler_tab.width2 - 1 ); + aRect2.SetBottom( rPos.Y() ); + } + else if ((!bRTL && nTabStyle == RULER_TAB_RIGHT) || (bRTL && nTabStyle == RULER_TAB_LEFT)) + { + aRect1.SetLeft( rPos.X() - ruler_tab.width + 1 ); + aRect1.SetTop( rPos.Y() - ruler_tab.height2 + 1 ); + aRect1.SetRight( rPos.X() ); + aRect1.SetBottom( rPos.Y() ); + + aRect2.SetLeft( rPos.X() - ruler_tab.width2 + 1 ); + aRect2.SetTop( rPos.Y() - ruler_tab.height + 1 ); + aRect2.SetRight( rPos.X() ); + aRect2.SetBottom( rPos.Y() ); + } + else + { + aRect1.SetLeft( rPos.X() - ruler_tab.cwidth2 + 1 ); + aRect1.SetTop( rPos.Y() - ruler_tab.height2 + 1 ); + aRect1.SetRight( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth + DPIOffset ); + aRect1.SetBottom( rPos.Y() ); + + aRect2.SetLeft( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth3 ); + aRect2.SetTop( rPos.Y() - ruler_tab.height + 1 ); + aRect2.SetRight( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth3 + ruler_tab.cwidth4 - 1 ); + aRect2.SetBottom( rPos.Y() ); + + if (nTabStyle == RULER_TAB_DECIMAL) + { + aRect3.SetLeft( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth - 1 ); + aRect3.SetTop( rPos.Y() - ruler_tab.height + 1 + 1 - DPIOffset ); + aRect3.SetRight( rPos.X() - ruler_tab.cwidth2 + ruler_tab.cwidth + DPIOffset ); + aRect3.SetBottom( rPos.Y() - ruler_tab.height + 1 + 2 ); + } + } + if (0 == (nWinBits & WB_HORZ)) + { + bool bRightAligned = 0 != (nWinBits & WB_RIGHT_ALIGNED); + lcl_RotateRect_Impl(aRect1, rPos.Y(), bRightAligned); + lcl_RotateRect_Impl(aRect2, rPos.Y(), bRightAligned); + lcl_RotateRect_Impl(aRect3, rPos.Y(), bRightAligned); + } + rRenderContext.DrawRect(aRect1); + rRenderContext.DrawRect(aRect2); + + if (!aRect3.IsEmpty()) + rRenderContext.DrawRect(aRect3); +} + +void Ruler::ImplDrawTab(vcl::RenderContext& rRenderContext, const Point& rPos, sal_uInt16 nStyle) +{ + if (nStyle & RULER_STYLE_INVISIBLE) + return; + + rRenderContext.SetLineColor(); + + if (nStyle & RULER_STYLE_DONTKNOW) + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetFaceColor()); + else + rRenderContext.SetFillColor(rRenderContext.GetSettings().GetStyleSettings().GetDarkShadowColor()); + + if (mpData->bTextRTL) + nStyle |= RULER_TAB_RTL; + + ImplDrawRulerTab(rRenderContext, rPos, nStyle, GetStyle()); +} + +void Ruler::ImplDrawTabs(vcl::RenderContext& rRenderContext, long nMin, long nMax, long nVirTop, long nVirBottom) +{ + for (const RulerTab & rTab : mpData->pTabs) + { + if (rTab.nStyle & RULER_STYLE_INVISIBLE) + continue; + + long aPosition; + aPosition = rTab.nPos; + aPosition += +mpData->nNullVirOff; + long nTopBottom = (GetStyle() & WB_RIGHT_ALIGNED) ? nVirTop : nVirBottom; + if (nMin <= aPosition && aPosition <= nMax) + ImplDrawTab(rRenderContext, Point( aPosition, nTopBottom ), rTab.nStyle); + } +} + +static int adjustSize(int nOrig) +{ + if (nOrig <= 0) + return 0; + + // make sure we return an odd number, that looks better in the ruler + return ( (3*nOrig) / 8) * 2 + 1; +} + +void Ruler::ApplySettings(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + vcl::Font aFont = rStyleSettings.GetToolFont(); + // make the font a bit smaller than default + Size aSize(adjustSize(aFont.GetFontSize().Width()), adjustSize(aFont.GetFontSize().Height())); + aFont.SetFontSize(aSize); + + ApplyControlFont(rRenderContext, aFont); + + ApplyControlForeground(*this, rStyleSettings.GetDarkShadowColor()); + SetTextFillColor(); + + Color aColor; + svtools::ColorConfig aColorConfig; + aColor = aColorConfig.GetColorValue(svtools::APPBACKGROUND).nColor; + ApplyControlBackground(rRenderContext, aColor); +} + +void Ruler::ImplInitSettings(bool bFont, bool bForeground, bool bBackground) +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + if (bFont) + { + vcl::Font aFont = rStyleSettings.GetToolFont(); + // make the font a bit smaller than default + Size aSize(adjustSize(aFont.GetFontSize().Width()), adjustSize(aFont.GetFontSize().Height())); + aFont.SetFontSize(aSize); + + ApplyControlFont(*this, aFont); + } + + if (bForeground || bFont) + { + ApplyControlForeground(*this, rStyleSettings.GetDarkShadowColor()); + SetTextFillColor(); + } + + if (bBackground) + { + Color aColor; + svtools::ColorConfig aColorConfig; + aColor = aColorConfig.GetColorValue(svtools::APPBACKGROUND).nColor; + ApplyControlBackground(*this, aColor); + } + + maVirDev->SetSettings( GetSettings() ); + maVirDev->SetBackground( GetBackground() ); + vcl::Font aFont = GetFont(); + + if (mnWinStyle & WB_VERT) + aFont.SetOrientation(900); + + maVirDev->SetFont(aFont); + maVirDev->SetTextColor(GetTextColor()); + maVirDev->SetTextFillColor(GetTextFillColor()); +} + +void Ruler::ImplCalc() +{ + // calculate offset + mpData->nRulVirOff = mnWinOff + mpData->nPageOff; + if ( mpData->nRulVirOff > mnVirOff ) + mpData->nRulVirOff -= mnVirOff; + else + mpData->nRulVirOff = 0; + long nRulWinOff = mpData->nRulVirOff+mnVirOff; + + // calculate non-visual part of the page + long nNotVisPageWidth; + if ( mpData->nPageOff < 0 ) + { + nNotVisPageWidth = -(mpData->nPageOff); + if ( nRulWinOff < mnWinOff ) + nNotVisPageWidth -= mnWinOff-nRulWinOff; + } + else + nNotVisPageWidth = 0; + + // calculate width + if ( mnWinStyle & WB_HORZ ) + { + if ( mbAutoWinWidth ) + mnWinWidth = mnWidth - mnVirOff; + if ( mpData->bAutoPageWidth ) + mpData->nPageWidth = mnWinWidth; + mpData->nRulWidth = std::min( mnWinWidth, mpData->nPageWidth-nNotVisPageWidth ); + if ( nRulWinOff+mpData->nRulWidth > mnWidth ) + mpData->nRulWidth = mnWidth-nRulWinOff; + } + else + { + if ( mbAutoWinWidth ) + mnWinWidth = mnHeight - mnVirOff; + if ( mpData->bAutoPageWidth ) + mpData->nPageWidth = mnWinWidth; + mpData->nRulWidth = std::min( mnWinWidth, mpData->nPageWidth-nNotVisPageWidth ); + if ( nRulWinOff+mpData->nRulWidth > mnHeight ) + mpData->nRulWidth = mnHeight-nRulWinOff; + } + + mbCalc = false; +} + +void Ruler::ImplFormat(vcl::RenderContext const & rRenderContext) +{ + // if already formatted, don't do it again + if (!mbFormat) + return; + + // don't do anything if the window still has no size + if (!mnVirWidth) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + long nP1; // pixel position of Page1 + long nP2; // pixel position of Page2 + long nM1; // pixel position of Margin1 + long nM2; // pixel position of Margin2 + long nVirTop; // top/left corner + long nVirBottom; // bottom/right corner + long nVirLeft; // left/top corner + long nVirRight; // right/bottom corner + long nNullVirOff; // for faster calculation + + // calculate values + if (mbCalc) + ImplCalc(); + + mpData->nNullVirOff = mnWinOff + mpData->nPageOff + mpData->nNullOff - mnVirOff; + + nNullVirOff = mpData->nNullVirOff; + nVirLeft = mpData->nRulVirOff; + nVirRight = nVirLeft + mpData->nRulWidth - 1; + nVirTop = 0; + nVirBottom = mnVirHeight - 1; + + if (!IsReallyVisible()) + return; + + Size aVirDevSize; + + // initialize VirtualDevice + if (mnWinStyle & WB_HORZ) + { + aVirDevSize.setWidth( mnVirWidth ); + aVirDevSize.setHeight( mnVirHeight ); + } + else + { + aVirDevSize.setHeight( mnVirWidth ); + aVirDevSize.setWidth( mnVirHeight ); + } + if (aVirDevSize != maVirDev->GetOutputSizePixel()) + maVirDev->SetOutputSizePixel(aVirDevSize); + else + maVirDev->Erase(); + + // calculate margins + if (!(mpData->nMargin1Style & RulerMarginStyle::Invisible)) + { + nM1 = mpData->nMargin1 + nNullVirOff; + if (mpData->bAutoPageWidth) + { + nP1 = nVirLeft; + if (nM1 < nVirLeft) + nP1--; + } + else + nP1 = nNullVirOff - mpData->nNullOff; + } + else + { + nM1 = nVirLeft-1; + nP1 = nM1; + } + if (!(mpData->nMargin2Style & RulerMarginStyle::Invisible)) + { + nM2 = mpData->nMargin2 + nNullVirOff; + if (mpData->bAutoPageWidth) + { + nP2 = nVirRight; + if (nM2 > nVirRight) + nP2++; + } + else + nP2 = nNullVirOff - mpData->nNullOff + mpData->nPageWidth; + if (nM2 > nP2) + nM2 = nP2; + } + else + { + nM2 = nVirRight+1; + nP2 = nM2; + } + + // top/bottom border + maVirDev->SetLineColor(rStyleSettings.GetShadowColor()); + ImplVDrawLine(*maVirDev, nVirLeft, nVirTop + 1, nM1, nVirTop + 1); //top left line + ImplVDrawLine(*maVirDev, nM2, nVirTop + 1, nP2 - 1, nVirTop + 1); //top right line + + nVirTop++; + nVirBottom--; + + // draw margin1, margin2 and in-between + maVirDev->SetLineColor(); + maVirDev->SetFillColor(rStyleSettings.GetDialogColor()); + if (nM1 > nVirLeft) + ImplVDrawRect(*maVirDev, nP1, nVirTop + 1, nM1, nVirBottom); //left gray rectangle + if (nM2 < nP2) + ImplVDrawRect(*maVirDev, nM2, nVirTop + 1, nP2, nVirBottom); //right gray rectangle + if (nM2 - nM1 > 0) + { + maVirDev->SetFillColor(rStyleSettings.GetWindowColor()); + ImplVDrawRect(*maVirDev, nM1 + 1, nVirTop, nM2 - 1, nVirBottom); //center rectangle + } + maVirDev->SetLineColor(rStyleSettings.GetShadowColor()); + if (nM1 > nVirLeft) + { + ImplVDrawLine(*maVirDev, nM1, nVirTop + 1, nM1, nVirBottom); //right line of the left rectangle + ImplVDrawLine(*maVirDev, nP1, nVirBottom, nM1, nVirBottom); //bottom line of the left rectangle + if (nP1 >= nVirLeft) + { + ImplVDrawLine(*maVirDev, nP1, nVirTop + 1, nP1, nVirBottom); //left line of the left rectangle + ImplVDrawLine(*maVirDev, nP1, nVirBottom, nP1 + 1, nVirBottom); //? + } + } + if (nM2 < nP2) + { + ImplVDrawLine(*maVirDev, nM2, nVirBottom, nP2 - 1, nVirBottom); //bottom line of the right rectangle + ImplVDrawLine(*maVirDev, nM2, nVirTop + 1, nM2, nVirBottom); //left line of the right rectangle + if (nP2 <= nVirRight + 1) + ImplVDrawLine(*maVirDev, nP2 - 1, nVirTop + 1, nP2 - 1, nVirBottom); //right line of the right rectangle + } + + long nMin = nVirLeft; + long nMax = nP2; + long nStart = 0; + + if (mpData->bTextRTL) + nStart = mpData->nRightFrameMargin + nNullVirOff; + else + nStart = mpData->nLeftFrameMargin + nNullVirOff; + + if (nP1 > nVirLeft) + nMin++; + + if (nP2 < nVirRight) + nMax--; + + // Draw captions + ImplDrawTicks(*maVirDev, nMin, nMax, nStart, nVirTop, nVirBottom); + + // Draw borders + if (!mpData->pBorders.empty()) + ImplDrawBorders(*maVirDev, nVirLeft, nP2, nVirTop, nVirBottom); + + // Draw indents + if (!mpData->pIndents.empty()) + ImplDrawIndents(*maVirDev, nVirLeft, nP2, nVirTop - 1, nVirBottom + 1); + + // Tabs + if (!mpData->pTabs.empty()) + ImplDrawTabs(*maVirDev, nVirLeft, nP2, nVirTop-1, nVirBottom + 1); + + mbFormat = false; +} + +void Ruler::ImplInitExtraField( bool bUpdate ) +{ + Size aWinSize = GetOutputSizePixel(); + + // extra field evaluate + if ( mnWinStyle & WB_EXTRAFIELD ) + { + maExtraRect.SetLeft( RULER_OFF ); + maExtraRect.SetTop( RULER_OFF ); + maExtraRect.SetRight( RULER_OFF + mnVirHeight - 1 ); + maExtraRect.SetBottom( RULER_OFF + mnVirHeight - 1 ); + if(mpData->bTextRTL) + { + if(mnWinStyle & WB_HORZ) + maExtraRect.Move(aWinSize.Width() - maExtraRect.GetWidth() - maExtraRect.Left(), 0); + else + maExtraRect.Move(0, aWinSize.Height() - maExtraRect.GetHeight() - maExtraRect.Top()); + mnVirOff = 0; + } + else + mnVirOff = maExtraRect.Right()+1; + + } + else + { + maExtraRect.SetEmpty(); + mnVirOff = 0; + } + + // mnVirWidth depends on mnVirOff + if ( (mnVirWidth > RULER_MIN_SIZE) || + ((aWinSize.Width() > RULER_MIN_SIZE) && (aWinSize.Height() > RULER_MIN_SIZE)) ) + { + if ( mnWinStyle & WB_HORZ ) + mnVirWidth = aWinSize.Width()-mnVirOff; + else + mnVirWidth = aWinSize.Height()-mnVirOff; + + if ( mnVirWidth < RULER_MIN_SIZE ) + mnVirWidth = 0; + } + + if ( bUpdate ) + { + mbCalc = true; + mbFormat = true; + Invalidate(); + } +} + +void Ruler::ImplDraw(vcl::RenderContext& rRenderContext) +{ + if (mbFormat) + { + ImplFormat(rRenderContext); + } + + if (!IsReallyVisible()) + return; + + // output the ruler to the virtual device + Point aOffPos; + Size aVirDevSize = maVirDev->GetOutputSizePixel(); + + if (mnWinStyle & WB_HORZ) + { + aOffPos.setX( mnVirOff ); + if (mpData->bTextRTL) + aVirDevSize.AdjustWidth( -(maExtraRect.GetWidth()) ); + + aOffPos.setY( RULER_OFF ); + } + else + { + aOffPos.setX( RULER_OFF ); + aOffPos.setY( mnVirOff ); + } + rRenderContext.DrawOutDev(aOffPos, aVirDevSize, Point(), aVirDevSize, *maVirDev); + + // redraw positionlines + ImplInvertLines(rRenderContext); +} + +void Ruler::ImplDrawExtra(vcl::RenderContext& rRenderContext) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + tools::Rectangle aRect = maExtraRect; + bool bEraseRect = false; + + aRect.AdjustLeft(2 ); + aRect.AdjustTop(2 ); + aRect.AdjustRight( -2 ); + aRect.AdjustBottom( -2 ); + + if (mnExtraStyle & RULER_STYLE_HIGHLIGHT) + { + rRenderContext.SetFillColor(rStyleSettings.GetCheckedColor()); + bEraseRect = true; + } + + if (bEraseRect) + { + rRenderContext.SetLineColor(); + rRenderContext.DrawRect(aRect); + } + + // output content + if (meExtraType == RulerExtra::NullOffset) + { + rRenderContext.SetLineColor(rStyleSettings.GetButtonTextColor()); + rRenderContext.DrawLine(Point(aRect.Left() + 1, aRect.Top() + 4), + Point(aRect.Right() - 1, aRect.Top() + 4)); + rRenderContext.DrawLine(Point(aRect.Left() + 4, aRect.Top() + 1), + Point(aRect.Left() + 4, aRect.Bottom() - 1)); + } + else if (meExtraType == RulerExtra::Tab) + { + sal_uInt16 nTabStyle = mnExtraStyle & RULER_TAB_STYLE; + if (mpData->bTextRTL) + nTabStyle |= RULER_TAB_RTL; + Point aCenter = aRect.Center(); + Point aDraw(aCenter); + ImplCenterTabPos(aDraw, nTabStyle); + WinBits nWinBits = GetStyle(); + if (0 == (nWinBits & WB_HORZ)) + { + if ((nWinBits & WB_RIGHT_ALIGNED) != 0) + aDraw.setY( 2 * aCenter.Y() - aDraw.Y() ); + + if (mpData->bTextRTL) + { + long nTemp = aDraw.X(); + aDraw.setX( aDraw.Y() ); + aDraw.setY( nTemp ); + } + } + ImplDrawTab(rRenderContext, aDraw, nTabStyle); + } +} + +void Ruler::ImplUpdate( bool bMustCalc ) +{ + // clear lines in this place so they aren't considered at recalculation + if (!mbFormat) + Invalidate(InvalidateFlags::NoErase); + + // set flags + if (bMustCalc) + mbCalc = true; + mbFormat = true; + + // abort if we are dragging as drag-handler will update the ruler after drag is finished + if (mbDrag) + return; + + // otherwise trigger update + if (IsReallyVisible() && IsUpdateMode()) + { + Invalidate(InvalidateFlags::NoErase); + } +} + +bool Ruler::ImplHitTest( const Point& rPos, RulerSelection* pHitTest, + bool bRequireStyle, RulerIndentStyle nRequiredStyle ) const +{ + sal_Int32 i; + sal_uInt16 nStyle; + long nHitBottom; + long nX; + long nY; + long n1; + + if ( !mbActive ) + return false; + + // determine positions + bool bIsHori = 0 != (mnWinStyle & WB_HORZ); + if ( bIsHori ) + { + nX = rPos.X(); + nY = rPos.Y(); + } + else + { + nX = rPos.Y(); + nY = rPos.X(); + } + nHitBottom = mnVirHeight + (RULER_OFF * 2); + + // #i32608# + pHitTest->nAryPos = 0; + pHitTest->mnDragSize = RulerDragSize::Move; + pHitTest->bSize = false; + pHitTest->bSizeBar = false; + + // so that leftover tabs and indents are taken into account + long nXExtraOff; + if ( !mpData->pTabs.empty() || !mpData->pIndents.empty() ) + nXExtraOff = (mnVirHeight / 2) - 4; + else + nXExtraOff = 0; + + // test if outside + nX -= mnVirOff; + if ( (nX < mpData->nRulVirOff - nXExtraOff) || + (nX > mpData->nRulVirOff + mpData->nRulWidth + nXExtraOff) || + (nY < 0) || + (nY > nHitBottom) ) + { + pHitTest->nPos = 0; + pHitTest->eType = RulerType::Outside; + return false; + } + + nX -= mpData->nNullVirOff; + pHitTest->nPos = nX; + pHitTest->eType = RulerType::DontKnow; + + // first test the tabs + tools::Rectangle aRect; + if ( !mpData->pTabs.empty() ) + { + aRect.SetBottom( nHitBottom ); + aRect.SetTop( aRect.Bottom() - ruler_tab.height - RULER_OFF ); + + for ( i = mpData->pTabs.size() - 1; i >= 0; i-- ) + { + nStyle = mpData->pTabs[i].nStyle; + if ( !(nStyle & RULER_STYLE_INVISIBLE) ) + { + nStyle &= RULER_TAB_STYLE; + + // default tabs are only shown (no action) + if ( nStyle != RULER_TAB_DEFAULT ) + { + n1 = mpData->pTabs[i].nPos; + + if ( nStyle == RULER_TAB_LEFT ) + { + aRect.SetLeft( n1 ); + aRect.SetRight( n1 + ruler_tab.width - 1 ); + } + else if ( nStyle == RULER_TAB_RIGHT ) + { + aRect.SetRight( n1 ); + aRect.SetLeft( n1 - ruler_tab.width - 1 ); + } + else + { + aRect.SetLeft( n1 - ruler_tab.cwidth2 + 1 ); + aRect.SetRight( n1 - ruler_tab.cwidth2 + ruler_tab.cwidth ); + } + + if ( aRect.IsInside( Point( nX, nY ) ) ) + { + pHitTest->eType = RulerType::Tab; + pHitTest->nAryPos = i; + return true; + } + } + } + } + } + + // Indents + if ( !mpData->pIndents.empty() ) + { + long nIndentHeight = (mnVirHeight / 2) - 1; + long nIndentWidth2 = nIndentHeight - 3; + + for ( i = mpData->pIndents.size(); i; i-- ) + { + RulerIndentStyle nIndentStyle = mpData->pIndents[i-1].nStyle; + if ( (! bRequireStyle || nIndentStyle == nRequiredStyle) && + !mpData->pIndents[i-1].bInvisible ) + { + n1 = mpData->pIndents[i-1].nPos; + + if ( (nIndentStyle == RulerIndentStyle::Bottom) != !bIsHori ) + { + aRect.SetLeft( n1-nIndentWidth2 ); + aRect.SetRight( n1+nIndentWidth2 ); + aRect.SetTop( nHitBottom-nIndentHeight-RULER_OFF+1 ); + aRect.SetBottom( nHitBottom ); + } + else + { + aRect.SetLeft( n1-nIndentWidth2 ); + aRect.SetRight( n1+nIndentWidth2 ); + aRect.SetTop( 0 ); + aRect.SetBottom( nIndentHeight+RULER_OFF-1 ); + } + + if ( aRect.IsInside( Point( nX, nY ) ) ) + { + pHitTest->eType = RulerType::Indent; + pHitTest->nAryPos = i-1; + return true; + } + } + } + } + + // test the borders + int nBorderTolerance = 1; + if(pHitTest->bExpandTest) + { + nBorderTolerance++; + } + + for ( i = mpData->pBorders.size(); i; i-- ) + { + n1 = mpData->pBorders[i-1].nPos; + long n2 = n1 + mpData->pBorders[i-1].nWidth; + + // borders have at least 3 pixel padding + if ( !mpData->pBorders[i-1].nWidth ) + { + n1 -= nBorderTolerance; + n2 += nBorderTolerance; + + } + + if ( (nX >= n1) && (nX <= n2) ) + { + RulerBorderStyle nBorderStyle = mpData->pBorders[i-1].nStyle; + if ( !(nBorderStyle & RulerBorderStyle::Invisible) ) + { + pHitTest->eType = RulerType::Border; + pHitTest->nAryPos = i-1; + + if ( !(nBorderStyle & RulerBorderStyle::Sizeable) ) + { + if ( nBorderStyle & RulerBorderStyle::Moveable ) + { + pHitTest->bSizeBar = true; + pHitTest->mnDragSize = RulerDragSize::Move; + } + } + else + { + long nMOff = RULER_MOUSE_BORDERWIDTH; + while ( nMOff*2 >= (n2-n1-RULER_MOUSE_BORDERMOVE) ) + { + if ( nMOff < 2 ) + { + nMOff = 0; + break; + } + else + nMOff--; + } + + if ( nX <= n1+nMOff ) + { + pHitTest->bSize = true; + pHitTest->mnDragSize = RulerDragSize::N1; + } + else if ( nX >= n2-nMOff ) + { + pHitTest->bSize = true; + pHitTest->mnDragSize = RulerDragSize::N2; + } + else + { + if ( nBorderStyle & RulerBorderStyle::Moveable ) + { + pHitTest->bSizeBar = true; + pHitTest->mnDragSize = RulerDragSize::Move; + } + } + } + + return true; + } + } + } + + // Margins + int nMarginTolerance = pHitTest->bExpandTest ? nBorderTolerance : RULER_MOUSE_MARGINWIDTH; + + if ( (mpData->nMargin1Style & (RulerMarginStyle::Sizeable | RulerMarginStyle::Invisible)) == RulerMarginStyle::Sizeable ) + { + n1 = mpData->nMargin1; + if ( (nX >= n1 - nMarginTolerance) && (nX <= n1 + nMarginTolerance) ) + { + pHitTest->eType = RulerType::Margin1; + pHitTest->bSize = true; + return true; + } + } + if ( (mpData->nMargin2Style & (RulerMarginStyle::Sizeable | RulerMarginStyle::Invisible)) == RulerMarginStyle::Sizeable ) + { + n1 = mpData->nMargin2; + if ( (nX >= n1 - nMarginTolerance) && (nX <= n1 + nMarginTolerance) ) + { + pHitTest->eType = RulerType::Margin2; + pHitTest->bSize = true; + return true; + } + } + + // test tabs again + if ( !mpData->pTabs.empty() ) + { + aRect.SetTop( RULER_OFF ); + aRect.SetBottom( nHitBottom ); + + for ( i = mpData->pTabs.size() - 1; i >= 0; i-- ) + { + nStyle = mpData->pTabs[i].nStyle; + if ( !(nStyle & RULER_STYLE_INVISIBLE) ) + { + nStyle &= RULER_TAB_STYLE; + + // default tabs are only shown (no action) + if ( nStyle != RULER_TAB_DEFAULT ) + { + n1 = mpData->pTabs[i].nPos; + + if ( nStyle == RULER_TAB_LEFT ) + { + aRect.SetLeft( n1 ); + aRect.SetRight( n1 + ruler_tab.width - 1 ); + } + else if ( nStyle == RULER_TAB_RIGHT ) + { + aRect.SetRight( n1 ); + aRect.SetLeft( n1 - ruler_tab.width - 1 ); + } + else + { + aRect.SetLeft( n1 - ruler_tab.cwidth2 + 1 ); + aRect.SetRight( n1 - ruler_tab.cwidth2 + ruler_tab.cwidth ); + } + + aRect.AdjustLeft( -1 ); + aRect.AdjustRight( 1 ); + + if ( aRect.IsInside( Point( nX, nY ) ) ) + { + pHitTest->eType = RulerType::Tab; + pHitTest->nAryPos = i; + return true; + } + } + } + } + } + + return false; +} + +bool Ruler::ImplDocHitTest( const Point& rPos, RulerType eDragType, + RulerSelection* pHitTest ) const +{ + Point aPos = rPos; + bool bRequiredStyle = false; + RulerIndentStyle nRequiredStyle = RulerIndentStyle::Top; + + if (eDragType == RulerType::Indent) + { + bRequiredStyle = true; + nRequiredStyle = RulerIndentStyle::Bottom; + } + + if ( mnWinStyle & WB_HORZ ) + aPos.AdjustX(mnWinOff ); + else + aPos.AdjustY(mnWinOff ); + + if ( (eDragType == RulerType::Indent) || (eDragType == RulerType::DontKnow) ) + { + if ( mnWinStyle & WB_HORZ ) + aPos.setY( RULER_OFF + 1 ); + else + aPos.setX( RULER_OFF + 1 ); + + if ( ImplHitTest( aPos, pHitTest, bRequiredStyle, nRequiredStyle ) ) + { + if ( (pHitTest->eType == eDragType) || (eDragType == RulerType::DontKnow) ) + return true; + } + } + + if ( (eDragType == RulerType::Indent) || + (eDragType == RulerType::Tab) || + (eDragType == RulerType::DontKnow) ) + { + if ( mnWinStyle & WB_HORZ ) + aPos.setY( mnHeight - RULER_OFF - 1 ); + else + aPos.setX( mnWidth - RULER_OFF - 1 ); + + if ( ImplHitTest( aPos, pHitTest, bRequiredStyle, nRequiredStyle ) ) + { + if ( (pHitTest->eType == eDragType) || (eDragType == RulerType::DontKnow) ) + return true; + } + } + + if ( (eDragType == RulerType::Margin1) || (eDragType == RulerType::Margin2) || + (eDragType == RulerType::Border) || (eDragType == RulerType::DontKnow) ) + { + if ( mnWinStyle & WB_HORZ ) + aPos.setY( RULER_OFF + (mnVirHeight / 2) ); + else + aPos.setX( RULER_OFF + (mnVirHeight / 2) ); + + if ( ImplHitTest( aPos, pHitTest ) ) + { + if ( (pHitTest->eType == eDragType) || (eDragType == RulerType::DontKnow) ) + return true; + } + } + + pHitTest->eType = RulerType::DontKnow; + + return false; +} + +bool Ruler::ImplStartDrag( RulerSelection const * pHitTest, sal_uInt16 nModifier ) +{ + // don't trigger drag if a border that was clicked can not be changed + if ( (pHitTest->eType == RulerType::Border) && + !pHitTest->bSize && !pHitTest->bSizeBar ) + return false; + + // Set drag data + meDragType = pHitTest->eType; + mnDragPos = pHitTest->nPos; + mnDragAryPos = pHitTest->nAryPos; + mnDragSize = pHitTest->mnDragSize; + mnDragModifier = nModifier; + *mpDragData = *mpSaveData; + mpData = mpDragData.get(); + + // call handler + if (StartDrag()) + { + // if the handler allows dragging, initialize dragging + mbDrag = true; + mnStartDragPos = mnDragPos; + StartTracking(); + Invalidate(InvalidateFlags::NoErase); + return true; + } + else + { + // otherwise reset the data + meDragType = RulerType::DontKnow; + mnDragPos = 0; + mnDragAryPos = 0; + mnDragSize = RulerDragSize::Move; + mnDragModifier = 0; + mpData = mpSaveData.get(); + } + + return false; +} + +void Ruler::ImplDrag( const Point& rPos ) +{ + long nX; + long nY; + long nOutHeight; + + if ( mnWinStyle & WB_HORZ ) + { + nX = rPos.X(); + nY = rPos.Y(); + nOutHeight = mnHeight; + } + else + { + nX = rPos.Y(); + nY = rPos.X(); + nOutHeight = mnWidth; + } + + // calculate and fit X + nX -= mnVirOff; + if ( nX < mpData->nRulVirOff ) + { + nX = mpData->nRulVirOff; + } + else if ( nX > mpData->nRulVirOff+mpData->nRulWidth ) + { + nX = mpData->nRulVirOff+mpData->nRulWidth; + } + nX -= mpData->nNullVirOff; + + // if upper or left from ruler, then consider old values + mbDragDelete = false; + if ( nY < 0 ) + { + if ( !mbDragCanceled ) + { + // reset the data + mbDragCanceled = true; + ImplRulerData aTempData = *mpDragData; + *mpDragData = *mpSaveData; + mbCalc = true; + mbFormat = true; + + // call handler + mnDragPos = mnStartDragPos; + Drag(); + + // and redraw + Invalidate(InvalidateFlags::NoErase); + + // reset the data as before cancel + *mpDragData = aTempData; + } + } + else + { + mbDragCanceled = false; + + // +2, so the tabs are not cleared too quickly + if ( nY > nOutHeight + 2 ) + mbDragDelete = true; + + mnDragPos = nX; + + // call handler + Drag(); + + // redraw + if (mbFormat) + Invalidate(InvalidateFlags::NoErase); + } +} + +void Ruler::ImplEndDrag() +{ + // get values + if ( mbDragCanceled ) + *mpDragData = *mpSaveData; + else + *mpSaveData = *mpDragData; + + mpData = mpSaveData.get(); + mbDrag = false; + + // call handler + EndDrag(); + + // reset drag values + meDragType = RulerType::DontKnow; + mnDragPos = 0; + mnDragAryPos = 0; + mnDragSize = RulerDragSize::Move; + mbDragCanceled = false; + mbDragDelete = false; + mnDragModifier = 0; + mnStartDragPos = 0; + + // redraw + Invalidate(InvalidateFlags::NoErase); +} + +void Ruler::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( !(rMEvt.IsLeft() && !IsTracking()) ) + return; + + Point aMousePos = rMEvt.GetPosPixel(); + sal_uInt16 nMouseClicks = rMEvt.GetClicks(); + sal_uInt16 nMouseModifier = rMEvt.GetModifier(); + + // update ruler + if ( mbFormat ) + { + Invalidate(InvalidateFlags::NoErase); + } + + if ( maExtraRect.IsInside( aMousePos ) ) + { + ExtraDown(); + } + else + { + std::unique_ptr<RulerSelection> pHitTest(new RulerSelection); + bool bHitTestResult = ImplHitTest(aMousePos, pHitTest.get()); + + if ( nMouseClicks == 1 ) + { + if ( bHitTestResult ) + { + ImplStartDrag( pHitTest.get(), nMouseModifier ); + } + else + { + // calculate position inside of ruler area + if ( pHitTest->eType == RulerType::DontKnow ) + { + mnDragPos = pHitTest->nPos; + Click(); + mnDragPos = 0; + + // call HitTest again as a click, for example, could set a new tab + if ( ImplHitTest(aMousePos, pHitTest.get()) ) + ImplStartDrag(pHitTest.get(), nMouseModifier); + } + } + } + else + { + if (bHitTestResult) + { + mnDragPos = pHitTest->nPos; + mnDragAryPos = pHitTest->nAryPos; + } + meDragType = pHitTest->eType; + + DoubleClick(); + + meDragType = RulerType::DontKnow; + mnDragPos = 0; + mnDragAryPos = 0; + } + } +} + +void Ruler::MouseMove( const MouseEvent& rMEvt ) +{ + PointerStyle ePtrStyle = PointerStyle::Arrow; + + mxPreviousHitTest.swap(mxCurrentHitTest); + + mxCurrentHitTest.reset(new RulerSelection); + + maHoverSelection.eType = RulerType::DontKnow; + + if (ImplHitTest( rMEvt.GetPosPixel(), mxCurrentHitTest.get() )) + { + maHoverSelection = *mxCurrentHitTest; + + if (mxCurrentHitTest->bSize) + { + if (mnWinStyle & WB_HORZ) + { + if (mxCurrentHitTest->mnDragSize == RulerDragSize::N1) + ePtrStyle = PointerStyle::TabSelectW; + else if (mxCurrentHitTest->mnDragSize == RulerDragSize::N2) + ePtrStyle = PointerStyle::TabSelectE; + else + ePtrStyle = PointerStyle::ESize; + } + else + { + if (mxCurrentHitTest->mnDragSize == RulerDragSize::N1) + ePtrStyle = PointerStyle::WindowNSize; + else if (mxCurrentHitTest->mnDragSize == RulerDragSize::N2) + ePtrStyle = PointerStyle::WindowSSize; + else + ePtrStyle = PointerStyle::SSize; + } + } + else if (mxCurrentHitTest->bSizeBar) + { + if (mnWinStyle & WB_HORZ) + ePtrStyle = PointerStyle::HSizeBar; + else + ePtrStyle = PointerStyle::VSizeBar; + } + } + + if (mxPreviousHitTest != nullptr && mxPreviousHitTest->eType != mxCurrentHitTest->eType) + { + mbFormat = true; + } + + SetPointer( ePtrStyle ); + + if (mbFormat) + { + Invalidate(InvalidateFlags::NoErase); + } +} + +void Ruler::Tracking( const TrackingEvent& rTEvt ) +{ + if ( rTEvt.IsTrackingEnded() ) + { + // reset the old state at cancel + if ( rTEvt.IsTrackingCanceled() ) + { + mbDragCanceled = true; + mbFormat = true; + } + + ImplEndDrag(); + } + else + ImplDrag( rTEvt.GetMouseEvent().GetPosPixel() ); +} + +void Ruler::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + ImplDraw(rRenderContext); + + // consider extra field + if (mnWinStyle & WB_EXTRAFIELD) + ImplDrawExtra(rRenderContext); +} + +void Ruler::Resize() +{ + Size aWinSize = GetOutputSizePixel(); + + long nNewHeight; + if ( mnWinStyle & WB_HORZ ) + { + if ( aWinSize.Height() != mnHeight ) + nNewHeight = aWinSize.Height(); + else + nNewHeight = 0; + } + else + { + if ( aWinSize.Width() != mnWidth ) + nNewHeight = aWinSize.Width(); + else + nNewHeight = 0; + } + + mbFormat = true; + + // clear lines + bool bVisible = IsReallyVisible(); + if ( bVisible && !mpData->pLines.empty() ) + { + mnUpdateFlags |= RULER_UPDATE_LINES; + Invalidate(InvalidateFlags::NoErase); + } + + // recalculate some values if the height/width changes + // extra field should always be updated + ImplInitExtraField( mpData->bTextRTL ); + if ( nNewHeight ) + { + mbCalc = true; + mnVirHeight = nNewHeight - mnBorderWidth - ( RULER_OFF * 2 ); + } + else + { + if ( mpData->bAutoPageWidth ) + ImplUpdate( true ); + else if ( mbAutoWinWidth ) + mbCalc = true; + } + + // clear part of the border + if ( bVisible ) + { + if ( nNewHeight ) + Invalidate(InvalidateFlags::NoErase); + else if ( mpData->bAutoPageWidth ) + { + // only at AutoPageWidth do we need to redraw + tools::Rectangle aRect; + + if ( mnWinStyle & WB_HORZ ) + { + if ( mnWidth < aWinSize.Width() ) + aRect.SetLeft( mnWidth - RULER_RESIZE_OFF ); + else + aRect.SetLeft( aWinSize.Width() - RULER_RESIZE_OFF ); + aRect.SetRight( aRect.Left() + RULER_RESIZE_OFF ); + aRect.SetTop( RULER_OFF ); + aRect.SetBottom( RULER_OFF + mnVirHeight ); + } + else + { + if ( mnHeight < aWinSize.Height() ) + aRect.SetTop( mnHeight-RULER_RESIZE_OFF ); + else + aRect.SetTop( aWinSize.Height()-RULER_RESIZE_OFF ); + aRect.SetBottom( aRect.Top() + RULER_RESIZE_OFF ); + aRect.SetLeft( RULER_OFF ); + aRect.SetRight( RULER_OFF + mnVirHeight ); + } + + Invalidate(aRect, InvalidateFlags::NoErase); + } + } + + mnWidth = aWinSize.Width(); + mnHeight = aWinSize.Height(); +} + +void Ruler::StateChanged( StateChangedType nType ) +{ + Window::StateChanged( nType ); + + if ( nType == StateChangedType::InitShow ) + Invalidate(); + else if ( nType == StateChangedType::UpdateMode ) + { + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); + } + else if ( (nType == StateChangedType::Zoom) || + (nType == StateChangedType::ControlFont) ) + { + ImplInitSettings( true, false, false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlForeground ) + { + ImplInitSettings( false, true, false ); + Invalidate(); + } + else if ( nType == StateChangedType::ControlBackground ) + { + ImplInitSettings( false, false, true ); + Invalidate(); + } +} + +void Ruler::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::DISPLAY) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) ) + { + mbFormat = true; + ImplInitSettings( true, true, true ); + Invalidate(); + } +} + +bool Ruler::StartDrag() +{ + return false; +} + +void Ruler::Drag() +{ +} + +void Ruler::EndDrag() +{ +} + +void Ruler::Click() +{ +} + +void Ruler::DoubleClick() +{ + maDoubleClickHdl.Call( this ); +} + +void Ruler::ExtraDown() +{ +} + +void Ruler::Activate() +{ + mbActive = true; + + // update positionlines - draw is delayed + mnUpdateFlags |= RULER_UPDATE_LINES; + Invalidate(InvalidateFlags::NoErase); +} + +void Ruler::Deactivate() +{ + // clear positionlines + Invalidate(InvalidateFlags::NoErase); + + mbActive = false; +} + +bool Ruler::StartDocDrag( const MouseEvent& rMEvt, RulerType eDragType ) +{ + if ( !mbDrag ) + { + Point aMousePos = rMEvt.GetPosPixel(); + sal_uInt16 nMouseClicks = rMEvt.GetClicks(); + sal_uInt16 nMouseModifier = rMEvt.GetModifier(); + RulerSelection aHitTest; + + if(eDragType != RulerType::DontKnow) + aHitTest.bExpandTest = true; + + // update ruler + if ( mbFormat ) + { + if (!IsReallyVisible()) + { + // set mpData for ImplDocHitTest() + ImplFormat(*this); + } + + Invalidate(InvalidateFlags::NoErase); + } + + if ( nMouseClicks == 1 ) + { + if ( ImplDocHitTest( aMousePos, eDragType, &aHitTest ) ) + { + PointerStyle aPtr = PointerStyle::Arrow; + + if ( aHitTest.bSize ) + { + if ( mnWinStyle & WB_HORZ ) + aPtr = PointerStyle::ESize; + else + aPtr = PointerStyle::SSize; + } + else if ( aHitTest.bSizeBar ) + { + if ( mnWinStyle & WB_HORZ ) + aPtr = PointerStyle::HSizeBar; + else + aPtr = PointerStyle::VSizeBar; + } + SetPointer( aPtr ); + return ImplStartDrag( &aHitTest, nMouseModifier ); + } + } + else if ( nMouseClicks == 2 ) + { + if ( ImplDocHitTest( aMousePos, eDragType, &aHitTest ) ) + { + mnDragPos = aHitTest.nPos; + mnDragAryPos = aHitTest.nAryPos; + } + + DoubleClick(); + + mnDragPos = 0; + mnDragAryPos = 0; + + return true; + } + } + + return false; +} + +void Ruler::CancelDrag() +{ + if ( mbDrag ) + { + ImplDrag( Point( -1, -1 ) ); + ImplEndDrag(); + } +} + +RulerType Ruler::GetType( const Point& rPos, sal_uInt16* pAryPos ) +{ + RulerSelection aHitTest; + + // update ruler + if ( IsReallyVisible() && mbFormat ) + { + Invalidate(InvalidateFlags::NoErase); + } + + (void)ImplHitTest(rPos, &aHitTest); + + // return values + if ( pAryPos ) + *pAryPos = aHitTest.nAryPos; + return aHitTest.eType; +} + +void Ruler::SetWinPos( long nNewOff, long nNewWidth ) +{ + // should widths be automatically calculated + if ( !nNewWidth ) + mbAutoWinWidth = true; + else + mbAutoWinWidth = false; + + mnWinOff = nNewOff; + mnWinWidth = nNewWidth; + ImplUpdate( true ); +} + +void Ruler::SetPagePos( long nNewOff, long nNewWidth ) +{ + // should we do anything? + if ( (mpData->nPageOff == nNewOff) && (mpData->nPageWidth == nNewWidth) ) + return; + + // should widths be automatically calculated + if ( !nNewWidth ) + mpData->bAutoPageWidth = true; + else + mpData->bAutoPageWidth = false; + + mpData->nPageOff = nNewOff; + mpData->nPageWidth = nNewWidth; + ImplUpdate( true ); +} + +void Ruler::SetBorderPos( long nOff ) +{ + if ( mnWinStyle & WB_BORDER ) + { + if ( mnBorderOff != nOff ) + { + mnBorderOff = nOff; + + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(InvalidateFlags::NoErase); + } + } +} + +void Ruler::SetUnit( FieldUnit eNewUnit ) +{ + if ( meUnit == eNewUnit ) + return; + + meUnit = eNewUnit; + switch ( meUnit ) + { + case FieldUnit::MM: + mnUnitIndex = RULER_UNIT_MM; + break; + case FieldUnit::CM: + mnUnitIndex = RULER_UNIT_CM; + break; + case FieldUnit::M: + mnUnitIndex = RULER_UNIT_M; + break; + case FieldUnit::KM: + mnUnitIndex = RULER_UNIT_KM; + break; + case FieldUnit::INCH: + mnUnitIndex = RULER_UNIT_INCH; + break; + case FieldUnit::FOOT: + mnUnitIndex = RULER_UNIT_FOOT; + break; + case FieldUnit::MILE: + mnUnitIndex = RULER_UNIT_MILE; + break; + case FieldUnit::POINT: + mnUnitIndex = RULER_UNIT_POINT; + break; + case FieldUnit::PICA: + mnUnitIndex = RULER_UNIT_PICA; + break; + case FieldUnit::CHAR: + mnUnitIndex = RULER_UNIT_CHAR; + break; + case FieldUnit::LINE: + mnUnitIndex = RULER_UNIT_LINE; + break; + default: + SAL_WARN( "svtools.control", "Ruler::SetUnit() - Wrong Unit" ); + break; + } + + maMapMode.SetMapUnit( aImplRulerUnitTab[mnUnitIndex].eMapUnit ); + ImplUpdate(); +} + +void Ruler::SetZoom( const Fraction& rNewZoom ) +{ + DBG_ASSERT( rNewZoom.GetNumerator(), "Ruler::SetZoom() with scale 0 is not allowed" ); + + if ( maZoom != rNewZoom ) + { + maZoom = rNewZoom; + maMapMode.SetScaleX( maZoom ); + maMapMode.SetScaleY( maZoom ); + ImplUpdate(); + } +} + +void Ruler::SetExtraType( RulerExtra eNewExtraType, sal_uInt16 nStyle ) +{ + if ( mnWinStyle & WB_EXTRAFIELD ) + { + meExtraType = eNewExtraType; + mnExtraStyle = nStyle; + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + } +} + +void Ruler::SetNullOffset( long nPos ) +{ + if ( mpData->nNullOff != nPos ) + { + mpData->nNullVirOff += nPos - mpData->nNullOff; + mpData->nNullOff = nPos; + ImplUpdate(); + } +} + +void Ruler::SetLeftFrameMargin( long nPos ) +{ + if ( mpData->nLeftFrameMargin != nPos ) + { + mpData->nLeftFrameMargin = nPos; + ImplUpdate(); + } +} + +void Ruler::SetRightFrameMargin( long nPos ) +{ + if ( mpData->nRightFrameMargin != nPos ) + { + mpData->nRightFrameMargin = nPos; + ImplUpdate(); + } +} + +void Ruler::SetMargin1( long nPos, RulerMarginStyle nMarginStyle ) +{ + if ( (mpData->nMargin1 != nPos) || (mpData->nMargin1Style != nMarginStyle) ) + { + mpData->nMargin1 = nPos; + mpData->nMargin1Style = nMarginStyle; + ImplUpdate(); + } +} + +void Ruler::SetMargin2( long nPos, RulerMarginStyle nMarginStyle ) +{ + DBG_ASSERT( (nPos >= mpData->nMargin1) || + (mpData->nMargin1Style & RulerMarginStyle::Invisible) || + (mpData->nMargin2Style & RulerMarginStyle::Invisible), + "Ruler::SetMargin2() - Margin2 < Margin1" ); + + if ( (mpData->nMargin2 != nPos) || (mpData->nMargin2Style != nMarginStyle) ) + { + mpData->nMargin2 = nPos; + mpData->nMargin2Style = nMarginStyle; + ImplUpdate(); + } +} + +void Ruler::SetLines( sal_uInt32 aLineArraySize, const RulerLine* pLineArray ) +{ + // To determine if what has changed + if ( mpData->pLines.size() == aLineArraySize ) + { + sal_uInt32 i = aLineArraySize; + vector<RulerLine>::const_iterator aItr1 = mpData->pLines.begin(); + const RulerLine* pAry2 = pLineArray; + while ( i ) + { + if ( aItr1->nPos != pAry2->nPos ) + break; + ++aItr1; + ++pAry2; + i--; + } + if ( !i ) + return; + } + + // New values and new share issue + bool bMustUpdate; + bMustUpdate = IsReallyVisible() && IsUpdateMode(); + + // Delete old lines + if ( bMustUpdate ) + Invalidate(InvalidateFlags::NoErase); + + // New data set + if ( !aLineArraySize || !pLineArray ) + { + if ( mpData->pLines.empty() ) + return; + mpData->pLines.clear(); + } + else + { + if ( mpData->pLines.size() != aLineArraySize ) + { + mpData->pLines.resize(aLineArraySize); + } + + std::copy( pLineArray, + pLineArray + aLineArraySize, + mpData->pLines.begin() ); + + if ( bMustUpdate ) + Invalidate(InvalidateFlags::NoErase); + } +} + +void Ruler::SetBorders( sal_uInt32 aBorderArraySize, const RulerBorder* pBorderArray ) +{ + if ( !aBorderArraySize || !pBorderArray ) + { + if ( mpData->pBorders.empty() ) + return; + mpData->pBorders.clear(); + } + else + { + if ( mpData->pBorders.size() != aBorderArraySize ) + { + mpData->pBorders.resize(aBorderArraySize); + } + else + { + sal_uInt32 i = aBorderArraySize; + const RulerBorder* pAry1 = mpData->pBorders.data(); + const RulerBorder* pAry2 = pBorderArray; + while ( i ) + { + if ( (pAry1->nPos != pAry2->nPos) || + (pAry1->nWidth != pAry2->nWidth) || + (pAry1->nStyle != pAry2->nStyle) ) + break; + pAry1++; + pAry2++; + i--; + } + if ( !i ) + return; + } + std::copy( pBorderArray, + pBorderArray + aBorderArraySize, + mpData->pBorders.begin() ); + } + + ImplUpdate(); +} + +void Ruler::SetIndents( sal_uInt32 aIndentArraySize, const RulerIndent* pIndentArray ) +{ + + if ( !aIndentArraySize || !pIndentArray ) + { + if ( mpData->pIndents.empty() ) + return; + mpData->pIndents.clear(); + } + else + { + if ( mpData->pIndents.size() != aIndentArraySize ) + { + mpData->pIndents.resize(aIndentArraySize); + } + else + { + sal_uInt32 i = aIndentArraySize; + const RulerIndent* pAry1 = mpData->pIndents.data(); + const RulerIndent* pAry2 = pIndentArray; + while ( i ) + { + if ( (pAry1->nPos != pAry2->nPos) || + (pAry1->nStyle != pAry2->nStyle) ) + break; + pAry1++; + pAry2++; + i--; + } + if ( !i ) + return; + } + + std::copy( pIndentArray, + pIndentArray + aIndentArraySize, + mpData->pIndents.begin() ); + } + + ImplUpdate(); +} + +void Ruler::SetTabs( sal_uInt32 aTabArraySize, const RulerTab* pTabArray ) +{ + if ( aTabArraySize == 0 || pTabArray == nullptr ) + { + if ( mpData->pTabs.empty() ) + return; + mpData->pTabs.clear(); + } + else + { + if ( mpData->pTabs.size() != aTabArraySize ) + { + mpData->pTabs.resize(aTabArraySize); + } + else + { + sal_uInt32 i = aTabArraySize; + vector<RulerTab>::iterator aTabIterator = mpData->pTabs.begin(); + const RulerTab* pInputArray = pTabArray; + while ( i ) + { + RulerTab& aCurrent = *aTabIterator; + if ( aCurrent.nPos != pInputArray->nPos || + aCurrent.nStyle != pInputArray->nStyle ) + { + break; + } + ++aTabIterator; + pInputArray++; + i--; + } + if ( !i ) + return; + } + std::copy(pTabArray, pTabArray + aTabArraySize, mpData->pTabs.begin()); + } + + ImplUpdate(); +} + +const std::vector<RulerTab>& Ruler::GetTabs() const +{ + return mpData->pTabs; +} + +void Ruler::SetStyle( WinBits nStyle ) +{ + if ( mnWinStyle != nStyle ) + { + mnWinStyle = nStyle; + ImplInitExtraField( true ); + } +} + +void Ruler::DrawTab(vcl::RenderContext& rRenderContext, const Color &rFillColor, const Point& rPos, sal_uInt16 nStyle) +{ + Point aPos(rPos); + sal_uInt16 nTabStyle = nStyle & (RULER_TAB_STYLE | RULER_TAB_RTL); + + rRenderContext.Push(PushFlags::LINECOLOR | PushFlags::FILLCOLOR); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rFillColor); + ImplCenterTabPos(aPos, nTabStyle); + ImplDrawRulerTab(rRenderContext, aPos, nTabStyle, nStyle); + rRenderContext.Pop(); +} + +void Ruler::SetTextRTL(bool bRTL) +{ + if(mpData->bTextRTL != bRTL) + { + mpData->bTextRTL = bRTL; + if ( IsReallyVisible() && IsUpdateMode() ) + ImplInitExtraField( true ); + } + +} + +long Ruler::GetPageOffset() const +{ + return mpData->nPageOff; +} + +long Ruler::GetNullOffset() const +{ + return mpData->nNullOff; +} + +long Ruler::GetMargin1() const +{ + return mpData->nMargin1; +} + +long Ruler::GetMargin2() const +{ + return mpData->nMargin2; +} + + +bool Ruler::GetTextRTL() const +{ + return mpData->bTextRTL; +} + +const RulerUnitData& Ruler::GetCurrentRulerUnit() const +{ + return aImplRulerUnitTab[mnUnitIndex]; +} + +void Ruler::DrawTicks() +{ + mbFormat = true; + Invalidate(InvalidateFlags::NoErase); +} + +uno::Reference< XAccessible > Ruler::CreateAccessible() +{ + vcl::Window* pParent = GetAccessibleParentWindow(); + OSL_ENSURE( pParent, "-SvxRuler::CreateAccessible(): No Parent!" ); + uno::Reference< XAccessible > xAccParent = pParent->GetAccessible(); + if( xAccParent.is() ) + { + // MT: Fixed compiler issue because the address from a temporary object was used. + // BUT: Should it really be a Pointer, instead of const&??? + OUString aStr; + if ( mnWinStyle & WB_HORZ ) + { + aStr = SvtResId(STR_SVT_ACC_RULER_HORZ_NAME); + } + else + { + aStr = SvtResId(STR_SVT_ACC_RULER_VERT_NAME); + } + mxAccContext = new SvtRulerAccessible( xAccParent, *this, aStr ); + SetAccessible(mxAccContext.get()); + return mxAccContext.get(); + } + else + return uno::Reference< XAccessible >(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/scriptedtext.cxx b/svtools/source/control/scriptedtext.cxx new file mode 100644 index 000000000..4c7155dea --- /dev/null +++ b/svtools/source/control/scriptedtext.cxx @@ -0,0 +1,317 @@ +/* -*- 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 <svtools/scriptedtext.hxx> +#include <vector> +#include <rtl/ustring.hxx> +#include <vcl/outdev.hxx> +#include <vcl/font.hxx> +#include <tools/debug.hxx> +#include <tools/gen.hxx> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> + + +using namespace ::std; +using namespace ::com::sun::star; + + +class SvtScriptedTextHelper_Impl +{ +private: + OutputDevice& mrOutDevice; /// The output device for drawing the text. + vcl::Font maLatinFont; /// The font for latin text portions. + vcl::Font maAsianFont; /// The font for asian text portions. + vcl::Font maCmplxFont; /// The font for complex text portions. + vcl::Font maDefltFont; /// The default font of the output device. + OUString maText; /// The text. + + vector< sal_Int32 > maPosVec; /// The start position of each text portion. + vector< sal_Int16 > maScriptVec; /// The script type of each text portion. + vector< sal_Int32 > maWidthVec; /// The output width of each text portion. + Size maTextSize; /// The size the text will take in the current output device. + + /** Gets the font of the given script type. */ + const vcl::Font& GetFont( sal_uInt16 _nScript ) const; + /** Sets a font on the output device depending on the script type. */ + void SetOutDevFont( sal_uInt16 _nScript ) + { mrOutDevice.SetFont( GetFont( _nScript ) ); } + /** Fills maPosVec with positions of all changes of script type. + This method expects correctly initialized maPosVec and maScriptVec. */ + void CalculateSizes(); + /** Fills maPosVec with positions of all changes of script type and + maScriptVec with the script type of each portion. */ + void CalculateBreaks( + const uno::Reference< i18n::XBreakIterator >& _xBreakIter ); + +public: + /** This constructor sets an output device and fonts for all script types. */ + explicit SvtScriptedTextHelper_Impl( + OutputDevice& _rOutDevice ); + + /** Sets new fonts and recalculates the text width. */ + void SetFonts( vcl::Font const * _pLatinFont, vcl::Font const * _pAsianFont, vcl::Font const * _pCmplxFont ); + /** Sets a new text and calculates all script breaks and the text width. */ + void SetText( + const OUString& _rText, + const uno::Reference< i18n::XBreakIterator >& _xBreakIter ); + + /** Returns a size struct containing the width and height of the text in the current output device. */ + const Size& GetTextSize() const { return maTextSize;} + + /** Draws the text in the current output device. */ + void DrawText( const Point& _rPos ); +}; + + +SvtScriptedTextHelper_Impl::SvtScriptedTextHelper_Impl( + OutputDevice& _rOutDevice ) : + mrOutDevice( _rOutDevice ), + maLatinFont( _rOutDevice.GetFont() ), + maAsianFont( _rOutDevice.GetFont() ), + maCmplxFont( _rOutDevice.GetFont() ), + maDefltFont( _rOutDevice.GetFont() ) +{ +} + +const vcl::Font& SvtScriptedTextHelper_Impl::GetFont( sal_uInt16 _nScript ) const +{ + switch( _nScript ) + { + case i18n::ScriptType::LATIN: return maLatinFont; + case i18n::ScriptType::ASIAN: return maAsianFont; + case i18n::ScriptType::COMPLEX: return maCmplxFont; + } + return maDefltFont; +} + +void SvtScriptedTextHelper_Impl::CalculateSizes() +{ + maTextSize.setWidth(0); + maTextSize.setHeight(0); + mrOutDevice.Push(PushFlags::FONT | PushFlags::TEXTCOLOR); + + // calculate text portion widths and total width + maWidthVec.clear(); + if( !maPosVec.empty() ) + { + DBG_ASSERT( maPosVec.size() - 1 == maScriptVec.size(), + "SvtScriptedTextHelper_Impl::CalculateWidth - invalid vectors" ); + + sal_Int32 nThisPos = maPosVec[ 0 ]; + sal_Int32 nNextPos; + sal_Int32 nPosVecSize = maPosVec.size(); + sal_Int32 nPosVecIndex = 1; + + sal_Int16 nScript; + sal_Int32 nScriptVecIndex = 0; + + sal_Int32 nCurrWidth; + + while( nPosVecIndex < nPosVecSize ) + { + nNextPos = maPosVec[ nPosVecIndex++ ]; + nScript = maScriptVec[ nScriptVecIndex++ ]; + + SetOutDevFont( nScript ); + nCurrWidth = mrOutDevice.GetTextWidth( maText, nThisPos, nNextPos - nThisPos ); + maWidthVec.push_back( nCurrWidth ); + maTextSize.AdjustWidth(nCurrWidth ); + nThisPos = nNextPos; + } + } + + // calculate maximum font height + SetOutDevFont( i18n::ScriptType::LATIN ); + maTextSize.setHeight( std::max( maTextSize.Height(), mrOutDevice.GetTextHeight() ) ); + SetOutDevFont( i18n::ScriptType::ASIAN ); + maTextSize.setHeight( std::max( maTextSize.Height(), mrOutDevice.GetTextHeight() ) ); + SetOutDevFont( i18n::ScriptType::COMPLEX ); + maTextSize.setHeight( std::max( maTextSize.Height(), mrOutDevice.GetTextHeight() ) ); + + mrOutDevice.Pop(); +} + +void SvtScriptedTextHelper_Impl::CalculateBreaks( const uno::Reference< i18n::XBreakIterator >& _xBreakIter ) +{ + maPosVec.clear(); + maScriptVec.clear(); + + DBG_ASSERT( _xBreakIter.is(), "SvtScriptedTextHelper_Impl::CalculateBreaks - no break iterator" ); + + sal_Int32 nLen = maText.getLength(); + if( nLen ) + { + if( _xBreakIter.is() ) + { + sal_Int32 nThisPos = 0; // first position of this portion + sal_Int32 nNextPos = 0; // first position of next portion + sal_Int16 nPortScript; // script type of this portion + do + { + nPortScript = _xBreakIter->getScriptType( maText, nThisPos ); + nNextPos = _xBreakIter->endOfScript( maText, nThisPos, nPortScript ); + + switch( nPortScript ) + { + case i18n::ScriptType::LATIN: + case i18n::ScriptType::ASIAN: + case i18n::ScriptType::COMPLEX: + maPosVec.push_back( nThisPos ); + maScriptVec.push_back( nPortScript ); + break; + default: + { +/* *** handling of weak characters *** +- first portion is weak: Use OutputDevice::HasGlyphs() to find the correct font +- weak portion follows another portion: Script type of preceding portion is used */ + if( maPosVec.empty() ) + { + sal_Int32 nCharIx = 0; + sal_Int32 nNextCharIx = 0; + sal_Int16 nScript; + do + { + nScript = i18n::ScriptType::LATIN; + while( (nScript != i18n::ScriptType::WEAK) && (nCharIx == nNextCharIx) ) + { + nNextCharIx = mrOutDevice.HasGlyphs( GetFont( nScript ), maText, nCharIx, nNextPos - nCharIx ); + if( nCharIx == nNextCharIx ) + ++nScript; + } + if( nNextCharIx == nCharIx ) + ++nNextCharIx; + + maPosVec.push_back( nCharIx ); + maScriptVec.push_back( nScript ); + nCharIx = nNextCharIx; + } + while( nCharIx < nNextPos && nCharIx != -1 ); + } + // nothing to do for following portions + } + } + nThisPos = nNextPos; + } + while( (0 <= nThisPos) && (nThisPos < nLen) ); + } + else // no break iterator: whole text LATIN + { + maPosVec.push_back( 0 ); + maScriptVec.push_back( i18n::ScriptType::LATIN ); + } + + // push end position of last portion + if( !maPosVec.empty() ) + maPosVec.push_back( nLen ); + } + CalculateSizes(); +} + +void SvtScriptedTextHelper_Impl::SetFonts( vcl::Font const * _pLatinFont, vcl::Font const * _pAsianFont, vcl::Font const * _pCmplxFont ) +{ + maLatinFont = _pLatinFont ? *_pLatinFont : maDefltFont; + maAsianFont = _pAsianFont ? *_pAsianFont : maDefltFont; + maCmplxFont = _pCmplxFont ? *_pCmplxFont : maDefltFont; + CalculateSizes(); +} + +void SvtScriptedTextHelper_Impl::SetText( const OUString& _rText, const uno::Reference< i18n::XBreakIterator >& _xBreakIter ) +{ + maText = _rText; + CalculateBreaks( _xBreakIter ); +} + + +void SvtScriptedTextHelper_Impl::DrawText( const Point& _rPos ) +{ + if( maText.isEmpty() || maPosVec.empty() ) + return; + + DBG_ASSERT( maPosVec.size() - 1 == maScriptVec.size(), "SvtScriptedTextHelper_Impl::DrawText - invalid vectors" ); + DBG_ASSERT( maScriptVec.size() == maWidthVec.size(), "SvtScriptedTextHelper_Impl::DrawText - invalid vectors" ); + + mrOutDevice.Push(PushFlags::FONT | PushFlags::TEXTCOLOR); + + Point aCurrPos( _rPos ); + sal_Int32 nThisPos = maPosVec[ 0 ]; + sal_Int32 nNextPos; + sal_Int32 nPosVecSize = maPosVec.size(); + sal_Int32 nPosVecIndex = 1; + + sal_Int16 nScript; + sal_Int32 nVecIndex = 0; + + while( nPosVecIndex < nPosVecSize ) + { + nNextPos = maPosVec[ nPosVecIndex++ ]; + nScript = maScriptVec[ nVecIndex ]; + + SetOutDevFont( nScript ); + mrOutDevice.DrawText( aCurrPos, maText, nThisPos, nNextPos - nThisPos ); + aCurrPos.AdjustX(maWidthVec[ nVecIndex++ ] ); + aCurrPos.AdjustX(mrOutDevice.GetTextHeight() / 5 ); // add 20% of font height as portion spacing + nThisPos = nNextPos; + } + + mrOutDevice.Pop(); +} + + +SvtScriptedTextHelper::SvtScriptedTextHelper( OutputDevice& _rOutDevice ) : + mpImpl( new SvtScriptedTextHelper_Impl( _rOutDevice ) ) +{ +} + +SvtScriptedTextHelper::SvtScriptedTextHelper( const SvtScriptedTextHelper& _rCopy ) : + mpImpl( new SvtScriptedTextHelper_Impl( *_rCopy.mpImpl ) ) +{ +} + +SvtScriptedTextHelper::~SvtScriptedTextHelper() +{ +} + +void SvtScriptedTextHelper::SetFonts( vcl::Font const * _pLatinFont, vcl::Font const * _pAsianFont, vcl::Font const * _pCmplxFont ) +{ + mpImpl->SetFonts( _pLatinFont, _pAsianFont, _pCmplxFont ); +} + +void SvtScriptedTextHelper::SetDefaultFont() +{ + mpImpl->SetFonts( nullptr, nullptr, nullptr ); +} + +void SvtScriptedTextHelper::SetText( const OUString& _rText, const uno::Reference< i18n::XBreakIterator >& _xBreakIter ) +{ + mpImpl->SetText( _rText, _xBreakIter ); +} + +const Size& SvtScriptedTextHelper::GetTextSize() const +{ + return mpImpl->GetTextSize(); +} + +void SvtScriptedTextHelper::DrawText( const Point& _rPos ) +{ + mpImpl->DrawText( _rPos ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/scrwin.cxx b/svtools/source/control/scrwin.cxx new file mode 100644 index 000000000..b56fe3db8 --- /dev/null +++ b/svtools/source/control/scrwin.cxx @@ -0,0 +1,345 @@ +/* -*- 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 <svtools/scrwin.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/scrbar.hxx> + +ScrollableWindow::ScrollableWindow( vcl::Window* pParent ) : + Window( pParent, WB_CLIPCHILDREN ), + aVScroll( VclPtr<ScrollBar>::Create(this, WinBits(WB_VSCROLL | WB_DRAG)) ), + aHScroll( VclPtr<ScrollBar>::Create(this, WinBits(WB_HSCROLL | WB_DRAG)) ), + aCornerWin( VclPtr<ScrollBarBox>::Create(this) ) +{ + bScrolling = false; + + // set the handlers for the scrollbars + aVScroll->SetScrollHdl( LINK(this, ScrollableWindow, ScrollHdl) ); + aHScroll->SetScrollHdl( LINK(this, ScrollableWindow, ScrollHdl) ); + aVScroll->SetEndScrollHdl( LINK(this, ScrollableWindow, EndScrollHdl) ); + aHScroll->SetEndScrollHdl( LINK(this, ScrollableWindow, EndScrollHdl) ); + + nColumnPixW = nLinePixH = GetSettings().GetStyleSettings().GetScrollBarSize(); +} + + +ScrollableWindow::~ScrollableWindow() +{ + disposeOnce(); +} + +void ScrollableWindow::dispose() +{ + aVScroll.disposeAndClear(); + aHScroll.disposeAndClear(); + aCornerWin.disposeAndClear(); + Window::dispose(); +} + + +void ScrollableWindow::Command( const CommandEvent& rCEvt ) +{ + if ( (rCEvt.GetCommand() == CommandEventId::Wheel) || + (rCEvt.GetCommand() == CommandEventId::StartAutoScroll) || + (rCEvt.GetCommand() == CommandEventId::AutoScroll) ) + { + ScrollBar* pHScrBar; + ScrollBar* pVScrBar; + if ( aHScroll->IsVisible() ) + pHScrBar = aHScroll.get(); + else + pHScrBar = nullptr; + if ( aVScroll->IsVisible() ) + pVScrBar = aVScroll.get(); + else + pVScrBar = nullptr; + if ( HandleScrollCommand( rCEvt, pHScrBar, pVScrBar ) ) + return; + } + + Window::Command( rCEvt ); +} + + +void ScrollableWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + Resize(); + Invalidate(); + } + + Window::DataChanged( rDCEvt ); +} + + +Size ScrollableWindow::GetOutputSizePixel() const +{ + Size aSz( Window::GetOutputSizePixel() ); + + long nTmp = GetSettings().GetStyleSettings().GetScrollBarSize(); + if ( aHScroll->IsVisible() ) + aSz.AdjustHeight( -nTmp ); + if ( aVScroll->IsVisible() ) + aSz.AdjustWidth( -nTmp ); + return aSz; +} + + +IMPL_LINK( ScrollableWindow, EndScrollHdl, ScrollBar *, /*pScroll*/, void ) +{ + // notify the end of scrolling + bScrolling = false; +} + + +IMPL_LINK( ScrollableWindow, ScrollHdl, ScrollBar *, pScroll, void ) +{ + // notify the start of scrolling, if not already scrolling + if ( !bScrolling ) + bScrolling = true; + + // get the delta in logic coordinates + Size aDelta( PixelToLogic( + Size( aHScroll->GetDelta(), aVScroll->GetDelta() ) ) ); + if ( pScroll == aHScroll.get() ) + Scroll( aDelta.Width(), 0 ); + else + Scroll( 0, aDelta.Height() ); +} + + +void ScrollableWindow::Resize() +{ + // get the new output-size in pixel + Size aOutPixSz = Window::GetOutputSizePixel(); + + // determine the size of the output-area and if we need scrollbars + const long nScrSize = GetSettings().GetStyleSettings().GetScrollBarSize(); + bool bVVisible = false; // by default no vertical-ScrollBar + bool bHVisible = false; // by default no horizontal-ScrollBar + bool bChanged; // determines if a visiblility was changed + do + { + bChanged = false; + + // does we need a vertical ScrollBar + if ( aOutPixSz.Width() < aTotPixSz.Width() && !bHVisible ) + { + bHVisible = true; + aOutPixSz.AdjustHeight( -nScrSize ); + bChanged = true; + } + + // does we need a horizontal ScrollBar + if ( aOutPixSz.Height() < aTotPixSz.Height() && !bVVisible ) + { + bVVisible = true; + aOutPixSz.AdjustWidth( -nScrSize ); + bChanged = true; + } + + } + while ( bChanged ); // until no visibility has changed + + // store the old offset and map-mode + MapMode aMap( GetMapMode() ); + Point aOldPixOffset( aPixOffset ); + + // justify (right/bottom borders should never exceed the virtual window) + Size aPixDelta; + if ( aPixOffset.X() < 0 && + aPixOffset.X() + aTotPixSz.Width() < aOutPixSz.Width() ) + aPixDelta.setWidth( + aOutPixSz.Width() - ( aPixOffset.X() + aTotPixSz.Width() ) ); + if ( aPixOffset.Y() < 0 && + aPixOffset.Y() + aTotPixSz.Height() < aOutPixSz.Height() ) + aPixDelta.setHeight( + aOutPixSz.Height() - ( aPixOffset.Y() + aTotPixSz.Height() ) ); + if ( aPixDelta.Width() || aPixDelta.Height() ) + { + aPixOffset.AdjustX(aPixDelta.Width() ); + aPixOffset.AdjustY(aPixDelta.Height() ); + } + + // for axis without scrollbar restore the origin + if ( !bVVisible || !bHVisible ) + { + aPixOffset = Point( + bHVisible + ? aPixOffset.X() + : (aOutPixSz.Width()-aTotPixSz.Width()) / 2, + bVVisible + ? aPixOffset.Y() + : (aOutPixSz.Height()-aTotPixSz.Height()) / 2 ); + } + if ( bHVisible && !aHScroll->IsVisible() ) + aPixOffset.setX( 0 ); + if ( bVVisible && !aVScroll->IsVisible() ) + aPixOffset.setY( 0 ); + + // select the shifted map-mode + if ( aPixOffset != aOldPixOffset ) + { + Window::SetMapMode( MapMode( MapUnit::MapPixel ) ); + Window::Scroll( + aPixOffset.X() - aOldPixOffset.X(), + aPixOffset.Y() - aOldPixOffset.Y() ); + SetMapMode( aMap ); + } + + // show or hide scrollbars + aVScroll->Show( bVVisible ); + aHScroll->Show( bHVisible ); + + // disable painting in the corner between the scrollbars + if ( bVVisible && bHVisible ) + { + aCornerWin->SetPosSizePixel(Point(aOutPixSz.Width(), aOutPixSz.Height()), + Size(nScrSize, nScrSize) ); + aCornerWin->Show(); + } + else + aCornerWin->Hide(); + + // resize scrollbars and set their ranges + if ( bHVisible ) + { + aHScroll->SetPosSizePixel( + Point( 0, aOutPixSz.Height() ), + Size( aOutPixSz.Width(), nScrSize ) ); + aHScroll->SetRange( Range( 0, aTotPixSz.Width() ) ); + aHScroll->SetPageSize( aOutPixSz.Width() ); + aHScroll->SetVisibleSize( aOutPixSz.Width() ); + aHScroll->SetLineSize( nColumnPixW ); + aHScroll->SetThumbPos( -aPixOffset.X() ); + } + if ( bVVisible ) + { + aVScroll->SetPosSizePixel( + Point( aOutPixSz.Width(), 0 ), + Size( nScrSize,aOutPixSz.Height() ) ); + aVScroll->SetRange( Range( 0, aTotPixSz.Height() ) ); + aVScroll->SetPageSize( aOutPixSz.Height() ); + aVScroll->SetVisibleSize( aOutPixSz.Height() ); + aVScroll->SetLineSize( nLinePixH ); + aVScroll->SetThumbPos( -aPixOffset.Y() ); + } +} + + +void ScrollableWindow::SetMapMode( const MapMode& rNewMapMode ) +{ + MapMode aMap( rNewMapMode ); + aMap.SetOrigin( aMap.GetOrigin() + PixelToLogic( aPixOffset, aMap ) ); + Window::SetMapMode( aMap ); +} + + +MapMode ScrollableWindow::GetMapMode() const +{ + MapMode aMap( Window::GetMapMode() ); + aMap.SetOrigin( aMap.GetOrigin() - PixelToLogic( aPixOffset ) ); + return aMap; +} + + +void ScrollableWindow::SetTotalSize( const Size& rNewSize ) +{ + aTotPixSz = LogicToPixel( rNewSize ); + ScrollableWindow::Resize(); +} + + +void ScrollableWindow::Scroll( long nDeltaX, long nDeltaY, ScrollFlags ) +{ + // get the delta in pixel + Size aDeltaPix( LogicToPixel( Size(nDeltaX, nDeltaY) ) ); + Size aOutPixSz( GetOutputSizePixel() ); + MapMode aMap( GetMapMode() ); + Point aNewPixOffset( aPixOffset ); + + // scrolling horizontally? + if ( nDeltaX != 0 ) + { + aNewPixOffset.AdjustX( -(aDeltaPix.Width()) ); + if ( ( aOutPixSz.Width() - aNewPixOffset.X() ) > aTotPixSz.Width() ) + aNewPixOffset.setX( - ( aTotPixSz.Width() - aOutPixSz.Width() ) ); + else if ( aNewPixOffset.X() > 0 ) + aNewPixOffset.setX( 0 ); + } + + // scrolling vertically? + if ( nDeltaY != 0 ) + { + aNewPixOffset.AdjustY( -(aDeltaPix.Height()) ); + if ( ( aOutPixSz.Height() - aNewPixOffset.Y() ) > aTotPixSz.Height() ) + aNewPixOffset.setY( - ( aTotPixSz.Height() - aOutPixSz.Height() ) ); + else if ( aNewPixOffset.Y() > 0 ) + aNewPixOffset.setY( 0 ); + } + + // recompute the logical scroll units + aDeltaPix.setWidth( aPixOffset.X() - aNewPixOffset.X() ); + aDeltaPix.setHeight( aPixOffset.Y() - aNewPixOffset.Y() ); + Size aDelta( PixelToLogic(aDeltaPix) ); + nDeltaX = aDelta.Width(); + nDeltaY = aDelta.Height(); + aPixOffset = aNewPixOffset; + + // scrolling? + if ( nDeltaX != 0 || nDeltaY != 0 ) + { + PaintImmediately(); + + // does the new area overlap the old one? + if ( std::abs( static_cast<int>(aDeltaPix.Height()) ) < aOutPixSz.Height() || + std::abs( static_cast<int>(aDeltaPix.Width()) ) < aOutPixSz.Width() ) + { + // scroll the overlapping area + SetMapMode( aMap ); + + // never scroll the scrollbars itself! + Window::Scroll(-nDeltaX, -nDeltaY, + PixelToLogic( tools::Rectangle( Point(0, 0), aOutPixSz ) ) ); + } + else + { + // repaint all + SetMapMode( aMap ); + Invalidate(); + } + + PaintImmediately(); + } + + if ( !bScrolling ) + { + if ( nDeltaX ) + aHScroll->SetThumbPos( -aPixOffset.X() ); + if ( nDeltaY ) + aVScroll->SetThumbPos( -aPixOffset.Y() ); + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/tabbar.cxx b/svtools/source/control/tabbar.cxx new file mode 100644 index 000000000..6670f1182 --- /dev/null +++ b/svtools/source/control/tabbar.cxx @@ -0,0 +1,2525 @@ +/* -*- 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 <svtools/tabbar.hxx> +#include <tools/time.hxx> +#include <tools/poly.hxx> +#include <vcl/svapp.hxx> +#include <vcl/help.hxx> +#include <vcl/decoview.hxx> +#include <vcl/button.hxx> +#include <vcl/edit.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/svtaccessiblefactory.hxx> +#include <vcl/accessiblefactory.hxx> +#include <vcl/ptrstyle.hxx> +#include <svtools/svtresid.hxx> +#include <svtools/strings.hrc> +#include <limits> +#include <memory> +#include <utility> +#include <vector> +#include <vcl/idle.hxx> +#include <bitmaps.hlst> + +namespace +{ + +constexpr sal_uInt16 TABBAR_DRAG_SCROLLOFF = 5; +constexpr sal_uInt16 TABBAR_MINSIZE = 5; + +constexpr sal_uInt16 ADDNEWPAGE_AREAWIDTH = 10; +constexpr sal_uInt16 BUTTON_MARGIN = 6; + +class TabDrawer +{ +private: + vcl::RenderContext& mrRenderContext; + const StyleSettings& mrStyleSettings; + + tools::Rectangle maRect; + tools::Rectangle maLineRect; + + Color maSelectedColor; + Color maCustomColor; + +public: + bool mbSelected:1; + bool mbCustomColored:1; + bool mbEnabled:1; + bool mbProtect:1; + + explicit TabDrawer(vcl::RenderContext& rRenderContext) + : mrRenderContext(rRenderContext) + , mrStyleSettings(rRenderContext.GetSettings().GetStyleSettings()) + , mbSelected(false) + , mbCustomColored(false) + , mbEnabled(false) + , mbProtect(false) + { + + } + + void drawOuterFrame() + { + // set correct FillInBrush depending on status + if (mbSelected) + { + // Currently selected Tab + mrRenderContext.SetFillColor(maSelectedColor); + mrRenderContext.SetLineColor(maSelectedColor); + mrRenderContext.DrawRect(maRect); + mrRenderContext.SetLineColor(mrStyleSettings.GetDarkShadowColor()); + } + else if (mbCustomColored) + { + mrRenderContext.SetFillColor(maCustomColor); + mrRenderContext.SetLineColor(maCustomColor); + mrRenderContext.DrawRect(maRect); + mrRenderContext.SetLineColor(mrStyleSettings.GetDarkShadowColor()); + } + } + + void drawText(const OUString& aText) + { + tools::Rectangle aRect = maRect; + long nTextWidth = mrRenderContext.GetTextWidth(aText); + long nTextHeight = mrRenderContext.GetTextHeight(); + Point aPos = aRect.TopLeft(); + aPos.AdjustX((aRect.getWidth() - nTextWidth) / 2 ); + aPos.AdjustY((aRect.getHeight() - nTextHeight) / 2 ); + + if (mbEnabled) + mrRenderContext.DrawText(aPos, aText); + else + mrRenderContext.DrawCtrlText(aPos, aText, 0, aText.getLength(), (DrawTextFlags::Disable | DrawTextFlags::Mnemonic)); + } + + void drawOverTopBorder() + { + Point aTopLeft = maRect.TopLeft() + Point(1, 0); + Point aTopRight = maRect.TopRight() + Point(-1, 0); + + tools::Rectangle aDelRect(aTopLeft, aTopRight); + mrRenderContext.DrawRect(aDelRect); + } + + void drawColorLine() + { + if (mbCustomColored && mbSelected) + { + mrRenderContext.SetFillColor(maCustomColor); + mrRenderContext.SetLineColor(maCustomColor); + mrRenderContext.DrawRect(maLineRect); + } + else if (mbSelected) + { + mrRenderContext.SetFillColor(mrStyleSettings.GetDarkShadowColor()); + mrRenderContext.SetLineColor(mrStyleSettings.GetDarkShadowColor()); + mrRenderContext.DrawRect(maLineRect); + } + } + + void drawTab() + { + drawOuterFrame(); + drawColorLine(); + if (mbProtect) + { + BitmapEx aBitmap(BMP_TAB_LOCK); + Point aPosition = maRect.TopLeft(); + aPosition.AdjustX(2); + aPosition.AdjustY((maRect.getHeight() - aBitmap.GetSizePixel().Height()) / 2); + mrRenderContext.DrawBitmapEx(aPosition, aBitmap); + } + } + + void setRect(const tools::Rectangle& rRect) + { + maLineRect = tools::Rectangle(rRect.BottomLeft(), rRect.BottomRight()); + maLineRect.AdjustTop(-2); + maRect = rRect; + } + + void setSelected(bool bSelected) + { + mbSelected = bSelected; + } + + void setCustomColored(bool bCustomColored) + { + mbCustomColored = bCustomColored; + } + + void setEnabled(bool bEnabled) + { + mbEnabled = bEnabled; + } + + void setSelectedFillColor(const Color& rColor) + { + maSelectedColor = rColor; + } + + void setCustomColor(const Color& rColor) + { + maCustomColor = rColor; + } +}; + +} // anonymous namespace + +struct ImplTabBarItem +{ + sal_uInt16 mnId; + TabBarPageBits mnBits; + OUString maText; + OUString maHelpText; + OUString maAuxiliaryText; // used in LayerTabBar for real layer name + tools::Rectangle maRect; + long mnWidth; + OString maHelpId; + bool mbShort : 1; + bool mbSelect : 1; + bool mbProtect : 1; + Color maTabBgColor; + Color maTabTextColor; + + ImplTabBarItem(sal_uInt16 nItemId, const OUString& rText, TabBarPageBits nPageBits) + : mnId(nItemId) + , mnBits(nPageBits) + , maText(rText) + , mnWidth(0) + , mbShort(false) + , mbSelect(false) + , mbProtect(false) + , maTabBgColor(COL_AUTO) + , maTabTextColor(COL_AUTO) + { + } + + bool IsDefaultTabBgColor() const + { + return maTabBgColor == COL_AUTO; + } + + bool IsSelected(ImplTabBarItem const * pCurItem) const + { + return mbSelect || (pCurItem == this); + } + + OUString const & GetRenderText() const + { + return maText; + } +}; + +class ImplTabButton : public PushButton +{ + bool mbModKey : 1; + +public: + ImplTabButton(TabBar* pParent, WinBits nWinStyle = 0) + : PushButton(pParent, nWinStyle | WB_FLATBUTTON | WB_RECTSTYLE | WB_SMALLSTYLE | WB_NOLIGHTBORDER | WB_NOPOINTERFOCUS) + , mbModKey(false) + {} + + TabBar* GetParent() const + { + return static_cast<TabBar*>(Window::GetParent()); + } + + bool isModKeyPressed() const + { + return mbModKey; + } + + virtual bool PreNotify(NotifyEvent& rNotifyEvent) override; + virtual void MouseButtonDown(const MouseEvent& rMouseEvent) override; + virtual void MouseButtonUp(const MouseEvent& rMouseEvent) override; + virtual void Command(const CommandEvent& rCommandEvent) override; +}; + +void ImplTabButton::MouseButtonDown(const MouseEvent& rMouseEvent) +{ + mbModKey = rMouseEvent.IsMod1(); + PushButton::MouseButtonDown(rMouseEvent); +} + +void ImplTabButton::MouseButtonUp(const MouseEvent& rMouseEvent) +{ + mbModKey = false; + PushButton::MouseButtonUp(rMouseEvent); +} + +void ImplTabButton::Command(const CommandEvent& rCommandEvent) +{ + if (rCommandEvent.GetCommand() == CommandEventId::ContextMenu) + { + TabBar* pParent = GetParent(); + pParent->maScrollAreaContextHdl.Call(rCommandEvent); + } + PushButton::Command(rCommandEvent); +} + +bool ImplTabButton::PreNotify(NotifyEvent& rNotifyEvent) +{ + if (rNotifyEvent.GetType() == MouseNotifyEvent::MOUSEBUTTONDOWN) + { + if (GetParent()->IsInEditMode()) + { + GetParent()->EndEditMode(); + return true; + } + } + + return PushButton::PreNotify(rNotifyEvent); +} + +class ImplTabSizer : public vcl::Window +{ +public: + ImplTabSizer( TabBar* pParent, WinBits nWinStyle ); + + TabBar* GetParent() const { return static_cast<TabBar*>(Window::GetParent()); } + +private: + void ImplTrack( const Point& rScreenPos ); + + virtual void MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual void Tracking( const TrackingEvent& rTEvt ) override; + virtual void Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) override; + + Point maStartPos; + long mnStartWidth; +}; + +ImplTabSizer::ImplTabSizer( TabBar* pParent, WinBits nWinStyle ) + : Window( pParent, nWinStyle & WB_3DLOOK ) + , mnStartWidth(0) +{ + SetPointer(PointerStyle::HSizeBar); + SetSizePixel(Size(7 * GetDPIScaleFactor(), 0)); +} + +void ImplTabSizer::ImplTrack( const Point& rScreenPos ) +{ + TabBar* pParent = GetParent(); + long nDiff = rScreenPos.X() - maStartPos.X(); + pParent->mnSplitSize = mnStartWidth + (pParent->IsMirrored() ? -nDiff : nDiff); + if ( pParent->mnSplitSize < TABBAR_MINSIZE ) + pParent->mnSplitSize = TABBAR_MINSIZE; + pParent->Split(); + pParent->PaintImmediately(); +} + +void ImplTabSizer::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if ( GetParent()->IsInEditMode() ) + { + GetParent()->EndEditMode(); + return; + } + + if ( rMEvt.IsLeft() ) + { + maStartPos = OutputToScreenPixel( rMEvt.GetPosPixel() ); + mnStartWidth = GetParent()->GetSizePixel().Width(); + StartTracking(); + } +} + +void ImplTabSizer::Tracking( const TrackingEvent& rTEvt ) +{ + if ( rTEvt.IsTrackingEnded() ) + { + if ( rTEvt.IsTrackingCanceled() ) + ImplTrack( maStartPos ); + GetParent()->mnSplitSize = 0; + } + else + ImplTrack( OutputToScreenPixel( rTEvt.GetMouseEvent().GetPosPixel() ) ); +} + +void ImplTabSizer::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& ) +{ + DecorationView aDecoView(&rRenderContext); + tools::Rectangle aOutputRect(Point(0, 0), GetOutputSizePixel()); + aDecoView.DrawHandle(aOutputRect); +} + +namespace { + +// Is not named Impl. as it may be both instantiated and derived from +class TabBarEdit : public Edit +{ +private: + Idle maLoseFocusIdle; + bool mbPostEvt; + + DECL_LINK( ImplEndEditHdl, void*, void ); + DECL_LINK( ImplEndTimerHdl, Timer*, void ); + +public: + TabBarEdit( TabBar* pParent, WinBits nWinStyle ); + + TabBar* GetParent() const { return static_cast<TabBar*>(Window::GetParent()); } + + void SetPostEvent() { mbPostEvt = true; } + void ResetPostEvent() { mbPostEvt = false; } + + virtual bool PreNotify( NotifyEvent& rNEvt ) override; + virtual void LoseFocus() override; +}; + +} + +TabBarEdit::TabBarEdit( TabBar* pParent, WinBits nWinStyle ) : + Edit( pParent, nWinStyle ) +{ + mbPostEvt = false; + maLoseFocusIdle.SetPriority( TaskPriority::REPAINT ); + maLoseFocusIdle.SetInvokeHandler( LINK( this, TabBarEdit, ImplEndTimerHdl ) ); + maLoseFocusIdle.SetDebugName( "svtools::TabBarEdit maLoseFocusIdle" ); +} + +bool TabBarEdit::PreNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::KEYINPUT ) + { + const KeyEvent* pKEvt = rNEvt.GetKeyEvent(); + if ( !pKEvt->GetKeyCode().GetModifier() ) + { + if ( pKEvt->GetKeyCode().GetCode() == KEY_RETURN ) + { + if ( !mbPostEvt ) + { + if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(false), true ) ) + mbPostEvt = true; + } + return true; + } + else if ( pKEvt->GetKeyCode().GetCode() == KEY_ESCAPE ) + { + if ( !mbPostEvt ) + { + if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(true), true ) ) + mbPostEvt = true; + } + return true; + } + } + } + + return Edit::PreNotify( rNEvt ); +} + +void TabBarEdit::LoseFocus() +{ + if ( !mbPostEvt ) + { + if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(false), true ) ) + mbPostEvt = true; + } + + Edit::LoseFocus(); +} + +IMPL_LINK( TabBarEdit, ImplEndEditHdl, void*, pCancel, void ) +{ + ResetPostEvent(); + maLoseFocusIdle.Stop(); + + // We need this query, because the edit gets a losefocus event, + // when it shows the context menu or the insert symbol dialog + if ( !HasFocus() && HasChildPathFocus( true ) ) + { + maLoseFocusIdle.Start(); + } + else + GetParent()->EndEditMode( pCancel != nullptr ); +} + +IMPL_LINK_NOARG(TabBarEdit, ImplEndTimerHdl, Timer *, void) +{ + if ( HasFocus() ) + return; + + // We need this query, because the edit gets a losefocus event, + // when it shows the context menu or the insert symbol dialog + if ( HasChildPathFocus( true ) ) + maLoseFocusIdle.Start(); + else + GetParent()->EndEditMode( true ); +} + +struct TabBar_Impl +{ + ScopedVclPtr<ImplTabSizer> mpSizer; + ScopedVclPtr<ImplTabButton> mpFirstButton; + ScopedVclPtr<ImplTabButton> mpPrevButton; + ScopedVclPtr<ImplTabButton> mpNextButton; + ScopedVclPtr<ImplTabButton> mpLastButton; + ScopedVclPtr<ImplTabButton> mpAddButton; + ScopedVclPtr<TabBarEdit> mpEdit; + std::vector<std::unique_ptr<ImplTabBarItem>> mpItemList; + + vcl::AccessibleFactoryAccess maAccessibleFactory; + + sal_uInt16 getItemSize() const + { + return static_cast<sal_uInt16>(mpItemList.size()); + } +}; + +TabBar::TabBar( vcl::Window* pParent, WinBits nWinStyle ) : + Window( pParent, (nWinStyle & WB_3DLOOK) | WB_CLIPCHILDREN ) +{ + ImplInit( nWinStyle ); + maCurrentItemList = 0; +} + +TabBar::~TabBar() +{ + disposeOnce(); +} + +void TabBar::dispose() +{ + EndEditMode( true ); + mpImpl.reset(); + Window::dispose(); +} + +const sal_uInt16 TabBar::APPEND = ::std::numeric_limits<sal_uInt16>::max(); +const sal_uInt16 TabBar::PAGE_NOT_FOUND = ::std::numeric_limits<sal_uInt16>::max(); + +void TabBar::ImplInit( WinBits nWinStyle ) +{ + mpImpl.reset(new TabBar_Impl); + + mnMaxPageWidth = 0; + mnCurMaxWidth = 0; + mnOffX = 0; + mnOffY = 0; + mnLastOffX = 0; + mnSplitSize = 0; + mnSwitchTime = 0; + mnWinStyle = nWinStyle; + mnCurPageId = 0; + mnFirstPos = 0; + mnDropPos = 0; + mnSwitchId = 0; + mnEditId = 0; + mbFormat = true; + mbFirstFormat = true; + mbSizeFormat = true; + mbAutoEditMode = false; + mbEditCanceled = false; + mbDropPos = false; + mbInSelect = false; + mbMirrored = false; + mbScrollAlwaysEnabled = false; + + ImplInitControls(); + + if (mpImpl->mpFirstButton) + mpImpl->mpFirstButton->SetAccessibleName(SvtResId(STR_TABBAR_PUSHBUTTON_MOVET0HOME)); + if (mpImpl->mpPrevButton) + mpImpl->mpPrevButton->SetAccessibleName(SvtResId(STR_TABBAR_PUSHBUTTON_MOVELEFT)); + if (mpImpl->mpNextButton) + mpImpl->mpNextButton->SetAccessibleName(SvtResId(STR_TABBAR_PUSHBUTTON_MOVERIGHT)); + if (mpImpl->mpLastButton) + mpImpl->mpLastButton->SetAccessibleName(SvtResId(STR_TABBAR_PUSHBUTTON_MOVETOEND)); + + if (mpImpl->mpAddButton) + mpImpl->mpAddButton->SetAccessibleName(SvtResId(STR_TABBAR_PUSHBUTTON_ADDTAB)); + + SetSizePixel( Size( 100, CalcWindowSizePixel().Height() ) ); + ImplInitSettings( true, true ); +} + +ImplTabBarItem* TabBar::seek( size_t i ) +{ + if ( i < mpImpl->mpItemList.size() ) + { + maCurrentItemList = i; + return mpImpl->mpItemList[maCurrentItemList].get(); + } + return nullptr; +} + +ImplTabBarItem* TabBar::prev() +{ + if ( maCurrentItemList > 0 ) + { + return mpImpl->mpItemList[--maCurrentItemList].get(); + } + return nullptr; +} + +ImplTabBarItem* TabBar::next() +{ + if ( maCurrentItemList + 1 < mpImpl->mpItemList.size() ) + { + return mpImpl->mpItemList[++maCurrentItemList].get(); + } + return nullptr; +} + +void TabBar::ImplInitSettings( bool bFont, bool bBackground ) +{ + // FIXME RenderContext + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + + if (bFont) + { + vcl::Font aToolFont = rStyleSettings.GetToolFont(); + aToolFont.SetWeight( WEIGHT_BOLD ); + ApplyControlFont(*this, aToolFont); + + // Adapt font size if window too small? + while (GetTextHeight() > (GetOutputSizePixel().Height() - 1)) + { + vcl::Font aFont = GetFont(); + if (aFont.GetFontHeight() <= 6) + break; + aFont.SetFontHeight(aFont.GetFontHeight() - 1); + SetFont(aFont); + } + } + + if (bBackground) + { + ApplyControlBackground(*this, rStyleSettings.GetFaceColor()); + } +} + +void TabBar::ImplGetColors(const StyleSettings& rStyleSettings, + Color& rFaceColor, Color& rFaceTextColor, + Color& rSelectColor, Color& rSelectTextColor) +{ + if (IsControlBackground()) + rFaceColor = GetControlBackground(); + else + rFaceColor = rStyleSettings.GetInactiveTabColor(); + if (IsControlForeground()) + rFaceTextColor = GetControlForeground(); + else + rFaceTextColor = rStyleSettings.GetButtonTextColor(); + rSelectColor = rStyleSettings.GetActiveTabColor(); + rSelectTextColor = rStyleSettings.GetWindowTextColor(); +} + +bool TabBar::ImplCalcWidth() +{ + // Sizes should only be retrieved if the text or the font was changed + if (!mbSizeFormat) + return false; + + // retrieve width of tabs with bold font + vcl::Font aFont = GetFont(); + if (aFont.GetWeight() != WEIGHT_BOLD) + { + aFont.SetWeight(WEIGHT_BOLD); + SetFont(aFont); + } + + if (mnMaxPageWidth) + mnCurMaxWidth = mnMaxPageWidth; + else + { + mnCurMaxWidth = mnLastOffX - mnOffX; + if (mnCurMaxWidth < 1) + mnCurMaxWidth = 1; + } + + bool bChanged = false; + for (auto& pItem : mpImpl->mpItemList) + { + long nNewWidth = GetTextWidth(pItem->GetRenderText()); + if (mnCurMaxWidth && (nNewWidth > mnCurMaxWidth)) + { + pItem->mbShort = true; + nNewWidth = mnCurMaxWidth; + } + else + { + pItem->mbShort = false; + } + + // Padding is dependent on font height - bigger font = bigger padding + long nFontWidth = aFont.GetFontHeight(); + if (pItem->mbProtect) + nNewWidth += 24; + nNewWidth += nFontWidth * 2; + + if (pItem->mnWidth != nNewWidth) + { + pItem->mnWidth = nNewWidth; + if (!pItem->maRect.IsEmpty()) + bChanged = true; + } + } + mbSizeFormat = false; + mbFormat = true; + return bChanged; +} + +void TabBar::ImplFormat() +{ + ImplCalcWidth(); + + if (!mbFormat) + return; + + sal_uInt16 nItemIndex = 0; + long x = mnOffX; + for (auto & pItem : mpImpl->mpItemList) + { + // At all non-visible tabs an empty rectangle is set + if ((nItemIndex + 1 < mnFirstPos) || (x > mnLastOffX)) + pItem->maRect.SetEmpty(); + else + { + // Slightly before the tab before the first visible page + // should also be visible + if (nItemIndex + 1 == mnFirstPos) + { + pItem->maRect.SetLeft(x - pItem->mnWidth); + } + else + { + pItem->maRect.SetLeft(x); + x += pItem->mnWidth; + } + pItem->maRect.SetRight(x); + pItem->maRect.SetBottom(maWinSize.Height() - 1); + + if (mbMirrored) + { + long nNewLeft = mnOffX + mnLastOffX - pItem->maRect.Right(); + long nNewRight = mnOffX + mnLastOffX - pItem->maRect.Left(); + pItem->maRect.SetRight(nNewRight); + pItem->maRect.SetLeft(nNewLeft); + } + } + + nItemIndex++; + } + + mbFormat = false; + + // enable/disable button + ImplEnableControls(); +} + +sal_uInt16 TabBar::ImplGetLastFirstPos() +{ + sal_uInt16 nCount = mpImpl->getItemSize(); + if (!nCount || mbSizeFormat || mbFormat) + return 0; + + sal_uInt16 nLastFirstPos = nCount - 1; + long nWinWidth = mnLastOffX - mnOffX - ADDNEWPAGE_AREAWIDTH; + long nWidth = mpImpl->mpItemList[nLastFirstPos]->mnWidth; + + while (nLastFirstPos && (nWidth < nWinWidth)) + { + nLastFirstPos--; + nWidth += mpImpl->mpItemList[nLastFirstPos]->mnWidth; + } + if ((nLastFirstPos != static_cast<sal_uInt16>(mpImpl->mpItemList.size() - 1)) && (nWidth > nWinWidth)) + nLastFirstPos++; + return nLastFirstPos; +} + +void TabBar::ImplInitControls() +{ + if (mnWinStyle & WB_SIZEABLE) + { + if (!mpImpl->mpSizer) + { + mpImpl->mpSizer.disposeAndReset(VclPtr<ImplTabSizer>::Create( this, mnWinStyle & (WB_DRAG | WB_3DLOOK))); + } + mpImpl->mpSizer->Show(); + } + else + { + mpImpl->mpSizer.disposeAndClear(); + } + + if ((mnWinStyle & WB_INSERTTAB) && !mpImpl->mpAddButton) + { + Link<Button*,void> aLink = LINK(this, TabBar, ImplAddClickHandler); + mpImpl->mpAddButton.disposeAndReset(VclPtr<ImplTabButton>::Create(this, WB_REPEAT)); + mpImpl->mpAddButton->SetClickHdl(aLink); + mpImpl->mpAddButton->SetSymbol(SymbolType::PLUS); + mpImpl->mpAddButton->Show(); + } + + + Link<Button*,void> aLink = LINK( this, TabBar, ImplClickHdl ); + + if (mnWinStyle & (WB_MINSCROLL | WB_SCROLL)) + { + if (!mpImpl->mpPrevButton) + { + mpImpl->mpPrevButton.disposeAndReset(VclPtr<ImplTabButton>::Create(this, WB_REPEAT)); + mpImpl->mpPrevButton->SetClickHdl(aLink); + } + mpImpl->mpPrevButton->SetSymbol(mbMirrored ? SymbolType::NEXT : SymbolType::PREV); + mpImpl->mpPrevButton->Show(); + + if (!mpImpl->mpNextButton) + { + mpImpl->mpNextButton.disposeAndReset(VclPtr<ImplTabButton>::Create(this, WB_REPEAT)); + mpImpl->mpNextButton->SetClickHdl(aLink); + } + mpImpl->mpNextButton->SetSymbol(mbMirrored ? SymbolType::PREV : SymbolType::NEXT); + mpImpl->mpNextButton->Show(); + } + else + { + mpImpl->mpPrevButton.disposeAndClear(); + mpImpl->mpNextButton.disposeAndClear(); + } + + if (mnWinStyle & WB_SCROLL) + { + if (!mpImpl->mpFirstButton) + { + mpImpl->mpFirstButton.disposeAndReset(VclPtr<ImplTabButton>::Create(this)); + mpImpl->mpFirstButton->SetClickHdl(aLink); + } + mpImpl->mpFirstButton->SetSymbol(mbMirrored ? SymbolType::LAST : SymbolType::FIRST); + mpImpl->mpFirstButton->Show(); + + if (!mpImpl->mpLastButton) + { + mpImpl->mpLastButton.disposeAndReset(VclPtr<ImplTabButton>::Create(this)); + mpImpl->mpLastButton->SetClickHdl(aLink); + } + mpImpl->mpLastButton->SetSymbol(mbMirrored ? SymbolType::FIRST : SymbolType::LAST); + mpImpl->mpLastButton->Show(); + } + else + { + mpImpl->mpFirstButton.disposeAndClear(); + mpImpl->mpLastButton.disposeAndClear(); + } +} + +void TabBar::ImplEnableControls() +{ + if (mbSizeFormat || mbFormat) + return; + + // enable/disable buttons + bool bEnableBtn = mbScrollAlwaysEnabled || mnFirstPos > 0; + if (mpImpl->mpFirstButton) + mpImpl->mpFirstButton->Enable(bEnableBtn); + if (mpImpl->mpPrevButton) + mpImpl->mpPrevButton->Enable(bEnableBtn); + + bEnableBtn = mbScrollAlwaysEnabled || mnFirstPos < ImplGetLastFirstPos(); + if (mpImpl->mpNextButton) + mpImpl->mpNextButton->Enable(bEnableBtn); + if (mpImpl->mpLastButton) + mpImpl->mpLastButton->Enable(bEnableBtn); +} + +void TabBar::SetScrollAlwaysEnabled(bool bScrollAlwaysEnabled) +{ + mbScrollAlwaysEnabled = bScrollAlwaysEnabled; + ImplEnableControls(); +} + +void TabBar::ImplShowPage( sal_uInt16 nPos ) +{ + if (nPos >= mpImpl->getItemSize()) + return; + + // calculate width + long nWidth = GetOutputSizePixel().Width(); + + auto& pItem = mpImpl->mpItemList[nPos]; + if (nPos < mnFirstPos) + SetFirstPageId( pItem->mnId ); + else if (pItem->maRect.Right() > nWidth) + { + while (pItem->maRect.Right() > nWidth) + { + sal_uInt16 nNewPos = mnFirstPos + 1; + SetFirstPageId(GetPageId(nNewPos)); + ImplFormat(); + if (nNewPos != mnFirstPos) + break; + } + } +} + +IMPL_LINK( TabBar, ImplClickHdl, Button*, pButton, void ) +{ + ImplTabButton* pBtn = static_cast<ImplTabButton*>(pButton); + EndEditMode(); + + sal_uInt16 nNewPos = mnFirstPos; + + if (pBtn == mpImpl->mpFirstButton.get() || (pBtn == mpImpl->mpPrevButton.get() && pBtn->isModKeyPressed())) + { + nNewPos = 0; + } + else if (pBtn == mpImpl->mpLastButton.get() || (pBtn == mpImpl->mpNextButton.get() && pBtn->isModKeyPressed())) + { + sal_uInt16 nCount = GetPageCount(); + if (nCount) + nNewPos = nCount - 1; + } + else if (pBtn == mpImpl->mpPrevButton.get()) + { + if (mnFirstPos) + nNewPos = mnFirstPos - 1; + } + else if (pBtn == mpImpl->mpNextButton.get()) + { + sal_uInt16 nCount = GetPageCount(); + if (mnFirstPos < nCount) + nNewPos = mnFirstPos+1; + } + else + { + return; + } + + if (nNewPos != mnFirstPos) + SetFirstPageId(GetPageId(nNewPos)); +} + +IMPL_LINK_NOARG(TabBar, ImplAddClickHandler, Button*, void) +{ + AddTabClick(); +} + +void TabBar::MouseMove(const MouseEvent& rMEvt) +{ + if (rMEvt.IsLeaveWindow()) + mbInSelect = false; + + Window::MouseMove(rMEvt); +} + +void TabBar::MouseButtonDown(const MouseEvent& rMEvt) +{ + // Only terminate EditMode and do not execute click + // if clicked inside our window, + if (IsInEditMode()) + { + EndEditMode(); + return; + } + + sal_uInt16 nSelId = GetPageId(rMEvt.GetPosPixel()); + + if (!rMEvt.IsLeft()) + { + Window::MouseButtonDown(rMEvt); + if (nSelId > 0 && nSelId != mnCurPageId) + { + if (ImplDeactivatePage()) + { + SetCurPageId(nSelId); + PaintImmediately(); + ImplActivatePage(); + ImplSelect(); + } + mbInSelect = true; + } + return; + } + + if (rMEvt.IsMod2() && mbAutoEditMode && nSelId) + { + if (StartEditMode(nSelId)) + return; + } + + if ((rMEvt.GetMode() & (MouseEventModifiers::MULTISELECT | MouseEventModifiers::RANGESELECT)) && (rMEvt.GetClicks() == 1)) + { + if (nSelId) + { + sal_uInt16 nPos = GetPagePos(nSelId); + + bool bSelectTab = false; + + if ((rMEvt.GetMode() & MouseEventModifiers::MULTISELECT) && (mnWinStyle & WB_MULTISELECT)) + { + if (nSelId != mnCurPageId) + { + SelectPage(nSelId, !IsPageSelected(nSelId)); + bSelectTab = true; + } + } + else if (mnWinStyle & (WB_MULTISELECT | WB_RANGESELECT)) + { + bSelectTab = true; + sal_uInt16 n; + bool bSelect; + sal_uInt16 nCurPos = GetPagePos(mnCurPageId); + if (nPos <= nCurPos) + { + // Deselect all tabs till the clicked tab + // and select all tabs from the clicked tab + // 'till the actual position + n = 0; + while (n < nCurPos) + { + auto& pItem = mpImpl->mpItemList[n]; + bSelect = n >= nPos; + + if (pItem->mbSelect != bSelect) + { + pItem->mbSelect = bSelect; + if (!pItem->maRect.IsEmpty()) + Invalidate(pItem->maRect); + } + + n++; + } + } + + if (nPos >= nCurPos) + { + // Select all tabs from the actual position till the clicked tab + // and deselect all tabs from the actual position + // till the last tab + sal_uInt16 nCount = mpImpl->getItemSize(); + n = nCurPos; + while (n < nCount) + { + auto& pItem = mpImpl->mpItemList[n]; + + bSelect = n <= nPos; + + if (pItem->mbSelect != bSelect) + { + pItem->mbSelect = bSelect; + if (!pItem->maRect.IsEmpty()) + Invalidate(pItem->maRect); + } + + n++; + } + } + } + + // scroll the selected tab if required + if (bSelectTab) + { + ImplShowPage(nPos); + PaintImmediately(); + ImplSelect(); + } + + mbInSelect = true; + + return; + } + } + else if (rMEvt.GetClicks() == 2) + { + // call double-click-handler if required + if (!rMEvt.GetModifier() && (!nSelId || (nSelId == mnCurPageId))) + { + sal_uInt16 nOldCurId = mnCurPageId; + mnCurPageId = nSelId; + DoubleClick(); + // check, as actual page could be switched inside + // the doubleclick-handler + if (mnCurPageId == nSelId) + mnCurPageId = nOldCurId; + } + + return; + } + else + { + if (nSelId) + { + // execute Select if not actual page + if (nSelId != mnCurPageId) + { + sal_uInt16 nPos = GetPagePos(nSelId); + auto& pItem = mpImpl->mpItemList[nPos]; + + if (!pItem->mbSelect) + { + // make not valid + bool bUpdate = false; + if (IsReallyVisible() && IsUpdateMode()) + bUpdate = true; + + // deselect all selected items + for (auto& xItem : mpImpl->mpItemList) + { + if (xItem->mbSelect || (xItem->mnId == mnCurPageId)) + { + xItem->mbSelect = false; + if (bUpdate) + Invalidate(xItem->maRect); + } + } + } + + if (ImplDeactivatePage()) + { + SetCurPageId(nSelId); + PaintImmediately(); + ImplActivatePage(); + ImplSelect(); + } + + mbInSelect = true; + } + + return; + } + } + + Window::MouseButtonDown(rMEvt); +} + +void TabBar::MouseButtonUp(const MouseEvent& rMEvt) +{ + mbInSelect = false; + Window::MouseButtonUp(rMEvt); +} + +void TabBar::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rect) +{ + if (rRenderContext.IsNativeControlSupported(ControlType::WindowBackground,ControlPart::Entire)) + { + rRenderContext.DrawNativeControl(ControlType::WindowBackground,ControlPart::Entire,rect, + ControlState::ENABLED,ImplControlValue(0),OUString()); + } + // calculate items and emit + sal_uInt16 nItemCount = mpImpl->getItemSize(); + if (!nItemCount) + return; + + ImplPrePaint(); + + Color aFaceColor, aSelectColor, aFaceTextColor, aSelectTextColor; + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + ImplGetColors(rStyleSettings, aFaceColor, aFaceTextColor, aSelectColor, aSelectTextColor); + + rRenderContext.Push(PushFlags::FONT | PushFlags::CLIPREGION); + rRenderContext.SetClipRegion(vcl::Region(GetPageArea())); + + // select font + vcl::Font aFont = rRenderContext.GetFont(); + vcl::Font aLightFont = aFont; + aLightFont.SetWeight(WEIGHT_NORMAL); + + TabDrawer aDrawer(rRenderContext); + + aDrawer.setSelectedFillColor(aSelectColor); + + // Now, start drawing the tabs. + + ImplTabBarItem* pItem = ImplGetLastTabBarItem(nItemCount); + ImplTabBarItem* pCurItem = nullptr; + while (pItem) + { + // emit CurrentItem last, as it covers all others + if (!pCurItem && (pItem->mnId == mnCurPageId)) + { + pCurItem = pItem; + pItem = prev(); + if (!pItem) + pItem = pCurItem; + continue; + } + + bool bCurrent = pItem == pCurItem; + + if (!pItem->maRect.IsEmpty()) + { + tools::Rectangle aRect = pItem->maRect; + bool bSelected = pItem->IsSelected(pCurItem); + // We disable custom background color in high contrast mode. + bool bCustomBgColor = !pItem->IsDefaultTabBgColor() && !rStyleSettings.GetHighContrastMode(); + OUString aText = pItem->mbShort ? + rRenderContext.GetEllipsisString(pItem->GetRenderText(), mnCurMaxWidth) : + pItem->GetRenderText(); + + aDrawer.setRect(aRect); + aDrawer.setSelected(bSelected); + aDrawer.setCustomColored(bCustomBgColor); + aDrawer.setEnabled(true); + aDrawer.setCustomColor(pItem->maTabBgColor); + aDrawer.mbProtect = pItem->mbProtect; + aDrawer.drawTab(); + + // actual page is drawn using a bold font + rRenderContext.SetFont(aLightFont); + + // Set the correct FillInBrush depending on status + + if (bSelected) + rRenderContext.SetTextColor(aSelectTextColor); + else if (bCustomBgColor) + rRenderContext.SetTextColor(pItem->maTabTextColor); + else + rRenderContext.SetTextColor(aFaceTextColor); + + // Special display of tab name depending on page bits + + if (pItem->mnBits & TabBarPageBits::Blue) + { + rRenderContext.SetTextColor(COL_LIGHTBLUE); + } + if (pItem->mnBits & TabBarPageBits::Italic) + { + vcl::Font aSpecialFont = rRenderContext.GetFont(); + aSpecialFont.SetItalic(FontItalic::ITALIC_NORMAL); + rRenderContext.SetFont(aSpecialFont); + } + if (pItem->mnBits & TabBarPageBits::Underline) + { + vcl::Font aSpecialFont = rRenderContext.GetFont(); + aSpecialFont.SetUnderline(LINESTYLE_SINGLE); + rRenderContext.SetFont(aSpecialFont); + } + + aDrawer.drawText(aText); + + if (bCurrent) + { + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(aSelectColor); + aDrawer.drawOverTopBorder(); + break; + } + + pItem = prev(); + } + else + { + if (bCurrent) + break; + + pItem = nullptr; + } + + if (!pItem) + pItem = pCurItem; + } + rRenderContext.Pop(); +} + +void TabBar::Resize() +{ + Size aNewSize = GetOutputSizePixel(); + + long nSizerWidth = 0; + long nButtonWidth = 0; + + // order the Sizer + if ( mpImpl->mpSizer ) + { + Size aSizerSize = mpImpl->mpSizer->GetSizePixel(); + Point aNewSizerPos( mbMirrored ? 0 : (aNewSize.Width()-aSizerSize.Width()), 0 ); + Size aNewSizerSize( aSizerSize.Width(), aNewSize.Height() ); + mpImpl->mpSizer->SetPosSizePixel( aNewSizerPos, aNewSizerSize ); + nSizerWidth = aSizerSize.Width(); + } + + // order the scroll buttons + long const nHeight = aNewSize.Height(); + // adapt font height? + ImplInitSettings( true, false ); + + long nButtonMargin = BUTTON_MARGIN * GetDPIScaleFactor(); + + long nX = mbMirrored ? (aNewSize.Width() - nHeight - nButtonMargin) : nButtonMargin; + long const nXDiff = mbMirrored ? -nHeight : nHeight; + + nButtonWidth += nButtonMargin; + + Size const aBtnSize( nHeight, nHeight ); + auto setButton = [aBtnSize, nXDiff, nHeight, &nX, &nButtonWidth]( + ScopedVclPtr<ImplTabButton> const & button) + { + if (button) { + button->SetPosSizePixel(Point(nX, 0), aBtnSize); + nX += nXDiff; + nButtonWidth += nHeight; + } + }; + + setButton(mpImpl->mpFirstButton); + setButton(mpImpl->mpPrevButton); + setButton(mpImpl->mpNextButton); + setButton(mpImpl->mpLastButton); + + nButtonWidth += nButtonMargin; + nX += mbMirrored ? -nButtonMargin : nButtonMargin; + + setButton(mpImpl->mpAddButton); + + nButtonWidth += nButtonMargin; + + // store size + maWinSize = aNewSize; + + if( mbMirrored ) + { + mnOffX = nSizerWidth; + mnLastOffX = maWinSize.Width() - nButtonWidth - 1; + } + else + { + mnOffX = nButtonWidth; + mnLastOffX = maWinSize.Width() - nSizerWidth - 1; + } + + // reformat + mbSizeFormat = true; + if ( IsReallyVisible() ) + { + if ( ImplCalcWidth() ) + Invalidate(); + + ImplFormat(); + + // Ensure as many tabs as possible are visible: + sal_uInt16 nLastFirstPos = ImplGetLastFirstPos(); + if ( mnFirstPos > nLastFirstPos ) + { + mnFirstPos = nLastFirstPos; + mbFormat = true; + Invalidate(); + } + // Ensure the currently selected page is visible + ImplShowPage(GetPagePos(mnCurPageId)); + + ImplFormat(); + } + + // enable/disable button + ImplEnableControls(); +} + +bool TabBar::PreNotify(NotifyEvent& rNEvt) +{ + if (rNEvt.GetType() == MouseNotifyEvent::COMMAND) + { + if (rNEvt.GetCommandEvent()->GetCommand() == CommandEventId::Wheel) + { + const CommandWheelData* pData = rNEvt.GetCommandEvent()->GetWheelData(); + sal_uInt16 nNewPos = mnFirstPos; + if (pData->GetNotchDelta() > 0) + { + if (mnFirstPos) + nNewPos = mnFirstPos - 1; + } + else if (pData->GetNotchDelta() < 0) + { + sal_uInt16 nCount = GetPageCount(); + if (mnFirstPos < nCount) + nNewPos = mnFirstPos + 1; + } + if (nNewPos != mnFirstPos) + SetFirstPageId(GetPageId(nNewPos)); + } + } + return Window::PreNotify(rNEvt); +} + +void TabBar::RequestHelp(const HelpEvent& rHEvt) +{ + sal_uInt16 nItemId = GetPageId(ScreenToOutputPixel(rHEvt.GetMousePosPixel())); + if (nItemId) + { + if (rHEvt.GetMode() & HelpEventMode::BALLOON) + { + OUString aStr = GetHelpText(nItemId); + if (!aStr.isEmpty()) + { + tools::Rectangle aItemRect = GetPageRect(nItemId); + Point aPt = OutputToScreenPixel(aItemRect.TopLeft()); + aItemRect.SetLeft( aPt.X() ); + aItemRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel(aItemRect.BottomRight()); + aItemRect.SetRight( aPt.X() ); + aItemRect.SetBottom( aPt.Y() ); + Help::ShowBalloon(this, aItemRect.Center(), aItemRect, aStr); + return; + } + } + + // show text for quick- or balloon-help + // if this is isolated or not fully visible + if (rHEvt.GetMode() & (HelpEventMode::QUICK | HelpEventMode::BALLOON)) + { + sal_uInt16 nPos = GetPagePos(nItemId); + auto& pItem = mpImpl->mpItemList[nPos]; + if (pItem->mbShort || (pItem->maRect.Right() - 5 > mnLastOffX)) + { + tools::Rectangle aItemRect = GetPageRect(nItemId); + Point aPt = OutputToScreenPixel(aItemRect.TopLeft()); + aItemRect.SetLeft( aPt.X() ); + aItemRect.SetTop( aPt.Y() ); + aPt = OutputToScreenPixel(aItemRect.BottomRight()); + aItemRect.SetRight( aPt.X() ); + aItemRect.SetBottom( aPt.Y() ); + OUString aStr = mpImpl->mpItemList[nPos]->maText; + if (!aStr.isEmpty()) + { + if (rHEvt.GetMode() & HelpEventMode::BALLOON) + Help::ShowBalloon(this, aItemRect.Center(), aItemRect, aStr); + else + Help::ShowQuickHelp(this, aItemRect, aStr); + return; + } + } + } + } + + Window::RequestHelp(rHEvt); +} + +void TabBar::StateChanged(StateChangedType nType) +{ + Window::StateChanged(nType); + + if (nType == StateChangedType::InitShow) + { + if ( (mbSizeFormat || mbFormat) && !mpImpl->mpItemList.empty() ) + ImplFormat(); + } + else if (nType == StateChangedType::Zoom || + nType == StateChangedType::ControlFont) + { + ImplInitSettings(true, false); + Invalidate(); + } + else if (nType == StateChangedType::ControlForeground) + Invalidate(); + else if (nType == StateChangedType::ControlBackground) + { + ImplInitSettings(false, true); + Invalidate(); + } + else if (nType == StateChangedType::Mirroring) + { + // reacts on calls of EnableRTL, have to mirror all child controls + if (mpImpl->mpFirstButton) + mpImpl->mpFirstButton->EnableRTL(IsRTLEnabled()); + if (mpImpl->mpPrevButton) + mpImpl->mpPrevButton->EnableRTL(IsRTLEnabled()); + if (mpImpl->mpNextButton) + mpImpl->mpNextButton->EnableRTL(IsRTLEnabled()); + if (mpImpl->mpLastButton) + mpImpl->mpLastButton->EnableRTL(IsRTLEnabled()); + if (mpImpl->mpSizer) + mpImpl->mpSizer->EnableRTL(IsRTLEnabled()); + if (mpImpl->mpAddButton) + mpImpl->mpAddButton->EnableRTL(IsRTLEnabled()); + if (mpImpl->mpEdit) + mpImpl->mpEdit->EnableRTL(IsRTLEnabled()); + } +} + +void TabBar::DataChanged(const DataChangedEvent& rDCEvt) +{ + Window::DataChanged(rDCEvt); + + if (rDCEvt.GetType() == DataChangedEventType::FONTS + || rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION + || (rDCEvt.GetType() == DataChangedEventType::SETTINGS + && rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) + { + ImplInitSettings(true, true); + Invalidate(); + } +} + +void TabBar::ImplSelect() +{ + Select(); + CallEventListeners(VclEventId::TabbarPageSelected, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(mnCurPageId))); +} + +void TabBar::Select() +{ + maSelectHdl.Call(this); +} + +void TabBar::DoubleClick() +{ +} + +void TabBar::Split() +{ + maSplitHdl.Call(this); +} + +void TabBar::ImplActivatePage() +{ + ActivatePage(); + + CallEventListeners(VclEventId::TabbarPageActivated, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(mnCurPageId))); +} + +void TabBar::ActivatePage() +{} + +bool TabBar::ImplDeactivatePage() +{ + bool bRet = DeactivatePage(); + + CallEventListeners(VclEventId::TabbarPageDeactivated, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(mnCurPageId))); + + return bRet; +} + +void TabBar::ImplPrePaint() +{ + sal_uInt16 nItemCount = mpImpl->getItemSize(); + if (!nItemCount) + return; + + // tabbar should be formatted + ImplFormat(); + + // assure the actual tabpage becomes visible at first format + if (!mbFirstFormat) + return; + + mbFirstFormat = false; + + if (!(mnCurPageId && (mnFirstPos == 0) && !mbDropPos)) + return; + + auto& pItem = mpImpl->mpItemList[GetPagePos(mnCurPageId)]; + if (pItem->maRect.IsEmpty()) + { + // set mbDropPos (or misuse) to prevent Invalidate() + mbDropPos = true; + SetFirstPageId(mnCurPageId); + mbDropPos = false; + if (mnFirstPos != 0) + ImplFormat(); + } +} + +ImplTabBarItem* TabBar::ImplGetLastTabBarItem( sal_uInt16 nItemCount ) +{ + // find last visible entry + sal_uInt16 n = mnFirstPos + 1; + if (n >= nItemCount) + n = nItemCount-1; + ImplTabBarItem* pItem = seek(n); + while (pItem) + { + if (!pItem->maRect.IsEmpty()) + { + n++; + pItem = next(); + } + else + break; + } + + // draw all tabs (from back to front and actual last) + if (pItem) + n--; + else if (n >= nItemCount) + n = nItemCount-1; + pItem = seek(n); + return pItem; +} + +bool TabBar::DeactivatePage() +{ + return true; +} + +bool TabBar::StartRenaming() +{ + return true; +} + +TabBarAllowRenamingReturnCode TabBar::AllowRenaming() +{ + return TABBAR_RENAMING_YES; +} + +void TabBar::EndRenaming() +{ +} + +void TabBar::Mirror() +{ + +} + +void TabBar::AddTabClick() +{ + +} + +void TabBar::InsertPage(sal_uInt16 nPageId, const OUString& rText, + TabBarPageBits nBits, sal_uInt16 nPos) +{ + assert (nPageId && "TabBar::InsertPage(): PageId must not be 0"); + assert ((GetPagePos(nPageId) == PAGE_NOT_FOUND) && "TabBar::InsertPage(): Page already exists"); + assert ((nBits <= TPB_DISPLAY_NAME_ALLFLAGS) && "TabBar::InsertPage(): Invalid flag set in nBits"); + + // create PageItem and insert in the item list + std::unique_ptr<ImplTabBarItem> pItem(new ImplTabBarItem( nPageId, rText, nBits )); + if (nPos < mpImpl->mpItemList.size()) + { + auto it = mpImpl->mpItemList.begin(); + it += nPos; + mpImpl->mpItemList.insert(it, std::move(pItem)); + } + else + { + mpImpl->mpItemList.push_back(std::move(pItem)); + } + mbSizeFormat = true; + + // set CurPageId if required + if (!mnCurPageId) + mnCurPageId = nPageId; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners(VclEventId::TabbarPageInserted, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(nPageId))); +} + +Color TabBar::GetTabBgColor(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos != PAGE_NOT_FOUND) + return mpImpl->mpItemList[nPos]->maTabBgColor; + else + return COL_AUTO; +} + +void TabBar::SetTabBgColor(sal_uInt16 nPageId, const Color& aTabBgColor) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos == PAGE_NOT_FOUND) + return; + + auto& pItem = mpImpl->mpItemList[nPos]; + if (aTabBgColor != COL_AUTO) + { + pItem->maTabBgColor = aTabBgColor; + if (aTabBgColor.GetLuminance() <= 128) //Do not use aTabBgColor.IsDark(), because that threshold is way too low... + pItem->maTabTextColor = COL_WHITE; + else + pItem->maTabTextColor = COL_BLACK; + } + else + { + pItem->maTabBgColor = COL_AUTO; + pItem->maTabTextColor = COL_AUTO; + } +} + +void TabBar::RemovePage(sal_uInt16 nPageId) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + // does item exist + if (nPos == PAGE_NOT_FOUND) + return; + + if (mnCurPageId == nPageId) + mnCurPageId = 0; + + // check if first visible page should be moved + if (mnFirstPos > nPos) + mnFirstPos--; + + // delete item data + auto it = mpImpl->mpItemList.begin(); + it += nPos; + mpImpl->mpItemList.erase(it); + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners(VclEventId::TabbarPageRemoved, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(nPageId))); +} + +void TabBar::MovePage(sal_uInt16 nPageId, sal_uInt16 nNewPos) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + Pair aPair(nPos, nNewPos); + + if (nPos < nNewPos) + nNewPos--; + + if (nPos == nNewPos) + return; + + // does item exit + if (nPos == PAGE_NOT_FOUND) + return; + + // move tabbar item in the list + auto it = mpImpl->mpItemList.begin(); + it += nPos; + std::unique_ptr<ImplTabBarItem> pItem = std::move(*it); + mpImpl->mpItemList.erase(it); + if (nNewPos < mpImpl->mpItemList.size()) + { + it = mpImpl->mpItemList.begin(); + it += nNewPos; + mpImpl->mpItemList.insert(it, std::move(pItem)); + } + else + { + mpImpl->mpItemList.push_back(std::move(pItem)); + } + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners( VclEventId::TabbarPageMoved, static_cast<void*>(&aPair) ); +} + +void TabBar::Clear() +{ + // delete all items + mpImpl->mpItemList.clear(); + + // remove items from the list + mbSizeFormat = true; + mnCurPageId = 0; + mnFirstPos = 0; + maCurrentItemList = 0; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners(VclEventId::TabbarPageRemoved, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(PAGE_NOT_FOUND))); +} + +bool TabBar::IsPageEnabled(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + return nPos != PAGE_NOT_FOUND; +} + +void TabBar::SetPageBits(sal_uInt16 nPageId, TabBarPageBits nBits) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos == PAGE_NOT_FOUND) + return; + + auto& pItem = mpImpl->mpItemList[nPos]; + + if (pItem->mnBits != nBits) + { + pItem->mnBits = nBits; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(pItem->maRect); + } +} + +TabBarPageBits TabBar::GetPageBits(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos != PAGE_NOT_FOUND) + return mpImpl->mpItemList[nPos]->mnBits; + else + return TabBarPageBits::NONE; +} + +sal_uInt16 TabBar::GetPageCount() const +{ + return mpImpl->getItemSize(); +} + +sal_uInt16 TabBar::GetPageId(sal_uInt16 nPos) const +{ + return nPos < mpImpl->mpItemList.size() ? mpImpl->mpItemList[nPos]->mnId : 0; +} + +sal_uInt16 TabBar::GetPagePos(sal_uInt16 nPageId) const +{ + for (size_t i = 0; i < mpImpl->mpItemList.size(); ++i) + { + if (mpImpl->mpItemList[i]->mnId == nPageId) + { + return static_cast<sal_uInt16>(i); + } + } + return PAGE_NOT_FOUND; +} + +sal_uInt16 TabBar::GetPageId(const Point& rPos) const +{ + for (const auto& pItem : mpImpl->mpItemList) + { + if (pItem->maRect.IsInside(rPos)) + return pItem->mnId; + } + + return 0; +} + +tools::Rectangle TabBar::GetPageRect(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos != PAGE_NOT_FOUND) + return mpImpl->mpItemList[nPos]->maRect; + else + return tools::Rectangle(); +} + +void TabBar::SetCurPageId(sal_uInt16 nPageId) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + // do nothing if item does not exit + if (nPos == PAGE_NOT_FOUND) + return; + + // do nothing if the actual page did not change + if (nPageId == mnCurPageId) + return; + + // make invalid + bool bUpdate = false; + if (IsReallyVisible() && IsUpdateMode()) + bUpdate = true; + + auto& pItem = mpImpl->mpItemList[nPos]; + ImplTabBarItem* pOldItem; + + if (mnCurPageId) + pOldItem = mpImpl->mpItemList[GetPagePos(mnCurPageId)].get(); + else + pOldItem = nullptr; + + // deselect previous page if page was not selected, if this is the + // only selected page + if (!pItem->mbSelect && pOldItem) + { + sal_uInt16 nSelPageCount = GetSelectPageCount(); + if (nSelPageCount == 1) + pOldItem->mbSelect = false; + pItem->mbSelect = true; + } + + mnCurPageId = nPageId; + mbFormat = true; + + // assure the actual page becomes visible + if (IsReallyVisible()) + { + if (nPos < mnFirstPos) + SetFirstPageId(nPageId); + else + { + // calculate visible width + long nWidth = mnLastOffX; + if (nWidth > ADDNEWPAGE_AREAWIDTH) + nWidth -= ADDNEWPAGE_AREAWIDTH; + + if (pItem->maRect.IsEmpty()) + ImplFormat(); + + while ((mbMirrored ? (pItem->maRect.Left() < mnOffX) : (pItem->maRect.Right() > nWidth)) || + pItem->maRect.IsEmpty()) + { + sal_uInt16 nNewPos = mnFirstPos + 1; + // assure at least the actual tabpages are visible as first tabpage + if (nNewPos >= nPos) + { + SetFirstPageId(nPageId); + break; + } + else + SetFirstPageId(GetPageId(nNewPos)); + ImplFormat(); + // abort if first page is not forwarded + if (nNewPos != mnFirstPos) + break; + } + } + } + + // redraw bar + if (bUpdate) + { + Invalidate(pItem->maRect); + if (pOldItem) + Invalidate(pOldItem->maRect); + } +} + +void TabBar::MakeVisible(sal_uInt16 nPageId) +{ + if (!IsReallyVisible()) + return; + + sal_uInt16 nPos = GetPagePos(nPageId); + + // do nothing if item does not exist + if (nPos == PAGE_NOT_FOUND) + return; + + if (nPos < mnFirstPos) + SetFirstPageId(nPageId); + else + { + auto& pItem = mpImpl->mpItemList[nPos]; + + // calculate visible area + long nWidth = mnLastOffX; + + if (mbFormat || pItem->maRect.IsEmpty()) + { + mbFormat = true; + ImplFormat(); + } + + while ((pItem->maRect.Right() > nWidth) || + pItem->maRect.IsEmpty()) + { + sal_uInt16 nNewPos = mnFirstPos+1; + // assure at least the actual tabpages are visible as first tabpage + if (nNewPos >= nPos) + { + SetFirstPageId(nPageId); + break; + } + else + SetFirstPageId(GetPageId(nNewPos)); + ImplFormat(); + // abort if first page is not forwarded + if (nNewPos != mnFirstPos) + break; + } + } +} + +void TabBar::SetFirstPageId(sal_uInt16 nPageId) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + // return false if item does not exist + if (nPos == PAGE_NOT_FOUND) + return; + + if (nPos == mnFirstPos) + return; + + // assure as much pages are visible as possible + ImplFormat(); + sal_uInt16 nLastFirstPos = ImplGetLastFirstPos(); + sal_uInt16 nNewPos; + if (nPos > nLastFirstPos) + nNewPos = nLastFirstPos; + else + nNewPos = nPos; + + if (nNewPos != mnFirstPos) + { + mnFirstPos = nNewPos; + mbFormat = true; + + // redraw bar (attention: check mbDropPos, + // as if this flag was set, we do not re-paint immediately + if (IsReallyVisible() && IsUpdateMode() && !mbDropPos) + Invalidate(); + } +} + +void TabBar::SelectPage(sal_uInt16 nPageId, bool bSelect) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + + if (nPos == PAGE_NOT_FOUND) + return; + + auto& pItem = mpImpl->mpItemList[nPos]; + + if (pItem->mbSelect != bSelect) + { + pItem->mbSelect = bSelect; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(pItem->maRect); + } +} + +sal_uInt16 TabBar::GetSelectPageCount() const +{ + sal_uInt16 nSelected = 0; + for (const auto& pItem : mpImpl->mpItemList) + { + if (pItem->mbSelect) + nSelected++; + } + + return nSelected; +} + +bool TabBar::IsPageSelected(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + return mpImpl->mpItemList[nPos]->mbSelect; + else + return false; +} + +void TabBar::SetProtectionSymbol(sal_uInt16 nPageId, bool bProtection) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + { + if (mpImpl->mpItemList[nPos]->mbProtect != bProtection) + { + mpImpl->mpItemList[nPos]->mbProtect = bProtection; + mbSizeFormat = true; // render text width changes, thus bar width + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + } + } +} + +bool TabBar::StartEditMode(sal_uInt16 nPageId) +{ + sal_uInt16 nPos = GetPagePos( nPageId ); + if (mpImpl->mpEdit || (nPos == PAGE_NOT_FOUND) || (mnLastOffX < 8)) + return false; + + mnEditId = nPageId; + if (StartRenaming()) + { + ImplShowPage(nPos); + ImplFormat(); + PaintImmediately(); + + mpImpl->mpEdit.disposeAndReset(VclPtr<TabBarEdit>::Create(this, WB_CENTER)); + tools::Rectangle aRect = GetPageRect( mnEditId ); + long nX = aRect.Left(); + long nWidth = aRect.GetWidth(); + if (mnEditId != GetCurPageId()) + nX += 1; + if (nX + nWidth > mnLastOffX) + nWidth = mnLastOffX-nX; + if (nWidth < 3) + { + nX = aRect.Left(); + nWidth = aRect.GetWidth(); + } + mpImpl->mpEdit->SetText(GetPageText(mnEditId)); + mpImpl->mpEdit->setPosSizePixel(nX, aRect.Top() + mnOffY + 1, nWidth, aRect.GetHeight() - 3); + vcl::Font aFont = GetPointFont(*this); // FIXME RenderContext + + Color aForegroundColor; + Color aBackgroundColor; + Color aFaceColor; + Color aSelectColor; + Color aFaceTextColor; + Color aSelectTextColor; + + ImplGetColors(Application::GetSettings().GetStyleSettings(), aFaceColor, aFaceTextColor, aSelectColor, aSelectTextColor); + + if (mnEditId != GetCurPageId()) + { + aFont.SetWeight(WEIGHT_LIGHT); + } + if (IsPageSelected(mnEditId) || mnEditId == GetCurPageId()) + { + aForegroundColor = aSelectTextColor; + aBackgroundColor = aSelectColor; + } + else + { + aForegroundColor = aFaceTextColor; + aBackgroundColor = aFaceColor; + } + if (GetPageBits( mnEditId ) & TabBarPageBits::Blue) + { + aForegroundColor = COL_LIGHTBLUE; + } + mpImpl->mpEdit->SetControlFont(aFont); + mpImpl->mpEdit->SetControlForeground(aForegroundColor); + mpImpl->mpEdit->SetControlBackground(aBackgroundColor); + mpImpl->mpEdit->GrabFocus(); + mpImpl->mpEdit->SetSelection(Selection(0, mpImpl->mpEdit->GetText().getLength())); + mpImpl->mpEdit->Show(); + return true; + } + else + { + mnEditId = 0; + return false; + } +} + +bool TabBar::IsInEditMode() const +{ + return mpImpl->mpEdit.get() != nullptr; +} + +void TabBar::EndEditMode(bool bCancel) +{ + if (!mpImpl->mpEdit) + return; + + // call hdl + bool bEnd = true; + mbEditCanceled = bCancel; + maEditText = mpImpl->mpEdit->GetText(); + mpImpl->mpEdit->SetPostEvent(); + if (!bCancel) + { + TabBarAllowRenamingReturnCode nAllowRenaming = AllowRenaming(); + if (nAllowRenaming == TABBAR_RENAMING_YES) + SetPageText(mnEditId, maEditText); + else if (nAllowRenaming == TABBAR_RENAMING_NO) + bEnd = false; + else // nAllowRenaming == TABBAR_RENAMING_CANCEL + mbEditCanceled = true; + } + + // renaming not allowed, then reset edit data + if (!bEnd) + { + mpImpl->mpEdit->ResetPostEvent(); + mpImpl->mpEdit->GrabFocus(); + } + else + { + // close edit and call end hdl + mpImpl->mpEdit.disposeAndClear(); + + EndRenaming(); + mnEditId = 0; + } + + // reset + maEditText.clear(); + mbEditCanceled = false; +} + +void TabBar::SetMirrored(bool bMirrored) +{ + if (mbMirrored != bMirrored) + { + mbMirrored = bMirrored; + mbSizeFormat = true; + ImplInitControls(); // for button images + Resize(); // recalculates control positions + Mirror(); + } +} + +void TabBar::SetEffectiveRTL(bool bRTL) +{ + SetMirrored( bRTL != AllSettings::GetLayoutRTL() ); +} + +bool TabBar::IsEffectiveRTL() const +{ + return IsMirrored() != AllSettings::GetLayoutRTL(); +} + +void TabBar::SetMaxPageWidth(long nMaxWidth) +{ + if (mnMaxPageWidth != nMaxWidth) + { + mnMaxPageWidth = nMaxWidth; + mbSizeFormat = true; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + } +} + +void TabBar::SetPageText(sal_uInt16 nPageId, const OUString& rText) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + { + mpImpl->mpItemList[nPos]->maText = rText; + mbSizeFormat = true; + + // redraw bar + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); + + CallEventListeners(VclEventId::TabbarPageTextChanged, reinterpret_cast<void*>(sal::static_int_cast<sal_IntPtr>(nPageId))); + } +} + +OUString TabBar::GetPageText(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + return mpImpl->mpItemList[nPos]->maText; + return OUString(); +} + +OUString TabBar::GetAuxiliaryText(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + return mpImpl->mpItemList[nPos]->maAuxiliaryText; + return OUString(); +} + +void TabBar::SetAuxiliaryText(sal_uInt16 nPageId, const OUString& rText ) +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + { + mpImpl->mpItemList[nPos]->maAuxiliaryText = rText; + // no redraw bar, no CallEventListener, internal use in LayerTabBar + } +} + +OUString TabBar::GetHelpText(sal_uInt16 nPageId) const +{ + sal_uInt16 nPos = GetPagePos(nPageId); + if (nPos != PAGE_NOT_FOUND) + { + auto& pItem = mpImpl->mpItemList[nPos]; + if (pItem->maHelpText.isEmpty() && !pItem->maHelpId.isEmpty()) + { + Help* pHelp = Application::GetHelp(); + if (pHelp) + pItem->maHelpText = pHelp->GetHelpText(OStringToOUString(pItem->maHelpId, RTL_TEXTENCODING_UTF8), this); + } + + return pItem->maHelpText; + } + return OUString(); +} + +bool TabBar::StartDrag(const CommandEvent& rCEvt, vcl::Region& rRegion) +{ + if (!(mnWinStyle & WB_DRAG) || (rCEvt.GetCommand() != CommandEventId::StartDrag)) + return false; + + // Check if the clicked page was selected. If this is not the case + // set it as actual entry. We check for this only at a mouse action + // if Drag and Drop can be triggered from the keyboard. + // We only do this, if Select() was not triggered, as the Select() + // could have scrolled the area + if (rCEvt.IsMouseEvent() && !mbInSelect) + { + sal_uInt16 nSelId = GetPageId(rCEvt.GetMousePosPixel()); + + // do not start dragging if no entry was clicked + if (!nSelId) + return false; + + // check if page was selected. If not set it as actual + // page and call Select() + if (!IsPageSelected(nSelId)) + { + if (ImplDeactivatePage()) + { + SetCurPageId(nSelId); + PaintImmediately(); + ImplActivatePage(); + ImplSelect(); + } + else + return false; + } + } + mbInSelect = false; + + vcl::Region aRegion; + + // assign region + rRegion = aRegion; + + return true; +} + +sal_uInt16 TabBar::ShowDropPos(const Point& rPos) +{ + sal_uInt16 nDropId; + sal_uInt16 nNewDropPos; + sal_uInt16 nItemCount = mpImpl->getItemSize(); + sal_Int16 nScroll = 0; + + if (rPos.X() > mnLastOffX-TABBAR_DRAG_SCROLLOFF) + { + auto& pItem = mpImpl->mpItemList[mpImpl->mpItemList.size() - 1]; + if (!pItem->maRect.IsEmpty() && (rPos.X() > pItem->maRect.Right())) + nNewDropPos = mpImpl->getItemSize(); + else + { + nNewDropPos = mnFirstPos + 1; + nScroll = 1; + } + } + else if ((rPos.X() <= mnOffX) || + (!mnOffX && (rPos.X() <= TABBAR_DRAG_SCROLLOFF))) + { + if (mnFirstPos) + { + nNewDropPos = mnFirstPos; + nScroll = -1; + } + else + nNewDropPos = 0; + } + else + { + nDropId = GetPageId(rPos); + if (nDropId) + { + nNewDropPos = GetPagePos(nDropId); + if (mnFirstPos && (nNewDropPos == mnFirstPos - 1)) + nScroll = -1; + } + else + nNewDropPos = nItemCount; + } + + if (mbDropPos && (nNewDropPos == mnDropPos) && !nScroll) + return mnDropPos; + + if (mbDropPos) + HideDropPos(); + mbDropPos = true; + mnDropPos = nNewDropPos; + + if (nScroll) + { + sal_uInt16 nOldFirstPos = mnFirstPos; + SetFirstPageId(GetPageId(mnFirstPos + nScroll)); + + // draw immediately, as Paint not possible during Drag and Drop + if (nOldFirstPos != mnFirstPos) + { + tools::Rectangle aRect(mnOffX, 0, mnLastOffX, maWinSize.Height()); + SetFillColor(GetBackground().GetColor()); + DrawRect(aRect); + Invalidate(aRect); + } + } + + // draw drop position arrows + Color aBlackColor(COL_BLACK); + long nX; + long nY = (maWinSize.Height() / 2) - 1; + sal_uInt16 nCurPos = GetPagePos(mnCurPageId); + + sal_Int32 nTriangleWidth = 3 * GetDPIScaleFactor(); + + if (mnDropPos < nItemCount) + { + SetLineColor(aBlackColor); + SetFillColor(aBlackColor); + + auto& pItem = mpImpl->mpItemList[mnDropPos]; + nX = pItem->maRect.Left(); + if ( mnDropPos == nCurPos ) + nX--; + else + nX++; + + if (!pItem->IsDefaultTabBgColor() && !pItem->mbSelect) + { + SetLineColor(pItem->maTabTextColor); + SetFillColor(pItem->maTabTextColor); + } + + tools::Polygon aPoly(3); + aPoly.SetPoint(Point(nX, nY), 0); + aPoly.SetPoint(Point(nX + nTriangleWidth, nY - nTriangleWidth), 1); + aPoly.SetPoint(Point(nX + nTriangleWidth, nY + nTriangleWidth), 2); + DrawPolygon(aPoly); + } + if (mnDropPos > 0 && mnDropPos < nItemCount + 1) + { + SetLineColor(aBlackColor); + SetFillColor(aBlackColor); + + auto& pItem = mpImpl->mpItemList[mnDropPos - 1]; + nX = pItem->maRect.Right(); + if (mnDropPos == nCurPos) + nX++; + if (!pItem->IsDefaultTabBgColor() && !pItem->mbSelect) + { + SetLineColor(pItem->maTabTextColor); + SetFillColor(pItem->maTabTextColor); + } + tools::Polygon aPoly(3); + aPoly.SetPoint(Point(nX, nY), 0); + aPoly.SetPoint(Point(nX - nTriangleWidth, nY - nTriangleWidth), 1); + aPoly.SetPoint(Point(nX - nTriangleWidth, nY + nTriangleWidth), 2); + DrawPolygon(aPoly); + } + + return mnDropPos; +} + +void TabBar::HideDropPos() +{ + if (!mbDropPos) + return; + + long nX; + long nY1 = (maWinSize.Height() / 2) - 3; + long nY2 = nY1 + 5; + sal_uInt16 nItemCount = mpImpl->getItemSize(); + + if (mnDropPos < nItemCount) + { + auto& pItem = mpImpl->mpItemList[mnDropPos]; + nX = pItem->maRect.Left(); + // immediately call Paint, as it is not possible during drag and drop + tools::Rectangle aRect( nX-1, nY1, nX+3, nY2 ); + vcl::Region aRegion( aRect ); + SetClipRegion( aRegion ); + Invalidate(aRect); + SetClipRegion(); + } + if (mnDropPos > 0 && mnDropPos < nItemCount + 1) + { + auto& pItem = mpImpl->mpItemList[mnDropPos - 1]; + nX = pItem->maRect.Right(); + // immediately call Paint, as it is not possible during drag and drop + tools::Rectangle aRect(nX - 2, nY1, nX + 1, nY2); + vcl::Region aRegion(aRect); + SetClipRegion(aRegion); + Invalidate(aRect); + SetClipRegion(); + } + + mbDropPos = false; + mnDropPos = 0; +} + +void TabBar::SwitchPage(const Point& rPos) +{ + sal_uInt16 nSwitchId = GetPageId(rPos); + if (!nSwitchId) + EndSwitchPage(); + else + { + if (nSwitchId != mnSwitchId) + { + mnSwitchId = nSwitchId; + mnSwitchTime = tools::Time::GetSystemTicks(); + } + else + { + // change only after 500 ms + if (mnSwitchId != GetCurPageId()) + { + if (tools::Time::GetSystemTicks() > mnSwitchTime + 500) + { + if (ImplDeactivatePage()) + { + SetCurPageId( mnSwitchId ); + PaintImmediately(); + ImplActivatePage(); + ImplSelect(); + } + } + } + } + } +} + +void TabBar::EndSwitchPage() +{ + mnSwitchTime = 0; + mnSwitchId = 0; +} + +void TabBar::SetStyle(WinBits nStyle) +{ + mnWinStyle = nStyle; + ImplInitControls(); + // order possible controls + if (IsReallyVisible() && IsUpdateMode()) + Resize(); +} + +Size TabBar::CalcWindowSizePixel() const +{ + long nWidth = 0; + + if (!mpImpl->mpItemList.empty()) + { + const_cast<TabBar*>(this)->ImplCalcWidth(); + for (const auto& pItem : mpImpl->mpItemList) + { + nWidth += pItem->mnWidth; + } + } + + return Size(nWidth, GetSettings().GetStyleSettings().GetScrollBarSize()); +} + +tools::Rectangle TabBar::GetPageArea() const +{ + return tools::Rectangle(Point(mnOffX, mnOffY), + Size(mnLastOffX - mnOffX + 1, GetSizePixel().Height() - mnOffY)); +} + +css::uno::Reference<css::accessibility::XAccessible> TabBar::CreateAccessible() +{ + return mpImpl->maAccessibleFactory.getFactory().createAccessibleTabBar(*this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/toolbarmenu.cxx b/svtools/source/control/toolbarmenu.cxx new file mode 100644 index 000000000..c96dba998 --- /dev/null +++ b/svtools/source/control/toolbarmenu.cxx @@ -0,0 +1,267 @@ +/* -*- 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 <memory> +#include <comphelper/processfactory.hxx> +#include <osl/diagnose.h> + +#include <vcl/taskpanelist.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/event.hxx> +#include <vcl/svapp.hxx> + +#include <svtools/framestatuslistener.hxx> +#include <svtools/valueset.hxx> +#include <svtools/toolbarmenu.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::accessibility; + +namespace svtools { + +static vcl::Window* GetTopMostParentSystemWindow( vcl::Window* pWindow ) +{ + OSL_ASSERT( pWindow ); + if ( pWindow ) + { + // ->manually search topmost system window + // required because their might be another system window between this and the top window + pWindow = pWindow->GetParent(); + SystemWindow* pTopMostSysWin = nullptr; + while ( pWindow ) + { + if ( pWindow->IsSystemWindow() ) + pTopMostSysWin = static_cast<SystemWindow*>(pWindow); + pWindow = pWindow->GetParent(); + } + pWindow = pTopMostSysWin; + OSL_ASSERT( pWindow ); + return pWindow; + } + + return nullptr; +} + +class ToolbarPopupStatusListener : public svt::FrameStatusListener +{ +public: + ToolbarPopupStatusListener( const css::uno::Reference< css::frame::XFrame >& xFrame, + ToolbarPopupBase& rToolbarPopup ); + + virtual void SAL_CALL dispose() override; + virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& Event ) override; + + ToolbarPopupBase* mpPopup; +}; + + +ToolbarPopupStatusListener::ToolbarPopupStatusListener( + const css::uno::Reference< css::frame::XFrame >& xFrame, + ToolbarPopupBase& rToolbarPopup ) +: svt::FrameStatusListener( ::comphelper::getProcessComponentContext(), xFrame ) +, mpPopup( &rToolbarPopup ) +{ +} + + +void SAL_CALL ToolbarPopupStatusListener::dispose() +{ + mpPopup = nullptr; + svt::FrameStatusListener::dispose(); +} + + +void SAL_CALL ToolbarPopupStatusListener::statusChanged( const css::frame::FeatureStateEvent& Event ) +{ + if( mpPopup ) + mpPopup->statusChanged( Event ); +} + +ToolbarPopupBase::ToolbarPopupBase(const css::uno::Reference<css::frame::XFrame>& rFrame) + : mxFrame(rFrame) +{ +} + +ToolbarPopupBase::~ToolbarPopupBase() +{ + if (mxStatusListener.is()) + { + mxStatusListener->dispose(); + mxStatusListener.clear(); + } +} + +ToolbarPopup::ToolbarPopup( const css::uno::Reference<css::frame::XFrame>& rFrame, vcl::Window* pParentWindow, + const OString& rID, const OUString& rUIXMLDescription ) + : DockingWindow(pParentWindow, rID, rUIXMLDescription, rFrame) + , ToolbarPopupBase(rFrame) +{ + init(); +} + +void ToolbarPopup::init() +{ + vcl::Window* pWindow = GetTopMostParentSystemWindow( this ); + if ( pWindow ) + static_cast<SystemWindow*>(pWindow)->GetTaskPaneList()->AddWindow( this ); +} + +ToolbarPopup::~ToolbarPopup() +{ + disposeOnce(); +} + +void ToolbarPopup::dispose() +{ + vcl::Window* pWindow = GetTopMostParentSystemWindow( this ); + if ( pWindow ) + static_cast<SystemWindow*>(pWindow)->GetTaskPaneList()->RemoveWindow( this ); + + if ( mxStatusListener.is() ) + { + mxStatusListener->dispose(); + mxStatusListener.clear(); + } + + mxFrame.clear(); + DockingWindow::dispose(); +} + +void ToolbarPopupBase::AddStatusListener( const OUString& rCommandURL ) +{ + if( !mxStatusListener.is() ) + mxStatusListener.set( new ToolbarPopupStatusListener( mxFrame, *this ) ); + + mxStatusListener->addStatusListener( rCommandURL ); +} + +void ToolbarPopupBase::statusChanged( const css::frame::FeatureStateEvent& /*Event*/ ) +{ +} + +void ToolbarPopup::EndPopupMode() +{ + GetDockingManager()->EndPopupMode(this); +} + + +} + +WeldToolbarPopup::WeldToolbarPopup(const css::uno::Reference<css::frame::XFrame>& rFrame, + weld::Widget* pParent, const OUString& rUIFile, + const OString& rId) + : ToolbarPopupBase(rFrame) + , m_xBuilder(Application::CreateBuilder(pParent, rUIFile)) + , m_xTopLevel(m_xBuilder->weld_container(rId)) + , m_xContainer(m_xBuilder->weld_container("container")) +{ + m_xTopLevel->connect_focus_in(LINK(this, WeldToolbarPopup, FocusHdl)); +} + +WeldToolbarPopup::~WeldToolbarPopup() +{ +} + +IMPL_LINK_NOARG(WeldToolbarPopup, FocusHdl, weld::Widget&, void) +{ + GrabFocus(); +} + +ToolbarPopupContainer::ToolbarPopupContainer(weld::Widget* pParent) + : m_xBuilder(Application::CreateBuilder(pParent, "svx/ui/toolbarpopover.ui")) + , m_xTopLevel(m_xBuilder->weld_container("ToolbarPopover")) + , m_xContainer(m_xBuilder->weld_container("container")) +{ + m_xTopLevel->connect_focus_in(LINK(this, ToolbarPopupContainer, FocusHdl)); +} + +void ToolbarPopupContainer::setPopover(std::unique_ptr<WeldToolbarPopup> xPopup) +{ + m_xPopup = std::move(xPopup); + // move the WeldToolbarPopup contents into this toolbar so on-demand contents can appear inside a preexisting gtk popover + // because the arrow for the popover is only enabled if there's a popover set + m_xPopup->getTopLevel()->move(m_xPopup->getContainer(), m_xContainer.get()); + m_xPopup->GrabFocus(); +} + +void ToolbarPopupContainer::unsetPopover() +{ + if (!m_xPopup) + return; + m_xContainer->move(m_xPopup->getContainer(), m_xPopup->getTopLevel()); + m_xPopup.reset(); +} + +ToolbarPopupContainer::~ToolbarPopupContainer() +{ + unsetPopover(); +} + +IMPL_LINK_NOARG(ToolbarPopupContainer, FocusHdl, weld::Widget&, void) +{ + if (m_xPopup) + m_xPopup->GrabFocus(); +} + +InterimToolbarPopup::InterimToolbarPopup(const css::uno::Reference<css::frame::XFrame>& rFrame, vcl::Window* pParent, + std::unique_ptr<WeldToolbarPopup> xPopup, bool bTearable) + : ToolbarPopup(rFrame, pParent, + !bTearable ? OString("InterimDockParent") : OString("InterimTearableParent"), + !bTearable ? OUString("svx/ui/interimdockparent.ui") : OUString("svx/ui/interimtearableparent.ui")) + , m_xBox(get("box")) + , m_xBuilder(Application::CreateInterimBuilder(m_xBox.get(), "svx/ui/interimparent.ui")) + , m_xContainer(m_xBuilder->weld_container("container")) + , m_xPopup(std::move(xPopup)) +{ + // move the WeldToolbarPopup contents into this interim toolbar so welded contents can appear as a dropdown in an unwelded toolbar + m_xPopup->getTopLevel()->move(m_xPopup->getContainer(), m_xContainer.get()); +} + +void InterimToolbarPopup::GetFocus() +{ + ToolbarPopup::GetFocus(); + m_xPopup->GrabFocus(); +} + +void InterimToolbarPopup::dispose() +{ + // if we have focus when disposed, pick the document window as destination + // for focus rather than let it go to an arbitrary windows + if (HasFocus()) + { + if (auto xWindow = mxFrame->getContainerWindow()) + xWindow->setFocus(); + } + // move the contents back where it belongs + m_xContainer->move(m_xPopup->getContainer(), m_xPopup->getTopLevel()); + m_xPopup.reset(); + m_xContainer.reset(); + m_xBox.clear(); + ToolbarPopup::dispose(); +} + +InterimToolbarPopup::~InterimToolbarPopup() +{ + disposeOnce(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/valueacc.cxx b/svtools/source/control/valueacc.cxx new file mode 100644 index 000000000..c43e8d070 --- /dev/null +++ b/svtools/source/control/valueacc.cxx @@ -0,0 +1,1020 @@ +/* -*- 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 <unotools/accessiblestatesethelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <tools/diagnose_ex.h> +#include <svtools/valueset.hxx> +#include "valueimp.hxx" +#include <comphelper/servicehelper.hxx> +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <unotools/accessiblerelationsethelper.hxx> + +using namespace ::com::sun::star; + + +ValueSetItem::ValueSetItem( ValueSet& rParent ) + : mrParent(rParent) + , mnId(0) + , meType(VALUESETITEM_NONE) + , mbVisible(true) + , mpData(nullptr) + , mxAcc() +{ +} + + +ValueSetItem::~ValueSetItem() +{ + if( mxAcc.is() ) + { + mxAcc->ParentDestroyed(); + } +} + +uno::Reference< accessibility::XAccessible > ValueSetItem::GetAccessible( bool bIsTransientChildrenDisabled ) +{ + if( !mxAcc.is() ) + mxAcc = new ValueItemAcc( this, bIsTransientChildrenDisabled ); + + return mxAcc.get(); +} + +ValueItemAcc::ValueItemAcc( ValueSetItem* pParent, bool bIsTransientChildrenDisabled ) : + mpParent( pParent ), + mbIsTransientChildrenDisabled( bIsTransientChildrenDisabled ) +{ +} + +ValueItemAcc::~ValueItemAcc() +{ +} + +void ValueItemAcc::ParentDestroyed() +{ + const ::osl::MutexGuard aGuard( maMutex ); + mpParent = nullptr; +} + +namespace +{ + class theValueItemAccUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theValueItemAccUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 >& ValueItemAcc::getUnoTunnelId() +{ + return theValueItemAccUnoTunnelId::get().getSeq(); +} + + +ValueItemAcc* ValueItemAcc::getImplementation( const uno::Reference< uno::XInterface >& rxData ) + throw() +{ + try + { + return comphelper::getUnoTunnelImplementation<ValueItemAcc>(rxData); + } + catch(const css::uno::Exception&) + { + return nullptr; + } +} + + +uno::Reference< accessibility::XAccessibleContext > SAL_CALL ValueItemAcc::getAccessibleContext() +{ + return this; +} + + +sal_Int32 SAL_CALL ValueItemAcc::getAccessibleChildCount() +{ + return 0; +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueItemAcc::getAccessibleChild( sal_Int32 ) +{ + throw lang::IndexOutOfBoundsException(); +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueItemAcc::getAccessibleParent() +{ + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xRet; + + if( mpParent ) + xRet = mpParent->mrParent.mxAccessible; + + return xRet; +} + + +sal_Int32 SAL_CALL ValueItemAcc::getAccessibleIndexInParent() +{ + const SolarMutexGuard aSolarGuard; + // The index defaults to -1 to indicate the child does not belong to its + // parent. + sal_Int32 nIndexInParent = -1; + + if( mpParent ) + { + bool bDone = false; + + sal_uInt16 nCount = mpParent->mrParent.ImplGetVisibleItemCount(); + ValueSetItem* pItem; + for (sal_uInt16 i=0; i<nCount && !bDone; i++) + { + // Guard the retrieval of the i-th child with a try/catch block + // just in case the number of children changes in the meantime. + try + { + pItem = mpParent->mrParent.ImplGetItem(i); + } + catch (const lang::IndexOutOfBoundsException&) + { + pItem = nullptr; + } + + // Do not create an accessible object for the test. + if (pItem != nullptr && pItem->mxAcc.is()) + if (pItem->GetAccessible( mbIsTransientChildrenDisabled ).get() == this ) + { + nIndexInParent = i; + bDone = true; + } + } + } + + //if this valueset contain a none field(common value is default), then we should increase the real index and set the noitem index value equal 0. + if ( mpParent && ( (mpParent->mrParent.GetStyle() & WB_NONEFIELD) != 0 ) ) + { + ValueSetItem* pFirstItem = mpParent->mrParent.ImplGetItem (VALUESET_ITEM_NONEITEM); + if( pFirstItem && pFirstItem ->GetAccessible(mbIsTransientChildrenDisabled).get() == this ) + nIndexInParent = 0; + else + nIndexInParent++; + } + return nIndexInParent; +} + + +sal_Int16 SAL_CALL ValueItemAcc::getAccessibleRole() +{ + return accessibility::AccessibleRole::LIST_ITEM; +} + + +OUString SAL_CALL ValueItemAcc::getAccessibleDescription() +{ + return OUString(); +} + + +OUString SAL_CALL ValueItemAcc::getAccessibleName() +{ + const SolarMutexGuard aSolarGuard; + + if( mpParent ) + { + if (mpParent->maText.isEmpty()) + return "Item " + OUString::number(static_cast<sal_Int32>(mpParent->mnId)); + else + return mpParent->maText; + } + + return OUString(); +} + + +uno::Reference< accessibility::XAccessibleRelationSet > SAL_CALL ValueItemAcc::getAccessibleRelationSet() +{ + return uno::Reference< accessibility::XAccessibleRelationSet >(); +} + + +uno::Reference< accessibility::XAccessibleStateSet > SAL_CALL ValueItemAcc::getAccessibleStateSet() +{ + const SolarMutexGuard aSolarGuard; + ::utl::AccessibleStateSetHelper* pStateSet = new ::utl::AccessibleStateSetHelper; + + if( mpParent ) + { + pStateSet->AddState (accessibility::AccessibleStateType::ENABLED); + pStateSet->AddState (accessibility::AccessibleStateType::SENSITIVE); + pStateSet->AddState (accessibility::AccessibleStateType::SHOWING); + pStateSet->AddState (accessibility::AccessibleStateType::VISIBLE); + if ( !mbIsTransientChildrenDisabled ) + pStateSet->AddState (accessibility::AccessibleStateType::TRANSIENT); + + // SELECTABLE + pStateSet->AddState( accessibility::AccessibleStateType::SELECTABLE ); + // pStateSet->AddState( accessibility::AccessibleStateType::FOCUSABLE ); + + // SELECTED + if( mpParent->mrParent.GetSelectedItemId() == mpParent->mnId ) + { + pStateSet->AddState( accessibility::AccessibleStateType::SELECTED ); + // pStateSet->AddState( accessibility::AccessibleStateType::FOCUSED ); + } + } + + return pStateSet; +} + + +lang::Locale SAL_CALL ValueItemAcc::getLocale() +{ + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xParent( getAccessibleParent() ); + lang::Locale aRet( "", "", "" ); + + if( xParent.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + + if( xParentContext.is() ) + aRet = xParentContext->getLocale(); + } + + return aRet; +} + + +void SAL_CALL ValueItemAcc::addAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + const ::osl::MutexGuard aGuard( maMutex ); + + if( !rxListener.is() ) + return; + + bool bFound = false; + + for (auto const& eventListener : mxEventListeners) + { + if(eventListener == rxListener) + { + bFound = true; + break; + } + } + + if (!bFound) + mxEventListeners.push_back( rxListener ); +} + + +void SAL_CALL ValueItemAcc::removeAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + const ::osl::MutexGuard aGuard( maMutex ); + + if( rxListener.is() ) + { + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > >::iterator aIter = + std::find(mxEventListeners.begin(), mxEventListeners.end(), rxListener); + + if (aIter != mxEventListeners.end()) + mxEventListeners.erase(aIter); + } +} + + +sal_Bool SAL_CALL ValueItemAcc::containsPoint( const awt::Point& aPoint ) +{ + const awt::Rectangle aRect( getBounds() ); + const Point aSize( aRect.Width, aRect.Height ); + const Point aNullPoint, aTestPoint( aPoint.X, aPoint.Y ); + + return tools::Rectangle( aNullPoint, aSize ).IsInside( aTestPoint ); +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueItemAcc::getAccessibleAtPoint( const awt::Point& ) +{ + uno::Reference< accessibility::XAccessible > xRet; + return xRet; +} + +awt::Rectangle SAL_CALL ValueItemAcc::getBounds() +{ + const SolarMutexGuard aSolarGuard; + awt::Rectangle aRet; + + if( mpParent ) + { + tools::Rectangle aRect( mpParent->mrParent.GetItemRect(mpParent->mnId) ); + tools::Rectangle aParentRect( Point(), mpParent->mrParent.GetOutputSizePixel() ); + + aRect.Intersection( aParentRect ); + + aRet.X = aRect.Left(); + aRet.Y = aRect.Top(); + aRet.Width = aRect.GetWidth(); + aRet.Height = aRect.GetHeight(); + } + + return aRet; +} + +awt::Point SAL_CALL ValueItemAcc::getLocation() +{ + const awt::Rectangle aRect( getBounds() ); + awt::Point aRet; + + aRet.X = aRect.X; + aRet.Y = aRect.Y; + + return aRet; +} + +awt::Point SAL_CALL ValueItemAcc::getLocationOnScreen() +{ + const SolarMutexGuard aSolarGuard; + awt::Point aRet; + + if( mpParent ) + { + const Point aPos = mpParent->mrParent.GetItemRect(mpParent->mnId).TopLeft(); + const Point aScreenPos( mpParent->mrParent.GetDrawingArea()->get_accessible_location() ); + + aRet.X = aPos.X() + aScreenPos.X(); + aRet.Y = aPos.Y() + aScreenPos.Y(); + } + + return aRet; +} + +awt::Size SAL_CALL ValueItemAcc::getSize() +{ + const awt::Rectangle aRect( getBounds() ); + awt::Size aRet; + + aRet.Width = aRect.Width; + aRet.Height = aRect.Height; + + return aRet; +} + +void SAL_CALL ValueItemAcc::grabFocus() +{ + // nothing to do +} + +sal_Int32 SAL_CALL ValueItemAcc::getForeground( ) +{ + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowTextColor(); + return static_cast<sal_Int32>(nColor); +} + +sal_Int32 SAL_CALL ValueItemAcc::getBackground( ) +{ + Color nColor; + if (mpParent && mpParent->meType == VALUESETITEM_COLOR) + nColor = mpParent->maColor; + else + nColor = Application::GetSettings().GetStyleSettings().GetWindowColor(); + return static_cast<sal_Int32>(nColor); +} + +sal_Int64 SAL_CALL ValueItemAcc::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + sal_Int64 nRet; + + if( isUnoTunnelId<ValueItemAcc>(rId) ) + nRet = reinterpret_cast< sal_Int64 >( this ); + else + nRet = 0; + + return nRet; +} + +void ValueItemAcc::FireAccessibleEvent( short nEventId, const uno::Any& rOldValue, const uno::Any& rNewValue ) +{ + if( !nEventId ) + return; + + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > > aTmpListeners( mxEventListeners ); + accessibility::AccessibleEventObject aEvtObject; + + aEvtObject.EventId = nEventId; + aEvtObject.Source = static_cast<uno::XWeak*>(this); + aEvtObject.NewValue = rNewValue; + aEvtObject.OldValue = rOldValue; + + for (auto const& tmpListener : aTmpListeners) + { + tmpListener->notifyEvent( aEvtObject ); + } +} + +ValueSetAcc::ValueSetAcc( ValueSet* pParent ) : + ValueSetAccComponentBase (m_aMutex), + mpParent( pParent ), + mbIsFocused(false) +{ +} + + +ValueSetAcc::~ValueSetAcc() +{ +} + + +void ValueSetAcc::FireAccessibleEvent( short nEventId, const uno::Any& rOldValue, const uno::Any& rNewValue ) +{ + if( !nEventId ) + return; + + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > > aTmpListeners( mxEventListeners ); + accessibility::AccessibleEventObject aEvtObject; + + aEvtObject.EventId = nEventId; + aEvtObject.Source = static_cast<uno::XWeak*>(this); + aEvtObject.NewValue = rNewValue; + aEvtObject.OldValue = rOldValue; + + for (auto const& tmpListener : aTmpListeners) + { + try + { + tmpListener->notifyEvent( aEvtObject ); + } + catch(const uno::Exception&) + { + } + } +} + +namespace +{ + class theValueSetAccUnoTunnelId : public rtl::Static< UnoTunnelIdInit, theValueSetAccUnoTunnelId > {}; +} + +const uno::Sequence< sal_Int8 >& ValueSetAcc::getUnoTunnelId() +{ + return theValueSetAccUnoTunnelId::get().getSeq(); +} + + +ValueSetAcc* ValueSetAcc::getImplementation( const uno::Reference< uno::XInterface >& rxData ) + throw() +{ + try + { + return comphelper::getUnoTunnelImplementation<ValueSetAcc>(rxData); + } + catch(const css::uno::Exception&) + { + return nullptr; + } +} + + +void ValueSetAcc::GetFocus() +{ + mbIsFocused = true; + + // Broadcast the state change. + css::uno::Any aOldState, aNewState; + aNewState <<= css::accessibility::AccessibleStateType::FOCUSED; + FireAccessibleEvent( + css::accessibility::AccessibleEventId::STATE_CHANGED, + aOldState, aNewState); +} + + +void ValueSetAcc::LoseFocus() +{ + mbIsFocused = false; + + // Broadcast the state change. + css::uno::Any aOldState, aNewState; + aOldState <<= css::accessibility::AccessibleStateType::FOCUSED; + FireAccessibleEvent( + css::accessibility::AccessibleEventId::STATE_CHANGED, + aOldState, aNewState); +} + + +uno::Reference< accessibility::XAccessibleContext > SAL_CALL ValueSetAcc::getAccessibleContext() +{ + ThrowIfDisposed(); + return this; +} + + +sal_Int32 SAL_CALL ValueSetAcc::getAccessibleChildCount() +{ + const SolarMutexGuard aSolarGuard; + ThrowIfDisposed(); + + sal_Int32 nCount = mpParent->ImplGetVisibleItemCount(); + if (HasNoneField()) + nCount += 1; + return nCount; +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getAccessibleChild( sal_Int32 i ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ValueSetItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(i)); + + if( !pItem ) + throw lang::IndexOutOfBoundsException(); + + uno::Reference< accessibility::XAccessible > xRet = pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ); + return xRet; +} + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getAccessibleParent() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + return mpParent->GetDrawingArea()->get_accessible_parent(); +} + +sal_Int32 SAL_CALL ValueSetAcc::getAccessibleIndexInParent() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + + // -1 for child not found/no parent (according to specification) + sal_Int32 nRet = -1; + + uno::Reference<accessibility::XAccessible> xParent(getAccessibleParent()); + if (!xParent) + return nRet; + + try + { + uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent->getAccessibleContext()); + + // iterate over parent's children and search for this object + if ( xParentContext.is() ) + { + sal_Int32 nChildCount = xParentContext->getAccessibleChildCount(); + for ( sal_Int32 nChild = 0; ( nChild < nChildCount ) && ( -1 == nRet ); ++nChild ) + { + uno::Reference<XAccessible> xChild(xParentContext->getAccessibleChild(nChild)); + if ( xChild.get() == this ) + nRet = nChild; + } + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION( "svtools", "OAccessibleContextHelper::getAccessibleIndexInParent" ); + } + + return nRet; +} + +sal_Int16 SAL_CALL ValueSetAcc::getAccessibleRole() +{ + ThrowIfDisposed(); + return accessibility::AccessibleRole::LIST; +} + + +OUString SAL_CALL ValueSetAcc::getAccessibleDescription() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + OUString aRet; + + if (mpParent) + { + aRet = mpParent->GetAccessibleDescription(); + } + + return aRet; +} + + +OUString SAL_CALL ValueSetAcc::getAccessibleName() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + OUString aRet; + + if (mpParent) + { + aRet = mpParent->GetAccessibleName(); + } + + return aRet; +} + +uno::Reference< accessibility::XAccessibleRelationSet > SAL_CALL ValueSetAcc::getAccessibleRelationSet() +{ + ThrowIfDisposed(); + SolarMutexGuard g; + return mpParent->GetDrawingArea()->get_accessible_relation_set(); +} + +uno::Reference< accessibility::XAccessibleStateSet > SAL_CALL ValueSetAcc::getAccessibleStateSet() +{ + ThrowIfDisposed(); + ::utl::AccessibleStateSetHelper* pStateSet = new ::utl::AccessibleStateSetHelper(); + + // Set some states. + pStateSet->AddState (accessibility::AccessibleStateType::ENABLED); + pStateSet->AddState (accessibility::AccessibleStateType::SENSITIVE); + pStateSet->AddState (accessibility::AccessibleStateType::SHOWING); + pStateSet->AddState (accessibility::AccessibleStateType::VISIBLE); + pStateSet->AddState (accessibility::AccessibleStateType::MANAGES_DESCENDANTS); + pStateSet->AddState (accessibility::AccessibleStateType::FOCUSABLE); + if (mbIsFocused) + pStateSet->AddState (accessibility::AccessibleStateType::FOCUSED); + + return pStateSet; +} + + +lang::Locale SAL_CALL ValueSetAcc::getLocale() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xParent( getAccessibleParent() ); + lang::Locale aRet( "", "", "" ); + + if( xParent.is() ) + { + uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + + if( xParentContext.is() ) + aRet = xParentContext->getLocale (); + } + + return aRet; +} + + +void SAL_CALL ValueSetAcc::addAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + ThrowIfDisposed(); + ::osl::MutexGuard aGuard (m_aMutex); + + if( !rxListener.is() ) + return; + + bool bFound = false; + + for (auto const& eventListener : mxEventListeners) + { + if(eventListener == rxListener) + { + bFound = true; + break; + } + } + + if (!bFound) + mxEventListeners.push_back( rxListener ); +} + + +void SAL_CALL ValueSetAcc::removeAccessibleEventListener( const uno::Reference< accessibility::XAccessibleEventListener >& rxListener ) +{ + ThrowIfDisposed(); + ::osl::MutexGuard aGuard (m_aMutex); + + if( rxListener.is() ) + { + ::std::vector< uno::Reference< accessibility::XAccessibleEventListener > >::iterator aIter = + std::find(mxEventListeners.begin(), mxEventListeners.end(), rxListener); + + if (aIter != mxEventListeners.end()) + mxEventListeners.erase(aIter); + } +} + + +sal_Bool SAL_CALL ValueSetAcc::containsPoint( const awt::Point& aPoint ) +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + const Point aSize( aRect.Width, aRect.Height ); + const Point aNullPoint, aTestPoint( aPoint.X, aPoint.Y ); + + return tools::Rectangle( aNullPoint, aSize ).IsInside( aTestPoint ); +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getAccessibleAtPoint( const awt::Point& aPoint ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + const sal_uInt16 nItemId = mpParent->GetItemId( Point( aPoint.X, aPoint.Y ) ); + uno::Reference< accessibility::XAccessible > xRet; + + if ( nItemId ) + { + const size_t nItemPos = mpParent->GetItemPos( nItemId ); + + if( VALUESET_ITEM_NONEITEM != nItemPos ) + { + ValueSetItem *const pItem = mpParent->mItemList[nItemPos].get(); + xRet = pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ); + } + } + + return xRet; +} + + +awt::Rectangle SAL_CALL ValueSetAcc::getBounds() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + const Point aOutPos; + const Size aOutSize( mpParent->GetOutputSizePixel() ); + awt::Rectangle aRet; + + aRet.X = aOutPos.X(); + aRet.Y = aOutPos.Y(); + aRet.Width = aOutSize.Width(); + aRet.Height = aOutSize.Height(); + + return aRet; +} + +awt::Point SAL_CALL ValueSetAcc::getLocation() +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + awt::Point aRet; + + aRet.X = aRect.X; + aRet.Y = aRect.Y; + + return aRet; +} + +awt::Point SAL_CALL ValueSetAcc::getLocationOnScreen() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + awt::Point aScreenLoc(0, 0); + + uno::Reference<accessibility::XAccessible> xParent(getAccessibleParent()); + if (xParent) + { + uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent->getAccessibleContext()); + uno::Reference<accessibility::XAccessibleComponent> xParentComponent(xParentContext, css::uno::UNO_QUERY); + OSL_ENSURE( xParentComponent.is(), "ValueSetAcc::getLocationOnScreen: no parent component!" ); + if ( xParentComponent.is() ) + { + awt::Point aParentScreenLoc( xParentComponent->getLocationOnScreen() ); + awt::Point aOwnRelativeLoc( getLocation() ); + aScreenLoc.X = aParentScreenLoc.X + aOwnRelativeLoc.X; + aScreenLoc.Y = aParentScreenLoc.Y + aOwnRelativeLoc.Y; + } + } + + return aScreenLoc; +} + +awt::Size SAL_CALL ValueSetAcc::getSize() +{ + ThrowIfDisposed(); + const awt::Rectangle aRect( getBounds() ); + awt::Size aRet; + + aRet.Width = aRect.Width; + aRet.Height = aRect.Height; + + return aRet; +} + +void SAL_CALL ValueSetAcc::grabFocus() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + mpParent->GrabFocus(); +} + +sal_Int32 SAL_CALL ValueSetAcc::getForeground( ) +{ + ThrowIfDisposed(); + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowTextColor(); + return static_cast<sal_Int32>(nColor); +} + +sal_Int32 SAL_CALL ValueSetAcc::getBackground( ) +{ + ThrowIfDisposed(); + Color nColor = Application::GetSettings().GetStyleSettings().GetWindowColor(); + return static_cast<sal_Int32>(nColor); +} + +void SAL_CALL ValueSetAcc::selectAccessibleChild( sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ValueSetItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(nChildIndex)); + + if(pItem == nullptr) + throw lang::IndexOutOfBoundsException(); + + mpParent->SelectItem( pItem->mnId ); +} + + +sal_Bool SAL_CALL ValueSetAcc::isAccessibleChildSelected( sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + ValueSetItem* pItem = getItem (sal::static_int_cast< sal_uInt16 >(nChildIndex)); + + if (pItem == nullptr) + throw lang::IndexOutOfBoundsException(); + + bool bRet = mpParent->IsItemSelected( pItem->mnId ); + return bRet; +} + + +void SAL_CALL ValueSetAcc::clearAccessibleSelection() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + mpParent->SetNoSelection(); +} + + +void SAL_CALL ValueSetAcc::selectAllAccessibleChildren() +{ + ThrowIfDisposed(); + // unsupported due to single selection only +} + + +sal_Int32 SAL_CALL ValueSetAcc::getSelectedAccessibleChildCount() +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + sal_Int32 nRet = 0; + + for( sal_uInt16 i = 0, nCount = getItemCount(); i < nCount; i++ ) + { + ValueSetItem* pItem = getItem (i); + + if( pItem && mpParent->IsItemSelected( pItem->mnId ) ) + ++nRet; + } + + return nRet; +} + + +uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + uno::Reference< accessibility::XAccessible > xRet; + + for( sal_uInt16 i = 0, nCount = getItemCount(), nSel = 0; ( i < nCount ) && !xRet.is(); i++ ) + { + ValueSetItem* pItem = getItem(i); + + if( pItem && mpParent->IsItemSelected( pItem->mnId ) && ( nSelectedChildIndex == static_cast< sal_Int32 >( nSel++ ) ) ) + xRet = pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ); + } + + return xRet; +} + + +void SAL_CALL ValueSetAcc::deselectAccessibleChild( sal_Int32 nChildIndex ) +{ + ThrowIfDisposed(); + const SolarMutexGuard aSolarGuard; + // Because of the single selection we can reset the whole selection when + // the specified child is currently selected. + if (isAccessibleChildSelected(nChildIndex)) + mpParent->SetNoSelection(); +} + + +sal_Int64 SAL_CALL ValueSetAcc::getSomething( const uno::Sequence< sal_Int8 >& rId ) +{ + sal_Int64 nRet; + + if( isUnoTunnelId<ValueSetAcc>(rId) ) + nRet = reinterpret_cast< sal_Int64 >( this ); + else + nRet = 0; + + return nRet; +} + + +void SAL_CALL ValueSetAcc::disposing() +{ + ::std::vector<uno::Reference<accessibility::XAccessibleEventListener> > aListenerListCopy; + + { + // Make a copy of the list and clear the original. + const SolarMutexGuard aSolarGuard; + ::osl::MutexGuard aGuard (m_aMutex); + aListenerListCopy.swap(mxEventListeners); + + // Reset the pointer to the parent. It has to be the one who has + // disposed us because he is dying. + mpParent = nullptr; + } + + // Inform all listeners that this objects is disposing. + lang::EventObject aEvent (static_cast<accessibility::XAccessible*>(this)); + for (auto const& listenerCopy : aListenerListCopy) + { + try + { + listenerCopy->disposing (aEvent); + } + catch(const uno::Exception&) + { + // Ignore exceptions. + } + } +} + + +sal_uInt16 ValueSetAcc::getItemCount() const +{ + sal_uInt16 nCount = mpParent->ImplGetVisibleItemCount(); + // When the None-Item is visible then increase the number of items by + // one. + if (HasNoneField()) + nCount += 1; + return nCount; +} + +ValueSetItem* ValueSetAcc::getItem (sal_uInt16 nIndex) const +{ + ValueSetItem* pItem = nullptr; + + if (HasNoneField()) + { + if (nIndex == 0) + // When present the first item is the then always visible none field. + pItem = mpParent->ImplGetItem (VALUESET_ITEM_NONEITEM); + else + // Shift down the index to compensate for the none field. + nIndex -= 1; + } + if (pItem == nullptr) + pItem = mpParent->ImplGetItem (nIndex); + + return pItem; +} + + +void ValueSetAcc::ThrowIfDisposed() +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + SAL_WARN("svx", "Calling disposed object. Throwing exception:"); + throw lang::DisposedException ( + "object has been already disposed", + static_cast<uno::XWeak*>(this)); + } + else + { + DBG_ASSERT (mpParent!=nullptr, "ValueSetAcc not disposed but mpParent == NULL"); + } +} + +bool ValueSetAcc::HasNoneField() const +{ + assert(mpParent && "ValueSetAcc::HasNoneField called with mpParent==NULL"); + return ((mpParent->GetStyle() & WB_NONEFIELD) != 0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/valueimp.hxx b/svtools/source/control/valueimp.hxx new file mode 100644 index 000000000..5f874acb9 --- /dev/null +++ b/svtools/source/control/valueimp.hxx @@ -0,0 +1,255 @@ +/* -*- 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 . + */ + +#pragma once + +#include <osl/mutex.hxx> +#include <tools/color.hxx> +#include <vcl/image.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> + +#include <vector> + +#define VALUESET_ITEM_NONEITEM 0xFFFE + +enum ValueSetItemType +{ + VALUESETITEM_NONE, + VALUESETITEM_IMAGE, + VALUESETITEM_IMAGE_AND_TEXT, + VALUESETITEM_COLOR, + VALUESETITEM_USERDRAW +}; + +class ValueItemAcc; +class ValueSet; + +struct ValueSetItem +{ + ValueSet& mrParent; + sal_uInt16 mnId; + sal_uInt8 meType; + bool mbVisible; + Image maImage; + Color maColor; + OUString maText; + void* mpData; + rtl::Reference< ValueItemAcc > mxAcc; + + explicit ValueSetItem( ValueSet& rParent ); + ~ValueSetItem(); + + css::uno::Reference< css::accessibility::XAccessible > + GetAccessible( bool bIsTransientChildrenDisabled ); +}; + +typedef ::cppu::WeakComponentImplHelper< + css::accessibility::XAccessible, + css::accessibility::XAccessibleEventBroadcaster, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleSelection, + css::lang::XUnoTunnel > + ValueSetAccComponentBase; + +class ValueSetAcc : + public ::cppu::BaseMutex, + public ValueSetAccComponentBase +{ +public: + + explicit ValueSetAcc(ValueSet* pParent); + virtual ~ValueSetAcc() override; + + void FireAccessibleEvent( short nEventId, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue ); + bool HasAccessibleListeners() const { return( mxEventListeners.size() > 0 ); } + + static ValueSetAcc* getImplementation( const css::uno::Reference< css::uno::XInterface >& rxData ) throw(); + +public: + + /** Called by the corresponding ValueSet when it gets the focus. + Stores the new focus state and broadcasts a state change event. + */ + void GetFocus(); + + /** Called by the corresponding ValueSet when it loses the focus. + Stores the new focus state and broadcasts a state change event. + */ + void LoseFocus(); + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XAccessibleSelection + virtual void SAL_CALL selectAccessibleChild( sal_Int32 nChildIndex ) override; + virtual sal_Bool SAL_CALL isAccessibleChildSelected( sal_Int32 nChildIndex ) override; + virtual void SAL_CALL clearAccessibleSelection( ) override; + virtual void SAL_CALL selectAllAccessibleChildren( ) override; + virtual sal_Int32 SAL_CALL getSelectedAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( sal_Int32 nSelectedChildIndex ) override; + virtual void SAL_CALL deselectAccessibleChild( sal_Int32 nSelectedChildIndex ) override; + + // XUnoTunnel + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) override; + +private: + ::std::vector< css::uno::Reference< + css::accessibility::XAccessibleEventListener > > mxEventListeners; + ValueSet* mpParent; + /// The current FOCUSED state. + bool mbIsFocused; + + /** Tell all listeners that the object is dying. This callback is + usually called from the WeakComponentImplHelper class. + */ + virtual void SAL_CALL disposing() override; + + /** Return the number of items. This takes the None-Item into account. + */ + sal_uInt16 getItemCount() const; + + /** Return the item associated with the given index. The None-Item is + taken into account which, when present, is taken to be the first + (with index 0) item. + @param nIndex + Index of the item to return. The index 0 denotes the None-Item + when present. + @return + Returns NULL when the given index is out of range. + */ + ValueSetItem* getItem (sal_uInt16 nIndex) const; + + /** Check whether or not the object has been disposed (or is in the + state of being disposed). If that is the case then + DisposedException is thrown to inform the (indirect) caller of the + foul deed. + @throws css::lang::DisposedException + */ + void ThrowIfDisposed(); + + /** Check whether the value set has a 'none' field, i.e. a field (button) + that deselects any items (selects none of them). + @return + Returns <true/> if there is a 'none' field and <false/> if it is + missing. + */ + bool HasNoneField() const; +}; + +class ValueItemAcc : public ::cppu::WeakImplHelper< css::accessibility::XAccessible, + css::accessibility::XAccessibleEventBroadcaster, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::lang::XUnoTunnel > +{ +private: + + ::std::vector< css::uno::Reference< + css::accessibility::XAccessibleEventListener > > mxEventListeners; + ::osl::Mutex maMutex; + ValueSetItem* mpParent; + bool mbIsTransientChildrenDisabled; + +public: + + ValueItemAcc(ValueSetItem* pParent, bool bIsTransientChildrenDisabled); + virtual ~ValueItemAcc() override; + + void ParentDestroyed(); + + void FireAccessibleEvent( short nEventId, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue ); + + static ValueItemAcc* getImplementation( const css::uno::Reference< css::uno::XInterface >& rxData ) throw(); + +public: + + // XAccessible + virtual css::uno::Reference< css::accessibility::XAccessibleContext > SAL_CALL getAccessibleContext( ) override; + + // XAccessibleEventBroadcaster + virtual void SAL_CALL addAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + virtual void SAL_CALL removeAccessibleEventListener( const css::uno::Reference< css::accessibility::XAccessibleEventListener >& xListener ) override; + + // XAccessibleContext + virtual sal_Int32 SAL_CALL getAccessibleChildCount( ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int32 i ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override; + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent( ) override; + virtual sal_Int16 SAL_CALL getAccessibleRole( ) override; + virtual OUString SAL_CALL getAccessibleDescription( ) override; + virtual OUString SAL_CALL getAccessibleName( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleRelationSet > SAL_CALL getAccessibleRelationSet( ) override; + virtual css::uno::Reference< css::accessibility::XAccessibleStateSet > SAL_CALL getAccessibleStateSet( ) override; + virtual css::lang::Locale SAL_CALL getLocale( ) override; + + // XAccessibleComponent + virtual sal_Bool SAL_CALL containsPoint( const css::awt::Point& aPoint ) override; + virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleAtPoint( const css::awt::Point& aPoint ) override; + virtual css::awt::Rectangle SAL_CALL getBounds( ) override; + virtual css::awt::Point SAL_CALL getLocation( ) override; + virtual css::awt::Point SAL_CALL getLocationOnScreen( ) override; + virtual css::awt::Size SAL_CALL getSize( ) override; + virtual void SAL_CALL grabFocus( ) override; + virtual sal_Int32 SAL_CALL getForeground( ) override; + virtual sal_Int32 SAL_CALL getBackground( ) override; + + // XUnoTunnel + static const css::uno::Sequence< sal_Int8 >& getUnoTunnelId(); + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& rId ) override; +}; + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/svtools/source/control/valueset.cxx b/svtools/source/control/valueset.cxx new file mode 100644 index 000000000..8e679562a --- /dev/null +++ b/svtools/source/control/valueset.cxx @@ -0,0 +1,1942 @@ +/* -*- 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 <sal/config.h> + +#include <o3tl/safeint.hxx> +#include <tools/debug.hxx> +#include <tools/stream.hxx> +#include <comphelper/base64.hxx> +#include <vcl/decoview.hxx> +#include <vcl/event.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/virdev.hxx> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include "valueimp.hxx" + +#include <svtools/valueset.hxx> +#include <boost/property_tree/ptree.hpp> + +using namespace css::uno; +using namespace css::lang; +using namespace css::accessibility; + +namespace +{ + +enum +{ + ITEM_OFFSET = 4, + ITEM_OFFSET_DOUBLE = 6, + NAME_LINE_OFF_X = 2, + NAME_LINE_OFF_Y = 2, + NAME_LINE_HEIGHT = 2, + NAME_OFFSET = 2, + SCRBAR_OFFSET = 1 +}; + +} + +ValueSet::ValueSet(std::unique_ptr<weld::ScrolledWindow> pScrolledWindow) + : maVirDev( VclPtr<VirtualDevice>::Create()) + , mxScrolledWindow(std::move(pScrolledWindow)) + , mnHighItemId(0) + , maColor(COL_TRANSPARENT) + , mnStyle(0) + , mbFormat(true) + , mbHighlight(false) +{ + maVirDev->SetBackground(Application::GetSettings().GetStyleSettings().GetFaceColor()); + + mnItemWidth = 0; + mnItemHeight = 0; + mnTextOffset = 0; + mnVisLines = 0; + mnLines = 0; + mnUserItemWidth = 0; + mnUserItemHeight = 0; + mnFirstLine = 0; + mnSelItemId = 0; + mnSavedItemId = -1; + mnCols = 0; + mnCurCol = 0; + mnUserCols = 0; + mnUserVisLines = 0; + mnSpacing = 0; + mnFrameStyle = DrawFrameStyle::NONE; + mbNoSelection = true; + mbDrawSelection = true; + mbBlackSel = false; + mbDoubleSel = false; + mbScroll = false; + mbFullMode = true; + mbEdgeBlending = false; + mbHasVisibleItems = false; + + if (mxScrolledWindow) + { + mxScrolledWindow->set_user_managed_scrolling(); + mxScrolledWindow->connect_vadjustment_changed(LINK(this, ValueSet, ImplScrollHdl)); + } +} + +void ValueSet::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + CustomWidgetController::SetDrawingArea(pDrawingArea); + // #106446#, #106601# force mirroring of virtual device + maVirDev->EnableRTL(pDrawingArea->get_direction()); +} + +Reference<XAccessible> ValueSet::CreateAccessible() +{ + if (!mxAccessible) + mxAccessible.set(new ValueSetAcc(this)); + return mxAccessible; +} + +ValueSet::~ValueSet() +{ + Reference<XComponent> xComponent(mxAccessible, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + + ImplDeleteItems(); +} + +void ValueSet::ImplDeleteItems() +{ + const size_t n = mItemList.size(); + + for ( size_t i = 0; i < n; ++i ) + { + ValueSetItem* pItem = mItemList[i].get(); + if ( pItem->mbVisible && ImplHasAccessibleListeners() ) + { + Any aOldAny; + Any aNewAny; + + aOldAny <<= pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ); + ImplFireAccessibleEvent(AccessibleEventId::CHILD, aOldAny, aNewAny); + } + + mItemList[i].reset(); + } + + mItemList.clear(); +} + +void ValueSet::Select() +{ + maSelectHdl.Call( this ); +} + +void ValueSet::UserDraw( const UserDrawEvent& ) +{ +} + +size_t ValueSet::ImplGetItem( const Point& rPos ) const +{ + if (!mbHasVisibleItems) + { + return VALUESET_ITEM_NOTFOUND; + } + + if (mpNoneItem && maNoneItemRect.IsInside(rPos)) + { + return VALUESET_ITEM_NONEITEM; + } + + if (maItemListRect.IsInside(rPos)) + { + const int xc = rPos.X() - maItemListRect.Left(); + const int yc = rPos.Y() - maItemListRect.Top(); + // The point is inside the area of item list, + // let's find the containing item. + const int col = xc / (mnItemWidth + mnSpacing); + const int x = xc % (mnItemWidth + mnSpacing); + const int row = yc / (mnItemHeight + mnSpacing); + const int y = yc % (mnItemHeight + mnSpacing); + + if (x < mnItemWidth && y < mnItemHeight) + { + // the point is inside item rect and not inside spacing + const size_t item = (mnFirstLine + row) * static_cast<size_t>(mnCols) + col; + if (item < mItemList.size()) + { + return item; + } + } + } + + return VALUESET_ITEM_NOTFOUND; +} + +ValueSetItem* ValueSet::ImplGetItem( size_t nPos ) +{ + if (nPos == VALUESET_ITEM_NONEITEM) + return mpNoneItem.get(); + else + return (nPos < mItemList.size()) ? mItemList[nPos].get() : nullptr; +} + +ValueSetItem* ValueSet::ImplGetFirstItem() +{ + return !mItemList.empty() ? mItemList[0].get() : nullptr; +} + +sal_uInt16 ValueSet::ImplGetVisibleItemCount() const +{ + sal_uInt16 nRet = 0; + const size_t nItemCount = mItemList.size(); + + for ( size_t n = 0; n < nItemCount; ++n ) + { + if ( mItemList[n]->mbVisible ) + ++nRet; + } + + return nRet; +} + +void ValueSet::ImplFireAccessibleEvent( short nEventId, const Any& rOldValue, const Any& rNewValue ) +{ + ValueSetAcc* pAcc = ValueSetAcc::getImplementation(mxAccessible); + + if( pAcc ) + pAcc->FireAccessibleEvent( nEventId, rOldValue, rNewValue ); +} + +bool ValueSet::ImplHasAccessibleListeners() +{ + ValueSetAcc* pAcc = ValueSetAcc::getImplementation(mxAccessible); + return( pAcc && pAcc->HasAccessibleListeners() ); +} + +IMPL_LINK(ValueSet, ImplScrollHdl, weld::ScrolledWindow&, rScrollWin, void) +{ + auto nNewFirstLine = rScrollWin.vadjustment_get_value(); + if ( nNewFirstLine != mnFirstLine ) + { + mnFirstLine = nNewFirstLine; + mbFormat = true; + Invalidate(); + } +} + +void ValueSet::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + if (GetStyle() & WB_FLATVALUESET) + { + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetFaceColor()); + long nOffY = maVirDev->GetOutputSizePixel().Height(); + Size aWinSize(GetOutputSizePixel()); + rRenderContext.DrawRect(tools::Rectangle(Point(0, nOffY ), Point( aWinSize.Width(), aWinSize.Height()))); + } + + ImplDraw(rRenderContext); +} + +void ValueSet::GetFocus() +{ + SAL_INFO("svtools", "value set getting focus"); + Invalidate(); + CustomWidgetController::GetFocus(); + + // Tell the accessible object that we got the focus. + ValueSetAcc* pAcc = ValueSetAcc::getImplementation(mxAccessible); + if (pAcc) + pAcc->GetFocus(); +} + +void ValueSet::LoseFocus() +{ + SAL_INFO("svtools", "value set losing focus"); + Invalidate(); + CustomWidgetController::LoseFocus(); + + // Tell the accessible object that we lost the focus. + ValueSetAcc* pAcc = ValueSetAcc::getImplementation(mxAccessible); + if( pAcc ) + pAcc->LoseFocus(); +} + +void ValueSet::Resize() +{ + mbFormat = true; + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); + CustomWidgetController::Resize(); +} + +bool ValueSet::KeyInput( const KeyEvent& rKeyEvent ) +{ + size_t nLastItem = mItemList.size(); + + if ( !nLastItem || !ImplGetFirstItem() ) + return CustomWidgetController::KeyInput(rKeyEvent); + + if (mbFormat) + Invalidate(); + + --nLastItem; + + const size_t nCurPos + = mnSelItemId ? GetItemPos(mnSelItemId) : (mpNoneItem ? VALUESET_ITEM_NONEITEM : 0); + size_t nItemPos = VALUESET_ITEM_NOTFOUND; + size_t nVStep = mnCols; + + switch (rKeyEvent.GetKeyCode().GetCode()) + { + case KEY_HOME: + nItemPos = mpNoneItem ? VALUESET_ITEM_NONEITEM : 0; + break; + + case KEY_END: + nItemPos = nLastItem; + break; + + case KEY_LEFT: + if (nCurPos != VALUESET_ITEM_NONEITEM) + { + if (nCurPos) + { + nItemPos = nCurPos-1; + } + else if (mpNoneItem) + { + nItemPos = VALUESET_ITEM_NONEITEM; + } + } + break; + + case KEY_RIGHT: + if (nCurPos < nLastItem) + { + if (nCurPos == VALUESET_ITEM_NONEITEM) + { + nItemPos = 0; + } + else + { + nItemPos = nCurPos+1; + } + } + break; + + case KEY_PAGEUP: + if (rKeyEvent.GetKeyCode().IsShift() || rKeyEvent.GetKeyCode().IsMod1() || rKeyEvent.GetKeyCode().IsMod2()) + { + return CustomWidgetController::KeyInput(rKeyEvent); + } + nVStep *= mnVisLines; + [[fallthrough]]; + case KEY_UP: + if (nCurPos != VALUESET_ITEM_NONEITEM) + { + if (nCurPos == nLastItem) + { + const size_t nCol = mnCols ? nLastItem % mnCols : 0; + if (nCol < mnCurCol) + { + // Move to previous row/page, keeping the old column + nVStep -= mnCurCol - nCol; + } + } + if (nCurPos >= nVStep) + { + // Go up of a whole page + nItemPos = nCurPos-nVStep; + } + else if (mpNoneItem) + { + nItemPos = VALUESET_ITEM_NONEITEM; + } + else if (nCurPos > mnCols) + { + // Go to same column in first row + nItemPos = nCurPos % mnCols; + } + } + break; + + case KEY_PAGEDOWN: + if (rKeyEvent.GetKeyCode().IsShift() || rKeyEvent.GetKeyCode().IsMod1() || rKeyEvent.GetKeyCode().IsMod2()) + { + return CustomWidgetController::KeyInput(rKeyEvent); + } + nVStep *= mnVisLines; + [[fallthrough]]; + case KEY_DOWN: + if (nCurPos != nLastItem) + { + if (nCurPos == VALUESET_ITEM_NONEITEM) + { + nItemPos = nVStep-mnCols+mnCurCol; + } + else + { + nItemPos = nCurPos+nVStep; + } + if (nItemPos > nLastItem) + { + nItemPos = nLastItem; + } + } + break; + + case KEY_RETURN: + if (GetStyle() & WB_NO_DIRECTSELECT) + { + Select(); + break; + } + [[fallthrough]]; + default: + return CustomWidgetController::KeyInput(rKeyEvent); + } + + if ( nItemPos == VALUESET_ITEM_NOTFOUND ) + return true; + + if ( nItemPos!=VALUESET_ITEM_NONEITEM && nItemPos<nLastItem ) + { + // update current column only in case of a new position + // which is also not a "specially" handled one. + mnCurCol = mnCols ? nItemPos % mnCols : 0; + } + const sal_uInt16 nItemId = (nItemPos != VALUESET_ITEM_NONEITEM) ? GetItemId( nItemPos ) : 0; + if ( nItemId != mnSelItemId ) + { + SelectItem( nItemId ); + if (!(GetStyle() & WB_NO_DIRECTSELECT)) + { + // select only if WB_NO_DIRECTSELECT is not set + Select(); + } + } + + return true; +} + +void ValueSet::ImplTracking(const Point& rPos) +{ + ValueSetItem* pItem = ImplGetItem( ImplGetItem( rPos ) ); + if ( pItem ) + { + if( GetStyle() & WB_MENUSTYLEVALUESET || GetStyle() & WB_FLATVALUESET ) + mbHighlight = true; + + ImplHighlightItem( pItem->mnId ); + } + else + { + if( GetStyle() & WB_MENUSTYLEVALUESET || GetStyle() & WB_FLATVALUESET ) + mbHighlight = true; + + ImplHighlightItem( mnSelItemId, false ); + } +} + +bool ValueSet::MouseButtonDown( const MouseEvent& rMouseEvent ) +{ + if ( rMouseEvent.IsLeft() ) + { + ValueSetItem* pItem = ImplGetItem( ImplGetItem( rMouseEvent.GetPosPixel() ) ); + if (pItem && !rMouseEvent.IsMod2()) + { + if (rMouseEvent.GetClicks() == 1) + { + SelectItem( pItem->mnId ); + if (!(GetStyle() & WB_NOPOINTERFOCUS)) + GrabFocus(); + } + else if ( rMouseEvent.GetClicks() == 2 ) + maDoubleClickHdl.Call( this ); + + return true; + } + } + + return CustomWidgetController::MouseButtonDown( rMouseEvent ); +} + +bool ValueSet::MouseButtonUp( const MouseEvent& rMouseEvent ) +{ + if (rMouseEvent.IsLeft() && !rMouseEvent.IsMod2()) + { + Select(); + return true; + } + + return CustomWidgetController::MouseButtonUp( rMouseEvent ); +} + +bool ValueSet::MouseMove(const MouseEvent& rMouseEvent) +{ + // because of SelectionMode + if ((GetStyle() & WB_MENUSTYLEVALUESET) || (GetStyle() & WB_FLATVALUESET)) + ImplTracking(rMouseEvent.GetPosPixel()); + return CustomWidgetController::MouseMove(rMouseEvent); +} + +void ValueSet::QueueReformat() +{ + queue_resize(); + RecalcScrollBar(); + mbFormat = true; + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); +} + +void ValueSet::RemoveItem( sal_uInt16 nItemId ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + if ( nPos < mItemList.size() ) { + mItemList.erase( mItemList.begin() + nPos ); + } + + // reset variables + if (mnHighItemId == nItemId || mnSelItemId == nItemId) + { + mnCurCol = 0; + mnHighItemId = 0; + mnSelItemId = 0; + mbNoSelection = true; + } + + QueueReformat(); +} + +bool ValueSet::TurnOffScrollBar() +{ + if (mxScrolledWindow->get_vpolicy() == VclPolicyType::NEVER) + return false; + mxScrolledWindow->set_vpolicy(VclPolicyType::NEVER); + weld::DrawingArea* pDrawingArea = GetDrawingArea(); + Size aPrefSize(pDrawingArea->get_preferred_size()); + pDrawingArea->set_size_request(aPrefSize.Width() + GetScrollWidth(), aPrefSize.Height()); + return true; +} + +void ValueSet::TurnOnScrollBar() +{ + if (mxScrolledWindow->get_vpolicy() == VclPolicyType::ALWAYS) + return; + mxScrolledWindow->set_vpolicy(VclPolicyType::ALWAYS); + weld::DrawingArea* pDrawingArea = GetDrawingArea(); + Size aPrefSize(pDrawingArea->get_preferred_size()); + pDrawingArea->set_size_request(aPrefSize.Width() - GetScrollWidth(), aPrefSize.Height()); +} + +void ValueSet::RecalcScrollBar() +{ + if (!mxScrolledWindow) + return; + const bool bScrollAllowed = GetStyle() & WB_VSCROLL; + if (!bScrollAllowed) + return; + // reset scrolled window state to initial value so it will get configured + // to the right adjustment on the next format which we toggle on to happen + // if the scrolledwindow wasn't in its initial state already + if (TurnOffScrollBar()) + mbFormat = true; +} + +void ValueSet::Clear() +{ + ImplDeleteItems(); + + // reset variables + mnFirstLine = 0; + mnCurCol = 0; + mnHighItemId = 0; + mnSelItemId = 0; + mbNoSelection = true; + + RecalcScrollBar(); + + mbFormat = true; + if ( IsReallyVisible() && IsUpdateMode() ) + Invalidate(); +} + +size_t ValueSet::GetItemCount() const +{ + return mItemList.size(); +} + +size_t ValueSet::GetItemPos( sal_uInt16 nItemId ) const +{ + for ( size_t i = 0, n = mItemList.size(); i < n; ++i ) { + if ( mItemList[i]->mnId == nItemId ) { + return i; + } + } + return VALUESET_ITEM_NOTFOUND; +} + +sal_uInt16 ValueSet::GetItemId( size_t nPos ) const +{ + return ( nPos < mItemList.size() ) ? mItemList[nPos]->mnId : 0 ; +} + +sal_uInt16 ValueSet::GetItemId( const Point& rPos ) const +{ + size_t nItemPos = ImplGetItem( rPos ); + if ( nItemPos != VALUESET_ITEM_NOTFOUND ) + return GetItemId( nItemPos ); + + return 0; +} + +tools::Rectangle ValueSet::GetItemRect( sal_uInt16 nItemId ) const +{ + const size_t nPos = GetItemPos( nItemId ); + + if ( nPos!=VALUESET_ITEM_NOTFOUND && mItemList[nPos]->mbVisible ) + return ImplGetItemRect( nPos ); + + return tools::Rectangle(); +} + +tools::Rectangle ValueSet::ImplGetItemRect( size_t nPos ) const +{ + const size_t nVisibleBegin = static_cast<size_t>(mnFirstLine)*mnCols; + const size_t nVisibleEnd = nVisibleBegin + static_cast<size_t>(mnVisLines)*mnCols; + + // Check if the item is inside the range of the displayed ones, + // taking into account that last row could be incomplete + if ( nPos<nVisibleBegin || nPos>=nVisibleEnd || nPos>=mItemList.size() ) + return tools::Rectangle(); + + nPos -= nVisibleBegin; + + const size_t row = mnCols ? nPos/mnCols : 0; + const size_t col = mnCols ? nPos%mnCols : 0; + const long x = maItemListRect.Left()+col*(mnItemWidth+mnSpacing); + const long y = maItemListRect.Top()+row*(mnItemHeight+mnSpacing); + + return tools::Rectangle( Point(x, y), Size(mnItemWidth, mnItemHeight) ); +} + +void ValueSet::ImplHighlightItem( sal_uInt16 nItemId, bool bIsSelection ) +{ + if ( mnHighItemId == nItemId ) + return; + + // remember the old item to delete the previous selection + mnHighItemId = nItemId; + + // don't draw the selection if nothing is selected + if ( !bIsSelection && mbNoSelection ) + mbDrawSelection = false; + + // remove the old selection and draw the new one + Invalidate(); + mbDrawSelection = true; +} + +void ValueSet::ImplDraw(vcl::RenderContext& rRenderContext) +{ + if (mbFormat) + Format(rRenderContext); + + Point aDefPos; + Size aSize = maVirDev->GetOutputSizePixel(); + + rRenderContext.DrawOutDev(aDefPos, aSize, aDefPos, aSize, *maVirDev); + + // draw parting line to the Namefield + if (GetStyle() & WB_NAMEFIELD) + { + if (!(GetStyle() & WB_FLATVALUESET)) + { + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + Size aWinSize(GetOutputSizePixel()); + Point aPos1(NAME_LINE_OFF_X, mnTextOffset + NAME_LINE_OFF_Y); + Point aPos2(aWinSize.Width() - (NAME_LINE_OFF_X * 2), mnTextOffset + NAME_LINE_OFF_Y); + if (!(rStyleSettings.GetOptions() & StyleSettingsOptions::Mono)) + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawLine(aPos1, aPos2); + aPos1.AdjustY( 1 ); + aPos2.AdjustY( 1 ); + rRenderContext.SetLineColor(rStyleSettings.GetLightColor()); + } + else + rRenderContext.SetLineColor(rStyleSettings.GetWindowTextColor()); + rRenderContext.DrawLine(aPos1, aPos2); + } + } + + ImplDrawSelect(rRenderContext); +} + +/** + * An inelegant method; sets the item width & height such that + * all of the included items and their labels fit; if we can + * calculate that. + */ +void ValueSet::RecalculateItemSizes() +{ + Size aLargestItem = GetLargestItemSize(); + + if ( mnUserItemWidth != aLargestItem.Width() || + mnUserItemHeight != aLargestItem.Height() ) + { + mnUserItemWidth = aLargestItem.Width(); + mnUserItemHeight = aLargestItem.Height(); + QueueReformat(); + } +} + +void ValueSet::SetFirstLine(sal_uInt16 nNewFirstLine) +{ + if (nNewFirstLine != mnFirstLine) + { + mnFirstLine = nNewFirstLine; + if (mxScrolledWindow) + mxScrolledWindow->vadjustment_set_value(mnFirstLine); + } +} + +void ValueSet::SelectItem( sal_uInt16 nItemId ) +{ + size_t nItemPos = 0; + + if ( nItemId ) + { + nItemPos = GetItemPos( nItemId ); + if ( nItemPos == VALUESET_ITEM_NOTFOUND ) + return; + } + + if ( !((mnSelItemId != nItemId) || mbNoSelection) ) + return; + + const sal_uInt16 nOldItem = mnSelItemId; + mnSelItemId = nItemId; + mbNoSelection = false; + + bool bNewOut = !mbFormat && IsReallyVisible() && IsUpdateMode(); + bool bNewLine = false; + + if (weld::DrawingArea* pNeedsFormatToScroll = !mnCols ? GetDrawingArea() : nullptr) + { + Format(pNeedsFormatToScroll->get_ref_device()); + // reset scrollbar so its set to the later calculated mnFirstLine on + // the next Format + RecalcScrollBar(); // reset scrollbar so its set to the later calculated + } + + // if necessary scroll to the visible area + if (mbScroll && nItemId && mnCols) + { + sal_uInt16 nNewLine = static_cast<sal_uInt16>(nItemPos / mnCols); + if ( nNewLine < mnFirstLine ) + { + SetFirstLine(nNewLine); + bNewLine = true; + } + else if ( nNewLine > o3tl::make_unsigned(mnFirstLine+mnVisLines-1) ) + { + SetFirstLine(static_cast<sal_uInt16>(nNewLine-mnVisLines+1)); + bNewLine = true; + } + } + + if ( bNewOut ) + { + if ( bNewLine ) + { + // redraw everything if the visible area has changed + mbFormat = true; + } + Invalidate(); + } + + if( ImplHasAccessibleListeners() ) + { + // focus event (deselect) + if( nOldItem ) + { + const size_t nPos = GetItemPos( nItemId ); + + if( nPos != VALUESET_ITEM_NOTFOUND ) + { + ValueItemAcc* pItemAcc = ValueItemAcc::getImplementation( + mItemList[nPos]->GetAccessible( false/*bIsTransientChildrenDisabled*/ ) ); + + if( pItemAcc ) + { + Any aOldAny; + Any aNewAny; + aOldAny <<= Reference<XInterface>(static_cast<cppu::OWeakObject*>(pItemAcc)); + ImplFireAccessibleEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldAny, aNewAny ); + } + } + } + + // focus event (select) + const size_t nPos = GetItemPos( mnSelItemId ); + + ValueSetItem* pItem; + if( nPos != VALUESET_ITEM_NOTFOUND ) + pItem = mItemList[nPos].get(); + else + pItem = mpNoneItem.get(); + + ValueItemAcc* pItemAcc = nullptr; + if (pItem != nullptr) + pItemAcc = ValueItemAcc::getImplementation( pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ ) ); + + if( pItemAcc ) + { + Any aOldAny; + Any aNewAny; + aNewAny <<= Reference<XInterface>(static_cast<cppu::OWeakObject*>(pItemAcc)); + ImplFireAccessibleEvent(AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldAny, aNewAny); + } + + // selection event + Any aOldAny; + Any aNewAny; + ImplFireAccessibleEvent(AccessibleEventId::SELECTION_CHANGED, aOldAny, aNewAny); + } +} + +void ValueSet::SetNoSelection() +{ + mbNoSelection = true; + mbHighlight = false; + + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); +} + +void ValueSet::SetStyle(WinBits nStyle) +{ + if (nStyle != mnStyle) + { + mnStyle = nStyle; + mbFormat = true; + Invalidate(); + } +} + +void ValueSet::Format(vcl::RenderContext const & rRenderContext) +{ + Size aWinSize(GetOutputSizePixel()); + size_t nItemCount = mItemList.size(); + WinBits nStyle = GetStyle(); + long nTxtHeight = rRenderContext.GetTextHeight(); + long nOff; + long nNoneHeight; + long nNoneSpace; + + if (mxScrolledWindow && !(nStyle & WB_VSCROLL) && mxScrolledWindow->get_vpolicy() != VclPolicyType::NEVER) + TurnOffScrollBar(); + + // calculate item offset + if (nStyle & WB_ITEMBORDER) + { + if (nStyle & WB_DOUBLEBORDER) + nOff = ITEM_OFFSET_DOUBLE; + else + nOff = ITEM_OFFSET; + } + else + nOff = 0; + + // consider size, if NameField does exist + if (nStyle & WB_NAMEFIELD) + { + mnTextOffset = aWinSize.Height() - nTxtHeight - NAME_OFFSET; + aWinSize.AdjustHeight( -(nTxtHeight + NAME_OFFSET) ); + + if (!(nStyle & WB_FLATVALUESET)) + { + mnTextOffset -= NAME_LINE_HEIGHT + NAME_LINE_OFF_Y; + aWinSize.AdjustHeight( -(NAME_LINE_HEIGHT + NAME_LINE_OFF_Y) ); + } + } + else + mnTextOffset = 0; + + // consider offset and size, if NoneField does exist + if (nStyle & WB_NONEFIELD) + { + nNoneHeight = nTxtHeight + nOff; + nNoneSpace = mnSpacing; + } + else + { + nNoneHeight = 0; + nNoneSpace = 0; + mpNoneItem.reset(); + } + + // calculate number of columns + if (!mnUserCols) + { + if (mnUserItemWidth) + { + mnCols = static_cast<sal_uInt16>((aWinSize.Width() - mnSpacing) / (mnUserItemWidth + mnSpacing)); + if (mnCols <= 0) + mnCols = 1; + } + else + { + mnCols = 1; + } + } + else + { + mnCols = mnUserCols; + } + + // calculate number of rows + mbScroll = false; + + auto nOldLines = mnLines; + // Floor( (M+N-1)/N )==Ceiling( M/N ) + mnLines = (static_cast<long>(nItemCount) + mnCols - 1) / mnCols; + if (mnLines <= 0) + mnLines = 1; + + bool bAdjustmentOutOfDate = nOldLines != mnLines; + + auto nOldVisLines = mnVisLines; + + long nCalcHeight = aWinSize.Height() - nNoneHeight; + if (mnUserVisLines) + { + mnVisLines = mnUserVisLines; + } + else if (mnUserItemHeight) + { + mnVisLines = (nCalcHeight - nNoneSpace + mnSpacing) / (mnUserItemHeight + mnSpacing); + if (!mnVisLines) + mnVisLines = 1; + } + else + { + mnVisLines = mnLines; + } + + bAdjustmentOutOfDate |= nOldVisLines != mnVisLines; + + if (mnLines > mnVisLines) + mbScroll = true; + + if (mnLines <= mnVisLines) + { + SetFirstLine(0); + } + else + { + if (mnFirstLine > o3tl::make_unsigned(mnLines - mnVisLines)) + SetFirstLine(static_cast<sal_uInt16>(mnLines - mnVisLines)); + } + + // calculate item size + const long nColSpace = (mnCols - 1) * static_cast<long>(mnSpacing); + const long nLineSpace = ((mnVisLines - 1) * mnSpacing) + nNoneSpace; + if (mnUserItemWidth && !mnUserCols) + { + mnItemWidth = mnUserItemWidth; + if (mnItemWidth > aWinSize.Width() - nColSpace) + mnItemWidth = aWinSize.Width() - nColSpace; + } + else + mnItemWidth = (aWinSize.Width() - nColSpace) / mnCols; + if (mnUserItemHeight && !mnUserVisLines) + { + mnItemHeight = mnUserItemHeight; + if (mnItemHeight > nCalcHeight - nNoneSpace) + mnItemHeight = nCalcHeight - nNoneSpace; + } + else + { + nCalcHeight -= nLineSpace; + mnItemHeight = nCalcHeight / mnVisLines; + } + + // Init VirDev + maVirDev->SetSettings(rRenderContext.GetSettings()); + maVirDev->SetOutputSizePixel(aWinSize); + + // nothing is changed in case of too small items + if ((mnItemWidth <= 0) || + (mnItemHeight <= ((nStyle & WB_ITEMBORDER) ? 4 : 2)) || + !nItemCount) + { + mbHasVisibleItems = false; + + if ((nStyle & WB_NONEFIELD) && mpNoneItem) + { + mpNoneItem->mbVisible = false; + mpNoneItem->maText = GetText(); + } + + for (size_t i = 0; i < nItemCount; i++) + { + mItemList[i]->mbVisible = false; + } + + if (mxScrolledWindow && mxScrolledWindow->get_vpolicy() != VclPolicyType::NEVER) + TurnOffScrollBar(); + } + else + { + mbHasVisibleItems = true; + + // determine Frame-Style + if (nStyle & WB_DOUBLEBORDER) + mnFrameStyle = DrawFrameStyle::DoubleIn; + else + mnFrameStyle = DrawFrameStyle::In; + + // determine selected color and width + // if necessary change the colors, to make the selection + // better detectable + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + Color aHighColor(rStyleSettings.GetHighlightColor()); + if (((aHighColor.GetRed() > 0x80) || (aHighColor.GetGreen() > 0x80) || + (aHighColor.GetBlue() > 0x80)) || + ((aHighColor.GetRed() == 0x80) && (aHighColor.GetGreen() == 0x80) && + (aHighColor.GetBlue() == 0x80))) + { + mbBlackSel = true; + } + else + { + mbBlackSel = false; + } + // draw the selection with double width if the items are bigger + if ((nStyle & WB_DOUBLEBORDER) && + ((mnItemWidth >= 25) && (mnItemHeight >= 20))) + { + mbDoubleSel = true; + } + else + { + mbDoubleSel = false; + } + + // calculate offsets + long nStartX; + long nStartY; + if (mbFullMode) + { + long nAllItemWidth = (mnItemWidth * mnCols) + nColSpace; + long nAllItemHeight = (mnItemHeight * mnVisLines) + nNoneHeight + nLineSpace; + nStartX = (aWinSize.Width() - nAllItemWidth) / 2; + nStartY = (aWinSize.Height() - nAllItemHeight) / 2; + } + else + { + nStartX = 0; + nStartY = 0; + } + + // calculate and draw items + maVirDev->SetLineColor(); + long x = nStartX; + long y = nStartY; + + // create NoSelection field and show it + if (nStyle & WB_NONEFIELD) + { + if (!mpNoneItem) + mpNoneItem.reset(new ValueSetItem(*this)); + + mpNoneItem->mnId = 0; + mpNoneItem->meType = VALUESETITEM_NONE; + mpNoneItem->mbVisible = true; + maNoneItemRect.SetLeft( x ); + maNoneItemRect.SetTop( y ); + maNoneItemRect.SetRight( maNoneItemRect.Left() + aWinSize.Width() - x - 1 ); + maNoneItemRect.SetBottom( y + nNoneHeight - 1 ); + + ImplFormatItem(rRenderContext, mpNoneItem.get(), maNoneItemRect); + + y += nNoneHeight + nNoneSpace; + } + + // draw items + sal_uLong nFirstItem = static_cast<sal_uLong>(mnFirstLine) * mnCols; + sal_uLong nLastItem = nFirstItem + (mnVisLines * mnCols); + + maItemListRect.SetLeft( x ); + maItemListRect.SetTop( y ); + maItemListRect.SetRight( x + mnCols * (mnItemWidth + mnSpacing) - mnSpacing - 1 ); + maItemListRect.SetBottom( y + mnVisLines * (mnItemHeight + mnSpacing) - mnSpacing - 1 ); + + if (!mbFullMode) + { + // If want also draw parts of items in the last line, + // then we add one more line if parts of these line are + // visible + if (y + (mnVisLines * (mnItemHeight + mnSpacing)) < aWinSize.Height()) + nLastItem += mnCols; + maItemListRect.SetBottom( aWinSize.Height() - y ); + } + for (size_t i = 0; i < nItemCount; i++) + { + ValueSetItem* pItem = mItemList[i].get(); + + if (i >= nFirstItem && i < nLastItem) + { + if (!pItem->mbVisible && ImplHasAccessibleListeners()) + { + Any aOldAny; + Any aNewAny; + + aNewAny <<= pItem->GetAccessible(false/*bIsTransientChildrenDisabled*/); + ImplFireAccessibleEvent(AccessibleEventId::CHILD, aOldAny, aNewAny); + } + + pItem->mbVisible = true; + ImplFormatItem(rRenderContext, pItem, tools::Rectangle(Point(x, y), Size(mnItemWidth, mnItemHeight))); + + if (!((i + 1) % mnCols)) + { + x = nStartX; + y += mnItemHeight + mnSpacing; + } + else + x += mnItemWidth + mnSpacing; + } + else + { + if (pItem->mbVisible && ImplHasAccessibleListeners()) + { + Any aOldAny; + Any aNewAny; + + aOldAny <<= pItem->GetAccessible(false/*bIsTransientChildrenDisabled*/); + ImplFireAccessibleEvent(AccessibleEventId::CHILD, aOldAny, aNewAny); + } + + pItem->mbVisible = false; + } + } + + // arrange ScrollBar, set values and show it + if (mxScrolledWindow && (nStyle & WB_VSCROLL)) + { + bool bTurnScrollbarOn = mxScrolledWindow->get_vpolicy() != VclPolicyType::ALWAYS; + if (bAdjustmentOutOfDate || bTurnScrollbarOn) + { + long nPageSize = mnVisLines; + if (nPageSize < 1) + nPageSize = 1; + mxScrolledWindow->vadjustment_configure(mnFirstLine, 0, mnLines, 1, + mnVisLines, nPageSize); + } + + if (bTurnScrollbarOn) + TurnOnScrollBar(); + } + } + + // waiting for the next since the formatting is finished + mbFormat = false; +} + +void ValueSet::ImplDrawSelect(vcl::RenderContext& rRenderContext) +{ + if (!IsReallyVisible()) + return; + + const bool bFocus = HasFocus(); + const bool bDrawSel = !((mbNoSelection && !mbHighlight) || (!mbDrawSelection && mbHighlight)); + + if (!bFocus && !bDrawSel) + { + ImplDrawItemText(rRenderContext, OUString()); + return; + } + + ImplDrawSelect(rRenderContext, mnSelItemId, bFocus, bDrawSel); + if (mbHighlight) + { + ImplDrawSelect(rRenderContext, mnHighItemId, bFocus, bDrawSel); + } +} + +void ValueSet::ImplDrawSelect(vcl::RenderContext& rRenderContext, sal_uInt16 nItemId, const bool bFocus, const bool bDrawSel ) +{ + ValueSetItem* pItem; + tools::Rectangle aRect; + if (nItemId) + { + const size_t nPos = GetItemPos( nItemId ); + pItem = mItemList[ nPos ].get(); + aRect = ImplGetItemRect( nPos ); + } + else if (mpNoneItem) + { + pItem = mpNoneItem.get(); + aRect = maNoneItemRect; + } + else if (bFocus && (pItem = ImplGetFirstItem())) + { + aRect = ImplGetItemRect(0); + } + else + { + return; + } + + if (!pItem->mbVisible) + return; + + // draw selection + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetFillColor(); + + Color aDoubleColor(rStyleSettings.GetHighlightColor()); + Color aSingleColor(rStyleSettings.GetHighlightTextColor()); + if (!mbDoubleSel) + { + /* + * #99777# contrast enhancement for thin mode + */ + const Wallpaper& rWall = maVirDev->GetBackground(); + if (!rWall.IsBitmap() && ! rWall.IsGradient()) + { + const Color& rBack = rWall.GetColor(); + if (rBack.IsDark() && ! aDoubleColor.IsBright()) + { + aDoubleColor = COL_WHITE; + aSingleColor = COL_BLACK; + } + else if (rBack.IsBright() && !aDoubleColor.IsDark()) + { + aDoubleColor = COL_BLACK; + aSingleColor = COL_WHITE; + } + } + } + + // specify selection output + WinBits nStyle = GetStyle(); + if (nStyle & WB_MENUSTYLEVALUESET) + { + if (bFocus) + DrawFocusRect(rRenderContext, aRect); + if (bDrawSel) + { + rRenderContext.SetLineColor(mbBlackSel ? COL_BLACK : aDoubleColor); + rRenderContext.DrawRect(aRect); + } + } + else + { + if (bDrawSel) + { + rRenderContext.SetLineColor(mbBlackSel ? COL_BLACK : aDoubleColor); + rRenderContext.DrawRect(aRect); + } + if (mbDoubleSel) + { + aRect.AdjustLeft( 1 ); + aRect.AdjustTop( 1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + if (bDrawSel) + rRenderContext.DrawRect(aRect); + } + aRect.AdjustLeft( 1 ); + aRect.AdjustTop( 1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + tools::Rectangle aRect2 = aRect; + aRect.AdjustLeft( 1 ); + aRect.AdjustTop( 1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + if (bDrawSel) + rRenderContext.DrawRect(aRect); + if (mbDoubleSel) + { + aRect.AdjustLeft( 1 ); + aRect.AdjustTop( 1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + if (bDrawSel) + rRenderContext.DrawRect(aRect); + } + + if (bDrawSel) + { + rRenderContext.SetLineColor(mbBlackSel ? COL_WHITE : aSingleColor); + } + else + { + rRenderContext.SetLineColor(COL_LIGHTGRAY); + } + rRenderContext.DrawRect(aRect2); + if (bFocus) + DrawFocusRect(rRenderContext, aRect2); + } + + ImplDrawItemText(rRenderContext, pItem->maText); +} + +void ValueSet::ImplFormatItem(vcl::RenderContext const & rRenderContext, ValueSetItem* pItem, tools::Rectangle aRect) +{ + WinBits nStyle = GetStyle(); + if (nStyle & WB_ITEMBORDER) + { + aRect.AdjustLeft(1 ); + aRect.AdjustTop(1 ); + aRect.AdjustRight( -1 ); + aRect.AdjustBottom( -1 ); + + if (nStyle & WB_FLATVALUESET) + { + sal_Int32 nBorder = (nStyle & WB_DOUBLEBORDER) ? 2 : 1; + + aRect.AdjustLeft(nBorder ); + aRect.AdjustTop(nBorder ); + aRect.AdjustRight( -nBorder ); + aRect.AdjustBottom( -nBorder ); + } + else + { + DecorationView aView(maVirDev.get()); + aRect = aView.DrawFrame(aRect, mnFrameStyle); + } + } + + if (pItem == mpNoneItem.get()) + pItem->maText = GetText(); + + if (!((aRect.GetHeight() > 0) && (aRect.GetWidth() > 0))) + return; + + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + if (pItem == mpNoneItem.get()) + { + maVirDev->SetFont(rRenderContext.GetFont()); + maVirDev->SetTextColor((nStyle & WB_MENUSTYLEVALUESET) ? rStyleSettings.GetMenuTextColor() : rStyleSettings.GetWindowTextColor()); + maVirDev->SetTextFillColor(); + maVirDev->SetFillColor((nStyle & WB_MENUSTYLEVALUESET) ? rStyleSettings.GetMenuColor() : rStyleSettings.GetWindowColor()); + maVirDev->DrawRect(aRect); + Point aTxtPos(aRect.Left() + 2, aRect.Top()); + long nTxtWidth = rRenderContext.GetTextWidth(pItem->maText); + if ((aTxtPos.X() + nTxtWidth) > aRect.Right()) + { + maVirDev->SetClipRegion(vcl::Region(aRect)); + maVirDev->DrawText(aTxtPos, pItem->maText); + maVirDev->SetClipRegion(); + } + else + maVirDev->DrawText(aTxtPos, pItem->maText); + } + else if (pItem->meType == VALUESETITEM_COLOR) + { + maVirDev->SetFillColor(pItem->maColor); + maVirDev->DrawRect(aRect); + } + else + { + if (IsColor()) + maVirDev->SetFillColor(maColor); + else if (nStyle & WB_MENUSTYLEVALUESET) + maVirDev->SetFillColor(rStyleSettings.GetMenuColor()); + else if (IsEnabled()) + maVirDev->SetFillColor(rStyleSettings.GetWindowColor()); + else + maVirDev->SetFillColor(rStyleSettings.GetFaceColor()); + maVirDev->DrawRect(aRect); + + if (pItem->meType == VALUESETITEM_USERDRAW) + { + UserDrawEvent aUDEvt(nullptr, maVirDev.get(), aRect, pItem->mnId); + UserDraw(aUDEvt); + } + else + { + Size aImageSize = pItem->maImage.GetSizePixel(); + Size aRectSize = aRect.GetSize(); + Point aPos(aRect.Left(), aRect.Top()); + aPos.AdjustX((aRectSize.Width() - aImageSize.Width()) / 2 ); + + if (pItem->meType != VALUESETITEM_IMAGE_AND_TEXT) + aPos.AdjustY((aRectSize.Height() - aImageSize.Height()) / 2 ); + + DrawImageFlags nImageStyle = DrawImageFlags::NONE; + if (!IsEnabled()) + nImageStyle |= DrawImageFlags::Disable; + + if (aImageSize.Width() > aRectSize.Width() || + aImageSize.Height() > aRectSize.Height()) + { + maVirDev->SetClipRegion(vcl::Region(aRect)); + maVirDev->DrawImage(aPos, pItem->maImage, nImageStyle); + maVirDev->SetClipRegion(); + } + else + maVirDev->DrawImage(aPos, pItem->maImage, nImageStyle); + + if (pItem->meType == VALUESETITEM_IMAGE_AND_TEXT) + { + maVirDev->SetFont(rRenderContext.GetFont()); + maVirDev->SetTextColor((nStyle & WB_MENUSTYLEVALUESET) ? rStyleSettings.GetMenuTextColor() : rStyleSettings.GetWindowTextColor()); + maVirDev->SetTextFillColor(); + + long nTxtWidth = maVirDev->GetTextWidth(pItem->maText); + + if (nTxtWidth > aRect.GetWidth()) + maVirDev->SetClipRegion(vcl::Region(aRect)); + + maVirDev->DrawText(Point(aRect.Left() + + (aRect.GetWidth() - nTxtWidth) / 2, + aRect.Bottom() - maVirDev->GetTextHeight()), + pItem->maText); + + if (nTxtWidth > aRect.GetWidth()) + maVirDev->SetClipRegion(); + } + } + } + + const sal_uInt16 nEdgeBlendingPercent(GetEdgeBlending() ? rStyleSettings.GetEdgeBlending() : 0); + + if (nEdgeBlendingPercent) + { + const Color& rTopLeft(rStyleSettings.GetEdgeBlendingTopLeftColor()); + const Color& rBottomRight(rStyleSettings.GetEdgeBlendingBottomRightColor()); + const sal_uInt8 nAlpha((nEdgeBlendingPercent * 255) / 100); + const BitmapEx aBlendFrame(createBlendFrame(aRect.GetSize(), nAlpha, rTopLeft, rBottomRight)); + + if (!aBlendFrame.IsEmpty()) + { + maVirDev->DrawBitmapEx(aRect.TopLeft(), aBlendFrame); + } + } +} + +void ValueSet::ImplDrawItemText(vcl::RenderContext& rRenderContext, const OUString& rText) +{ + if (!(GetStyle() & WB_NAMEFIELD)) + return; + + Size aWinSize(GetOutputSizePixel()); + long nTxtWidth = rRenderContext.GetTextWidth(rText); + long nTxtOffset = mnTextOffset; + + // delete rectangle and show text + if (GetStyle() & WB_FLATVALUESET) + { + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rStyleSettings.GetFaceColor()); + rRenderContext.DrawRect(tools::Rectangle(Point(0, nTxtOffset), Point(aWinSize.Width(), aWinSize.Height()))); + rRenderContext.SetTextColor(rStyleSettings.GetButtonTextColor()); + } + else + { + nTxtOffset += NAME_LINE_HEIGHT+NAME_LINE_OFF_Y; + rRenderContext.SetBackground(Application::GetSettings().GetStyleSettings().GetFaceColor()); + rRenderContext.Erase(tools::Rectangle(Point(0, nTxtOffset), Point(aWinSize.Width(), aWinSize.Height()))); + } + rRenderContext.DrawText(Point((aWinSize.Width() - nTxtWidth) / 2, nTxtOffset + (NAME_OFFSET / 2)), rText); +} + +void ValueSet::StyleUpdated() +{ + mbFormat = true; + CustomWidgetController::StyleUpdated(); +} + +void ValueSet::EnableFullItemMode( bool bFullMode ) +{ + mbFullMode = bFullMode; +} + +void ValueSet::SetColCount( sal_uInt16 nNewCols ) +{ + if ( mnUserCols != nNewCols ) + { + mnUserCols = nNewCols; + QueueReformat(); + } +} + +void ValueSet::SetItemImage( sal_uInt16 nItemId, const Image& rImage ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + ValueSetItem* pItem = mItemList[nPos].get(); + pItem->meType = VALUESETITEM_IMAGE; + pItem->maImage = rImage; + + if ( !mbFormat && IsReallyVisible() && IsUpdateMode() ) + { + const tools::Rectangle aRect = ImplGetItemRect(nPos); + Invalidate(aRect); + } + else + mbFormat = true; +} + +void ValueSet::SetItemColor( sal_uInt16 nItemId, const Color& rColor ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + ValueSetItem* pItem = mItemList[nPos].get(); + pItem->meType = VALUESETITEM_COLOR; + pItem->maColor = rColor; + + if ( !mbFormat && IsReallyVisible() && IsUpdateMode() ) + { + const tools::Rectangle aRect = ImplGetItemRect(nPos); + Invalidate( aRect ); + } + else + mbFormat = true; +} + +Color ValueSet::GetItemColor( sal_uInt16 nItemId ) const +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos != VALUESET_ITEM_NOTFOUND ) + return mItemList[nPos]->maColor; + else + return Color(); +} + +Size ValueSet::CalcWindowSizePixel( const Size& rItemSize, sal_uInt16 nDesireCols, + sal_uInt16 nDesireLines ) const +{ + size_t nCalcCols = nDesireCols; + size_t nCalcLines = nDesireLines; + + if ( !nCalcCols ) + { + if ( mnUserCols ) + nCalcCols = mnUserCols; + else + nCalcCols = 1; + } + + if ( !nCalcLines ) + { + nCalcLines = mnVisLines; + + if ( mbFormat ) + { + if ( mnUserVisLines ) + nCalcLines = mnUserVisLines; + else + { + // Floor( (M+N-1)/N )==Ceiling( M/N ) + nCalcLines = (mItemList.size()+nCalcCols-1) / nCalcCols; + if ( !nCalcLines ) + nCalcLines = 1; + } + } + } + + Size aSize( rItemSize.Width() * nCalcCols, rItemSize.Height() * nCalcLines ); + WinBits nStyle = GetStyle(); + long nTxtHeight = GetTextHeight(); + long n; + + if ( nStyle & WB_ITEMBORDER ) + { + if ( nStyle & WB_DOUBLEBORDER ) + n = ITEM_OFFSET_DOUBLE; + else + n = ITEM_OFFSET; + + aSize.AdjustWidth(n * nCalcCols ); + aSize.AdjustHeight(n * nCalcLines ); + } + else + n = 0; + + if ( mnSpacing ) + { + aSize.AdjustWidth(mnSpacing * (nCalcCols - 1) ); + aSize.AdjustHeight(mnSpacing * (nCalcLines - 1) ); + } + + if ( nStyle & WB_NAMEFIELD ) + { + aSize.AdjustHeight(nTxtHeight + NAME_OFFSET ); + if ( !(nStyle & WB_FLATVALUESET) ) + aSize.AdjustHeight(NAME_LINE_HEIGHT + NAME_LINE_OFF_Y ); + } + + if ( nStyle & WB_NONEFIELD ) + { + aSize.AdjustHeight(nTxtHeight + n + mnSpacing ); + } + + return aSize; +} + +void ValueSet::InsertItem( sal_uInt16 nItemId, const Image& rImage ) +{ + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = VALUESETITEM_IMAGE; + pItem->maImage = rImage; + ImplInsertItem( std::move(pItem), VALUESET_APPEND ); +} + +void ValueSet::InsertItem( sal_uInt16 nItemId, const Image& rImage, + const OUString& rText, size_t nPos, + bool bShowLegend ) +{ + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = bShowLegend ? VALUESETITEM_IMAGE_AND_TEXT : VALUESETITEM_IMAGE; + pItem->maImage = rImage; + pItem->maText = rText; + ImplInsertItem( std::move(pItem), nPos ); +} + +void ValueSet::InsertItem( sal_uInt16 nItemId, size_t nPos ) +{ + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = VALUESETITEM_USERDRAW; + ImplInsertItem( std::move(pItem), nPos ); +} + +void ValueSet::InsertItem( sal_uInt16 nItemId, const Color& rColor, + const OUString& rText ) +{ + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = VALUESETITEM_COLOR; + pItem->maColor = rColor; + pItem->maText = rText; + ImplInsertItem( std::move(pItem), VALUESET_APPEND ); +} + +void ValueSet::ImplInsertItem( std::unique_ptr<ValueSetItem> pItem, const size_t nPos ) +{ + DBG_ASSERT( pItem->mnId, "ValueSet::InsertItem(): ItemId == 0" ); + DBG_ASSERT( GetItemPos( pItem->mnId ) == VALUESET_ITEM_NOTFOUND, + "ValueSet::InsertItem(): ItemId already exists" ); + + if ( nPos < mItemList.size() ) { + mItemList.insert( mItemList.begin() + nPos, std::move(pItem) ); + } else { + mItemList.push_back( std::move(pItem) ); + } + + QueueReformat(); +} + +int ValueSet::GetScrollWidth() const +{ + if (mxScrolledWindow) + return mxScrolledWindow->get_vscroll_width(); + return 0; +} + +void ValueSet::SetEdgeBlending(bool bNew) +{ + if(mbEdgeBlending != bNew) + { + mbEdgeBlending = bNew; + mbFormat = true; + + if (GetDrawingArea() && IsReallyVisible() && IsUpdateMode()) + { + Invalidate(); + } + } +} + +Size ValueSet::CalcItemSizePixel( const Size& rItemSize) const +{ + Size aSize = rItemSize; + + WinBits nStyle = GetStyle(); + if ( nStyle & WB_ITEMBORDER ) + { + long n; + + if ( nStyle & WB_DOUBLEBORDER ) + n = ITEM_OFFSET_DOUBLE; + else + n = ITEM_OFFSET; + + aSize.AdjustWidth(n ); + aSize.AdjustHeight(n ); + } + + return aSize; +} + +void ValueSet::SetLineCount( sal_uInt16 nNewLines ) +{ + if ( mnUserVisLines != nNewLines ) + { + mnUserVisLines = nNewLines; + QueueReformat(); + } +} + +void ValueSet::SetItemWidth( long nNewItemWidth ) +{ + if ( mnUserItemWidth != nNewItemWidth ) + { + mnUserItemWidth = nNewItemWidth; + QueueReformat(); + } +} + +//method to set accessible when the style is user draw. +void ValueSet::InsertItem( sal_uInt16 nItemId, const OUString& rText, size_t nPos ) +{ + DBG_ASSERT( nItemId, "ValueSet::InsertItem(): ItemId == 0" ); + DBG_ASSERT( GetItemPos( nItemId ) == VALUESET_ITEM_NOTFOUND, + "ValueSet::InsertItem(): ItemId already exists" ); + std::unique_ptr<ValueSetItem> pItem(new ValueSetItem( *this )); + pItem->mnId = nItemId; + pItem->meType = VALUESETITEM_USERDRAW; + pItem->maText = rText; + ImplInsertItem( std::move(pItem), nPos ); +} + +void ValueSet::SetItemHeight( long nNewItemHeight ) +{ + if ( mnUserItemHeight != nNewItemHeight ) + { + mnUserItemHeight = nNewItemHeight; + QueueReformat(); + } +} + +OUString ValueSet::RequestHelp(tools::Rectangle& rHelpRect) +{ + Point aPos = rHelpRect.TopLeft(); + const size_t nItemPos = ImplGetItem( aPos ); + OUString sRet; + if (nItemPos != VALUESET_ITEM_NOTFOUND) + { + rHelpRect = ImplGetItemRect(nItemPos); + sRet = GetItemText(ImplGetItem(nItemPos)->mnId); + } + return sRet; +} + +OUString ValueSet::GetItemText(sal_uInt16 nItemId) const +{ + const size_t nPos = GetItemPos(nItemId); + + if ( nPos != VALUESET_ITEM_NOTFOUND ) + return mItemList[nPos]->maText; + + return OUString(); +} + +void ValueSet::SetExtraSpacing( sal_uInt16 nNewSpacing ) +{ + if ( GetStyle() & WB_ITEMBORDER ) + { + mnSpacing = nNewSpacing; + QueueReformat(); + } +} + +void ValueSet::SetFormat() +{ + mbFormat = true; +} + +void ValueSet::SetItemData( sal_uInt16 nItemId, void* pData ) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + ValueSetItem* pItem = mItemList[nPos].get(); + pItem->mpData = pData; + + if ( pItem->meType == VALUESETITEM_USERDRAW ) + { + if ( !mbFormat && IsReallyVisible() && IsUpdateMode() ) + { + const tools::Rectangle aRect = ImplGetItemRect(nPos); + Invalidate(aRect); + } + else + mbFormat = true; + } +} + +void* ValueSet::GetItemData( sal_uInt16 nItemId ) const +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos != VALUESET_ITEM_NOTFOUND ) + return mItemList[nPos]->mpData; + else + return nullptr; +} + +void ValueSet::SetItemText(sal_uInt16 nItemId, const OUString& rText) +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos == VALUESET_ITEM_NOTFOUND ) + return; + + ValueSetItem* pItem = mItemList[nPos].get(); + + // Remember old and new name for accessibility event. + Any aOldName; + Any aNewName; + OUString sString (pItem->maText); + aOldName <<= sString; + sString = rText; + aNewName <<= sString; + + pItem->maText = rText; + + if (!mbFormat && IsReallyVisible() && IsUpdateMode()) + { + sal_uInt16 nTempId = mnSelItemId; + + if (mbHighlight) + nTempId = mnHighItemId; + + if (nTempId == nItemId) + Invalidate(); + } + + if (ImplHasAccessibleListeners()) + { + Reference<XAccessible> xAccessible(pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/)); + ValueItemAcc* pValueItemAcc = static_cast<ValueItemAcc*>(xAccessible.get()); + pValueItemAcc->FireAccessibleEvent(AccessibleEventId::NAME_CHANGED, aOldName, aNewName); + } +} + +Size ValueSet::GetLargestItemSize() +{ + Size aLargestItem; + + for (const std::unique_ptr<ValueSetItem>& pItem : mItemList) + { + if (!pItem->mbVisible) + continue; + + if (pItem->meType != VALUESETITEM_IMAGE && + pItem->meType != VALUESETITEM_IMAGE_AND_TEXT) + { + // handle determining an optimal size for this case + continue; + } + + Size aSize = pItem->maImage.GetSizePixel(); + if (pItem->meType == VALUESETITEM_IMAGE_AND_TEXT) + { + aSize.AdjustHeight(3 * NAME_LINE_HEIGHT + + maVirDev->GetTextHeight() ); + aSize.setWidth( std::max(aSize.Width(), + maVirDev->GetTextWidth(pItem->maText) + NAME_OFFSET) ); + } + + aLargestItem.setWidth( std::max(aLargestItem.Width(), aSize.Width()) ); + aLargestItem.setHeight( std::max(aLargestItem.Height(), aSize.Height()) ); + } + + return aLargestItem; +} + +void ValueSet::SetOptimalSize() +{ + Size aLargestSize(GetLargestItemSize()); + aLargestSize.setWidth(std::max(aLargestSize.Width(), mnUserItemWidth)); + aLargestSize.setHeight(std::max(aLargestSize.Height(), mnUserItemHeight)); + Size aPrefSize(CalcWindowSizePixel(aLargestSize)); + GetDrawingArea()->set_size_request(aPrefSize.Width(), aPrefSize.Height()); +} + +Image ValueSet::GetItemImage(sal_uInt16 nItemId) const +{ + size_t nPos = GetItemPos( nItemId ); + + if ( nPos != VALUESET_ITEM_NOTFOUND ) + return mItemList[nPos]->maImage; + else + return Image(); +} + +void ValueSet::SetColor(const Color& rColor) +{ + maColor = rColor; + mbFormat = true; + if (IsReallyVisible() && IsUpdateMode()) + Invalidate(); +} + +void ValueSet::Show() +{ + if (mxScrolledWindow) + mxScrolledWindow->show(); + CustomWidgetController::Show(); +} + +void ValueSet::Hide() +{ + CustomWidgetController::Hide(); + if (mxScrolledWindow) + mxScrolledWindow->hide(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |