summaryrefslogtreecommitdiffstats
path: root/svtools/source/control
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svtools/source/control
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svtools/source/control')
-rw-r--r--svtools/source/control/accessibleruler.cxx354
-rw-r--r--svtools/source/control/accessibleruler.hxx188
-rw-r--r--svtools/source/control/asynclink.cxx71
-rw-r--r--svtools/source/control/collatorres.cxx65
-rw-r--r--svtools/source/control/ctrlbox.cxx1685
-rw-r--r--svtools/source/control/ctrltool.cxx848
-rw-r--r--svtools/source/control/indexentryres.cxx58
-rw-r--r--svtools/source/control/inettbc.cxx1113
-rw-r--r--svtools/source/control/ruler.cxx2778
-rw-r--r--svtools/source/control/scriptedtext.cxx318
-rw-r--r--svtools/source/control/scrolladaptor.cxx125
-rw-r--r--svtools/source/control/tabbar.cxx2544
-rw-r--r--svtools/source/control/toolbarmenu.cxx218
-rw-r--r--svtools/source/control/valueacc.cxx967
-rw-r--r--svtools/source/control/valueimp.hxx245
-rw-r--r--svtools/source/control/valueset.cxx1982
16 files changed, 13559 insertions, 0 deletions
diff --git a/svtools/source/control/accessibleruler.cxx b/svtools/source/control/accessibleruler.cxx
new file mode 100644
index 0000000000..cf7436f2d0
--- /dev/null
+++ b/svtools/source/control/accessibleruler.cxx
@@ -0,0 +1,354 @@
+/* -*- 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 <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <comphelper/accessibleeventnotifier.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <toolkit/helper/convert.hxx>
+#include <utility>
+#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(
+ uno::Reference< XAccessible > xParent, Ruler& rRepr, OUString aName ) :
+
+ SvtRulerAccessible_Base( m_aMutex ),
+ msName(std::move( aName )),
+ mxParent(std::move( xParent )),
+ 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() ).Contains( 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_Int64 SAL_CALL SvtRulerAccessible::getAccessibleChildCount()
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ ThrowExceptionIfNotAlive();
+
+ return 0;
+}
+
+uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleChild( sal_Int64 )
+{
+ uno::Reference< XAccessible > xChild ;
+
+ return xChild;
+}
+
+uno::Reference< XAccessible > SAL_CALL SvtRulerAccessible::getAccessibleParent()
+{
+ return mxParent;
+}
+
+sal_Int64 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_Int64 nChildCount = xParentContext->getAccessibleChildCount();
+ for( sal_Int64 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()
+{
+ 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 >();
+}
+
+
+sal_Int64 SAL_CALL SvtRulerAccessible::getAccessibleStateSet()
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+ sal_Int64 nStateSet = 0;
+
+ if( IsAlive() )
+ {
+ nStateSet |= AccessibleStateType::ENABLED;
+
+ nStateSet |= AccessibleStateType::SHOWING;
+
+ if( isVisible() )
+ nStateSet |= AccessibleStateType::VISIBLE;
+
+ if ( mpRepr->GetStyle() & WB_HORZ )
+ nStateSet |= AccessibleStateType::HORIZONTAL;
+ else
+ nStateSet |= AccessibleStateType::VERTICAL;
+ }
+
+ return nStateSet;
+}
+
+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 0000000000..ba03d0843a
--- /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/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <vcl/vclptr.hxx>
+
+namespace tools { class Rectangle; }
+class Ruler;
+
+
+typedef ::cppu::WeakComponentImplHelper<
+ 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(
+ css::uno::Reference< css::accessibility::XAccessible> xParent, Ruler& rRepresentation, OUString aName );
+
+ /// @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_Int64 SAL_CALL
+ getAccessibleChildCount() override;
+
+ virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL
+ getAccessibleChild( sal_Int64 nIndex ) override;
+
+ virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL
+ getAccessibleParent() override;
+
+ virtual sal_Int64 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 sal_Int64 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 0000000000..95b8a66858
--- /dev/null
+++ b/svtools/source/control/asynclink.cxx
@@ -0,0 +1,71 @@
+/* -*- 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 <tools/debug.hxx>
+#include <vcl/svapp.hxx>
+
+
+namespace svtools {
+
+void AsynchronLink::Call( void* pObj, bool bAllowDoubles )
+{
+ if( !_aLink.IsSet() )
+ return;
+
+ _pArg = pObj;
+ DBG_ASSERT( bAllowDoubles || !_nEventId, "Already made a call" );
+ ClearPendingCall();
+ std::scoped_lock aGuard(_aMutex);
+ _nEventId = Application::PostUserEvent( LINK( this, AsynchronLink, HandleCall_PostUserEvent) );
+}
+
+AsynchronLink::~AsynchronLink()
+{
+ if( _nEventId )
+ {
+ Application::RemoveUserEvent( _nEventId );
+ }
+}
+
+void AsynchronLink::ClearPendingCall()
+{
+ std::scoped_lock aGuard(_aMutex);
+ if( _nEventId )
+ {
+ Application::RemoveUserEvent( _nEventId );
+ _nEventId = nullptr;
+ }
+}
+
+IMPL_LINK_NOARG( AsynchronLink, HandleCall_PostUserEvent, void*, void )
+{
+ {
+ std::scoped_lock aGuard(_aMutex);
+ _nEventId = nullptr;
+ // need to release the lock before calling the client since
+ // the client may call back into us
+ }
+ _aLink.Call( _pArg );
+}
+
+}
+
+/* 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 0000000000..d88113998d
--- /dev/null
+++ b/svtools/source/control/collatorres.cxx
@@ -0,0 +1,65 @@
+/* -*- 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 0000000000..5b06c75400
--- /dev/null
+++ b/svtools/source/control/ctrlbox.cxx
@@ -0,0 +1,1685 @@
+/* -*- 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 <config_folders.h>
+
+#include <comphelper/lok.hxx>
+#include <i18nutil/unicode.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <tools/stream.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 <vcl/weldutils.hxx>
+#include <rtl/math.hxx>
+#include <sal/macros.h>
+#include <sal/log.hxx>
+#include <comphelper/string.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <unotools/syslocale.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 <borderline.hrc>
+
+#include <stdio.h>
+
+#define IMGOUTERTEXTSPACE 5
+#define EXTRAFONTSIZE 5
+#define GAPTOEXTRAPREVIEW 10
+#define MINGAPWIDTH 2
+
+constexpr OUStringLiteral FONTNAMEBOXMRUENTRIESFILE = u"/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 );
+}
+
+tools::Long BorderWidthImpl::GetLine1( tools::Long nWidth ) const
+{
+ tools::Long result = static_cast<tools::Long>(m_nRate1);
+ if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE1 )
+ {
+ tools::Long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2;
+ tools::Long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap;
+ result = std::max<tools::Long>(0,
+ static_cast<tools::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;
+}
+
+tools::Long BorderWidthImpl::GetLine2( tools::Long nWidth ) const
+{
+ tools::Long result = static_cast<tools::Long>(m_nRate2);
+ if ( m_nFlags & BorderWidthImplFlags::CHANGE_LINE2)
+ {
+ tools::Long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1;
+ tools::Long const nConstantD = (m_nFlags & BorderWidthImplFlags::CHANGE_DIST ) ? 0 : m_nRateGap;
+ result = std::max<tools::Long>(0,
+ static_cast<tools::Long>((m_nRate2 * nWidth) + 0.5)
+ - (nConstant1 + nConstantD));
+ }
+ return result;
+}
+
+tools::Long BorderWidthImpl::GetGap( tools::Long nWidth ) const
+{
+ tools::Long result = static_cast<tools::Long>(m_nRateGap);
+ if ( m_nFlags & BorderWidthImplFlags::CHANGE_DIST )
+ {
+ tools::Long const nConstant1 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE1) ? 0 : m_nRate1;
+ tools::Long const nConstant2 = (m_nFlags & BorderWidthImplFlags::CHANGE_LINE2) ? 0 : m_nRate2;
+ result = std::max<tools::Long>(0,
+ static_cast<tools::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( tools::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;
+}
+
+tools::Long BorderWidthImpl::GuessWidth( tools::Long nLine1, tools::Long nLine2, tools::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, tools::Long nWidth, SvxBorderLineStyle nDashing )
+{
+ AntialiasingFlags nOldAA = rDev.GetAntialiasing();
+ rDev.SetAntialiasing( nOldAA & ~AntialiasingFlags::Enable );
+
+ tools::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;
+static int gHighestDPI = 0;
+
+namespace
+{
+ std::vector<VclPtr<VirtualDevice>>& getFontPreviewVirDevs()
+ {
+ return gFontPreviewVirDevs;
+ }
+
+ void clearFontPreviewVirDevs()
+ {
+ for (auto &rDev : gFontPreviewVirDevs)
+ rDev.disposeAndClear();
+ gFontPreviewVirDevs.clear();
+ }
+
+ std::vector<OUString>& getRenderedFontNames()
+ {
+ return gRenderedFontNames;
+ }
+
+ void clearRenderedFontNames()
+ {
+ gRenderedFontNames.clear();
+ }
+
+ void calcCustomItemSize(const weld::ComboBox& rComboBox)
+ {
+ gUserItemSz = Size(rComboBox.get_approximate_digit_width() * 52, rComboBox.get_text_height());
+ gUserItemSz.setHeight(gUserItemSz.Height() * 16);
+ gUserItemSz.setHeight(gUserItemSz.Height() / 10);
+
+ size_t nMaxDeviceHeight = SAL_MAX_INT16 / 16; // see limitXCreatePixmap and be generous wrt up to x16 hidpi
+ assert(gUserItemSz.Height() != 0);
+ gPreviewsPerDevice = gUserItemSz.Height() == 0 ? 16 : nMaxDeviceHeight / gUserItemSz.Height();
+ if (comphelper::LibreOfficeKit::isActive())
+ gPreviewsPerDevice = 1;
+ }
+}
+
+IMPL_LINK(FontNameBox, SettingsChangedHdl, VclSimpleEvent&, rEvent, void)
+{
+ if (rEvent.GetId() != VclEventId::ApplicationDataChanged)
+ return;
+
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ DataChangedEvent* pData = static_cast<DataChangedEvent*>(static_cast<VclWindowEvent&>(rEvent).GetData());
+ if (pData->GetType() == DataChangedEventType::SETTINGS)
+ {
+ clearFontPreviewVirDevs();
+ clearRenderedFontNames();
+ calcCustomItemSize(*m_xComboBox);
+ if (mbWYSIWYG && mpFontList && !mpFontList->empty())
+ {
+ mnPreviewProgress = 0;
+ maUpdateIdle.Start();
+ }
+ }
+}
+
+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));
+
+ Application::AddEventListener(LINK(this, FontNameBox, SettingsChangedHdl));
+}
+
+FontNameBox::~FontNameBox()
+{
+ Application::RemoveEventListener(LINK(this, FontNameBox, SettingsChangedHdl));
+
+ if (mpFontList)
+ {
+ SaveMRUEntries (maFontMRUEntriesFile);
+ ImplDestroyFontList();
+ }
+ --gFontNameBoxes;
+ if (!gFontNameBoxes)
+ {
+ clearFontPreviewVirDevs();
+ clearRenderedFontNames();
+ }
+}
+
+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( "" );
+}
+
+void FontNameBox::LoadMRUEntries( const OUString& aFontMRUEntriesFile )
+{
+ if (aFontMRUEntriesFile.isEmpty())
+ return;
+
+ if (!officecfg::Office::Common::Font::View::ShowFontBoxWYSIWYG::get())
+ 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);
+}
+
+static bool IsRunningUnitTest() { return getenv("LO_TESTNAME") != nullptr; }
+
+void FontNameBox::EnableWYSIWYG(bool bEnable)
+{
+ if (IsRunningUnitTest())
+ return;
+ if (mbWYSIWYG == bEnable)
+ return;
+ mbWYSIWYG = bEnable;
+
+ if (mbWYSIWYG)
+ {
+ calcCustomItemSize(*m_xComboBox);
+ 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_LINK(FontNameBox, CustomGetSizeHdl, OutputDevice&, rDevice, Size)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ calcCustomItemSize(*m_xComboBox);
+ gUserItemSz.setWidth(1.0 * rDevice.GetDPIX() / 96.0 * gUserItemSz.getWidth());
+ gUserItemSz.setHeight(1.0 * rDevice.GetDPIY() / 96.0 * gUserItemSz.getHeight());
+ }
+ return mbWYSIWYG ? gUserItemSz : Size();
+}
+
+namespace
+{
+ tools::Long shrinkFontToFit(OUString const &rSampleText, tools::Long nH, vcl::Font &rFont, OutputDevice &rDevice, tools::Rectangle &rTextRect)
+ {
+ tools::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)
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ return;
+
+ 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.
+ 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(vcl::PushFlags::TEXTCOLOR);
+
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ if (bSelected)
+ rDevice.SetTextColor(rStyleSettings.GetHighlightTextColor());
+ else
+ rDevice.SetTextColor(rStyleSettings.GetDialogTextColor());
+
+ tools::Long nX = rTopLeft.X();
+ tools::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);
+ }
+
+ tools::Long nTextHeight = aTextRect.GetHeight();
+ tools::Long nDesiredGap = (nH-nTextHeight)/2;
+ tools::Long nVertAdjust = nDesiredGap - aTextRect.Top();
+ Point aPos( nX, rTopLeft.Y() + nVertAdjust );
+ rDevice.DrawText(aPos, sFontName);
+ tools::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
+ tools::Long nSpace = rItemSize.Width() - nTextX - IMGOUTERTEXTSPACE;
+ if (nSpace >= 0)
+ {
+ //Make sure it fits in the available height, and get how wide that would be
+ tools::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,
+ sal_Int32 nDPIX, sal_Int32 nDPIY)
+{
+ SolarMutexGuard aGuard;
+ const FontMetric& rFontMetric = (*mpFontList)[nIndex];
+ const OUString& rFontName = rFontMetric.GetFamilyName();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ // we want to cache only best quality previews
+ if (gHighestDPI < nDPIX || gHighestDPI < nDPIY)
+ {
+ clearRenderedFontNames();
+ clearFontPreviewVirDevs();
+ gHighestDPI = std::max(nDPIX, nDPIY);
+ }
+ else if (gHighestDPI > nDPIX || gHighestDPI > nDPIY)
+ {
+ nDPIX = gHighestDPI;
+ nDPIY = gHighestDPI;
+ }
+ }
+
+ size_t nPreviewIndex;
+ auto& rFontNames = getRenderedFontNames();
+ auto& rVirtualDevs = getFontPreviewVirDevs();
+ auto xFind = std::find(rFontNames.begin(), rFontNames.end(), rFontName);
+ bool bPreviewAvailable = xFind != rFontNames.end();
+ if (!bPreviewAvailable)
+ {
+ nPreviewIndex = rFontNames.size();
+ rFontNames.push_back(rFontName);
+ }
+ else
+ nPreviewIndex = std::distance(rFontNames.begin(), xFind);
+
+ size_t nPage = nPreviewIndex / gPreviewsPerDevice;
+ size_t nIndexInPage = nPreviewIndex - (nPage * gPreviewsPerDevice);
+
+ Point aTopLeft(0, gUserItemSz.Height() * nIndexInPage);
+
+ if (!bPreviewAvailable)
+ {
+ if (nPage >= rVirtualDevs.size())
+ {
+ bool bIsLOK = comphelper::LibreOfficeKit::isActive();
+ if (bIsLOK) // allow transparent background in LOK case
+ rVirtualDevs.emplace_back(VclPtr<VirtualDevice>::Create(DeviceFormat::WITH_ALPHA));
+ else
+ rVirtualDevs.emplace_back(m_xComboBox->create_render_virtual_device());
+
+ VirtualDevice& rDevice = *rVirtualDevs.back();
+ rDevice.SetOutputSizePixel(Size(gUserItemSz.Width(), gUserItemSz.Height() * gPreviewsPerDevice));
+ if (bIsLOK)
+ {
+ rDevice.SetDPIX(nDPIX);
+ rDevice.SetDPIY(nDPIY);
+ }
+
+ weld::SetPointFont(rDevice, m_xComboBox->get_font(), bIsLOK);
+ assert(rVirtualDevs.size() == nPage + 1);
+ }
+
+ DrawPreview(rFontMetric, aTopLeft, *rVirtualDevs.back(), false);
+ }
+
+ if (pTopLeft)
+ *pTopLeft = aTopLeft;
+
+ return *rVirtualDevs[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);
+ m_aLivePreviewHdl.Call(rFontMetric);
+ }
+ else
+ {
+ // use cache of unselected entries
+ Point aTopLeft;
+ OutputDevice& rDevice = CachePreview(nIndex, &aTopLeft,
+ rRenderContext.GetDPIX(),
+ rRenderContext.GetDPIY());
+
+ Size aSourceSize = comphelper::LibreOfficeKit::isActive() ? rDevice.GetOutputSizePixel() : gUserItemSz;
+ rRenderContext.DrawOutDev(aDestPoint, gUserItemSz,
+ aTopLeft, aSourceSize,
+ 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( std::u16string_view rName, const FontList* pList )
+{
+ OUString aOldText = m_xComboBox->get_active_text();
+ int nPos = m_xComboBox->get_active();
+
+ m_xComboBox->freeze();
+ 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());
+ }
+ }
+ 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());
+ }
+
+ m_xComboBox->thaw();
+
+ 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);
+ }
+ }
+}
+
+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 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 int* pTempAry;
+ const int* pAry = nullptr;
+
+ 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_uInt32 nCount = aFontSizeNames.Count();
+ for( sal_uInt32 i = 0; i < nCount; i++ )
+ {
+ OUString aSizeName = aFontSizeNames.GetIndexName( i );
+ int 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;
+ }
+
+ m_xComboBox->thaw();
+ set_active_or_entry_text(aStr);
+ m_xComboBox->select_entry_region(nSelectionStart, nSelectionEnd);
+}
+
+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( 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( tools::Long nLine1, tools::Long nLine2, tools::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 );
+ tools::Long nPix = aVirDev->PixelToLogic( Size( 0, 1 ) ).Height();
+ sal_uInt32 n1 = nLine1;
+ sal_uInt32 n2 = nLine2;
+ tools::Long nDist = nDistance;
+ n1 += nPix-1;
+ n1 -= n1%nPix;
+ if ( n2 )
+ {
+ nDist += nPix-1;
+ nDist -= nDist%nPix;
+ n2 += nPix-1;
+ n2 -= n2%nPix;
+ }
+ tools::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 ) );
+}
+
+SvtLineListBox::SvtLineListBox(std::unique_ptr<weld::MenuButton> pControl)
+ : WeldToolbarPopup(css::uno::Reference<css::frame::XFrame>(), pControl.get(), "svt/ui/linewindow.ui", "line_popup_window")
+ , m_xControl(std::move(pControl))
+ , 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)
+{
+ 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_xControl->set_popover(m_xTopLevel.get());
+ m_xControl->connect_toggled(LINK(this, SvtLineListBox, ToggleHdl));
+ m_xControl->connect_style_updated(LINK(this, SvtLineListBox, StyleUpdatedHdl));
+
+ // 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));
+}
+
+void SvtLineListBox::GrabFocus()
+{
+ if (GetSelectEntryStyle() == SvxBorderLineStyle::NONE)
+ m_xNoneButton->grab_focus();
+ else
+ m_xLineSet->GrabFocus();
+}
+
+IMPL_LINK(SvtLineListBox, ToggleHdl, weld::Toggleable&, rButton, void)
+{
+ if (rButton.get_active())
+ GrabFocus();
+}
+
+IMPL_LINK_NOARG(SvtLineListBox, StyleUpdatedHdl, weld::Widget&, void)
+{
+ UpdateEntries();
+ UpdatePreview();
+}
+
+IMPL_LINK_NOARG(SvtLineListBox, NoneHdl, weld::Button&, void)
+{
+ SelectEntry(SvxBorderLineStyle::NONE);
+ ValueSelectHdl(nullptr);
+}
+
+SvtLineListBox::~SvtLineListBox()
+{
+}
+
+OUString SvtLineListBox::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;
+}
+
+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, tools::Long nMinWidth,
+ ColorFunc pColor1Fn, ColorFunc pColor2Fn, ColorDistFunc pColorDistFn )
+{
+ m_vLineList.emplace_back(new ImpLineListData(
+ rWidthImpl, nStyle, nMinWidth, pColor1Fn, pColor2Fn, pColorDistFn));
+}
+
+void SvtLineListBox::UpdateEntries()
+{
+ SvxBorderLineStyle eSelected = GetSelectEntryStyle();
+
+ // Remove the old entries
+ m_xLineSet->Clear();
+
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+ Color aFieldColor = rSettings.GetFieldColor();
+
+ // 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 ),
+ pData->GetColorLine1(aColor),
+ pData->GetColorLine2(aColor),
+ pData->GetColorDist(aColor, aFieldColor),
+ 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();
+}
+
+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(vcl::PushFlags::MAPMODE);
+ aVirDev->SetMapMode(MapMode(MapUnit::MapPixel));
+ const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
+ aVirDev->SetBackground(rSettings.GetFieldColor());
+ aVirDev->Erase();
+ aVirDev->DrawImage(Point(0, nPos), aImage);
+ m_xControl->set_image(aVirDev.get());
+ aVirDev->Pop();
+ }
+}
+
+SvtCalendarBox::SvtCalendarBox(std::unique_ptr<weld::MenuButton> pControl, bool bUseLabel)
+ : m_bUseLabel(bUseLabel)
+ , m_xControl(std::move(pControl))
+ , m_xBuilder(Application::CreateBuilder(m_xControl.get(), "svt/ui/datewindow.ui"))
+ , m_xTopLevel(m_xBuilder->weld_popover("date_popup_window"))
+ , m_xCalendar(m_xBuilder->weld_calendar("date_picker"))
+{
+ 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()
+{
+ if (!m_bUseLabel)
+ return;
+ 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 0000000000..438b229c73
--- /dev/null
+++ b/svtools/source/control/ctrltool.cxx
@@ -0,0 +1,848 @@
+/* -*- 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 <memory>
+#include <string.h>
+#include <string_view>
+
+#include <tools/debug.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <utility>
+#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 <o3tl/string_view.hxx>
+#include <comphelper/lok.hxx>
+
+// Standard fontsizes for scalable Fonts
+const int FontList::aStdSizeAry[] =
+{
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 105,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ 180,
+ 200,
+ 210,
+ 220,
+ 240,
+ 260,
+ 280,
+ 320,
+ 360,
+ 400,
+ 420,
+ 440,
+ 480,
+ 540,
+ 600,
+ 660,
+ 720,
+ 800,
+ 880,
+ 960,
+ 0
+};
+
+namespace {
+
+class ImplFontListFontMetric : public FontMetric
+{
+ friend FontList;
+
+private:
+ ImplFontListFontMetric* mpNext;
+
+public:
+ ImplFontListFontMetric( const FontMetric& rInfo ) :
+ FontMetric( rInfo ), mpNext(nullptr)
+ {
+ }
+
+};
+
+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(OUString aSearchName)
+ : maSearchName(std::move(aSearchName))
+ , 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(std::u16string_view rStr)
+{
+ // check for features before alternate font separator
+ if (size_t nColon = rStr.find(':'); nColon != std::u16string_view::npos)
+ if (size_t nSemiColon = rStr.find(';'); nSemiColon == std::u16string_view::npos || nColon < nSemiColon)
+ return ImplMakeSearchString(OUString(o3tl::getToken(rStr, 0, ':' )));
+ return ImplMakeSearchString(OUString(o3tl::getToken(rStr, 0, ';' )));
+}
+
+ImplFontListNameInfo* FontList::ImplFind(std::u16string_view 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.compare( 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.compare(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.compare(pCompareData->maSearchName);
+ if (nComp > 0)
+ *pIndex = (nMid+1);
+ else
+ *pIndex = nMid;
+ }
+
+ return const_cast<ImplFontListNameInfo*>(pFoundData);
+}
+
+ImplFontListNameInfo* FontList::ImplFindByName(std::u16string_view 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->GetFontFaceCollectionCount();
+ if (n == 0 && comphelper::LibreOfficeKit::isActive())
+ {
+ pDevice->RefreshFontData(true);
+ n = pDevice->GetFontFaceCollectionCount();
+ }
+
+ for (int i = 0; i < n; ++i)
+ {
+ FontMetric aFontMetric = pDevice->GetFontMetricFromCollection( 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 );
+ 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 );
+ 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 == "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);
+ 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);
+ // tdf#147739 medium is not a synonym of normal
+ else if (aCompareStyleName == "medium")
+ aStyleName = SvtResId(STR_SVT_STYLE_MEDIUM);
+ else if (aCompareStyleName == "mediumitalic")
+ aStyleName = SvtResId(STR_SVT_STYLE_MEDIUM_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, std::u16string_view 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, u"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(std::u16string_view 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(std::u16string_view 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;
+}
+
+struct ImplFSNameItem
+{
+ sal_Int32 mnSize;
+ const char* mszUtf8Name;
+};
+
+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::getConfiguredSystemUILanguage();
+
+ 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( std::u16string_view rName ) const
+{
+ if ( mnElem )
+ {
+ OString aName(OUStringToOString(rName,
+ RTL_TEXTENCODING_UTF8));
+
+ // linear search is sufficient for this rare case
+ for( tools::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( tools::Long lower = 0, upper = mnElem - 1; lower <= upper; )
+ {
+ tools::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 0000000000..9420a37b27
--- /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 0000000000..0ffad5414c
--- /dev/null
+++ b/svtools/source/control/inettbc.cxx
@@ -0,0 +1,1113 @@
+/* -*- 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 <comphelper/diagnose_ex.hxx>
+#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/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 <salhelper/thread.hxx>
+#include <tools/debug.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/file.hxx>
+#include <osl/mutex.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 <mutex>
+#include <utility>
+#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(u"",m_aFilters);
+ }
+};
+
+class SvtMatchContext_Impl: public salhelper::Thread
+{
+ std::vector<OUString> aPickList;
+ std::vector<OUString> aCompletions;
+ std::vector<OUString> aURLs;
+ svtools::AsynchronLink aLink;
+ OUString aText;
+ SvtURLBox* pBox;
+ bool bOnlyDirectories;
+ bool bNoSelection;
+
+ std::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, OUString aText );
+ void Stop();
+};
+
+
+namespace
+{
+ ::osl::Mutex& theSvtMatchContextMutex()
+ {
+ static ::osl::Mutex SINGLETON;
+ return SINGLETON;
+ }
+}
+
+SvtMatchContext_Impl::SvtMatchContext_Impl(SvtURLBox* pBoxP, OUString _aText)
+ : Thread( "MatchContext_Impl" )
+ , aLink( LINK( this, SvtMatchContext_Impl, Select_Impl ) )
+ , aText(std::move( _aText ))
+ , pBox( pBoxP )
+ , bOnlyDirectories( pBoxP->bOnlyDirectories )
+ , bNoSelection( pBoxP->bNoSelection )
+ , stopped_(false)
+ , commandId_(0)
+{
+ FillPicklist( aPickList );
+}
+
+SvtMatchContext_Impl::~SvtMatchContext_Impl()
+{
+ aLink.ClearPendingCall();
+}
+
+void SvtMatchContext_Impl::FillPicklist(std::vector<OUString>& rPickList)
+{
+ // Read the history of picks
+ std::vector< SvtHistoryOptions::HistoryItem > seqPicklist = SvtHistoryOptions::GetList( EHistoryType::PickList );
+ sal_uInt32 nCount = seqPicklist.size();
+
+ for( sal_uInt32 nItem=0; nItem < nCount; nItem++ )
+ {
+ INetURLObject aURL;
+ aURL.SetURL( seqPicklist[nItem].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);
+ {
+ std::scoped_lock 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
+ {
+ std::scoped_lock 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;
+
+ try
+ {
+ ResultSetInclude eInclude = INCLUDE_FOLDERS_AND_DOCUMENTS;
+ if ( bOnlyDirectories )
+ eInclude = INCLUDE_FOLDERS_ONLY;
+ uno::Reference< XDynamicResultSet > xDynResultSet = aCnt.createDynamicCursor( { "Title", "IsFolder" }, eInclude );
+
+ uno::Reference < XAnyCompareFactory > xCompare;
+ uno::Reference < XSortedDynamicResultSetFactory > xSRSFac =
+ SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() );
+
+ uno::Reference< XDynamicResultSet > xDynamicResultSet =
+ xSRSFac->createSortedDynamicResultSet( xDynResultSet, { { 2, false }, { 1, true } }, 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() );
+ {
+ // have we been stopped while we were waiting for the mutex?
+ std::scoped_lock 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{
+ { /* Name */ "IsFolder",
+ /* Handle */ -1,
+ /* Type */ cppu::UnoType< bool >::get(),
+ /* Attributes */ {} }
+ };
+ 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 {
+ {
+ std::scoped_lock g(mutex_);
+ processor_ = proc;
+ commandId_ = id;
+ }
+ res = proc->execute(
+ css::ucb::Command(
+ "getPropertyValues", -1,
+ css::uno::Any(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);
+ }
+ {
+ std::scoped_lock 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( u"" );
+ aCurObj.SetParam( u"" );
+ aCurObj.SetURLPath( u"" );
+ 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( u"" );
+ aCurObj.SetParam( u"" );
+ aCurObj.SetURLPath( u"" );
+ 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.subView( 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)
+ : aChangedIdle("svtools::URLBox aChangedIdle")
+ , 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));
+}
+
+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 std::vector< SvtHistoryOptions::HistoryItem > seqPicklist = SvtHistoryOptions::GetList( EHistoryType::PickList );
+ INetURLObject aCurObj;
+
+ for( const SvtHistoryOptions::HistoryItem& rPropertySet : seqPicklist )
+ {
+ aCurObj.SetURL( rPropertySet.sURL );
+
+ if ( !rPropertySet.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() );
+
+ 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();
+ {
+ OUString aFileURL;
+
+ Any aAny = UCBContentHelper::GetProperty(aURL, "CasePreservingURL");
+ 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() );
+
+ // Reset match lists
+ pImpl->aCompletions.clear();
+ pImpl->aURLs.clear();
+
+ aBaseURL = rURL;
+}
+
+void SvtURLBox::DisableHistory()
+{
+ bHistoryDisabled = true;
+ UpdatePicklistForSmartProtocol_Impl();
+}
+
+void SvtURLBox::SetFilter(std::u16string_view _sFilter)
+{
+ pImpl->m_aFilters.clear();
+ FilterMatch::createWildCardFilterList(_sFilter,pImpl->m_aFilters);
+}
+
+void FilterMatch::createWildCardFilterList(std::u16string_view _rFilterList,::std::vector< WildCard >& _rFilters)
+{
+ if( !_rFilterList.empty() )
+ {
+ // filter is given
+ sal_Int32 nIndex = 0;
+ OUString sToken;
+ do
+ {
+ sToken = o3tl::getToken(_rFilterList, 0, ';', nIndex );
+ if ( !sToken.isEmpty() )
+ {
+ _rFilters.emplace_back( sToken.toAsciiUpperCase() );
+ }
+ }
+ while ( nIndex >= 0 );
+ }
+ else
+ {
+ // no filter is given -> match all
+ _rFilters.emplace_back(u"*" );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svtools/source/control/ruler.cxx b/svtools/source/control/ruler.cxx
new file mode 100644
index 0000000000..6ddac0ccd7
--- /dev/null
+++ b/svtools/source/control/ruler.cxx
@@ -0,0 +1,2778 @@
+/* -*- 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 ::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, {}, {}, SalLayoutFlags::GlyphItemsOnly);
+ if (!pLayout)
+ return nullptr;
+
+ // Remember the calculation result.
+ rTextGlyphs = pLayout->GetGlyphs();
+
+ return &rTextGlyphs;
+}
+}
+
+class ImplRulerData
+{
+ friend class Ruler;
+
+private:
+ std::vector<RulerLine> pLines;
+ std::vector<RulerBorder> pBorders;
+ std::vector<RulerIndent> pIndents;
+ std::vector<RulerTab> pTabs;
+
+ tools::Long nNullVirOff;
+ tools::Long nRulVirOff;
+ tools::Long nRulWidth;
+ tools::Long nPageOff;
+ tools::Long nPageWidth;
+ tools::Long nNullOff;
+ tools::Long nMargin1;
+ tools::Long nMargin2;
+ // In this context, "frame margin" means paragraph margins (indents)
+ tools::Long nLeftFrameMargin;
+ tools::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)
+{
+}
+
+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;
+ GetOutDev()->GetTextBoundRect( aRect, "0123456789" );
+ tools::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(*GetOutDev()) ),
+ 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, tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2)
+{
+ if ( nX1 < -RULER_CLIP )
+ {
+ nX1 = -RULER_CLIP;
+ if ( nX2 < -RULER_CLIP )
+ return;
+ }
+ tools::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, tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2)
+{
+ if ( nX1 < -RULER_CLIP )
+ {
+ nX1 = -RULER_CLIP;
+ if ( nX2 < -RULER_CLIP )
+ return;
+ }
+ tools::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, tools::Long nX, tools::Long nY, const OUString& rText, tools::Long nMin, tools::Long nMax)
+{
+ tools::Rectangle aRect;
+ SalLayoutGlyphs* pTextLayout
+ = lcl_GetRulerTextGlyphs(rRenderContext, rText, maTextGlyphs[rText]);
+ rRenderContext.GetTextBoundRect(aRect, rText, 0, 0, -1, 0, {}, {}, pTextLayout);
+
+ tools::Long nShiftX = ( aRect.GetWidth() / 2 ) + aRect.Left();
+ tools::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;
+
+ tools::Long nNullWinOff = mpData->nNullVirOff + mnVirOff;
+ tools::Long nRulX1 = mpData->nRulVirOff + mnVirOff;
+ tools::Long nRulX2 = nRulX1 + mpData->nRulWidth;
+ tools::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 tools::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);
+ GetOutDev()->Invert(aRect);
+ }
+ }
+ mnUpdateFlags = 0;
+}
+
+void Ruler::ImplDrawTicks(vcl::RenderContext& rRenderContext, tools::Long nMin, tools::Long nMax, tools::Long nStart, tools::Long nTop, tools::Long nBottom)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ double nCenter = nTop + ((nBottom - nTop) / 2);
+
+ tools::Long nTickLength3 = (nBottom - nTop) * 0.5;
+ tools::Long nTickLength2 = nTickLength3 * 0.66;
+ tools::Long nTickLength1 = nTickLength2 * 0.66;
+
+ tools::Long nScale = ruler_tab.DPIScaleFactor;
+ tools::Long DPIOffset = nScale - 1;
+
+ double nTick4 = aImplRulerUnitTab[mnUnitIndex].nTick4;
+ double nTick2 = 0;
+ double nTickCount = aImplRulerUnitTab[mnUnitIndex].nTick1 / nScale;
+ double nTickUnit = 0;
+ tools::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_deg10);
+ else
+ aFont.SetOrientation(900_deg10);
+ rRenderContext.SetFont(aFont);
+ nTickWidth = aPixSize.Height();
+ }
+
+ tools::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);
+ tools::Long nTxtWidth = rRenderContext.GetTextWidth( aNumString );
+ const tools::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
+ tools::Long nMulti = 1;
+ tools::Long nOrgTick4 = nTick4;
+
+ while (nTickWidth < nTxtWidth + nTextOff)
+ {
+ tools::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(rStyleSettings.GetShadowColor());
+ }
+
+ if (bNoTicks)
+ return;
+
+ tools::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);
+ tools::Long nTickGap1 = mnWinStyle & WB_HORZ ? nTickGapSize.Width() : nTickGapSize.Height();
+ nTickGapSize = rRenderContext.LogicToPixel(Size(nTick2, nTick2), maMapMode);
+ tools::Long nTickGap2 = mnWinStyle & WB_HORZ ? nTickGapSize.Width() : nTickGapSize.Height();
+ nTickGapSize = rRenderContext.LogicToPixel(Size(nTick3, nTick3), maMapMode);
+ tools::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;
+ rRenderContext.SetFillColor(rStyleSettings.GetShadowColor());
+
+ 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);
+
+ tools::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
+ {
+ tools::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))
+ {
+ tools::Long nT1 = nCenter - (nTickLength / 2.0);
+ tools::Long nT2 = nT1 + nTickLength - 1;
+ tools::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, tools::Long nMin, tools::Long nMax, tools::Long nVirTop, tools::Long nVirBottom)
+{
+ const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
+ tools::Long n;
+ tools::Long n1;
+ tools::Long n2;
+ tools::Long nTemp1;
+ tools::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);
+ tools::Long nTemp3 = nTemp1 + RULER_VAR_SIZE - 1;
+ tools::Long nTemp4 = nTemp2 + RULER_VAR_SIZE - 1;
+ tools::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, tools::Long nMin, tools::Long nMax, tools::Long nVirTop, tools::Long nVirBottom)
+{
+ tools::Long n;
+ tools::Long nIndentHeight = (mnVirHeight / 2) - 1;
+ tools::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 tools::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)
+ {
+ tools::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;
+
+ // A tabstop is drawn using three rectangles
+ tools::Rectangle aRect1; // A horizontal short line
+ tools::Rectangle aRect2; // A vertical short line
+ tools::Rectangle aRect3; // A small square
+
+ 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, tools::Long nMin, tools::Long nMax, tools::Long nVirTop, tools::Long nVirBottom)
+{
+ for (const RulerTab & rTab : mpData->pTabs)
+ {
+ if (rTab.nStyle & RULER_STYLE_INVISIBLE)
+ continue;
+
+ tools::Long aPosition;
+ aPosition = rTab.nPos;
+ aPosition += +mpData->nNullVirOff;
+ tools::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(*GetOutDev(), rStyleSettings.GetDarkShadowColor());
+ SetTextFillColor();
+
+ Color aColor;
+ svtools::ColorConfig aColorConfig;
+ aColor = aColorConfig.GetColorValue(svtools::APPBACKGROUND).nColor;
+ ApplyControlBackground(rRenderContext, aColor);
+ // A hack to get it to change the non-ruler application background to change immediately
+ if (aColor != maVirDev->GetBackground().GetColor())
+ {
+ maVirDev->SetBackground(aColor);
+ Resize();
+ }
+}
+
+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(*GetOutDev(), aFont);
+ }
+
+ if (bForeground || bFont)
+ {
+ ApplyControlForeground(*GetOutDev(), rStyleSettings.GetDarkShadowColor());
+ SetTextFillColor();
+ }
+
+ if (bBackground)
+ {
+ Color aColor;
+ svtools::ColorConfig aColorConfig;
+ aColor = aColorConfig.GetColorValue(svtools::APPBACKGROUND).nColor;
+ ApplyControlBackground(*GetOutDev(), aColor);
+ }
+
+ maVirDev->SetSettings( GetSettings() );
+ maVirDev->SetBackground( GetBackground() );
+ vcl::Font aFont = GetFont();
+
+ if (mnWinStyle & WB_VERT)
+ aFont.SetOrientation(900_deg10);
+
+ 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;
+ tools::Long nRulWinOff = mpData->nRulVirOff+mnVirOff;
+
+ // calculate non-visual part of the page
+ tools::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();
+ tools::Long nP1; // pixel position of Page1
+ tools::Long nP2; // pixel position of Page2
+ tools::Long nM1; // pixel position of Margin1
+ tools::Long nM2; // pixel position of Margin2
+ tools::Long nVirTop; // top/left corner
+ tools::Long nVirBottom; // bottom/right corner
+ tools::Long nVirLeft; // left/top corner
+ tools::Long nVirRight; // right/bottom corner
+ tools::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
+ }
+
+ tools::Long nMin = nVirLeft;
+ tools::Long nMax = nP2;
+ tools::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)
+ {
+ tools::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::ImplDoHitTest( const Point& rPos, RulerSelection* pHitTest,
+ bool bRequireStyle, RulerIndentStyle nRequiredStyle ) const
+{
+ sal_Int32 i;
+ sal_uInt16 nStyle;
+ tools::Long nHitBottom;
+ tools::Long nX;
+ tools::Long nY;
+ tools::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
+ tools::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.Contains( Point( nX, nY ) ) )
+ {
+ pHitTest->eType = RulerType::Tab;
+ pHitTest->nAryPos = i;
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // Indents
+ if ( !mpData->pIndents.empty() )
+ {
+ tools::Long nIndentHeight = (mnVirHeight / 2) - 1;
+ tools::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.Contains( 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;
+ tools::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
+ {
+ tools::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.Contains( 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 ( ImplDoHitTest( 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 ( ImplDoHitTest( 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 ( ImplDoHitTest( 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 )
+{
+ tools::Long nX;
+ tools::Long nY;
+ tools::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.Contains( aMousePos ) )
+ {
+ ExtraDown();
+ }
+ else
+ {
+ RulerSelection aHitTest;
+ bool bHitTestResult = ImplDoHitTest(aMousePos, &aHitTest);
+
+ if ( nMouseClicks == 1 )
+ {
+ if ( bHitTestResult )
+ {
+ ImplStartDrag( &aHitTest, nMouseModifier );
+ }
+ else
+ {
+ // calculate position inside of ruler area
+ if ( aHitTest.eType == RulerType::DontKnow )
+ {
+ mnDragPos = aHitTest.nPos;
+ Click();
+ mnDragPos = 0;
+
+ // call HitTest again as a click, for example, could set a new tab
+ if ( ImplDoHitTest(aMousePos, &aHitTest) )
+ ImplStartDrag(&aHitTest, nMouseModifier);
+ }
+ }
+ }
+ else
+ {
+ if (bHitTestResult)
+ {
+ mnDragPos = aHitTest.nPos;
+ mnDragAryPos = aHitTest.nAryPos;
+ }
+ meDragType = aHitTest.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 (ImplDoHitTest( 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();
+
+ tools::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(*GetOutDev());
+ }
+
+ 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::GetRulerType( const Point& rPos, sal_uInt16* pAryPos )
+{
+ RulerSelection aHitTest;
+
+ // update ruler
+ if ( IsReallyVisible() && mbFormat )
+ {
+ Invalidate(InvalidateFlags::NoErase);
+ }
+
+ (void)ImplDoHitTest(rPos, &aHitTest);
+
+ // return values
+ if ( pAryPos )
+ *pAryPos = aHitTest.nAryPos;
+ return aHitTest.eType;
+}
+
+void Ruler::SetWinPos( tools::Long nNewOff, tools::Long nNewWidth )
+{
+ // should widths be automatically calculated
+ if ( !nNewWidth )
+ mbAutoWinWidth = true;
+ else
+ mbAutoWinWidth = false;
+
+ mnWinOff = nNewOff;
+ mnWinWidth = nNewWidth;
+ ImplUpdate( true );
+}
+
+void Ruler::SetPagePos( tools::Long nNewOff, tools::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( tools::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( tools::Long nPos )
+{
+ if ( mpData->nNullOff != nPos )
+ {
+ mpData->nNullVirOff += nPos - mpData->nNullOff;
+ mpData->nNullOff = nPos;
+ ImplUpdate();
+ }
+}
+
+void Ruler::SetLeftFrameMargin( tools::Long nPos )
+{
+ if ( mpData->nLeftFrameMargin != nPos )
+ {
+ mpData->nLeftFrameMargin = nPos;
+ ImplUpdate();
+ }
+}
+
+void Ruler::SetRightFrameMargin( tools::Long nPos )
+{
+ if ( mpData->nRightFrameMargin != nPos )
+ {
+ mpData->nRightFrameMargin = nPos;
+ ImplUpdate();
+ }
+}
+
+void Ruler::SetMargin1( tools::Long nPos, RulerMarginStyle nMarginStyle )
+{
+ if ( (mpData->nMargin1 != nPos) || (mpData->nMargin1Style != nMarginStyle) )
+ {
+ mpData->nMargin1 = nPos;
+ mpData->nMargin1Style = nMarginStyle;
+ ImplUpdate();
+ }
+}
+
+void Ruler::SetMargin2( tools::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;
+ std::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;
+ std::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(vcl::PushFlags::LINECOLOR | vcl::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 );
+ }
+
+}
+
+tools::Long Ruler::GetPageOffset() const
+{
+ return mpData->nPageOff;
+}
+
+tools::Long Ruler::GetNullOffset() const
+{
+ return mpData->nNullOff;
+}
+
+tools::Long Ruler::GetMargin1() const
+{
+ return mpData->nMargin1;
+}
+
+tools::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);
+ return mxAccContext;
+ }
+ 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 0000000000..e47924687b
--- /dev/null
+++ b/svtools/source/control/scriptedtext.cxx
@@ -0,0 +1,318 @@
+/* -*- 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 ::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.
+
+ std::vector< sal_Int32 > maPosVec; /// The start position of each text portion.
+ std::vector< sal_Int16 > maScriptVec; /// The script type of each text portion.
+ std::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(vcl::PushFlags::FONT | vcl::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 std::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 std::vectors" );
+ DBG_ASSERT( maScriptVec.size() == maWidthVec.size(), "SvtScriptedTextHelper_Impl::DrawText - invalid std::vectors" );
+
+ mrOutDevice.Push(vcl::PushFlags::FONT | vcl::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 ];
+ vcl::Font aFont = GetFont( nScript );
+ mrOutDevice.SetFont( aFont );
+ if (aFont.GetColor() == COL_AUTO)
+ mrOutDevice.SetTextColor( mrOutDevice.GetFillColor().IsDark() ? COL_WHITE : COL_BLACK);
+ 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/scrolladaptor.cxx b/svtools/source/control/scrolladaptor.cxx
new file mode 100644
index 0000000000..6d01e9d414
--- /dev/null
+++ b/svtools/source/control/scrolladaptor.cxx
@@ -0,0 +1,125 @@
+/* -*- 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/scrolladaptor.hxx>
+
+ScrollAdaptor::ScrollAdaptor(vcl::Window* pWin, bool bHoriz)
+ : InterimItemWindow(pWin, "svt/ui/scrollbars.ui", "ScrollBars")
+ , m_xScrollBar(
+ m_xBuilder->weld_scrollbar(bHoriz ? OUString("horizontal") : OUString("vertical")))
+ , m_bHori(bHoriz)
+{
+ m_xScrollBar->show();
+ SetSizePixel(GetOptimalSize());
+}
+
+void ScrollAdaptor::dispose()
+{
+ m_xScrollBar.reset();
+ InterimItemWindow::dispose();
+}
+
+void ScrollAdaptor::SetRange(const Range& rRange)
+{
+ m_xScrollBar->adjustment_set_lower(rRange.Min());
+ m_xScrollBar->adjustment_set_upper(rRange.Max());
+}
+
+Range ScrollAdaptor::GetRange() const
+{
+ return Range(m_xScrollBar->adjustment_get_lower(), m_xScrollBar->adjustment_get_upper());
+}
+
+void ScrollAdaptor::SetRangeMin(tools::Long nNewRange)
+{
+ m_xScrollBar->adjustment_set_lower(nNewRange);
+}
+
+tools::Long ScrollAdaptor::GetRangeMin() const { return m_xScrollBar->adjustment_get_lower(); }
+
+void ScrollAdaptor::SetRangeMax(tools::Long nNewRange)
+{
+ m_xScrollBar->adjustment_set_upper(nNewRange);
+}
+
+tools::Long ScrollAdaptor::GetRangeMax() const { return m_xScrollBar->adjustment_get_upper(); }
+
+void ScrollAdaptor::SetLineSize(tools::Long nNewSize)
+{
+ m_xScrollBar->adjustment_set_step_increment(nNewSize);
+}
+
+tools::Long ScrollAdaptor::GetLineSize() const
+{
+ return m_xScrollBar->adjustment_get_step_increment();
+}
+
+void ScrollAdaptor::SetPageSize(tools::Long nNewSize)
+{
+ m_xScrollBar->adjustment_set_page_increment(nNewSize);
+}
+
+tools::Long ScrollAdaptor::GetPageSize() const
+{
+ return m_xScrollBar->adjustment_get_page_increment();
+}
+
+void ScrollAdaptor::SetVisibleSize(tools::Long nNewSize)
+{
+ m_xScrollBar->adjustment_set_page_size(nNewSize);
+}
+
+tools::Long ScrollAdaptor::GetVisibleSize() const
+{
+ return m_xScrollBar->adjustment_get_page_size();
+}
+
+void ScrollAdaptor::SetThumbPos(tools::Long nThumbPos)
+{
+ m_xScrollBar->adjustment_set_value(nThumbPos);
+}
+
+tools::Long ScrollAdaptor::GetThumbPos() const { return m_xScrollBar->adjustment_get_value(); }
+
+ScrollType ScrollAdaptor::GetScrollType() const { return m_xScrollBar->get_scroll_type(); }
+
+void ScrollAdaptor::EnableRTL(bool bEnable) { m_xScrollBar->set_direction(bEnable); }
+
+void ScrollAdaptor::SetScrollHdl(const Link<weld::Scrollbar&, void>& rLink)
+{
+ m_aLink = rLink;
+ m_xScrollBar->connect_adjustment_changed(rLink);
+}
+
+void ScrollAdaptor::SetMouseReleaseHdl(const Link<const MouseEvent&, bool>& rLink)
+{
+ m_xScrollBar->connect_mouse_release(rLink);
+}
+
+tools::Long ScrollAdaptor::DoScroll(tools::Long nNewPos)
+{
+ const auto nOrig = m_xScrollBar->adjustment_get_value();
+ m_xScrollBar->adjustment_set_value(nNewPos);
+ m_aLink.Call(*m_xScrollBar);
+ return m_xScrollBar->adjustment_get_value() - nOrig;
+}
+
+void ScrollAdaptor::SetThickness(int nThickness) { m_xScrollBar->set_scroll_thickness(nThickness); }
+
+/* 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 0000000000..f9faa2bbe0
--- /dev/null
+++ b/svtools/source/control/tabbar.cxx
@@ -0,0 +1,2544 @@
+/* -*- 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 <utility>
+#include <vcl/InterimItemWindow.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/help.hxx>
+#include <vcl/decoview.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 <vcl/weldutils.hxx>
+#include <svtools/svtresid.hxx>
+#include <svtools/strings.hrc>
+#include <limits>
+#include <memory>
+#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;
+
+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;
+ tools::Long nTextWidth = mrRenderContext.GetTextWidth(aText);
+ tools::Long nTextHeight = mrRenderContext.GetTextHeight();
+ Point aPos = aRect.TopLeft();
+ aPos.AdjustX((aRect.getOpenWidth() - nTextWidth) / 2 );
+ aPos.AdjustY((aRect.getOpenHeight() - 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 (!mbSelected)
+ return;
+
+ // tdf#141396: the color must be different from the rest of the selected tab
+ Color aLineColor = (mbCustomColored && maCustomColor != maSelectedColor)
+ ? maCustomColor
+ : mrStyleSettings.GetDarkShadowColor();
+ mrRenderContext.SetFillColor(aLineColor);
+ mrRenderContext.SetLineColor(aLineColor);
+ mrRenderContext.DrawRect(maLineRect);
+ }
+
+ void drawSeparator()
+ {
+ const tools::Long cMargin = 5;
+ const tools::Long aRight( maRect.Right() - 1 );
+ mrRenderContext.SetLineColor(mrStyleSettings.GetShadowColor());
+ mrRenderContext.DrawLine(Point(aRight, maRect.Top() + cMargin),
+ Point(aRight, maRect.Bottom() - cMargin));
+ }
+
+ void drawTab()
+ {
+ drawOuterFrame();
+ drawColorLine();
+ if (!mbSelected && !mbCustomColored)
+ drawSeparator();
+ if (mbProtect)
+ {
+ BitmapEx aBitmap(BMP_TAB_LOCK);
+ Point aPosition = maRect.TopLeft();
+ aPosition.AdjustX(2);
+ aPosition.AdjustY((maRect.getOpenHeight() - 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;
+ tools::Long mnWidth;
+ OString maHelpId;
+ bool mbShort : 1;
+ bool mbSelect : 1;
+ bool mbProtect : 1;
+ Color maTabBgColor;
+ Color maTabTextColor;
+
+ ImplTabBarItem(sal_uInt16 nItemId, OUString aText, TabBarPageBits nPageBits)
+ : mnId(nItemId)
+ , mnBits(nPageBits)
+ , maText(std::move(aText))
+ , 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 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;
+ tools::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();
+ tools::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 final : public InterimItemWindow
+{
+private:
+ std::unique_ptr<weld::Entry> m_xEntry;
+ Idle maLoseFocusIdle;
+ bool mbPostEvt;
+
+ DECL_LINK( ImplEndEditHdl, void*, void );
+ DECL_LINK( ImplEndTimerHdl, Timer*, void );
+ DECL_LINK( ActivatedHdl, weld::Entry&, bool );
+ DECL_LINK( KeyInputHdl, const KeyEvent&, bool );
+ DECL_LINK( FocusOutHdl, weld::Widget&, void );
+
+public:
+ TabBarEdit(TabBar* pParent);
+ virtual void dispose() override;
+
+ TabBar* GetParent() const { return static_cast<TabBar*>(Window::GetParent()); }
+
+ weld::Entry& get_widget() { return *m_xEntry; }
+
+ void SetPostEvent() { mbPostEvt = true; }
+ void ResetPostEvent() { mbPostEvt = false; }
+};
+
+}
+
+TabBarEdit::TabBarEdit(TabBar* pParent)
+ : InterimItemWindow(pParent, "svt/ui/tabbaredit.ui", "TabBarEdit")
+ , m_xEntry(m_xBuilder->weld_entry("entry"))
+ , maLoseFocusIdle( "svtools::TabBarEdit maLoseFocusIdle" )
+{
+ InitControlBase(m_xEntry.get());
+
+ mbPostEvt = false;
+ maLoseFocusIdle.SetPriority( TaskPriority::REPAINT );
+ maLoseFocusIdle.SetInvokeHandler( LINK( this, TabBarEdit, ImplEndTimerHdl ) );
+
+ m_xEntry->connect_activate(LINK(this, TabBarEdit, ActivatedHdl));
+ m_xEntry->connect_key_press(LINK(this, TabBarEdit, KeyInputHdl));
+ m_xEntry->connect_focus_out(LINK(this, TabBarEdit, FocusOutHdl));
+}
+
+void TabBarEdit::dispose()
+{
+ m_xEntry.reset();
+ InterimItemWindow::dispose();
+}
+
+IMPL_LINK_NOARG(TabBarEdit, ActivatedHdl, weld::Entry&, bool)
+{
+ if ( !mbPostEvt )
+ {
+ if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(false), true ) )
+ mbPostEvt = true;
+ }
+ return true;
+}
+
+IMPL_LINK(TabBarEdit, KeyInputHdl, const KeyEvent&, rKEvt, bool)
+{
+ if (!rKEvt.GetKeyCode().GetModifier() && rKEvt.GetKeyCode().GetCode() == KEY_ESCAPE)
+ {
+ if ( !mbPostEvt )
+ {
+ if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(true), true ) )
+ mbPostEvt = true;
+ }
+ return true;
+ }
+ return false;
+}
+
+IMPL_LINK_NOARG(TabBarEdit, FocusOutHdl, weld::Widget&, void)
+{
+ if ( !mbPostEvt )
+ {
+ if ( PostUserEvent( LINK( this, TabBarEdit, ImplEndEditHdl ), reinterpret_cast<void*>(false), true ) )
+ mbPostEvt = true;
+ }
+}
+
+IMPL_LINK( TabBarEdit, ImplEndEditHdl, void*, pCancel, void )
+{
+ ResetPostEvent();
+ maLoseFocusIdle.Stop();
+
+ // tdf#156958: when renaming and clicking on canvas, LO goes into GetParent()->EndEditMode first time
+ // then it calls TabBarEdit::dispose method which resets m_xEntry BUT, on the same thread, LO comes here again
+ // so return if already disposed to avoid a crash
+ if (isDisposed())
+ return;
+
+ // We need this query, because the edit gets a losefocus event,
+ // when it shows the context menu or the insert symbol dialog
+ if (!m_xEntry->has_focus() && m_xEntry->has_child_focus())
+ maLoseFocusIdle.Start();
+ else
+ GetParent()->EndEditMode( pCancel != nullptr );
+}
+
+IMPL_LINK_NOARG(TabBarEdit, ImplEndTimerHdl, Timer *, void)
+{
+ if (m_xEntry->has_focus())
+ return;
+
+ // We need this query, because the edit gets a losefocus event,
+ // when it shows the context menu or the insert symbol dialog
+ if (m_xEntry->has_child_focus())
+ maLoseFocusIdle.Start();
+ else
+ GetParent()->EndEditMode( true );
+}
+
+namespace {
+
+class TabButtons final : public InterimItemWindow
+{
+public:
+ std::unique_ptr<weld::Button> m_xFirstButton;
+ std::unique_ptr<weld::Button> m_xPrevButton;
+ std::unique_ptr<weld::Button> m_xNextButton;
+ std::unique_ptr<weld::Button> m_xLastButton;
+ std::unique_ptr<weld::Button> m_xAddButton;
+ std::shared_ptr<weld::ButtonPressRepeater> m_xAddRepeater;
+ std::shared_ptr<weld::ButtonPressRepeater> m_xPrevRepeater;
+ std::shared_ptr<weld::ButtonPressRepeater> m_xNextRepeater;
+
+ TabButtons(TabBar* pParent, bool bSheets)
+ : InterimItemWindow(pParent,
+ pParent->IsMirrored() ? OUString("svt/ui/tabbuttonsmirrored.ui")
+ : OUString("svt/ui/tabbuttons.ui"),
+ "TabButtons")
+ , m_xFirstButton(m_xBuilder->weld_button("first"))
+ , m_xPrevButton(m_xBuilder->weld_button("prev"))
+ , m_xNextButton(m_xBuilder->weld_button("next"))
+ , m_xLastButton(m_xBuilder->weld_button("last"))
+ , m_xAddButton(m_xBuilder->weld_button("add"))
+ {
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ SetPaintTransparent(false);
+ SetBackground(rStyleSettings.GetFaceColor());
+
+ m_xFirstButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVET0HOME));
+ m_xPrevButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVELEFT));
+ m_xNextButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVERIGHT));
+ m_xLastButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_MOVETOEND));
+ m_xAddButton->set_accessible_name(SvtResId(STR_TABBAR_PUSHBUTTON_ADDTAB));
+
+ if (bSheets)
+ {
+ m_xFirstButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_MOVETOHOME_SHEETS));
+ m_xPrevButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_MOVELEFT_SHEETS));
+ m_xNextButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_MOVERIGHT_SHEETS));
+ m_xLastButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_MOVETOEND_SHEETS));
+ m_xAddButton->set_tooltip_text(SvtResId(STR_TABBAR_HINT_ADDTAB_SHEETS));
+ }
+ }
+
+ void AdaptToHeight(int nHeight)
+ {
+ if (m_xFirstButton->get_preferred_size() == Size(nHeight, nHeight))
+ return;
+ m_xFirstButton->set_size_request(nHeight, nHeight);
+ m_xPrevButton->set_size_request(nHeight, nHeight);
+ m_xNextButton->set_size_request(nHeight, nHeight);
+ m_xLastButton->set_size_request(nHeight, nHeight);
+ m_xAddButton->set_size_request(nHeight, nHeight);
+ InvalidateChildSizeCache();
+ }
+
+ virtual void dispose() override
+ {
+ m_xNextRepeater.reset();
+ m_xPrevRepeater.reset();
+ m_xAddRepeater.reset();
+ m_xAddButton.reset();
+ m_xLastButton.reset();
+ m_xNextButton.reset();
+ m_xPrevButton.reset();
+ m_xFirstButton.reset();
+ InterimItemWindow::dispose();
+ }
+};
+
+}
+
+struct TabBar_Impl
+{
+ ScopedVclPtr<ImplTabSizer> mpSizer;
+ ScopedVclPtr<TabButtons> mxButtonBox;
+ ScopedVclPtr<TabBarEdit> mxEdit;
+ std::vector<ImplTabBarItem> maItemList;
+
+ vcl::AccessibleFactoryAccess maAccessibleFactory;
+
+ sal_uInt16 getItemSize() const
+ {
+ return static_cast<sal_uInt16>(maItemList.size());
+ }
+};
+
+TabBar::TabBar( vcl::Window* pParent, WinBits nWinStyle, bool bSheets ) :
+ Window( pParent, (nWinStyle & WB_3DLOOK) | WB_CLIPCHILDREN )
+{
+ ImplInit( nWinStyle, bSheets );
+ 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, bool bSheets )
+{
+ 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;
+ mbSheets = bSheets;
+
+ ImplInitControls();
+
+ SetSizePixel( Size( 100, CalcWindowSizePixel().Height() ) );
+ ImplInitSettings( true, true );
+}
+
+ImplTabBarItem* TabBar::seek( size_t i )
+{
+ if ( i < mpImpl->maItemList.size() )
+ {
+ maCurrentItemList = i;
+ return &mpImpl->maItemList[maCurrentItemList];
+ }
+ return nullptr;
+}
+
+ImplTabBarItem* TabBar::prev()
+{
+ if ( maCurrentItemList > 0 )
+ {
+ return &mpImpl->maItemList[--maCurrentItemList];
+ }
+ return nullptr;
+}
+
+ImplTabBarItem* TabBar::next()
+{
+ if ( maCurrentItemList + 1 < mpImpl->maItemList.size() )
+ {
+ return &mpImpl->maItemList[++maCurrentItemList];
+ }
+ 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(*GetOutDev(), 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(*GetOutDev(), 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& rItem : mpImpl->maItemList)
+ {
+ tools::Long nNewWidth = GetTextWidth(rItem.GetRenderText());
+ if (mnCurMaxWidth && (nNewWidth > mnCurMaxWidth))
+ {
+ rItem.mbShort = true;
+ nNewWidth = mnCurMaxWidth;
+ }
+ else
+ {
+ rItem.mbShort = false;
+ }
+
+ // Padding is dependent on font height - bigger font = bigger padding
+ tools::Long nFontWidth = aFont.GetFontHeight();
+ if (rItem.mbProtect)
+ nNewWidth += 24;
+ nNewWidth += nFontWidth * 2;
+
+ if (rItem.mnWidth != nNewWidth)
+ {
+ rItem.mnWidth = nNewWidth;
+ if (!rItem.maRect.IsEmpty())
+ bChanged = true;
+ }
+ }
+ mbSizeFormat = false;
+ mbFormat = true;
+ return bChanged;
+}
+
+void TabBar::ImplFormat()
+{
+ ImplCalcWidth();
+
+ if (!mbFormat)
+ return;
+
+ sal_uInt16 nItemIndex = 0;
+ tools::Long x = mnOffX;
+ for (auto & rItem : mpImpl->maItemList)
+ {
+ // At all non-visible tabs an empty rectangle is set
+ if ((nItemIndex + 1 < mnFirstPos) || (x > mnLastOffX))
+ rItem.maRect.SetEmpty();
+ else
+ {
+ // Slightly before the tab before the first visible page
+ // should also be visible
+ if (nItemIndex + 1 == mnFirstPos)
+ {
+ rItem.maRect.SetLeft(x - rItem.mnWidth);
+ }
+ else
+ {
+ rItem.maRect.SetLeft(x);
+ x += rItem.mnWidth;
+ }
+ rItem.maRect.SetRight(x);
+ rItem.maRect.SetBottom(maWinSize.Height() - 1);
+
+ if (mbMirrored)
+ {
+ tools::Long nNewLeft = mnOffX + mnLastOffX - rItem.maRect.Right();
+ tools::Long nNewRight = mnOffX + mnLastOffX - rItem.maRect.Left();
+ rItem.maRect.SetRight(nNewRight);
+ rItem.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;
+ tools::Long nWinWidth = mnLastOffX - mnOffX - ADDNEWPAGE_AREAWIDTH;
+ tools::Long nWidth = mpImpl->maItemList[nLastFirstPos].mnWidth;
+
+ while (nLastFirstPos && (nWidth < nWinWidth))
+ {
+ nLastFirstPos--;
+ nWidth += mpImpl->maItemList[nLastFirstPos].mnWidth;
+ }
+ if ((nLastFirstPos != static_cast<sal_uInt16>(mpImpl->maItemList.size() - 1)) && (nWidth > nWinWidth))
+ nLastFirstPos++;
+ return nLastFirstPos;
+}
+
+IMPL_LINK(TabBar, ContextMenuHdl, const CommandEvent&, rCommandEvent, void)
+{
+ maScrollAreaContextHdl.Call(rCommandEvent);
+}
+
+IMPL_LINK(TabBar, MousePressHdl, const MouseEvent&, rMouseEvent, bool)
+{
+ if (rMouseEvent.IsRight())
+ ContextMenuHdl(CommandEvent(rMouseEvent.GetPosPixel(), CommandEventId::ContextMenu, true));
+ return false;
+}
+
+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();
+ }
+
+ mpImpl->mxButtonBox.disposeAndReset(VclPtr<TabButtons>::Create(this, mbSheets));
+
+ Link<const CommandEvent&, void> aContextLink = LINK( this, TabBar, ContextMenuHdl );
+
+ if (mnWinStyle & WB_INSERTTAB)
+ {
+ Link<weld::Button&,void> aLink = LINK(this, TabBar, ImplAddClickHandler);
+ mpImpl->mxButtonBox->m_xAddRepeater = std::make_shared<weld::ButtonPressRepeater>(
+ *mpImpl->mxButtonBox->m_xAddButton, aLink, aContextLink);
+ mpImpl->mxButtonBox->m_xAddButton->show();
+ }
+
+ Link<weld::Button&,void> aLink = LINK( this, TabBar, ImplClickHdl );
+
+ if (mnWinStyle & (WB_MINSCROLL | WB_SCROLL))
+ {
+ mpImpl->mxButtonBox->m_xPrevRepeater = std::make_shared<weld::ButtonPressRepeater>(
+ *mpImpl->mxButtonBox->m_xPrevButton, aLink, aContextLink);
+ mpImpl->mxButtonBox->m_xPrevButton->show();
+ mpImpl->mxButtonBox->m_xNextRepeater = std::make_shared<weld::ButtonPressRepeater>(
+ *mpImpl->mxButtonBox->m_xNextButton, aLink, aContextLink);
+ mpImpl->mxButtonBox->m_xNextButton->show();
+ }
+
+ if (mnWinStyle & WB_SCROLL)
+ {
+ Link<const MouseEvent&, bool> aBtnContextLink = LINK(this, TabBar, MousePressHdl);
+
+ mpImpl->mxButtonBox->m_xFirstButton->connect_clicked(aLink);
+ mpImpl->mxButtonBox->m_xFirstButton->connect_mouse_press(aBtnContextLink);
+ mpImpl->mxButtonBox->m_xFirstButton->show();
+ mpImpl->mxButtonBox->m_xLastButton->connect_clicked(aLink);
+ mpImpl->mxButtonBox->m_xLastButton->connect_mouse_press(aBtnContextLink);
+ mpImpl->mxButtonBox->m_xLastButton->show();
+ }
+
+ mpImpl->mxButtonBox->Show();
+}
+
+void TabBar::ImplEnableControls()
+{
+ if (mbSizeFormat || mbFormat)
+ return;
+
+ // enable/disable buttons
+ bool bEnableBtn = mbScrollAlwaysEnabled || mnFirstPos > 0;
+ mpImpl->mxButtonBox->m_xFirstButton->set_sensitive(bEnableBtn);
+ mpImpl->mxButtonBox->m_xPrevButton->set_sensitive(bEnableBtn);
+ if (!bEnableBtn && mpImpl->mxButtonBox->m_xPrevRepeater)
+ mpImpl->mxButtonBox->m_xPrevRepeater->Stop();
+ bEnableBtn = mbScrollAlwaysEnabled || mnFirstPos < ImplGetLastFirstPos();
+ mpImpl->mxButtonBox->m_xLastButton->set_sensitive(bEnableBtn);
+ mpImpl->mxButtonBox->m_xNextButton->set_sensitive(bEnableBtn);
+ if (!bEnableBtn && mpImpl->mxButtonBox->m_xNextRepeater)
+ mpImpl->mxButtonBox->m_xNextRepeater->Stop();
+}
+
+void TabBar::SetScrollAlwaysEnabled(bool bScrollAlwaysEnabled)
+{
+ mbScrollAlwaysEnabled = bScrollAlwaysEnabled;
+ ImplEnableControls();
+}
+
+void TabBar::ImplShowPage( sal_uInt16 nPos )
+{
+ if (nPos >= mpImpl->getItemSize())
+ return;
+
+ // calculate width
+ tools::Long nWidth = GetOutputSizePixel().Width();
+
+ auto& rItem = mpImpl->maItemList[nPos];
+ if (nPos < mnFirstPos)
+ SetFirstPageId( rItem.mnId );
+ else if (rItem.maRect.Right() > nWidth)
+ {
+ while (rItem.maRect.Right() > nWidth)
+ {
+ sal_uInt16 nNewPos = mnFirstPos + 1;
+ SetFirstPageId(GetPageId(nNewPos));
+ ImplFormat();
+ if (nNewPos != mnFirstPos)
+ break;
+ }
+ }
+}
+
+IMPL_LINK( TabBar, ImplClickHdl, weld::Button&, rBtn, void )
+{
+ if (&rBtn != mpImpl->mxButtonBox->m_xFirstButton.get() && &rBtn != mpImpl->mxButtonBox->m_xLastButton.get())
+ {
+ if ((GetPointerState().mnState & (MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT)) == 0)
+ {
+ // like tdf#149482 if we didn't see a mouse up, but find that the mouse is no
+ // longer pressed at this point, then bail
+ mpImpl->mxButtonBox->m_xPrevRepeater->Stop();
+ mpImpl->mxButtonBox->m_xNextRepeater->Stop();
+ return;
+ }
+ }
+
+ EndEditMode();
+
+ sal_uInt16 nNewPos = mnFirstPos;
+
+ if (&rBtn == mpImpl->mxButtonBox->m_xFirstButton.get() || (&rBtn == mpImpl->mxButtonBox->m_xPrevButton.get() &&
+ mpImpl->mxButtonBox->m_xPrevRepeater->IsModKeyPressed()))
+ {
+ nNewPos = 0;
+ }
+ else if (&rBtn == mpImpl->mxButtonBox->m_xLastButton.get() || (&rBtn == mpImpl->mxButtonBox->m_xNextButton.get() &&
+ mpImpl->mxButtonBox->m_xNextRepeater->IsModKeyPressed()))
+ {
+ sal_uInt16 nCount = GetPageCount();
+ if (nCount)
+ nNewPos = nCount - 1;
+ }
+ else if (&rBtn == mpImpl->mxButtonBox->m_xPrevButton.get())
+ {
+ if (mnFirstPos)
+ nNewPos = mnFirstPos - 1;
+ }
+ else if (&rBtn == mpImpl->mxButtonBox->m_xNextButton.get())
+ {
+ sal_uInt16 nCount = GetPageCount();
+ if (mnFirstPos < nCount)
+ nNewPos = mnFirstPos+1;
+ }
+ else
+ {
+ return;
+ }
+
+ if (nNewPos != mnFirstPos)
+ SetFirstPageId(GetPageId(nNewPos));
+}
+
+IMPL_LINK_NOARG(TabBar, ImplAddClickHandler, weld::Button&, void)
+{
+ if ((GetPointerState().mnState & (MOUSE_LEFT | MOUSE_MIDDLE | MOUSE_RIGHT)) == 0)
+ {
+ // tdf#149482 if we didn't see a mouse up, but find that the mouse is no
+ // longer pressed at this point, then bail
+ mpImpl->mxButtonBox->m_xAddRepeater->Stop();
+ return;
+ }
+
+ EndEditMode();
+ 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& rItem = mpImpl->maItemList[n];
+ bSelect = n >= nPos;
+
+ if (rItem.mbSelect != bSelect)
+ {
+ rItem.mbSelect = bSelect;
+ if (!rItem.maRect.IsEmpty())
+ Invalidate(rItem.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& rItem = mpImpl->maItemList[n];
+
+ bSelect = n <= nPos;
+
+ if (rItem.mbSelect != bSelect)
+ {
+ rItem.mbSelect = bSelect;
+ if (!rItem.maRect.IsEmpty())
+ Invalidate(rItem.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& rItem = mpImpl->maItemList[nPos];
+
+ if (!rItem.mbSelect)
+ {
+ // make not valid
+ bool bUpdate = false;
+ if (IsReallyVisible() && IsUpdateMode())
+ bUpdate = true;
+
+ // deselect all selected items
+ for (auto& xItem : mpImpl->maItemList)
+ {
+ 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(vcl::PushFlags::FONT | vcl::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();
+
+ // currently visible sheet is drawn using a bold font
+ if (bCurrent)
+ rRenderContext.SetFont(aFont);
+ else
+ 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();
+
+ tools::Long nSizerWidth = 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
+ tools::Long const nHeight = aNewSize.Height();
+ // adapt font height?
+ ImplInitSettings( true, false );
+
+ mpImpl->mxButtonBox->AdaptToHeight(nHeight);
+ Size const aBtnsSize(mpImpl->mxButtonBox->get_preferred_size().Width(), nHeight);
+ Point aPos(mbMirrored ? (aNewSize.Width() - aBtnsSize.Width()) : 0, 0);
+ mpImpl->mxButtonBox->SetPosSizePixel(aPos, aBtnsSize);
+ auto nButtonWidth = aBtnsSize.Width();
+
+ // 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() == NotifyEventType::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& rItem = mpImpl->maItemList[nPos];
+ if (rItem.mbShort || (rItem.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->maItemList[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->maItemList.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)
+ {
+ bool bIsRTLEnabled = IsRTLEnabled();
+ // reacts on calls of EnableRTL, have to mirror all child controls
+ if (mpImpl->mpSizer)
+ mpImpl->mpSizer->EnableRTL(bIsRTLEnabled);
+ if (mpImpl->mxButtonBox)
+ {
+ mpImpl->mxButtonBox->m_xFirstButton->set_direction(bIsRTLEnabled);
+ mpImpl->mxButtonBox->m_xPrevButton->set_direction(bIsRTLEnabled);
+ mpImpl->mxButtonBox->m_xNextButton->set_direction(bIsRTLEnabled);
+ mpImpl->mxButtonBox->m_xLastButton->set_direction(bIsRTLEnabled);
+ mpImpl->mxButtonBox->m_xAddButton->set_direction(bIsRTLEnabled);
+ }
+ if (mpImpl->mxEdit)
+ {
+ weld::Entry& rEntry = mpImpl->mxEdit->get_widget();
+ rEntry.set_direction(bIsRTLEnabled);
+ }
+ }
+}
+
+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& rItem = mpImpl->maItemList[GetPagePos(mnCurPageId)];
+ if (rItem.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
+ ImplTabBarItem aItem( nPageId, rText, nBits );
+ if (nPos < mpImpl->maItemList.size())
+ {
+ auto it = mpImpl->maItemList.begin();
+ it += nPos;
+ mpImpl->maItemList.insert(it, aItem);
+ }
+ else
+ {
+ mpImpl->maItemList.push_back(aItem);
+ }
+ 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->maItemList[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& rItem = mpImpl->maItemList[nPos];
+ if (aTabBgColor != COL_AUTO)
+ {
+ rItem.maTabBgColor = aTabBgColor;
+ if (aTabBgColor.GetLuminance() <= 128) //Do not use aTabBgColor.IsDark(), because that threshold is way too low...
+ rItem.maTabTextColor = COL_WHITE;
+ else
+ rItem.maTabTextColor = COL_BLACK;
+ }
+ else
+ {
+ rItem.maTabBgColor = COL_AUTO;
+ rItem.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->maItemList.begin();
+ it += nPos;
+ mpImpl->maItemList.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->maItemList.begin();
+ it += nPos;
+ ImplTabBarItem aItem = std::move(*it);
+ mpImpl->maItemList.erase(it);
+ if (nNewPos < mpImpl->maItemList.size())
+ {
+ it = mpImpl->maItemList.begin();
+ it += nNewPos;
+ mpImpl->maItemList.insert(it, aItem);
+ }
+ else
+ {
+ mpImpl->maItemList.push_back(aItem);
+ }
+
+ // redraw bar
+ if (IsReallyVisible() && IsUpdateMode())
+ Invalidate();
+
+ CallEventListeners( VclEventId::TabbarPageMoved, static_cast<void*>(&aPair) );
+}
+
+void TabBar::Clear()
+{
+ // delete all items
+ mpImpl->maItemList.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
+{
+ if (isDisposed())
+ return false;
+ 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& rItem = mpImpl->maItemList[nPos];
+
+ if (rItem.mnBits != nBits)
+ {
+ rItem.mnBits = nBits;
+
+ // redraw bar
+ if (IsReallyVisible() && IsUpdateMode())
+ Invalidate(rItem.maRect);
+ }
+}
+
+TabBarPageBits TabBar::GetPageBits(sal_uInt16 nPageId) const
+{
+ sal_uInt16 nPos = GetPagePos(nPageId);
+
+ if (nPos != PAGE_NOT_FOUND)
+ return mpImpl->maItemList[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->maItemList.size() ? mpImpl->maItemList[nPos].mnId : 0;
+}
+
+sal_uInt16 TabBar::GetPagePos(sal_uInt16 nPageId) const
+{
+ for (size_t i = 0; i < mpImpl->maItemList.size(); ++i)
+ {
+ if (mpImpl->maItemList[i].mnId == nPageId)
+ {
+ return static_cast<sal_uInt16>(i);
+ }
+ }
+ return PAGE_NOT_FOUND;
+}
+
+sal_uInt16 TabBar::GetPageId(const Point& rPos) const
+{
+ for (const auto& rItem : mpImpl->maItemList)
+ {
+ if (rItem.maRect.Contains(rPos))
+ return rItem.mnId;
+ }
+
+ return 0;
+}
+
+tools::Rectangle TabBar::GetPageRect(sal_uInt16 nPageId) const
+{
+ sal_uInt16 nPos = GetPagePos(nPageId);
+
+ if (nPos != PAGE_NOT_FOUND)
+ return mpImpl->maItemList[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& rItem = mpImpl->maItemList[nPos];
+ ImplTabBarItem* pOldItem;
+
+ if (mnCurPageId)
+ pOldItem = &mpImpl->maItemList[GetPagePos(mnCurPageId)];
+ else
+ pOldItem = nullptr;
+
+ // deselect previous page if page was not selected, if this is the
+ // only selected page
+ if (!rItem.mbSelect && pOldItem)
+ {
+ sal_uInt16 nSelPageCount = GetSelectPageCount();
+ if (nSelPageCount == 1)
+ pOldItem->mbSelect = false;
+ rItem.mbSelect = true;
+ }
+
+ mnCurPageId = nPageId;
+ mbFormat = true;
+
+ // assure the actual page becomes visible
+ if (IsReallyVisible())
+ {
+ if (nPos < mnFirstPos)
+ SetFirstPageId(nPageId);
+ else
+ {
+ // calculate visible width
+ tools::Long nWidth = mnLastOffX;
+ if (nWidth > ADDNEWPAGE_AREAWIDTH)
+ nWidth -= ADDNEWPAGE_AREAWIDTH;
+
+ if (rItem.maRect.IsEmpty())
+ ImplFormat();
+
+ while ((mbMirrored ? (rItem.maRect.Left() < mnOffX) : (rItem.maRect.Right() > nWidth)) ||
+ rItem.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(rItem.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& rItem = mpImpl->maItemList[nPos];
+
+ // calculate visible area
+ tools::Long nWidth = mnLastOffX;
+
+ if (mbFormat || rItem.maRect.IsEmpty())
+ {
+ mbFormat = true;
+ ImplFormat();
+ }
+
+ while ((rItem.maRect.Right() > nWidth) ||
+ rItem.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& rItem = mpImpl->maItemList[nPos];
+
+ if (rItem.mbSelect != bSelect)
+ {
+ rItem.mbSelect = bSelect;
+
+ // redraw bar
+ if (IsReallyVisible() && IsUpdateMode())
+ Invalidate(rItem.maRect);
+ }
+}
+
+sal_uInt16 TabBar::GetSelectPageCount() const
+{
+ sal_uInt16 nSelected = 0;
+ for (const auto& rItem : mpImpl->maItemList)
+ {
+ if (rItem.mbSelect)
+ nSelected++;
+ }
+
+ return nSelected;
+}
+
+bool TabBar::IsPageSelected(sal_uInt16 nPageId) const
+{
+ sal_uInt16 nPos = GetPagePos(nPageId);
+ if (nPos != PAGE_NOT_FOUND)
+ return mpImpl->maItemList[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->maItemList[nPos].mbProtect != bProtection)
+ {
+ mpImpl->maItemList[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->mxEdit || (nPos == PAGE_NOT_FOUND) || (mnLastOffX < 8))
+ return false;
+
+ mnEditId = nPageId;
+ if (StartRenaming())
+ {
+ ImplShowPage(nPos);
+ ImplFormat();
+ PaintImmediately();
+
+ mpImpl->mxEdit.disposeAndReset(VclPtr<TabBarEdit>::Create(this));
+ tools::Rectangle aRect = GetPageRect( mnEditId );
+ tools::Long nX = aRect.Left();
+ tools::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();
+ }
+ weld::Entry& rEntry = mpImpl->mxEdit->get_widget();
+ rEntry.set_text(GetPageText(mnEditId));
+ mpImpl->mxEdit->SetPosSizePixel(Point(nX, aRect.Top() + mnOffY + 1), Size(nWidth, aRect.GetHeight() - 3));
+ vcl::Font aFont = GetPointFont(*GetOutDev()); // 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;
+ }
+ rEntry.set_font(aFont);
+ rEntry.set_font_color(aForegroundColor);
+ mpImpl->mxEdit->SetControlBackground(aBackgroundColor);
+ rEntry.grab_focus();
+ rEntry.select_region(0, -1);
+ mpImpl->mxEdit->Show();
+ return true;
+ }
+ else
+ {
+ mnEditId = 0;
+ return false;
+ }
+}
+
+bool TabBar::IsInEditMode() const
+{
+ return bool(mpImpl->mxEdit);
+}
+
+void TabBar::EndEditMode(bool bCancel)
+{
+ if (!mpImpl->mxEdit)
+ return;
+
+ // call hdl
+ bool bEnd = true;
+ mbEditCanceled = bCancel;
+ weld::Entry& rEntry = mpImpl->mxEdit->get_widget();
+ maEditText = rEntry.get_text();
+ mpImpl->mxEdit->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->mxEdit->ResetPostEvent();
+ rEntry.grab_focus();
+ }
+ else
+ {
+ // close edit and call end hdl
+ mpImpl->mxEdit.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(tools::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->maItemList[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->maItemList[nPos].maText;
+ return OUString();
+}
+
+OUString TabBar::GetAuxiliaryText(sal_uInt16 nPageId) const
+{
+ sal_uInt16 nPos = GetPagePos(nPageId);
+ if (nPos != PAGE_NOT_FOUND)
+ return mpImpl->maItemList[nPos].maAuxiliaryText;
+ return OUString();
+}
+
+void TabBar::SetAuxiliaryText(sal_uInt16 nPageId, const OUString& rText )
+{
+ sal_uInt16 nPos = GetPagePos(nPageId);
+ if (nPos != PAGE_NOT_FOUND)
+ {
+ mpImpl->maItemList[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& rItem = mpImpl->maItemList[nPos];
+ if (rItem.maHelpText.isEmpty() && !rItem.maHelpId.isEmpty())
+ {
+ Help* pHelp = Application::GetHelp();
+ if (pHelp)
+ rItem.maHelpText = pHelp->GetHelpText(OStringToOUString(rItem.maHelpId, RTL_TEXTENCODING_UTF8), this);
+ }
+
+ return rItem.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 nNewDropPos;
+ sal_uInt16 nItemCount = mpImpl->getItemSize();
+ sal_Int16 nScroll = 0;
+
+ if (rPos.X() > mnLastOffX-TABBAR_DRAG_SCROLLOFF)
+ {
+ auto& rItem = mpImpl->maItemList[mpImpl->maItemList.size() - 1];
+ if (!rItem.maRect.IsEmpty() && (rPos.X() > rItem.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
+ {
+ sal_uInt16 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());
+ GetOutDev()->SetFillColor(GetBackground().GetColor());
+ GetOutDev()->DrawRect(aRect);
+ Invalidate(aRect);
+ }
+ }
+
+ // draw drop position arrows
+ const StyleSettings& rStyles = Application::GetSettings().GetStyleSettings();
+ const Color aTextColor = rStyles.GetLabelTextColor();
+ tools::Long nX;
+ tools::Long nY = (maWinSize.Height() / 2) - 1;
+ sal_uInt16 nCurPos = GetPagePos(mnCurPageId);
+
+ sal_Int32 nTriangleWidth = 3 * GetDPIScaleFactor();
+
+ if (mnDropPos < nItemCount)
+ {
+ GetOutDev()->SetLineColor(aTextColor);
+ GetOutDev()->SetFillColor(aTextColor);
+
+ auto& rItem = mpImpl->maItemList[mnDropPos];
+ nX = rItem.maRect.Left();
+ if ( mnDropPos == nCurPos )
+ nX--;
+ else
+ nX++;
+
+ if (!rItem.IsDefaultTabBgColor() && !rItem.mbSelect)
+ {
+ GetOutDev()->SetLineColor(rItem.maTabTextColor);
+ GetOutDev()->SetFillColor(rItem.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);
+ GetOutDev()->DrawPolygon(aPoly);
+ }
+ if (mnDropPos > 0 && mnDropPos < nItemCount + 1)
+ {
+ GetOutDev()->SetLineColor(aTextColor);
+ GetOutDev()->SetFillColor(aTextColor);
+
+ auto& rItem = mpImpl->maItemList[mnDropPos - 1];
+ nX = rItem.maRect.Right();
+ if (mnDropPos == nCurPos)
+ nX++;
+ if (!rItem.IsDefaultTabBgColor() && !rItem.mbSelect)
+ {
+ GetOutDev()->SetLineColor(rItem.maTabTextColor);
+ GetOutDev()->SetFillColor(rItem.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);
+ GetOutDev()->DrawPolygon(aPoly);
+ }
+
+ return mnDropPos;
+}
+
+void TabBar::HideDropPos()
+{
+ if (!mbDropPos)
+ return;
+
+ tools::Long nX;
+ tools::Long nY1 = (maWinSize.Height() / 2) - 3;
+ tools::Long nY2 = nY1 + 5;
+ sal_uInt16 nItemCount = mpImpl->getItemSize();
+
+ if (mnDropPos < nItemCount)
+ {
+ auto& rItem = mpImpl->maItemList[mnDropPos];
+ nX = rItem.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 );
+ GetOutDev()->SetClipRegion( aRegion );
+ Invalidate(aRect);
+ GetOutDev()->SetClipRegion();
+ }
+ if (mnDropPos > 0 && mnDropPos < nItemCount + 1)
+ {
+ auto& rItem = mpImpl->maItemList[mnDropPos - 1];
+ nX = rItem.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);
+ GetOutDev()->SetClipRegion(aRegion);
+ Invalidate(aRect);
+ GetOutDev()->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)
+{
+ if (mnWinStyle == nStyle)
+ return;
+ mnWinStyle = nStyle;
+ ImplInitControls();
+ // order possible controls
+ if (IsReallyVisible() && IsUpdateMode())
+ Resize();
+}
+
+Size TabBar::CalcWindowSizePixel() const
+{
+ tools::Long nWidth = 0;
+
+ if (!mpImpl->maItemList.empty())
+ {
+ const_cast<TabBar*>(this)->ImplCalcWidth();
+ for (const auto& rItem : mpImpl->maItemList)
+ {
+ nWidth += rItem.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));
+}
+
+void TabBar::SetAddButtonEnabled(bool bAddButtonEnabled)
+{
+ mpImpl->mxButtonBox->m_xAddButton->set_sensitive(bAddButtonEnabled);
+}
+
+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 0000000000..92519ad356
--- /dev/null
+++ b/svtools/source/control/toolbarmenu.cxx
@@ -0,0 +1,218 @@
+/* -*- 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/lok.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <utility>
+#include <vcl/taskpanelist.hxx>
+#include <vcl/svapp.hxx>
+
+#include <framestatuslistener.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 {
+
+SystemWindow* GetTopMostParentSystemWindow(const vcl::Window& rWindow)
+{
+ // ->manually search topmost system window
+ // required because their might be another system window between this and the top window
+ vcl::Window* pWindow = rWindow.GetParent();
+ SystemWindow* pTopMostSysWin = nullptr;
+ while ( pWindow )
+ {
+ if ( pWindow->IsSystemWindow() )
+ pTopMostSysWin = static_cast<SystemWindow*>(pWindow);
+ pWindow = pWindow->GetParent();
+ }
+ return pTopMostSysWin;
+}
+
+class ToolbarPopupStatusListener : public svt::FrameStatusListener
+{
+public:
+ ToolbarPopupStatusListener( const css::uno::Reference< css::frame::XFrame >& xFrame,
+ WeldToolbarPopup& rToolbarPopup );
+
+ virtual void SAL_CALL dispose() override;
+ virtual void SAL_CALL statusChanged( const css::frame::FeatureStateEvent& Event ) override;
+
+ WeldToolbarPopup* mpPopup;
+};
+
+
+ToolbarPopupStatusListener::ToolbarPopupStatusListener(
+ const css::uno::Reference< css::frame::XFrame >& xFrame,
+ WeldToolbarPopup& 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 );
+}
+
+}
+
+void WeldToolbarPopup::AddStatusListener(const OUString& rCommandURL)
+{
+ if (!m_xStatusListener.is())
+ m_xStatusListener.set(new ToolbarPopupStatusListener(m_xFrame, *this));
+
+ m_xStatusListener->addStatusListener(rCommandURL);
+}
+
+void WeldToolbarPopup::statusChanged(const css::frame::FeatureStateEvent& /*Event*/)
+{
+}
+
+void InterimToolbarPopup::EndPopupMode()
+{
+ GetDockingManager()->EndPopupMode(this);
+}
+
+WeldToolbarPopup::WeldToolbarPopup(css::uno::Reference<css::frame::XFrame> xFrame,
+ weld::Widget* pParent, const OUString& rUIFile,
+ const OUString& rId)
+ : m_xBuilder(Application::CreateBuilder(pParent, rUIFile))
+ , m_xTopLevel(m_xBuilder->weld_popover(rId))
+ , m_xContainer(m_xBuilder->weld_container("container"))
+ , m_xFrame(std::move(xFrame))
+{
+ m_xTopLevel->connect_focus_in(LINK(this, WeldToolbarPopup, FocusHdl));
+}
+
+WeldToolbarPopup::~WeldToolbarPopup()
+{
+ if (m_xStatusListener.is())
+ m_xStatusListener->dispose();
+}
+
+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());
+
+ // in online LoseFocus event is fired due to this line and popup is closed
+ // when first time opened any popup from not focused sidebar
+ if (!comphelper::LibreOfficeKit::isActive())
+ 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)
+ : DropdownDockingWindow(pParent, rFrame, bTearable)
+ , m_xFrame(rFrame)
+ , m_xBuilder(Application::CreateInterimBuilder(m_xBox.get(), "svt/ui/interimparent.ui", false))
+ , m_xContainer(m_xBuilder->weld_container("container"))
+ , m_xPopup(std::move(xPopup))
+{
+ if (SystemWindow* pWindow = GetTopMostParentSystemWindow(*this))
+ pWindow->GetTaskPaneList()->AddWindow(this);
+
+ // 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()
+{
+ DropdownDockingWindow::GetFocus();
+ if (!m_xPopup)
+ return;
+ m_xPopup->GrabFocus();
+}
+
+void InterimToolbarPopup::dispose()
+{
+ if (SystemWindow* pWindow = GetTopMostParentSystemWindow(*this))
+ pWindow->GetTaskPaneList()->RemoveWindow(this);
+
+ // 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 = m_xFrame->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_xBuilder.reset();
+ m_xFrame.clear();
+ DropdownDockingWindow::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 0000000000..2497d4513b
--- /dev/null
+++ b/svtools/source/control/valueacc.cxx
@@ -0,0 +1,967 @@
+/* -*- 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/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#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/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+
+using namespace ::com::sun::star;
+
+
+ValueSetItem::ValueSetItem( ValueSet& rParent )
+ : mrParent(rParent)
+ , mpData(nullptr)
+ , mxAcc()
+ , mnId(0)
+ , meType(VALUESETITEM_NONE)
+ , mbVisible(true)
+{
+}
+
+
+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;
+}
+
+ValueItemAcc::ValueItemAcc( ValueSetItem* pParent, bool bIsTransientChildrenDisabled ) :
+ mpParent( pParent ),
+ mbIsTransientChildrenDisabled( bIsTransientChildrenDisabled )
+{
+}
+
+ValueItemAcc::~ValueItemAcc()
+{
+}
+
+void ValueItemAcc::ParentDestroyed()
+{
+ std::scoped_lock aGuard( maMutex );
+ mpParent = nullptr;
+}
+
+ValueItemAcc* ValueItemAcc::getImplementation( const uno::Reference< uno::XInterface >& rxData )
+ noexcept
+{
+ return dynamic_cast<ValueItemAcc*>(rxData.get());
+}
+
+
+uno::Reference< accessibility::XAccessibleContext > SAL_CALL ValueItemAcc::getAccessibleContext()
+{
+ return this;
+}
+
+
+sal_Int64 SAL_CALL ValueItemAcc::getAccessibleChildCount()
+{
+ return 0;
+}
+
+
+uno::Reference< accessibility::XAccessible > SAL_CALL ValueItemAcc::getAccessibleChild( sal_Int64 )
+{
+ 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_Int64 SAL_CALL ValueItemAcc::getAccessibleIndexInParent()
+{
+ const SolarMutexGuard aSolarGuard;
+ // The index defaults to -1 to indicate the child does not belong to its
+ // parent.
+ sal_Int64 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 >();
+}
+
+
+sal_Int64 SAL_CALL ValueItemAcc::getAccessibleStateSet()
+{
+ const SolarMutexGuard aSolarGuard;
+ sal_Int64 nStateSet = 0;
+
+ if( mpParent )
+ {
+ nStateSet |= accessibility::AccessibleStateType::ENABLED;
+ nStateSet |= accessibility::AccessibleStateType::SENSITIVE;
+ nStateSet |= accessibility::AccessibleStateType::SHOWING;
+ nStateSet |= accessibility::AccessibleStateType::VISIBLE;
+ if ( !mbIsTransientChildrenDisabled )
+ nStateSet |= accessibility::AccessibleStateType::TRANSIENT;
+
+ nStateSet |= accessibility::AccessibleStateType::SELECTABLE;
+ nStateSet |= accessibility::AccessibleStateType::FOCUSABLE;
+
+ if( mpParent->mrParent.GetSelectedItemId() == mpParent->mnId )
+ {
+
+ nStateSet |= accessibility::AccessibleStateType::SELECTED;
+ if (mpParent->mrParent.HasChildFocus())
+ nStateSet |= accessibility::AccessibleStateType::FOCUSED;
+ }
+ }
+
+ return nStateSet;
+}
+
+
+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 )
+{
+ std::scoped_lock 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 )
+{
+ std::scoped_lock 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 ).Contains( 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_on_screen());
+
+ 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);
+}
+
+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 = getXWeak();
+ aEvtObject.NewValue = rNewValue;
+ aEvtObject.OldValue = rOldValue;
+
+ for (auto const& tmpListener : aTmpListeners)
+ {
+ tmpListener->notifyEvent( aEvtObject );
+ }
+}
+
+ValueSetAcc::ValueSetAcc( ValueSet* pParent ) :
+ 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 = getXWeak();
+ aEvtObject.NewValue = rNewValue;
+ aEvtObject.OldValue = rOldValue;
+ aEvtObject.IndexHint = -1;
+
+ for (auto const& tmpListener : aTmpListeners)
+ {
+ try
+ {
+ tmpListener->notifyEvent( aEvtObject );
+ }
+ catch(const uno::Exception&)
+ {
+ }
+ }
+}
+
+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()
+{
+ // still allow retrieving a11y context when not disposed yet, but ValueSet is unset
+ ThrowIfDisposed(false);
+ return this;
+}
+
+
+sal_Int64 SAL_CALL ValueSetAcc::getAccessibleChildCount()
+{
+ const SolarMutexGuard aSolarGuard;
+ ThrowIfDisposed();
+
+ sal_Int64 nCount = mpParent->ImplGetVisibleItemCount();
+ if (HasNoneField())
+ nCount += 1;
+ return nCount;
+}
+
+
+uno::Reference< accessibility::XAccessible > SAL_CALL ValueSetAcc::getAccessibleChild( sal_Int64 i )
+{
+ ThrowIfDisposed();
+ const SolarMutexGuard aSolarGuard;
+
+ if (i < 0 || i >= getAccessibleChildCount())
+ throw lang::IndexOutOfBoundsException();
+
+ 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_Int64 SAL_CALL ValueSetAcc::getAccessibleIndexInParent()
+{
+ ThrowIfDisposed();
+ const SolarMutexGuard aSolarGuard;
+
+ // -1 for child not found/no parent (according to specification)
+ sal_Int64 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_Int64 nChildCount = xParentContext->getAccessibleChildCount();
+ for ( sal_Int64 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", "ValueSetAcc::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();
+}
+
+sal_Int64 SAL_CALL ValueSetAcc::getAccessibleStateSet()
+{
+ ThrowIfDisposed();
+ sal_Int64 nStateSet = 0;
+
+ // Set some states.
+ nStateSet |= accessibility::AccessibleStateType::ENABLED;
+ nStateSet |= accessibility::AccessibleStateType::SENSITIVE;
+ nStateSet |= accessibility::AccessibleStateType::SHOWING;
+ nStateSet |= accessibility::AccessibleStateType::VISIBLE;
+ nStateSet |= accessibility::AccessibleStateType::MANAGES_DESCENDANTS;
+ nStateSet |= accessibility::AccessibleStateType::FOCUSABLE;
+ if (mbIsFocused)
+ nStateSet |= accessibility::AccessibleStateType::FOCUSED;
+
+ return nStateSet;
+}
+
+
+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(false);
+ std::unique_lock 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(false);
+ std::unique_lock 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 ).Contains( 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_Int64 nChildIndex )
+{
+ ThrowIfDisposed();
+ const SolarMutexGuard aSolarGuard;
+
+ if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount())
+ throw lang::IndexOutOfBoundsException();
+
+ 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_Int64 nChildIndex )
+{
+ ThrowIfDisposed();
+ const SolarMutexGuard aSolarGuard;
+
+ if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount())
+ throw lang::IndexOutOfBoundsException();
+
+ 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_Int64 SAL_CALL ValueSetAcc::getSelectedAccessibleChildCount()
+{
+ ThrowIfDisposed();
+ const SolarMutexGuard aSolarGuard;
+ sal_Int64 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_Int64 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_Int64 >( nSel++ ) ) )
+ xRet = pItem->GetAccessible( false/*bIsTransientChildrenDisabled*/ );
+ }
+
+ return xRet;
+}
+
+
+void SAL_CALL ValueSetAcc::deselectAccessibleChild( sal_Int64 nChildIndex )
+{
+ ThrowIfDisposed();
+ const SolarMutexGuard aSolarGuard;
+
+ if (nChildIndex < 0 || nChildIndex >= getAccessibleChildCount())
+ throw lang::IndexOutOfBoundsException();
+
+ // Because of the single selection we can reset the whole selection when
+ // the specified child is currently selected.
+ if (isAccessibleChildSelected(nChildIndex))
+ mpParent->SetNoSelection();
+}
+
+void ValueSetAcc::Invalidate()
+{
+ mpParent = nullptr;
+}
+
+void ValueSetAcc::disposing(std::unique_lock<std::mutex>& rGuard)
+{
+ // Make a copy of the list and clear the original.
+ ::std::vector<uno::Reference<accessibility::XAccessibleEventListener> > aListenerListCopy = std::move(mxEventListeners);
+
+ if (aListenerListCopy.empty())
+ return;
+
+ rGuard.unlock();
+ // 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(bool bCheckParent)
+{
+ if (m_bDisposed)
+ {
+ SAL_WARN("svx", "Calling disposed object. Throwing exception:");
+ throw lang::DisposedException (
+ "object has been already disposed",
+ getXWeak());
+ }
+
+ if (bCheckParent && !mpParent)
+ {
+ assert(false && "ValueSetAcc not disposed but mpParent == NULL");
+ throw css::uno::RuntimeException("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 0000000000..61e849cc1a
--- /dev/null
+++ b/svtools/source/control/valueimp.hxx
@@ -0,0 +1,245 @@
+/* -*- 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 <tools/color.hxx>
+#include <vcl/image.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/compbase.hxx>
+#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 <mutex>
+#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;
+ OUString maText;
+ void* mpData;
+ rtl::Reference< ValueItemAcc > mxAcc;
+ Image maImage;
+ Color maColor;
+ sal_uInt16 mnId;
+ sal_uInt8 meType;
+ bool mbVisible;
+
+ explicit ValueSetItem( ValueSet& rParent );
+ ~ValueSetItem();
+
+ css::uno::Reference< css::accessibility::XAccessible >
+ GetAccessible( bool bIsTransientChildrenDisabled );
+};
+
+typedef comphelper::WeakComponentImplHelper<
+ css::accessibility::XAccessible,
+ css::accessibility::XAccessibleEventBroadcaster,
+ css::accessibility::XAccessibleContext,
+ css::accessibility::XAccessibleComponent,
+ css::accessibility::XAccessibleSelection >
+ ValueSetAccComponentBase;
+
+class ValueSetAcc final : 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 ); }
+
+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();
+
+ /** Called by the corresponding ValueSet when it gets destroyed. */
+ void Invalidate();
+
+ // 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_Int64 SAL_CALL getAccessibleChildCount( ) override;
+ virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int64 i ) override;
+ virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override;
+ virtual sal_Int64 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 sal_Int64 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_Int64 nChildIndex ) override;
+ virtual sal_Bool SAL_CALL isAccessibleChildSelected( sal_Int64 nChildIndex ) override;
+ virtual void SAL_CALL clearAccessibleSelection( ) override;
+ virtual void SAL_CALL selectAllAccessibleChildren( ) override;
+ virtual sal_Int64 SAL_CALL getSelectedAccessibleChildCount( ) override;
+ virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getSelectedAccessibleChild( sal_Int64 nSelectedChildIndex ) override;
+ virtual void SAL_CALL deselectAccessibleChild( sal_Int64 nSelectedChildIndex ) 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 disposing(std::unique_lock<std::mutex>&) 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.
+ @param bCheckValueSet
+ Whether to also check that the ValueSet (parent)
+ is non-null.
+ @throws css::lang::DisposedException
+ */
+ void ThrowIfDisposed(bool bCheckParent = true);
+
+ /** 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 >
+{
+private:
+
+ ::std::vector< css::uno::Reference<
+ css::accessibility::XAccessibleEventListener > > mxEventListeners;
+ std::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 ) noexcept;
+
+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_Int64 SAL_CALL getAccessibleChildCount( ) override;
+ virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleChild( sal_Int64 i ) override;
+ virtual css::uno::Reference< css::accessibility::XAccessible > SAL_CALL getAccessibleParent( ) override;
+ virtual sal_Int64 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 sal_Int64 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;
+};
+
+
+/* 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 0000000000..87696d1a78
--- /dev/null
+++ b/svtools/source/control/valueset.cxx
@@ -0,0 +1,1982 @@
+/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <tools/debug.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/event.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.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 <uiobject.hxx>
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+
+using namespace css::uno;
+using namespace css::lang;
+using namespace css::accessibility;
+
+namespace
+{
+void collectUIInformation( const OUString& aID , const OUString& aParentID , const OUString& aPos )
+{
+ EventDescription aDescription;
+ aDescription.aID = aID ;
+ aDescription.aParameters = {{"POS", aPos }};
+ aDescription.aAction = "SELECT";
+ aDescription.aKeyWord = "ValueSet";
+ aDescription.aParent = aParentID;
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+
+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,
+};
+
+}
+
+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)
+{
+ 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;
+ mbDoubleSel = false;
+ mbScroll = false;
+ mbFullMode = true;
+ mbEdgeBlending = false;
+ mbHasVisibleItems = false;
+
+ if (mxScrolledWindow)
+ 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()
+{
+ if (mxAccessible)
+ mxAccessible->Invalidate();
+
+ 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()
+{
+ collectUIInformation(GetDrawingArea()->get_buildable_name() , GetDrawingArea()->get_help_id() , OUString::number(GetSelectedItemId()));
+ 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.Contains(rPos))
+ {
+ return VALUESET_ITEM_NONEITEM;
+ }
+
+ if (maItemListRect.Contains(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 )
+{
+ if( mxAccessible )
+ mxAccessible->FireAccessibleEvent( nEventId, rOldValue, rNewValue );
+}
+
+bool ValueSet::ImplHasAccessibleListeners() const
+{
+ return mxAccessible && mxAccessible->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&)
+{
+ rRenderContext.SetBackground(Application::GetSettings().GetStyleSettings().GetFaceColor());
+ rRenderContext.Erase();
+ ImplDraw(rRenderContext);
+}
+
+void ValueSet::GetFocus()
+{
+ SAL_INFO("svtools", "value set getting focus");
+ Invalidate();
+ CustomWidgetController::GetFocus();
+
+ // Tell the accessible object that we got the focus.
+ if (mxAccessible)
+ mxAccessible->GetFocus();
+}
+
+void ValueSet::LoseFocus()
+{
+ SAL_INFO("svtools", "value set losing focus");
+ Invalidate();
+ CustomWidgetController::LoseFocus();
+
+ // Tell the accessible object that we lost the focus.
+ if( mxAccessible )
+ mxAccessible->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)
+ {
+ // tdf#142479 on return select the entry the cursor is in
+ // before calling Select
+ if (nCurPos != VALUESET_ITEM_NONEITEM)
+ {
+ const sal_uInt16 nItemId = GetItemId(nCurPos);
+ if (nItemId != mnSelItemId)
+ SelectItem(nItemId);
+ }
+ 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(bool bLeaveWindow, const Point& rPos)
+{
+ ValueSetItem* pItem = bLeaveWindow ? nullptr : 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(0);
+ }
+}
+
+bool ValueSet::MouseButtonDown( const MouseEvent& rMouseEvent )
+{
+ if (rMouseEvent.IsLeft() && !rMouseEvent.IsMod2())
+ {
+ bool bConsumed = false;
+ ValueSetItem* pItem = ImplGetItem( ImplGetItem( rMouseEvent.GetPosPixel() ) );
+ if (rMouseEvent.GetClicks() == 1)
+ {
+ if (pItem)
+ SelectItem(pItem->mnId);
+ GrabFocus();
+ bConsumed = true;
+ }
+ else if (pItem && rMouseEvent.GetClicks() == 2)
+ {
+ maDoubleClickHdl.Call(this);
+ bConsumed = true;
+ }
+ return bConsumed;
+ }
+
+ return CustomWidgetController::MouseButtonDown( rMouseEvent );
+}
+
+bool ValueSet::MouseButtonUp( const MouseEvent& rMouseEvent )
+{
+ if (rMouseEvent.IsLeft() && !rMouseEvent.IsMod2())
+ {
+ // tdf#142150 MouseUp seen without previous MouseDown
+ if (mnSelItemId)
+ 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.IsLeaveWindow(), 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 tools::Long x = maItemListRect.Left()+col*(mnItemWidth+mnSpacing);
+ const tools::Long y = maItemListRect.Top()+row*(mnItemHeight+mnSpacing);
+
+ return tools::Rectangle( Point(x, y), Size(mnItemWidth, mnItemHeight) );
+}
+
+void ValueSet::ImplHighlightItem(sal_uInt16 nItemId)
+{
+ if ( mnHighItemId == nItemId )
+ return;
+
+ // remember the old item to delete the previous selection
+ mnHighItemId = nItemId;
+
+ // remove the old selection and draw the new one
+ Invalidate();
+}
+
+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 = Application::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 it's set to the later calculated mnFirstLine on
+ // the next Format
+ RecalcScrollBar();
+ }
+
+ // 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() )
+ return;
+
+ // 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(getXWeak(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(getXWeak(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();
+ tools::Long nTxtHeight = rRenderContext.GetTextHeight();
+ tools::Long nOff;
+ tools::Long nNoneHeight;
+ tools::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<tools::Long>(nItemCount) + mnCols - 1) / mnCols;
+ if (mnLines <= 0)
+ mnLines = 1;
+
+ bool bAdjustmentOutOfDate = nOldLines != mnLines;
+
+ auto nOldVisLines = mnVisLines;
+
+ tools::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 tools::Long nColSpace = (mnCols - 1) * static_cast<tools::Long>(mnSpacing);
+ const tools::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->SetBackground(Application::GetSettings().GetStyleSettings().GetFaceColor());
+ maVirDev->SetOutputSizePixel(aWinSize);
+ maVirDev->Erase();
+
+ // 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;
+
+ // 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
+ tools::Long nStartX;
+ tools::Long nStartY;
+ if (mbFullMode)
+ {
+ tools::Long nAllItemWidth = (mnItemWidth * mnCols) + nColSpace;
+ tools::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();
+ tools::Long x = nStartX;
+ tools::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
+ size_t nFirstItem = static_cast<size_t>(mnFirstLine) * mnCols;
+ size_t 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)
+ {
+ tools::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();
+
+ if (!bFocus && mbNoSelection && !mbHighlight)
+ return;
+
+ tools::Rectangle aSelectedRect, aHoverRect;
+ ValueSetItem* pSelectedItem = ImplGetDrawSelectItem(mnSelItemId, bFocus, aSelectedRect);
+ ValueSetItem* pHighlightItem = mnHighItemId ? ImplGetDrawSelectItem(mnHighItemId, false, aHoverRect) : nullptr;
+
+ if (pSelectedItem)
+ {
+ const bool bHover = pSelectedItem == pHighlightItem;
+ ImplDrawSelect(rRenderContext, aSelectedRect, pSelectedItem, bFocus, !mbNoSelection, true, bHover);
+ }
+ if (pHighlightItem && (pSelectedItem != pHighlightItem || mbNoSelection))
+ {
+ // For the case that there isn't a selected item, but due to wanting to
+ // show focus is in the valueset, the above block will have drawn the
+ // first item with a focus rect. For that situation; if the valueset is
+ // the thin WB_MENUSTYLEVALUESET case then blend this highlight border
+ // on top of that focus rect and it will appear with a highlighted
+ // focus rect. If it's the other case of a thicker border then redraw
+ // the focus rect highlighted with the hover color.
+ bool bDrawFocus;
+ WinBits nStyle = GetStyle();
+ if (nStyle & WB_MENUSTYLEVALUESET)
+ bDrawFocus = false;
+ else
+ bDrawFocus = pSelectedItem == pHighlightItem && mbNoSelection;
+
+ ImplDrawSelect(rRenderContext, aHoverRect, pHighlightItem, bDrawFocus, mbHighlight, false, true);
+ }
+}
+
+ValueSetItem* ValueSet::ImplGetDrawSelectItem(sal_uInt16 nItemId, const bool bFocus, tools::Rectangle& rRect)
+{
+ ValueSetItem* pItem = nullptr;
+ if (nItemId)
+ {
+ const size_t nPos = GetItemPos( nItemId );
+ pItem = mItemList[ nPos ].get();
+ rRect = ImplGetItemRect( nPos );
+ }
+ else if (mpNoneItem)
+ {
+ pItem = mpNoneItem.get();
+ rRect = maNoneItemRect;
+ }
+ else if (bFocus && (pItem = ImplGetFirstItem()))
+ {
+ rRect = ImplGetItemRect(0);
+ }
+ return pItem;
+}
+
+void ValueSet::ImplDrawSelect(vcl::RenderContext& rRenderContext,
+ const tools::Rectangle& rRect, const ValueSetItem* pItem,
+ const bool bFocus, const bool bDrawSel,
+ const bool bSelected, const bool bHover)
+{
+ tools::Rectangle aRect(rRect);
+
+ // draw selection
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ rRenderContext.SetFillColor();
+
+ Color aDoubleColor;
+ Color aSingleColor;
+
+ sal_uInt16 nTransparencePercent = 0;
+
+ if (bSelected && bHover)
+ {
+ aDoubleColor = rStyleSettings.GetActiveColor();
+ aSingleColor = rStyleSettings.GetActiveTextColor();
+ }
+ else if (bSelected || bHover)
+ {
+ aDoubleColor = rStyleSettings.GetHighlightColor();
+ aSingleColor = rStyleSettings.GetHighlightTextColor();
+ if (bHover)
+ {
+ nTransparencePercent = 55;
+ }
+ }
+
+ // specify selection output
+ WinBits nStyle = GetStyle();
+ if (nStyle & WB_MENUSTYLEVALUESET)
+ {
+ if (bFocus)
+ InvertFocusRect(rRenderContext, aRect);
+ if (bDrawSel)
+ {
+ rRenderContext.SetLineColor(aDoubleColor);
+ tools::PolyPolygon aPolyPoly(1);
+ aPolyPoly.Insert(tools::Polygon(aRect));
+ rRenderContext.DrawTransparent(aPolyPoly, nTransparencePercent);
+ }
+ }
+ else
+ {
+ rRenderContext.SetLineColor(aDoubleColor);
+ tools::Rectangle aFocusRect;
+
+ if (!mbDoubleSel)
+ {
+ // an outer rectangle surrounding a "focus" rectangle, surrounding
+ // an inner rectangle. Focus rectangle is always drawn, but rendered
+ // empty when there is no focus. e.g. as seen in color valuesets
+ if (bDrawSel)
+ {
+ tools::PolyPolygon aPolyPoly(1);
+ aPolyPoly.Insert(tools::Polygon(aRect));
+ rRenderContext.DrawTransparent(aPolyPoly, nTransparencePercent);
+ }
+
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustTop( 1 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustBottom( -1 );
+
+ aFocusRect = aRect;
+
+ aRect.AdjustLeft( 1 );
+ aRect.AdjustTop( 1 );
+ aRect.AdjustRight( -1 );
+ aRect.AdjustBottom( -1 );
+
+ if (bDrawSel)
+ {
+ tools::PolyPolygon aPolyPoly(1);
+ aPolyPoly.Insert(tools::Polygon(aRect));
+ rRenderContext.DrawTransparent(aPolyPoly, nTransparencePercent);
+ }
+
+ if (bDrawSel)
+ rRenderContext.SetLineColor(aSingleColor);
+ else
+ rRenderContext.SetLineColor(COL_LIGHTGRAY);
+
+ rRenderContext.DrawRect(aFocusRect);
+ }
+ else
+ {
+ // a thick bordered rectangle surrounding an optional "focus"
+ // rectangle which is only drawn when focused, as seen in format,
+ // bullets and numbering in writer
+ const int nAdjust = 2;
+
+ aRect.AdjustLeft(nAdjust);
+ aRect.AdjustTop(nAdjust);
+ aRect.AdjustRight(-nAdjust);
+ aRect.AdjustBottom(-nAdjust);
+
+ aFocusRect = aRect;
+
+ if (bDrawSel)
+ {
+ const basegfx::B2DPolygon aRectPoly(
+ basegfx::utils::createPolygonFromRect(
+ vcl::unotools::b2DRectangleFromRectangle(aRect)));
+
+ const int nThickness = nAdjust * 2;
+
+ if (!rRenderContext.DrawPolyLineDirect(basegfx::B2DHomMatrix(),
+ aRectPoly,
+ nThickness,
+ nTransparencePercent / 100.0,
+ nullptr,
+ basegfx::B2DLineJoin::Miter))
+ {
+ SAL_WARN("svtools", "presumably impossible in practice, but fallback to see something");
+ rRenderContext.DrawPolyLine(aRectPoly, nThickness, basegfx::B2DLineJoin::Miter);
+ }
+ }
+
+ if (bFocus)
+ {
+ if (bDrawSel)
+ rRenderContext.SetLineColor(aSingleColor);
+ else
+ rRenderContext.SetLineColor(COL_LIGHTGRAY);
+ rRenderContext.DrawRect(aFocusRect);
+ }
+ }
+
+ if (bFocus)
+ InvertFocusRect(rRenderContext, aFocusRect);
+ }
+
+ 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 = Application::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());
+ tools::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(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();
+
+ tools::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());
+ tools::Long nTxtWidth = rRenderContext.GetTextWidth(rText);
+ tools::Long nTxtOffset = mnTextOffset;
+
+ rRenderContext.Push(vcl::PushFlags::TEXTCOLOR);
+
+ // delete rectangle and show text
+ const bool bFlat(GetStyle() & WB_FLATVALUESET);
+ if (!bFlat)
+ nTxtOffset += NAME_LINE_HEIGHT+NAME_LINE_OFF_Y;
+
+ rRenderContext.SetTextColor(Application::GetSettings().GetStyleSettings().GetButtonTextColor());
+ // tdf#153787 highlighted entry text is drawn in the same Paint as the selected text, so can
+ // overwrite already rendered text
+ rRenderContext.Erase(tools::Rectangle(Point(0, nTxtOffset), Point(aWinSize.Width(), aWinSize.Height())));
+ rRenderContext.DrawText(Point((aWinSize.Width() - nTxtWidth) / 2, nTxtOffset + (NAME_OFFSET / 2)), rText);
+
+ rRenderContext.Pop();
+}
+
+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();
+ tools::Long nTxtHeight = GetTextHeight();
+ tools::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_scroll_thickness();
+ 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 )
+ {
+ tools::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( tools::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( tools::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();
+}
+
+FactoryFunction ValueSet::GetUITestFactory() const
+{
+ return ValueSetUIObject::create;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */