diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svtools/source/control | |
parent | Initial commit. (diff) | |
download | libreoffice-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.cxx | 354 | ||||
-rw-r--r-- | svtools/source/control/accessibleruler.hxx | 188 | ||||
-rw-r--r-- | svtools/source/control/asynclink.cxx | 71 | ||||
-rw-r--r-- | svtools/source/control/collatorres.cxx | 65 | ||||
-rw-r--r-- | svtools/source/control/ctrlbox.cxx | 1685 | ||||
-rw-r--r-- | svtools/source/control/ctrltool.cxx | 848 | ||||
-rw-r--r-- | svtools/source/control/indexentryres.cxx | 58 | ||||
-rw-r--r-- | svtools/source/control/inettbc.cxx | 1113 | ||||
-rw-r--r-- | svtools/source/control/ruler.cxx | 2778 | ||||
-rw-r--r-- | svtools/source/control/scriptedtext.cxx | 318 | ||||
-rw-r--r-- | svtools/source/control/scrolladaptor.cxx | 125 | ||||
-rw-r--r-- | svtools/source/control/tabbar.cxx | 2544 | ||||
-rw-r--r-- | svtools/source/control/toolbarmenu.cxx | 218 | ||||
-rw-r--r-- | svtools/source/control/valueacc.cxx | 967 | ||||
-rw-r--r-- | svtools/source/control/valueimp.hxx | 245 | ||||
-rw-r--r-- | svtools/source/control/valueset.cxx | 1982 |
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: */ |